summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKim Woelders <kim@woelders.dk>2022-01-15 19:25:19 +0100
committerKim Woelders <kim@woelders.dk>2022-01-16 20:53:58 +0100
commit88912466105d731ff7b73af76cb4cd707876e518 (patch)
tree78f082fc2650207e8e1def1c19493561b4db379c
parent5dfccd57134f0f7ed9eaafe9e15d1bf25886e31a (diff)
downloadimlib2-88912466105d731ff7b73af76cb4cd707876e518.tar.gz
PNG loader: Add multiframe support
-rw-r--r--src/modules/loaders/loader_png.c286
1 files changed, 285 insertions, 1 deletions
diff --git a/src/modules/loaders/loader_png.c b/src/modules/loaders/loader_png.c
index bd6777b..a395972 100644
--- a/src/modules/loaders/loader_png.c
+++ b/src/modules/loaders/loader_png.c
@@ -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)