diff mbox series

[FFmpeg-devel] avcodec/png: support sRGB and cICP chunks

Message ID 20230116200139.232178-1-leo.izen@gmail.com
State New
Headers show
Series [FFmpeg-devel] avcodec/png: support sRGB and cICP chunks | expand

Checks

Context Check Description
yinshiyou/make_loongarch64 success Make finished
yinshiyou/make_fate_loongarch64 success Make fate finished

Commit Message

Leo Izen Jan. 16, 2023, 8:01 p.m. UTC
This commit adds decode-side support for sRGB chunks and cICP chunks.

The sRGB chunk is part of the PNG specification, 2nd edition, and
FFmpeg already supports writing this chunk on the encode-side, but this
chunk is ignored upon decode. This commit causes the PNG decoder to tag
the output video stream as sRGB using the enum values if the sRGB chunk
is present, and ignore the gAMA chunk (per the specification).

The cICP chunk allows the PNG color space to be tagged with any of the
enum values in H.273, not just sRGB, and it is defined in the working
draft of the PNG specification (3rd edition). libavutil/pixfmt.h uses
exact the same values as H.273 so these are translated as-is. This
commit also adds support for writing the cICP chunk for video frames
tagged as anything that isn't sRGB. It still writes cHRM and gAMA as a
fallback.

PNG specification, 2nd edition:
https://www.w3.org/TR/2003/REC-PNG-20031110/

PNG specification working draft, 3rd edition:
https://www.w3.org/TR/png-3/

Signed-off-by: Leo Izen <leo.izen@gmail.com>
---
 libavcodec/pngdec.c | 80 +++++++++++++++++++++++++++++++++++++++++++++
 libavcodec/pngenc.c |  8 +++++
 2 files changed, 88 insertions(+)
diff mbox series

Patch

diff --git a/libavcodec/pngdec.c b/libavcodec/pngdec.c
index f1cad26c52..358a4d69e6 100644
--- a/libavcodec/pngdec.c
+++ b/libavcodec/pngdec.c
@@ -30,6 +30,7 @@ 
 #include "libavutil/intreadwrite.h"
 #include "libavutil/stereo3d.h"
 #include "libavutil/mastering_display_metadata.h"
+#include "libavutil/pixfmt.h"
 
 #include "avcodec.h"
 #include "bytestream.h"
@@ -91,6 +92,9 @@  typedef struct PNGDecContext {
     int has_trns;
     uint8_t transparent_color_be[6];
 
+    int have_cicp;
+    int have_srgb;
+
     uint32_t palette[256];
     uint8_t *crow_buf;
     uint8_t *last_row;
@@ -876,6 +880,58 @@  static int decode_trns_chunk(AVCodecContext *avctx, PNGDecContext *s,
     return 0;
 }
 
+static int decode_cicp_chunk(AVCodecContext *avctx, PNGDecContext *s,
+                             GetByteContext *gb, AVFrame *f)
+{
+    int primaries, trc, colorspace, range;
+
+    if (!(s->hdr_state & PNG_IHDR)) {
+        av_log(avctx, AV_LOG_ERROR, "cICP before IHDR\n");
+        return AVERROR_INVALIDDATA;
+    }
+
+    if (s->pic_state & PNG_PLTE) {
+        av_log(avctx, AV_LOG_ERROR, "cICP after PLTE\n");
+        return AVERROR_INVALIDDATA;
+    }
+
+    if (s->pic_state & PNG_IDAT) {
+        av_log(avctx, AV_LOG_ERROR, "cICP after IDAT\n");
+        return AVERROR_INVALIDDATA;
+    }
+
+    /* these enum values match H.273 */
+    primaries = bytestream2_get_byte(gb);
+    if (primaries >= AVCOL_PRI_NB)
+        av_log(avctx, AV_LOG_WARNING, "unrecognized cICP primaries\n");
+    else
+        avctx->color_primaries = f->color_primaries = primaries;
+
+    trc = bytestream2_get_byte(gb);
+    if (trc >= AVCOL_TRC_NB)
+        av_log(avctx, AV_LOG_WARNING, "unrecognized cICP transfer\n");
+    else
+        avctx->color_trc = f->color_trc = trc;
+
+    colorspace = bytestream2_get_byte(gb);
+    if (colorspace != 0)
+        av_log(avctx, AV_LOG_WARNING, "only RGB pngs supported\n");
+    else
+        avctx->colorspace = f->colorspace = colorspace;
+
+    range = bytestream2_get_byte(gb);
+    if (range != 0 && range != 1) {
+        av_log(avctx, AV_LOG_ERROR, "invalid cICP range\n");
+        return AVERROR_INVALIDDATA;
+    }
+    avctx->color_range = f->color_range = range ? AVCOL_RANGE_JPEG : AVCOL_RANGE_MPEG;
+
+    /* clear gAMA-chunk derived value */
+    av_dict_set(&s->frame_metadata, "gamma", NULL, 0);
+    s->have_cicp = 1;
+    return 0;
+}
+
 static int decode_iccp_chunk(PNGDecContext *s, GetByteContext *gb, AVFrame *f)
 {
     int ret, cnt = 0;
@@ -1311,6 +1367,25 @@  static int decode_frame_common(AVCodecContext *avctx, PNGDecContext *s,
             }
             break;
         }
+        case MKTAG('s', 'R', 'G', 'B'):
+            bytestream2_get_byte(&gb_chunk); /* read rendering intent */
+
+            /* cICP overrides sRGB */
+            if (s->have_cicp)
+                break;
+            s->have_srgb = 1;
+
+            avctx->colorspace = p->colorspace = AVCOL_SPC_RGB;
+            avctx->color_primaries = p->color_primaries = AVCOL_PRI_BT709;
+            avctx->color_trc = p->color_trc = AVCOL_TRC_IEC61966_2_1;
+
+            /* clear gAMA-chunk derived value */
+            av_dict_set(&s->frame_metadata, "gamma", NULL, 0);
+            break;
+        case MKTAG('c', 'I', 'C', 'P'):
+            if ((ret = decode_cicp_chunk(avctx, s, &gb_chunk, p)) < 0)
+                goto fail;
+            break;
         case MKTAG('i', 'C', 'C', 'P'): {
             if ((ret = decode_iccp_chunk(s, &gb_chunk, p)) < 0)
                 goto fail;
@@ -1335,6 +1410,10 @@  static int decode_frame_common(AVCodecContext *avctx, PNGDecContext *s,
             char *gamma_str;
             int num = bytestream2_get_be32(&gb_chunk);
 
+            /* sRGB and cICP override gAMA */
+            if (s->have_srgb || s->have_cicp)
+                break;
+
             av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
             av_bprintf(&bp, "%i/%i", num, 100000);
             ret = av_bprint_finalize(&bp, &gamma_str);
@@ -1548,6 +1627,7 @@  static int decode_frame_png(AVCodecContext *avctx, AVFrame *p,
     s->y = s->has_trns = 0;
     s->hdr_state = 0;
     s->pic_state = 0;
+    s->have_srgb = s->have_cicp = 0;
 
     /* Reset z_stream */
     ret = inflateReset(&s->zstream.zstream);
diff --git a/libavcodec/pngenc.c b/libavcodec/pngenc.c
index ca1a186ca8..e52104526d 100644
--- a/libavcodec/pngenc.c
+++ b/libavcodec/pngenc.c
@@ -34,6 +34,7 @@ 
 #include "libavutil/opt.h"
 #include "libavutil/color_utils.h"
 #include "libavutil/stereo3d.h"
+#include "libavutil/pixfmt.h"
 
 #include <zlib.h>
 
@@ -438,6 +439,13 @@  static int encode_headers(AVCodecContext *avctx, const AVFrame *pict)
         pict->color_trc == AVCOL_TRC_IEC61966_2_1) {
         s->buf[0] = 1; /* rendering intent, relative colorimetric by default */
         png_write_chunk(&s->bytestream, MKTAG('s', 'R', 'G', 'B'), s->buf, 1);
+    } else if (pict->color_primaries != AVCOL_PRI_UNSPECIFIED ||
+        pict->color_trc != AVCOL_TRC_UNSPECIFIED) {
+        s->buf[0] = pict->color_primaries;
+        s->buf[1] = pict->color_trc;
+        s->buf[2] = 0; /* only matrix=0, i.e. RGB supported */
+        s->buf[3] = pict->color_range == AVCOL_RANGE_MPEG ? 0 : 1;
+        png_write_chunk(&s->bytestream, MKTAG('c', 'I', 'C', 'P'), s->buf, 4);
     }
 
     if (png_get_chrm(pict->color_primaries, s->buf))