Skip to content

Commit

Permalink
Use custom IO functions in AVIF loader
Browse files Browse the repository at this point in the history
- Write custom IO loader based on ifstream to avoid having to convert Windows filename to local character set
  • Loading branch information
ajtribick committed Oct 31, 2024
1 parent de0525b commit 1d1fc1b
Showing 1 changed file with 90 additions and 15 deletions.
105 changes: 90 additions & 15 deletions src/celimage/avif.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 <cstddef>
#include <cstdint>
#include <fstream>
#include <ios>
#include <limits>
#include <memory>
#include <vector>

#include <celcompat/filesystem.h>
#include <celimage/image.h>
#include <celutil/logger.h>
#include <celutil/uniquedel.h>

extern "C"
{
Expand All @@ -19,50 +29,115 @@ extern "C"
namespace celestia::engine
{

Image* LoadAVIFImage(const fs::path& filename)
namespace
{

using UniqueAVIFDecoder = util::UniquePtrDel<avifDecoder, avifDecoderDestroy>;

// 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<std::uint8_t> 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<FStreamAVIFIO*>(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<std::size_t>(availableSize);

if (size > std::numeric_limits<std::streamsize>::max())
size = static_cast<std::size_t>(std::numeric_limits<std::streamsize>::max());

if (size > 0)
{
if (offset > std::numeric_limits<std::streamoff>::max())
return AVIF_RESULT_IO_ERROR;

if (!reader->file.seekg(static_cast<std::streamoff>(offset), std::ios_base::beg))
return AVIF_RESULT_IO_ERROR;

reader->buffer.resize(size);

if (!reader->file.read(reinterpret_cast<char*>(reader->buffer.data()), static_cast<std::streamsize>(size)) &&
!reader->file.eof())
{
return AVIF_RESULT_IO_ERROR;
}

size = static_cast<std::size_t>(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<std::uint64_t>(fs::file_size(filename));
reader.io.persistent = AVIF_FALSE;
reader.io.data = nullptr; // unused

avifDecoderSetIO(decoder.get(), reinterpret_cast<avifIO*>(&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;
}

avifRGBImage rgb;
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<Image>(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

0 comments on commit 1d1fc1b

Please sign in to comment.