From patchwork Tue Mar 15 11:10:42 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Niklas Haas X-Patchwork-Id: 34754 Delivered-To: ffmpegpatchwork2@gmail.com Received: by 2002:a05:6838:3486:0:0:0:0 with SMTP id ek6csp2913308nkb; Tue, 15 Mar 2022 04:13:45 -0700 (PDT) X-Google-Smtp-Source: ABdhPJyRp+meL9NeCZES1V18aeGUf0DgZ8mkd9H4NLZhk5WBNGUc+cJ/5/0qCQt9JD8/DYcD0Wqf X-Received: by 2002:a17:906:8558:b0:6da:e4a4:f526 with SMTP id h24-20020a170906855800b006dae4a4f526mr21942798ejy.19.1647342825485; Tue, 15 Mar 2022 04:13:45 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1647342825; cv=none; d=google.com; s=arc-20160816; b=ziQxZMxhrTDuu9IQ5LZsmzeEl9A4rMwY/SmFHMubcZZa9FEv8Xdl/MYAz1wl0D0Jwn vUN3IW0kBI+HZlmQAx/dEkU5p5zyvFRCfek3IiCTnwTdyNBiigF3KDLZ6KumeOi+do5o YsLr7XxzNorbZgelWnxbQRUrFjdUDHGP3tXwrfZNRPqNmE7Td/7yBjAfS6LF4RQWm6tU 1/S3jg3CUUPM9Cm0fLgMSxWCy6sg3UejYQ+BAl6ff6X2NP1XDDEttG6XaAo4f+H6PHJf yPv2d8+u/B9A7Mf/1euYIpCnCjOjljXZVGRErl/RG/cA1T4YbkBO4owlhmkb+bFMw3H3 55Sw== 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=ImBwQ+jrNRYIv6cMz/vKbudPdpWqy7qqSLCimxhsbC4=; b=Zd3yIvNL/wklw16ts02zxY7i1ysGyDLWMrxGOk/9AoZOlbEwKIYdq3MdTaV+PMyGtg YjQ6tmi3ht8aWP0CxNVor0S5kyiGpDeJmewn0PQu4hb1GMMJ6FGfOHxBnkkV9TOBZE+M gwpbKbSKEXkc2w/z8hAapLPcsri6NAPwV/Usg2W8zDkQiMttQkoCIEJhEF1X3mGwhU10 wPBGBkHre4TNmjzmn0M2ML23jA2Ek6sA6RDhAhuemBcK/1GB6+yYHBVWDZrDLzd39Cs6 p6eorobpUGn1yqbmctCRVd5dA52dio+5ge5o9a43A0wrTbu9k3/bhvxYmI2r5BB4y7tl DH4w== ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@haasn.xyz header.s=mail header.b=S3dhQGGP; 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 c14-20020a05640227ce00b00416bc1222d7si9442285ede.118.2022.03.15.04.13.44; Tue, 15 Mar 2022 04:13:45 -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=S3dhQGGP; 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 63A2068B1AA; Tue, 15 Mar 2022 13:13:41 +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 687E3680C04 for ; Tue, 15 Mar 2022 13:13:34 +0200 (EET) Received: from haasn.dev (unknown [10.30.0.2]) by haasn.dev (Postfix) with ESMTP id 460AE496B1; Tue, 15 Mar 2022 12:13:33 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=haasn.xyz; s=mail; t=1647342813; bh=ycTA9sIsmN0yiVxzBeWm+6T9z2yUh8sFtsKOc9sT0xw=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=S3dhQGGPdpTdvT8IWsxvVDats9T8E8jh01LC4PU95Jn1G4H4xeFf2NRLHqh9kHZ/V FPaJv1HhlwbFLsXr5iu9Si9aEoQ3jr66s0Raeez55/P+dm0ajls565Se2HBNSJ4s74 OMIsKbRqZ8K6RocuPS8HFlUWuIBxiPPBSelXcdVo= From: Niklas Haas To: ffmpeg-devel@ffmpeg.org Date: Tue, 15 Mar 2022 12:10:42 +0100 Message-Id: <20220315111041.51203-1-ffmpeg@haasn.xyz> X-Mailer: git-send-email 2.35.1 In-Reply-To: References: MIME-Version: 1.0 Subject: [FFmpeg-devel] [PATCH v4] 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: WSbz9ldJpHlh From: Niklas Haas encode_zbuf is mostly a mirror image of decode_zbuf. 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. --- Changes in v4: - Remove stray commas on line endings - Re-use s->zstream instead of creating new deflate instances - Add size checking and explicit header padding - Delete the now redundant and overly specialized single-use helpers - Don't redundantly print error code, just print cause - Correctly account for max_packet_size in encode_apng as well - Fix FATE_PNG_PROBE -> FATE_PNG and add -show_frames to test --- libavcodec/pngenc.c | 86 +++++++++++++++++++++++++++++++++++++++++- tests/fate/image.mak | 3 ++ tests/ref/fate/png-icc | 43 +++++++++++++++++++++ 3 files changed, 130 insertions(+), 2 deletions(-) create mode 100644 tests/ref/fate/png-icc diff --git a/libavcodec/pngenc.c b/libavcodec/pngenc.c index 3ebcc1e571..b0091f2b40 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,34 @@ 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; + + 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 +615,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 +947,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]