This is an automated email from the git hooks/post-receive script.
git pushed a commit to branch y4m
in repository legacy-imlib2.
View the commit online.
commit 85d3bdc22bee98b2e82fb78ad33ff1776d9c7c25
Author: NRK <n...@disroot.org>
AuthorDate: Sun Jun 18 13:31:13 2023 +0600
Y4M loader: use custom y4m parser
avoid dependency on liby4m, which has some quality issues and isn't
available on any distros according to repology.
additionally, the new loader now supports:
* loading from memory
* multi-frame images
* mono colourspace y4m images
Fixes: https://git.enlightenment.org/old/legacy-imlib2/issues/13
---
configure.ac | 2 +-
src/modules/loaders/loader_y4m.c | 490 ++++++++++++++++++++++++++++++---------
2 files changed, 387 insertions(+), 105 deletions(-)
diff --git a/configure.ac b/configure.ac
index d666ba0..c2e09b7 100644
--- a/configure.ac
+++ b/configure.ac
@@ -225,7 +225,7 @@ EC_LOADER_CHECK(PS, auto, libspectre)
EC_LOADER_CHECK(SVG, auto, librsvg-2.0 >= 2.46)
EC_LOADER_CHECK(TIFF, auto, libtiff-4)
EC_LOADER_CHECK(WEBP, auto, libwebpdemux)
-EC_LOADER_CHECK(Y4M, auto, liby4m libyuv)
+EC_LOADER_CHECK(Y4M, auto, libyuv)
# Decompressors
loader_check_bz2() {
diff --git a/src/modules/loaders/loader_y4m.c b/src/modules/loaders/loader_y4m.c
index 9637e79..aefbcf9 100644
--- a/src/modules/loaders/loader_y4m.c
+++ b/src/modules/loaders/loader_y4m.c
@@ -1,143 +1,425 @@
-/*
- * Loader for Y4M images.
- *
- * Only loads the image.
- */
#include "config.h"
#include "Imlib2_Loader.h"
-#include <stdio.h>
-#include <unistd.h>
+#include <limits.h>
+#include <stddef.h>
+#include <stdint.h>
-#include <y4mTypes.h>
-#include <liby4m.h>
#include <libyuv.h>
+#define DBG_PFX "LDR-y4m"
+
static const char *const _formats[] = { "y4m" };
-// Notes:
-// * y4m does not support alpha channels.
+// Parser code taken from (commit 98c2822):
+// https://codeberg.org/NRK/slashtmp/src/branch/master/parsers/y4m.c
+//
+// only parses the format, doesn't do any colorspace conversion.
+// comments and frame parameters are ignored.
+//
+// This is free and unencumbered software released into the public domain.
+// For more information, please refer to <https://unlicense.org/>
+#define Y4M_PARSE_API static
+#if IMLIB2_DEBUG
+#define Y4M_PARSE_ASSERT(X) do { if (!(X)) D("%d: %s\n", __LINE__, #X); } while (0)
+#else
+#define Y4M_PARSE_ASSERT(X)
+#endif
+
+enum Y4mParseStatus {
+ Y4M_PARSE_OK,
+ Y4M_PARSE_NOT_Y4M,
+ Y4M_PARSE_EOF,
+ Y4M_PARSE_CORRUPTED,
+ Y4M_PARSE_UNSUPPORTED,
+};
+
+typedef struct {
+ ptrdiff_t w, h;
+ enum { /* also represents the delay in miliseconds */
+ Y4M_PARSE_FPS_INVALID = 0,
+ Y4M_PARSE_FPS_23_976 = 41,
+ Y4M_PARSE_FPS_24 = 41,
+ Y4M_PARSE_FPS_25 = 40,
+ Y4M_PARSE_FPS_29_97 = 33,
+ Y4M_PARSE_FPS_30 = 33,
+ } fps;
+ enum {
+ Y4M_PARSE_CS_420, /* default picked from ffmpeg */
+ Y4M_PARSE_CS_420JPEG,
+ Y4M_PARSE_CS_420PALDV,
+ Y4M_PARSE_CS_422,
+ Y4M_PARSE_CS_444,
+ Y4M_PARSE_CS_MONO,
+ } colour_space;
+ enum {
+ Y4M_PARSE_IL_PROGRESSIVE,
+ Y4M_PARSE_IL_TOP,
+ Y4M_PARSE_IL_BOTTOM,
+ Y4M_PARSE_IL_MIXED,
+ } interlacing;
+ enum {
+ Y4M_PARSE_ASPECT_DEFAULT,
+ Y4M_PARSE_ASPECT_UNKNOWN,
+ Y4M_PARSE_ASPECT_1_1,
+ Y4M_PARSE_ASPECT_4_3,
+ Y4M_PARSE_ASPECT_4_5,
+ Y4M_PARSE_ASPECT_32_27,
+ } aspect;
+
+ const void *frame_data;
+ ptrdiff_t frame_data_len;
+ const void *y, *u, *v;
+ ptrdiff_t y_stride, u_stride, v_stride;
+
+ // private
+ const uint8_t *p, *end;
+} Y4mParse;
+
+Y4M_PARSE_API enum Y4mParseStatus y4m_parse_init(Y4mParse * res,
+ const void *buffer,
+ ptrdiff_t size);
+Y4M_PARSE_API enum Y4mParseStatus y4m_parse_frame(Y4mParse * res);
+
+// implementation
-// in the following code, "encoded" refers to YUV data (as present in a
-// y4m file).
static int
-_load(ImlibImage * im, int load_data)
+y4m__int(ptrdiff_t *out, const uint8_t ** p, const uint8_t * end)
{
- static const uint8_t magic[10] = "YUV4MPEG2 ";
- int rc = LOAD_FAIL;
- int broken_image = 0;
- uint32_t *ptr = NULL;
+ uint64_t n = 0;
+ const uint8_t *s = *p;
- /* we do not support the file being loaded from memory */
- if (!im->fi->fp)
- goto quit;
+ for (; s < end && *s >= '0' && *s <= '9'; ++s)
+ {
+ n = (n * 10) + (*s - '0');
+ if (n > INT_MAX)
+ {
+ return 0;
+ }
+ }
+ *out = n;
+ *p = s;
+ return 1;
+}
- /* guess whether this is a y4m file */
- const char *fptr = im->fi->fdata;
+static int
+y4m__match(const char *match, ptrdiff_t mlen,
+ const uint8_t ** p, const uint8_t * end)
+{
+ const uint8_t *m = (uint8_t *) match;
+ const uint8_t *mend = m + mlen;
+ const uint8_t *s = *p;
- if (im->fi->fsize < (int)sizeof(magic) ||
- memcmp(fptr, magic, sizeof(magic)) != 0)
- goto quit;
+ for (; s < end && m < mend && *s == *m; ++s, ++m)
+ ;
+ if (m == mend)
+ {
+ *p = s;
+ return 1;
+ }
+ return 0;
+}
- /* format accepted */
- rc = LOAD_BADIMAGE;
+static enum Y4mParseStatus
+y4m__parse_params(Y4mParse * res, const uint8_t ** start, const uint8_t * end)
+{
+ const uint8_t *p = *start;
- /* liby4m insists of getting control of the fp, so we will dup it */
- FILE *fp2 = fdopen(dup(fileno(im->fi->fp)), "r");
+ for (;;)
+ {
+ if (end - p <= 0)
+ return Y4M_PARSE_CORRUPTED;
+ switch (*p++)
+ {
+ case ' ':
+ break;
+ case '\n':
+ *start = p;
+ if (res->w < 0 || res->h < 0 || res->fps == Y4M_PARSE_FPS_INVALID)
+ return Y4M_PARSE_CORRUPTED;
+ return Y4M_PARSE_OK;
+ break;
+ case 'W':
+ if (!y4m__int(&res->w, &p, end))
+ return Y4M_PARSE_CORRUPTED;
+ break;
+ case 'H':
+ if (!y4m__int(&res->h, &p, end))
+ return Y4M_PARSE_CORRUPTED;
+ break;
+ case 'F':
+ if (y4m__match("30:1", 4, &p, end))
+ res->fps = Y4M_PARSE_FPS_30;
+ else if (y4m__match("24:1", 4, &p, end))
+ res->fps = Y4M_PARSE_FPS_24;
+ else if (y4m__match("25:1", 4, &p, end))
+ res->fps = Y4M_PARSE_FPS_25;
+ else if (y4m__match("30000:1001", 10, &p, end))
+ res->fps = Y4M_PARSE_FPS_29_97;
+ else if (y4m__match("24000:1001", 10, &p, end))
+ res->fps = Y4M_PARSE_FPS_23_976;
+ else
+ return Y4M_PARSE_CORRUPTED;
+ break;
+ case 'I':
+ if (y4m__match("p", 1, &p, end))
+ res->interlacing = Y4M_PARSE_IL_PROGRESSIVE;
+ else if (y4m__match("t", 1, &p, end))
+ res->interlacing = Y4M_PARSE_IL_TOP;
+ else if (y4m__match("b", 1, &p, end))
+ res->interlacing = Y4M_PARSE_IL_BOTTOM;
+ else if (y4m__match("m", 1, &p, end))
+ res->interlacing = Y4M_PARSE_IL_MIXED;
+ else
+ return Y4M_PARSE_CORRUPTED;
+ break;
+ case 'C':
+ if (y4m__match("mono", 4, &p, end))
+ res->colour_space = Y4M_PARSE_CS_MONO;
+ else if (y4m__match("420jpeg", 7, &p, end))
+ res->colour_space = Y4M_PARSE_CS_420JPEG;
+ else if (y4m__match("420paldv", 8, &p, end))
+ res->colour_space = Y4M_PARSE_CS_420PALDV;
+ else if (y4m__match("420", 3, &p, end))
+ res->colour_space = Y4M_PARSE_CS_420;
+ else if (y4m__match("422", 3, &p, end))
+ res->colour_space = Y4M_PARSE_CS_422;
+ else if (y4m__match("444", 3, &p, end))
+ res->colour_space = Y4M_PARSE_CS_444;
+ else
+ return Y4M_PARSE_CORRUPTED;
+ break;
+ case 'A':
+ if (y4m__match("0:0", 3, &p, end))
+ res->aspect = Y4M_PARSE_ASPECT_UNKNOWN;
+ else if (y4m__match("1:1", 3, &p, end))
+ res->aspect = Y4M_PARSE_ASPECT_1_1;
+ else if (y4m__match("4:3", 3, &p, end))
+ res->aspect = Y4M_PARSE_ASPECT_4_3;
+ else if (y4m__match("4:5", 3, &p, end))
+ res->aspect = Y4M_PARSE_ASPECT_4_5;
+ else if (y4m__match("32:27", 5, &p, end))
+ res->aspect = Y4M_PARSE_ASPECT_32_27;
+ else
+ return Y4M_PARSE_CORRUPTED;
+ break;
+ case 'X': /* comments ignored */
+ for (; p < end && *p != ' ' && *p != '\n'; ++p)
+ ;
+ break;
+ default:
+ return Y4M_PARSE_CORRUPTED;
+ break;
+ }
+ }
+ Y4M_PARSE_ASSERT(!"unreachable");
+ return -1; // silence warning
+}
- /* read the input file pointer */
- y4mFile_t y4m_file;
+Y4M_PARSE_API enum Y4mParseStatus
+y4m_parse_init(Y4mParse * res, const void *buffer, ptrdiff_t size)
+{
+ const char magic[10] = "YUV4MPEG2 ";
- if (y4mOpenFp(&y4m_file, fp2))
+ memset(res, 0x0, sizeof(*res));
+ res->w = res->h = -1;
+ res->p = buffer;
+ res->end = res->p + size;
+
+ if (!y4m__match(magic, sizeof(magic), &res->p, res->end))
{
- broken_image = 1;
- goto clean;
+ return Y4M_PARSE_NOT_Y4M;
}
+ return y4m__parse_params(res, &res->p, res->end);
+}
- /* read image info */
- im->w = y4mGetWidth(&y4m_file);
- im->h = y4mGetHeight(&y4m_file);
- if (!IMAGE_DIMENSIONS_OK(im->w, im->h))
- goto clean;
- /* y4m does not support alpha channels */
- im->has_alpha = 0;
+Y4M_PARSE_API enum Y4mParseStatus
+y4m_parse_frame(Y4mParse * res)
+{
+ ptrdiff_t npixels, sdiv, voff;
+ const uint8_t *p = res->p, *end = res->end;
- /* check whether we are done */
- if (!load_data)
- goto clean;
-
- /* load data */
- /* allocate space for the raw image */
- ptr = __imlib_AllocateData(im);
- if (!ptr)
- goto clean;
- uint8_t *data = "" *) ptr;
- char *input_ptr = y4mGetFrameDataPointer(&y4m_file);
-
- /* convert input to ARGB (as expected by imlib) */
- if (y4m_file.colourspace == COLOUR_C422)
+ Y4M_PARSE_ASSERT(p <= end);
+ if (p == end)
{
- if (I422ToARGB((const uint8_t *)input_ptr, // src_y,
- im->w, // src_stride_y
- (const uint8_t *)(input_ptr + im->w * im->h), // src_u
- im->w / 2, // src_stride_u
- (const uint8_t *)(input_ptr + im->w * im->h * 3 / 2), // src_v
- im->w / 2, // src_stride_v
- data, // dst_bgra
- im->w * 4, // dst_stride_bgra
- im->w, // width
- im->h) != 0) // height
- goto clean;
+ return Y4M_PARSE_EOF;
+ }
+ if (!y4m__match("FRAME", 5, &p, end))
+ {
+ return Y4M_PARSE_CORRUPTED;
+ }
+ // NOTE: skip frame params, ffmpeg seems to do the same...
+ for (; p < end && *p != '\n'; ++p)
+ ;
+ if (p == end)
+ return Y4M_PARSE_CORRUPTED;
+ ++p; /* skip '\n' */
+ res->frame_data = p;
+ npixels = res->w * res->h;
+ switch (res->colour_space)
+ {
+ case Y4M_PARSE_CS_420JPEG:
+ case Y4M_PARSE_CS_420PALDV:
+ case Y4M_PARSE_CS_420:
+ res->frame_data_len = npixels * 3 / 2;
+ sdiv = 2;
+ voff = (npixels * 5) / 4;
+ break;
+ case Y4M_PARSE_CS_422:
+ res->frame_data_len = npixels * 2;
+ sdiv = 2;
+ voff = (npixels * 3) / 2;
+ break;
+ case Y4M_PARSE_CS_444:
+ res->frame_data_len = npixels * 3;
+ sdiv = 1;
+ voff = npixels * 2;
+ break;
+ case Y4M_PARSE_CS_MONO:
+ res->frame_data_len = npixels;
+ break;
+ default:
+ return Y4M_PARSE_UNSUPPORTED;
+ break;
}
- else if (y4m_file.colourspace == COLOUR_C444)
+ if (end - p < res->frame_data_len)
{
- if (I444ToARGB((const uint8_t *)input_ptr, // src_y,
- im->w, // src_stride_y
- (const uint8_t *)(input_ptr + im->w * im->h), // src_u
- im->w, // src_stride_u
- (const uint8_t *)(input_ptr + im->w * im->h * 2), // src_v
- im->w, // src_stride_v
- data, // dst_bgra
- im->w * 4, // dst_stride_bgra
- im->w, // width
- im->h) != 0) // height
- goto clean;
+ return Y4M_PARSE_CORRUPTED;
+ }
+ res->y = p;
+ res->y_stride = res->w;
+ if (res->colour_space == Y4M_PARSE_CS_MONO)
+ {
+ res->u = res->v = NULL;
+ res->u_stride = res->v_stride = 0;
}
else
- { /* 4:2:0 */
- if (I420ToARGB((const uint8_t *)input_ptr, // src_y,
- im->w, // src_stride_y
- (const uint8_t *)(input_ptr + im->w * im->h), // src_u
- im->w / 2, // src_stride_u
- (const uint8_t *)(input_ptr + im->w * im->h * 5 / 4), // src_v
- im->w / 2, // src_stride_v
- data, // dst_bgra
- im->w * 4, // dst_stride_bgra
- im->w, // width
- im->h) != 0) // height
- goto clean;
+ {
+ res->u = p + npixels;
+ res->v = p + voff;
+ res->u_stride = res->v_stride = res->w / sdiv;
}
- rc = LOAD_SUCCESS;
+ res->p = p + res->frame_data_len; /* advance to next potential frame */
+ Y4M_PARSE_ASSERT(res->p <= end);
- /* implement progress callback */
- if (im->lc)
- __imlib_LoadProgressRows(im, 0, im->h);
+ return Y4M_PARSE_OK;
+}
+
+// END Y4mParse library
+
+/* wrapper for mono colour space to match the signature of other yuv conversion
+ * routines. */
+static int
+conv_mono(const uint8_t * y, int y_stride, const uint8_t * u, int u_stride,
+ const uint8_t * v, int v_stride, uint8_t * dst, int dst_stride,
+ int width, int height)
+{
+ return I400ToARGB(y, y_stride, dst, dst_stride, width, height);
+}
+
+static int
+_load(ImlibImage * im, int load_data)
+{
+ Y4mParse y4m;
+ int res, fcount, frame;
+ ImlibImageFrame *pf = NULL;
+ int (*conv)(const uint8_t *, int, const uint8_t *, int,
+ const uint8_t *, int, uint8_t *, int, int, int);
+
+ if (y4m_parse_init(&y4m, im->fi->fdata, im->fi->fsize) != Y4M_PARSE_OK)
+ return LOAD_FAIL;
+
+ frame = im->frame;
+ if (frame > 0)
+ {
+ fcount = 0;
+ // NOTE: this is fairly cheap since nothing is being decoded.
+ for (Y4mParse tmp = y4m; y4m_parse_frame(&tmp) == Y4M_PARSE_OK;)
+ ++fcount;
+ if (fcount == 0)
+ return LOAD_BADIMAGE;
+ if (frame > fcount)
+ return LOAD_BADFRAME;
+
+ pf = __imlib_GetFrame(im);
+ if (!pf)
+ return LOAD_OOM;
+ pf->frame_count = fcount;
+ pf->loop_count = 0; /* Loop forever */
+ if (pf->frame_count > 1)
+ pf->frame_flags |= FF_IMAGE_ANIMATED;
+ }
+ else
+ {
+ frame = 1;
+ }
- clean:
- y4mCloseFile(&y4m_file);
- if (broken_image == 1)
+ for (int i = 0; i < frame; ++i)
{
- QUIT_WITH_RC(LOAD_BADFILE);
+ if (y4m_parse_frame(&y4m) != Y4M_PARSE_OK)
+ return LOAD_BADIMAGE;
}
+
+ if (!IMAGE_DIMENSIONS_OK(y4m.w, y4m.h))
+ return LOAD_BADIMAGE;
+ // no interlacing support
+ if (y4m.interlacing != Y4M_PARSE_IL_PROGRESSIVE)
+ return LOAD_BADIMAGE;
+ // treat unknown and default as 1:1 similar to ffmpeg
+ if (y4m.aspect > Y4M_PARSE_ASPECT_1_1) /* no support for non 1:1 */
+ return LOAD_BADIMAGE;
+
+ im->w = y4m.w;
+ im->h = y4m.h;
+ im->has_alpha = 0;
+
+ switch (y4m.colour_space)
+ {
+ case Y4M_PARSE_CS_MONO:
+ conv = conv_mono;
+ break;
+ case Y4M_PARSE_CS_422:
+ conv = I422ToARGB;
+ break;
+ case Y4M_PARSE_CS_444:
+ conv = I444ToARGB;
+ break;
+ case Y4M_PARSE_CS_420JPEG:
+ case Y4M_PARSE_CS_420PALDV:
+ case Y4M_PARSE_CS_420:
+ conv = I420ToARGB;
+ break;
+ default:
+ DL("colour_space: %d\n", y4m.colour_space);
+ return LOAD_BADIMAGE;
+ break;
+ }
+
+ if (pf)
+ {
+ pf->canvas_w = im->w;
+ pf->canvas_h = im->h;
+ pf->frame_delay = y4m.fps;
+ }
+
if (!load_data)
- QUIT_WITH_RC(LOAD_SUCCESS);
- if (!ptr)
- QUIT_WITH_RC(LOAD_OOM);
- quit:
- return rc;
+ return LOAD_SUCCESS;
+
+ if (!__imlib_AllocateData(im))
+ return LOAD_OOM;
+
+ res = conv(y4m.y, y4m.y_stride, y4m.u, y4m.u_stride, y4m.v, y4m.v_stride,
+ (uint8_t *) im->data, im->w * 4, im->w, im->h);
+ if (res != 0)
+ return LOAD_BADIMAGE;
+
+ if (im->lc)
+ __imlib_LoadProgressRows(im, 0, im->h);
+
+ return LOAD_SUCCESS;
}
IMLIB_LOADER(_formats, _load, NULL);
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.