/***************************************************************************** * * 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 #include #include // avif #include // std #include #include #include namespace mapnik { using avif_decoder_ptr = std::unique_ptr; using avif_image_ptr = std::unique_ptr; template 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_; 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> 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(filename); } image_reader* create_avif_reader2(char const* data, size_t size) { return new avif_reader(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 avif_reader::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(file_size); file.read(reinterpret_cast(buffer->data()), buffer->size()); if (!file) { throw image_reader_exception("AVIF: Failed to read:" + filename); } buffer_ = std::move(buffer); init(); } template avif_reader::avif_reader(char const* data, size_t size) : buffer_(new buffer_policy_type(reinterpret_cast(data), size)) { init(); } // dtor template avif_reader::~avif_reader() {} template void avif_reader::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 unsigned avif_reader::width() const { return width_; } template unsigned avif_reader::height() const { return height_; } template std::optional> avif_reader::bounding_box() const { return std::nullopt; } template void avif_reader::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 image_any avif_reader::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