snippet/uncompng: support APNG (Animated PNG)
diff --git a/example/convert-to-nia/convert-to-nia.c b/example/convert-to-nia/convert-to-nia.c index 5e7a4e8..ef5abc7 100644 --- a/example/convert-to-nia/convert-to-nia.c +++ b/example/convert-to-nia/convert-to-nia.c
@@ -255,6 +255,17 @@ bool output_nia_or_crc32_digest; // Implicitly set. bool output_nie; bool output_uncompressed_png; + + // There is no "bool output_uncompressed_apng" option. At the file format + // level, writing an Animated PNG requires knowing the total number of frames + // very early in the output (to write in the acTL chunk). In contrast, + // convert-to-nia can convert from image formats (such as GIF) that do not + // have an explicit "number of frames" record, only an implicit one (like + // NIA), calculated by parsing every frame. But convert-to-nia also does not + // assume that its input or output is rewindable. It is designed to operate + // under a SECCOMP_MODE_STRICT sandbox in O(1) memory, where it only reads + // its input or write its output forward, as non-seekable streams. + } g_flags = {0}; const char* //
diff --git a/lib/uncompng/uncompng.go b/lib/uncompng/uncompng.go index fcb8003..87ac5a2 100644 --- a/lib/uncompng/uncompng.go +++ b/lib/uncompng/uncompng.go
@@ -764,13 +764,12 @@ fType |= frameTypeBitLastFrame } - ej := e.encodeFrameHeader(w, delayNumerator, delayDenominator) + ej := e.encodeFrameHeader(delayNumerator, delayDenominator) return e.enc.encodeFramePayload( w, fType, &e.seqNum, e.depth, e.colorType, int(e.width), int(e.height), pix, stride, ej) } func (e *AnimationEncoder) encodeFrameHeader( - w io.Writer, delayNumerator uint16, delayDenominator uint16) int {
diff --git a/script/convert-nia-to-apng.go b/script/convert-nia-to-apng.go new file mode 100644 index 0000000..79adca3 --- /dev/null +++ b/script/convert-nia-to-apng.go
@@ -0,0 +1,260 @@ +// Copyright 2026 The Wuffs Authors. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. +// +// SPDX-License-Identifier: Apache-2.0 OR MIT + +//go:build ignore +// +build ignore + +package main + +// convert-nie-to-png.go decodes NIA from stdin and encodes (animated) PNG to +// stdout. +// +// Usage: go run convert-nia-to-apng.go < foo.nia > foo.png + +import ( + "bytes" + "errors" + "io" + "os" + + "github.com/google/wuffs/lib/uncompng" +) + +func main() { + if err := main1(); err != nil { + os.Stderr.WriteString(err.Error() + "\n") + os.Exit(1) + } +} + +func main1() error { + // When writing APNG, we need to know (at the start of the file) the + // animation's number of frames, but when reading NIA, we only know that at + // the end of the file. + // + // We therefore use io.ReadAll, slurping the entire NIA data into memory + // instead of using the io.Reader streaming API directly, so that we can + // make two passes over the NIA data. + niaBytes, err := io.ReadAll(os.Stdin) + if err != nil { + return err + } else if len(niaBytes) < 24 { + return errors.New("input is not in the NIA format") + } + + premultiplied := niaBytes[6] == 'p' + depth8 := niaBytes[7] == '8' + width := u32le(niaBytes[8:]) + height := u32le(niaBytes[12:]) + padded := !depth8 && ((width & 1) != 0) && ((height & 1) != 0) + if (u32le(niaBytes[:]) != 0x41AF_C36E) || // 0x41AF_C36E is 'nïA'le. + (niaBytes[4] != 0xFF) || + (niaBytes[5] != 'b') || + ((niaBytes[6] != 'n') && (niaBytes[6] != 'p')) || + ((niaBytes[7] != '4') && (niaBytes[7] != '8')) || + (width >= 0x8000_0000) || + (height >= 0x8000_0000) { + return errors.New("input is not in the NIA format") + } else if (width > 0xFF_FFFF) || (height > 0xFF_FFFF) { + return errors.New("input is in an unsupported NIA format") + } + + nieSize := calculateNIESize(depth8, width, height) + if nieSize < 0 { + return errors.New("input image dimensions are unsupported (too large)") + } else if nieSize < 16 { + panic("unreachable") + } + + // NIA's depth8 is measured in *bytes* per *pixel*, but uncompng's depth is + // measured *bits* per *channel* (times four channels, for BGRA). + depth := uncompng.Depth8 + if depth8 { + depth = uncompng.Depth16 + } + + expectedNieHeader := append([]byte(nil), niaBytes[:16]...) + expectedNieHeader[3] = 'E' + + numFrames := uint64(0) + prevCDD := uint64(0) + lastCDD := uint64(0) + for buf := niaBytes[16:]; len(buf) >= 8; numFrames++ { + cdd := u64le(buf) + if (cdd >> 32) == 0x8000_0000 { + lastCDD = cdd + break + } else if ((cdd >> 32) > 0x8000_0000) || (cdd < prevCDD) { + return errors.New("bad CDD (Cumulative Display Duration)") + } + buf = buf[8:] + if int64(len(buf)) < nieSize { + return errors.New("bad NIE frame") + } else if !bytes.Equal(buf[:16], expectedNieHeader) { + return errors.New("bad NIE header") + } + buf = buf[nieSize:] + + if padded { + if len(buf) < 4 { + return errors.New("bad NIE padding") + } + buf = buf[4:] + } + prevCDD = cdd + } + + enc := uncompng.AnimationEncoder{} + if err := enc.EncodeHeader( + os.Stdout, depth, uncompng.ColorTypeNRGBA, + int(width), int(height), uint32(numFrames), uint32(lastCDD)); err != nil { + return err + } + + prevCDD = 0 + for buf := niaBytes[16:]; len(buf) >= 8; { + cdd := u64le(buf) + if (cdd >> 32) >= 0x8000_0000 { + break + } + buf = buf[8:] + + if depth8 { + swapBlueRedEndian8(buf[16:nieSize]) + if premultiplied { + unpremultiply8(buf[16:nieSize]) + } + } else { + swapBlueRedEndian4(buf[16:nieSize]) + if premultiplied { + unpremultiply4(buf[16:nieSize]) + } + } + + numer, denom := calculateDurationRatio(cdd - prevCDD) + if err := enc.EncodeFrame( + os.Stdout, buf[16:nieSize], 4*int(width), numer, denom); err != nil { + return err + } + + buf = buf[nieSize:] + + if padded { + buf = buf[4:] + } + prevCDD = cdd + } + + return nil +} + +// swapBlueRedEndian4 corrects for the fact that NIA uses little-endian BGRA +// order but the *Go* lib/uncompng library (unlike the *C* snippet/uncompng.c) +// uses big-endian RGBA order, the same as the Go standard library (and PNG). +func swapBlueRedEndian4(buf []byte) { + for ; len(buf) >= 4; buf = buf[4:] { + buf[0], buf[2] = buf[2], buf[0] + } +} + +func swapBlueRedEndian8(buf []byte) { + for ; len(buf) >= 8; buf = buf[8:] { + buf[0], buf[1], buf[4], buf[5] = buf[5], buf[4], buf[1], buf[0] + buf[2], buf[3], buf[6], buf[7] = buf[3], buf[2], buf[7], buf[6] + } +} + +func unpremultiply4(buf []byte) { + for ; len(buf) >= 4; buf = buf[4:] { + r := 0x101 * uint32(buf[0]) + g := 0x101 * uint32(buf[1]) + b := 0x101 * uint32(buf[2]) + a := 0x101 * uint32(buf[3]) + if (a == 0xFFFF) || (a == 0x0000) { + continue + } + r = (r * 0xFFFF) / a + g = (g * 0xFFFF) / a + b = (b * 0xFFFF) / a + buf[0] = byte(r >> 8) + buf[1] = byte(g >> 8) + buf[2] = byte(b >> 8) + } +} + +func unpremultiply8(buf []byte) { + for ; len(buf) >= 4; buf = buf[4:] { + r := (uint32(buf[0]) << 8) | uint32(buf[1]) + g := (uint32(buf[2]) << 8) | uint32(buf[3]) + b := (uint32(buf[4]) << 8) | uint32(buf[5]) + a := (uint32(buf[6]) << 8) | uint32(buf[7]) + if (a == 0xFFFF) || (a == 0x0000) { + continue + } + r = (r * 0xFFFF) / a + g = (g * 0xFFFF) / a + b = (b * 0xFFFF) / a + buf[0] = byte(r >> 8) + buf[1] = byte(r >> 0) + buf[2] = byte(g >> 8) + buf[3] = byte(g >> 0) + buf[4] = byte(b >> 8) + buf[5] = byte(b >> 0) + } +} + +// calculateDurationRatio converts from a time duration measured in flicks +// (frame-ticks, 1 / 705600000 of a second, to APNG's representation, which is +// a ratio of two uint16 values. +// +// TODO: be smarter about how to express a durationInFlicks that isn't an +// integer number of milliseconds (or is over 65.535 seconds). For now, just +// hard-code the denominator to 1000, meaning milliseconds, and round to +// nearest (capped at 65535) to get the numerator. +func calculateDurationRatio(durationInFlicks uint64) (numerator uint16, denominator uint16) { + const flicksPerMillisecond = 705600 + millis := (durationInFlicks + (flicksPerMillisecond / 2)) / flicksPerMillisecond + return uint16(min(0xFFFF, millis)), 1000 +} + +func calculateNIESize(depth8 bool, width uint32, height uint32) int64 { + const maxInt64 = (1 << 63) - 1 + const maxUint64 = (1 << 64) - 1 + + n := uint64(width) * uint64(height) * 4 + if depth8 { + if n > (maxUint64 / 2) { + return -1 + } + n *= 2 + } + if n > (maxInt64 - 16) { + return -1 + } + return int64(n + 16) +} + +func u32le(b []byte) uint32 { + return (uint32(b[0]) << 0) | + (uint32(b[1]) << 8) | + (uint32(b[2]) << 16) | + (uint32(b[3]) << 24) +} + +func u64le(b []byte) uint64 { + return (uint64(b[0]) << 0) | + (uint64(b[1]) << 8) | + (uint64(b[2]) << 16) | + (uint64(b[3]) << 24) | + (uint64(b[4]) << 32) | + (uint64(b[5]) << 40) | + (uint64(b[6]) << 48) | + (uint64(b[7]) << 56) +}
diff --git a/script/example-uncompng-apng-large.c b/script/example-uncompng-apng-large.c new file mode 100644 index 0000000..8a5b613 --- /dev/null +++ b/script/example-uncompng-apng-large.c
@@ -0,0 +1,193 @@ +// Copyright 2026 The Wuffs Authors. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. +// +// SPDX-License-Identifier: Apache-2.0 OR MIT + +// ---------------- + +// This exercises snippet/uncompng.c to write a large APNG image to stdout. +// +// It is a C port of lib/uncompng's TestAnimationEncoderLarge. +// +// Run it from the Wuffs root directory, the one that contains the +// wuffs-root-directory.txt file, so that it can find and read the +// test/data/hibiscus.regular.bmp file. + +#include <errno.h> +#include <fcntl.h> +#include <stdbool.h> +#include <stdio.h> +#include <unistd.h> + +#define UNCOMPNG_CONFIG__STATIC_FUNCTIONS +#define UNCOMPNG_IMPLEMENTATION +#include "../snippet/uncompng.c" + +#define WUFFS_CONFIG__MODULES +#define WUFFS_CONFIG__MODULE__BASE +#define WUFFS_CONFIG__MODULE__BMP +#define WUFFS_CONFIG__STATIC_FUNCTIONS +#define WUFFS_IMPLEMENTATION +#include "../release/c/wuffs-unsupported-snapshot.c" + +#define IMAGE_HEIGHT 442 +#define IMAGE_WIDTH 312 +#define NUM_FRAMES 5 +#define NUM_PLAYS 0 +#define SRC_BMP_SIZE 413850 + +static uint8_t g_src_bmp[SRC_BMP_SIZE]; +static uint8_t g_src_pixels[IMAGE_WIDTH * IMAGE_HEIGHT * 4]; + +bool // +load_src_bmp() { + int fd = open("test/data/hibiscus.regular.bmp", O_RDONLY, 0); + if (fd == -1) { + fprintf(stderr, "FAIL: open: %s\n", strerror(errno)); + return false; + } + + for (ssize_t num_read = 0; num_read < SRC_BMP_SIZE;) { + ssize_t n = read(fd, g_src_bmp + num_read, sizeof(g_src_bmp) - num_read); + if (n > 0) { + num_read += n; + } else if (n == 0) { + break; + } else if (errno == EINTR) { + // No-op. + } else { + close(fd); + fprintf(stderr, "FAIL: read: %s\n", strerror(errno)); + return false; + } + } + + close(fd); + return true; +} + +bool // +load_src_pixels() { + static wuffs_bmp__decoder dec; + + wuffs_base__io_buffer src = wuffs_base__make_io_buffer( + wuffs_base__make_slice_u8(g_src_bmp, SRC_BMP_SIZE), + wuffs_base__make_io_buffer_meta(SRC_BMP_SIZE, 0, 0, true)); + + wuffs_base__status status = wuffs_bmp__decoder__initialize( + &dec, sizeof dec, WUFFS_VERSION, WUFFS_INITIALIZE__DEFAULT_OPTIONS); + if (!wuffs_base__status__is_ok(&status)) { + fprintf(stderr, "FAIL: initialize: %s\n", + wuffs_base__status__message(&status)); + return false; + } + + wuffs_base__image_config ic; + status = wuffs_bmp__decoder__decode_image_config(&dec, &ic, &src); + if (!wuffs_base__status__is_ok(&status)) { + fprintf(stderr, "FAIL: decode_image_config: %s\n", + wuffs_base__status__message(&status)); + return false; + } + + uint32_t w = wuffs_base__pixel_config__width(&ic.pixcfg); + uint32_t h = wuffs_base__pixel_config__height(&ic.pixcfg); + uint32_t pixfmt = wuffs_base__pixel_config__pixel_format(&ic.pixcfg).repr; + if ((w != IMAGE_WIDTH) || (h != IMAGE_HEIGHT) || + (pixfmt != WUFFS_BASE__PIXEL_FORMAT__BGRX)) { + fprintf(stderr, "FAIL: decode_image_config: unexpected configuration\n"); + return false; + } + + if ((WUFFS_BASE__PIXEL_FORMAT__BGRX != UNCOMPNG__PIXEL_FORMAT__BGRX) || + (WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL != + UNCOMPNG__PIXEL_FORMAT__BGRA_NONPREMUL)) { + fprintf(stderr, "FAIL: wuffs and uncompng are incompatible\n"); + return false; + } + + wuffs_base__pixel_config__set(&ic.pixcfg, + WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL, + WUFFS_BASE__PIXEL_SUBSAMPLING__NONE, w, h); + + wuffs_base__pixel_buffer pb; + status = wuffs_base__pixel_buffer__set_from_slice( + &pb, &ic.pixcfg, + wuffs_base__make_slice_u8(&g_src_pixels[0], sizeof g_src_pixels)); + if (!wuffs_base__status__is_ok(&status)) { + fprintf(stderr, "FAIL: set_from_slice: %s\n", + wuffs_base__status__message(&status)); + return false; + } + + status = wuffs_bmp__decoder__decode_frame(&dec, &pb, &src, + WUFFS_BASE__PIXEL_BLEND__SRC, + wuffs_base__empty_slice_u8(), NULL); + if (!wuffs_base__status__is_ok(&status)) { + fprintf(stderr, "FAIL: decode_frame: %s\n", + wuffs_base__status__message(&status)); + return false; + } + + return true; +} + +int // +my_write_func(void* context, const uint8_t* data_ptr, size_t data_len) { + static const int stdout_fd = 1; + return (write(stdout_fd, data_ptr, data_len) < 0) ? -errno : 0; +} + +int // +main(int argc, char** argv) { + if (!load_src_bmp() || !load_src_pixels()) { + return 1; + } + + // Change "if (0)" to "if (1)" to write a still (not animated) PNG. + if (0) { + const int frame = 0; + return uncompng__encode( // + &my_write_func, NULL, // + UNCOMPNG__PIXEL_FORMAT__BGRA_NONPREMUL, IMAGE_WIDTH, IMAGE_HEIGHT, // + &g_src_pixels[0], sizeof(g_src_pixels), IMAGE_WIDTH * 4); + } + + static uint16_t delay_millis[NUM_FRAMES] = {300, 500, 600, 400, 200}; + + int err0 = uncompng__encode_apng_header( // + &my_write_func, NULL, // + UNCOMPNG__PIXEL_FORMAT__BGRA_NONPREMUL, IMAGE_WIDTH, IMAGE_HEIGHT, // + NUM_FRAMES, NUM_PLAYS); + if (err0) { + return err0; + } + + for (int frame = 0; frame < NUM_FRAMES; frame++) { + int err1 = uncompng__encode_apng_frame( // + &my_write_func, NULL, // + delay_millis[frame], 1000, // + &g_src_pixels[0], sizeof(g_src_pixels), IMAGE_WIDTH * 4); + if (err1) { + return err1; + } + + // Over time, set the R, G, B, A channel values to 0x80. + // + // Use RGBA order, not BGRA, to match the output of lib/uncompng's + // TestAnimationEncoderLarge, in Go. + if (frame < 4) { + static size_t order[4] = {2, 1, 0, 3}; + for (size_t i = order[frame]; i < sizeof(g_src_pixels); i += 4) { + g_src_pixels[i] = 0x80; + } + } + } + + return 0; +}
diff --git a/script/example-uncompng-apng-small.c b/script/example-uncompng-apng-small.c new file mode 100644 index 0000000..12913b7 --- /dev/null +++ b/script/example-uncompng-apng-small.c
@@ -0,0 +1,87 @@ +// Copyright 2026 The Wuffs Authors. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. +// +// SPDX-License-Identifier: Apache-2.0 OR MIT + +// ---------------- + +// This exercises snippet/uncompng.c to write a small APNG image to stdout. +// +// It is a C port of lib/uncompng's TestAnimationEncoderSmall. + +#include <errno.h> +#include <unistd.h> + +#define UNCOMPNG_CONFIG__STATIC_FUNCTIONS +#define UNCOMPNG_IMPLEMENTATION +#include "../snippet/uncompng.c" + +#define IMAGE_HEIGHT 2 +#define IMAGE_WIDTH 3 +#define NUM_FRAMES 2 +#define NUM_PLAYS 10 + +int // +my_write_func(void* context, const uint8_t* data_ptr, size_t data_len) { + static const int stdout_fd = 1; + return (write(stdout_fd, data_ptr, data_len) < 0) ? -errno : 0; +} + +int // +main(int argc, char** argv) { + static uint8_t pixels[NUM_FRAMES][IMAGE_WIDTH * IMAGE_HEIGHT * 4] = { + { + 0xFF, 0x00, 0x00, 0xFF, // Blue. + 0xFF, 0xFF, 0xFF, 0xFF, // White. + 0x00, 0x00, 0xFF, 0xFF, // Red. + + 0xFF, 0x00, 0x00, 0xFF, // Blue. + 0xFF, 0xFF, 0xFF, 0xFF, // White. + 0x00, 0x00, 0xFF, 0xFF, // Red. + }, + { + 0x00, 0xFF, 0x00, 0xFF, // Green. + 0xFF, 0xFF, 0xFF, 0xFF, // White. + 0x00, 0x00, 0xFF, 0xFF, // Red. + + 0x00, 0xFF, 0x00, 0xFF, // Green. + 0xFF, 0xFF, 0xFF, 0xFF, // White. + 0x00, 0x00, 0xFF, 0xFF, // Red. + }}; + + // Change "if (0)" to "if (1)" to write a still (not animated) PNG. + if (0) { + const int frame = 0; + return uncompng__encode( // + &my_write_func, NULL, // + UNCOMPNG__PIXEL_FORMAT__BGRX, IMAGE_WIDTH, IMAGE_HEIGHT, // + &pixels[frame][0], sizeof(pixels[frame]), IMAGE_WIDTH * 4); + } + + static uint16_t delay_millis[NUM_FRAMES] = {1000, 2000}; + + int err0 = uncompng__encode_apng_header( // + &my_write_func, NULL, // + UNCOMPNG__PIXEL_FORMAT__BGRX, IMAGE_WIDTH, IMAGE_HEIGHT, // + NUM_FRAMES, NUM_PLAYS); + if (err0) { + return err0; + } + + for (int frame = 0; frame < NUM_FRAMES; frame++) { + int err1 = uncompng__encode_apng_frame( // + &my_write_func, NULL, // + delay_millis[frame], 1000, // + &pixels[frame][0], sizeof(pixels[frame]), IMAGE_WIDTH * 4); + if (err1) { + return err1; + } + } + + return 0; +}
diff --git a/script/example-uncompng-png-julia.c b/script/example-uncompng-png-julia.c new file mode 100644 index 0000000..3e7cb4c --- /dev/null +++ b/script/example-uncompng-png-julia.c
@@ -0,0 +1,67 @@ +// Copyright 2026 The Wuffs Authors. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. +// +// SPDX-License-Identifier: Apache-2.0 OR MIT + +// ---------------- + +// This exercises snippet/uncompng.c to write a Julia Set image to stdout. +// +// See also +// https://nigeltao.github.io/blog/2025/uncompressed-png.html + +#include <errno.h> +#include <unistd.h> + +#define UNCOMPNG_CONFIG__STATIC_FUNCTIONS +#define UNCOMPNG_IMPLEMENTATION +#include "../snippet/uncompng.c" + +#define CONFIG_CX -0.70000f +#define CONFIG_CY +0.27015f +#define CONFIG_IMAGE_WIDTH 256 +#define CONFIG_IMAGE_HEIGHT 256 + +int // +my_write_func(void* context, const uint8_t* data_ptr, size_t data_len) { + static const int stdout_fd = 1; + return (write(stdout_fd, data_ptr, data_len) < 0) ? -errno : 0; +} + +uint8_t // +my_pixel_func(float fx, float fy) { + static const float escape_distance_squared = 100.0f; + for (int i = 255; i > 0; i--) { + float distance_squared = (fx * fx) + (fy * fy); + if (distance_squared >= escape_distance_squared) { + return i; + } + float gx = CONFIG_CX + (fx * fx) - (fy * fy); + float gy = CONFIG_CY + (2 * fx * fy); + fx = gx; + fy = gy; + } + return 0; +} + +int // +main(int argc, char** argv) { + static const float w2 = CONFIG_IMAGE_WIDTH / 2; + static const float h2 = CONFIG_IMAGE_HEIGHT / 2; + static uint8_t pixels[CONFIG_IMAGE_HEIGHT][CONFIG_IMAGE_WIDTH]; + for (int iy = 0; iy < CONFIG_IMAGE_HEIGHT; iy++) { + float fy = (iy - h2) / h2; + for (int ix = 0; ix < CONFIG_IMAGE_WIDTH; ix++) { + float fx = (ix - w2) / w2; + pixels[iy][ix] = my_pixel_func(fx, fy); + } + } + return uncompng__encode(&my_write_func, NULL, UNCOMPNG__PIXEL_FORMAT__Y, + CONFIG_IMAGE_WIDTH, CONFIG_IMAGE_HEIGHT, + &pixels[0][0], sizeof(pixels), CONFIG_IMAGE_WIDTH); +}
diff --git a/snippet/uncompng.c b/snippet/uncompng.c index 2b62c5f..4406675 100644 --- a/snippet/uncompng.c +++ b/snippet/uncompng.c
@@ -62,8 +62,9 @@ // also return its own negative error codes, which are passed on. #define UNCOMPNG__RESULT__OK 0 #define UNCOMPNG__RESULT__INVALID_ARGUMENT 1 -#define UNCOMPNG__RESULT__UNSUPPORTED_IMAGE_SIZE 2 -#define UNCOMPNG__RESULT__CONCURRENT_CALL 3 +#define UNCOMPNG__RESULT__INVALID_CALL_SEQUENCE 2 +#define UNCOMPNG__RESULT__UNSUPPORTED_IMAGE_SIZE 3 +#define UNCOMPNG__RESULT__CONCURRENT_CALL 4 // UNCOMPNG__DATA_LEN__INCL_MAX is the inclusive maximum value of write_func's // data_len argument. In hexadecimal, it equals 0x10000u. @@ -101,6 +102,28 @@ size_t pixel_len, size_t stride); +UNCOMPNG__MAYBE_STATIC int // +uncompng__encode_apng_header(int (*write_func)(void* context, + const uint8_t* data_ptr, + size_t data_len), + void* context, + uint32_t pixel_format, + uint32_t width, + uint32_t height, + uint32_t num_frames, + uint32_t num_plays); + +UNCOMPNG__MAYBE_STATIC int // +uncompng__encode_apng_frame(int (*write_func)(void* context, + const uint8_t* data_ptr, + size_t data_len), + void* context, + uint16_t delay_numerator, + uint16_t delay_denominator, + const uint8_t* pixel_ptr, + size_t pixel_len, + size_t stride); + // -------- #ifdef UNCOMPNG_IMPLEMENTATION @@ -171,10 +194,109 @@ static uint8_t uncompng__private_impl_buffer[65536]; -static void // -uncompng__private_impl_initialize_buffer(uint32_t width, - uint32_t height, - uint32_t pixel_format) { +static struct uncompng__private_impl_apng_encoder_state_struct { + uint32_t pixel_format; + uint32_t width; + uint32_t height; + uint32_t seq_num; + uint32_t num_frames; + uint32_t cur_frame; +} uncompng__private_impl_apng_encoder_state; + +static int // +uncompng__private_impl_encode_frame_header(uint32_t delay_numerator, + uint32_t delay_denominator) { + uncompng__private_impl_buffer[0x0000] = 0; + uncompng__private_impl_buffer[0x0001] = 0; + uncompng__private_impl_buffer[0x0002] = 0; + uncompng__private_impl_buffer[0x0003] = 0x1A; + uncompng__private_impl_buffer[0x0004] = 'f'; + uncompng__private_impl_buffer[0x0005] = 'c'; + uncompng__private_impl_buffer[0x0006] = 'T'; + uncompng__private_impl_buffer[0x0007] = 'L'; + uncompng__private_impl_buffer[0x0008] = + (uint8_t)(uncompng__private_impl_apng_encoder_state.seq_num >> 24); + uncompng__private_impl_buffer[0x0009] = + (uint8_t)(uncompng__private_impl_apng_encoder_state.seq_num >> 16); + uncompng__private_impl_buffer[0x000A] = + (uint8_t)(uncompng__private_impl_apng_encoder_state.seq_num >> 8); + uncompng__private_impl_buffer[0x000B] = + (uint8_t)(uncompng__private_impl_apng_encoder_state.seq_num >> 0); + uncompng__private_impl_buffer[0x000C] = + (uint8_t)(uncompng__private_impl_apng_encoder_state.width >> 24); + uncompng__private_impl_buffer[0x000D] = + (uint8_t)(uncompng__private_impl_apng_encoder_state.width >> 16); + uncompng__private_impl_buffer[0x000E] = + (uint8_t)(uncompng__private_impl_apng_encoder_state.width >> 8); + uncompng__private_impl_buffer[0x000F] = + (uint8_t)(uncompng__private_impl_apng_encoder_state.width >> 0); + uncompng__private_impl_buffer[0x0010] = + (uint8_t)(uncompng__private_impl_apng_encoder_state.height >> 24); + uncompng__private_impl_buffer[0x0011] = + (uint8_t)(uncompng__private_impl_apng_encoder_state.height >> 16); + uncompng__private_impl_buffer[0x0012] = + (uint8_t)(uncompng__private_impl_apng_encoder_state.height >> 8); + uncompng__private_impl_buffer[0x0013] = + (uint8_t)(uncompng__private_impl_apng_encoder_state.height >> 0); + uncompng__private_impl_buffer[0x0014] = 0; + uncompng__private_impl_buffer[0x0015] = 0; + uncompng__private_impl_buffer[0x0016] = 0; + uncompng__private_impl_buffer[0x0017] = 0; + uncompng__private_impl_buffer[0x0018] = 0; + uncompng__private_impl_buffer[0x0019] = 0; + uncompng__private_impl_buffer[0x001A] = 0; + uncompng__private_impl_buffer[0x001B] = 0; + uncompng__private_impl_buffer[0x001C] = (uint8_t)(delay_numerator >> 8); + uncompng__private_impl_buffer[0x001D] = (uint8_t)(delay_numerator >> 0); + uncompng__private_impl_buffer[0x001E] = (uint8_t)(delay_denominator >> 8); + uncompng__private_impl_buffer[0x001F] = (uint8_t)(delay_denominator >> 0); + uncompng__private_impl_buffer[0x0020] = 0; + uncompng__private_impl_buffer[0x0021] = 0; + uint32_t fctl_crc32 = uncompng__private_impl_crc32_ieee( + uncompng__private_impl_buffer + 0x0004, 0x0022 - 0x0004); + uncompng__private_impl_buffer[0x0022] = (uint8_t)(fctl_crc32 >> 24); + uncompng__private_impl_buffer[0x0023] = (uint8_t)(fctl_crc32 >> 16); + uncompng__private_impl_buffer[0x0024] = (uint8_t)(fctl_crc32 >> 8); + uncompng__private_impl_buffer[0x0025] = (uint8_t)(fctl_crc32 >> 0); + + int use_idat = uncompng__private_impl_apng_encoder_state.seq_num ? 0 : 1; + uncompng__private_impl_apng_encoder_state.seq_num++; + + uncompng__private_impl_buffer[0x0026] = 0; + uncompng__private_impl_buffer[0x0027] = 0; + uncompng__private_impl_buffer[0x0028] = 0; + uncompng__private_impl_buffer[0x0029] = 0; + uncompng__private_impl_buffer[0x002A] = "fI"[use_idat]; + uncompng__private_impl_buffer[0x002B] = "dD"[use_idat]; + uncompng__private_impl_buffer[0x002C] = 'A'; + uncompng__private_impl_buffer[0x002D] = 'T'; + int ej = 0x002E; + + if (!use_idat) { + uint32_t seq_num = uncompng__private_impl_apng_encoder_state.seq_num++; + uncompng__private_impl_buffer[0x002E] = (uint8_t)(seq_num >> 24); + uncompng__private_impl_buffer[0x002F] = (uint8_t)(seq_num >> 16); + uncompng__private_impl_buffer[0x0030] = (uint8_t)(seq_num >> 8); + uncompng__private_impl_buffer[0x0031] = (uint8_t)(seq_num >> 0); + ej = 0x0032; + } + + uncompng__private_impl_buffer[ej + 0x00] = 0x78; + uncompng__private_impl_buffer[ej + 0x01] = 0x01; + uncompng__private_impl_buffer[ej + 0x02] = 0; + uncompng__private_impl_buffer[ej + 0x03] = 0; + uncompng__private_impl_buffer[ej + 0x04] = 0; + uncompng__private_impl_buffer[ej + 0x05] = 0; + uncompng__private_impl_buffer[ej + 0x06] = 0; + return ej + 0x07; +} + +static int // +uncompng__private_impl_encode_image_header(uint32_t pixel_format, + uint32_t width, + uint32_t height, + uint32_t num_frames, + uint32_t num_plays) { uncompng__private_impl_buffer[0x0000] = 0x89; uncompng__private_impl_buffer[0x0001] = 'P'; uncompng__private_impl_buffer[0x0002] = 'N'; @@ -230,7 +352,7 @@ color_type = 2; break; default: - return; + return 0; } uncompng__private_impl_buffer[0x0018] = depth; uncompng__private_impl_buffer[0x0019] = color_type; @@ -245,6 +367,32 @@ uncompng__private_impl_buffer[0x001F] = (uint8_t)(ihdr_crc32 >> 8); uncompng__private_impl_buffer[0x0020] = (uint8_t)(ihdr_crc32 >> 0); + if (num_frames > 0) { + uncompng__private_impl_buffer[0x0021] = 0; + uncompng__private_impl_buffer[0x0022] = 0; + uncompng__private_impl_buffer[0x0023] = 0; + uncompng__private_impl_buffer[0x0024] = 0x08; + uncompng__private_impl_buffer[0x0025] = 'a'; + uncompng__private_impl_buffer[0x0026] = 'c'; + uncompng__private_impl_buffer[0x0027] = 'T'; + uncompng__private_impl_buffer[0x0028] = 'L'; + uncompng__private_impl_buffer[0x0029] = (uint8_t)(num_frames >> 24); + uncompng__private_impl_buffer[0x002A] = (uint8_t)(num_frames >> 16); + uncompng__private_impl_buffer[0x002B] = (uint8_t)(num_frames >> 8); + uncompng__private_impl_buffer[0x002C] = (uint8_t)(num_frames >> 0); + uncompng__private_impl_buffer[0x002D] = (uint8_t)(num_plays >> 24); + uncompng__private_impl_buffer[0x002E] = (uint8_t)(num_plays >> 16); + uncompng__private_impl_buffer[0x002F] = (uint8_t)(num_plays >> 8); + uncompng__private_impl_buffer[0x0030] = (uint8_t)(num_plays >> 0); + uint32_t actl_crc32 = uncompng__private_impl_crc32_ieee( + uncompng__private_impl_buffer + 0x0025, 0x0031 - 0x0025); + uncompng__private_impl_buffer[0x0031] = (uint8_t)(actl_crc32 >> 24); + uncompng__private_impl_buffer[0x0032] = (uint8_t)(actl_crc32 >> 16); + uncompng__private_impl_buffer[0x0033] = (uint8_t)(actl_crc32 >> 8); + uncompng__private_impl_buffer[0x0034] = (uint8_t)(actl_crc32 >> 0); + return 0x0035; + } + uncompng__private_impl_buffer[0x0021] = 0; uncompng__private_impl_buffer[0x0022] = 0; uncompng__private_impl_buffer[0x0023] = 0; @@ -260,10 +408,7 @@ uncompng__private_impl_buffer[0x002D] = 0; uncompng__private_impl_buffer[0x002E] = 0; uncompng__private_impl_buffer[0x002F] = 0; - uncompng__private_impl_buffer[0xFFFC] = 0; - uncompng__private_impl_buffer[0xFFFD] = 0; - uncompng__private_impl_buffer[0xFFFE] = 0; - uncompng__private_impl_buffer[0xFFFF] = 1; + return 0x0030; } static void // @@ -295,39 +440,41 @@ const uint8_t* data_ptr, size_t data_len), void* context, - int ej, - bool final) { - static const int ei_first = 0x0030; - static const int ei_later = 0x000D; - - int crc32_start = 0; - int ei = 0; - if (uncompng__private_impl_buffer[0x0004] == 0x0D) { - uint32_t idat_chunk_len = ej - 0x0029; - if (final) { - idat_chunk_len += 4; - } - uncompng__private_impl_buffer[0x0021] = (uint8_t)(idat_chunk_len >> 24); - uncompng__private_impl_buffer[0x0022] = (uint8_t)(idat_chunk_len >> 16); - uncompng__private_impl_buffer[0x0023] = (uint8_t)(idat_chunk_len >> 8); - uncompng__private_impl_buffer[0x0024] = (uint8_t)(idat_chunk_len >> 0); - crc32_start = 0x0025; - ei = ei_first; - } else { - uint32_t idat_chunk_len = ej - 0x0008; - if (final) { - idat_chunk_len += 4; - } - uncompng__private_impl_buffer[0x0000] = (uint8_t)(idat_chunk_len >> 24); - uncompng__private_impl_buffer[0x0001] = (uint8_t)(idat_chunk_len >> 16); - uncompng__private_impl_buffer[0x0002] = (uint8_t)(idat_chunk_len >> 8); - uncompng__private_impl_buffer[0x0003] = (uint8_t)(idat_chunk_len >> 0); - crc32_start = 0x0004; - ei = ei_later; + uint32_t f_type, + int* ptr_to_ej, + bool final_idat_of_frame) { + int idat_chunk_start = 0; + if (uncompng__private_impl_buffer[0x0007] == 0x0A) { + idat_chunk_start = 0x0021; + } else if (uncompng__private_impl_buffer[0x0007] == 0x4C) { + idat_chunk_start = 0x0026; } + int ei = idat_chunk_start + 13; + if (uncompng__private_impl_buffer[0x0007] != 0x54) { + ei += 2; + } + if ((f_type & 1) == 0) { + ei += 4; + } + + int ej = *ptr_to_ej; + int idat_chunk_len = ej - (idat_chunk_start + 8); + if (final_idat_of_frame) { + idat_chunk_len += 4; + } + uncompng__private_impl_buffer[idat_chunk_start + 0] = + (uint8_t)(idat_chunk_len >> 24); + uncompng__private_impl_buffer[idat_chunk_start + 1] = + (uint8_t)(idat_chunk_len >> 16); + uncompng__private_impl_buffer[idat_chunk_start + 2] = + (uint8_t)(idat_chunk_len >> 8); + uncompng__private_impl_buffer[idat_chunk_start + 3] = + (uint8_t)(idat_chunk_len >> 0); + uint32_t deflate_block_len = (uint32_t)(ej - ei); - uncompng__private_impl_buffer[ei - 5] = final ? 1 : 0; + + uncompng__private_impl_buffer[ei - 5] = final_idat_of_frame ? 1 : 0; uncompng__private_impl_buffer[ei - 4] = 0x00u ^ (uint8_t)(deflate_block_len >> 0); uncompng__private_impl_buffer[ei - 3] = @@ -338,7 +485,7 @@ 0xFFu ^ (uint8_t)(deflate_block_len >> 8); uncompng__private_impl_update_adler32(ei, ej); - if (final) { + if (final_idat_of_frame) { uncompng__private_impl_buffer[ej + 0] = uncompng__private_impl_buffer[0xFFFC]; uncompng__private_impl_buffer[ej + 1] = @@ -350,6 +497,7 @@ ej += 4; } + int crc32_start = idat_chunk_start + 4; uint32_t idat_crc32 = uncompng__private_impl_crc32_ieee( uncompng__private_impl_buffer + crc32_start, ej - crc32_start); uncompng__private_impl_buffer[ej + 0] = (uint8_t)(idat_crc32 >> 24); @@ -358,19 +506,35 @@ uncompng__private_impl_buffer[ej + 3] = (uint8_t)(idat_crc32 >> 0); ej += 4; - if (!final) { + if (!final_idat_of_frame) { int err0 = (*write_func)(context, uncompng__private_impl_buffer, (size_t)ej); if (err0 != 0) { return err0; } - uncompng__private_impl_buffer[0x0004] = 'I'; - uncompng__private_impl_buffer[0x0005] = 'D'; + int t = f_type & 1; + uncompng__private_impl_buffer[0x0004] = "fI"[t]; + uncompng__private_impl_buffer[0x0005] = "dD"[t]; uncompng__private_impl_buffer[0x0006] = 'A'; uncompng__private_impl_buffer[0x0007] = 'T'; + if (t) { + *ptr_to_ej = 0x000D + 0; + return 0; + } + + uint32_t seq_num = uncompng__private_impl_apng_encoder_state.seq_num++; + uncompng__private_impl_buffer[0x0008] = (uint8_t)(seq_num >> 24); + uncompng__private_impl_buffer[0x0009] = (uint8_t)(seq_num >> 16); + uncompng__private_impl_buffer[0x000A] = (uint8_t)(seq_num >> 8); + uncompng__private_impl_buffer[0x000B] = (uint8_t)(seq_num >> 0); + *ptr_to_ej = 0x000D + 4; return 0; } + if ((f_type & 2) == 0) { + return (*write_func)(context, uncompng__private_impl_buffer, (size_t)ej); + } + static const uint8_t iend_chunk[] = { 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82, }; @@ -398,31 +562,31 @@ } static int // -uncompng__private_impl_do_encode(int (*write_func)(void* context, - const uint8_t* data_ptr, - size_t data_len), - void* context, - const uint8_t* pixel_ptr, - size_t pixel_len, - uint32_t width, - uint32_t height, - size_t stride, - uint32_t pixel_format) { - static const int ei_first = 0x0030; - static const int ei_later = 0x000D; +uncompng__private_impl_encode_frame_payload( + int (*write_func)(void* context, const uint8_t* data_ptr, size_t data_len), + void* context, + uint32_t f_type, + uint32_t pixel_format, + uint32_t width, + uint32_t height, + const uint8_t* pixel_ptr, + size_t pixel_len, + size_t stride, + int ej) { + uncompng__private_impl_buffer[0xFFFC] = 0; + uncompng__private_impl_buffer[0xFFFD] = 0; + uncompng__private_impl_buffer[0xFFFE] = 0; + uncompng__private_impl_buffer[0xFFFF] = 1; + static const int ej_max = 0xFFF8; - uncompng__private_impl_initialize_buffer(width, height, pixel_format); - - int ej = ei_first; - for (uint32_t y = 0; y < height; y++) { if ((ej + 1) > ej_max) { - int err = uncompng__private_impl_flush(write_func, context, ej, false); + int err = + uncompng__private_impl_flush(write_func, context, f_type, &ej, false); if (err != 0) { return err; } - ej = ei_later; } uncompng__private_impl_buffer[ej++] = 0; @@ -432,12 +596,11 @@ case UNCOMPNG__PIXEL_FORMAT__Y: for (uint32_t x = 0; x < width; x++) { if ((ej + 1) > ej_max) { - int err = - uncompng__private_impl_flush(write_func, context, ej, false); + int err = uncompng__private_impl_flush(write_func, context, f_type, + &ej, false); if (err != 0) { return err; } - ej = ei_later; } uncompng__private_impl_buffer[ej++] = row[0]; row += 1; @@ -447,12 +610,11 @@ case UNCOMPNG__PIXEL_FORMAT__Y_16LE: for (uint32_t x = 0; x < width; x++) { if ((ej + 2) > ej_max) { - int err = - uncompng__private_impl_flush(write_func, context, ej, false); + int err = uncompng__private_impl_flush(write_func, context, f_type, + &ej, false); if (err != 0) { return err; } - ej = ei_later; } uncompng__private_impl_buffer[ej++] = row[1]; uncompng__private_impl_buffer[ej++] = row[0]; @@ -463,12 +625,11 @@ case UNCOMPNG__PIXEL_FORMAT__YXXX: for (uint32_t x = 0; x < width; x++) { if ((ej + 1) > ej_max) { - int err = - uncompng__private_impl_flush(write_func, context, ej, false); + int err = uncompng__private_impl_flush(write_func, context, f_type, + &ej, false); if (err != 0) { return err; } - ej = ei_later; } uncompng__private_impl_buffer[ej++] = row[0]; row += 4; @@ -478,12 +639,11 @@ case UNCOMPNG__PIXEL_FORMAT__YXXX_4X16LE: for (uint32_t x = 0; x < width; x++) { if ((ej + 2) > ej_max) { - int err = - uncompng__private_impl_flush(write_func, context, ej, false); + int err = uncompng__private_impl_flush(write_func, context, f_type, + &ej, false); if (err != 0) { return err; } - ej = ei_later; } uncompng__private_impl_buffer[ej++] = row[1]; uncompng__private_impl_buffer[ej++] = row[0]; @@ -494,12 +654,11 @@ case UNCOMPNG__PIXEL_FORMAT__BGRA_NONPREMUL: for (uint32_t x = 0; x < width; x++) { if ((ej + 4) > ej_max) { - int err = - uncompng__private_impl_flush(write_func, context, ej, false); + int err = uncompng__private_impl_flush(write_func, context, f_type, + &ej, false); if (err != 0) { return err; } - ej = ei_later; } uncompng__private_impl_buffer[ej++] = row[2]; uncompng__private_impl_buffer[ej++] = row[1]; @@ -512,12 +671,11 @@ case UNCOMPNG__PIXEL_FORMAT__BGRA_NONPREMUL_4X16LE: for (uint32_t x = 0; x < width; x++) { if ((ej + 8) > ej_max) { - int err = - uncompng__private_impl_flush(write_func, context, ej, false); + int err = uncompng__private_impl_flush(write_func, context, f_type, + &ej, false); if (err != 0) { return err; } - ej = ei_later; } uncompng__private_impl_buffer[ej++] = row[5]; uncompng__private_impl_buffer[ej++] = row[4]; @@ -534,12 +692,11 @@ case UNCOMPNG__PIXEL_FORMAT__BGRX: for (uint32_t x = 0; x < width; x++) { if ((ej + 3) > ej_max) { - int err = - uncompng__private_impl_flush(write_func, context, ej, false); + int err = uncompng__private_impl_flush(write_func, context, f_type, + &ej, false); if (err != 0) { return err; } - ej = ei_later; } uncompng__private_impl_buffer[ej++] = row[2]; uncompng__private_impl_buffer[ej++] = row[1]; @@ -551,12 +708,11 @@ case UNCOMPNG__PIXEL_FORMAT__BGRX_4X16LE: for (uint32_t x = 0; x < width; x++) { if ((ej + 6) > ej_max) { - int err = - uncompng__private_impl_flush(write_func, context, ej, false); + int err = uncompng__private_impl_flush(write_func, context, f_type, + &ej, false); if (err != 0) { return err; } - ej = ei_later; } uncompng__private_impl_buffer[ej++] = row[5]; uncompng__private_impl_buffer[ej++] = row[4]; @@ -573,21 +729,20 @@ } } - return uncompng__private_impl_flush(write_func, context, ej, true); + return uncompng__private_impl_flush(write_func, context, f_type, &ej, true); } -UNCOMPNG__MAYBE_STATIC int // -uncompng__encode(int (*write_func)(void* context, - const uint8_t* data_ptr, - size_t data_len), - void* context, - uint32_t pixel_format, - uint32_t width, - uint32_t height, - const uint8_t* pixel_ptr, - size_t pixel_len, - size_t stride) { - if (!write_func) { +static int // +uncompng__private_impl_check_arguments( + int (*write_func)(void* context, const uint8_t* data_ptr, size_t data_len), + uint32_t pixel_format, + uint32_t width, + uint32_t height, + uint32_t num_frames, + bool extras, + size_t pixel_len, + size_t stride) { + if (!write_func || !width || !height || !num_frames) { return UNCOMPNG__RESULT__INVALID_ARGUMENT; } uint64_t bytes_per_pixel; @@ -615,7 +770,7 @@ return UNCOMPNG__RESULT__UNSUPPORTED_IMAGE_SIZE; } - if (height > 0u) { + if (extras && (height > 0u)) { // This calculation is similar to the one used in // wuffs_base__table__flattened_length. uint64_t n = ((uint64_t)stride * (uint64_t)(height - 1u)) + @@ -625,6 +780,26 @@ } } + return 0; +} + +UNCOMPNG__MAYBE_STATIC int // +uncompng__encode(int (*write_func)(void* context, + const uint8_t* data_ptr, + size_t data_len), + void* context, + uint32_t pixel_format, + uint32_t width, + uint32_t height, + const uint8_t* pixel_ptr, + size_t pixel_len, + size_t stride) { + int check = uncompng__private_impl_check_arguments( + write_func, pixel_format, width, height, 1, true, pixel_len, stride); + if (check != 0) { + return check; + } + // uncompng__private_impl_buffer is a global variable in this C code, unlike // the original Go code, so try to reject concurrent use. This isn't perfect, // as it doesn't use atomics, but it's better than nothing. @@ -633,12 +808,122 @@ return UNCOMPNG__RESULT__CONCURRENT_CALL; } concurrent = true; - int ret = uncompng__private_impl_do_encode(write_func, context, pixel_ptr, - pixel_len, width, height, stride, - pixel_format); + + int ej = uncompng__private_impl_encode_image_header(pixel_format, width, + height, 0, 0); + + int ret = uncompng__private_impl_encode_frame_payload( // + write_func, context, 3, // + pixel_format, width, height, // + pixel_ptr, pixel_len, stride, ej); + concurrent = false; return ret; } +UNCOMPNG__MAYBE_STATIC int // +uncompng__encode_apng_header(int (*write_func)(void* context, + const uint8_t* data_ptr, + size_t data_len), + void* context, + uint32_t pixel_format, + uint32_t width, + uint32_t height, + uint32_t num_frames, + uint32_t num_plays) { + int check = uncompng__private_impl_check_arguments( + write_func, pixel_format, width, height, num_frames, false, 0, 0); + if (check != 0) { + return check; + } + + static volatile bool concurrent = false; + if (concurrent) { + return UNCOMPNG__RESULT__CONCURRENT_CALL; + } + concurrent = true; + + uncompng__private_impl_apng_encoder_state.pixel_format = pixel_format; + uncompng__private_impl_apng_encoder_state.width = width; + uncompng__private_impl_apng_encoder_state.height = height; + uncompng__private_impl_apng_encoder_state.seq_num = 0; + uncompng__private_impl_apng_encoder_state.num_frames = num_frames; + uncompng__private_impl_apng_encoder_state.cur_frame = 0; + + int ej = uncompng__private_impl_encode_image_header( + pixel_format, width, height, num_frames, num_plays); + + int ret = (*write_func)(context, uncompng__private_impl_buffer, (size_t)ej); + + concurrent = false; + return 0; +} + +UNCOMPNG__MAYBE_STATIC int // +uncompng__encode_apng_frame(int (*write_func)(void* context, + const uint8_t* data_ptr, + size_t data_len), + void* context, + uint16_t delay_numerator, + uint16_t delay_denominator, + const uint8_t* pixel_ptr, + size_t pixel_len, + size_t stride) { + if (uncompng__private_impl_apng_encoder_state.num_frames == 0) { + return UNCOMPNG__RESULT__INVALID_CALL_SEQUENCE; + } else if (uncompng__private_impl_apng_encoder_state.seq_num >= 0x80000000u) { + return UNCOMPNG__RESULT__UNSUPPORTED_IMAGE_SIZE; + } + int check = uncompng__private_impl_check_arguments( // + write_func, // + uncompng__private_impl_apng_encoder_state.pixel_format, // + uncompng__private_impl_apng_encoder_state.width, // + uncompng__private_impl_apng_encoder_state.height, // + uncompng__private_impl_apng_encoder_state.num_frames, // + true, pixel_len, stride); + if (check != 0) { + return check; + } + + static volatile bool concurrent = false; + if (concurrent) { + return UNCOMPNG__RESULT__CONCURRENT_CALL; + } + concurrent = true; + + uint32_t f_type = 4; + if (uncompng__private_impl_apng_encoder_state.cur_frame == 0) { + f_type |= 1; + } + uncompng__private_impl_apng_encoder_state.cur_frame++; + if (uncompng__private_impl_apng_encoder_state.cur_frame == + uncompng__private_impl_apng_encoder_state.num_frames) { + f_type |= 2; + } + + int ej = uncompng__private_impl_encode_frame_header(delay_numerator, + delay_denominator); + + int ret = uncompng__private_impl_encode_frame_payload( // + write_func, context, f_type, // + uncompng__private_impl_apng_encoder_state.pixel_format, // + uncompng__private_impl_apng_encoder_state.width, // + uncompng__private_impl_apng_encoder_state.height, // + pixel_ptr, pixel_len, stride, ej); + + if (ret || (uncompng__private_impl_apng_encoder_state.num_frames == + uncompng__private_impl_apng_encoder_state.cur_frame)) { + uncompng__private_impl_apng_encoder_state.pixel_format = 0; + uncompng__private_impl_apng_encoder_state.width = 0; + uncompng__private_impl_apng_encoder_state.height = 0; + uncompng__private_impl_apng_encoder_state.seq_num = 0; + uncompng__private_impl_apng_encoder_state.num_frames = 0; + uncompng__private_impl_apng_encoder_state.cur_frame = 0; + } + + concurrent = false; + return 0; +} + #endif // UNCOMPNG_IMPLEMENTATION #endif // UNCOMPNG_INCLUDE_GUARD