diff mbox series

[FFmpeg-devel,v2,1/6] fflcms2: move to libavcodec

Message ID 20220629101251.13236-2-ffmpeg@haasn.xyz
State New
Headers show
Series ICC profile support in avcodec | expand

Checks

Context Check Description
yinshiyou/make_loongarch64 success Make finished
yinshiyou/make_fate_loongarch64 success Make fate finished
andriy/make_x86 success Make finished
andriy/make_fate_x86 success Make fate finished

Commit Message

Niklas Haas June 29, 2022, 10:12 a.m. UTC
From: Niklas Haas <git@haasn.dev>

We will need this helper inside libavcodec in the future, so move it
there, leaving behind an #include to the raw source file in its old
location in libvfilter. This approach is inspired by the handling of
vulkan.c, and avoids us needing to expose any of it publicly (or
semi-publicly) in e.g. libavutil, thus avoiding any ABI headaches.

It's debatable whether the actual code belongs in libavcodec or
libavfilter, but I decided to put it into libavcodec because it
conceptually deals with encoding and decoding ICC profiles, and will be
used to decode embedded ICC profiles in image files.

Signed-off-by: Niklas Haas <git@haasn.dev>
---
 libavcodec/Makefile   |   1 +
 libavcodec/fflcms2.c  | 311 ++++++++++++++++++++++++++++++++++++++++++
 libavcodec/fflcms2.h  |  87 ++++++++++++
 libavfilter/fflcms2.c | 294 +--------------------------------------
 libavfilter/fflcms2.h |  65 +--------
 5 files changed, 401 insertions(+), 357 deletions(-)
 create mode 100644 libavcodec/fflcms2.c
 create mode 100644 libavcodec/fflcms2.h

Comments

Andreas Rheinhardt July 6, 2022, 2:18 p.m. UTC | #1
Niklas Haas:
> From: Niklas Haas <git@haasn.dev>
> 
> We will need this helper inside libavcodec in the future, so move it
> there, leaving behind an #include to the raw source file in its old
> location in libvfilter. This approach is inspired by the handling of
> vulkan.c, and avoids us needing to expose any of it publicly (or
> semi-publicly) in e.g. libavutil, thus avoiding any ABI headaches.

This is only correct if you work under the assumption that when building
with static libraries, all the static libraries come from the same
commit. If you e.g. allow to build with an older libavfilter and a newer
libavcodec (like with shared libs), this assumption breaks down: if the
newer version of this file exports another function, you get linker
(ODR) errors because both versions might be pulled in.
Behaviour/signature changes by any function or modifications to any of
the structs would be similarly catastrophic.

A way out of this mess would be to version everything in the header like so:
#define FFLCMS2_VERSION 1

void AV_JOIN(ff_icc_context_uninit, FFLCMS2_VERSION)(FFIccContext *s);

(of course, there should be a dedicated macro for this to reduce typing.)

A patch that makes any of the "catastrophic" modifications described
above would need to bump the version. If one uses compatible versions,
the files would be deduplicated for static builds.
It would of course have the downside that these macros would be
everywhere where it is used, not only in fflcms2.[ch].

> 
> It's debatable whether the actual code belongs in libavcodec or
> libavfilter, but I decided to put it into libavcodec because it
> conceptually deals with encoding and decoding ICC profiles, and will be
> used to decode embedded ICC profiles in image files.
> 
> Signed-off-by: Niklas Haas <git@haasn.dev>
> ---
>  libavcodec/Makefile   |   1 +
>  libavcodec/fflcms2.c  | 311 ++++++++++++++++++++++++++++++++++++++++++
>  libavcodec/fflcms2.h  |  87 ++++++++++++
>  libavfilter/fflcms2.c | 294 +--------------------------------------
>  libavfilter/fflcms2.h |  65 +--------
>  5 files changed, 401 insertions(+), 357 deletions(-)
>  create mode 100644 libavcodec/fflcms2.c
>  create mode 100644 libavcodec/fflcms2.h
>
Niklas Haas July 12, 2022, 12:09 p.m. UTC | #2
On Wed, 06 Jul 2022 16:18:21 +0200 Andreas Rheinhardt <andreas.rheinhardt@outlook.com> wrote:
> Niklas Haas:
> > From: Niklas Haas <git@haasn.dev>
> > 
> > We will need this helper inside libavcodec in the future, so move it
> > there, leaving behind an #include to the raw source file in its old
> > location in libvfilter. This approach is inspired by the handling of
> > vulkan.c, and avoids us needing to expose any of it publicly (or
> > semi-publicly) in e.g. libavutil, thus avoiding any ABI headaches.
> 
> This is only correct if you work under the assumption that when building
> with static libraries, all the static libraries come from the same
> commit. If you e.g. allow to build with an older libavfilter and a newer
> libavcodec (like with shared libs), this assumption breaks down: if the
> newer version of this file exports another function, you get linker
> (ODR) errors because both versions might be pulled in.
> Behaviour/signature changes by any function or modifications to any of
> the structs would be similarly catastrophic.
> 
> A way out of this mess would be to version everything in the header like so:
> #define FFLCMS2_VERSION 1
> 
> void AV_JOIN(ff_icc_context_uninit, FFLCMS2_VERSION)(FFIccContext *s);
> 
> (of course, there should be a dedicated macro for this to reduce typing.)
> 
> A patch that makes any of the "catastrophic" modifications described
> above would need to bump the version. If one uses compatible versions,
> the files would be deduplicated for static builds.
> It would of course have the downside that these macros would be
> everywhere where it is used, not only in fflcms2.[ch].

I suppose you could also version this API on a symbol-by-symbol basis,
with v1 being the current version (and then v2 getting a 2 suffix, and
so on).

In that case, we don't need to do anything currently, rather the first
patch to modify this API would have to worry about making sure the new
symbol doesn't conflict.

> 
> > 
> > It's debatable whether the actual code belongs in libavcodec or
> > libavfilter, but I decided to put it into libavcodec because it
> > conceptually deals with encoding and decoding ICC profiles, and will be
> > used to decode embedded ICC profiles in image files.
> > 
> > Signed-off-by: Niklas Haas <git@haasn.dev>
> > ---
> >  libavcodec/Makefile   |   1 +
> >  libavcodec/fflcms2.c  | 311 ++++++++++++++++++++++++++++++++++++++++++
> >  libavcodec/fflcms2.h  |  87 ++++++++++++
> >  libavfilter/fflcms2.c | 294 +--------------------------------------
> >  libavfilter/fflcms2.h |  65 +--------
> >  5 files changed, 401 insertions(+), 357 deletions(-)
> >  create mode 100644 libavcodec/fflcms2.c
> >  create mode 100644 libavcodec/fflcms2.h
> > 
> 
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
> 
> To unsubscribe, visit link above, or email
> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
diff mbox series

Patch

diff --git a/libavcodec/Makefile b/libavcodec/Makefile
index 050934101c..5c4f62c631 100644
--- a/libavcodec/Makefile
+++ b/libavcodec/Makefile
@@ -1235,6 +1235,7 @@  SKIPHEADERS-$(CONFIG_AMF)              += amfenc.h
 SKIPHEADERS-$(CONFIG_D3D11VA)          += d3d11va.h dxva2_internal.h
 SKIPHEADERS-$(CONFIG_DXVA2)            += dxva2.h dxva2_internal.h
 SKIPHEADERS-$(CONFIG_JNI)              += ffjni.h
+SKIPHEADERS-$(CONFIG_LCMS2)            += fflcms2.h
 SKIPHEADERS-$(CONFIG_LIBJXL)           += libjxl.h
 SKIPHEADERS-$(CONFIG_LIBVPX)           += libvpx.h
 SKIPHEADERS-$(CONFIG_LIBWEBP_ENCODER)  += libwebpenc_common.h
diff --git a/libavcodec/fflcms2.c b/libavcodec/fflcms2.c
new file mode 100644
index 0000000000..fd370fb310
--- /dev/null
+++ b/libavcodec/fflcms2.c
@@ -0,0 +1,311 @@ 
+/*
+ * Copyright (c) 2022 Niklas Haas
+ * 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 "libavutil/color_utils.h"
+#include "libavutil/csp.h"
+
+#include "fflcms2.h"
+
+static void log_cb(cmsContext ctx, cmsUInt32Number error, const char *str)
+{
+    FFIccContext *s = cmsGetContextUserData(ctx);
+    av_log(s->avctx, AV_LOG_ERROR, "lcms2: [%"PRIu32"] %s\n", error, str);
+}
+
+int ff_icc_context_init(FFIccContext *s, void *avctx)
+{
+    memset(s, 0, sizeof(*s));
+    s->avctx = avctx;
+    s->ctx = cmsCreateContext(NULL, s);
+    if (!s->ctx)
+        return AVERROR(ENOMEM);
+
+    cmsSetLogErrorHandlerTHR(s->ctx, log_cb);
+    return 0;
+}
+
+void ff_icc_context_uninit(FFIccContext *s)
+{
+    for (int i = 0; i < FF_ARRAY_ELEMS(s->curves); i++)
+        cmsFreeToneCurve(s->curves[i]);
+    cmsDeleteContext(s->ctx);
+    memset(s, 0, sizeof(*s));
+}
+
+static int get_curve(FFIccContext *s, enum AVColorTransferCharacteristic trc,
+                     cmsToneCurve **out_curve)
+{
+    if (trc >= AVCOL_TRC_NB)
+        return AVERROR_INVALIDDATA;
+
+    if (s->curves[trc])
+        goto done;
+
+    switch (trc) {
+    case AVCOL_TRC_LINEAR:
+        s->curves[trc] = cmsBuildGamma(s->ctx, 1.0);
+        break;
+    case AVCOL_TRC_GAMMA22:
+        s->curves[trc] = cmsBuildGamma(s->ctx, 2.2);
+        break;
+    case AVCOL_TRC_GAMMA28:
+        s->curves[trc] = cmsBuildGamma(s->ctx, 2.8);
+        break;
+    case AVCOL_TRC_BT709:
+    case AVCOL_TRC_SMPTE170M:
+    case AVCOL_TRC_BT2020_10:
+    case AVCOL_TRC_BT2020_12:
+        s->curves[trc] = cmsBuildParametricToneCurve(s->ctx, 4, (double[5]) {
+            /* γ = */ 1/0.45,
+            /* a = */ 1/1.099296826809442,
+            /* b = */ 1 - 1/1.099296826809442,
+            /* c = */ 1/4.5,
+            /* d = */ 4.5 * 0.018053968510807,
+        });
+        break;
+    case AVCOL_TRC_SMPTE240M:
+        s->curves[trc] = cmsBuildParametricToneCurve(s->ctx, 4, (double[5]) {
+            /* γ = */ 1/0.45,
+            /* a = */ 1/1.1115,
+            /* b = */ 1 - 1/1.1115,
+            /* c = */ 1/4.0,
+            /* d = */ 4.0 * 0.0228,
+        });
+        break;
+    case AVCOL_TRC_LOG:
+        s->curves[trc] = cmsBuildParametricToneCurve(s->ctx, 8, (double[5]) {
+            /* a = */ 1.0,
+            /* b = */ 10.0,
+            /* c = */ 2.0,
+            /* d = */ -1.0,
+            /* e = */ 0.0
+        });
+        break;
+    case AVCOL_TRC_LOG_SQRT:
+        s->curves[trc] = cmsBuildParametricToneCurve(s->ctx, 8, (double[5]) {
+            /* a = */ 1.0,
+            /* b = */ 10.0,
+            /* c = */ 2.5,
+            /* d = */ -1.0,
+            /* e = */ 0.0
+        });
+        break;
+    case AVCOL_TRC_IEC61966_2_1:
+        s->curves[trc] = cmsBuildParametricToneCurve(s->ctx, 4, (double[5]) {
+            /* γ = */ 2.4,
+            /* a = */ 1/1.055,
+            /* b = */ 1 - 1/1.055,
+            /* c = */ 1/12.92,
+            /* d = */ 12.92 * 0.0031308,
+        });
+        break;
+    case AVCOL_TRC_SMPTE428:
+        s->curves[trc] = cmsBuildParametricToneCurve(s->ctx, 2, (double[3]) {
+            /* γ = */ 2.6,
+            /* a = */ pow(52.37/48.0, 1/2.6),
+            /* b = */ 0.0
+        });
+        break;
+
+    /* Can't be represented using the existing parametric tone curves.
+     * FIXME: use cmsBuildTabulatedToneCurveFloat instead */
+    case AVCOL_TRC_IEC61966_2_4:
+    case AVCOL_TRC_BT1361_ECG:
+    case AVCOL_TRC_SMPTE2084:
+    case AVCOL_TRC_ARIB_STD_B67:
+        return AVERROR_PATCHWELCOME;
+
+    default:
+        return AVERROR_INVALIDDATA;
+    }
+
+    if (!s->curves[trc])
+        return AVERROR(ENOMEM);
+
+done:
+    *out_curve = s->curves[trc];
+    return 0;
+}
+
+int ff_icc_profile_generate(FFIccContext *s,
+                            enum AVColorPrimaries color_prim,
+                            enum AVColorTransferCharacteristic color_trc,
+                            cmsHPROFILE *out_profile)
+{
+    cmsToneCurve *tonecurve;
+    const AVColorPrimariesDesc *prim;
+    int ret;
+
+    if (!(prim = av_csp_primaries_desc_from_id(color_prim)))
+        return AVERROR_INVALIDDATA;
+    if ((ret = get_curve(s, color_trc, &tonecurve)) < 0)
+        return ret;
+
+    *out_profile = cmsCreateRGBProfileTHR(s->ctx,
+        &(cmsCIExyY) { av_q2d(prim->wp.x), av_q2d(prim->wp.y), 1.0 },
+        &(cmsCIExyYTRIPLE) {
+            .Red    = { av_q2d(prim->prim.r.x), av_q2d(prim->prim.r.y), 1.0 },
+            .Green  = { av_q2d(prim->prim.g.x), av_q2d(prim->prim.g.y), 1.0 },
+            .Blue   = { av_q2d(prim->prim.b.x), av_q2d(prim->prim.b.y), 1.0 },
+        },
+        (cmsToneCurve *[3]) { tonecurve, tonecurve, tonecurve }
+    );
+
+    return *out_profile == NULL ? AVERROR(ENOMEM) : 0;
+}
+
+int ff_icc_profile_attach(FFIccContext *s, cmsHPROFILE profile, AVFrame *frame)
+{
+    cmsUInt32Number size;
+    AVBufferRef *buf;
+
+    if (!cmsSaveProfileToMem(profile, NULL, &size))
+        return AVERROR_EXTERNAL;
+
+    buf = av_buffer_alloc(size);
+    if (!buf)
+        return AVERROR(ENOMEM);
+
+    if (!cmsSaveProfileToMem(profile, buf->data, &size) || size != buf->size) {
+        av_buffer_unref(&buf);
+        return AVERROR_EXTERNAL;
+    }
+
+    if (!av_frame_new_side_data_from_buf(frame, AV_FRAME_DATA_ICC_PROFILE, buf)) {
+        av_buffer_unref(&buf);
+        return AVERROR(ENOMEM);
+    }
+
+    return 0;
+}
+
+static av_always_inline void XYZ_xy(cmsCIEXYZ XYZ, AVCIExy *xy)
+{
+    double k = 1.0 / (XYZ.X + XYZ.Y + XYZ.Z);
+    xy->x = av_d2q(k * XYZ.X, 100000);
+    xy->y = av_d2q(k * XYZ.Y, 100000);
+}
+
+int ff_icc_profile_read_primaries(FFIccContext *s, cmsHPROFILE profile,
+                                  AVColorPrimariesDesc *out_primaries)
+{
+    static const uint8_t testprimaries[4][3] = {
+        { 0xFF,    0,    0 }, /* red */
+        {    0, 0xFF,    0 }, /* green */
+        {    0,    0, 0xFF }, /* blue */
+        { 0xFF, 0xFF, 0xFF }, /* white */
+    };
+
+    AVWhitepointCoefficients *wp = &out_primaries->wp;
+    AVPrimaryCoefficients *prim = &out_primaries->prim;
+    cmsFloat64Number prev_adapt;
+    cmsHPROFILE xyz;
+    cmsHTRANSFORM tf;
+    cmsCIEXYZ dst[4];
+
+    xyz = cmsCreateXYZProfileTHR(s->ctx);
+    if (!xyz)
+        return AVERROR(ENOMEM);
+
+    /* We need to use an unadapted observer to get the raw values */
+    prev_adapt = cmsSetAdaptationStateTHR(s->ctx, 0.0);
+    tf = cmsCreateTransformTHR(s->ctx, profile, TYPE_RGB_8, xyz, TYPE_XYZ_DBL,
+                               INTENT_ABSOLUTE_COLORIMETRIC,
+                               /* Note: These flags mostly don't do anything
+                                * anyway, but specify them regardless */
+                               cmsFLAGS_NOCACHE |
+                               cmsFLAGS_NOOPTIMIZE |
+                               cmsFLAGS_LOWRESPRECALC |
+                               cmsFLAGS_GRIDPOINTS(2));
+    cmsSetAdaptationStateTHR(s->ctx, prev_adapt);
+    cmsCloseProfile(xyz);
+    if (!tf) {
+        av_log(s->avctx, AV_LOG_ERROR, "Invalid ICC profile (e.g. CMYK)\n");
+        return AVERROR_INVALIDDATA;
+    }
+
+    cmsDoTransform(tf, testprimaries, dst, 4);
+    cmsDeleteTransform(tf);
+    XYZ_xy(dst[0], &prim->r);
+    XYZ_xy(dst[1], &prim->g);
+    XYZ_xy(dst[2], &prim->b);
+    XYZ_xy(dst[3], wp);
+    return 0;
+}
+
+int ff_icc_profile_detect_transfer(FFIccContext *s, cmsHPROFILE profile,
+                                   enum AVColorTransferCharacteristic *out_trc)
+{
+    /* 8-bit linear grayscale ramp */
+    static const uint8_t testramp[16][3] = {
+        {  1,   1,   1}, /* avoid exact zero due to log100 etc. */
+        { 17,  17,  17},
+        { 34,  34,  34},
+        { 51,  51,  51},
+        { 68,  68,  68},
+        { 85,  85,  85},
+        { 02,  02,  02},
+        {119, 119, 119},
+        {136, 136, 136},
+        {153, 153, 153},
+        {170, 170, 170},
+        {187, 187, 187},
+        {204, 204, 204},
+        {221, 221, 221},
+        {238, 238, 238},
+        {255, 255, 255},
+    };
+
+    double dst[FF_ARRAY_ELEMS(testramp)];
+
+    for (enum AVColorTransferCharacteristic trc = 0; trc < AVCOL_TRC_NB; trc++) {
+        cmsToneCurve *tonecurve;
+        cmsHPROFILE ref;
+        cmsHTRANSFORM tf;
+        double delta = 0.0;
+        if (get_curve(s, trc, &tonecurve) < 0)
+            continue;
+
+        ref = cmsCreateGrayProfileTHR(s->ctx, cmsD50_xyY(), tonecurve);
+        if (!ref)
+            return AVERROR(ENOMEM);
+
+        tf = cmsCreateTransformTHR(s->ctx, profile, TYPE_RGB_8, ref, TYPE_GRAY_DBL,
+                                   INTENT_RELATIVE_COLORIMETRIC,
+                                   cmsFLAGS_NOCACHE | cmsFLAGS_NOOPTIMIZE);
+        cmsCloseProfile(ref);
+        if (!tf) {
+            av_log(s->avctx, AV_LOG_ERROR, "Invalid ICC profile (e.g. CMYK)\n");
+            return AVERROR_INVALIDDATA;
+        }
+
+        cmsDoTransform(tf, testramp, dst, FF_ARRAY_ELEMS(dst));
+        cmsDeleteTransform(tf);
+
+        for (int i = 0; i < FF_ARRAY_ELEMS(dst); i++)
+            delta += fabs(testramp[i][0] / 255.0 - dst[i]);
+        if (delta < 0.01) {
+            *out_trc = trc;
+            return 0;
+        }
+    }
+
+    *out_trc = AVCOL_TRC_UNSPECIFIED;
+    return 0;
+}
diff --git a/libavcodec/fflcms2.h b/libavcodec/fflcms2.h
new file mode 100644
index 0000000000..af63c9a13c
--- /dev/null
+++ b/libavcodec/fflcms2.h
@@ -0,0 +1,87 @@ 
+/*
+ * Copyright (c) 2022 Niklas Haas
+ * 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
+ */
+
+/**
+ * @file
+ * Various functions for dealing with ICC profiles
+ */
+
+#ifndef AVCODEC_FFLCMS2_H
+#define AVCODEC_FFLCMS2_H
+
+#include "libavutil/csp.h"
+#include "libavutil/frame.h"
+#include "libavutil/pixfmt.h"
+
+#include <lcms2.h>
+
+typedef struct FFIccContext {
+    void *avctx;
+    cmsContext ctx;
+    cmsToneCurve *curves[AVCOL_TRC_NB]; /* tone curve cache */
+} FFIccContext;
+
+/**
+ * Initializes an FFIccContext. This must be done prior to using it.
+ *
+ * Returns 0 on success, or a negative error code.
+ */
+int ff_icc_context_init(FFIccContext *s, void *avctx);
+void ff_icc_context_uninit(FFIccContext *s);
+
+/**
+ * Generate an ICC profile for a given combination of color primaries and
+ * transfer function. Both values must be set to valid entries (not
+ * "undefined") for this function to work.
+ *
+ * Returns 0 on success, or a negative error code.
+ */
+int ff_icc_profile_generate(FFIccContext *s,
+                            enum AVColorPrimaries color_prim,
+                            enum AVColorTransferCharacteristic color_trc,
+                            cmsHPROFILE *out_profile);
+
+/**
+ * Attach an ICC profile to a frame. Helper wrapper around cmsSaveProfileToMem
+ * and av_frame_new_side_data_from_buf.
+ *
+ * Returns 0 on success, or a negative error code.
+ */
+int ff_icc_profile_attach(FFIccContext *s, cmsHPROFILE profile, AVFrame *frame);
+
+/**
+ * Read the color primaries and white point coefficients encoded by an ICC
+ * profile, and return the raw values in `out_primaries`.
+ *
+ * Returns 0 on success, or a negative error code.
+ */
+int ff_icc_profile_read_primaries(FFIccContext *s, cmsHPROFILE profile,
+                                  AVColorPrimariesDesc *out_primaries);
+
+/**
+ * Attempt detecting the transfer characteristic that best approximates the
+ * transfer function encoded by an ICC profile. Sets `out_trc` to
+ * AVCOL_TRC_UNSPECIFIED if no clear match can be identified.
+ *
+ * Returns 0 on success (including no match), or a negative error code.
+ */
+int ff_icc_profile_detect_transfer(FFIccContext *s, cmsHPROFILE profile,
+                                   enum AVColorTransferCharacteristic *out_trc);
+
+#endif /* AVCODEC_FFLCMS2_H */
diff --git a/libavfilter/fflcms2.c b/libavfilter/fflcms2.c
index fd370fb310..822462de87 100644
--- a/libavfilter/fflcms2.c
+++ b/libavfilter/fflcms2.c
@@ -1,5 +1,4 @@ 
 /*
- * Copyright (c) 2022 Niklas Haas
  * This file is part of FFmpeg.
  *
  * FFmpeg is free software; you can redistribute it and/or
@@ -17,295 +16,4 @@ 
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  */
 
-#include "libavutil/color_utils.h"
-#include "libavutil/csp.h"
-
-#include "fflcms2.h"
-
-static void log_cb(cmsContext ctx, cmsUInt32Number error, const char *str)
-{
-    FFIccContext *s = cmsGetContextUserData(ctx);
-    av_log(s->avctx, AV_LOG_ERROR, "lcms2: [%"PRIu32"] %s\n", error, str);
-}
-
-int ff_icc_context_init(FFIccContext *s, void *avctx)
-{
-    memset(s, 0, sizeof(*s));
-    s->avctx = avctx;
-    s->ctx = cmsCreateContext(NULL, s);
-    if (!s->ctx)
-        return AVERROR(ENOMEM);
-
-    cmsSetLogErrorHandlerTHR(s->ctx, log_cb);
-    return 0;
-}
-
-void ff_icc_context_uninit(FFIccContext *s)
-{
-    for (int i = 0; i < FF_ARRAY_ELEMS(s->curves); i++)
-        cmsFreeToneCurve(s->curves[i]);
-    cmsDeleteContext(s->ctx);
-    memset(s, 0, sizeof(*s));
-}
-
-static int get_curve(FFIccContext *s, enum AVColorTransferCharacteristic trc,
-                     cmsToneCurve **out_curve)
-{
-    if (trc >= AVCOL_TRC_NB)
-        return AVERROR_INVALIDDATA;
-
-    if (s->curves[trc])
-        goto done;
-
-    switch (trc) {
-    case AVCOL_TRC_LINEAR:
-        s->curves[trc] = cmsBuildGamma(s->ctx, 1.0);
-        break;
-    case AVCOL_TRC_GAMMA22:
-        s->curves[trc] = cmsBuildGamma(s->ctx, 2.2);
-        break;
-    case AVCOL_TRC_GAMMA28:
-        s->curves[trc] = cmsBuildGamma(s->ctx, 2.8);
-        break;
-    case AVCOL_TRC_BT709:
-    case AVCOL_TRC_SMPTE170M:
-    case AVCOL_TRC_BT2020_10:
-    case AVCOL_TRC_BT2020_12:
-        s->curves[trc] = cmsBuildParametricToneCurve(s->ctx, 4, (double[5]) {
-            /* γ = */ 1/0.45,
-            /* a = */ 1/1.099296826809442,
-            /* b = */ 1 - 1/1.099296826809442,
-            /* c = */ 1/4.5,
-            /* d = */ 4.5 * 0.018053968510807,
-        });
-        break;
-    case AVCOL_TRC_SMPTE240M:
-        s->curves[trc] = cmsBuildParametricToneCurve(s->ctx, 4, (double[5]) {
-            /* γ = */ 1/0.45,
-            /* a = */ 1/1.1115,
-            /* b = */ 1 - 1/1.1115,
-            /* c = */ 1/4.0,
-            /* d = */ 4.0 * 0.0228,
-        });
-        break;
-    case AVCOL_TRC_LOG:
-        s->curves[trc] = cmsBuildParametricToneCurve(s->ctx, 8, (double[5]) {
-            /* a = */ 1.0,
-            /* b = */ 10.0,
-            /* c = */ 2.0,
-            /* d = */ -1.0,
-            /* e = */ 0.0
-        });
-        break;
-    case AVCOL_TRC_LOG_SQRT:
-        s->curves[trc] = cmsBuildParametricToneCurve(s->ctx, 8, (double[5]) {
-            /* a = */ 1.0,
-            /* b = */ 10.0,
-            /* c = */ 2.5,
-            /* d = */ -1.0,
-            /* e = */ 0.0
-        });
-        break;
-    case AVCOL_TRC_IEC61966_2_1:
-        s->curves[trc] = cmsBuildParametricToneCurve(s->ctx, 4, (double[5]) {
-            /* γ = */ 2.4,
-            /* a = */ 1/1.055,
-            /* b = */ 1 - 1/1.055,
-            /* c = */ 1/12.92,
-            /* d = */ 12.92 * 0.0031308,
-        });
-        break;
-    case AVCOL_TRC_SMPTE428:
-        s->curves[trc] = cmsBuildParametricToneCurve(s->ctx, 2, (double[3]) {
-            /* γ = */ 2.6,
-            /* a = */ pow(52.37/48.0, 1/2.6),
-            /* b = */ 0.0
-        });
-        break;
-
-    /* Can't be represented using the existing parametric tone curves.
-     * FIXME: use cmsBuildTabulatedToneCurveFloat instead */
-    case AVCOL_TRC_IEC61966_2_4:
-    case AVCOL_TRC_BT1361_ECG:
-    case AVCOL_TRC_SMPTE2084:
-    case AVCOL_TRC_ARIB_STD_B67:
-        return AVERROR_PATCHWELCOME;
-
-    default:
-        return AVERROR_INVALIDDATA;
-    }
-
-    if (!s->curves[trc])
-        return AVERROR(ENOMEM);
-
-done:
-    *out_curve = s->curves[trc];
-    return 0;
-}
-
-int ff_icc_profile_generate(FFIccContext *s,
-                            enum AVColorPrimaries color_prim,
-                            enum AVColorTransferCharacteristic color_trc,
-                            cmsHPROFILE *out_profile)
-{
-    cmsToneCurve *tonecurve;
-    const AVColorPrimariesDesc *prim;
-    int ret;
-
-    if (!(prim = av_csp_primaries_desc_from_id(color_prim)))
-        return AVERROR_INVALIDDATA;
-    if ((ret = get_curve(s, color_trc, &tonecurve)) < 0)
-        return ret;
-
-    *out_profile = cmsCreateRGBProfileTHR(s->ctx,
-        &(cmsCIExyY) { av_q2d(prim->wp.x), av_q2d(prim->wp.y), 1.0 },
-        &(cmsCIExyYTRIPLE) {
-            .Red    = { av_q2d(prim->prim.r.x), av_q2d(prim->prim.r.y), 1.0 },
-            .Green  = { av_q2d(prim->prim.g.x), av_q2d(prim->prim.g.y), 1.0 },
-            .Blue   = { av_q2d(prim->prim.b.x), av_q2d(prim->prim.b.y), 1.0 },
-        },
-        (cmsToneCurve *[3]) { tonecurve, tonecurve, tonecurve }
-    );
-
-    return *out_profile == NULL ? AVERROR(ENOMEM) : 0;
-}
-
-int ff_icc_profile_attach(FFIccContext *s, cmsHPROFILE profile, AVFrame *frame)
-{
-    cmsUInt32Number size;
-    AVBufferRef *buf;
-
-    if (!cmsSaveProfileToMem(profile, NULL, &size))
-        return AVERROR_EXTERNAL;
-
-    buf = av_buffer_alloc(size);
-    if (!buf)
-        return AVERROR(ENOMEM);
-
-    if (!cmsSaveProfileToMem(profile, buf->data, &size) || size != buf->size) {
-        av_buffer_unref(&buf);
-        return AVERROR_EXTERNAL;
-    }
-
-    if (!av_frame_new_side_data_from_buf(frame, AV_FRAME_DATA_ICC_PROFILE, buf)) {
-        av_buffer_unref(&buf);
-        return AVERROR(ENOMEM);
-    }
-
-    return 0;
-}
-
-static av_always_inline void XYZ_xy(cmsCIEXYZ XYZ, AVCIExy *xy)
-{
-    double k = 1.0 / (XYZ.X + XYZ.Y + XYZ.Z);
-    xy->x = av_d2q(k * XYZ.X, 100000);
-    xy->y = av_d2q(k * XYZ.Y, 100000);
-}
-
-int ff_icc_profile_read_primaries(FFIccContext *s, cmsHPROFILE profile,
-                                  AVColorPrimariesDesc *out_primaries)
-{
-    static const uint8_t testprimaries[4][3] = {
-        { 0xFF,    0,    0 }, /* red */
-        {    0, 0xFF,    0 }, /* green */
-        {    0,    0, 0xFF }, /* blue */
-        { 0xFF, 0xFF, 0xFF }, /* white */
-    };
-
-    AVWhitepointCoefficients *wp = &out_primaries->wp;
-    AVPrimaryCoefficients *prim = &out_primaries->prim;
-    cmsFloat64Number prev_adapt;
-    cmsHPROFILE xyz;
-    cmsHTRANSFORM tf;
-    cmsCIEXYZ dst[4];
-
-    xyz = cmsCreateXYZProfileTHR(s->ctx);
-    if (!xyz)
-        return AVERROR(ENOMEM);
-
-    /* We need to use an unadapted observer to get the raw values */
-    prev_adapt = cmsSetAdaptationStateTHR(s->ctx, 0.0);
-    tf = cmsCreateTransformTHR(s->ctx, profile, TYPE_RGB_8, xyz, TYPE_XYZ_DBL,
-                               INTENT_ABSOLUTE_COLORIMETRIC,
-                               /* Note: These flags mostly don't do anything
-                                * anyway, but specify them regardless */
-                               cmsFLAGS_NOCACHE |
-                               cmsFLAGS_NOOPTIMIZE |
-                               cmsFLAGS_LOWRESPRECALC |
-                               cmsFLAGS_GRIDPOINTS(2));
-    cmsSetAdaptationStateTHR(s->ctx, prev_adapt);
-    cmsCloseProfile(xyz);
-    if (!tf) {
-        av_log(s->avctx, AV_LOG_ERROR, "Invalid ICC profile (e.g. CMYK)\n");
-        return AVERROR_INVALIDDATA;
-    }
-
-    cmsDoTransform(tf, testprimaries, dst, 4);
-    cmsDeleteTransform(tf);
-    XYZ_xy(dst[0], &prim->r);
-    XYZ_xy(dst[1], &prim->g);
-    XYZ_xy(dst[2], &prim->b);
-    XYZ_xy(dst[3], wp);
-    return 0;
-}
-
-int ff_icc_profile_detect_transfer(FFIccContext *s, cmsHPROFILE profile,
-                                   enum AVColorTransferCharacteristic *out_trc)
-{
-    /* 8-bit linear grayscale ramp */
-    static const uint8_t testramp[16][3] = {
-        {  1,   1,   1}, /* avoid exact zero due to log100 etc. */
-        { 17,  17,  17},
-        { 34,  34,  34},
-        { 51,  51,  51},
-        { 68,  68,  68},
-        { 85,  85,  85},
-        { 02,  02,  02},
-        {119, 119, 119},
-        {136, 136, 136},
-        {153, 153, 153},
-        {170, 170, 170},
-        {187, 187, 187},
-        {204, 204, 204},
-        {221, 221, 221},
-        {238, 238, 238},
-        {255, 255, 255},
-    };
-
-    double dst[FF_ARRAY_ELEMS(testramp)];
-
-    for (enum AVColorTransferCharacteristic trc = 0; trc < AVCOL_TRC_NB; trc++) {
-        cmsToneCurve *tonecurve;
-        cmsHPROFILE ref;
-        cmsHTRANSFORM tf;
-        double delta = 0.0;
-        if (get_curve(s, trc, &tonecurve) < 0)
-            continue;
-
-        ref = cmsCreateGrayProfileTHR(s->ctx, cmsD50_xyY(), tonecurve);
-        if (!ref)
-            return AVERROR(ENOMEM);
-
-        tf = cmsCreateTransformTHR(s->ctx, profile, TYPE_RGB_8, ref, TYPE_GRAY_DBL,
-                                   INTENT_RELATIVE_COLORIMETRIC,
-                                   cmsFLAGS_NOCACHE | cmsFLAGS_NOOPTIMIZE);
-        cmsCloseProfile(ref);
-        if (!tf) {
-            av_log(s->avctx, AV_LOG_ERROR, "Invalid ICC profile (e.g. CMYK)\n");
-            return AVERROR_INVALIDDATA;
-        }
-
-        cmsDoTransform(tf, testramp, dst, FF_ARRAY_ELEMS(dst));
-        cmsDeleteTransform(tf);
-
-        for (int i = 0; i < FF_ARRAY_ELEMS(dst); i++)
-            delta += fabs(testramp[i][0] / 255.0 - dst[i]);
-        if (delta < 0.01) {
-            *out_trc = trc;
-            return 0;
-        }
-    }
-
-    *out_trc = AVCOL_TRC_UNSPECIFIED;
-    return 0;
-}
+#include "libavcodec/fflcms2.c"
diff --git a/libavfilter/fflcms2.h b/libavfilter/fflcms2.h
index 0d238c679f..1ac29e357b 100644
--- a/libavfilter/fflcms2.h
+++ b/libavfilter/fflcms2.h
@@ -1,5 +1,4 @@ 
 /*
- * Copyright (c) 2022 Niklas Haas
  * This file is part of FFmpeg.
  *
  * FFmpeg is free software; you can redistribute it and/or
@@ -17,71 +16,9 @@ 
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  */
 
-/**
- * @file
- * Various functions for dealing with ICC profiles
- */
-
 #ifndef AVFILTER_FFLCMS2_H
 #define AVFILTER_FFLCMS2_H
 
-#include "libavutil/csp.h"
-#include "libavutil/frame.h"
-#include "libavutil/pixfmt.h"
-
-#include <lcms2.h>
-
-typedef struct FFIccContext {
-    void *avctx;
-    cmsContext ctx;
-    cmsToneCurve *curves[AVCOL_TRC_NB]; /* tone curve cache */
-} FFIccContext;
-
-/**
- * Initializes an FFIccContext. This must be done prior to using it.
- *
- * Returns 0 on success, or a negative error code.
- */
-int ff_icc_context_init(FFIccContext *s, void *avctx);
-void ff_icc_context_uninit(FFIccContext *s);
-
-/**
- * Generate an ICC profile for a given combination of color primaries and
- * transfer function. Both values must be set to valid entries (not
- * "undefined") for this function to work.
- *
- * Returns 0 on success, or a negative error code.
- */
-int ff_icc_profile_generate(FFIccContext *s,
-                            enum AVColorPrimaries color_prim,
-                            enum AVColorTransferCharacteristic color_trc,
-                            cmsHPROFILE *out_profile);
-
-/**
- * Attach an ICC profile to a frame. Helper wrapper around cmsSaveProfileToMem
- * and av_frame_new_side_data_from_buf.
- *
- * Returns 0 on success, or a negative error code.
- */
-int ff_icc_profile_attach(FFIccContext *s, cmsHPROFILE profile, AVFrame *frame);
-
-/**
- * Read the color primaries and white point coefficients encoded by an ICC
- * profile, and return the raw values in `out_primaries`.
- *
- * Returns 0 on success, or a negative error code.
- */
-int ff_icc_profile_read_primaries(FFIccContext *s, cmsHPROFILE profile,
-                                  AVColorPrimariesDesc *out_primaries);
-
-/**
- * Attempt detecting the transfer characteristic that best approximates the
- * transfer function encoded by an ICC profile. Sets `out_trc` to
- * AVCOL_TRC_UNSPECIFIED if no clear match can be identified.
- *
- * Returns 0 on success (including no match), or a negative error code.
- */
-int ff_icc_profile_detect_transfer(FFIccContext *s, cmsHPROFILE profile,
-                                   enum AVColorTransferCharacteristic *out_trc);
+#include "libavcodec/fflcms2.h"
 
 #endif /* AVFILTER_FFLCMS2_H */