diff --git a/src/celimage/avif.cpp b/src/celimage/avif.cpp index fa5138265a..f994acd5b9 100644 --- a/src/celimage/avif.cpp +++ b/src/celimage/avif.cpp @@ -8,8 +8,18 @@ // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. +#include +#include +#include +#include +#include +#include +#include + +#include #include #include +#include extern "C" { @@ -19,29 +29,97 @@ extern "C" namespace celestia::engine { -Image* LoadAVIFImage(const fs::path& filename) +namespace +{ + +using UniqueAVIFDecoder = util::UniquePtrDel; + +// We implement our own AVIF solution because libavif does not provide a way +// to open a file with a Windows-style wchar_t-based path. + +struct FStreamAVIFIO +{ + avifIO io; // First member to ensure pointer interconvertibility + std::ifstream file; + std::vector buffer; +}; + +avifResult +FStreamAVIFIORead(avifIO* io, std::uint32_t readFlags, std::uint64_t offset, std::size_t size, avifROData* out) { - avifDecoder* decoder = avifDecoderCreate(); - avifResult result = avifDecoderSetIOFile(decoder, filename.string().c_str()); - if (result != AVIF_RESULT_OK) + if (readFlags != 0) + return AVIF_RESULT_IO_ERROR; + + auto reader = reinterpret_cast(io); + if (offset > reader->io.sizeHint) + return AVIF_RESULT_IO_ERROR; + + if (std::uint64_t availableSize = reader->io.sizeHint - offset; size > availableSize) + size = static_cast(availableSize); + + if (size > std::numeric_limits::max()) + size = static_cast(std::numeric_limits::max()); + + if (size > 0) + { + if (offset > std::numeric_limits::max()) + return AVIF_RESULT_IO_ERROR; + + if (!reader->file.seekg(static_cast(offset), std::ios_base::beg)) + return AVIF_RESULT_IO_ERROR; + + reader->buffer.resize(size); + + if (!reader->file.read(reinterpret_cast(reader->buffer.data()), static_cast(size)) && + !reader->file.eof()) + { + return AVIF_RESULT_IO_ERROR; + } + + size = static_cast(reader->file.gcount()); + } + + out->data = reader->buffer.data(); + out->size = size; + + return AVIF_RESULT_OK; +} + +} // end unnamed namespace + +Image* +LoadAVIFImage(const fs::path& filename) +{ + UniqueAVIFDecoder decoder{ avifDecoderCreate() }; + if (!decoder) + return nullptr; + + FStreamAVIFIO reader; + reader.file = std::ifstream(filename, std::ios_base::in | std::ios_base::binary); + if (!reader.file) { util::GetLogger()->error("Cannot open file for read: '{}'\n", filename); - avifDecoderDestroy(decoder); return nullptr; } - result = avifDecoderParse(decoder); - if (result != AVIF_RESULT_OK) + reader.io.read = &FStreamAVIFIORead; + reader.io.write = nullptr; // unused + reader.io.destroy = nullptr; // automatic lifetime ends on scope exit + reader.io.sizeHint = static_cast(fs::file_size(filename)); + reader.io.persistent = AVIF_FALSE; + reader.io.data = nullptr; // unused + + avifDecoderSetIO(decoder.get(), reinterpret_cast(&reader)); + + if (avifResult result = avifDecoderParse(decoder.get()); result != AVIF_RESULT_OK) { util::GetLogger()->error("Failed to decode image: {}\n", avifResultToString(result)); - avifDecoderDestroy(decoder); return nullptr; } - if (avifDecoderNextImage(decoder) != AVIF_RESULT_OK) + if (avifDecoderNextImage(decoder.get()) != AVIF_RESULT_OK) { util::GetLogger()->error("No image available: {}\n", filename); - avifDecoderDestroy(decoder); return nullptr; } @@ -49,20 +127,17 @@ Image* LoadAVIFImage(const fs::path& filename) rgb.format = AVIF_RGB_FORMAT_RGBA; avifRGBImageSetDefaults(&rgb, decoder->image); - Image* image = new Image(PixelFormat::RGBA, rgb.width, rgb.height); + auto image = std::make_unique(PixelFormat::RGBA, rgb.width, rgb.height); rgb.pixels = image->getPixels(); rgb.rowBytes = image->getWidth() * image->getComponents(); if (avifImageYUVToRGB(decoder->image, &rgb) != AVIF_RESULT_OK) { util::GetLogger()->error("Conversion from YUV failed: {}\n", filename); - delete image; - avifDecoderDestroy(decoder); return nullptr; } - avifDecoderDestroy(decoder); - return image; + return image.release(); } } // namespace celestia::engine