From patchwork Mon Mar 28 15:26:11 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Niklas Haas X-Patchwork-Id: 35018 Delivered-To: ffmpegpatchwork2@gmail.com Received: by 2002:a05:6a20:c05:b0:7a:e998:b410 with SMTP id bw5csp316719pzb; Mon, 28 Mar 2022 08:26:39 -0700 (PDT) X-Google-Smtp-Source: ABdhPJxKxapxMNjTthYQEcZT7h0+Jdg5LmEPG50wi40tCIps7fLLTdS7dNHteE8cHq8t1exRQSZ+ X-Received: by 2002:a17:907:1c82:b0:6e0:acef:d238 with SMTP id nb2-20020a1709071c8200b006e0acefd238mr19181742ejc.96.1648481198825; Mon, 28 Mar 2022 08:26:38 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1648481198; cv=none; d=google.com; s=arc-20160816; b=i4MwRsnaMjbrAN6EwbKbubB0H7xezkbVW7J3oWTkPyK+fufNGfDJJqLB7isHROajIZ MlXfGV+pi3DciJmba0Y8Xseuqpf7qdrdSdRSzsFt+ytyhKmwBRu7cAk3GRjgADsD3sgG oWS6Nu8SkI7DgY58y0MzAykc2g/jylQsugLyjr78NRnpT1oWyS44ch0rU4Zy8mzrrGb9 oR9XjlUXgv/NyVAdx5PXYcfo2P/Ek3+R8ZJ5kNJyqBXt2TMZoYYNgSPbnI9nwNBxXNvS 8FNWjPUsbPqwa+7TvKdQM4nIngYHUySKn6KTzodyFqPjsC0k4LnNRktwNNEIXesPKjb0 MWwA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=sender:errors-to:content-transfer-encoding:cc:reply-to :list-subscribe:list-help:list-post:list-archive:list-unsubscribe :list-id:precedence:subject:mime-version:message-id:date:to:from :dkim-signature:delivered-to; bh=JKpQavdrUQadI+nG3aQIHAqd6dbbxMLiYspu/xvQ3h0=; b=dIlIqoNFSw7YW36Dg5ZYhiyjjKc96Jbx6g0FIh506+2UlUCmKGEW8xiq8CX0GtL+6S CUuS7Wn3IZTrtYdk/Cb2rbKXnivoNHdS8N3toWmT951Vkp1n1gGPIJd1KU6Ptv+2IXFz MK7IZ7A1ijoLbJm+2qbh2wVmMY5LYJL36yBhCdfzCzX/45Dm/6Il4aLcmY62+kl/5TYS aVOstfua+WDyjYxQ2pjiNkfbcEu4RVOfNjCkmZdIroZVefdNVWsyNq4ywChQ5govO3+B fDtq0kj/JQTaePgb92VKKs+5shp5AQIxWr98Xv7Ku+Y84sk9cwppy60KB4oMEIcKS1aA 71dg== ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@haasn.xyz header.s=mail header.b=Fp4U3FH7; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org Return-Path: Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org. [79.124.17.100]) by mx.google.com with ESMTP id v13-20020a50954d000000b0041ac9cb86e3si376235eda.90.2022.03.28.08.26.37; Mon, 28 Mar 2022 08:26:38 -0700 (PDT) Received-SPF: pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) client-ip=79.124.17.100; Authentication-Results: mx.google.com; dkim=neutral (body hash did not verify) header.i=@haasn.xyz header.s=mail header.b=Fp4U3FH7; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id 4961068B23D; Mon, 28 Mar 2022 18:26:22 +0300 (EEST) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from haasn.dev (haasn.dev [78.46.187.166]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id 8AE4568B18C for ; Mon, 28 Mar 2022 18:26:14 +0300 (EEST) Received: from haasn.dev (unknown [10.30.0.2]) by haasn.dev (Postfix) with ESMTP id 4833E49BC9; Mon, 28 Mar 2022 17:26:13 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=haasn.xyz; s=mail; t=1648481173; bh=d8viooEiozg5uyTrPta2KSYU/EIbADNwF1iDIun9EQM=; h=From:To:Cc:Subject:Date:From; b=Fp4U3FH78e3IE5CbUG6SX6YSGgY4G2cJMU16GisDfhH+nNXYMkKZxWw8JRslz1YHc hzTdrRwEFCVQvovp/KlnkAsImv3iFCsip6xFk/eg3Ak26NZ8vtrF6pEOuUf0ntzEey BiEVt+B7Oh3lc5UGCjKS1YioyZFXtcxasM8SFCMI= From: Niklas Haas To: ffmpeg-devel@ffmpeg.org Date: Mon, 28 Mar 2022 17:26:11 +0200 Message-Id: <20220328152612.95684-1-ffmpeg@haasn.xyz> X-Mailer: git-send-email 2.35.1 MIME-Version: 1.0 Subject: [FFmpeg-devel] [PATCH v7 1/2] avcodec/pngenc: support writing iCCP chunks X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches Cc: Niklas Haas Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" X-TUID: aYwdn5s73hA5 From: Niklas Haas We re-use the PNGEncContext.zstream for deflate-related operations. Other than that, the code is pretty straightforward. Special care needs to be taken to avoid writing more than 79 characters of the profile description (the maximum supported). To write the (dynamically sized) deflate-encoded data, we allocate extra space in the packet and use that directly as a scratch buffer. Modify png_write_chunk slightly to allow pre-writing the chunk contents like this. Also add a FATE transcode test to ensure that the ICC profile gets encoded correctly. Signed-off-by: Niklas Haas --- libavcodec/pngenc.c | 91 +++++++++++++++++++++++++++++++++++++++++- tests/fate/image.mak | 10 ++++- tests/ref/fate/png-icc | 43 ++++++++++++++++++++ 3 files changed, 140 insertions(+), 4 deletions(-) create mode 100644 tests/ref/fate/png-icc diff --git a/libavcodec/pngenc.c b/libavcodec/pngenc.c index f67f90cd14..1571673f7c 100644 --- a/libavcodec/pngenc.c +++ b/libavcodec/pngenc.c @@ -236,7 +236,8 @@ static void png_write_chunk(uint8_t **f, uint32_t tag, bytestream_put_be32(f, av_bswap32(tag)); if (length > 0) { crc = av_crc(crc_table, crc, buf, length); - memcpy(*f, buf, length); + if (*f != buf) + memcpy(*f, buf, length); *f += length; } bytestream_put_be32(f, ~crc); @@ -345,10 +346,54 @@ static int png_get_gama(enum AVColorTransferCharacteristic trc, uint8_t *buf) return 1; } +static int png_write_iccp(AVCodecContext *avctx, const AVFrameSideData *sd) +{ + PNGEncContext *s = avctx->priv_data; + z_stream *const zstream = &s->zstream.zstream; + const AVDictionaryEntry *entry; + const char *name; + uint8_t *start, *buf; + int ret; + + if (!sd || !sd->size) + return 0; + zstream->next_in = sd->data; + zstream->avail_in = sd->size; + + /* write the chunk contents first */ + start = s->bytestream + 8; /* make room for iCCP tag + length */ + buf = start; + + /* profile description */ + entry = av_dict_get(sd->metadata, "name", NULL, 0); + name = (entry && entry->value[0]) ? entry->value : "icc"; + for (int i = 0;; i++) { + char c = (i == 79) ? 0 : name[i]; + bytestream_put_byte(&buf, c); + if (!c) + break; + } + + /* compression method and profile data */ + bytestream_put_byte(&buf, 0); + zstream->next_out = buf; + zstream->avail_out = s->bytestream_end - buf; + ret = deflate(zstream, Z_FINISH); + deflateReset(zstream); + if (ret != Z_STREAM_END) + return AVERROR_EXTERNAL; + + /* rewind to the start and write the chunk header/crc */ + png_write_chunk(&s->bytestream, MKTAG('i', 'C', 'C', 'P'), start, + zstream->next_out - start); + return 0; +} + static int encode_headers(AVCodecContext *avctx, const AVFrame *pict) { AVFrameSideData *side_data; PNGEncContext *s = avctx->priv_data; + int ret; /* write png header */ AV_WB32(s->buf, avctx->width); @@ -401,7 +446,13 @@ static int encode_headers(AVCodecContext *avctx, const AVFrame *pict) if (png_get_gama(pict->color_trc, s->buf)) png_write_chunk(&s->bytestream, MKTAG('g', 'A', 'M', 'A'), s->buf, 4); - /* put the palette if needed */ + side_data = av_frame_get_side_data(pict, AV_FRAME_DATA_ICC_PROFILE); + if ((ret = png_write_iccp(avctx, side_data))) { + av_log(avctx, AV_LOG_WARNING, "Failed writing iCCP chunk\n"); + return ret; + } + + /* put the palette if needed, must be after colorspace information */ if (s->color_type == PNG_COLOR_TYPE_PALETTE) { int has_alpha, alpha, i; unsigned int v; @@ -525,6 +576,38 @@ the_end: return ret; } +static int add_icc_profile_size(AVCodecContext *avctx, const AVFrame *pict, + size_t *max_packet_size) +{ + PNGEncContext *s = avctx->priv_data; + const AVFrameSideData *sd; + const int hdr_size = 128; + size_t new_pkt_size; + uLong bound; + + if (!pict) + return 0; + sd = av_frame_get_side_data(pict, AV_FRAME_DATA_ICC_PROFILE); + if (!sd || !sd->size) + return 0; + if (sd->size > ULONG_MAX) + goto overflow; + + bound = deflateBound(&s->zstream.zstream, sd->size); + if (bound > INT32_MAX - hdr_size) + goto overflow; + + new_pkt_size = *max_packet_size + bound + hdr_size; + if (new_pkt_size < *max_packet_size) + goto overflow; + *max_packet_size = new_pkt_size; + return 0; + +overflow: + av_log(avctx, AV_LOG_WARNING, "ICC profile too large\n"); + return AVERROR_INVALIDDATA; +} + static int encode_png(AVCodecContext *avctx, AVPacket *pkt, const AVFrame *pict, int *got_packet) { @@ -541,6 +624,8 @@ static int encode_png(AVCodecContext *avctx, AVPacket *pkt, enc_row_size + 12 * (((int64_t)enc_row_size + IOBUF_SIZE - 1) / IOBUF_SIZE) // IDAT * ceil(enc_row_size / IOBUF_SIZE) ); + if ((ret = add_icc_profile_size(avctx, pict, &max_packet_size))) + return ret; ret = ff_alloc_packet(avctx, pkt, max_packet_size); if (ret < 0) return ret; @@ -870,6 +955,8 @@ static int encode_apng(AVCodecContext *avctx, AVPacket *pkt, enc_row_size + (4 + 12) * (((int64_t)enc_row_size + IOBUF_SIZE - 1) / IOBUF_SIZE) // fdAT * ceil(enc_row_size / IOBUF_SIZE) ); + if ((ret = add_icc_profile_size(avctx, pict, &max_packet_size))) + return ret; if (max_packet_size > INT_MAX) return AVERROR(ENOMEM); diff --git a/tests/fate/image.mak b/tests/fate/image.mak index 573d398915..c6374c7d8a 100644 --- a/tests/fate/image.mak +++ b/tests/fate/image.mak @@ -385,11 +385,15 @@ FATE_PNG_PROBE += fate-png-side-data fate-png-side-data: CMD = run ffprobe$(PROGSSUF)$(EXESUF) -show_frames \ -i $(TARGET_SAMPLES)/png1/lena-int_rgb24.png +FATE_PNG_TRANSCODE-$(call ENCDEC, PNG, IMAGE2) += fate-png-icc +fate-png-icc: CMD = transcode png_pipe $(TARGET_SAMPLES)/png1/lena-int_rgb24.png image2 "-c png" "" "" "-show_frames" + FATE_PNG-$(call DEMDEC, IMAGE2, PNG) += $(FATE_PNG) FATE_PNG_PROBE-$(call DEMDEC, IMAGE2, PNG) += $(FATE_PNG_PROBE) FATE_IMAGE += $(FATE_PNG-yes) FATE_IMAGE_PROBE += $(FATE_PNG_PROBE-yes) -fate-png: $(FATE_PNG-yes) $(FATE_PNG_PROBE-yes) +FATE_IMAGE_TRANSCODE += $(FATE_PNG_TRANSCODE-yes) +fate-png: $(FATE_PNG-yes) $(FATE_PNG_PROBE-yes) $(FATE_PNG_TRANSCODE-yes) FATE_IMAGE-$(call DEMDEC, IMAGE2, PTX) += fate-ptx fate-ptx: CMD = framecrc -i $(TARGET_SAMPLES)/ptx/_113kw_pic.ptx -pix_fmt rgb24 -vf scale @@ -551,8 +555,10 @@ fate-xbm: $(FATE_XBM-yes) FATE_IMAGE += $(FATE_IMAGE-yes) FATE_IMAGE_PROBE += $(FATE_IMAGE_PROBE-yes) +FATE_IMAGE_TRANSCODE += $(FATE_IMAGE_TRANSCODE-yes) FATE_SAMPLES_FFMPEG += $(FATE_IMAGE) FATE_SAMPLES_FFPROBE += $(FATE_IMAGE_PROBE) +FATE_SAMPLES_FFMPEG_FFPROBE += $(FATE_IMAGE_TRANSCODE) -fate-image: $(FATE_IMAGE) $(FATE_IMAGE_PROBE) +fate-image: $(FATE_IMAGE) $(FATE_IMAGE_PROBE) $(FATE_IMAGE_TRANSCODE) diff --git a/tests/ref/fate/png-icc b/tests/ref/fate/png-icc new file mode 100644 index 0000000000..542bb76f9a --- /dev/null +++ b/tests/ref/fate/png-icc @@ -0,0 +1,43 @@ +a50d37a0e72bddea2fcbba6fb773e2a0 *tests/data/fate/png-icc.image2 +49397 tests/data/fate/png-icc.image2 +#tb 0: 1/25 +#media_type 0: video +#codec_id 0: rawvideo +#dimensions 0: 128x128 +#sar 0: 2835/2835 +0, 0, 0, 1, 49152, 0xe0013dee +[FRAME] +media_type=video +stream_index=0 +key_frame=1 +pts=0 +pts_time=0.000000 +pkt_dts=0 +pkt_dts_time=0.000000 +best_effort_timestamp=0 +best_effort_timestamp_time=0.000000 +pkt_duration=1 +pkt_duration_time=0.040000 +pkt_pos=0 +pkt_size=49397 +width=128 +height=128 +pix_fmt=rgb24 +sample_aspect_ratio=1:1 +pict_type=I +coded_picture_number=0 +display_picture_number=0 +interlaced_frame=0 +top_field_first=0 +repeat_pict=0 +color_range=pc +color_space=unknown +color_primaries=unknown +color_transfer=unknown +chroma_location=unspecified +[SIDE_DATA] +side_data_type=ICC profile +name=Photoshop ICC profile +size=3144 +[/SIDE_DATA] +[/FRAME]