* Copyright (c) 2026 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "apng.h"
#include "png.h"
#include <cstring>
#include <map>
static const png_byte PNG_CHUNK_ACTL[5] = {'a', 'c', 'T', 'L', '\0'};
static const png_byte PNG_CHUNK_FCTL[5] = {'f', 'c', 'T', 'L', '\0'};
static const png_byte PNG_CHUNK_FDAT[5] = {'f', 'd', 'A', 'T', '\0'};
constexpr size_t CHUNK_NAME_SIZE = 4;
static std::map<void *, png_uint_32> g_frameIndexMap;
static png_uint_32 ReadChunkData(png_const_structrp pngPtr, png_inforp infoPtr, const png_byte *chunkName,
png_bytep data, png_uint_32 length)
{
png_unknown_chunkp unknowns;
int numUnknowns = png_get_unknown_chunks(const_cast<png_structrp>(pngPtr), infoPtr, &unknowns);
for (int i = 0; i < numUnknowns; i++) {
if (memcmp(unknowns[i].name, chunkName, CHUNK_NAME_SIZE) == 0) {
if (unknowns[i].size >= length) {
std::copy_n(unknowns[i].data, length, data);
return 1;
}
}
}
return 0;
}
static png_uint_32 ReadFcTLChunk(png_structrp pngPtr, png_inforp infoPtr, png_uint_32 frameIndex, png_bytep data,
png_uint_32 length)
{
png_unknown_chunkp unknowns;
int numUnknowns = png_get_unknown_chunks(pngPtr, infoPtr, &unknowns);
png_uint_32 tlCount = 0;
for (int i = 0; i < numUnknowns; i++) {
if (memcmp(unknowns[i].name, PNG_CHUNK_FCTL, CHUNK_NAME_SIZE) == 0) {
if (tlCount == frameIndex && unknowns[i].size >= length) {
std::copy_n(unknowns[i].data, length, data);
return 1;
}
tlCount++;
}
}
return 0;
}
constexpr png_uint_32 ACTL_DATA_BYTE_COUNT = 8;
constexpr size_t ACTL_NUM_FRAMES_OFFSET = 0;
constexpr size_t ACTL_NUM_PLAYS_OFFSET = 4;
constexpr png_uint_32 FCTL_DATA_BYTE_COUNT = 26;
constexpr size_t FCTL_WIDTH_OFFSET = 0;
constexpr size_t FCTL_HEIGHT_OFFSET = 4;
constexpr size_t FCTL_X_OFFSET_OFFSET = 8;
constexpr size_t FCTL_Y_OFFSET_OFFSET = 12;
constexpr size_t FCTL_DELAY_NUM_OFFSET = 16;
constexpr size_t FCTL_DELAY_DEN_OFFSET = 18;
constexpr size_t FCTL_DISPOSE_OP_OFFSET = 20;
constexpr size_t FCTL_BLEND_OP_OFFSET = 21;
struct FcTlOutputSlots {
png_uint_32 *width;
png_uint_32 *height;
png_uint_32 *xOffset;
png_uint_32 *yOffset;
png_uint_16 *delayNum;
png_uint_16 *delayDen;
png_byte *disposeOp;
png_byte *blendOp;
};
namespace {
void AssignFcTLFromBuffer(const png_byte *data, const FcTlOutputSlots& out)
{
if (out.width) {
*out.width = png_get_uint_32(data + FCTL_WIDTH_OFFSET);
}
if (out.height) {
*out.height = png_get_uint_32(data + FCTL_HEIGHT_OFFSET);
}
if (out.xOffset) {
*out.xOffset = png_get_uint_32(data + FCTL_X_OFFSET_OFFSET);
}
if (out.yOffset) {
*out.yOffset = png_get_uint_32(data + FCTL_Y_OFFSET_OFFSET);
}
if (out.delayNum) {
*out.delayNum = png_get_uint_16(data + FCTL_DELAY_NUM_OFFSET);
}
if (out.delayDen) {
*out.delayDen = png_get_uint_16(data + FCTL_DELAY_DEN_OFFSET);
}
if (out.disposeOp) {
*out.disposeOp = data[FCTL_DISPOSE_OP_OFFSET];
}
if (out.blendOp) {
*out.blendOp = data[FCTL_BLEND_OP_OFFSET];
}
}
png_uint_32 ReadAndApplyNextFcTL(png_structrp pngPtr, png_inforp infoPtr, const FcTlOutputSlots& out)
{
png_uint_32 frameIndex = 0;
void *key = const_cast<void *>(reinterpret_cast<const void *>(pngPtr));
auto it = g_frameIndexMap.find(key);
if (it != g_frameIndexMap.end()) {
frameIndex = it->second;
}
png_byte data[FCTL_DATA_BYTE_COUNT];
if (ReadFcTLChunk(pngPtr, infoPtr, frameIndex, data, FCTL_DATA_BYTE_COUNT)) {
AssignFcTLFromBuffer(data, out);
g_frameIndexMap[key] = frameIndex + 1;
return 1;
}
return 0;
}
}
PNG_EXPORT_TYPE(png_uint_32)
PNGAPI png_get_acTL PNGARG((png_const_structrp pngPtr, png_inforp infoPtr, png_uint_32 *numFrames,
png_uint_32 *numPlays))
{
if (!pngPtr || !infoPtr || !numFrames || !numPlays) {
return 0;
}
png_byte data[ACTL_DATA_BYTE_COUNT];
if (ReadChunkData(pngPtr, infoPtr, PNG_CHUNK_ACTL, data, ACTL_DATA_BYTE_COUNT)) {
*numFrames = png_get_uint_32(data + ACTL_NUM_FRAMES_OFFSET);
*numPlays = png_get_uint_32(data + ACTL_NUM_PLAYS_OFFSET);
return 1;
}
return 0;
}
PNG_EXPORT_TYPE(png_uint_32)
PNGAPI png_get_next_frame_fcTL PNGARG((png_structrp pngPtr, png_inforp infoPtr, png_uint_32 *width,
png_uint_32 *height, png_uint_32 *xOffset, png_uint_32 *yOffset, png_uint_16 *delayNum, png_uint_16 *delayDen,
png_byte *disposeOp, png_byte *blendOp))
{
if (!pngPtr || !infoPtr) {
return 0;
}
const FcTlOutputSlots out = { width, height, xOffset, yOffset, delayNum, delayDen, disposeOp, blendOp };
return ReadAndApplyNextFcTL(pngPtr, infoPtr, out);
}
PNG_EXPORT_TYPE(void) PNGAPI png_read_frame_head PNGARG((png_structrp pngPtr, png_inforp infoPtr))
{
if (pngPtr && infoPtr) {
}
}
PNG_EXPORT_TYPE(png_uint_32)
PNGAPI png_get_first_frame_is_hidden PNGARG((png_const_structrp pngPtr, png_const_inforp infoPtr))
{
return 0;
}