From patchwork Sat Mar 12 12:04:29 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Niklas Haas X-Patchwork-Id: 34715 Delivered-To: ffmpegpatchwork2@gmail.com Received: by 2002:a05:6838:3486:0:0:0:0 with SMTP id ek6csp583739nkb; Sat, 12 Mar 2022 04:09:15 -0800 (PST) X-Google-Smtp-Source: ABdhPJwxCZwKXo8ODgkwP9//Dk4x6cKurZfEi6V4UZOAoBPUGGsVtKx3tUz5NpKDvGcLNih3KYQg X-Received: by 2002:a17:907:e89:b0:6db:af1f:5e22 with SMTP id ho9-20020a1709070e8900b006dbaf1f5e22mr5135451ejc.649.1647086955548; Sat, 12 Mar 2022 04:09:15 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1647086955; cv=none; d=google.com; s=arc-20160816; b=QqQGzmmL2KCF80eKX6+dB8ekox3Zrjz8pmM6Te8p+tFHTbdZdi46rpGsiivl4I4MH1 oB+Zq4ZyHtEc5aIQTQ1lO2rmXeY+5dX4Ua4OVWZHbMl5QUt3LAdWLB8Kr2QV58Y4etRA EYikiQdA9fCnCDDylNb+phsqkP2KSzR+EvD0SEoGEfeaPAdGTwuD2JrhOFpsKTBnBv+k alS5op8bGQvZmC50ehX0Mt2yM0D/J84+7M2+aStODcEcnjYO9cn3Eo3fvDP/o5c1aNcK GM1T5aCdT8coT3CEvh4v3wHUC/xxFcx3hfA1uecdomXpOpfLpKbLrS3oAvXMdngn2x0Y ERmg== 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=4MHdla/H7Tett9y0R5RpCzHKnvJ6k0f7UMQ8VviNAaI=; b=BawzdTgVANG7dInmelrm8rFC480a+Je/j8qTl14fwxuX11AP+pTGS1K7Rl/GYwLiBM IQpdoCxKPWFzDcfmA+Sue91Wkwv56SiOFycH7iJqBKUY66lbE1dCl5C+EZ0djhw7s/rH Ep8O9kqZV6qfjnqwUTKhaMyNm3CsClrn5H1n2X/NbfiH3lwwAX8Qbm3imaOdhtk3H7v4 8UUeG+MF15aq8pqvDJ3CnpIJc2nYIu1KFL+of5zVtTiYWG1skOP4H5T52Z93GFlWIEXp UZ38ifLjYcP6lhmuv/zVmRcWWsj/bfQjUx0IjkpdNLDEXVQHPDF/UkDpEbN+ytp6WQzt rt3A== ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@haasn.xyz header.s=mail header.b=IAMVvWAq; 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 o5-20020a170906768500b006d05aff0233si6346710ejm.617.2022.03.12.04.09.14; Sat, 12 Mar 2022 04:09:15 -0800 (PST) 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=IAMVvWAq; 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 0A6EE68A958; Sat, 12 Mar 2022 14:09:11 +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 C494868A958 for ; Sat, 12 Mar 2022 14:09:04 +0200 (EET) Received: from haasn.dev (unknown [10.30.0.2]) by haasn.dev (Postfix) with ESMTP id 767954701D; Sat, 12 Mar 2022 13:09:04 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=haasn.xyz; s=mail; t=1647086944; bh=iJkicLOM05isaSFvQR3tLPc+tnp5xnuLv8WIZrEwM8Q=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=IAMVvWAqIszF+V/0JweAxHTHei662kFgx1t7R8I8vNz9l7GokjOOQGcfaT3TsAKaS Vpnv8cYJOSZSga9AuIHGxIl2vmku/9a6DGysngRt+6ry8ye1k3m6PpEVVVmYiqjzZ0 uxAYyYHO/WTOPZlrrHtNKUVESxCT9BCsStoja4sI= From: Niklas Haas To: ffmpeg-devel@ffmpeg.org Date: Sat, 12 Mar 2022 13:04:29 +0100 Message-Id: <20220312120428.103248-1-ffmpeg@haasn.xyz> X-Mailer: git-send-email 2.35.1 In-Reply-To: <20220312121047.GB102875@haasn.xyz> References: <20220312121047.GB102875@haasn.xyz> MIME-Version: 1.0 Subject: [FFmpeg-devel] [PATCH v3] 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: 0vVdpZaUDJrp 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. This implementation does unfortunately require initializing the deflate instance twice, but deflateBound() is not redundant with deflate() so we're not throwing away any significant work. Also add a FATE transcode test to ensure that the ICC profile gets encoded correctly. --- Changes in v3: - rewrite to write the chunk in-place (inside the packet buffer) Actually, I implemented an AVBPrint-less version that I think I'm happier with overall. The extent of the "crimes" needed to support writing chunks in-place was a single `if` in png_write_chunk and hard-coding an 8 byte start offset. I like this the most, since it doesn't require dynamic allocation _at all_. It also ends up producing a 1 byte smaller test file for some reason (not as a result of any obvious bug, but simply because zlib compresses the last few bytes of the stream in a slightly different way, probably as a result of some internal heuristics related to the buffer size - the decoded ICC profile checksum is the same). --- libavcodec/pngenc.c | 93 +++++++++++++++++++++++++++++++++++++++++- tests/fate/image.mak | 3 ++ tests/ref/fate/png-icc | 8 ++++ 3 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 tests/ref/fate/png-icc diff --git a/libavcodec/pngenc.c b/libavcodec/pngenc.c index 3ebcc1e571..e9bbe33adf 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,88 @@ static int png_get_gama(enum AVColorTransferCharacteristic trc, uint8_t *buf) return 1; } +static size_t zbuf_bound(const uint8_t *data, size_t size) +{ + z_stream zstream; + size_t bound; + + zstream.zalloc = ff_png_zalloc, + zstream.zfree = ff_png_zfree, + zstream.opaque = NULL; + if (deflateInit(&zstream, Z_DEFAULT_COMPRESSION) != Z_OK) + return 0; + + zstream.next_in = data; + zstream.avail_in = size; + bound = deflateBound(&zstream, size); + deflateEnd(&zstream); + return bound; +} + +static int encode_zbuf(uint8_t **buf, const uint8_t *buf_end, + const uint8_t *data, size_t size) +{ + z_stream zstream; + int ret; + + zstream.zalloc = ff_png_zalloc, + zstream.zfree = ff_png_zfree, + zstream.opaque = NULL; + if (deflateInit(&zstream, Z_DEFAULT_COMPRESSION) != Z_OK) + return AVERROR_EXTERNAL; + zstream.next_in = data; + zstream.avail_in = size; + zstream.next_out = *buf; + zstream.avail_out = buf_end - *buf; + ret = deflate(&zstream, Z_FINISH); + deflateEnd(&zstream); + if (ret != Z_STREAM_END) + return AVERROR_EXTERNAL; + + *buf = zstream.next_out; + return 0; +} + +static int png_write_iccp(uint8_t **bytestream, const uint8_t *end, + const AVFrameSideData *side_data) +{ + const AVDictionaryEntry *entry; + const char *name; + uint8_t *start, *buf; + int ret; + + if (!side_data || !side_data->size) + return 0; + + /* write the chunk contents first */ + start = *bytestream + 8; /* make room for iCCP tag + length */ + buf = start; + + /* profile description */ + entry = av_dict_get(side_data->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); + if ((ret = encode_zbuf(&buf, end, side_data->data, side_data->size))) + return ret; + + /* rewind to the start and write the chunk header/crc */ + png_write_chunk(bytestream, MKTAG('i', 'C', 'C', 'P'), start, buf - 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 +478,14 @@ 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(&s->bytestream, s->bytestream_end, side_data))) { + av_log(avctx, AV_LOG_WARNING, "Failed writing iCCP chunk: %s\n", + av_err2str(ret)); + 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; @@ -526,6 +612,7 @@ static int encode_png(AVCodecContext *avctx, AVPacket *pkt, const AVFrame *pict, int *got_packet) { PNGEncContext *s = avctx->priv_data; + const AVFrameSideData *sd; int ret; int enc_row_size; size_t max_packet_size; @@ -537,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 ((sd = av_frame_get_side_data(pict, AV_FRAME_DATA_ICC_PROFILE))) + max_packet_size += zbuf_bound(sd->data, sd->size); if (max_packet_size > INT_MAX) return AVERROR(ENOMEM); ret = ff_alloc_packet(avctx, pkt, max_packet_size); diff --git a/tests/fate/image.mak b/tests/fate/image.mak index 573d398915..da4f3709e9 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_PROBE += fate-png-icc +fate-png-icc: CMD = transcode png_pipe $(TARGET_SAMPLES)/png1/lena-int_rgb24.png image2 "-c png" + 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..fd92a9f949 --- /dev/null +++ b/tests/ref/fate/png-icc @@ -0,0 +1,8 @@ +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