From patchwork Tue Mar 15 11:34:58 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Niklas Haas X-Patchwork-Id: 34755 Delivered-To: ffmpegpatchwork2@gmail.com Received: by 2002:a05:6838:3486:0:0:0:0 with SMTP id ek6csp2929271nkb; Tue, 15 Mar 2022 04:36:02 -0700 (PDT) X-Google-Smtp-Source: ABdhPJyxGHpH7AXqPGpDVe2g5Yd+urj5RKYiGjwENBPfPwxB3TCr339qIYESABN/t97/XYT/3x/2 X-Received: by 2002:aa7:cb8b:0:b0:410:9aaf:2974 with SMTP id r11-20020aa7cb8b000000b004109aaf2974mr24858389edt.173.1647344161891; Tue, 15 Mar 2022 04:36:01 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1647344161; cv=none; d=google.com; s=arc-20160816; b=KiQbN1ca1MwesuMVz5DMrrQRE6h8YpPAsszs2VOUWYlV5U2WH5fnZU/Y1jHsbyW2DQ wm5B6FNhFHJYJfw5JLPoQKS4IhKq2UVlAPAFNxjaW7XQLeevV/1ltt7shTkHWQVQADpP s+UQGUf/SqgBz3Rr3Ekc29odl+3u0zkXjUXAn5+LvIznIqN0Vg1JGgGcneV97WH+HESv 6lpb2IspfkI8IopOSlPji4y7UrH/JZ2nl6pGQiPymeP0RzyD1OwRSKuib0ZSnTjZxkkL a9KcQ0IiT1NG8ZdHq0Y8VsXKsfCRSfFqOrpN9RMp7iVZW/o4+j9TK0F647K4lgDgqktt XODA== 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:references:in-reply-to :message-id:date:to:from:dkim-signature:delivered-to; bh=bsgnSVMPxvpFjzz9LwYd1Ypp2zX+mcm2Zr4Gwx8Bgfc=; b=pf6SnP5USFapHVCanyGWKzejTqC/DE7p8RZajXOXFNEFRhpTpZQxm+zDmhmCE7J4kX y8VA2x2LEYaZ6TqtbjPib7P8EJsDDLpc5AdoznhmATWvNQfmRVvSo4g6kLX9JAJaf88k Wi13apcxx3ht7FQXMcISqNu3p3bT1Xp9/Tw1xjH8k6bx97soLO8Ks/1vF48S8aVS9HmN 8vNBV394QpSWfRwrYcF9smf5o5iJwbXPEB/+3cLiq8RXUT0mb3QGSb4Ks4NlH1jb2xbT kiiHNFmaWptnz5z1GR98uwvx267McsOG53oSIf7J/FZEpBhZsj6Q1PBFKwIvWVOTP6pI oO0A== ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@haasn.xyz header.s=mail header.b=BbK7Jpso; 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 sh39-20020a1709076ea700b006daa8c141c3si12258137ejc.236.2022.03.15.04.36.01; Tue, 15 Mar 2022 04:36:01 -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=BbK7Jpso; 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 AC43A68B1AC; Tue, 15 Mar 2022 13:35:57 +0200 (EET) 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 41DD368B102 for ; Tue, 15 Mar 2022 13:35:50 +0200 (EET) Received: from haasn.dev (unknown [10.30.0.2]) by haasn.dev (Postfix) with ESMTP id EA04049909; Tue, 15 Mar 2022 12:35:49 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=haasn.xyz; s=mail; t=1647344150; bh=G4ohIZ1EhnUCe4fYenAya/8qzjzEZC7qq4B6X9MDEgI=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=BbK7JpsoUbsiTp/ccfwnbSMfS3YFg3VqcvyD8zjivNskjGPLE/0vgTpbxScR8wYSB PGEQKC8nsG13019Qr4Ef7axEeJ3mupqzEnBK6uSXAgjIRde7kAwj7ZX/tTfhw7tdyw weX3i5wtSphJYxKH0lH9hUS2zcp8ShKY2wdMn2NA= From: Niklas Haas To: ffmpeg-devel@ffmpeg.org Date: Tue, 15 Mar 2022 12:34:58 +0100 Message-Id: <20220315113457.36059-1-ffmpeg@haasn.xyz> X-Mailer: git-send-email 2.35.1 In-Reply-To: <20220315111041.51203-1-ffmpeg@haasn.xyz> References: <20220315111041.51203-1-ffmpeg@haasn.xyz> MIME-Version: 1.0 Subject: [FFmpeg-devel] [PATCH v5] 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: +V1Vayvl3aBg 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 --- Changes in v5: - Fix FATE failure (possible segfault in encode_apng) - Fix commit message (still talked about deleted encode_zbuf) - Add Signed-off-by line --- libavcodec/pngenc.c | 88 +++++++++++++++++++++++++++++++++++++++++- tests/fate/image.mak | 3 ++ tests/ref/fate/png-icc | 43 +++++++++++++++++++++ 3 files changed, 132 insertions(+), 2 deletions(-) create mode 100644 tests/ref/fate/png-icc diff --git a/libavcodec/pngenc.c b/libavcodec/pngenc.c index 3ebcc1e571..947138e3b5 100644 --- a/libavcodec/pngenc.c +++ b/libavcodec/pngenc.c @@ -235,7 +235,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); @@ -343,10 +344,53 @@ 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; + const AVDictionaryEntry *entry; + const char *name; + uint8_t *start, *buf; + int ret; + + if (!sd || !sd->size) + return 0; + s->zstream.next_in = sd->data; + s->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); + s->zstream.next_out = buf; + s->zstream.avail_out = s->bytestream_end - buf; + ret = deflate(&s->zstream, Z_FINISH); + deflateReset(&s->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, + s->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); @@ -399,7 +443,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; @@ -522,6 +572,36 @@ 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; + 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 > UINT_MAX) + goto too_large; + + s->zstream.next_in = sd->data; + s->zstream.avail_in = sd->size; + bound = deflateBound(&s->zstream, sd->size) + 128; /* header */ + deflateReset(&s->zstream); + if (bound > INT_MAX) + goto too_large; + + *max_packet_size += bound; + return 0; + +too_large: + av_log(avctx, AV_LOG_WARNING, "ICC profile too large for iCCP chunk\n"); + return AVERROR_INVALIDDATA; +} + static int encode_png(AVCodecContext *avctx, AVPacket *pkt, const AVFrame *pict, int *got_packet) { @@ -537,6 +617,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; if (max_packet_size > INT_MAX) return AVERROR(ENOMEM); ret = ff_alloc_packet(avctx, pkt, max_packet_size); @@ -867,6 +949,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..f5fd64e3ee 100644 --- a/tests/fate/image.mak +++ b/tests/fate/image.mak @@ -385,6 +385,9 @@ 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 += 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) 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]