fflcms2.c 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  1. /*
  2. * Copyright (c) 2022 Niklas Haas
  3. * This file is part of FFmpeg.
  4. *
  5. * FFmpeg is free software; you can redistribute it and/or
  6. * modify it under the terms of the GNU Lesser General Public
  7. * License as published by the Free Software Foundation; either
  8. * version 2.1 of the License, or (at your option) any later version.
  9. *
  10. * FFmpeg is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  13. * Lesser General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU Lesser General Public
  16. * License along with FFmpeg; if not, write to the Free Software
  17. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  18. */
  19. #include "libavutil/csp.h"
  20. #include "fflcms2.h"
  21. static void log_cb(cmsContext ctx, cmsUInt32Number error, const char *str)
  22. {
  23. FFIccContext *s = cmsGetContextUserData(ctx);
  24. av_log(s->avctx, AV_LOG_ERROR, "lcms2: [%"PRIu32"] %s\n", error, str);
  25. }
  26. int ff_icc_context_init(FFIccContext *s, void *avctx)
  27. {
  28. memset(s, 0, sizeof(*s));
  29. s->avctx = avctx;
  30. s->ctx = cmsCreateContext(NULL, s);
  31. if (!s->ctx)
  32. return AVERROR(ENOMEM);
  33. cmsSetLogErrorHandlerTHR(s->ctx, log_cb);
  34. return 0;
  35. }
  36. void ff_icc_context_uninit(FFIccContext *s)
  37. {
  38. for (int i = 0; i < FF_ARRAY_ELEMS(s->curves); i++)
  39. cmsFreeToneCurve(s->curves[i]);
  40. cmsDeleteContext(s->ctx);
  41. memset(s, 0, sizeof(*s));
  42. }
  43. static int get_curve(FFIccContext *s, enum AVColorTransferCharacteristic trc,
  44. cmsToneCurve **out_curve)
  45. {
  46. if ((unsigned)trc < AVCOL_TRC_NB && s->curves[trc])
  47. goto done;
  48. switch (trc) {
  49. case AVCOL_TRC_LINEAR:
  50. s->curves[trc] = cmsBuildGamma(s->ctx, 1.0);
  51. break;
  52. case AVCOL_TRC_GAMMA22:
  53. s->curves[trc] = cmsBuildGamma(s->ctx, 2.2);
  54. break;
  55. case AVCOL_TRC_GAMMA28:
  56. s->curves[trc] = cmsBuildGamma(s->ctx, 2.8);
  57. break;
  58. case AVCOL_TRC_BT709:
  59. case AVCOL_TRC_SMPTE170M:
  60. case AVCOL_TRC_BT2020_10:
  61. case AVCOL_TRC_BT2020_12:
  62. s->curves[trc] = cmsBuildParametricToneCurve(s->ctx, 4, (double[5]) {
  63. /* γ = */ 1/0.45,
  64. /* a = */ 1/1.099296826809442,
  65. /* b = */ 1 - 1/1.099296826809442,
  66. /* c = */ 1/4.5,
  67. /* d = */ 4.5 * 0.018053968510807,
  68. });
  69. break;
  70. case AVCOL_TRC_SMPTE240M:
  71. s->curves[trc] = cmsBuildParametricToneCurve(s->ctx, 4, (double[5]) {
  72. /* γ = */ 1/0.45,
  73. /* a = */ 1/1.1115,
  74. /* b = */ 1 - 1/1.1115,
  75. /* c = */ 1/4.0,
  76. /* d = */ 4.0 * 0.0228,
  77. });
  78. break;
  79. case AVCOL_TRC_LOG:
  80. s->curves[trc] = cmsBuildParametricToneCurve(s->ctx, 8, (double[5]) {
  81. /* a = */ 1.0,
  82. /* b = */ 10.0,
  83. /* c = */ 2.0,
  84. /* d = */ -1.0,
  85. /* e = */ 0.0
  86. });
  87. break;
  88. case AVCOL_TRC_LOG_SQRT:
  89. s->curves[trc] = cmsBuildParametricToneCurve(s->ctx, 8, (double[5]) {
  90. /* a = */ 1.0,
  91. /* b = */ 10.0,
  92. /* c = */ 2.5,
  93. /* d = */ -1.0,
  94. /* e = */ 0.0
  95. });
  96. break;
  97. case AVCOL_TRC_IEC61966_2_1:
  98. s->curves[trc] = cmsBuildParametricToneCurve(s->ctx, 4, (double[5]) {
  99. /* γ = */ 2.4,
  100. /* a = */ 1/1.055,
  101. /* b = */ 1 - 1/1.055,
  102. /* c = */ 1/12.92,
  103. /* d = */ 12.92 * 0.0031308,
  104. });
  105. break;
  106. case AVCOL_TRC_SMPTE428:
  107. s->curves[trc] = cmsBuildParametricToneCurve(s->ctx, 2, (double[3]) {
  108. /* γ = */ 2.6,
  109. /* a = */ pow(52.37/48.0, 1/2.6),
  110. /* b = */ 0.0
  111. });
  112. break;
  113. /* Can't be represented using the existing parametric tone curves.
  114. * FIXME: use cmsBuildTabulatedToneCurveFloat instead */
  115. case AVCOL_TRC_IEC61966_2_4:
  116. case AVCOL_TRC_BT1361_ECG:
  117. case AVCOL_TRC_SMPTE2084:
  118. case AVCOL_TRC_ARIB_STD_B67:
  119. case AVCOL_TRC_V_LOG:
  120. return AVERROR_PATCHWELCOME;
  121. default:
  122. return AVERROR_INVALIDDATA;
  123. }
  124. if (!s->curves[trc])
  125. return AVERROR(ENOMEM);
  126. done:
  127. *out_curve = s->curves[trc];
  128. return 0;
  129. }
  130. int ff_icc_profile_generate(FFIccContext *s,
  131. enum AVColorPrimaries color_prim,
  132. enum AVColorTransferCharacteristic color_trc,
  133. cmsHPROFILE *out_profile)
  134. {
  135. cmsToneCurve *tonecurve;
  136. const AVColorPrimariesDesc *prim;
  137. int ret;
  138. if (!(prim = av_csp_primaries_desc_from_id(color_prim)))
  139. return AVERROR_INVALIDDATA;
  140. if ((ret = get_curve(s, color_trc, &tonecurve)) < 0)
  141. return ret;
  142. *out_profile = cmsCreateRGBProfileTHR(s->ctx,
  143. &(cmsCIExyY) { av_q2d(prim->wp.x), av_q2d(prim->wp.y), 1.0 },
  144. &(cmsCIExyYTRIPLE) {
  145. .Red = { av_q2d(prim->prim.r.x), av_q2d(prim->prim.r.y), 1.0 },
  146. .Green = { av_q2d(prim->prim.g.x), av_q2d(prim->prim.g.y), 1.0 },
  147. .Blue = { av_q2d(prim->prim.b.x), av_q2d(prim->prim.b.y), 1.0 },
  148. },
  149. (cmsToneCurve *[3]) { tonecurve, tonecurve, tonecurve }
  150. );
  151. return *out_profile == NULL ? AVERROR(ENOMEM) : 0;
  152. }
  153. int ff_icc_profile_attach(FFIccContext *s, cmsHPROFILE profile, AVFrame *frame)
  154. {
  155. cmsUInt32Number size;
  156. AVBufferRef *buf;
  157. if (!cmsSaveProfileToMem(profile, NULL, &size))
  158. return AVERROR_EXTERNAL;
  159. buf = av_buffer_alloc(size);
  160. if (!buf)
  161. return AVERROR(ENOMEM);
  162. if (!cmsSaveProfileToMem(profile, buf->data, &size) || size != buf->size) {
  163. av_buffer_unref(&buf);
  164. return AVERROR_EXTERNAL;
  165. }
  166. if (!av_frame_new_side_data_from_buf(frame, AV_FRAME_DATA_ICC_PROFILE, buf)) {
  167. av_buffer_unref(&buf);
  168. return AVERROR(ENOMEM);
  169. }
  170. return 0;
  171. }
  172. static av_always_inline void XYZ_xy(cmsCIEXYZ XYZ, AVCIExy *xy)
  173. {
  174. double k = 1.0 / (XYZ.X + XYZ.Y + XYZ.Z);
  175. xy->x = av_d2q(k * XYZ.X, 100000);
  176. xy->y = av_d2q(k * XYZ.Y, 100000);
  177. }
  178. static av_always_inline AVRational abs_sub_q(AVRational r1, AVRational r2)
  179. {
  180. AVRational diff = av_sub_q(r1, r2);
  181. /* denominator assumed to be positive */
  182. return av_make_q(abs(diff.num), diff.den);
  183. }
  184. static const AVCIExy wp_d50 = { {3457, 10000}, {3585, 10000} }; /* CIE D50 */
  185. int ff_icc_profile_sanitize(FFIccContext *s, cmsHPROFILE profile)
  186. {
  187. cmsCIEXYZ *white, fixed;
  188. AVCIExy wpxy;
  189. AVRational diff, z;
  190. if (!profile)
  191. return 0;
  192. if (cmsGetEncodedICCversion(profile) >= 0x4000000) { // ICC v4
  193. switch (cmsGetHeaderRenderingIntent(profile)) {
  194. case INTENT_RELATIVE_COLORIMETRIC:
  195. case INTENT_ABSOLUTE_COLORIMETRIC: ;
  196. /* ICC v4 colorimetric profiles are specified to always use D50
  197. * media white point, anything else is a violation of the spec.
  198. * Sadly, such profiles are incredibly common (Apple...), so make
  199. * an effort to fix them. */
  200. if (!(white = cmsReadTag(profile, cmsSigMediaWhitePointTag)))
  201. return AVERROR_INVALIDDATA;
  202. XYZ_xy(*white, &wpxy);
  203. diff = av_add_q(abs_sub_q(wpxy.x, wp_d50.x), abs_sub_q(wpxy.y, wp_d50.y));
  204. if (av_cmp_q(diff, av_make_q(1, 1000)) > 0) {
  205. av_log(s->avctx, AV_LOG_WARNING, "Invalid colorimetric ICCv4 "
  206. "profile media white point tag (expected %.4f %.4f, "
  207. "got %.4f %.4f)\n",
  208. av_q2d(wp_d50.x), av_q2d(wp_d50.y),
  209. av_q2d(wpxy.x), av_q2d(wpxy.y));
  210. /* x+y+z = 1 */
  211. z = av_sub_q(av_sub_q(av_make_q(1, 1), wp_d50.x), wp_d50.y);
  212. fixed.X = av_q2d(av_div_q(wp_d50.x, wp_d50.y)) * white->Y;
  213. fixed.Y = white->Y;
  214. fixed.Z = av_q2d(av_div_q(z, wp_d50.y)) * white->Y;
  215. if (!cmsWriteTag(profile, cmsSigMediaWhitePointTag, &fixed))
  216. return AVERROR_EXTERNAL;
  217. }
  218. break;
  219. default: break;
  220. }
  221. }
  222. return 0;
  223. }
  224. int ff_icc_profile_read_primaries(FFIccContext *s, cmsHPROFILE profile,
  225. AVColorPrimariesDesc *out_primaries)
  226. {
  227. static const uint8_t testprimaries[4][3] = {
  228. { 0xFF, 0, 0 }, /* red */
  229. { 0, 0xFF, 0 }, /* green */
  230. { 0, 0, 0xFF }, /* blue */
  231. { 0xFF, 0xFF, 0xFF }, /* white */
  232. };
  233. AVWhitepointCoefficients *wp = &out_primaries->wp;
  234. AVPrimaryCoefficients *prim = &out_primaries->prim;
  235. cmsFloat64Number prev_adapt;
  236. cmsHPROFILE xyz;
  237. cmsHTRANSFORM tf;
  238. cmsCIEXYZ dst[4];
  239. xyz = cmsCreateXYZProfileTHR(s->ctx);
  240. if (!xyz)
  241. return AVERROR(ENOMEM);
  242. /* We need to use an unadapted observer to get the raw values */
  243. prev_adapt = cmsSetAdaptationStateTHR(s->ctx, 0.0);
  244. tf = cmsCreateTransformTHR(s->ctx, profile, TYPE_RGB_8, xyz, TYPE_XYZ_DBL,
  245. INTENT_ABSOLUTE_COLORIMETRIC,
  246. /* Note: These flags mostly don't do anything
  247. * anyway, but specify them regardless */
  248. cmsFLAGS_NOCACHE |
  249. cmsFLAGS_NOOPTIMIZE |
  250. cmsFLAGS_LOWRESPRECALC |
  251. cmsFLAGS_GRIDPOINTS(2));
  252. cmsSetAdaptationStateTHR(s->ctx, prev_adapt);
  253. cmsCloseProfile(xyz);
  254. if (!tf) {
  255. av_log(s->avctx, AV_LOG_ERROR, "Invalid ICC profile (e.g. CMYK)\n");
  256. return AVERROR_INVALIDDATA;
  257. }
  258. cmsDoTransform(tf, testprimaries, dst, 4);
  259. cmsDeleteTransform(tf);
  260. XYZ_xy(dst[0], &prim->r);
  261. XYZ_xy(dst[1], &prim->g);
  262. XYZ_xy(dst[2], &prim->b);
  263. XYZ_xy(dst[3], wp);
  264. return 0;
  265. }
  266. int ff_icc_profile_detect_transfer(FFIccContext *s, cmsHPROFILE profile,
  267. enum AVColorTransferCharacteristic *out_trc)
  268. {
  269. /* 8-bit linear grayscale ramp */
  270. static const uint8_t testramp[16][3] = {
  271. { 1, 1, 1}, /* avoid exact zero due to log100 etc. */
  272. { 17, 17, 17},
  273. { 34, 34, 34},
  274. { 51, 51, 51},
  275. { 68, 68, 68},
  276. { 85, 85, 85},
  277. { 02, 02, 02},
  278. {119, 119, 119},
  279. {136, 136, 136},
  280. {153, 153, 153},
  281. {170, 170, 170},
  282. {187, 187, 187},
  283. {204, 204, 204},
  284. {221, 221, 221},
  285. {238, 238, 238},
  286. {255, 255, 255},
  287. };
  288. double dst[FF_ARRAY_ELEMS(testramp)];
  289. for (enum AVColorTransferCharacteristic trc = 0; trc < AVCOL_TRC_NB; trc++) {
  290. cmsToneCurve *tonecurve;
  291. cmsHPROFILE ref;
  292. cmsHTRANSFORM tf;
  293. double delta = 0.0;
  294. if (get_curve(s, trc, &tonecurve) < 0)
  295. continue;
  296. ref = cmsCreateGrayProfileTHR(s->ctx, cmsD50_xyY(), tonecurve);
  297. if (!ref)
  298. return AVERROR(ENOMEM);
  299. tf = cmsCreateTransformTHR(s->ctx, profile, TYPE_RGB_8, ref, TYPE_GRAY_DBL,
  300. INTENT_RELATIVE_COLORIMETRIC,
  301. cmsFLAGS_NOCACHE | cmsFLAGS_NOOPTIMIZE);
  302. cmsCloseProfile(ref);
  303. if (!tf) {
  304. av_log(s->avctx, AV_LOG_ERROR, "Invalid ICC profile (e.g. CMYK)\n");
  305. return AVERROR_INVALIDDATA;
  306. }
  307. cmsDoTransform(tf, testramp, dst, FF_ARRAY_ELEMS(dst));
  308. cmsDeleteTransform(tf);
  309. for (int i = 0; i < FF_ARRAY_ELEMS(dst); i++)
  310. delta += fabs(testramp[i][0] / 255.0 - dst[i]);
  311. if (delta < 0.01) {
  312. *out_trc = trc;
  313. return 0;
  314. }
  315. }
  316. *out_trc = AVCOL_TRC_UNSPECIFIED;
  317. return 0;
  318. }