mirror of
https://git.ffmpeg.org/ffmpeg.git
synced 2026-10-24 02:09:03 +03:00
tests/fate/apng: test sub-8-bit frames with a frame offset
Generate 1/2/4-bit palette animations whose later frames sit on an odd x offset, so the offset is not a byte offset in the packed row. The fixtures are built by apnggen, so the tests do not need fate-suite samples. Signed-off-by: Jun Zhao <barryjzhao@tencent.com>
This commit is contained in:
2
Makefile
2
Makefile
@@ -18,7 +18,7 @@ vpath %.ptx $(SRC_PATH)
|
||||
vpath %.metal $(SRC_PATH)
|
||||
vpath %/fate_config.sh.template $(SRC_PATH)
|
||||
|
||||
TESTTOOLS = audiogen videogen rotozoom tiny_psnr tiny_ssim base64 audiomatch amrgen
|
||||
TESTTOOLS = audiogen videogen rotozoom tiny_psnr tiny_ssim base64 audiomatch amrgen apnggen
|
||||
HOSTPROGS := $(TESTTOOLS:%=tests/%) doc/print_options
|
||||
|
||||
ALLFFLIBS = \
|
||||
|
||||
@@ -69,6 +69,10 @@ tests/data/fate/%.amr: TAG = GEN
|
||||
tests/data/fate/%.amr: tests/amrgen$(HOSTEXESUF) | tests/data/fate
|
||||
$(M)./$< $@
|
||||
|
||||
tests/data/fate/apng-small-bpp-%.png: TAG = GEN
|
||||
tests/data/fate/apng-small-bpp-%.png: tests/apnggen$(HOSTEXESUF) | tests/data/fate
|
||||
$(M)./$< $@
|
||||
|
||||
RUNNING_FATE := $(filter check fate%,$(filter-out fate-rsync,$(MAKECMDGOALS)))
|
||||
|
||||
# Check sanity of dependencies when running FATE tests.
|
||||
|
||||
225
tests/apnggen.c
Normal file
225
tests/apnggen.c
Normal file
@@ -0,0 +1,225 @@
|
||||
/*
|
||||
* Generate the sub-8-bit APNG streams used by the FATE tests.
|
||||
*
|
||||
* The frames are palette images. The first frame fills the canvas. Every
|
||||
* later frame is a smaller rectangle whose x offset is odd, so it is not
|
||||
* aligned to the byte that holds the packed samples. These files are built
|
||||
* here, the same way audiogen builds its wave files, so the tests do not
|
||||
* need media from the fate-suite.
|
||||
*
|
||||
* Copyright (c) 2026 Jun Zhao
|
||||
*
|
||||
* This file is part of FFmpeg.
|
||||
*
|
||||
* FFmpeg is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* FFmpeg is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with FFmpeg; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
enum { CANVAS_W = 24, CANVAS_H = 20, FRAMES = 6 };
|
||||
|
||||
static const uint8_t palette[16][3] = {
|
||||
{ 0, 0, 0 }, { 255, 0, 0 }, { 0, 255, 0 }, { 0, 0, 255 },
|
||||
{ 255, 255, 0 }, { 255, 0, 255 }, { 0, 255, 255 }, { 255, 128, 0 },
|
||||
{ 128, 0, 255 }, { 128, 255, 0 }, { 0, 128, 255 }, { 255, 0, 128 },
|
||||
{ 64, 64, 64 }, { 192, 192, 192 }, { 128, 64, 0 }, { 255, 255, 255 },
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
uint8_t data[2048];
|
||||
int len;
|
||||
} Buf;
|
||||
|
||||
static void die(const char *path)
|
||||
{
|
||||
fprintf(stderr, "apnggen: %s: %s\n", path, strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
static uint32_t crc32(const uint8_t *data, size_t size)
|
||||
{
|
||||
static uint32_t table[256];
|
||||
uint32_t crc = 0xFFFFFFFFu;
|
||||
size_t i;
|
||||
|
||||
if (!table[1]) {
|
||||
for (i = 0; i < 256; i++) {
|
||||
uint32_t c = i;
|
||||
int k;
|
||||
|
||||
for (k = 0; k < 8; k++)
|
||||
c = (c & 1) ? 0xEDB88320u ^ (c >> 1) : c >> 1;
|
||||
table[i] = c;
|
||||
}
|
||||
}
|
||||
for (i = 0; i < size; i++)
|
||||
crc = table[(crc ^ data[i]) & 0xFF] ^ (crc >> 8);
|
||||
return crc ^ 0xFFFFFFFFu;
|
||||
}
|
||||
|
||||
static void put(Buf *b, const void *src, int n)
|
||||
{
|
||||
if (n < 0 || b->len > (int)sizeof(b->data) - n) {
|
||||
fprintf(stderr, "apnggen: output too large\n");
|
||||
exit(1);
|
||||
}
|
||||
memcpy(b->data + b->len, src, n);
|
||||
b->len += n;
|
||||
}
|
||||
|
||||
static void put_be32(Buf *b, uint32_t v)
|
||||
{
|
||||
uint8_t p[4] = { v >> 24, v >> 16, v >> 8, v };
|
||||
|
||||
put(b, p, 4);
|
||||
}
|
||||
|
||||
static void chunk(Buf *b, const char type[4], const uint8_t *data, int n)
|
||||
{
|
||||
int start = b->len + 4;
|
||||
|
||||
put_be32(b, n);
|
||||
put(b, type, 4);
|
||||
if (n)
|
||||
put(b, data, n);
|
||||
put_be32(b, crc32(b->data + start, 4 + n));
|
||||
}
|
||||
|
||||
/* First frame covers the canvas. Later frames are 7x5 rectangles on odd x
|
||||
* offsets, one pixel further right each frame. */
|
||||
static int sample(int depth, int frame, int x, int y)
|
||||
{
|
||||
int max = (1 << depth) - 1;
|
||||
|
||||
if (!frame)
|
||||
return (x / 4 + y / 5) & max;
|
||||
if (depth == 1)
|
||||
return (x + y + frame) & 1;
|
||||
return 1 + (x + y + frame) % max;
|
||||
}
|
||||
|
||||
static int pack_frame(uint8_t *dst, int depth, int frame, int w, int h)
|
||||
{
|
||||
int row_bytes = (w * depth + 7) / 8;
|
||||
int out = 0, y, x;
|
||||
|
||||
for (y = 0; y < h; y++) {
|
||||
dst[out++] = 0;
|
||||
memset(dst + out, 0, row_bytes);
|
||||
for (x = 0; x < w; x++) {
|
||||
int bit = x * depth;
|
||||
|
||||
dst[out + bit / 8] |= sample(depth, frame, x, y) << (8 - depth - bit % 8);
|
||||
}
|
||||
out += row_bytes;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/* zlib wrapper around one stored deflate block. No compressor needed. */
|
||||
static void put_stored(Buf *b, const uint8_t *raw, int n)
|
||||
{
|
||||
unsigned neg = ~n & 0xFFFF;
|
||||
uint8_t hdr[7] = { 0x78, 0x01, 0x01, n, n >> 8, neg, neg >> 8 };
|
||||
uint32_t a = 1, s = 0;
|
||||
int i;
|
||||
|
||||
put(b, hdr, 7);
|
||||
put(b, raw, n);
|
||||
for (i = 0; i < n; i++) {
|
||||
a += raw[i];
|
||||
if (a >= 65521)
|
||||
a -= 65521;
|
||||
s += a;
|
||||
if (s >= 65521)
|
||||
s -= 65521;
|
||||
}
|
||||
put_be32(b, (s << 16) | a);
|
||||
}
|
||||
|
||||
static void write_apng(const char *path, int depth)
|
||||
{
|
||||
static const uint8_t sig[8] = { 137, 80, 78, 71, 13, 10, 26, 10 };
|
||||
uint8_t ihdr[13] = { 0, 0, 0, CANVAS_W, 0, 0, 0, CANVAS_H, depth, 3 };
|
||||
uint8_t actl[8] = { 0, 0, 0, FRAMES, 0, 0, 0, 1 };
|
||||
uint8_t plte[16 * 3], raw[512], fctl[26];
|
||||
Buf out = { 0 }, image = { 0 };
|
||||
FILE *f;
|
||||
int colors = 1 << depth, seq = 0, frame, i;
|
||||
|
||||
for (i = 0; i < colors; i++)
|
||||
memcpy(plte + 3 * i, palette[i], 3);
|
||||
|
||||
put(&out, sig, 8);
|
||||
chunk(&out, "IHDR", ihdr, sizeof(ihdr));
|
||||
chunk(&out, "acTL", actl, sizeof(actl));
|
||||
chunk(&out, "PLTE", plte, colors * 3);
|
||||
|
||||
for (frame = 0; frame < FRAMES; frame++) {
|
||||
int sub = frame != 0;
|
||||
int x = sub ? 1 + 2 * (frame - 1) : 0;
|
||||
int y = sub ? 1 + (frame - 1) : 0;
|
||||
int w = sub ? 7 : CANVAS_W;
|
||||
int h = sub ? 5 : CANVAS_H;
|
||||
|
||||
memset(fctl, 0, sizeof(fctl));
|
||||
fctl[3] = seq++;
|
||||
fctl[7] = w;
|
||||
fctl[11] = h;
|
||||
fctl[15] = x;
|
||||
fctl[19] = y;
|
||||
fctl[21] = 1; /* delay numerator */
|
||||
fctl[23] = 10; /* delay denominator: 1/10 s */
|
||||
chunk(&out, "fcTL", fctl, sizeof(fctl));
|
||||
|
||||
image.len = 0;
|
||||
if (sub)
|
||||
put_be32(&image, seq++);
|
||||
put_stored(&image, raw, pack_frame(raw, depth, frame, w, h));
|
||||
chunk(&out, sub ? "fdAT" : "IDAT", image.data, image.len);
|
||||
}
|
||||
chunk(&out, "IEND", NULL, 0);
|
||||
|
||||
f = fopen(path, "wb");
|
||||
if (!f || fwrite(out.data, 1, out.len, f) != (size_t)out.len || fclose(f))
|
||||
die(path);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
const char *path = argc == 2 ? argv[1] : NULL;
|
||||
int depth = 0;
|
||||
|
||||
if (!path) {
|
||||
fprintf(stderr, "usage: apnggen OUTPUT.png\n");
|
||||
return 1;
|
||||
}
|
||||
if (strstr(path, "1bit"))
|
||||
depth = 1;
|
||||
else if (strstr(path, "2bit"))
|
||||
depth = 2;
|
||||
else if (strstr(path, "4bit"))
|
||||
depth = 4;
|
||||
if (!depth) {
|
||||
fprintf(stderr, "apnggen: unsupported file name %s\n", path);
|
||||
return 1;
|
||||
}
|
||||
write_apng(path, depth);
|
||||
return 0;
|
||||
}
|
||||
@@ -16,4 +16,22 @@ fate-apng-dispose-background2: CMD = framecrc -i $(TARGET_SAMPLES)/apng/alogo.pn
|
||||
FATE_APNG-$(call FRAMECRC, APNG, APNG) += $(FATE_APNG)
|
||||
|
||||
FATE_SAMPLES_FFMPEG += $(FATE_APNG-yes)
|
||||
fate-apng: $(FATE_APNG-yes)
|
||||
|
||||
# Generated here: a 1/2/4-bit palette animation whose later frames sit on an
|
||||
# odd x offset, so the offset is not a byte offset in the packed row.
|
||||
FATE_APNG_SMALL_BPP += fate-apng-small-bpp-1bit
|
||||
fate-apng-small-bpp-1bit: CMD = framecrc -i $(TARGET_PATH)/tests/data/fate/apng-small-bpp-1bit.png -fps_mode passthrough
|
||||
fate-apng-small-bpp-1bit: tests/data/fate/apng-small-bpp-1bit.png
|
||||
|
||||
FATE_APNG_SMALL_BPP += fate-apng-small-bpp-2bit
|
||||
fate-apng-small-bpp-2bit: CMD = framecrc -i $(TARGET_PATH)/tests/data/fate/apng-small-bpp-2bit.png -fps_mode passthrough
|
||||
fate-apng-small-bpp-2bit: tests/data/fate/apng-small-bpp-2bit.png
|
||||
|
||||
FATE_APNG_SMALL_BPP += fate-apng-small-bpp-4bit
|
||||
fate-apng-small-bpp-4bit: CMD = framecrc -i $(TARGET_PATH)/tests/data/fate/apng-small-bpp-4bit.png -fps_mode passthrough
|
||||
fate-apng-small-bpp-4bit: tests/data/fate/apng-small-bpp-4bit.png
|
||||
|
||||
FATE_APNG_SMALL_BPP-$(call FRAMECRC, APNG, APNG) += $(FATE_APNG_SMALL_BPP)
|
||||
FATE_FFMPEG += $(FATE_APNG_SMALL_BPP-yes)
|
||||
|
||||
fate-apng: $(FATE_APNG-yes) $(FATE_APNG_SMALL_BPP-yes)
|
||||
|
||||
11
tests/ref/fate/apng-small-bpp-1bit
Normal file
11
tests/ref/fate/apng-small-bpp-1bit
Normal file
@@ -0,0 +1,11 @@
|
||||
#tb 0: 1/10
|
||||
#media_type 0: video
|
||||
#codec_id 0: rawvideo
|
||||
#dimensions 0: 24x20
|
||||
#sar 0: 0/1
|
||||
0, 0, 0, 1, 1920, 0x119ecd4e
|
||||
0, 1, 1, 1, 1920, 0x4ffdcc4f
|
||||
0, 2, 2, 1, 1920, 0x877cce4d
|
||||
0, 3, 3, 1, 1920, 0x1e02ce4d
|
||||
0, 4, 4, 1, 1920, 0x1551cf4c
|
||||
0, 5, 5, 1, 1920, 0x2f7ad447
|
||||
11
tests/ref/fate/apng-small-bpp-2bit
Normal file
11
tests/ref/fate/apng-small-bpp-2bit
Normal file
@@ -0,0 +1,11 @@
|
||||
#tb 0: 1/10
|
||||
#media_type 0: video
|
||||
#codec_id 0: rawvideo
|
||||
#dimensions 0: 24x20
|
||||
#sar 0: 0/1
|
||||
0, 0, 0, 1, 1920, 0xf24744e5
|
||||
0, 1, 1, 1, 1920, 0xe89950d9
|
||||
0, 2, 2, 1, 1920, 0xec9550d9
|
||||
0, 3, 3, 1, 1920, 0xf58c50d9
|
||||
0, 4, 4, 1, 1920, 0xbce958d1
|
||||
0, 5, 5, 1, 1920, 0xd9c364c5
|
||||
11
tests/ref/fate/apng-small-bpp-4bit
Normal file
11
tests/ref/fate/apng-small-bpp-4bit
Normal file
@@ -0,0 +1,11 @@
|
||||
#tb 0: 1/10
|
||||
#media_type 0: video
|
||||
#codec_id 0: rawvideo
|
||||
#dimensions 0: 24x20
|
||||
#sar 0: 0/1
|
||||
0, 0, 0, 1, 1920, 0xf88da1b4
|
||||
0, 1, 1, 1, 1920, 0x67b7c2dd
|
||||
0, 2, 2, 1, 1920, 0x910fccd9
|
||||
0, 3, 3, 1, 1920, 0x2d6bd617
|
||||
0, 4, 4, 1, 1920, 0x630cd7dd
|
||||
0, 5, 5, 1, 1920, 0x146fd429
|
||||
Reference in New Issue
Block a user