avcodec/pngenc: support writing iCCP chunks

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 <git@haasn.dev>
This commit is contained in:
Niklas Haas 2022-03-11 11:07:14 +01:00
parent d6d46a2c50
commit 4a580975d4
3 changed files with 133 additions and 4 deletions

View File

@ -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,53 @@ static int png_get_gama(enum AVColorTransferCharacteristic trc, uint8_t *buf)
return 1;
}
static int png_write_iccp(PNGEncContext *s, const AVFrameSideData *sd)
{
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 +445,11 @@ 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, side_data)))
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 +573,34 @@ the_end:
return ret;
}
static int add_icc_profile_size(AVCodecContext *avctx, const AVFrame *pict,
uint64_t *max_packet_size)
{
PNGEncContext *s = avctx->priv_data;
const AVFrameSideData *sd;
const int hdr_size = 128;
uint64_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) sd->size)
return AVERROR_INVALIDDATA;
bound = deflateBound(&s->zstream.zstream, sd->size);
if (bound > INT32_MAX - hdr_size)
return AVERROR_INVALIDDATA;
new_pkt_size = *max_packet_size + bound + hdr_size;
if (new_pkt_size < *max_packet_size)
return AVERROR_INVALIDDATA;
*max_packet_size = new_pkt_size;
return 0;
}
static int encode_png(AVCodecContext *avctx, AVPacket *pkt,
const AVFrame *pict, int *got_packet)
{
@ -541,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;
ret = ff_alloc_packet(avctx, pkt, max_packet_size);
if (ret < 0)
return ret;
@ -870,6 +948,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);

View File

@ -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)

43
tests/ref/fate/png-icc Normal file
View File

@ -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]