mapnik/src/avif_reader.cpp
2025-08-01 15:00:00 +01:00

249 lines
8.2 KiB
C++

/*****************************************************************************
*
* This file is part of Mapnik (c++ mapping toolkit)
*
* Copyright (C) 2025 Artem Pavlenko
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*****************************************************************************/
// mapnik
#include <mapnik/image_reader.hpp>
#include <mapnik/image_util.hpp>
#include <mapnik/color.hpp>
// avif
#include <avif/avif.h>
// std
#include <cstdio>
#include <memory>
#include <fstream>
namespace mapnik {
using avif_decoder_ptr = std::unique_ptr<avifDecoder, void (*)(avifDecoder*)>;
using avif_image_ptr = std::unique_ptr<avifImage, void (*)(avifImage*)>;
template<typename T>
class avif_reader : public image_reader
{
public:
using buffer_policy_type = T;
private:
avif_decoder_ptr decoder_{avifDecoderCreate(), [](avifDecoder* ptr) {
avifDecoderDestroy(ptr);
}};
std::unique_ptr<buffer_policy_type> buffer_;
size_t size_ = 0;
unsigned width_ = 0;
unsigned height_ = 0;
unsigned depth_ = 8;
bool has_alpha_ = false;
public:
explicit avif_reader(std::string const& filename);
explicit avif_reader(char const* data, size_t size);
~avif_reader();
unsigned width() const final;
unsigned height() const final;
std::optional<box2d<double>> bounding_box() const override final;
inline bool has_alpha() const final { return has_alpha_; }
void read(unsigned x, unsigned y, image_rgba8& image) final;
image_any read(unsigned x, unsigned y, unsigned width, unsigned height) final;
private:
void init();
};
image_reader* create_avif_reader(std::string const& filename)
{
return new avif_reader<internal_buffer_policy>(filename);
}
image_reader* create_avif_reader2(char const* data, size_t size)
{
return new avif_reader<external_buffer_policy>(data, size);
}
void register_avif_reader()
{
[[maybe_unused]] bool const registered = register_image_reader("avif", create_avif_reader);
[[maybe_unused]] bool const registered2 = register_image_reader("avif", create_avif_reader2);
}
// ctors
template<typename T>
avif_reader<T>::avif_reader(std::string const& filename)
: buffer_(nullptr)
{
std::ifstream file(filename.c_str(), std::ios::binary);
if (!file)
{
throw image_reader_exception("AVIF: Can't read file:" + filename);
}
std::streampos beg = file.tellg();
file.seekg(0, std::ios::end);
std::streampos end = file.tellg();
std::size_t file_size = end - beg;
file.seekg(0, std::ios::beg);
auto buffer = std::make_unique<buffer_policy_type>(file_size);
file.read(reinterpret_cast<char*>(buffer->data()), buffer->size());
if (!file)
{
throw image_reader_exception("AVIF: Failed to read:" + filename);
}
buffer_ = std::move(buffer);
init();
}
template<typename T>
avif_reader<T>::avif_reader(char const* data, size_t size)
: buffer_(new buffer_policy_type(reinterpret_cast<uint8_t const*>(data), size))
{
init();
}
// dtor
template<typename T>
avif_reader<T>::~avif_reader()
{}
template<typename T>
void avif_reader<T>::init()
{
avifResult result = avifDecoderSetIOMemory(decoder_.get(), buffer_->data(), buffer_->size());
if (result != AVIF_RESULT_OK)
{
throw image_reader_exception(std::string("AVIF Reader:") + avifResultToString(result));
}
result = avifDecoderParse(decoder_.get());
if (result != AVIF_RESULT_OK)
{
throw image_reader_exception(std::string("AVIF Reader:") + avifResultToString(result));
}
width_ = decoder_->image->width;
height_ = decoder_->image->height;
depth_ = decoder_->image->depth;
has_alpha_ = decoder_->alphaPresent;
}
template<typename T>
unsigned avif_reader<T>::width() const
{
return width_;
}
template<typename T>
unsigned avif_reader<T>::height() const
{
return height_;
}
template<typename T>
std::optional<box2d<double>> avif_reader<T>::bounding_box() const
{
return std::nullopt;
}
template<typename T>
void avif_reader<T>::read(unsigned x0, unsigned y0, image_rgba8& image)
{
bool cropped = x0 != 0 || y0 != 0 || image.width() != width_ || image.height() != height_;
avifRGBImage rgb;
std::memset(&rgb, 0, sizeof(rgb));
// read first image
if (avifDecoderNextImage(decoder_.get()) == AVIF_RESULT_OK)
{
if (!cropped)
{
avifRGBImageSetDefaults(&rgb, decoder_->image);
rgb.depth = 8;
std::uint32_t const pixelSize = avifRGBImagePixelSize(&rgb);
std::uint32_t const rowBytes = rgb.width * pixelSize;
rgb.pixels = image.bytes();
rgb.rowBytes = rowBytes;
avifResult result = avifImageYUVToRGB(decoder_->image, &rgb);
if (result != AVIF_RESULT_OK)
{
throw image_reader_exception(std::string("AVIF Reader:") + avifResultToString(result));
}
}
else
{
mapnik::avif_image_ptr crop{avifImageCreateEmpty(), [](avifImage* ptr) {
avifImageDestroy(ptr);
}};
avifPixelFormatInfo formatInfo;
avifGetPixelFormatInfo(decoder_->image->yuvFormat, &formatInfo);
std::uint32_t x_padding = x0 & formatInfo.chromaShiftX;
std::uint32_t y_padding = y0 & formatInfo.chromaShiftY;
std::uint32_t w = image.width();
std::uint32_t h = image.height();
auto r_x0 = x0 - x_padding;
auto r_y0 = y0 - y_padding;
auto r_width = std::min(decoder_->image->width, w + x_padding);
auto r_height = std::min(decoder_->image->height, h + y_padding);
avifCropRect rect = {r_x0, r_y0, r_width, r_height};
avifResult result = avifImageSetViewRect(crop.get(), decoder_->image, &rect);
if (result != AVIF_RESULT_OK)
{
image_reader_exception(std::string("AVIF Reader:") + avifResultToString(result));
}
avifRGBImageSetDefaults(&rgb, crop.get());
rgb.depth = 8;
std::uint32_t const pixelSize = avifRGBImagePixelSize(&rgb);
std::uint32_t const rowBytes = rgb.width * pixelSize;
if (x_padding == 0 && y_padding == 0)
{
rgb.pixels = image.bytes();
rgb.rowBytes = rowBytes;
result = avifImageYUVToRGB(crop.get(), &rgb);
if (result != AVIF_RESULT_OK)
{
image_reader_exception(std::string("AVIF Reader:") + avifResultToString(result));
}
}
else
{
mapnik::image_rgba8 padded_image(r_width, r_height, true, true);
rgb.pixels = padded_image.bytes();
rgb.rowBytes = rowBytes;
result = avifImageYUVToRGB(crop.get(), &rgb);
if (result != AVIF_RESULT_OK)
{
image_reader_exception(std::string("AVIF Reader:") + avifResultToString(result));
}
for (std::size_t row = 0; row < image.height(); ++row)
{
image.set_row(row, padded_image.get_row(row + y_padding) + x_padding, image.width());
}
}
}
}
}
template<typename T>
image_any avif_reader<T>::read(unsigned x, unsigned y, unsigned width, unsigned height)
{
image_rgba8 data(width, height, true, true);
read(x, y, data);
return image_any(std::move(data));
}
} // namespace mapnik