From 7e02ac82f0c9a9466d788c1a479473211ce92a7e Mon Sep 17 00:00:00 2001 From: Spartan322 Date: Mon, 20 Jan 2025 10:47:27 -0500 Subject: [PATCH] Add APNG import Add APNG file loading Add APNG image load test assertions Add APNG patch to libpng --- SConstruct | 1 + core/io/image_frames.cpp | 11 + core/io/image_frames.h | 4 + doc/classes/ImageFrames.xml | 11 +- drivers/png/image_frames_loader_png.cpp | 70 + drivers/png/image_frames_loader_png.h | 45 + drivers/png/png_driver_common.cpp | 310 ++++ drivers/png/png_driver_common.h | 4 + drivers/register_driver_types.cpp | 8 + tests/core/io/test_image_frames.h | 11 + tests/data/image_frames/icon.apng | Bin 0 -> 6528 bytes thirdparty/README.md | 4 + thirdparty/libpng/patches/0001-add-apng.patch | 1549 +++++++++++++++++ thirdparty/libpng/png.h | 92 + thirdparty/libpng/pngget.c | 162 ++ thirdparty/libpng/pnginfo.h | 13 + thirdparty/libpng/pngpread.c | 200 +++ thirdparty/libpng/pngpriv.h | 52 + thirdparty/libpng/pngread.c | 80 + thirdparty/libpng/pngrutil.c | 290 ++- thirdparty/libpng/pngset.c | 146 ++ thirdparty/libpng/pngstruct.h | 21 + thirdparty/libpng/pngwrite.c | 47 + thirdparty/libpng/pngwutil.c | 139 +- 24 files changed, 3266 insertions(+), 4 deletions(-) create mode 100644 drivers/png/image_frames_loader_png.cpp create mode 100644 drivers/png/image_frames_loader_png.h create mode 100644 tests/data/image_frames/icon.apng create mode 100644 thirdparty/libpng/patches/0001-add-apng.patch diff --git a/SConstruct b/SConstruct index ae3d554d17..f5dbee173b 100644 --- a/SConstruct +++ b/SConstruct @@ -873,6 +873,7 @@ if env.msvc and not methods.using_clang(env): # MSVC "/wd4514", # C4514 (unreferenced inline function has been removed) "/wd4714", # C4714 (function marked as __forceinline not inlined) "/wd4820", # C4820 (padding added after construct) + "/wd4611", # C4611 (interaction between '_setjmp' and C++ object destruction is non-portable): libpng uses setjmp use for error handling ] if env["warnings"] == "extra": diff --git a/core/io/image_frames.cpp b/core/io/image_frames.cpp index 5ed36ac411..51528ed8a8 100644 --- a/core/io/image_frames.cpp +++ b/core/io/image_frames.cpp @@ -125,6 +125,10 @@ Ref ImageFrames::load_from_file(const String &p_path) { return img_frames; } +Error ImageFrames::load_apng_from_buffer(const PackedByteArray &p_array, int p_max_frames) { + return _load_from_buffer(p_array, _apng_mem_loader_func, p_max_frames); +} + Error ImageFrames::load_gif_from_buffer(const PackedByteArray &p_array, int p_max_frames) { ERR_FAIL_NULL_V_MSG( _gif_mem_loader_func, @@ -133,6 +137,12 @@ Error ImageFrames::load_gif_from_buffer(const PackedByteArray &p_array, int p_ma return _load_from_buffer(p_array, _gif_mem_loader_func, p_max_frames); } +ImageFrames::ImageFrames(const uint8_t *p_mem_apng, int p_len) { + if (_apng_mem_loader_func) { + copy_internals_from(_apng_mem_loader_func(p_mem_apng, p_len, 0)); + } +} + ImageFrames::ImageFrames(const Vector> &p_images, float p_delay) { set_frame_count(p_images.size()); for (uint32_t index = 0; index < p_images.size(); index++) { @@ -169,6 +179,7 @@ void ImageFrames::_bind_methods() { ClassDB::bind_method(D_METHOD("load", "path"), &ImageFrames::load); ClassDB::bind_static_method("ImageFrames", D_METHOD("load_from_file", "path"), &ImageFrames::load_from_file); + ClassDB::bind_method(D_METHOD("load_apng_from_buffer", "buffer", "max_frames"), &ImageFrames::load_apng_from_buffer); ClassDB::bind_method(D_METHOD("load_gif_from_buffer", "buffer", "max_frames"), &ImageFrames::load_gif_from_buffer); ADD_PROPERTY(PropertyInfo(Variant::INT, "frame_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_frame_count", "get_frame_count"); diff --git a/core/io/image_frames.h b/core/io/image_frames.h index 34f6432b94..ec4f88f832 100644 --- a/core/io/image_frames.h +++ b/core/io/image_frames.h @@ -44,6 +44,7 @@ class ImageFrames : public Resource { public: static inline ImageFramesMemLoadFunc _gif_mem_loader_func = nullptr; + static inline ImageFramesMemLoadFunc _apng_mem_loader_func = nullptr; private: struct Frame { @@ -75,6 +76,7 @@ public: bool is_empty() const; ImageFrames() = default; // Create empty image frames. + ImageFrames(const uint8_t *p_mem_apng, int p_len); ImageFrames(const Vector> &p_images, float p_delay = 1.0); // Import images from an image vector and delay. ImageFrames(const Vector> &p_images, const Vector &p_delays); // Import images from an image vector and delay vector. @@ -82,6 +84,8 @@ public: Error load(const String &p_path); static Ref load_from_file(const String &p_path); + + Error load_apng_from_buffer(const PackedByteArray &p_array, int p_max_frames = 0); Error load_gif_from_buffer(const PackedByteArray &p_array, int p_max_frames = 0); void copy_internals_from(const Ref &p_frames) { diff --git a/doc/classes/ImageFrames.xml b/doc/classes/ImageFrames.xml index 2a41c7de17..7dfea6e749 100644 --- a/doc/classes/ImageFrames.xml +++ b/doc/classes/ImageFrames.xml @@ -6,7 +6,7 @@ A container of [Image]s used to load and arrange a sequence of frames. Each frame can specify a delay for animated images. Can be used to load animated image formats externally. - Supported animated image formats are [url=https://www.w3.org/Graphics/GIF/spec-gif89a.txt]GIF[/url] ([code].gif[/code]) and any format exposed via a GDExtension plugin. + Supported animated image formats are [url=https://www.w3.org/Graphics/GIF/spec-gif89a.txt]GIF[/url] ([code].gif[/code]), [url=https://wiki.mozilla.org/APNG_Specification]APNG[/url] ([code].png[/code] and [code].apng[/code]), and any format exposed via a GDExtension plugin. An [ImageTexture] is not meant to be operated from within the editor interface directly, and is mostly useful for rendering images on screen dynamically via code. If you need to generate images procedurally from within the editor, consider saving and importing images as custom texture resources implementing a new [EditorImportPlugin]. @@ -57,6 +57,15 @@ [/codeblock] + + + + + + Loads image frames from the binary contents of an APNG file. + [b]Note:[/b] This function will read standard PNG files just like [method Image.load_png_from_buffer]. If libpng is not compiled with support for reading APNG files, APNG files are treated as PNG files. + + diff --git a/drivers/png/image_frames_loader_png.cpp b/drivers/png/image_frames_loader_png.cpp new file mode 100644 index 0000000000..91c3e929ce --- /dev/null +++ b/drivers/png/image_frames_loader_png.cpp @@ -0,0 +1,70 @@ +/**************************************************************************/ +/* image_frames_loader_png.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* REDOT ENGINE */ +/* https://redotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2024-present Redot Engine contributors */ +/* (see REDOT_AUTHORS.md) */ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "image_frames_loader_png.h" + +#include "drivers/png/png_driver_common.h" + +Error ImageFramesLoaderPNG::load_image_frames(Ref p_image, Ref f, BitField p_flags, float p_scale, int p_max_frames) { + const uint64_t buffer_size = f->get_length(); + Vector file_buffer; + Error err = file_buffer.resize(buffer_size); + if (err) { + return err; + } + { + uint8_t *writer = file_buffer.ptrw(); + f->get_buffer(writer, buffer_size); + } + const uint8_t *reader = file_buffer.ptr(); + return PNGDriverCommon::apng_to_image_frames(reader, buffer_size, p_flags & FLAG_FORCE_LINEAR, p_max_frames, p_image); +} + +void ImageFramesLoaderPNG::get_recognized_extensions(List *p_extensions) const { + p_extensions->push_back("png"); + p_extensions->push_back("apng"); +} + +Ref ImageFramesLoaderPNG::load_mem_apng(const uint8_t *p_png, int p_size, int p_max_frames) { + Ref img_frames; + img_frames.instantiate(); + + // the value of p_force_linear does not matter since it only applies to 16 bit + Error err = PNGDriverCommon::apng_to_image_frames(p_png, p_size, false, p_max_frames, img_frames); + ERR_FAIL_COND_V(err, Ref()); + + return img_frames; +} + +ImageFramesLoaderPNG::ImageFramesLoaderPNG() { + ImageFrames::_apng_mem_loader_func = load_mem_apng; +} diff --git a/drivers/png/image_frames_loader_png.h b/drivers/png/image_frames_loader_png.h new file mode 100644 index 0000000000..59f68f5bfc --- /dev/null +++ b/drivers/png/image_frames_loader_png.h @@ -0,0 +1,45 @@ +/**************************************************************************/ +/* image_frames_loader_png.h */ +/**************************************************************************/ +/* This file is part of: */ +/* REDOT ENGINE */ +/* https://redotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2024-present Redot Engine contributors */ +/* (see REDOT_AUTHORS.md) */ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#pragma once + +#include "core/io/image_frames_loader.h" + +class ImageFramesLoaderPNG : public ImageFramesFormatLoader { +private: + static Ref load_mem_apng(const uint8_t *p_png, int p_size, int p_max_frames); + +public: + virtual Error load_image_frames(Ref p_image, Ref f, BitField p_flags, float p_scale = 1.0, int p_max_frames = 0); + virtual void get_recognized_extensions(List *p_extensions) const; + ImageFramesLoaderPNG(); +}; diff --git a/drivers/png/png_driver_common.cpp b/drivers/png/png_driver_common.cpp index 2b43ae26a9..6c04e68f9d 100644 --- a/drivers/png/png_driver_common.cpp +++ b/drivers/png/png_driver_common.cpp @@ -204,4 +204,314 @@ Error image_to_png(const Ref &p_image, Vector &p_buffer) { return OK; } + +/// APNG functions + +static void apng_error_func(png_struct *p_struct, const char *p_message) { + ERR_PRINT(p_message); +} + +static void apng_warn_func(png_struct *p_struct, const char *p_message) { + WARN_PRINT(p_message); +} + +struct APngBuffer { + uint8_t *data; + size_t size; + int index; +}; + +static void apng_read_buffer(png_struct *p_struct, png_byte *p_data, size_t p_length) { + APngBuffer *png_data = static_cast(png_get_io_ptr(p_struct)); + if (png_data->index + p_length > png_data->size) { + p_length = png_data->size - png_data->index; + } + + memcpy(p_data, &png_data->data[png_data->index], p_length); + png_data->index += p_length; +} + +Error apng_to_image_frames(const uint8_t *p_source, size_t p_size, bool p_force_linear, uint32_t p_frame_limit, Ref p_frames) { +#ifdef PNG_READ_APNG_SUPPORTED + struct Frame { + Vector buffer; + uint32_t width; + uint32_t height; + uint32_t offset_x; + uint32_t offset_y; + float delay; + uint8_t dispose_op; + uint8_t blend_op; + }; + + png_image png_img; + + memset(&png_img, 0, sizeof(png_img)); + png_img.version = PNG_IMAGE_VERSION; + png_struct *struct_ = png_create_read_struct(PNG_LIBPNG_VER_STRING, &png_img, &apng_error_func, &apng_warn_func); + png_info *info = nullptr; + + if (struct_ == nullptr) { + png_destroy_read_struct(&struct_, nullptr, nullptr); + png_image_free(&png_img); + } else { + info = png_create_info_struct(struct_); + if (info == nullptr) { + png_destroy_read_struct(&struct_, nullptr, nullptr); + png_image_free(&png_img); + } + } + + ERR_FAIL_COND_V_MSG(struct_ == nullptr, ERR_FILE_CORRUPT, "Couldn't create APNG structure."); + ERR_FAIL_COND_V_MSG(info == nullptr, ERR_FILE_CORRUPT, "Couldn't create APNG info structure."); + + if (setjmp(png_jmpbuf(struct_))) { + png_destroy_read_struct(&struct_, &info, nullptr); + ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, "Couldn't load APNG."); + } + + APngBuffer read_buffer_obj = { const_cast(p_source), p_size, 0 }; + + png_set_read_fn(struct_, &read_buffer_obj, apng_read_buffer); + png_read_info(struct_, info); + + png_byte bit_depth = png_get_bit_depth(struct_, info); + if (bit_depth == 16) { + png_set_scale_16(struct_); + } + + const int8_t NO_TRANSPARENCY = -1; + + switch (png_get_color_type(struct_, info)) { + case PNG_COLOR_TYPE_GRAY: + case PNG_COLOR_TYPE_GRAY_ALPHA: + if (bit_depth < 8) { + png_set_expand_gray_1_2_4_to_8(struct_); + } + break; + case PNG_COLOR_TYPE_PALETTE: + png_set_palette_to_rgb(struct_); + break; + case PNG_COLOR_TYPE_RGB: + case PNG_COLOR_TYPE_RGB_ALPHA: + break; + default: + ERR_PRINT("Unsupported png format."); + return ERR_UNAVAILABLE; + } + + png_read_update_info(struct_, info); + + Image::Format dest_format; + int8_t alpha_component_index = NO_TRANSPARENCY; + switch (png_get_color_type(struct_, info)) { + case PNG_COLOR_TYPE_GRAY: + dest_format = Image::FORMAT_L8; + break; + case PNG_COLOR_TYPE_GRAY_ALPHA: + dest_format = Image::FORMAT_LA8; + alpha_component_index = 1; + break; + case PNG_COLOR_TYPE_RGB: + dest_format = Image::FORMAT_RGB8; + break; + case PNG_COLOR_TYPE_RGB_ALPHA: + dest_format = Image::FORMAT_RGBA8; + alpha_component_index = 3; + break; + default: + ERR_PRINT("Unsupported png format."); + return ERR_UNAVAILABLE; + } + + uint8_t pixel_size = Image::get_format_pixel_size(dest_format); + + // PNG properties + uint32_t width = png_get_image_width(struct_, info); + uint32_t height = png_get_image_height(struct_, info); + + // APNG properties + bool is_animated = png_get_valid(struct_, info, PNG_INFO_acTL); + uint32_t frame_count = is_animated ? png_get_num_frames(struct_, info) : 1; + uint32_t loop_count = png_get_num_plays(struct_, info); + bool is_first_frame_hidden = is_animated ? png_get_first_frame_is_hidden(struct_, info) : false; + + auto read_image = [pixel_size, struct_](Vector &p_buffer, uint32_t p_width, uint32_t p_height) { + LocalVector line_buffer; + line_buffer.resize(p_height); + for (uint32_t y = 0; y < p_height; y++) { + line_buffer[y] = p_buffer.ptrw() + (p_width * y * pixel_size); + } + png_read_image(struct_, line_buffer.ptr()); + }; + + Vector screen; + screen.resize_zeroed(width * height * pixel_size); + if (is_animated) { + // Skip first frame + if (is_first_frame_hidden) { + frame_count--; + read_image(screen, width, height); + } + + frame_count = p_frame_limit > 0 ? MIN(frame_count, p_frame_limit) : frame_count; + + p_frames->set_frame_count(frame_count); + p_frames->set_loop_count(loop_count); + + auto read_frame = [pixel_size, struct_, info, &read_image]() -> Frame { + Frame frame{}; + + uint16_t delay_num; + uint16_t delay_den; + + png_read_frame_head(struct_, info); + if (png_get_next_frame_fcTL(struct_, info, + &frame.width, + &frame.height, + &frame.offset_x, + &frame.offset_y, + &delay_num, + &delay_den, + &frame.dispose_op, + &frame.blend_op) == 0) { + return frame; + } + + frame.delay = float(delay_num) / float(delay_den == 0 ? 100.0 : delay_den); + + frame.buffer.resize_zeroed(frame.width * frame.height * pixel_size); + read_image(frame.buffer, frame.width, frame.height); + return frame; + }; + + // Read initial frame + Frame previous_frame{}; + Frame current_frame = read_frame(); + + ERR_FAIL_COND_V_MSG(current_frame.buffer.is_empty(), ERR_FILE_CORRUPT, "Couldn't read APNG initial frame."); + + if (current_frame.dispose_op == PNG_DISPOSE_OP_PREVIOUS) { + current_frame.dispose_op = PNG_DISPOSE_OP_BACKGROUND; + memset(screen.ptrw(), 0, screen.size()); + } + + Vector backup_buffer; + for (uint32_t current_frame_index = 0; current_frame_index < frame_count; current_frame_index++) { + if (current_frame_index != 0) { + previous_frame = std::move(current_frame); + current_frame = read_frame(); + } + + ERR_FAIL_COND_V_MSG(current_frame.buffer.is_empty(), ERR_FILE_CORRUPT, "Couldn't read APNG frame."); + + // Optimize padding frames + if (current_frame_index != 0 && current_frame.blend_op == PNG_BLEND_OP_OVER && current_frame.buffer.size() == pixel_size && current_frame.buffer.count(0) == current_frame.buffer.size()) { + frame_count--; + current_frame_index--; + p_frames->set_frame_count(frame_count); + p_frames->set_frame_delay(current_frame_index, p_frames->get_frame_delay(current_frame_index) + current_frame.delay); + continue; + } + + if (current_frame_index != 0 && current_frame.dispose_op == PNG_DISPOSE_OP_PREVIOUS && previous_frame.dispose_op == PNG_DISPOSE_OP_PREVIOUS) { + if (backup_buffer.is_empty()) { + backup_buffer.resize(screen.size()); + memcpy(backup_buffer.ptrw(), screen.ptr(), backup_buffer.size()); + } else { + SWAP(screen, backup_buffer); + } + } else { + if (current_frame.dispose_op == PNG_DISPOSE_OP_PREVIOUS) { + if (backup_buffer.is_empty()) { + backup_buffer.resize(screen.size()); + } + memcpy(backup_buffer.ptrw(), screen.ptr(), backup_buffer.size()); + } + + if (current_frame_index != 0) { + // Prepare from previous frame + switch (previous_frame.dispose_op) { + case PNG_DISPOSE_OP_NONE: + break; + case PNG_DISPOSE_OP_PREVIOUS: + ERR_FAIL_COND_V_MSG(backup_buffer.is_empty(), ERR_FILE_CORRUPT, "Bug: Error in APNG frame processing logic, please report."); + memcpy(screen.ptrw(), backup_buffer.ptr(), screen.size()); + break; + default: + memset(screen.ptrw(), 0, screen.size()); + break; + } + } + } + + uint32_t copy_width = MIN(width - current_frame.offset_x, current_frame.width); + uint32_t copy_height = MIN(height - current_frame.offset_y, current_frame.height); + size_t length = copy_width * pixel_size; + + for (uint32_t y = 0; y < copy_height; y++) { + const uint8_t *src = ¤t_frame.buffer[y * current_frame.width * pixel_size]; + uint8_t *dest = &screen.write[((current_frame.offset_y + y) * width + current_frame.offset_x) * pixel_size]; + + // alpha_index == NO_TRANSPARENCY means there is no alpha component, treat as opaque + if (alpha_component_index == NO_TRANSPARENCY || current_frame.blend_op != PNG_BLEND_OP_OVER) { + memcpy(dest, src, length); + } else { + // Blend frames + for (size_t index = 0; index < length; index += pixel_size, src += pixel_size, dest += pixel_size) { + if (src[alpha_component_index] == 255) { + memcpy(dest, src, pixel_size); + continue; + } + + if (src[alpha_component_index] != 0) { + if (dest[alpha_component_index] != 0) { + int u = src[alpha_component_index] * 255; + int v = (255 - src[alpha_component_index]) * dest[alpha_component_index]; + int a1 = u + v; + for (int color_index = 0; color_index < pixel_size; color_index++) { + if (color_index == alpha_component_index) { + continue; + } + dest[color_index] = (src[color_index] * u + dest[color_index] * v) / a1; + } + dest[alpha_component_index] = a1 / 255; + } else { + memcpy(dest, src, pixel_size); + } + } + } + } + } + + Ref image = memnew(Image(width, height, false, dest_format, screen)); + p_frames->set_frame_image(current_frame_index, image); + p_frames->set_frame_delay(current_frame_index, current_frame.delay); + } + + png_read_end(struct_, info); + } else { + p_frames->set_frame_count(1); + + read_image(screen, width, height); + + Ref image = memnew(Image(width, height, false, dest_format, screen)); + p_frames->set_frame_image(0, image); + + png_read_end(struct_, info); + } + + png_destroy_read_struct(&struct_, &info, nullptr); + + return OK; +#else + WARN_PRINT("Reading APNG files is disabled, reading APNG as PNG instead. Compile with builtin_png=yes."); + Ref image; + image.instantiate(); + png_to_image(p_source, p_size, p_force_linear, image); + p_frames->set_frame_count(1); + p_frames->set_frame_image(0, image); + return OK; +#endif // PNG_READ_APNG_SUPPORTED +} } // namespace PNGDriverCommon diff --git a/drivers/png/png_driver_common.h b/drivers/png/png_driver_common.h index e22d385296..403ed9adba 100644 --- a/drivers/png/png_driver_common.h +++ b/drivers/png/png_driver_common.h @@ -33,6 +33,7 @@ #pragma once #include "core/io/image.h" +#include "core/io/image_frames.h" namespace PNGDriverCommon { @@ -42,4 +43,7 @@ Error png_to_image(const uint8_t *p_source, size_t p_size, bool p_force_linear, // Append p_image, as a png, to p_buffer. // Contents of p_buffer is unspecified if error returned. Error image_to_png(const Ref &p_image, Vector &p_buffer); + +// Attempt to load apng from buffer (p_source, p_size) into p_frames +Error apng_to_image_frames(const uint8_t *p_source, size_t p_size, bool p_force_linear, uint32_t p_frame_limit, Ref p_frames); } // namespace PNGDriverCommon diff --git a/drivers/register_driver_types.cpp b/drivers/register_driver_types.cpp index 619c0ec027..ecc33202be 100644 --- a/drivers/register_driver_types.cpp +++ b/drivers/register_driver_types.cpp @@ -32,16 +32,21 @@ #include "register_driver_types.h" +#include "drivers/png/image_frames_loader_png.h" #include "drivers/png/image_loader_png.h" #include "drivers/png/resource_saver_png.h" static Ref image_loader_png; +static Ref image_frames_loader_png; static Ref resource_saver_png; void register_core_driver_types() { image_loader_png.instantiate(); ImageLoader::add_image_format_loader(image_loader_png); + image_frames_loader_png.instantiate(); + ImageFramesLoader::add_image_frames_format_loader(image_frames_loader_png); + resource_saver_png.instantiate(); ResourceSaver::add_resource_format_saver(resource_saver_png); } @@ -50,6 +55,9 @@ void unregister_core_driver_types() { ImageLoader::remove_image_format_loader(image_loader_png); image_loader_png.unref(); + ImageFramesLoader::remove_image_frames_format_loader(image_frames_loader_png); + image_frames_loader_png.unref(); + ResourceSaver::remove_resource_format_saver(resource_saver_png); resource_saver_png.unref(); } diff --git a/tests/core/io/test_image_frames.h b/tests/core/io/test_image_frames.h index b67df4383c..23765b4c65 100644 --- a/tests/core/io/test_image_frames.h +++ b/tests/core/io/test_image_frames.h @@ -98,6 +98,17 @@ TEST_CASE("[ImageFrames] Loading") { image_frames_gif->load_gif_from_buffer(data_gif) == OK, "The GIF image frame should load successfully."); #endif + + // Load APNG + Ref image_frames_apng = memnew(ImageFrames()); + Ref f_apng = FileAccess::open(TestUtils::get_data_path("image_frames/icon.apng"), FileAccess::READ, &err); + REQUIRE(f_apng.is_valid()); + PackedByteArray data_apng; + data_apng.resize(f_apng->get_length() + 1); + f_apng->get_buffer(data_apng.ptrw(), f_apng->get_length()); + CHECK_MESSAGE( + image_frames_apng->load_apng_from_buffer(data_apng) == OK, + "The APNG image frame should load successfully."); } TEST_CASE("[ImageFrames] Basic getters") { diff --git a/tests/data/image_frames/icon.apng b/tests/data/image_frames/icon.apng new file mode 100644 index 0000000000000000000000000000000000000000..c4c694d62538a3fa0eb68640257aea3b8fd06d3d GIT binary patch literal 6528 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 & PNG_INFO_acTL) && ++ num_frames != NULL && num_plays != NULL) ++ { ++ *num_frames = info_ptr->num_frames; ++ *num_plays = info_ptr->num_plays; ++ return (1); ++ } ++ ++ return (0); ++} ++ ++png_uint_32 PNGAPI ++png_get_num_frames(png_structp png_ptr, png_infop info_ptr) ++{ ++ png_debug(1, "in png_get_num_frames()"); ++ ++ if (png_ptr != NULL && info_ptr != NULL) ++ return (info_ptr->num_frames); ++ return (0); ++} ++ ++png_uint_32 PNGAPI ++png_get_num_plays(png_structp png_ptr, png_infop info_ptr) ++{ ++ png_debug(1, "in png_get_num_plays()"); ++ ++ if (png_ptr != NULL && info_ptr != NULL) ++ return (info_ptr->num_plays); ++ return (0); ++} ++ ++png_uint_32 PNGAPI ++png_get_next_frame_fcTL(png_structp png_ptr, png_infop info_ptr, ++ png_uint_32 *width, png_uint_32 *height, ++ png_uint_32 *x_offset, png_uint_32 *y_offset, ++ png_uint_16 *delay_num, png_uint_16 *delay_den, ++ png_byte *dispose_op, png_byte *blend_op) ++{ ++ png_debug1(1, "in %s retrieval function", "fcTL"); ++ ++ if (png_ptr != NULL && info_ptr != NULL && ++ (info_ptr->valid & PNG_INFO_fcTL) && ++ width != NULL && height != NULL && ++ x_offset != NULL && y_offset != NULL && ++ delay_num != NULL && delay_den != NULL && ++ dispose_op != NULL && blend_op != NULL) ++ { ++ *width = info_ptr->next_frame_width; ++ *height = info_ptr->next_frame_height; ++ *x_offset = info_ptr->next_frame_x_offset; ++ *y_offset = info_ptr->next_frame_y_offset; ++ *delay_num = info_ptr->next_frame_delay_num; ++ *delay_den = info_ptr->next_frame_delay_den; ++ *dispose_op = info_ptr->next_frame_dispose_op; ++ *blend_op = info_ptr->next_frame_blend_op; ++ return (1); ++ } ++ ++ return (0); ++} ++ ++png_uint_32 PNGAPI ++png_get_next_frame_width(png_structp png_ptr, png_infop info_ptr) ++{ ++ png_debug(1, "in png_get_next_frame_width()"); ++ ++ if (png_ptr != NULL && info_ptr != NULL) ++ return (info_ptr->next_frame_width); ++ return (0); ++} ++ ++png_uint_32 PNGAPI ++png_get_next_frame_height(png_structp png_ptr, png_infop info_ptr) ++{ ++ png_debug(1, "in png_get_next_frame_height()"); ++ ++ if (png_ptr != NULL && info_ptr != NULL) ++ return (info_ptr->next_frame_height); ++ return (0); ++} ++ ++png_uint_32 PNGAPI ++png_get_next_frame_x_offset(png_structp png_ptr, png_infop info_ptr) ++{ ++ png_debug(1, "in png_get_next_frame_x_offset()"); ++ ++ if (png_ptr != NULL && info_ptr != NULL) ++ return (info_ptr->next_frame_x_offset); ++ return (0); ++} ++ ++png_uint_32 PNGAPI ++png_get_next_frame_y_offset(png_structp png_ptr, png_infop info_ptr) ++{ ++ png_debug(1, "in png_get_next_frame_y_offset()"); ++ ++ if (png_ptr != NULL && info_ptr != NULL) ++ return (info_ptr->next_frame_y_offset); ++ return (0); ++} ++ ++png_uint_16 PNGAPI ++png_get_next_frame_delay_num(png_structp png_ptr, png_infop info_ptr) ++{ ++ png_debug(1, "in png_get_next_frame_delay_num()"); ++ ++ if (png_ptr != NULL && info_ptr != NULL) ++ return (info_ptr->next_frame_delay_num); ++ return (0); ++} ++ ++png_uint_16 PNGAPI ++png_get_next_frame_delay_den(png_structp png_ptr, png_infop info_ptr) ++{ ++ png_debug(1, "in png_get_next_frame_delay_den()"); ++ ++ if (png_ptr != NULL && info_ptr != NULL) ++ return (info_ptr->next_frame_delay_den); ++ return (0); ++} ++ ++png_byte PNGAPI ++png_get_next_frame_dispose_op(png_structp png_ptr, png_infop info_ptr) ++{ ++ png_debug(1, "in png_get_next_frame_dispose_op()"); ++ ++ if (png_ptr != NULL && info_ptr != NULL) ++ return (info_ptr->next_frame_dispose_op); ++ return (0); ++} ++ ++png_byte PNGAPI ++png_get_next_frame_blend_op(png_structp png_ptr, png_infop info_ptr) ++{ ++ png_debug(1, "in png_get_next_frame_blend_op()"); ++ ++ if (png_ptr != NULL && info_ptr != NULL) ++ return (info_ptr->next_frame_blend_op); ++ return (0); ++} ++ ++png_byte PNGAPI ++png_get_first_frame_is_hidden(png_structp png_ptr, png_infop info_ptr) ++{ ++ png_debug(1, "in png_first_frame_is_hidden()"); ++ ++ if (png_ptr != NULL) ++ return (png_byte)(png_ptr->apng_flags & PNG_FIRST_FRAME_HIDDEN); ++ ++ PNG_UNUSED(info_ptr) ++ ++ return 0; ++} ++#endif /* PNG_APNG_SUPPORTED */ + #endif /* READ || WRITE */ +diff --git a/thirdparty/libpng/pnginfo.h b/thirdparty/libpng/pnginfo.h +index c2a907bc58..61d6d378ea 100644 +--- a/thirdparty/libpng/pnginfo.h ++++ b/thirdparty/libpng/pnginfo.h +@@ -282,5 +282,18 @@ defined(PNG_READ_BACKGROUND_SUPPORTED) + #ifdef PNG_sRGB_SUPPORTED + int rendering_intent; + #endif ++ ++#ifdef PNG_APNG_SUPPORTED ++ png_uint_32 num_frames; /* including default image */ ++ png_uint_32 num_plays; ++ png_uint_32 next_frame_width; ++ png_uint_32 next_frame_height; ++ png_uint_32 next_frame_x_offset; ++ png_uint_32 next_frame_y_offset; ++ png_uint_16 next_frame_delay_num; ++ png_uint_16 next_frame_delay_den; ++ png_byte next_frame_dispose_op; ++ png_byte next_frame_blend_op; ++#endif + }; + #endif /* PNGINFO_H */ +diff --git a/thirdparty/libpng/pngpread.c b/thirdparty/libpng/pngpread.c +index 60d810693b..323320dac7 100644 +--- a/thirdparty/libpng/pngpread.c ++++ b/thirdparty/libpng/pngpread.c +@@ -200,6 +200,106 @@ png_push_read_chunk(png_structrp png_ptr, png_inforp info_ptr) + + chunk_name = png_ptr->chunk_name; + ++#ifdef PNG_READ_APNG_SUPPORTED ++ if (png_ptr->num_frames_read > 0 && ++ png_ptr->num_frames_read < info_ptr->num_frames) ++ { ++ if (chunk_name == png_IDAT) ++ { ++ /* Discard trailing IDATs for the first frame */ ++ if (png_ptr->mode & PNG_HAVE_fcTL || png_ptr->num_frames_read > 1) ++ png_error(png_ptr, "out of place IDAT"); ++ ++ if (png_ptr->push_length + 4 > png_ptr->buffer_size) ++ { ++ png_push_save_buffer(png_ptr); ++ return; ++ } ++ ++ png_ptr->mode &= ~PNG_HAVE_CHUNK_HEADER; ++ return; ++ } ++ else if (chunk_name == png_fdAT) ++ { ++ if (png_ptr->buffer_size < 4) ++ { ++ png_push_save_buffer(png_ptr); ++ return; ++ } ++ ++ png_ensure_sequence_number(png_ptr, 4); ++ ++ if (!(png_ptr->mode & PNG_HAVE_fcTL)) ++ { ++ /* Discard trailing fdATs for frames other than the first */ ++ if (png_ptr->num_frames_read < 2) ++ png_error(png_ptr, "out of place fdAT"); ++ ++ if (png_ptr->push_length + 4 > png_ptr->buffer_size) ++ { ++ png_push_save_buffer(png_ptr); ++ return; ++ } ++ ++ png_ptr->mode &= ~PNG_HAVE_CHUNK_HEADER; ++ return; ++ } ++ ++ else ++ { ++ /* frame data follows */ ++ png_ptr->idat_size = png_ptr->push_length - 4; ++ png_ptr->mode |= PNG_HAVE_IDAT; ++ png_ptr->process_mode = PNG_READ_IDAT_MODE; ++ ++ return; ++ } ++ } ++ ++ else if (chunk_name == png_fcTL) ++ { ++ if (png_ptr->push_length + 4 > png_ptr->buffer_size) ++ { ++ png_push_save_buffer(png_ptr); ++ return; ++ } ++ ++ png_read_reset(png_ptr); ++ png_ptr->mode &= ~PNG_HAVE_fcTL; ++ ++ png_handle_fcTL(png_ptr, info_ptr, png_ptr->push_length); ++ ++ if (!(png_ptr->mode & PNG_HAVE_fcTL)) ++ png_error(png_ptr, "missing required fcTL chunk"); ++ ++ png_read_reinit(png_ptr, info_ptr); ++ png_progressive_read_reset(png_ptr); ++ ++ if (png_ptr->frame_info_fn != NULL) ++ (*(png_ptr->frame_info_fn))(png_ptr, png_ptr->num_frames_read); ++ ++ png_ptr->mode &= ~PNG_HAVE_CHUNK_HEADER; ++ ++ return; ++ } ++ ++ else ++ { ++ if (png_ptr->push_length + 4 > png_ptr->buffer_size) ++ { ++ png_push_save_buffer(png_ptr); ++ return; ++ } ++ png_warning(png_ptr, "Skipped (ignored) a chunk " ++ "between APNG chunks"); ++ png_ptr->mode &= ~PNG_HAVE_CHUNK_HEADER; ++ return; ++ } ++ ++ return; ++ } ++#endif /* PNG_READ_APNG_SUPPORTED */ ++ + if (chunk_name == png_IDAT) + { + if ((png_ptr->mode & PNG_AFTER_IDAT) != 0) +@@ -260,6 +360,9 @@ png_push_read_chunk(png_structrp png_ptr, png_inforp info_ptr) + + else if (chunk_name == png_IDAT) + { ++#ifdef PNG_READ_APNG_SUPPORTED ++ png_have_info(png_ptr, info_ptr); ++#endif + png_ptr->idat_size = png_ptr->push_length; + png_ptr->process_mode = PNG_READ_IDAT_MODE; + png_push_have_info(png_ptr, info_ptr); +@@ -270,6 +373,31 @@ png_push_read_chunk(png_structrp png_ptr, png_inforp info_ptr) + return; + } + ++#ifdef PNG_READ_APNG_SUPPORTED ++ else if (chunk_name == png_acTL) ++ { ++ if (png_ptr->push_length + 4 > png_ptr->buffer_size) ++ { ++ png_push_save_buffer(png_ptr); ++ return; ++ } ++ ++ png_handle_acTL(png_ptr, info_ptr, png_ptr->push_length); ++ } ++ ++ else if (chunk_name == png_fcTL) ++ { ++ if (png_ptr->push_length + 4 > png_ptr->buffer_size) ++ { ++ png_push_save_buffer(png_ptr); ++ return; ++ } ++ ++ png_handle_fcTL(png_ptr, info_ptr, png_ptr->push_length); ++ } ++ ++#endif /* PNG_READ_APNG_SUPPORTED */ ++ + else + { + PNG_PUSH_SAVE_BUFFER_IF_FULL +@@ -401,7 +529,11 @@ png_push_read_IDAT(png_structrp png_ptr) + png_byte chunk_tag[4]; + + /* TODO: this code can be commoned up with the same code in push_read */ ++#ifdef PNG_READ_APNG_SUPPORTED ++ PNG_PUSH_SAVE_BUFFER_IF_LT(12) ++#else + PNG_PUSH_SAVE_BUFFER_IF_LT(8) ++#endif + png_push_fill_buffer(png_ptr, chunk_length, 4); + png_ptr->push_length = png_get_uint_31(png_ptr, chunk_length); + png_reset_crc(png_ptr); +@@ -409,17 +541,64 @@ png_push_read_IDAT(png_structrp png_ptr) + png_ptr->chunk_name = PNG_CHUNK_FROM_STRING(chunk_tag); + png_ptr->mode |= PNG_HAVE_CHUNK_HEADER; + ++#ifdef PNG_READ_APNG_SUPPORTED ++ if (png_ptr->chunk_name != png_fdAT && png_ptr->num_frames_read > 0) ++ { ++ if (png_ptr->flags & PNG_FLAG_ZSTREAM_ENDED) ++ { ++ png_ptr->process_mode = PNG_READ_CHUNK_MODE; ++ if (png_ptr->frame_end_fn != NULL) ++ (*(png_ptr->frame_end_fn))(png_ptr, png_ptr->num_frames_read); ++ png_ptr->num_frames_read++; ++ return; ++ } ++ else ++ { ++ if (png_ptr->chunk_name == png_IEND) ++ png_error(png_ptr, "Not enough image data"); ++ if (png_ptr->push_length + 4 > png_ptr->buffer_size) ++ { ++ png_push_save_buffer(png_ptr); ++ return; ++ } ++ png_warning(png_ptr, "Skipping (ignoring) a chunk between " ++ "APNG chunks"); ++ png_crc_finish(png_ptr, png_ptr->push_length); ++ png_ptr->mode &= ~PNG_HAVE_CHUNK_HEADER; ++ return; ++ } ++ } ++ else ++#endif ++#ifdef PNG_READ_APNG_SUPPORTED ++ if (png_ptr->chunk_name != png_IDAT && png_ptr->num_frames_read == 0) ++#else + if (png_ptr->chunk_name != png_IDAT) ++#endif + { + png_ptr->process_mode = PNG_READ_CHUNK_MODE; + + if ((png_ptr->flags & PNG_FLAG_ZSTREAM_ENDED) == 0) + png_error(png_ptr, "Not enough compressed data"); + ++#ifdef PNG_READ_APNG_SUPPORTED ++ if (png_ptr->frame_end_fn != NULL) ++ (*(png_ptr->frame_end_fn))(png_ptr, png_ptr->num_frames_read); ++ png_ptr->num_frames_read++; ++#endif ++ + return; + } + + png_ptr->idat_size = png_ptr->push_length; ++ ++#ifdef PNG_READ_APNG_SUPPORTED ++ if (png_ptr->num_frames_read > 0) ++ { ++ png_ensure_sequence_number(png_ptr, 4); ++ png_ptr->idat_size -= 4; ++ } ++#endif + } + + if (png_ptr->idat_size != 0 && png_ptr->save_buffer_size != 0) +@@ -493,6 +672,15 @@ png_process_IDAT_data(png_structrp png_ptr, png_bytep buffer, + if (!(buffer_length > 0) || buffer == NULL) + png_error(png_ptr, "No IDAT data (internal error)"); + ++#ifdef PNG_READ_APNG_SUPPORTED ++ /* If the app is not APNG-aware, decode only the first frame */ ++ if (!(png_ptr->apng_flags & PNG_APNG_APP) && png_ptr->num_frames_read > 0) ++ { ++ png_ptr->flags |= PNG_FLAG_ZSTREAM_ENDED; ++ return; ++ } ++#endif ++ + /* This routine must process all the data it has been given + * before returning, calling the row callback as required to + * handle the uncompressed results. +@@ -926,6 +1114,18 @@ png_set_progressive_read_fn(png_structrp png_ptr, png_voidp progressive_ptr, + png_set_read_fn(png_ptr, progressive_ptr, png_push_fill_buffer); + } + ++#ifdef PNG_READ_APNG_SUPPORTED ++void PNGAPI ++png_set_progressive_frame_fn(png_structp png_ptr, ++ png_progressive_frame_ptr frame_info_fn, ++ png_progressive_frame_ptr frame_end_fn) ++{ ++ png_ptr->frame_info_fn = frame_info_fn; ++ png_ptr->frame_end_fn = frame_end_fn; ++ png_ptr->apng_flags |= PNG_APNG_APP; ++} ++#endif ++ + png_voidp PNGAPI + png_get_progressive_ptr(png_const_structrp png_ptr) + { +diff --git a/thirdparty/libpng/pngpriv.h b/thirdparty/libpng/pngpriv.h +index d514dff5c1..d9d0956d45 100644 +--- a/thirdparty/libpng/pngpriv.h ++++ b/thirdparty/libpng/pngpriv.h +@@ -620,6 +620,10 @@ + #define PNG_HAVE_CHUNK_AFTER_IDAT 0x2000U /* Have another chunk after IDAT */ + #define PNG_WROTE_eXIf 0x4000U + #define PNG_IS_READ_STRUCT 0x8000U /* Else is a write struct */ ++#ifdef PNG_APNG_SUPPORTED ++#define PNG_HAVE_acTL 0x10000U ++#define PNG_HAVE_fcTL 0x20000U ++#endif + + /* Flags for the transformations the PNG library does on the image data */ + #define PNG_BGR 0x0001U +@@ -884,6 +888,13 @@ + #define png_tRNS PNG_U32(116, 82, 78, 83) + #define png_zTXt PNG_U32(122, 84, 88, 116) + ++#ifdef PNG_APNG_SUPPORTED ++ ++/* For png_struct.apng_flags: */ ++#define PNG_FIRST_FRAME_HIDDEN 0x0001U ++#define PNG_APNG_APP 0x0002U ++#endif ++ + /* The following will work on (signed char*) strings, whereas the get_uint_32 + * macro will fail on top-bit-set values because of the sign extension. + */ +@@ -1671,6 +1682,47 @@ PNG_INTERNAL_FUNCTION(void,png_read_push_finish_row,(png_structrp png_ptr), + PNG_EMPTY); + #endif /* PROGRESSIVE_READ */ + ++#ifdef PNG_APNG_SUPPORTED ++PNG_INTERNAL_FUNCTION(void,png_ensure_fcTL_is_valid,(png_structp png_ptr, ++ png_uint_32 width, png_uint_32 height, ++ png_uint_32 x_offset, png_uint_32 y_offset, ++ png_uint_16 delay_num, png_uint_16 delay_den, ++ png_byte dispose_op, png_byte blend_op), PNG_EMPTY); ++ ++#ifdef PNG_READ_APNG_SUPPORTED ++PNG_INTERNAL_FUNCTION(void,png_handle_acTL,(png_structp png_ptr, png_infop info_ptr, ++ png_uint_32 length),PNG_EMPTY); ++PNG_INTERNAL_FUNCTION(void,png_handle_fcTL,(png_structp png_ptr, png_infop info_ptr, ++ png_uint_32 length),PNG_EMPTY); ++PNG_INTERNAL_FUNCTION(void,png_handle_fdAT,(png_structp png_ptr, png_infop info_ptr, ++ png_uint_32 length),PNG_EMPTY); ++PNG_INTERNAL_FUNCTION(void,png_have_info,(png_structp png_ptr, png_infop info_ptr),PNG_EMPTY); ++PNG_INTERNAL_FUNCTION(void,png_ensure_sequence_number,(png_structp png_ptr, ++ png_uint_32 length),PNG_EMPTY); ++PNG_INTERNAL_FUNCTION(void,png_read_reset,(png_structp png_ptr),PNG_EMPTY); ++PNG_INTERNAL_FUNCTION(void,png_read_reinit,(png_structp png_ptr, ++ png_infop info_ptr),PNG_EMPTY); ++#ifdef PNG_PROGRESSIVE_READ_SUPPORTED ++PNG_INTERNAL_FUNCTION(void,png_progressive_read_reset,(png_structp png_ptr),PNG_EMPTY); ++#endif /* PNG_PROGRESSIVE_READ_SUPPORTED */ ++#endif /* PNG_READ_APNG_SUPPORTED */ ++ ++#ifdef PNG_WRITE_APNG_SUPPORTED ++PNG_INTERNAL_FUNCTION(void,png_write_acTL,(png_structp png_ptr, ++ png_uint_32 num_frames, png_uint_32 num_plays),PNG_EMPTY); ++PNG_INTERNAL_FUNCTION(void,png_write_fcTL,(png_structp png_ptr, ++ png_uint_32 width, png_uint_32 height, ++ png_uint_32 x_offset, png_uint_32 y_offset, ++ png_uint_16 delay_num, png_uint_16 delay_den, ++ png_byte dispose_op, png_byte blend_op),PNG_EMPTY); ++PNG_INTERNAL_FUNCTION(void,png_write_fdAT,(png_structp png_ptr, ++ png_const_bytep data, png_size_t length),PNG_EMPTY); ++PNG_INTERNAL_FUNCTION(void,png_write_reset,(png_structp png_ptr),PNG_EMPTY); ++PNG_INTERNAL_FUNCTION(void,png_write_reinit,(png_structp png_ptr, ++ png_infop info_ptr, png_uint_32 width, png_uint_32 height),PNG_EMPTY); ++#endif /* PNG_WRITE_APNG_SUPPORTED */ ++#endif /* PNG_APNG_SUPPORTED */ ++ + #ifdef PNG_iCCP_SUPPORTED + /* Routines for checking parts of an ICC profile. */ + #ifdef PNG_READ_iCCP_SUPPORTED +diff --git a/thirdparty/libpng/pngread.c b/thirdparty/libpng/pngread.c +index 0fd364827e..865c97e073 100644 +--- a/thirdparty/libpng/pngread.c ++++ b/thirdparty/libpng/pngread.c +@@ -155,16 +155,96 @@ png_read_info(png_structrp png_ptr, png_inforp info_ptr) + + else if (chunk_name == png_IDAT) + { ++#ifdef PNG_READ_APNG_SUPPORTED ++ png_have_info(png_ptr, info_ptr); ++#endif + png_ptr->idat_size = length; + break; + } + ++#ifdef PNG_READ_APNG_SUPPORTED ++ else if (chunk_name == png_acTL) ++ png_handle_acTL(png_ptr, info_ptr, length); ++ ++ else if (chunk_name == png_fcTL) ++ png_handle_fcTL(png_ptr, info_ptr, length); ++ ++ else if (chunk_name == png_fdAT) ++ png_handle_fdAT(png_ptr, info_ptr, length); ++#endif ++ + else + png_handle_chunk(png_ptr, info_ptr, length); + } + } + #endif /* SEQUENTIAL_READ */ + ++#ifdef PNG_READ_APNG_SUPPORTED ++void PNGAPI ++png_read_frame_head(png_structp png_ptr, png_infop info_ptr) ++{ ++ png_byte have_chunk_after_DAT; /* after IDAT or after fdAT */ ++ ++ png_debug(0, "Reading frame head"); ++ ++ if (!(png_ptr->mode & PNG_HAVE_acTL)) ++ png_error(png_ptr, "attempt to png_read_frame_head() but " ++ "no acTL present"); ++ ++ /* do nothing for the main IDAT */ ++ if (png_ptr->num_frames_read == 0) ++ return; ++ ++ png_read_reset(png_ptr); ++ png_ptr->flags &= ~PNG_FLAG_ROW_INIT; ++ png_ptr->mode &= ~PNG_HAVE_fcTL; ++ ++ have_chunk_after_DAT = 0; ++ for (;;) ++ { ++ png_uint_32 length = png_read_chunk_header(png_ptr); ++ ++ if (png_ptr->chunk_name == png_IDAT) ++ { ++ /* discard trailing IDATs for the first frame */ ++ if (have_chunk_after_DAT || png_ptr->num_frames_read > 1) ++ png_error(png_ptr, "png_read_frame_head(): out of place IDAT"); ++ png_crc_finish(png_ptr, length); ++ } ++ ++ else if (png_ptr->chunk_name == png_fcTL) ++ { ++ png_handle_fcTL(png_ptr, info_ptr, length); ++ have_chunk_after_DAT = 1; ++ } ++ ++ else if (png_ptr->chunk_name == png_fdAT) ++ { ++ png_ensure_sequence_number(png_ptr, length); ++ ++ /* discard trailing fdATs for frames other than the first */ ++ if (!have_chunk_after_DAT && png_ptr->num_frames_read > 1) ++ png_crc_finish(png_ptr, length - 4); ++ else if(png_ptr->mode & PNG_HAVE_fcTL) ++ { ++ png_ptr->idat_size = length - 4; ++ png_ptr->mode |= PNG_HAVE_IDAT; ++ ++ break; ++ } ++ else ++ png_error(png_ptr, "png_read_frame_head(): out of place fdAT"); ++ } ++ else ++ { ++ png_warning(png_ptr, "Skipped (ignored) a chunk " ++ "between APNG chunks"); ++ png_crc_finish(png_ptr, length); ++ } ++ } ++} ++#endif /* PNG_READ_APNG_SUPPORTED */ ++ + /* Optional call to update the users info_ptr structure */ + void PNGAPI + png_read_update_info(png_structrp png_ptr, png_inforp info_ptr) +diff --git a/thirdparty/libpng/pngrutil.c b/thirdparty/libpng/pngrutil.c +index d0f3ed35d2..9eebf9e416 100644 +--- a/thirdparty/libpng/pngrutil.c ++++ b/thirdparty/libpng/pngrutil.c +@@ -922,6 +922,11 @@ png_handle_IHDR(png_structrp png_ptr, png_inforp info_ptr, png_uint_32 length) + filter_type = buf[11]; + interlace_type = buf[12]; + ++#ifdef PNG_READ_APNG_SUPPORTED ++ png_ptr->first_frame_width = width; ++ png_ptr->first_frame_height = height; ++#endif ++ + /* Set internal variables */ + png_ptr->width = width; + png_ptr->height = height; +@@ -2730,6 +2735,179 @@ png_handle_iTXt(png_structrp png_ptr, png_inforp info_ptr, png_uint_32 length) + # define png_handle_iTXt NULL + #endif + ++#ifdef PNG_READ_APNG_SUPPORTED ++void /* PRIVATE */ ++png_handle_acTL(png_structp png_ptr, png_infop info_ptr, png_uint_32 length) ++{ ++ png_byte data[8]; ++ png_uint_32 num_frames; ++ png_uint_32 num_plays; ++ png_uint_32 didSet; ++ ++ png_debug(1, "in png_handle_acTL"); ++ ++ if (!(png_ptr->mode & PNG_HAVE_IHDR)) ++ { ++ png_error(png_ptr, "Missing IHDR before acTL"); ++ } ++ else if (png_ptr->mode & PNG_HAVE_IDAT) ++ { ++ png_warning(png_ptr, "Invalid acTL after IDAT skipped"); ++ png_crc_finish(png_ptr, length); ++ return; ++ } ++ else if (png_ptr->mode & PNG_HAVE_acTL) ++ { ++ png_warning(png_ptr, "Duplicate acTL skipped"); ++ png_crc_finish(png_ptr, length); ++ return; ++ } ++ else if (length != 8) ++ { ++ png_warning(png_ptr, "acTL with invalid length skipped"); ++ png_crc_finish(png_ptr, length); ++ return; ++ } ++ ++ png_crc_read(png_ptr, data, 8); ++ png_crc_finish(png_ptr, 0); ++ ++ num_frames = png_get_uint_31(png_ptr, data); ++ num_plays = png_get_uint_31(png_ptr, data + 4); ++ ++ /* the set function will do error checking on num_frames */ ++ didSet = png_set_acTL(png_ptr, info_ptr, num_frames, num_plays); ++ if(didSet) ++ png_ptr->mode |= PNG_HAVE_acTL; ++} ++ ++void /* PRIVATE */ ++png_handle_fcTL(png_structp png_ptr, png_infop info_ptr, png_uint_32 length) ++{ ++ png_byte data[22]; ++ png_uint_32 width; ++ png_uint_32 height; ++ png_uint_32 x_offset; ++ png_uint_32 y_offset; ++ png_uint_16 delay_num; ++ png_uint_16 delay_den; ++ png_byte dispose_op; ++ png_byte blend_op; ++ ++ png_debug(1, "in png_handle_fcTL"); ++ ++ png_ensure_sequence_number(png_ptr, length); ++ ++ if (!(png_ptr->mode & PNG_HAVE_IHDR)) ++ { ++ png_error(png_ptr, "Missing IHDR before fcTL"); ++ } ++ else if (png_ptr->mode & PNG_HAVE_IDAT) ++ { ++ /* for any frames other then the first this message may be misleading, ++ * but correct. PNG_HAVE_IDAT is unset before the frame head is read ++ * i can't think of a better message */ ++ png_warning(png_ptr, "Invalid fcTL after IDAT skipped"); ++ png_crc_finish(png_ptr, length-4); ++ return; ++ } ++ else if (png_ptr->mode & PNG_HAVE_fcTL) ++ { ++ png_warning(png_ptr, "Duplicate fcTL within one frame skipped"); ++ png_crc_finish(png_ptr, length-4); ++ return; ++ } ++ else if (length != 26) ++ { ++ png_warning(png_ptr, "fcTL with invalid length skipped"); ++ png_crc_finish(png_ptr, length-4); ++ return; ++ } ++ ++ png_crc_read(png_ptr, data, 22); ++ png_crc_finish(png_ptr, 0); ++ ++ width = png_get_uint_31(png_ptr, data); ++ height = png_get_uint_31(png_ptr, data + 4); ++ x_offset = png_get_uint_31(png_ptr, data + 8); ++ y_offset = png_get_uint_31(png_ptr, data + 12); ++ delay_num = png_get_uint_16(data + 16); ++ delay_den = png_get_uint_16(data + 18); ++ dispose_op = data[20]; ++ blend_op = data[21]; ++ ++ if (png_ptr->num_frames_read == 0 && (x_offset != 0 || y_offset != 0)) ++ { ++ png_warning(png_ptr, "fcTL for the first frame must have zero offset"); ++ return; ++ } ++ ++ if (info_ptr != NULL) ++ { ++ if (png_ptr->num_frames_read == 0 && ++ (width != info_ptr->width || height != info_ptr->height)) ++ { ++ png_warning(png_ptr, "size in first frame's fcTL must match " ++ "the size in IHDR"); ++ return; ++ } ++ ++ /* The set function will do more error checking */ ++ png_set_next_frame_fcTL(png_ptr, info_ptr, width, height, ++ x_offset, y_offset, delay_num, delay_den, ++ dispose_op, blend_op); ++ ++ png_read_reinit(png_ptr, info_ptr); ++ ++ png_ptr->mode |= PNG_HAVE_fcTL; ++ } ++} ++ ++void /* PRIVATE */ ++png_have_info(png_structp png_ptr, png_infop info_ptr) ++{ ++ if((info_ptr->valid & PNG_INFO_acTL) && !(info_ptr->valid & PNG_INFO_fcTL)) ++ { ++ png_ptr->apng_flags |= PNG_FIRST_FRAME_HIDDEN; ++ info_ptr->num_frames++; ++ } ++} ++ ++void /* PRIVATE */ ++png_handle_fdAT(png_structp png_ptr, png_infop info_ptr, png_uint_32 length) ++{ ++ png_ensure_sequence_number(png_ptr, length); ++ ++ /* This function is only called from png_read_end(), png_read_info(), ++ * and png_push_read_chunk() which means that: ++ * - the user doesn't want to read this frame ++ * - or this is an out-of-place fdAT ++ * in either case it is safe to ignore the chunk with a warning */ ++ png_warning(png_ptr, "ignoring fdAT chunk"); ++ png_crc_finish(png_ptr, length - 4); ++ PNG_UNUSED(info_ptr) ++} ++ ++void /* PRIVATE */ ++png_ensure_sequence_number(png_structp png_ptr, png_uint_32 length) ++{ ++ png_byte data[4]; ++ png_uint_32 sequence_number; ++ ++ if (length < 4) ++ png_error(png_ptr, "invalid fcTL or fdAT chunk found"); ++ ++ png_crc_read(png_ptr, data, 4); ++ sequence_number = png_get_uint_31(png_ptr, data); ++ ++ if (sequence_number != png_ptr->next_seq_num) ++ png_error(png_ptr, "fcTL or fdAT chunk with out-of-order sequence " ++ "number found"); ++ ++ png_ptr->next_seq_num++; ++} ++#endif /* PNG_READ_APNG_SUPPORTED */ ++ + #ifdef PNG_READ_UNKNOWN_CHUNKS_SUPPORTED + /* Utility function for png_handle_unknown; set up png_ptr::unknown_chunk */ + static int +@@ -4212,7 +4390,38 @@ png_read_IDAT_data(png_structrp png_ptr, png_bytep output, + { + uInt avail_in; + png_bytep buffer; ++#ifdef PNG_READ_APNG_SUPPORTED ++ png_uint_32 bytes_to_skip = 0; + ++ while (png_ptr->idat_size == 0 || bytes_to_skip != 0) ++ { ++ png_crc_finish(png_ptr, bytes_to_skip); ++ bytes_to_skip = 0; ++ ++ png_ptr->idat_size = png_read_chunk_header(png_ptr); ++ if (png_ptr->num_frames_read == 0) ++ { ++ if (png_ptr->chunk_name != png_IDAT) ++ png_error(png_ptr, "Not enough image data"); ++ } ++ else ++ { ++ if (png_ptr->chunk_name == png_IEND) ++ png_error(png_ptr, "Not enough image data"); ++ if (png_ptr->chunk_name != png_fdAT) ++ { ++ png_warning(png_ptr, "Skipped (ignored) a chunk " ++ "between APNG chunks"); ++ bytes_to_skip = png_ptr->idat_size; ++ continue; ++ } ++ ++ png_ensure_sequence_number(png_ptr, png_ptr->idat_size); ++ ++ png_ptr->idat_size -= 4; ++ } ++ } ++#else + while (png_ptr->idat_size == 0) + { + png_crc_finish(png_ptr, 0); +@@ -4224,7 +4433,7 @@ png_read_IDAT_data(png_structrp png_ptr, png_bytep output, + if (png_ptr->chunk_name != png_IDAT) + png_error(png_ptr, "Not enough image data"); + } +- ++#endif /* PNG_READ_APNG_SUPPORTED */ + avail_in = png_ptr->IDAT_read_size; + + if (avail_in > png_chunk_max(png_ptr)) +@@ -4295,6 +4504,9 @@ png_read_IDAT_data(png_structrp png_ptr, png_bytep output, + + png_ptr->mode |= PNG_AFTER_IDAT; + png_ptr->flags |= PNG_FLAG_ZSTREAM_ENDED; ++#ifdef PNG_READ_APNG_SUPPORTED ++ png_ptr->num_frames_read++; ++#endif + + if (png_ptr->zstream.avail_in > 0 || png_ptr->idat_size > 0) + png_chunk_benign_error(png_ptr, "Extra compressed data"); +@@ -4704,4 +4916,80 @@ defined(PNG_USER_TRANSFORM_PTR_SUPPORTED) + + png_ptr->flags |= PNG_FLAG_ROW_INIT; + } ++ ++#ifdef PNG_READ_APNG_SUPPORTED ++/* This function is to be called after the main IDAT set has been read and ++ * before a new IDAT is read. It resets some parts of png_ptr ++ * to make them usable by the read functions again */ ++void /* PRIVATE */ ++png_read_reset(png_structp png_ptr) ++{ ++ png_ptr->mode &= ~PNG_HAVE_IDAT; ++ png_ptr->mode &= ~PNG_AFTER_IDAT; ++ png_ptr->row_number = 0; ++ png_ptr->pass = 0; ++} ++ ++void /* PRIVATE */ ++png_read_reinit(png_structp png_ptr, png_infop info_ptr) ++{ ++ png_ptr->width = info_ptr->next_frame_width; ++ png_ptr->height = info_ptr->next_frame_height; ++ png_ptr->rowbytes = PNG_ROWBYTES(png_ptr->pixel_depth,png_ptr->width); ++ png_ptr->info_rowbytes = PNG_ROWBYTES(info_ptr->pixel_depth, ++ png_ptr->width); ++ if (png_ptr->prev_row) ++ memset(png_ptr->prev_row, 0, png_ptr->rowbytes + 1); ++} ++ ++#ifdef PNG_PROGRESSIVE_READ_SUPPORTED ++/* same as png_read_reset() but for the progressive reader */ ++void /* PRIVATE */ ++png_progressive_read_reset(png_structp png_ptr) ++{ ++#ifdef PNG_READ_INTERLACING_SUPPORTED ++ /* Arrays to facilitate easy interlacing - use pass (0 - 6) as index */ ++ ++ /* Start of interlace block */ ++ const int png_pass_start[] = {0, 4, 0, 2, 0, 1, 0}; ++ ++ /* Offset to next interlace block */ ++ const int png_pass_inc[] = {8, 8, 4, 4, 2, 2, 1}; ++ ++ /* Start of interlace block in the y direction */ ++ const int png_pass_ystart[] = {0, 0, 4, 0, 2, 0, 1}; ++ ++ /* Offset to next interlace block in the y direction */ ++ const int png_pass_yinc[] = {8, 8, 8, 4, 4, 2, 2}; ++ ++ if (png_ptr->interlaced) ++ { ++ if (!(png_ptr->transformations & PNG_INTERLACE)) ++ png_ptr->num_rows = (png_ptr->height + png_pass_yinc[0] - 1 - ++ png_pass_ystart[0]) / png_pass_yinc[0]; ++ else ++ png_ptr->num_rows = png_ptr->height; ++ ++ png_ptr->iwidth = (png_ptr->width + ++ png_pass_inc[png_ptr->pass] - 1 - ++ png_pass_start[png_ptr->pass]) / ++ png_pass_inc[png_ptr->pass]; ++ } ++ else ++#endif /* PNG_READ_INTERLACING_SUPPORTED */ ++ { ++ png_ptr->num_rows = png_ptr->height; ++ png_ptr->iwidth = png_ptr->width; ++ } ++ png_ptr->flags &= ~PNG_FLAG_ZSTREAM_ENDED; ++ if (inflateReset(&(png_ptr->zstream)) != Z_OK) ++ png_error(png_ptr, "inflateReset failed"); ++ png_ptr->zstream.avail_in = 0; ++ png_ptr->zstream.next_in = 0; ++ png_ptr->zstream.next_out = png_ptr->row_buf; ++ png_ptr->zstream.avail_out = (uInt)PNG_ROWBYTES(png_ptr->pixel_depth, ++ png_ptr->iwidth) + 1; ++} ++#endif /* PNG_PROGRESSIVE_READ_SUPPORTED */ ++#endif /* PNG_READ_APNG_SUPPORTED */ + #endif /* READ */ +diff --git a/thirdparty/libpng/pngset.c b/thirdparty/libpng/pngset.c +index d7f3393c43..65456686a5 100644 +--- a/thirdparty/libpng/pngset.c ++++ b/thirdparty/libpng/pngset.c +@@ -463,6 +463,11 @@ png_set_IHDR(png_const_structrp png_ptr, png_inforp info_ptr, + info_ptr->pixel_depth = (png_byte)(info_ptr->channels * info_ptr->bit_depth); + + info_ptr->rowbytes = PNG_ROWBYTES(info_ptr->pixel_depth, width); ++ ++#ifdef PNG_APNG_SUPPORTED ++ /* for non-animated png. this may be overwritten from an acTL chunk later */ ++ info_ptr->num_frames = 1; ++#endif + } + + #ifdef PNG_oFFs_SUPPORTED +@@ -1318,6 +1323,147 @@ png_set_sPLT(png_const_structrp png_ptr, + } + #endif /* sPLT */ + ++#ifdef PNG_APNG_SUPPORTED ++png_uint_32 PNGAPI ++png_set_acTL(png_structp png_ptr, png_infop info_ptr, ++ png_uint_32 num_frames, png_uint_32 num_plays) ++{ ++ png_debug1(1, "in %s storage function", "acTL"); ++ ++ if (png_ptr == NULL || info_ptr == NULL) ++ { ++ png_warning(png_ptr, ++ "Call to png_set_acTL() with NULL png_ptr " ++ "or info_ptr ignored"); ++ return (0); ++ } ++ if (num_frames == 0) ++ { ++ png_warning(png_ptr, ++ "Ignoring attempt to set acTL with num_frames zero"); ++ return (0); ++ } ++ if (num_frames > PNG_UINT_31_MAX) ++ { ++ png_warning(png_ptr, ++ "Ignoring attempt to set acTL with num_frames > 2^31-1"); ++ return (0); ++ } ++ if (num_plays > PNG_UINT_31_MAX) ++ { ++ png_warning(png_ptr, ++ "Ignoring attempt to set acTL with num_plays " ++ "> 2^31-1"); ++ return (0); ++ } ++ ++ info_ptr->num_frames = num_frames; ++ info_ptr->num_plays = num_plays; ++ ++ info_ptr->valid |= PNG_INFO_acTL; ++ ++ return (1); ++} ++ ++/* delay_num and delay_den can hold any 16-bit values including zero */ ++png_uint_32 PNGAPI ++png_set_next_frame_fcTL(png_structp png_ptr, png_infop info_ptr, ++ png_uint_32 width, png_uint_32 height, ++ png_uint_32 x_offset, png_uint_32 y_offset, ++ png_uint_16 delay_num, png_uint_16 delay_den, ++ png_byte dispose_op, png_byte blend_op) ++{ ++ png_debug1(1, "in %s storage function", "fcTL"); ++ ++ if (png_ptr == NULL || info_ptr == NULL) ++ { ++ png_warning(png_ptr, ++ "Call to png_set_fcTL() with NULL png_ptr or info_ptr " ++ "ignored"); ++ return (0); ++ } ++ ++ png_ensure_fcTL_is_valid(png_ptr, width, height, x_offset, y_offset, ++ delay_num, delay_den, dispose_op, blend_op); ++ ++ if (blend_op == PNG_BLEND_OP_OVER) ++ { ++ if (!(png_ptr->color_type & PNG_COLOR_MASK_ALPHA) && ++ !(png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))) ++ { ++ png_warning(png_ptr, "PNG_BLEND_OP_OVER is meaningless " ++ "and wasteful for opaque images, ignored"); ++ blend_op = PNG_BLEND_OP_SOURCE; ++ } ++ } ++ ++ info_ptr->next_frame_width = width; ++ info_ptr->next_frame_height = height; ++ info_ptr->next_frame_x_offset = x_offset; ++ info_ptr->next_frame_y_offset = y_offset; ++ info_ptr->next_frame_delay_num = delay_num; ++ info_ptr->next_frame_delay_den = delay_den; ++ info_ptr->next_frame_dispose_op = dispose_op; ++ info_ptr->next_frame_blend_op = blend_op; ++ ++ info_ptr->valid |= PNG_INFO_fcTL; ++ ++ return (1); ++} ++ ++void /* PRIVATE */ ++png_ensure_fcTL_is_valid(png_structp png_ptr, ++ png_uint_32 width, png_uint_32 height, ++ png_uint_32 x_offset, png_uint_32 y_offset, ++ png_uint_16 delay_num, png_uint_16 delay_den, ++ png_byte dispose_op, png_byte blend_op) ++{ ++ if (width == 0 || width > PNG_UINT_31_MAX) ++ png_error(png_ptr, "invalid width in fcTL (> 2^31-1)"); ++ if (height == 0 || height > PNG_UINT_31_MAX) ++ png_error(png_ptr, "invalid height in fcTL (> 2^31-1)"); ++ if (x_offset > PNG_UINT_31_MAX) ++ png_error(png_ptr, "invalid x_offset in fcTL (> 2^31-1)"); ++ if (y_offset > PNG_UINT_31_MAX) ++ png_error(png_ptr, "invalid y_offset in fcTL (> 2^31-1)"); ++ if (width + x_offset > png_ptr->first_frame_width || ++ height + y_offset > png_ptr->first_frame_height) ++ png_error(png_ptr, "dimensions of a frame are greater than" ++ "the ones in IHDR"); ++ ++ if (dispose_op != PNG_DISPOSE_OP_NONE && ++ dispose_op != PNG_DISPOSE_OP_BACKGROUND && ++ dispose_op != PNG_DISPOSE_OP_PREVIOUS) ++ png_error(png_ptr, "invalid dispose_op in fcTL"); ++ ++ if (blend_op != PNG_BLEND_OP_SOURCE && ++ blend_op != PNG_BLEND_OP_OVER) ++ png_error(png_ptr, "invalid blend_op in fcTL"); ++ ++ PNG_UNUSED(delay_num) ++ PNG_UNUSED(delay_den) ++} ++ ++png_uint_32 PNGAPI ++png_set_first_frame_is_hidden(png_structp png_ptr, png_infop info_ptr, ++ png_byte is_hidden) ++{ ++ png_debug(1, "in png_first_frame_is_hidden()"); ++ ++ if (png_ptr == NULL) ++ return 0; ++ ++ if (is_hidden) ++ png_ptr->apng_flags |= PNG_FIRST_FRAME_HIDDEN; ++ else ++ png_ptr->apng_flags &= ~PNG_FIRST_FRAME_HIDDEN; ++ ++ PNG_UNUSED(info_ptr) ++ ++ return 1; ++} ++#endif /* PNG_APNG_SUPPORTED */ ++ + #ifdef PNG_STORE_UNKNOWN_CHUNKS_SUPPORTED + static png_byte + check_location(png_const_structrp png_ptr, int location) +diff --git a/thirdparty/libpng/pngstruct.h b/thirdparty/libpng/pngstruct.h +index 324424495e..d436a78f06 100644 +--- a/thirdparty/libpng/pngstruct.h ++++ b/thirdparty/libpng/pngstruct.h +@@ -392,6 +392,27 @@ struct png_struct_def + png_byte filter_type; + #endif + ++#ifdef PNG_APNG_SUPPORTED ++ png_uint_32 apng_flags; ++ png_uint_32 next_seq_num; /* next fcTL/fdAT chunk sequence number */ ++ png_uint_32 first_frame_width; ++ png_uint_32 first_frame_height; ++ ++#ifdef PNG_READ_APNG_SUPPORTED ++ png_uint_32 num_frames_read; /* incremented after all image data of */ ++ /* a frame is read */ ++#ifdef PNG_PROGRESSIVE_READ_SUPPORTED ++ png_progressive_frame_ptr frame_info_fn; /* frame info read callback */ ++ png_progressive_frame_ptr frame_end_fn; /* frame data read callback */ ++#endif ++#endif ++ ++#ifdef PNG_WRITE_APNG_SUPPORTED ++ png_uint_32 num_frames_to_write; ++ png_uint_32 num_frames_written; ++#endif ++#endif /* PNG_APNG_SUPPORTED */ ++ + /* New members added in libpng-1.2.0 */ + + /* New members added in libpng-1.0.2 but first enabled by default in 1.2.0 */ +diff --git a/thirdparty/libpng/pngwrite.c b/thirdparty/libpng/pngwrite.c +index b7aeff4ce4..f1dc4c7e5b 100644 +--- a/thirdparty/libpng/pngwrite.c ++++ b/thirdparty/libpng/pngwrite.c +@@ -127,6 +127,11 @@ png_write_info_before_PLTE(png_structrp png_ptr, png_const_inforp info_ptr) + * the application continues writing the PNG. So check the 'invalid' + * flag here too. + */ ++#ifdef PNG_WRITE_APNG_SUPPORTED ++ if ((info_ptr->valid & PNG_INFO_acTL) != 0) ++ png_write_acTL(png_ptr, info_ptr->num_frames, info_ptr->num_plays); ++#endif ++ + #ifdef PNG_WRITE_UNKNOWN_CHUNKS_SUPPORTED + /* Write unknown chunks first; PNG v3 establishes a precedence order + * for colourspace chunks. It is certain therefore that new +@@ -405,6 +410,11 @@ png_write_end(png_structrp png_ptr, png_inforp info_ptr) + png_benign_error(png_ptr, "Wrote palette index exceeding num_palette"); + #endif + ++#ifdef PNG_WRITE_APNG_SUPPORTED ++ if (png_ptr->num_frames_written != png_ptr->num_frames_to_write) ++ png_error(png_ptr, "Not enough frames written"); ++#endif ++ + /* See if user wants us to write information chunks */ + if (info_ptr != NULL) + { +@@ -1515,6 +1525,43 @@ png_write_png(png_structrp png_ptr, png_inforp info_ptr, + } + #endif + ++#ifdef PNG_WRITE_APNG_SUPPORTED ++void PNGAPI ++png_write_frame_head(png_structp png_ptr, png_infop info_ptr, ++ png_bytepp row_pointers, png_uint_32 width, png_uint_32 height, ++ png_uint_32 x_offset, png_uint_32 y_offset, ++ png_uint_16 delay_num, png_uint_16 delay_den, png_byte dispose_op, ++ png_byte blend_op) ++{ ++ png_debug(1, "in png_write_frame_head"); ++ ++ /* there is a chance this has been set after png_write_info was called, ++ * so it would be set but not written. is there a way to be sure? */ ++ if (!(info_ptr->valid & PNG_INFO_acTL)) ++ png_error(png_ptr, "png_write_frame_head(): acTL not set"); ++ ++ png_write_reset(png_ptr); ++ ++ png_write_reinit(png_ptr, info_ptr, width, height); ++ ++ if ( !(png_ptr->num_frames_written == 0 && ++ (png_ptr->apng_flags & PNG_FIRST_FRAME_HIDDEN) ) ) ++ png_write_fcTL(png_ptr, width, height, x_offset, y_offset, ++ delay_num, delay_den, dispose_op, blend_op); ++ ++ PNG_UNUSED(row_pointers) ++} ++ ++void PNGAPI ++png_write_frame_tail(png_structp png_ptr, png_infop info_ptr) ++{ ++ png_debug(1, "in png_write_frame_tail"); ++ ++ png_ptr->num_frames_written++; ++ ++ PNG_UNUSED(info_ptr) ++} ++#endif /* PNG_WRITE_APNG_SUPPORTED */ + + #ifdef PNG_SIMPLIFIED_WRITE_SUPPORTED + /* Initialize the write structure - general purpose utility. */ +diff --git a/thirdparty/libpng/pngwutil.c b/thirdparty/libpng/pngwutil.c +index be706afe64..297534c5d2 100644 +--- a/thirdparty/libpng/pngwutil.c ++++ b/thirdparty/libpng/pngwutil.c +@@ -838,6 +838,11 @@ png_write_IHDR(png_structrp png_ptr, png_uint_32 width, png_uint_32 height, + /* Write the chunk */ + png_write_complete_chunk(png_ptr, png_IHDR, buf, 13); + ++#ifdef PNG_WRITE_APNG_SUPPORTED ++ png_ptr->first_frame_width = width; ++ png_ptr->first_frame_height = height; ++#endif ++ + if ((png_ptr->do_filter) == PNG_NO_FILTERS) + { + if (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE || +@@ -1019,8 +1024,17 @@ png_compress_IDAT(png_structrp png_ptr, png_const_bytep input, + optimize_cmf(data, png_image_size(png_ptr)); + #endif + +- if (size > 0) +- png_write_complete_chunk(png_ptr, png_IDAT, data, size); ++ if (size > 0) ++#ifdef PNG_WRITE_APNG_SUPPORTED ++ { ++ if (png_ptr->num_frames_written == 0) ++#endif ++ png_write_complete_chunk(png_ptr, png_IDAT, data, size); ++#ifdef PNG_WRITE_APNG_SUPPORTED ++ else ++ png_write_fdAT(png_ptr, data, size); ++ } ++#endif /* PNG_WRITE_APNG_SUPPORTED */ + png_ptr->mode |= PNG_HAVE_IDAT; + + png_ptr->zstream.next_out = data; +@@ -1067,7 +1081,17 @@ png_compress_IDAT(png_structrp png_ptr, png_const_bytep input, + #endif + + if (size > 0) ++#ifdef PNG_WRITE_APNG_SUPPORTED ++ { ++ if (png_ptr->num_frames_written == 0) ++#endif + png_write_complete_chunk(png_ptr, png_IDAT, data, size); ++#ifdef PNG_WRITE_APNG_SUPPORTED ++ else ++ png_write_fdAT(png_ptr, data, size); ++ } ++#endif /* PNG_WRITE_APNG_SUPPORTED */ ++ + png_ptr->zstream.avail_out = 0; + png_ptr->zstream.next_out = NULL; + png_ptr->mode |= PNG_HAVE_IDAT | PNG_AFTER_IDAT; +@@ -1969,6 +1993,82 @@ png_write_tIME(png_structrp png_ptr, png_const_timep mod_time) + } + #endif + ++#ifdef PNG_WRITE_APNG_SUPPORTED ++void /* PRIVATE */ ++png_write_acTL(png_structp png_ptr, ++ png_uint_32 num_frames, png_uint_32 num_plays) ++{ ++ png_byte buf[8]; ++ ++ png_debug(1, "in png_write_acTL"); ++ ++ png_ptr->num_frames_to_write = num_frames; ++ ++ if (png_ptr->apng_flags & PNG_FIRST_FRAME_HIDDEN) ++ num_frames--; ++ ++ png_save_uint_32(buf, num_frames); ++ png_save_uint_32(buf + 4, num_plays); ++ ++ png_write_complete_chunk(png_ptr, png_acTL, buf, (png_size_t)8); ++} ++ ++void /* PRIVATE */ ++png_write_fcTL(png_structp png_ptr, png_uint_32 width, png_uint_32 height, ++ png_uint_32 x_offset, png_uint_32 y_offset, ++ png_uint_16 delay_num, png_uint_16 delay_den, png_byte dispose_op, ++ png_byte blend_op) ++{ ++ png_byte buf[26]; ++ ++ png_debug(1, "in png_write_fcTL"); ++ ++ if (png_ptr->num_frames_written == 0 && (x_offset != 0 || y_offset != 0)) ++ png_error(png_ptr, "x and/or y offset for the first frame aren't 0"); ++ if (png_ptr->num_frames_written == 0 && ++ (width != png_ptr->first_frame_width || ++ height != png_ptr->first_frame_height)) ++ png_error(png_ptr, "width and/or height in the first frame's fcTL " ++ "don't match the ones in IHDR"); ++ ++ /* more error checking */ ++ png_ensure_fcTL_is_valid(png_ptr, width, height, x_offset, y_offset, ++ delay_num, delay_den, dispose_op, blend_op); ++ ++ png_save_uint_32(buf, png_ptr->next_seq_num); ++ png_save_uint_32(buf + 4, width); ++ png_save_uint_32(buf + 8, height); ++ png_save_uint_32(buf + 12, x_offset); ++ png_save_uint_32(buf + 16, y_offset); ++ png_save_uint_16(buf + 20, delay_num); ++ png_save_uint_16(buf + 22, delay_den); ++ buf[24] = dispose_op; ++ buf[25] = blend_op; ++ ++ png_write_complete_chunk(png_ptr, png_fcTL, buf, (png_size_t)26); ++ ++ png_ptr->next_seq_num++; ++} ++ ++void /* PRIVATE */ ++png_write_fdAT(png_structp png_ptr, ++ png_const_bytep data, png_size_t length) ++{ ++ png_byte buf[4]; ++ ++ png_write_chunk_header(png_ptr, png_fdAT, (png_uint_32)(4 + length)); ++ ++ png_save_uint_32(buf, png_ptr->next_seq_num); ++ png_write_chunk_data(png_ptr, buf, 4); ++ ++ png_write_chunk_data(png_ptr, data, length); ++ ++ png_write_chunk_end(png_ptr); ++ ++ png_ptr->next_seq_num++; ++} ++#endif /* PNG_WRITE_APNG_SUPPORTED */ ++ + /* Initializes the row writing capability of libpng */ + void /* PRIVATE */ + png_write_start_row(png_structrp png_ptr) +@@ -2822,4 +2922,39 @@ png_write_filtered_row(png_structrp png_ptr, png_bytep filtered_row, + } + #endif /* WRITE_FLUSH */ + } ++ ++#ifdef PNG_WRITE_APNG_SUPPORTED ++void /* PRIVATE */ ++png_write_reset(png_structp png_ptr) ++{ ++ png_ptr->row_number = 0; ++ png_ptr->pass = 0; ++ png_ptr->mode &= ~PNG_HAVE_IDAT; ++} ++ ++void /* PRIVATE */ ++png_write_reinit(png_structp png_ptr, png_infop info_ptr, ++ png_uint_32 width, png_uint_32 height) ++{ ++ if (png_ptr->num_frames_written == 0 && ++ (width != png_ptr->first_frame_width || ++ height != png_ptr->first_frame_height)) ++ png_error(png_ptr, "width and/or height in the first frame's fcTL " ++ "don't match the ones in IHDR"); ++ if (width > png_ptr->first_frame_width || ++ height > png_ptr->first_frame_height) ++ png_error(png_ptr, "width and/or height for a frame greater than" ++ "the ones in IHDR"); ++ ++ png_set_IHDR(png_ptr, info_ptr, width, height, ++ info_ptr->bit_depth, info_ptr->color_type, ++ info_ptr->interlace_type, info_ptr->compression_type, ++ info_ptr->filter_type); ++ ++ png_ptr->width = width; ++ png_ptr->height = height; ++ png_ptr->rowbytes = PNG_ROWBYTES(png_ptr->pixel_depth, width); ++ png_ptr->usr_width = png_ptr->width; ++} ++#endif /* PNG_WRITE_APNG_SUPPORTED */ + #endif /* WRITE */ diff --git a/thirdparty/libpng/png.h b/thirdparty/libpng/png.h index 9b069e4ee8..90271057f6 100644 --- a/thirdparty/libpng/png.h +++ b/thirdparty/libpng/png.h @@ -328,6 +328,10 @@ # include "pnglibconf.h" #endif +#define PNG_APNG_SUPPORTED +#define PNG_READ_APNG_SUPPORTED +#define PNG_WRITE_APNG_SUPPORTED + #ifndef PNG_VERSION_INFO_ONLY /* Machine specific configuration. */ # include "pngconf.h" @@ -423,6 +427,17 @@ extern "C" { * See pngconf.h for base types that vary by machine/system */ +#ifdef PNG_APNG_SUPPORTED +/* dispose_op flags from inside fcTL */ +#define PNG_DISPOSE_OP_NONE 0x00U +#define PNG_DISPOSE_OP_BACKGROUND 0x01U +#define PNG_DISPOSE_OP_PREVIOUS 0x02U + +/* blend_op flags from inside fcTL */ +#define PNG_BLEND_OP_SOURCE 0x00U +#define PNG_BLEND_OP_OVER 0x01U +#endif /* PNG_APNG_SUPPORTED */ + /* This triggers a compiler error in png.c, if png.c and png.h * do not agree upon the version number. */ @@ -796,6 +811,10 @@ typedef PNG_CALLBACK(void, *png_write_status_ptr, (png_structp, png_uint_32, #ifdef PNG_PROGRESSIVE_READ_SUPPORTED typedef PNG_CALLBACK(void, *png_progressive_info_ptr, (png_structp, png_infop)); typedef PNG_CALLBACK(void, *png_progressive_end_ptr, (png_structp, png_infop)); +#ifdef PNG_APNG_SUPPORTED +typedef PNG_CALLBACK(void, *png_progressive_frame_ptr, (png_structp, + png_uint_32)); +#endif /* The following callback receives png_uint_32 row_number, int pass for the * png_bytep data of the row. When transforming an interlaced image the @@ -3338,6 +3357,75 @@ PNG_EXPORT(244, int, png_set_option, (png_structrp png_ptr, int option, * END OF HARDWARE AND SOFTWARE OPTIONS ******************************************************************************/ +#ifdef PNG_APNG_SUPPORTED +PNG_EXPORT(260, png_uint_32, png_get_acTL, (png_structp png_ptr, + png_infop info_ptr, png_uint_32 *num_frames, png_uint_32 *num_plays)); + +PNG_EXPORT(261, png_uint_32, png_set_acTL, (png_structp png_ptr, + png_infop info_ptr, png_uint_32 num_frames, png_uint_32 num_plays)); + +PNG_EXPORT(262, png_uint_32, png_get_num_frames, (png_structp png_ptr, + png_infop info_ptr)); + +PNG_EXPORT(263, png_uint_32, png_get_num_plays, (png_structp png_ptr, + png_infop info_ptr)); + +PNG_EXPORT(264, png_uint_32, png_get_next_frame_fcTL, + (png_structp png_ptr, png_infop info_ptr, png_uint_32 *width, + png_uint_32 *height, png_uint_32 *x_offset, png_uint_32 *y_offset, + png_uint_16 *delay_num, png_uint_16 *delay_den, png_byte *dispose_op, + png_byte *blend_op)); + +PNG_EXPORT(265, png_uint_32, png_set_next_frame_fcTL, + (png_structp png_ptr, png_infop info_ptr, png_uint_32 width, + png_uint_32 height, png_uint_32 x_offset, png_uint_32 y_offset, + png_uint_16 delay_num, png_uint_16 delay_den, png_byte dispose_op, + png_byte blend_op)); + +PNG_EXPORT(266, png_uint_32, png_get_next_frame_width, + (png_structp png_ptr, png_infop info_ptr)); +PNG_EXPORT(267, png_uint_32, png_get_next_frame_height, + (png_structp png_ptr, png_infop info_ptr)); +PNG_EXPORT(268, png_uint_32, png_get_next_frame_x_offset, + (png_structp png_ptr, png_infop info_ptr)); +PNG_EXPORT(269, png_uint_32, png_get_next_frame_y_offset, + (png_structp png_ptr, png_infop info_ptr)); +PNG_EXPORT(270, png_uint_16, png_get_next_frame_delay_num, + (png_structp png_ptr, png_infop info_ptr)); +PNG_EXPORT(271, png_uint_16, png_get_next_frame_delay_den, + (png_structp png_ptr, png_infop info_ptr)); +PNG_EXPORT(272, png_byte, png_get_next_frame_dispose_op, + (png_structp png_ptr, png_infop info_ptr)); +PNG_EXPORT(273, png_byte, png_get_next_frame_blend_op, + (png_structp png_ptr, png_infop info_ptr)); +PNG_EXPORT(274, png_byte, png_get_first_frame_is_hidden, + (png_structp png_ptr, png_infop info_ptr)); +PNG_EXPORT(275, png_uint_32, png_set_first_frame_is_hidden, + (png_structp png_ptr, png_infop info_ptr, png_byte is_hidden)); + +#ifdef PNG_READ_APNG_SUPPORTED +PNG_EXPORT(276, void, png_read_frame_head, (png_structp png_ptr, + png_infop info_ptr)); +#ifdef PNG_PROGRESSIVE_READ_SUPPORTED +PNG_EXPORT(277, void, png_set_progressive_frame_fn, (png_structp png_ptr, + png_progressive_frame_ptr frame_info_fn, + png_progressive_frame_ptr frame_end_fn)); +#endif /* PNG_PROGRESSIVE_READ_SUPPORTED */ +#endif /* PNG_READ_APNG_SUPPORTED */ + +#ifdef PNG_WRITE_APNG_SUPPORTED +PNG_EXPORT(278, void, png_write_frame_head, (png_structp png_ptr, + png_infop info_ptr, png_bytepp row_pointers, + png_uint_32 width, png_uint_32 height, + png_uint_32 x_offset, png_uint_32 y_offset, + png_uint_16 delay_num, png_uint_16 delay_den, png_byte dispose_op, + png_byte blend_op)); + +PNG_EXPORT(279, void, png_write_frame_tail, (png_structp png_ptr, + png_infop info_ptr)); +#endif /* PNG_WRITE_APNG_SUPPORTED */ +#endif /* PNG_APNG_SUPPORTED */ + /* Maintainer: Put new public prototypes here ^, in libpng.3, in project * defs, and in scripts/symbols.def. */ @@ -3346,7 +3434,11 @@ PNG_EXPORT(244, int, png_set_option, (png_structrp png_ptr, int option, * one to use is one more than this.) */ #ifdef PNG_EXPORT_LAST_ORDINAL +#ifdef PNG_APNG_SUPPORTED + PNG_EXPORT_LAST_ORDINAL(279); +#else PNG_EXPORT_LAST_ORDINAL(259); +#endif /* PNG_APNG_SUPPORTED */ #endif #ifdef __cplusplus diff --git a/thirdparty/libpng/pngget.c b/thirdparty/libpng/pngget.c index 3623c5c7c3..6ec3407ecc 100644 --- a/thirdparty/libpng/pngget.c +++ b/thirdparty/libpng/pngget.c @@ -1367,4 +1367,166 @@ png_get_palette_max(png_const_structp png_ptr, png_const_infop info_ptr) # endif #endif +#ifdef PNG_APNG_SUPPORTED +png_uint_32 PNGAPI +png_get_acTL(png_structp png_ptr, png_infop info_ptr, + png_uint_32 *num_frames, png_uint_32 *num_plays) +{ + png_debug1(1, "in %s retrieval function", "acTL"); + + if (png_ptr != NULL && info_ptr != NULL && + (info_ptr->valid & PNG_INFO_acTL) && + num_frames != NULL && num_plays != NULL) + { + *num_frames = info_ptr->num_frames; + *num_plays = info_ptr->num_plays; + return (1); + } + + return (0); +} + +png_uint_32 PNGAPI +png_get_num_frames(png_structp png_ptr, png_infop info_ptr) +{ + png_debug(1, "in png_get_num_frames()"); + + if (png_ptr != NULL && info_ptr != NULL) + return (info_ptr->num_frames); + return (0); +} + +png_uint_32 PNGAPI +png_get_num_plays(png_structp png_ptr, png_infop info_ptr) +{ + png_debug(1, "in png_get_num_plays()"); + + if (png_ptr != NULL && info_ptr != NULL) + return (info_ptr->num_plays); + return (0); +} + +png_uint_32 PNGAPI +png_get_next_frame_fcTL(png_structp png_ptr, png_infop info_ptr, + png_uint_32 *width, png_uint_32 *height, + png_uint_32 *x_offset, png_uint_32 *y_offset, + png_uint_16 *delay_num, png_uint_16 *delay_den, + png_byte *dispose_op, png_byte *blend_op) +{ + png_debug1(1, "in %s retrieval function", "fcTL"); + + if (png_ptr != NULL && info_ptr != NULL && + (info_ptr->valid & PNG_INFO_fcTL) && + width != NULL && height != NULL && + x_offset != NULL && y_offset != NULL && + delay_num != NULL && delay_den != NULL && + dispose_op != NULL && blend_op != NULL) + { + *width = info_ptr->next_frame_width; + *height = info_ptr->next_frame_height; + *x_offset = info_ptr->next_frame_x_offset; + *y_offset = info_ptr->next_frame_y_offset; + *delay_num = info_ptr->next_frame_delay_num; + *delay_den = info_ptr->next_frame_delay_den; + *dispose_op = info_ptr->next_frame_dispose_op; + *blend_op = info_ptr->next_frame_blend_op; + return (1); + } + + return (0); +} + +png_uint_32 PNGAPI +png_get_next_frame_width(png_structp png_ptr, png_infop info_ptr) +{ + png_debug(1, "in png_get_next_frame_width()"); + + if (png_ptr != NULL && info_ptr != NULL) + return (info_ptr->next_frame_width); + return (0); +} + +png_uint_32 PNGAPI +png_get_next_frame_height(png_structp png_ptr, png_infop info_ptr) +{ + png_debug(1, "in png_get_next_frame_height()"); + + if (png_ptr != NULL && info_ptr != NULL) + return (info_ptr->next_frame_height); + return (0); +} + +png_uint_32 PNGAPI +png_get_next_frame_x_offset(png_structp png_ptr, png_infop info_ptr) +{ + png_debug(1, "in png_get_next_frame_x_offset()"); + + if (png_ptr != NULL && info_ptr != NULL) + return (info_ptr->next_frame_x_offset); + return (0); +} + +png_uint_32 PNGAPI +png_get_next_frame_y_offset(png_structp png_ptr, png_infop info_ptr) +{ + png_debug(1, "in png_get_next_frame_y_offset()"); + + if (png_ptr != NULL && info_ptr != NULL) + return (info_ptr->next_frame_y_offset); + return (0); +} + +png_uint_16 PNGAPI +png_get_next_frame_delay_num(png_structp png_ptr, png_infop info_ptr) +{ + png_debug(1, "in png_get_next_frame_delay_num()"); + + if (png_ptr != NULL && info_ptr != NULL) + return (info_ptr->next_frame_delay_num); + return (0); +} + +png_uint_16 PNGAPI +png_get_next_frame_delay_den(png_structp png_ptr, png_infop info_ptr) +{ + png_debug(1, "in png_get_next_frame_delay_den()"); + + if (png_ptr != NULL && info_ptr != NULL) + return (info_ptr->next_frame_delay_den); + return (0); +} + +png_byte PNGAPI +png_get_next_frame_dispose_op(png_structp png_ptr, png_infop info_ptr) +{ + png_debug(1, "in png_get_next_frame_dispose_op()"); + + if (png_ptr != NULL && info_ptr != NULL) + return (info_ptr->next_frame_dispose_op); + return (0); +} + +png_byte PNGAPI +png_get_next_frame_blend_op(png_structp png_ptr, png_infop info_ptr) +{ + png_debug(1, "in png_get_next_frame_blend_op()"); + + if (png_ptr != NULL && info_ptr != NULL) + return (info_ptr->next_frame_blend_op); + return (0); +} + +png_byte PNGAPI +png_get_first_frame_is_hidden(png_structp png_ptr, png_infop info_ptr) +{ + png_debug(1, "in png_first_frame_is_hidden()"); + + if (png_ptr != NULL) + return (png_byte)(png_ptr->apng_flags & PNG_FIRST_FRAME_HIDDEN); + + PNG_UNUSED(info_ptr) + + return 0; +} +#endif /* PNG_APNG_SUPPORTED */ #endif /* READ || WRITE */ diff --git a/thirdparty/libpng/pnginfo.h b/thirdparty/libpng/pnginfo.h index c2a907bc58..61d6d378ea 100644 --- a/thirdparty/libpng/pnginfo.h +++ b/thirdparty/libpng/pnginfo.h @@ -282,5 +282,18 @@ defined(PNG_READ_BACKGROUND_SUPPORTED) #ifdef PNG_sRGB_SUPPORTED int rendering_intent; #endif + +#ifdef PNG_APNG_SUPPORTED + png_uint_32 num_frames; /* including default image */ + png_uint_32 num_plays; + png_uint_32 next_frame_width; + png_uint_32 next_frame_height; + png_uint_32 next_frame_x_offset; + png_uint_32 next_frame_y_offset; + png_uint_16 next_frame_delay_num; + png_uint_16 next_frame_delay_den; + png_byte next_frame_dispose_op; + png_byte next_frame_blend_op; +#endif }; #endif /* PNGINFO_H */ diff --git a/thirdparty/libpng/pngpread.c b/thirdparty/libpng/pngpread.c index 60d810693b..323320dac7 100644 --- a/thirdparty/libpng/pngpread.c +++ b/thirdparty/libpng/pngpread.c @@ -200,6 +200,106 @@ png_push_read_chunk(png_structrp png_ptr, png_inforp info_ptr) chunk_name = png_ptr->chunk_name; +#ifdef PNG_READ_APNG_SUPPORTED + if (png_ptr->num_frames_read > 0 && + png_ptr->num_frames_read < info_ptr->num_frames) + { + if (chunk_name == png_IDAT) + { + /* Discard trailing IDATs for the first frame */ + if (png_ptr->mode & PNG_HAVE_fcTL || png_ptr->num_frames_read > 1) + png_error(png_ptr, "out of place IDAT"); + + if (png_ptr->push_length + 4 > png_ptr->buffer_size) + { + png_push_save_buffer(png_ptr); + return; + } + + png_ptr->mode &= ~PNG_HAVE_CHUNK_HEADER; + return; + } + else if (chunk_name == png_fdAT) + { + if (png_ptr->buffer_size < 4) + { + png_push_save_buffer(png_ptr); + return; + } + + png_ensure_sequence_number(png_ptr, 4); + + if (!(png_ptr->mode & PNG_HAVE_fcTL)) + { + /* Discard trailing fdATs for frames other than the first */ + if (png_ptr->num_frames_read < 2) + png_error(png_ptr, "out of place fdAT"); + + if (png_ptr->push_length + 4 > png_ptr->buffer_size) + { + png_push_save_buffer(png_ptr); + return; + } + + png_ptr->mode &= ~PNG_HAVE_CHUNK_HEADER; + return; + } + + else + { + /* frame data follows */ + png_ptr->idat_size = png_ptr->push_length - 4; + png_ptr->mode |= PNG_HAVE_IDAT; + png_ptr->process_mode = PNG_READ_IDAT_MODE; + + return; + } + } + + else if (chunk_name == png_fcTL) + { + if (png_ptr->push_length + 4 > png_ptr->buffer_size) + { + png_push_save_buffer(png_ptr); + return; + } + + png_read_reset(png_ptr); + png_ptr->mode &= ~PNG_HAVE_fcTL; + + png_handle_fcTL(png_ptr, info_ptr, png_ptr->push_length); + + if (!(png_ptr->mode & PNG_HAVE_fcTL)) + png_error(png_ptr, "missing required fcTL chunk"); + + png_read_reinit(png_ptr, info_ptr); + png_progressive_read_reset(png_ptr); + + if (png_ptr->frame_info_fn != NULL) + (*(png_ptr->frame_info_fn))(png_ptr, png_ptr->num_frames_read); + + png_ptr->mode &= ~PNG_HAVE_CHUNK_HEADER; + + return; + } + + else + { + if (png_ptr->push_length + 4 > png_ptr->buffer_size) + { + png_push_save_buffer(png_ptr); + return; + } + png_warning(png_ptr, "Skipped (ignored) a chunk " + "between APNG chunks"); + png_ptr->mode &= ~PNG_HAVE_CHUNK_HEADER; + return; + } + + return; + } +#endif /* PNG_READ_APNG_SUPPORTED */ + if (chunk_name == png_IDAT) { if ((png_ptr->mode & PNG_AFTER_IDAT) != 0) @@ -260,6 +360,9 @@ png_push_read_chunk(png_structrp png_ptr, png_inforp info_ptr) else if (chunk_name == png_IDAT) { +#ifdef PNG_READ_APNG_SUPPORTED + png_have_info(png_ptr, info_ptr); +#endif png_ptr->idat_size = png_ptr->push_length; png_ptr->process_mode = PNG_READ_IDAT_MODE; png_push_have_info(png_ptr, info_ptr); @@ -270,6 +373,31 @@ png_push_read_chunk(png_structrp png_ptr, png_inforp info_ptr) return; } +#ifdef PNG_READ_APNG_SUPPORTED + else if (chunk_name == png_acTL) + { + if (png_ptr->push_length + 4 > png_ptr->buffer_size) + { + png_push_save_buffer(png_ptr); + return; + } + + png_handle_acTL(png_ptr, info_ptr, png_ptr->push_length); + } + + else if (chunk_name == png_fcTL) + { + if (png_ptr->push_length + 4 > png_ptr->buffer_size) + { + png_push_save_buffer(png_ptr); + return; + } + + png_handle_fcTL(png_ptr, info_ptr, png_ptr->push_length); + } + +#endif /* PNG_READ_APNG_SUPPORTED */ + else { PNG_PUSH_SAVE_BUFFER_IF_FULL @@ -401,7 +529,11 @@ png_push_read_IDAT(png_structrp png_ptr) png_byte chunk_tag[4]; /* TODO: this code can be commoned up with the same code in push_read */ +#ifdef PNG_READ_APNG_SUPPORTED + PNG_PUSH_SAVE_BUFFER_IF_LT(12) +#else PNG_PUSH_SAVE_BUFFER_IF_LT(8) +#endif png_push_fill_buffer(png_ptr, chunk_length, 4); png_ptr->push_length = png_get_uint_31(png_ptr, chunk_length); png_reset_crc(png_ptr); @@ -409,17 +541,64 @@ png_push_read_IDAT(png_structrp png_ptr) png_ptr->chunk_name = PNG_CHUNK_FROM_STRING(chunk_tag); png_ptr->mode |= PNG_HAVE_CHUNK_HEADER; +#ifdef PNG_READ_APNG_SUPPORTED + if (png_ptr->chunk_name != png_fdAT && png_ptr->num_frames_read > 0) + { + if (png_ptr->flags & PNG_FLAG_ZSTREAM_ENDED) + { + png_ptr->process_mode = PNG_READ_CHUNK_MODE; + if (png_ptr->frame_end_fn != NULL) + (*(png_ptr->frame_end_fn))(png_ptr, png_ptr->num_frames_read); + png_ptr->num_frames_read++; + return; + } + else + { + if (png_ptr->chunk_name == png_IEND) + png_error(png_ptr, "Not enough image data"); + if (png_ptr->push_length + 4 > png_ptr->buffer_size) + { + png_push_save_buffer(png_ptr); + return; + } + png_warning(png_ptr, "Skipping (ignoring) a chunk between " + "APNG chunks"); + png_crc_finish(png_ptr, png_ptr->push_length); + png_ptr->mode &= ~PNG_HAVE_CHUNK_HEADER; + return; + } + } + else +#endif +#ifdef PNG_READ_APNG_SUPPORTED + if (png_ptr->chunk_name != png_IDAT && png_ptr->num_frames_read == 0) +#else if (png_ptr->chunk_name != png_IDAT) +#endif { png_ptr->process_mode = PNG_READ_CHUNK_MODE; if ((png_ptr->flags & PNG_FLAG_ZSTREAM_ENDED) == 0) png_error(png_ptr, "Not enough compressed data"); +#ifdef PNG_READ_APNG_SUPPORTED + if (png_ptr->frame_end_fn != NULL) + (*(png_ptr->frame_end_fn))(png_ptr, png_ptr->num_frames_read); + png_ptr->num_frames_read++; +#endif + return; } png_ptr->idat_size = png_ptr->push_length; + +#ifdef PNG_READ_APNG_SUPPORTED + if (png_ptr->num_frames_read > 0) + { + png_ensure_sequence_number(png_ptr, 4); + png_ptr->idat_size -= 4; + } +#endif } if (png_ptr->idat_size != 0 && png_ptr->save_buffer_size != 0) @@ -493,6 +672,15 @@ png_process_IDAT_data(png_structrp png_ptr, png_bytep buffer, if (!(buffer_length > 0) || buffer == NULL) png_error(png_ptr, "No IDAT data (internal error)"); +#ifdef PNG_READ_APNG_SUPPORTED + /* If the app is not APNG-aware, decode only the first frame */ + if (!(png_ptr->apng_flags & PNG_APNG_APP) && png_ptr->num_frames_read > 0) + { + png_ptr->flags |= PNG_FLAG_ZSTREAM_ENDED; + return; + } +#endif + /* This routine must process all the data it has been given * before returning, calling the row callback as required to * handle the uncompressed results. @@ -926,6 +1114,18 @@ png_set_progressive_read_fn(png_structrp png_ptr, png_voidp progressive_ptr, png_set_read_fn(png_ptr, progressive_ptr, png_push_fill_buffer); } +#ifdef PNG_READ_APNG_SUPPORTED +void PNGAPI +png_set_progressive_frame_fn(png_structp png_ptr, + png_progressive_frame_ptr frame_info_fn, + png_progressive_frame_ptr frame_end_fn) +{ + png_ptr->frame_info_fn = frame_info_fn; + png_ptr->frame_end_fn = frame_end_fn; + png_ptr->apng_flags |= PNG_APNG_APP; +} +#endif + png_voidp PNGAPI png_get_progressive_ptr(png_const_structrp png_ptr) { diff --git a/thirdparty/libpng/pngpriv.h b/thirdparty/libpng/pngpriv.h index d514dff5c1..d9d0956d45 100644 --- a/thirdparty/libpng/pngpriv.h +++ b/thirdparty/libpng/pngpriv.h @@ -620,6 +620,10 @@ #define PNG_HAVE_CHUNK_AFTER_IDAT 0x2000U /* Have another chunk after IDAT */ #define PNG_WROTE_eXIf 0x4000U #define PNG_IS_READ_STRUCT 0x8000U /* Else is a write struct */ +#ifdef PNG_APNG_SUPPORTED +#define PNG_HAVE_acTL 0x10000U +#define PNG_HAVE_fcTL 0x20000U +#endif /* Flags for the transformations the PNG library does on the image data */ #define PNG_BGR 0x0001U @@ -884,6 +888,13 @@ #define png_tRNS PNG_U32(116, 82, 78, 83) #define png_zTXt PNG_U32(122, 84, 88, 116) +#ifdef PNG_APNG_SUPPORTED + +/* For png_struct.apng_flags: */ +#define PNG_FIRST_FRAME_HIDDEN 0x0001U +#define PNG_APNG_APP 0x0002U +#endif + /* The following will work on (signed char*) strings, whereas the get_uint_32 * macro will fail on top-bit-set values because of the sign extension. */ @@ -1671,6 +1682,47 @@ PNG_INTERNAL_FUNCTION(void,png_read_push_finish_row,(png_structrp png_ptr), PNG_EMPTY); #endif /* PROGRESSIVE_READ */ +#ifdef PNG_APNG_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_ensure_fcTL_is_valid,(png_structp png_ptr, + png_uint_32 width, png_uint_32 height, + png_uint_32 x_offset, png_uint_32 y_offset, + png_uint_16 delay_num, png_uint_16 delay_den, + png_byte dispose_op, png_byte blend_op), PNG_EMPTY); + +#ifdef PNG_READ_APNG_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_handle_acTL,(png_structp png_ptr, png_infop info_ptr, + png_uint_32 length),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_handle_fcTL,(png_structp png_ptr, png_infop info_ptr, + png_uint_32 length),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_handle_fdAT,(png_structp png_ptr, png_infop info_ptr, + png_uint_32 length),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_have_info,(png_structp png_ptr, png_infop info_ptr),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_ensure_sequence_number,(png_structp png_ptr, + png_uint_32 length),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_read_reset,(png_structp png_ptr),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_read_reinit,(png_structp png_ptr, + png_infop info_ptr),PNG_EMPTY); +#ifdef PNG_PROGRESSIVE_READ_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_progressive_read_reset,(png_structp png_ptr),PNG_EMPTY); +#endif /* PNG_PROGRESSIVE_READ_SUPPORTED */ +#endif /* PNG_READ_APNG_SUPPORTED */ + +#ifdef PNG_WRITE_APNG_SUPPORTED +PNG_INTERNAL_FUNCTION(void,png_write_acTL,(png_structp png_ptr, + png_uint_32 num_frames, png_uint_32 num_plays),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_write_fcTL,(png_structp png_ptr, + png_uint_32 width, png_uint_32 height, + png_uint_32 x_offset, png_uint_32 y_offset, + png_uint_16 delay_num, png_uint_16 delay_den, + png_byte dispose_op, png_byte blend_op),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_write_fdAT,(png_structp png_ptr, + png_const_bytep data, png_size_t length),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_write_reset,(png_structp png_ptr),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_write_reinit,(png_structp png_ptr, + png_infop info_ptr, png_uint_32 width, png_uint_32 height),PNG_EMPTY); +#endif /* PNG_WRITE_APNG_SUPPORTED */ +#endif /* PNG_APNG_SUPPORTED */ + #ifdef PNG_iCCP_SUPPORTED /* Routines for checking parts of an ICC profile. */ #ifdef PNG_READ_iCCP_SUPPORTED diff --git a/thirdparty/libpng/pngread.c b/thirdparty/libpng/pngread.c index 0fd364827e..865c97e073 100644 --- a/thirdparty/libpng/pngread.c +++ b/thirdparty/libpng/pngread.c @@ -155,16 +155,96 @@ png_read_info(png_structrp png_ptr, png_inforp info_ptr) else if (chunk_name == png_IDAT) { +#ifdef PNG_READ_APNG_SUPPORTED + png_have_info(png_ptr, info_ptr); +#endif png_ptr->idat_size = length; break; } +#ifdef PNG_READ_APNG_SUPPORTED + else if (chunk_name == png_acTL) + png_handle_acTL(png_ptr, info_ptr, length); + + else if (chunk_name == png_fcTL) + png_handle_fcTL(png_ptr, info_ptr, length); + + else if (chunk_name == png_fdAT) + png_handle_fdAT(png_ptr, info_ptr, length); +#endif + else png_handle_chunk(png_ptr, info_ptr, length); } } #endif /* SEQUENTIAL_READ */ +#ifdef PNG_READ_APNG_SUPPORTED +void PNGAPI +png_read_frame_head(png_structp png_ptr, png_infop info_ptr) +{ + png_byte have_chunk_after_DAT; /* after IDAT or after fdAT */ + + png_debug(0, "Reading frame head"); + + if (!(png_ptr->mode & PNG_HAVE_acTL)) + png_error(png_ptr, "attempt to png_read_frame_head() but " + "no acTL present"); + + /* do nothing for the main IDAT */ + if (png_ptr->num_frames_read == 0) + return; + + png_read_reset(png_ptr); + png_ptr->flags &= ~PNG_FLAG_ROW_INIT; + png_ptr->mode &= ~PNG_HAVE_fcTL; + + have_chunk_after_DAT = 0; + for (;;) + { + png_uint_32 length = png_read_chunk_header(png_ptr); + + if (png_ptr->chunk_name == png_IDAT) + { + /* discard trailing IDATs for the first frame */ + if (have_chunk_after_DAT || png_ptr->num_frames_read > 1) + png_error(png_ptr, "png_read_frame_head(): out of place IDAT"); + png_crc_finish(png_ptr, length); + } + + else if (png_ptr->chunk_name == png_fcTL) + { + png_handle_fcTL(png_ptr, info_ptr, length); + have_chunk_after_DAT = 1; + } + + else if (png_ptr->chunk_name == png_fdAT) + { + png_ensure_sequence_number(png_ptr, length); + + /* discard trailing fdATs for frames other than the first */ + if (!have_chunk_after_DAT && png_ptr->num_frames_read > 1) + png_crc_finish(png_ptr, length - 4); + else if(png_ptr->mode & PNG_HAVE_fcTL) + { + png_ptr->idat_size = length - 4; + png_ptr->mode |= PNG_HAVE_IDAT; + + break; + } + else + png_error(png_ptr, "png_read_frame_head(): out of place fdAT"); + } + else + { + png_warning(png_ptr, "Skipped (ignored) a chunk " + "between APNG chunks"); + png_crc_finish(png_ptr, length); + } + } +} +#endif /* PNG_READ_APNG_SUPPORTED */ + /* Optional call to update the users info_ptr structure */ void PNGAPI png_read_update_info(png_structrp png_ptr, png_inforp info_ptr) diff --git a/thirdparty/libpng/pngrutil.c b/thirdparty/libpng/pngrutil.c index d0f3ed35d2..9eebf9e416 100644 --- a/thirdparty/libpng/pngrutil.c +++ b/thirdparty/libpng/pngrutil.c @@ -922,6 +922,11 @@ png_handle_IHDR(png_structrp png_ptr, png_inforp info_ptr, png_uint_32 length) filter_type = buf[11]; interlace_type = buf[12]; +#ifdef PNG_READ_APNG_SUPPORTED + png_ptr->first_frame_width = width; + png_ptr->first_frame_height = height; +#endif + /* Set internal variables */ png_ptr->width = width; png_ptr->height = height; @@ -2730,6 +2735,179 @@ png_handle_iTXt(png_structrp png_ptr, png_inforp info_ptr, png_uint_32 length) # define png_handle_iTXt NULL #endif +#ifdef PNG_READ_APNG_SUPPORTED +void /* PRIVATE */ +png_handle_acTL(png_structp png_ptr, png_infop info_ptr, png_uint_32 length) +{ + png_byte data[8]; + png_uint_32 num_frames; + png_uint_32 num_plays; + png_uint_32 didSet; + + png_debug(1, "in png_handle_acTL"); + + if (!(png_ptr->mode & PNG_HAVE_IHDR)) + { + png_error(png_ptr, "Missing IHDR before acTL"); + } + else if (png_ptr->mode & PNG_HAVE_IDAT) + { + png_warning(png_ptr, "Invalid acTL after IDAT skipped"); + png_crc_finish(png_ptr, length); + return; + } + else if (png_ptr->mode & PNG_HAVE_acTL) + { + png_warning(png_ptr, "Duplicate acTL skipped"); + png_crc_finish(png_ptr, length); + return; + } + else if (length != 8) + { + png_warning(png_ptr, "acTL with invalid length skipped"); + png_crc_finish(png_ptr, length); + return; + } + + png_crc_read(png_ptr, data, 8); + png_crc_finish(png_ptr, 0); + + num_frames = png_get_uint_31(png_ptr, data); + num_plays = png_get_uint_31(png_ptr, data + 4); + + /* the set function will do error checking on num_frames */ + didSet = png_set_acTL(png_ptr, info_ptr, num_frames, num_plays); + if(didSet) + png_ptr->mode |= PNG_HAVE_acTL; +} + +void /* PRIVATE */ +png_handle_fcTL(png_structp png_ptr, png_infop info_ptr, png_uint_32 length) +{ + png_byte data[22]; + png_uint_32 width; + png_uint_32 height; + png_uint_32 x_offset; + png_uint_32 y_offset; + png_uint_16 delay_num; + png_uint_16 delay_den; + png_byte dispose_op; + png_byte blend_op; + + png_debug(1, "in png_handle_fcTL"); + + png_ensure_sequence_number(png_ptr, length); + + if (!(png_ptr->mode & PNG_HAVE_IHDR)) + { + png_error(png_ptr, "Missing IHDR before fcTL"); + } + else if (png_ptr->mode & PNG_HAVE_IDAT) + { + /* for any frames other then the first this message may be misleading, + * but correct. PNG_HAVE_IDAT is unset before the frame head is read + * i can't think of a better message */ + png_warning(png_ptr, "Invalid fcTL after IDAT skipped"); + png_crc_finish(png_ptr, length-4); + return; + } + else if (png_ptr->mode & PNG_HAVE_fcTL) + { + png_warning(png_ptr, "Duplicate fcTL within one frame skipped"); + png_crc_finish(png_ptr, length-4); + return; + } + else if (length != 26) + { + png_warning(png_ptr, "fcTL with invalid length skipped"); + png_crc_finish(png_ptr, length-4); + return; + } + + png_crc_read(png_ptr, data, 22); + png_crc_finish(png_ptr, 0); + + width = png_get_uint_31(png_ptr, data); + height = png_get_uint_31(png_ptr, data + 4); + x_offset = png_get_uint_31(png_ptr, data + 8); + y_offset = png_get_uint_31(png_ptr, data + 12); + delay_num = png_get_uint_16(data + 16); + delay_den = png_get_uint_16(data + 18); + dispose_op = data[20]; + blend_op = data[21]; + + if (png_ptr->num_frames_read == 0 && (x_offset != 0 || y_offset != 0)) + { + png_warning(png_ptr, "fcTL for the first frame must have zero offset"); + return; + } + + if (info_ptr != NULL) + { + if (png_ptr->num_frames_read == 0 && + (width != info_ptr->width || height != info_ptr->height)) + { + png_warning(png_ptr, "size in first frame's fcTL must match " + "the size in IHDR"); + return; + } + + /* The set function will do more error checking */ + png_set_next_frame_fcTL(png_ptr, info_ptr, width, height, + x_offset, y_offset, delay_num, delay_den, + dispose_op, blend_op); + + png_read_reinit(png_ptr, info_ptr); + + png_ptr->mode |= PNG_HAVE_fcTL; + } +} + +void /* PRIVATE */ +png_have_info(png_structp png_ptr, png_infop info_ptr) +{ + if((info_ptr->valid & PNG_INFO_acTL) && !(info_ptr->valid & PNG_INFO_fcTL)) + { + png_ptr->apng_flags |= PNG_FIRST_FRAME_HIDDEN; + info_ptr->num_frames++; + } +} + +void /* PRIVATE */ +png_handle_fdAT(png_structp png_ptr, png_infop info_ptr, png_uint_32 length) +{ + png_ensure_sequence_number(png_ptr, length); + + /* This function is only called from png_read_end(), png_read_info(), + * and png_push_read_chunk() which means that: + * - the user doesn't want to read this frame + * - or this is an out-of-place fdAT + * in either case it is safe to ignore the chunk with a warning */ + png_warning(png_ptr, "ignoring fdAT chunk"); + png_crc_finish(png_ptr, length - 4); + PNG_UNUSED(info_ptr) +} + +void /* PRIVATE */ +png_ensure_sequence_number(png_structp png_ptr, png_uint_32 length) +{ + png_byte data[4]; + png_uint_32 sequence_number; + + if (length < 4) + png_error(png_ptr, "invalid fcTL or fdAT chunk found"); + + png_crc_read(png_ptr, data, 4); + sequence_number = png_get_uint_31(png_ptr, data); + + if (sequence_number != png_ptr->next_seq_num) + png_error(png_ptr, "fcTL or fdAT chunk with out-of-order sequence " + "number found"); + + png_ptr->next_seq_num++; +} +#endif /* PNG_READ_APNG_SUPPORTED */ + #ifdef PNG_READ_UNKNOWN_CHUNKS_SUPPORTED /* Utility function for png_handle_unknown; set up png_ptr::unknown_chunk */ static int @@ -4212,7 +4390,38 @@ png_read_IDAT_data(png_structrp png_ptr, png_bytep output, { uInt avail_in; png_bytep buffer; +#ifdef PNG_READ_APNG_SUPPORTED + png_uint_32 bytes_to_skip = 0; + while (png_ptr->idat_size == 0 || bytes_to_skip != 0) + { + png_crc_finish(png_ptr, bytes_to_skip); + bytes_to_skip = 0; + + png_ptr->idat_size = png_read_chunk_header(png_ptr); + if (png_ptr->num_frames_read == 0) + { + if (png_ptr->chunk_name != png_IDAT) + png_error(png_ptr, "Not enough image data"); + } + else + { + if (png_ptr->chunk_name == png_IEND) + png_error(png_ptr, "Not enough image data"); + if (png_ptr->chunk_name != png_fdAT) + { + png_warning(png_ptr, "Skipped (ignored) a chunk " + "between APNG chunks"); + bytes_to_skip = png_ptr->idat_size; + continue; + } + + png_ensure_sequence_number(png_ptr, png_ptr->idat_size); + + png_ptr->idat_size -= 4; + } + } +#else while (png_ptr->idat_size == 0) { png_crc_finish(png_ptr, 0); @@ -4224,7 +4433,7 @@ png_read_IDAT_data(png_structrp png_ptr, png_bytep output, if (png_ptr->chunk_name != png_IDAT) png_error(png_ptr, "Not enough image data"); } - +#endif /* PNG_READ_APNG_SUPPORTED */ avail_in = png_ptr->IDAT_read_size; if (avail_in > png_chunk_max(png_ptr)) @@ -4295,6 +4504,9 @@ png_read_IDAT_data(png_structrp png_ptr, png_bytep output, png_ptr->mode |= PNG_AFTER_IDAT; png_ptr->flags |= PNG_FLAG_ZSTREAM_ENDED; +#ifdef PNG_READ_APNG_SUPPORTED + png_ptr->num_frames_read++; +#endif if (png_ptr->zstream.avail_in > 0 || png_ptr->idat_size > 0) png_chunk_benign_error(png_ptr, "Extra compressed data"); @@ -4704,4 +4916,80 @@ defined(PNG_USER_TRANSFORM_PTR_SUPPORTED) png_ptr->flags |= PNG_FLAG_ROW_INIT; } + +#ifdef PNG_READ_APNG_SUPPORTED +/* This function is to be called after the main IDAT set has been read and + * before a new IDAT is read. It resets some parts of png_ptr + * to make them usable by the read functions again */ +void /* PRIVATE */ +png_read_reset(png_structp png_ptr) +{ + png_ptr->mode &= ~PNG_HAVE_IDAT; + png_ptr->mode &= ~PNG_AFTER_IDAT; + png_ptr->row_number = 0; + png_ptr->pass = 0; +} + +void /* PRIVATE */ +png_read_reinit(png_structp png_ptr, png_infop info_ptr) +{ + png_ptr->width = info_ptr->next_frame_width; + png_ptr->height = info_ptr->next_frame_height; + png_ptr->rowbytes = PNG_ROWBYTES(png_ptr->pixel_depth,png_ptr->width); + png_ptr->info_rowbytes = PNG_ROWBYTES(info_ptr->pixel_depth, + png_ptr->width); + if (png_ptr->prev_row) + memset(png_ptr->prev_row, 0, png_ptr->rowbytes + 1); +} + +#ifdef PNG_PROGRESSIVE_READ_SUPPORTED +/* same as png_read_reset() but for the progressive reader */ +void /* PRIVATE */ +png_progressive_read_reset(png_structp png_ptr) +{ +#ifdef PNG_READ_INTERLACING_SUPPORTED + /* Arrays to facilitate easy interlacing - use pass (0 - 6) as index */ + + /* Start of interlace block */ + const int png_pass_start[] = {0, 4, 0, 2, 0, 1, 0}; + + /* Offset to next interlace block */ + const int png_pass_inc[] = {8, 8, 4, 4, 2, 2, 1}; + + /* Start of interlace block in the y direction */ + const int png_pass_ystart[] = {0, 0, 4, 0, 2, 0, 1}; + + /* Offset to next interlace block in the y direction */ + const int png_pass_yinc[] = {8, 8, 8, 4, 4, 2, 2}; + + if (png_ptr->interlaced) + { + if (!(png_ptr->transformations & PNG_INTERLACE)) + png_ptr->num_rows = (png_ptr->height + png_pass_yinc[0] - 1 - + png_pass_ystart[0]) / png_pass_yinc[0]; + else + png_ptr->num_rows = png_ptr->height; + + png_ptr->iwidth = (png_ptr->width + + png_pass_inc[png_ptr->pass] - 1 - + png_pass_start[png_ptr->pass]) / + png_pass_inc[png_ptr->pass]; + } + else +#endif /* PNG_READ_INTERLACING_SUPPORTED */ + { + png_ptr->num_rows = png_ptr->height; + png_ptr->iwidth = png_ptr->width; + } + png_ptr->flags &= ~PNG_FLAG_ZSTREAM_ENDED; + if (inflateReset(&(png_ptr->zstream)) != Z_OK) + png_error(png_ptr, "inflateReset failed"); + png_ptr->zstream.avail_in = 0; + png_ptr->zstream.next_in = 0; + png_ptr->zstream.next_out = png_ptr->row_buf; + png_ptr->zstream.avail_out = (uInt)PNG_ROWBYTES(png_ptr->pixel_depth, + png_ptr->iwidth) + 1; +} +#endif /* PNG_PROGRESSIVE_READ_SUPPORTED */ +#endif /* PNG_READ_APNG_SUPPORTED */ #endif /* READ */ diff --git a/thirdparty/libpng/pngset.c b/thirdparty/libpng/pngset.c index d7f3393c43..65456686a5 100644 --- a/thirdparty/libpng/pngset.c +++ b/thirdparty/libpng/pngset.c @@ -463,6 +463,11 @@ png_set_IHDR(png_const_structrp png_ptr, png_inforp info_ptr, info_ptr->pixel_depth = (png_byte)(info_ptr->channels * info_ptr->bit_depth); info_ptr->rowbytes = PNG_ROWBYTES(info_ptr->pixel_depth, width); + +#ifdef PNG_APNG_SUPPORTED + /* for non-animated png. this may be overwritten from an acTL chunk later */ + info_ptr->num_frames = 1; +#endif } #ifdef PNG_oFFs_SUPPORTED @@ -1318,6 +1323,147 @@ png_set_sPLT(png_const_structrp png_ptr, } #endif /* sPLT */ +#ifdef PNG_APNG_SUPPORTED +png_uint_32 PNGAPI +png_set_acTL(png_structp png_ptr, png_infop info_ptr, + png_uint_32 num_frames, png_uint_32 num_plays) +{ + png_debug1(1, "in %s storage function", "acTL"); + + if (png_ptr == NULL || info_ptr == NULL) + { + png_warning(png_ptr, + "Call to png_set_acTL() with NULL png_ptr " + "or info_ptr ignored"); + return (0); + } + if (num_frames == 0) + { + png_warning(png_ptr, + "Ignoring attempt to set acTL with num_frames zero"); + return (0); + } + if (num_frames > PNG_UINT_31_MAX) + { + png_warning(png_ptr, + "Ignoring attempt to set acTL with num_frames > 2^31-1"); + return (0); + } + if (num_plays > PNG_UINT_31_MAX) + { + png_warning(png_ptr, + "Ignoring attempt to set acTL with num_plays " + "> 2^31-1"); + return (0); + } + + info_ptr->num_frames = num_frames; + info_ptr->num_plays = num_plays; + + info_ptr->valid |= PNG_INFO_acTL; + + return (1); +} + +/* delay_num and delay_den can hold any 16-bit values including zero */ +png_uint_32 PNGAPI +png_set_next_frame_fcTL(png_structp png_ptr, png_infop info_ptr, + png_uint_32 width, png_uint_32 height, + png_uint_32 x_offset, png_uint_32 y_offset, + png_uint_16 delay_num, png_uint_16 delay_den, + png_byte dispose_op, png_byte blend_op) +{ + png_debug1(1, "in %s storage function", "fcTL"); + + if (png_ptr == NULL || info_ptr == NULL) + { + png_warning(png_ptr, + "Call to png_set_fcTL() with NULL png_ptr or info_ptr " + "ignored"); + return (0); + } + + png_ensure_fcTL_is_valid(png_ptr, width, height, x_offset, y_offset, + delay_num, delay_den, dispose_op, blend_op); + + if (blend_op == PNG_BLEND_OP_OVER) + { + if (!(png_ptr->color_type & PNG_COLOR_MASK_ALPHA) && + !(png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))) + { + png_warning(png_ptr, "PNG_BLEND_OP_OVER is meaningless " + "and wasteful for opaque images, ignored"); + blend_op = PNG_BLEND_OP_SOURCE; + } + } + + info_ptr->next_frame_width = width; + info_ptr->next_frame_height = height; + info_ptr->next_frame_x_offset = x_offset; + info_ptr->next_frame_y_offset = y_offset; + info_ptr->next_frame_delay_num = delay_num; + info_ptr->next_frame_delay_den = delay_den; + info_ptr->next_frame_dispose_op = dispose_op; + info_ptr->next_frame_blend_op = blend_op; + + info_ptr->valid |= PNG_INFO_fcTL; + + return (1); +} + +void /* PRIVATE */ +png_ensure_fcTL_is_valid(png_structp png_ptr, + png_uint_32 width, png_uint_32 height, + png_uint_32 x_offset, png_uint_32 y_offset, + png_uint_16 delay_num, png_uint_16 delay_den, + png_byte dispose_op, png_byte blend_op) +{ + if (width == 0 || width > PNG_UINT_31_MAX) + png_error(png_ptr, "invalid width in fcTL (> 2^31-1)"); + if (height == 0 || height > PNG_UINT_31_MAX) + png_error(png_ptr, "invalid height in fcTL (> 2^31-1)"); + if (x_offset > PNG_UINT_31_MAX) + png_error(png_ptr, "invalid x_offset in fcTL (> 2^31-1)"); + if (y_offset > PNG_UINT_31_MAX) + png_error(png_ptr, "invalid y_offset in fcTL (> 2^31-1)"); + if (width + x_offset > png_ptr->first_frame_width || + height + y_offset > png_ptr->first_frame_height) + png_error(png_ptr, "dimensions of a frame are greater than" + "the ones in IHDR"); + + if (dispose_op != PNG_DISPOSE_OP_NONE && + dispose_op != PNG_DISPOSE_OP_BACKGROUND && + dispose_op != PNG_DISPOSE_OP_PREVIOUS) + png_error(png_ptr, "invalid dispose_op in fcTL"); + + if (blend_op != PNG_BLEND_OP_SOURCE && + blend_op != PNG_BLEND_OP_OVER) + png_error(png_ptr, "invalid blend_op in fcTL"); + + PNG_UNUSED(delay_num) + PNG_UNUSED(delay_den) +} + +png_uint_32 PNGAPI +png_set_first_frame_is_hidden(png_structp png_ptr, png_infop info_ptr, + png_byte is_hidden) +{ + png_debug(1, "in png_first_frame_is_hidden()"); + + if (png_ptr == NULL) + return 0; + + if (is_hidden) + png_ptr->apng_flags |= PNG_FIRST_FRAME_HIDDEN; + else + png_ptr->apng_flags &= ~PNG_FIRST_FRAME_HIDDEN; + + PNG_UNUSED(info_ptr) + + return 1; +} +#endif /* PNG_APNG_SUPPORTED */ + #ifdef PNG_STORE_UNKNOWN_CHUNKS_SUPPORTED static png_byte check_location(png_const_structrp png_ptr, int location) diff --git a/thirdparty/libpng/pngstruct.h b/thirdparty/libpng/pngstruct.h index 324424495e..d436a78f06 100644 --- a/thirdparty/libpng/pngstruct.h +++ b/thirdparty/libpng/pngstruct.h @@ -392,6 +392,27 @@ struct png_struct_def png_byte filter_type; #endif +#ifdef PNG_APNG_SUPPORTED + png_uint_32 apng_flags; + png_uint_32 next_seq_num; /* next fcTL/fdAT chunk sequence number */ + png_uint_32 first_frame_width; + png_uint_32 first_frame_height; + +#ifdef PNG_READ_APNG_SUPPORTED + png_uint_32 num_frames_read; /* incremented after all image data of */ + /* a frame is read */ +#ifdef PNG_PROGRESSIVE_READ_SUPPORTED + png_progressive_frame_ptr frame_info_fn; /* frame info read callback */ + png_progressive_frame_ptr frame_end_fn; /* frame data read callback */ +#endif +#endif + +#ifdef PNG_WRITE_APNG_SUPPORTED + png_uint_32 num_frames_to_write; + png_uint_32 num_frames_written; +#endif +#endif /* PNG_APNG_SUPPORTED */ + /* New members added in libpng-1.2.0 */ /* New members added in libpng-1.0.2 but first enabled by default in 1.2.0 */ diff --git a/thirdparty/libpng/pngwrite.c b/thirdparty/libpng/pngwrite.c index b7aeff4ce4..f1dc4c7e5b 100644 --- a/thirdparty/libpng/pngwrite.c +++ b/thirdparty/libpng/pngwrite.c @@ -127,6 +127,11 @@ png_write_info_before_PLTE(png_structrp png_ptr, png_const_inforp info_ptr) * the application continues writing the PNG. So check the 'invalid' * flag here too. */ +#ifdef PNG_WRITE_APNG_SUPPORTED + if ((info_ptr->valid & PNG_INFO_acTL) != 0) + png_write_acTL(png_ptr, info_ptr->num_frames, info_ptr->num_plays); +#endif + #ifdef PNG_WRITE_UNKNOWN_CHUNKS_SUPPORTED /* Write unknown chunks first; PNG v3 establishes a precedence order * for colourspace chunks. It is certain therefore that new @@ -405,6 +410,11 @@ png_write_end(png_structrp png_ptr, png_inforp info_ptr) png_benign_error(png_ptr, "Wrote palette index exceeding num_palette"); #endif +#ifdef PNG_WRITE_APNG_SUPPORTED + if (png_ptr->num_frames_written != png_ptr->num_frames_to_write) + png_error(png_ptr, "Not enough frames written"); +#endif + /* See if user wants us to write information chunks */ if (info_ptr != NULL) { @@ -1515,6 +1525,43 @@ png_write_png(png_structrp png_ptr, png_inforp info_ptr, } #endif +#ifdef PNG_WRITE_APNG_SUPPORTED +void PNGAPI +png_write_frame_head(png_structp png_ptr, png_infop info_ptr, + png_bytepp row_pointers, png_uint_32 width, png_uint_32 height, + png_uint_32 x_offset, png_uint_32 y_offset, + png_uint_16 delay_num, png_uint_16 delay_den, png_byte dispose_op, + png_byte blend_op) +{ + png_debug(1, "in png_write_frame_head"); + + /* there is a chance this has been set after png_write_info was called, + * so it would be set but not written. is there a way to be sure? */ + if (!(info_ptr->valid & PNG_INFO_acTL)) + png_error(png_ptr, "png_write_frame_head(): acTL not set"); + + png_write_reset(png_ptr); + + png_write_reinit(png_ptr, info_ptr, width, height); + + if ( !(png_ptr->num_frames_written == 0 && + (png_ptr->apng_flags & PNG_FIRST_FRAME_HIDDEN) ) ) + png_write_fcTL(png_ptr, width, height, x_offset, y_offset, + delay_num, delay_den, dispose_op, blend_op); + + PNG_UNUSED(row_pointers) +} + +void PNGAPI +png_write_frame_tail(png_structp png_ptr, png_infop info_ptr) +{ + png_debug(1, "in png_write_frame_tail"); + + png_ptr->num_frames_written++; + + PNG_UNUSED(info_ptr) +} +#endif /* PNG_WRITE_APNG_SUPPORTED */ #ifdef PNG_SIMPLIFIED_WRITE_SUPPORTED /* Initialize the write structure - general purpose utility. */ diff --git a/thirdparty/libpng/pngwutil.c b/thirdparty/libpng/pngwutil.c index be706afe64..297534c5d2 100644 --- a/thirdparty/libpng/pngwutil.c +++ b/thirdparty/libpng/pngwutil.c @@ -838,6 +838,11 @@ png_write_IHDR(png_structrp png_ptr, png_uint_32 width, png_uint_32 height, /* Write the chunk */ png_write_complete_chunk(png_ptr, png_IHDR, buf, 13); +#ifdef PNG_WRITE_APNG_SUPPORTED + png_ptr->first_frame_width = width; + png_ptr->first_frame_height = height; +#endif + if ((png_ptr->do_filter) == PNG_NO_FILTERS) { if (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE || @@ -1019,8 +1024,17 @@ png_compress_IDAT(png_structrp png_ptr, png_const_bytep input, optimize_cmf(data, png_image_size(png_ptr)); #endif - if (size > 0) - png_write_complete_chunk(png_ptr, png_IDAT, data, size); + if (size > 0) +#ifdef PNG_WRITE_APNG_SUPPORTED + { + if (png_ptr->num_frames_written == 0) +#endif + png_write_complete_chunk(png_ptr, png_IDAT, data, size); +#ifdef PNG_WRITE_APNG_SUPPORTED + else + png_write_fdAT(png_ptr, data, size); + } +#endif /* PNG_WRITE_APNG_SUPPORTED */ png_ptr->mode |= PNG_HAVE_IDAT; png_ptr->zstream.next_out = data; @@ -1067,7 +1081,17 @@ png_compress_IDAT(png_structrp png_ptr, png_const_bytep input, #endif if (size > 0) +#ifdef PNG_WRITE_APNG_SUPPORTED + { + if (png_ptr->num_frames_written == 0) +#endif png_write_complete_chunk(png_ptr, png_IDAT, data, size); +#ifdef PNG_WRITE_APNG_SUPPORTED + else + png_write_fdAT(png_ptr, data, size); + } +#endif /* PNG_WRITE_APNG_SUPPORTED */ + png_ptr->zstream.avail_out = 0; png_ptr->zstream.next_out = NULL; png_ptr->mode |= PNG_HAVE_IDAT | PNG_AFTER_IDAT; @@ -1969,6 +1993,82 @@ png_write_tIME(png_structrp png_ptr, png_const_timep mod_time) } #endif +#ifdef PNG_WRITE_APNG_SUPPORTED +void /* PRIVATE */ +png_write_acTL(png_structp png_ptr, + png_uint_32 num_frames, png_uint_32 num_plays) +{ + png_byte buf[8]; + + png_debug(1, "in png_write_acTL"); + + png_ptr->num_frames_to_write = num_frames; + + if (png_ptr->apng_flags & PNG_FIRST_FRAME_HIDDEN) + num_frames--; + + png_save_uint_32(buf, num_frames); + png_save_uint_32(buf + 4, num_plays); + + png_write_complete_chunk(png_ptr, png_acTL, buf, (png_size_t)8); +} + +void /* PRIVATE */ +png_write_fcTL(png_structp png_ptr, png_uint_32 width, png_uint_32 height, + png_uint_32 x_offset, png_uint_32 y_offset, + png_uint_16 delay_num, png_uint_16 delay_den, png_byte dispose_op, + png_byte blend_op) +{ + png_byte buf[26]; + + png_debug(1, "in png_write_fcTL"); + + if (png_ptr->num_frames_written == 0 && (x_offset != 0 || y_offset != 0)) + png_error(png_ptr, "x and/or y offset for the first frame aren't 0"); + if (png_ptr->num_frames_written == 0 && + (width != png_ptr->first_frame_width || + height != png_ptr->first_frame_height)) + png_error(png_ptr, "width and/or height in the first frame's fcTL " + "don't match the ones in IHDR"); + + /* more error checking */ + png_ensure_fcTL_is_valid(png_ptr, width, height, x_offset, y_offset, + delay_num, delay_den, dispose_op, blend_op); + + png_save_uint_32(buf, png_ptr->next_seq_num); + png_save_uint_32(buf + 4, width); + png_save_uint_32(buf + 8, height); + png_save_uint_32(buf + 12, x_offset); + png_save_uint_32(buf + 16, y_offset); + png_save_uint_16(buf + 20, delay_num); + png_save_uint_16(buf + 22, delay_den); + buf[24] = dispose_op; + buf[25] = blend_op; + + png_write_complete_chunk(png_ptr, png_fcTL, buf, (png_size_t)26); + + png_ptr->next_seq_num++; +} + +void /* PRIVATE */ +png_write_fdAT(png_structp png_ptr, + png_const_bytep data, png_size_t length) +{ + png_byte buf[4]; + + png_write_chunk_header(png_ptr, png_fdAT, (png_uint_32)(4 + length)); + + png_save_uint_32(buf, png_ptr->next_seq_num); + png_write_chunk_data(png_ptr, buf, 4); + + png_write_chunk_data(png_ptr, data, length); + + png_write_chunk_end(png_ptr); + + png_ptr->next_seq_num++; +} +#endif /* PNG_WRITE_APNG_SUPPORTED */ + /* Initializes the row writing capability of libpng */ void /* PRIVATE */ png_write_start_row(png_structrp png_ptr) @@ -2822,4 +2922,39 @@ png_write_filtered_row(png_structrp png_ptr, png_bytep filtered_row, } #endif /* WRITE_FLUSH */ } + +#ifdef PNG_WRITE_APNG_SUPPORTED +void /* PRIVATE */ +png_write_reset(png_structp png_ptr) +{ + png_ptr->row_number = 0; + png_ptr->pass = 0; + png_ptr->mode &= ~PNG_HAVE_IDAT; +} + +void /* PRIVATE */ +png_write_reinit(png_structp png_ptr, png_infop info_ptr, + png_uint_32 width, png_uint_32 height) +{ + if (png_ptr->num_frames_written == 0 && + (width != png_ptr->first_frame_width || + height != png_ptr->first_frame_height)) + png_error(png_ptr, "width and/or height in the first frame's fcTL " + "don't match the ones in IHDR"); + if (width > png_ptr->first_frame_width || + height > png_ptr->first_frame_height) + png_error(png_ptr, "width and/or height for a frame greater than" + "the ones in IHDR"); + + png_set_IHDR(png_ptr, info_ptr, width, height, + info_ptr->bit_depth, info_ptr->color_type, + info_ptr->interlace_type, info_ptr->compression_type, + info_ptr->filter_type); + + png_ptr->width = width; + png_ptr->height = height; + png_ptr->rowbytes = PNG_ROWBYTES(png_ptr->pixel_depth, width); + png_ptr->usr_width = png_ptr->width; +} +#endif /* PNG_WRITE_APNG_SUPPORTED */ #endif /* WRITE */