PNG loader: Add multiframe support

This commit is contained in:
Kim Woelders 2022-01-15 19:25:19 +01:00
parent 5dfccd5713
commit 8891246610
1 changed files with 285 additions and 1 deletions

View File

@ -3,6 +3,7 @@
#include <png.h>
#include <stdint.h>
#include <sys/mman.h>
#include <arpa/inet.h>
#define DBG_PFX "LDR-png"
@ -11,11 +12,85 @@
#define _PNG_MIN_SIZE 60 /* Min. PNG file size (8 + 3*12 + 13 (+3) */
#define _PNG_SIG_SIZE 8 /* Signature size */
#define T(a,b,c,d) ((a << 0) | (b << 8) | (c << 16) | (d << 24))
#define PNG_TYPE_IHDR T('I', 'H', 'D', 'R')
#define PNG_TYPE_acTL T('a', 'c', 'T', 'L')
#define PNG_TYPE_fcTL T('f', 'c', 'T', 'L')
#define PNG_TYPE_fdAT T('f', 'd', 'A', 'T')
#define PNG_TYPE_IDAT T('I', 'D', 'A', 'T')
#define PNG_TYPE_IEND T('I', 'E', 'N', 'D')
#define APNG_DISPOSE_OP_NONE 0
#define APNG_DISPOSE_OP_BACKGROUND 1
#define APNG_DISPOSE_OP_PREVIOUS 2
#define APNG_BLEND_OP_SOURCE 0
#define APNG_BLEND_OP_OVER 1
typedef struct {
uint32_t len;
union {
uint32_t type;
char name[4];
};
} png_chunk_hdr_t;
/* IHDR */
typedef struct {
uint32_t w; // Width
uint32_t h; // Height
uint8_t depth; // Bit depth (1, 2, 4, 8, or 16)
uint8_t color; // Color type (0, 2, 3, 4, or 6)
uint8_t comp; // Compression method (0)
uint8_t filt; // filter method (0)
uint8_t interl; // interlace method (0 "no interlace" or 1 "Adam7 interlace")
} png_ihdr_t;
#define _PNG_IHDR_SIZE (4 + 4 + 13 + 4)
/* acTL */
typedef struct {
uint32_t num_frames; // Number of frames
uint32_t num_plays; // Number of times to loop this APNG. 0 indicates infinite looping.
} png_actl_t;
/* fcTL */
typedef struct {
uint32_t frame; // -- // Sequence number of the animation chunk, starting from 0
uint32_t w; // -- // Width of the following frame
uint32_t h; // -- // Height of the following frame
uint32_t x; // -- // X position at which to render the following frame
uint32_t y; // -- // Y position at which to render the following frame
uint16_t delay_num; // Frame delay fraction numerator
uint16_t delay_den; // Frame delay fraction denominator
uint8_t dispose_op; // Type of frame area disposal to be done after rendering this frame
uint8_t blend_op; // Type of frame area rendering for this frame
} png_fctl_t;
/* fdAT */
typedef struct {
uint32_t frame; // -- // Sequence number of the animation chunk, starting from 0
uint8_t data[];
} png_fdat_t;
typedef struct {
png_chunk_hdr_t hdr;
union {
png_ihdr_t ihdr;
png_actl_t actl;
png_fctl_t fctl;
png_fdat_t fdat;
};
uint32_t crc; // Misplaced - just indication
} png_chunk_t;
typedef struct {
ImlibImage *im;
char load_data;
char rc;
const png_chunk_t *pch_fctl; // Placed here to avoid clobber warning
char interlace;
} ctx_t;
@ -59,6 +134,7 @@ info_callback(png_struct * png_ptr, png_info * info_ptr)
rc = LOAD_BADIMAGE; /* Format accepted */
/* Semi-redundant fetch/check */
png_get_IHDR(png_ptr, info_ptr, &w32, &h32, &bit_depth, &color_type,
&interlace_type, NULL, NULL);
@ -84,6 +160,7 @@ info_callback(png_struct * png_ptr, png_info * info_ptr)
QUIT_WITH_RC(LOAD_SUCCESS);
/* Load data */
ctx->interlace = interlace_type;
/* Prep for transformations... ultimately we want ARGB */
/* expand palette -> RGB if necessary */
@ -216,6 +293,13 @@ load2(ImlibImage * im, int load_data)
png_structp png_ptr = NULL;
png_infop info_ptr = NULL;
ctx_t ctx = { 0 };
int ic;
unsigned char *fptr;
const png_chunk_t *chunk;
const png_fctl_t *pfctl;
unsigned int len, val;
int w, h, frame, save_fdat;
png_chunk_t cbuf;
/* read header */
rc = LOAD_FAIL;
@ -253,7 +337,207 @@ load2(ImlibImage * im, int load_data)
png_set_progressive_read_fn(png_ptr, &ctx,
info_callback, row_callback, NULL);
png_process_data(png_ptr, info_ptr, fdata, im->fsize);
if (im->frame_num <= 0)
goto scan_done;
/* Animation info requested. Look it up to find the frame's
* w,h which we need for making a "fake" IHDR in next pass. */
frame = 0; /* Frame number */
ctx.pch_fctl = NULL; /* Ponter to requested frame fcTL chunk */
fptr = (unsigned char *)fdata + _PNG_SIG_SIZE;
for (ic = 0;; ic++, fptr += 8 + len + 4)
{
chunk = (const png_chunk_t *)fptr;
len = htonl(chunk->hdr.len);
D("Scan %3d: %06lx: %6d: %.4s: ", ic,
fptr - (unsigned char *)fdata, len, chunk->hdr.name);
if (fptr + len - (unsigned char *)fdata > im->fsize)
break;
switch (chunk->hdr.type)
{
case PNG_TYPE_IDAT:
D("\n");
if (im->frame_count == 0)
goto scan_done; /* No acTL before IDAT - Regular PNG */
break;
case PNG_TYPE_acTL:
#define P (&chunk->actl)
im->frame_count = htonl(P->num_frames);
D("num_frames=%d num_plays=%d\n", im->frame_count,
htonl(P->num_plays));
if (im->frame_num > im->frame_count)
QUIT_WITH_RC(LOAD_BADFRAME);
break;
#undef P
case PNG_TYPE_fcTL:
#define P (&chunk->fctl)
frame++;
D("frame=%d(%d) x,y=%d,%d wxh=%dx%d delay=%d/%d disp=%d blend=%d\n", //
frame, htonl(P->frame),
htonl(P->x), htonl(P->y), htonl(P->w), htonl(P->h),
htons(P->delay_num), htons(P->delay_den),
P->dispose_op, P->blend_op);
if (im->frame_num != frame)
break;
ctx.pch_fctl = chunk; /* Remember fcTL location */
#if IMLIB2_DEBUG
break; /* Show all frames */
#else
goto scan_done;
#endif
#undef P
case PNG_TYPE_IEND:
D("\n");
goto scan_check;
default:
D("\n");
break;
}
}
scan_check:
if (!ctx.pch_fctl)
goto quit; /* Requested frame not found */
scan_done:
/* Now feed data into libpng to extract requested frame */
save_fdat = 0;
/* At this point we start "progressive" PNG data processing */
fptr = fdata;
png_process_data(png_ptr, info_ptr, fptr, _PNG_SIG_SIZE);
fptr = (unsigned char *)fdata + _PNG_SIG_SIZE;
for (ic = 0;; ic++, fptr += 8 + len + 4)
{
chunk = (const png_chunk_t *)fptr;
len = htonl(chunk->hdr.len);
D("Chunk %3d: %06lx: %6d: %.4s: ", ic,
fptr - (unsigned char *)fdata, len, chunk->hdr.name);
if (fptr + len - (unsigned char *)fdata > im->fsize)
break;
switch (chunk->hdr.type)
{
case PNG_TYPE_IHDR:
#define P (&chunk->ihdr)
w = htonl(P->w);
h = htonl(P->h);
if (!ctx.pch_fctl)
{
D("WxH=%dx%d depth=%d color=%d comp=%d filt=%d interlace=%d\n", //
w, h, P->depth, P->color, P->comp, P->filt, P->interl);
break; /* Process actual IHDR chunk */
}
/* Deal with frame animation info */
pfctl = &ctx.pch_fctl->fctl;
#if 0
im->w = htonl(pfctl->w);
im->h = htonl(pfctl->h);
#endif
im->canvas_w = w;
im->canvas_h = h;
im->frame_x = htonl(pfctl->x);
im->frame_y = htonl(pfctl->y);
if (pfctl->dispose_op == APNG_DISPOSE_OP_BACKGROUND)
im->frame_flags |= FF_FRAME_DISPOSE_CLEAR;
else if (pfctl->dispose_op == APNG_DISPOSE_OP_PREVIOUS)
im->frame_flags |= FF_FRAME_DISPOSE_PREV;
if (pfctl->blend_op != APNG_BLEND_OP_SOURCE)
im->frame_flags |= FF_FRAME_BLEND;
val = htons(pfctl->delay_den);
im->frame_delay =
val > 0 ? 1000 * htons(pfctl->delay_num) / val : 100;
D("WxH=%dx%d(%dx%d) X,Y=%d,%d depth=%d color=%d comp=%d filt=%d interlace=%d disp=%d blend=%d delay=%d/%d\n", //
htonl(pfctl->w), htonl(pfctl->h),
im->canvas_w, im->canvas_h, im->frame_x, im->frame_y,
P->depth, P->color, P->comp, P->filt, P->interl,
pfctl->dispose_op, pfctl->blend_op,
pfctl->delay_num, pfctl->delay_den);
if (im->frame_num <= 1)
break; /* Process actual IHDR chunk */
/* Process fake IHDR for frame */
memcpy(&cbuf, fptr, len + 12);
cbuf.ihdr.w = pfctl->w;
cbuf.ihdr.h = pfctl->h;
png_set_crc_action(png_ptr, PNG_CRC_QUIET_USE, PNG_CRC_QUIET_USE);
png_process_data(png_ptr, info_ptr, (void *)&cbuf, len + 12);
png_set_crc_action(png_ptr, PNG_CRC_DEFAULT, PNG_CRC_DEFAULT);
continue;
#undef P
case PNG_TYPE_IDAT:
D("\n");
/* Needed chunks should now be read */
/* Note - Just before starting to process data chunks libpng will
* call info_callback() */
if (im->frame_num <= 1)
break; /* Process actual IDAT chunk */
/* Jump to the record after the frame's fcTL, will typically be
* the frame's first fdAT chunk */
fptr = (unsigned char *)ctx.pch_fctl;
len = htonl(ctx.pch_fctl->hdr.len);
save_fdat = 1; /* Save fdAT's as of now (until next fcTL) */
continue;
case PNG_TYPE_acTL:
#define P (&chunk->actl)
if (im->frame_count > 1)
im->frame_flags |= FF_IMAGE_ANIMATED;
D("num_frames=%d num_plays=%d\n",
im->frame_count, htonl(P->num_plays));
continue;
#undef P
case PNG_TYPE_fcTL:
D("\n");
if (save_fdat)
goto done; /* First fcTL after frame's fdAT's - done */
continue;
case PNG_TYPE_fdAT:
#define P (&chunk->fdat)
D("\n");
if (im->frame_num <= 1)
continue;
if (!save_fdat)
continue;
/* Process fake IDAT frame data */
cbuf.hdr.len = htonl(len - 4);
memcpy(cbuf.hdr.name, "IDAT", 4);
png_process_data(png_ptr, info_ptr, (void *)&cbuf, 8);
png_set_crc_action(png_ptr, PNG_CRC_QUIET_USE, PNG_CRC_QUIET_USE);
png_process_data(png_ptr, info_ptr, (void *)P->data, len + 4 - 4);
png_set_crc_action(png_ptr, PNG_CRC_DEFAULT, PNG_CRC_DEFAULT);
continue;
#undef P
default:
D("\n");
break;
}
png_process_data(png_ptr, info_ptr, fptr, len + 12);
if (chunk->hdr.type == PNG_TYPE_IEND)
break;
}
done:
rc = ctx.rc;
if (rc <= 0)