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.

Reply via email to