2007-07-15 21:24:26 +03:00
|
|
|
/*
|
|
|
|
* PNG image format
|
2009-01-19 17:46:40 +02:00
|
|
|
* Copyright (c) 2003 Fabrice Bellard
|
2007-07-15 21:24:26 +03:00
|
|
|
*
|
|
|
|
* This file is part of FFmpeg.
|
|
|
|
*
|
|
|
|
* FFmpeg is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
|
|
* License as published by the Free Software Foundation; either
|
|
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
|
|
*
|
|
|
|
* FFmpeg is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
* Lesser General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
|
|
* License along with FFmpeg; if not, write to the Free Software
|
|
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
|
|
*/
|
2014-03-14 20:32:00 +03:00
|
|
|
|
2007-07-15 21:24:26 +03:00
|
|
|
#include "avcodec.h"
|
2022-03-16 19:18:28 +02:00
|
|
|
#include "codec_internal.h"
|
2021-04-25 01:43:26 +02:00
|
|
|
#include "encode.h"
|
2007-07-15 21:24:26 +03:00
|
|
|
#include "bytestream.h"
|
2017-01-08 04:13:48 +02:00
|
|
|
#include "lossless_videoencdsp.h"
|
2007-07-15 21:24:26 +03:00
|
|
|
#include "png.h"
|
2015-03-31 22:58:26 +02:00
|
|
|
#include "apng.h"
|
2022-03-15 19:20:04 +02:00
|
|
|
#include "zlib_wrapper.h"
|
2007-07-15 21:24:26 +03:00
|
|
|
|
2012-06-11 22:41:03 +03:00
|
|
|
#include "libavutil/avassert.h"
|
2015-04-02 17:09:18 +02:00
|
|
|
#include "libavutil/crc.h"
|
avcodec/png: use libavutil/csp.h for cHRM chunks
The cHRM chunk is descriptive. That is, it describes the primaries that should
be used to interpret the pixel data in the PNG file. This is notably different
from Mastering Display Metadata, which describes which subset of the presented
gamut is relevant. MDM describes a gamut and says colors outside the gamut are
not required to be preserved, but it does not actually describe the gamut that
the pixel data from the frame resides in. Thus, to decode a cHRM chunk present
in a PNG file to Mastering Display Metadata is incorrect.
This commit changes this behavior so the cHRM chunk, if present, is decoded to
color metadata. For example, if the cHRM chunk describes BT.709 primaries, the
resulting AVFrame will be tagged with AVCOL_PRI_BT709, as a description of its
pixel data. To do this, it utilizes libavutil/csp.h, which exposes a funcction
av_csp_primaries_id_from_desc, to detect which enum value accurately describes
the white point and primaries represented by the cHRM chunk.
This commit also changes pngenc.c to utilize the libavuitl/csp.h API, since it
previously duplicated code contained in that API. Instead, taking advantage of
the API that exists makes more sense. pngenc.c does properly utilize the color
tags rather than incorrectly using MDM, so that required no change.
Signed-off-by: Leo Izen <leo.izen@gmail.com>
2023-01-17 18:09:29 +02:00
|
|
|
#include "libavutil/csp.h"
|
2015-03-02 15:54:31 +02:00
|
|
|
#include "libavutil/libm.h"
|
2013-05-10 18:45:11 +03:00
|
|
|
#include "libavutil/opt.h"
|
avcodec/png: use libavutil/csp.h for cHRM chunks
The cHRM chunk is descriptive. That is, it describes the primaries that should
be used to interpret the pixel data in the PNG file. This is notably different
from Mastering Display Metadata, which describes which subset of the presented
gamut is relevant. MDM describes a gamut and says colors outside the gamut are
not required to be preserved, but it does not actually describe the gamut that
the pixel data from the frame resides in. Thus, to decode a cHRM chunk present
in a PNG file to Mastering Display Metadata is incorrect.
This commit changes this behavior so the cHRM chunk, if present, is decoded to
color metadata. For example, if the cHRM chunk describes BT.709 primaries, the
resulting AVFrame will be tagged with AVCOL_PRI_BT709, as a description of its
pixel data. To do this, it utilizes libavutil/csp.h, which exposes a funcction
av_csp_primaries_id_from_desc, to detect which enum value accurately describes
the white point and primaries represented by the cHRM chunk.
This commit also changes pngenc.c to utilize the libavuitl/csp.h API, since it
previously duplicated code contained in that API. Instead, taking advantage of
the API that exists makes more sense. pngenc.c does properly utilize the color
tags rather than incorrectly using MDM, so that required no change.
Signed-off-by: Leo Izen <leo.izen@gmail.com>
2023-01-17 18:09:29 +02:00
|
|
|
#include "libavutil/rational.h"
|
2015-10-22 22:36:52 +02:00
|
|
|
#include "libavutil/stereo3d.h"
|
2012-06-11 22:41:03 +03:00
|
|
|
|
2007-07-15 21:24:26 +03:00
|
|
|
#include <zlib.h>
|
|
|
|
|
2007-07-15 22:23:55 +03:00
|
|
|
#define IOBUF_SIZE 4096
|
|
|
|
|
2015-08-19 08:41:23 +02:00
|
|
|
typedef struct APNGFctlChunk {
|
|
|
|
uint32_t sequence_number;
|
|
|
|
uint32_t width, height;
|
|
|
|
uint32_t x_offset, y_offset;
|
|
|
|
uint16_t delay_num, delay_den;
|
|
|
|
uint8_t dispose_op, blend_op;
|
|
|
|
} APNGFctlChunk;
|
|
|
|
|
2007-07-15 22:23:55 +03:00
|
|
|
typedef struct PNGEncContext {
|
2013-05-10 18:45:11 +03:00
|
|
|
AVClass *class;
|
2017-01-08 04:13:48 +02:00
|
|
|
LLVidEncDSPContext llvidencdsp;
|
2008-02-25 12:34:23 +02:00
|
|
|
|
2007-07-15 22:23:55 +03:00
|
|
|
uint8_t *bytestream;
|
|
|
|
uint8_t *bytestream_start;
|
|
|
|
uint8_t *bytestream_end;
|
|
|
|
|
2008-02-25 12:34:23 +02:00
|
|
|
int filter_type;
|
|
|
|
|
2022-03-15 19:20:04 +02:00
|
|
|
FFZStream zstream;
|
2007-07-15 22:23:55 +03:00
|
|
|
uint8_t buf[IOBUF_SIZE];
|
2013-05-10 18:45:11 +03:00
|
|
|
int dpi; ///< Physical pixel density, in dots per inch, if set
|
|
|
|
int dpm; ///< Physical pixel density, in dots per meter, if set
|
2015-03-29 13:05:42 +02:00
|
|
|
|
|
|
|
int is_progressive;
|
|
|
|
int bit_depth;
|
|
|
|
int color_type;
|
|
|
|
int bits_per_pixel;
|
2015-03-31 22:58:26 +02:00
|
|
|
|
|
|
|
// APNG
|
|
|
|
uint32_t palette_checksum; // Used to ensure a single unique palette
|
|
|
|
uint32_t sequence_number;
|
2016-10-27 22:34:48 +02:00
|
|
|
int extra_data_updated;
|
|
|
|
uint8_t *extra_data;
|
|
|
|
int extra_data_size;
|
2015-08-19 08:41:23 +02:00
|
|
|
|
|
|
|
AVFrame *prev_frame;
|
|
|
|
AVFrame *last_frame;
|
|
|
|
APNGFctlChunk last_frame_fctl;
|
|
|
|
uint8_t *last_frame_packet;
|
|
|
|
size_t last_frame_packet_size;
|
2007-07-15 22:23:55 +03:00
|
|
|
} PNGEncContext;
|
|
|
|
|
2007-07-15 21:24:26 +03:00
|
|
|
static void png_get_interlaced_row(uint8_t *dst, int row_size,
|
|
|
|
int bits_per_pixel, int pass,
|
|
|
|
const uint8_t *src, int width)
|
|
|
|
{
|
|
|
|
int x, mask, dst_x, j, b, bpp;
|
|
|
|
uint8_t *d;
|
|
|
|
const uint8_t *s;
|
2013-04-17 01:37:05 +03:00
|
|
|
static const int masks[] = {0x80, 0x08, 0x88, 0x22, 0xaa, 0x55, 0xff};
|
2007-07-15 21:24:26 +03:00
|
|
|
|
2013-04-17 01:37:05 +03:00
|
|
|
mask = masks[pass];
|
2014-03-14 20:32:00 +03:00
|
|
|
switch (bits_per_pixel) {
|
2007-07-15 21:24:26 +03:00
|
|
|
case 1:
|
|
|
|
memset(dst, 0, row_size);
|
|
|
|
dst_x = 0;
|
2014-03-14 20:32:00 +03:00
|
|
|
for (x = 0; x < width; x++) {
|
2007-07-15 21:24:26 +03:00
|
|
|
j = (x & 7);
|
|
|
|
if ((mask << j) & 0x80) {
|
|
|
|
b = (src[x >> 3] >> (7 - j)) & 1;
|
|
|
|
dst[dst_x >> 3] |= b << (7 - (dst_x & 7));
|
|
|
|
dst_x++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
bpp = bits_per_pixel >> 3;
|
|
|
|
d = dst;
|
|
|
|
s = src;
|
2014-03-14 20:32:00 +03:00
|
|
|
for (x = 0; x < width; x++) {
|
2007-07-15 21:24:26 +03:00
|
|
|
j = x & 7;
|
|
|
|
if ((mask << j) & 0x80) {
|
|
|
|
memcpy(d, s, bpp);
|
|
|
|
d += bpp;
|
|
|
|
}
|
|
|
|
s += bpp;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-26 09:09:44 +02:00
|
|
|
static void sub_png_paeth_prediction(uint8_t *dst, const uint8_t *src, const uint8_t *top,
|
2014-03-14 20:32:00 +03:00
|
|
|
int w, int bpp)
|
2008-02-25 12:34:23 +02:00
|
|
|
{
|
|
|
|
int i;
|
2014-03-14 20:32:00 +03:00
|
|
|
for (i = 0; i < w; i++) {
|
2008-02-25 12:34:23 +02:00
|
|
|
int a, b, c, p, pa, pb, pc;
|
|
|
|
|
|
|
|
a = src[i - bpp];
|
|
|
|
b = top[i];
|
|
|
|
c = top[i - bpp];
|
|
|
|
|
2014-03-14 20:32:00 +03:00
|
|
|
p = b - c;
|
2008-02-25 12:34:23 +02:00
|
|
|
pc = a - c;
|
|
|
|
|
|
|
|
pa = abs(p);
|
|
|
|
pb = abs(pc);
|
|
|
|
pc = abs(p + pc);
|
|
|
|
|
|
|
|
if (pa <= pb && pa <= pc)
|
|
|
|
p = a;
|
|
|
|
else if (pb <= pc)
|
|
|
|
p = b;
|
|
|
|
else
|
|
|
|
p = c;
|
|
|
|
dst[i] = src[i] - p;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-05-28 01:03:59 +03:00
|
|
|
static void sub_left_prediction(PNGEncContext *c, uint8_t *dst, const uint8_t *src, int bpp, int size)
|
2014-02-23 22:11:30 +03:00
|
|
|
{
|
|
|
|
const uint8_t *src1 = src + bpp;
|
|
|
|
const uint8_t *src2 = src;
|
|
|
|
int x, unaligned_w;
|
|
|
|
|
|
|
|
memcpy(dst, src, bpp);
|
|
|
|
dst += bpp;
|
|
|
|
size -= bpp;
|
|
|
|
unaligned_w = FFMIN(32 - bpp, size);
|
|
|
|
for (x = 0; x < unaligned_w; x++)
|
|
|
|
*dst++ = *src1++ - *src2++;
|
|
|
|
size -= unaligned_w;
|
2017-01-08 04:13:48 +02:00
|
|
|
c->llvidencdsp.diff_bytes(dst, src1, src2, size);
|
2014-02-23 22:11:30 +03:00
|
|
|
}
|
|
|
|
|
2013-12-23 20:42:11 +03:00
|
|
|
static void png_filter_row(PNGEncContext *c, uint8_t *dst, int filter_type,
|
2022-07-26 09:09:44 +02:00
|
|
|
const uint8_t *src, const uint8_t *top, int size, int bpp)
|
2008-02-25 12:34:23 +02:00
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
2014-03-14 20:32:00 +03:00
|
|
|
switch (filter_type) {
|
2008-02-25 12:34:23 +02:00
|
|
|
case PNG_FILTER_VALUE_NONE:
|
|
|
|
memcpy(dst, src, size);
|
|
|
|
break;
|
|
|
|
case PNG_FILTER_VALUE_SUB:
|
2014-05-28 01:03:59 +03:00
|
|
|
sub_left_prediction(c, dst, src, bpp, size);
|
2008-02-25 12:34:23 +02:00
|
|
|
break;
|
|
|
|
case PNG_FILTER_VALUE_UP:
|
2017-01-08 04:13:48 +02:00
|
|
|
c->llvidencdsp.diff_bytes(dst, src, top, size);
|
2008-02-25 12:34:23 +02:00
|
|
|
break;
|
|
|
|
case PNG_FILTER_VALUE_AVG:
|
2014-03-14 20:32:00 +03:00
|
|
|
for (i = 0; i < bpp; i++)
|
2008-02-25 12:34:23 +02:00
|
|
|
dst[i] = src[i] - (top[i] >> 1);
|
2014-03-14 20:32:00 +03:00
|
|
|
for (; i < size; i++)
|
|
|
|
dst[i] = src[i] - ((src[i - bpp] + top[i]) >> 1);
|
2008-02-25 12:34:23 +02:00
|
|
|
break;
|
|
|
|
case PNG_FILTER_VALUE_PAETH:
|
2014-03-14 20:32:00 +03:00
|
|
|
for (i = 0; i < bpp; i++)
|
2008-02-25 12:34:23 +02:00
|
|
|
dst[i] = src[i] - top[i];
|
2014-03-14 20:32:00 +03:00
|
|
|
sub_png_paeth_prediction(dst + i, src + i, top + i, size - i, bpp);
|
2008-02-25 12:34:23 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static uint8_t *png_choose_filter(PNGEncContext *s, uint8_t *dst,
|
2022-07-26 09:09:44 +02:00
|
|
|
const uint8_t *src, const uint8_t *top, int size, int bpp)
|
2008-02-25 12:34:23 +02:00
|
|
|
{
|
|
|
|
int pred = s->filter_type;
|
2012-06-11 22:41:03 +03:00
|
|
|
av_assert0(bpp || !pred);
|
2014-03-14 20:32:00 +03:00
|
|
|
if (!top && pred)
|
2008-02-25 12:34:23 +02:00
|
|
|
pred = PNG_FILTER_VALUE_SUB;
|
2014-03-14 20:32:00 +03:00
|
|
|
if (pred == PNG_FILTER_VALUE_MIXED) {
|
2008-02-25 12:34:23 +02:00
|
|
|
int i;
|
|
|
|
int cost, bcost = INT_MAX;
|
|
|
|
uint8_t *buf1 = dst, *buf2 = dst + size + 16;
|
2014-03-14 20:32:00 +03:00
|
|
|
for (pred = 0; pred < 5; pred++) {
|
2013-12-23 20:42:11 +03:00
|
|
|
png_filter_row(s, buf1 + 1, pred, src, top, size, bpp);
|
2008-02-25 12:34:23 +02:00
|
|
|
buf1[0] = pred;
|
|
|
|
cost = 0;
|
2014-03-14 20:32:00 +03:00
|
|
|
for (i = 0; i <= size; i++)
|
|
|
|
cost += abs((int8_t) buf1[i]);
|
|
|
|
if (cost < bcost) {
|
2008-02-25 12:34:23 +02:00
|
|
|
bcost = cost;
|
2014-03-14 20:32:00 +03:00
|
|
|
FFSWAP(uint8_t *, buf1, buf2);
|
2008-02-25 12:34:23 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return buf2;
|
|
|
|
} else {
|
2013-12-23 20:42:11 +03:00
|
|
|
png_filter_row(s, dst + 1, pred, src, top, size, bpp);
|
2008-02-25 12:34:23 +02:00
|
|
|
dst[0] = pred;
|
|
|
|
return dst;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-07-15 21:24:26 +03:00
|
|
|
static void png_write_chunk(uint8_t **f, uint32_t tag,
|
|
|
|
const uint8_t *buf, int length)
|
|
|
|
{
|
2015-04-02 17:09:18 +02:00
|
|
|
const AVCRC *crc_table = av_crc_get_table(AV_CRC_32_IEEE_LE);
|
|
|
|
uint32_t crc = ~0U;
|
2007-07-15 21:24:26 +03:00
|
|
|
uint8_t tagbuf[4];
|
|
|
|
|
|
|
|
bytestream_put_be32(f, length);
|
|
|
|
AV_WL32(tagbuf, tag);
|
2015-04-02 17:09:18 +02:00
|
|
|
crc = av_crc(crc_table, crc, tagbuf, 4);
|
2010-07-11 01:12:30 +03:00
|
|
|
bytestream_put_be32(f, av_bswap32(tag));
|
2007-07-15 21:24:26 +03:00
|
|
|
if (length > 0) {
|
2015-04-02 17:09:18 +02:00
|
|
|
crc = av_crc(crc_table, crc, buf, length);
|
2022-03-11 12:07:14 +02:00
|
|
|
if (*f != buf)
|
|
|
|
memcpy(*f, buf, length);
|
2007-07-15 21:24:26 +03:00
|
|
|
*f += length;
|
|
|
|
}
|
2015-04-02 17:09:18 +02:00
|
|
|
bytestream_put_be32(f, ~crc);
|
2007-07-15 21:24:26 +03:00
|
|
|
}
|
|
|
|
|
2015-03-31 22:58:26 +02:00
|
|
|
static void png_write_image_data(AVCodecContext *avctx,
|
|
|
|
const uint8_t *buf, int length)
|
|
|
|
{
|
|
|
|
PNGEncContext *s = avctx->priv_data;
|
2015-04-02 17:09:18 +02:00
|
|
|
const AVCRC *crc_table = av_crc_get_table(AV_CRC_32_IEEE_LE);
|
|
|
|
uint32_t crc = ~0U;
|
2015-03-31 22:58:26 +02:00
|
|
|
|
2015-04-02 22:19:41 +02:00
|
|
|
if (avctx->codec_id == AV_CODEC_ID_PNG || avctx->frame_number == 0) {
|
|
|
|
png_write_chunk(&s->bytestream, MKTAG('I', 'D', 'A', 'T'), buf, length);
|
|
|
|
return;
|
|
|
|
}
|
2015-03-31 22:58:26 +02:00
|
|
|
|
|
|
|
bytestream_put_be32(&s->bytestream, length + 4);
|
|
|
|
|
|
|
|
bytestream_put_be32(&s->bytestream, MKBETAG('f', 'd', 'A', 'T'));
|
|
|
|
bytestream_put_be32(&s->bytestream, s->sequence_number);
|
2015-04-02 17:09:18 +02:00
|
|
|
crc = av_crc(crc_table, crc, s->bytestream - 8, 8);
|
2015-03-31 22:58:26 +02:00
|
|
|
|
2015-04-02 17:09:18 +02:00
|
|
|
crc = av_crc(crc_table, crc, buf, length);
|
2015-03-31 22:58:26 +02:00
|
|
|
memcpy(s->bytestream, buf, length);
|
|
|
|
s->bytestream += length;
|
|
|
|
|
2015-04-02 17:09:18 +02:00
|
|
|
bytestream_put_be32(&s->bytestream, ~crc);
|
2015-03-31 22:58:26 +02:00
|
|
|
|
|
|
|
++s->sequence_number;
|
|
|
|
}
|
|
|
|
|
2007-07-15 21:24:26 +03:00
|
|
|
/* XXX: do filtering */
|
2015-03-31 22:58:26 +02:00
|
|
|
static int png_write_row(AVCodecContext *avctx, const uint8_t *data, int size)
|
2007-07-15 21:24:26 +03:00
|
|
|
{
|
2015-03-31 22:58:26 +02:00
|
|
|
PNGEncContext *s = avctx->priv_data;
|
2022-03-15 19:20:04 +02:00
|
|
|
z_stream *const zstream = &s->zstream.zstream;
|
2007-07-15 21:24:26 +03:00
|
|
|
int ret;
|
|
|
|
|
2022-03-15 19:20:04 +02:00
|
|
|
zstream->avail_in = size;
|
|
|
|
zstream->next_in = data;
|
|
|
|
while (zstream->avail_in > 0) {
|
|
|
|
ret = deflate(zstream, Z_NO_FLUSH);
|
2007-07-15 21:24:26 +03:00
|
|
|
if (ret != Z_OK)
|
|
|
|
return -1;
|
2022-03-15 19:20:04 +02:00
|
|
|
if (zstream->avail_out == 0) {
|
2014-03-14 20:32:00 +03:00
|
|
|
if (s->bytestream_end - s->bytestream > IOBUF_SIZE + 100)
|
2015-03-31 22:58:26 +02:00
|
|
|
png_write_image_data(avctx, s->buf, IOBUF_SIZE);
|
2022-03-15 19:20:04 +02:00
|
|
|
zstream->avail_out = IOBUF_SIZE;
|
|
|
|
zstream->next_out = s->buf;
|
2007-07-15 21:24:26 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-03-04 03:06:08 +02:00
|
|
|
#define AV_WB32_PNG(buf, n) AV_WB32(buf, lrint((n) * 100000))
|
avcodec/png: use libavutil/csp.h for cHRM chunks
The cHRM chunk is descriptive. That is, it describes the primaries that should
be used to interpret the pixel data in the PNG file. This is notably different
from Mastering Display Metadata, which describes which subset of the presented
gamut is relevant. MDM describes a gamut and says colors outside the gamut are
not required to be preserved, but it does not actually describe the gamut that
the pixel data from the frame resides in. Thus, to decode a cHRM chunk present
in a PNG file to Mastering Display Metadata is incorrect.
This commit changes this behavior so the cHRM chunk, if present, is decoded to
color metadata. For example, if the cHRM chunk describes BT.709 primaries, the
resulting AVFrame will be tagged with AVCOL_PRI_BT709, as a description of its
pixel data. To do this, it utilizes libavutil/csp.h, which exposes a funcction
av_csp_primaries_id_from_desc, to detect which enum value accurately describes
the white point and primaries represented by the cHRM chunk.
This commit also changes pngenc.c to utilize the libavuitl/csp.h API, since it
previously duplicated code contained in that API. Instead, taking advantage of
the API that exists makes more sense. pngenc.c does properly utilize the color
tags rather than incorrectly using MDM, so that required no change.
Signed-off-by: Leo Izen <leo.izen@gmail.com>
2023-01-17 18:09:29 +02:00
|
|
|
#define AV_WB32_PNG_D(buf, d) AV_WB32_PNG(buf, av_q2d(d))
|
2015-02-27 22:31:38 +02:00
|
|
|
static int png_get_chrm(enum AVColorPrimaries prim, uint8_t *buf)
|
|
|
|
{
|
avcodec/png: use libavutil/csp.h for cHRM chunks
The cHRM chunk is descriptive. That is, it describes the primaries that should
be used to interpret the pixel data in the PNG file. This is notably different
from Mastering Display Metadata, which describes which subset of the presented
gamut is relevant. MDM describes a gamut and says colors outside the gamut are
not required to be preserved, but it does not actually describe the gamut that
the pixel data from the frame resides in. Thus, to decode a cHRM chunk present
in a PNG file to Mastering Display Metadata is incorrect.
This commit changes this behavior so the cHRM chunk, if present, is decoded to
color metadata. For example, if the cHRM chunk describes BT.709 primaries, the
resulting AVFrame will be tagged with AVCOL_PRI_BT709, as a description of its
pixel data. To do this, it utilizes libavutil/csp.h, which exposes a funcction
av_csp_primaries_id_from_desc, to detect which enum value accurately describes
the white point and primaries represented by the cHRM chunk.
This commit also changes pngenc.c to utilize the libavuitl/csp.h API, since it
previously duplicated code contained in that API. Instead, taking advantage of
the API that exists makes more sense. pngenc.c does properly utilize the color
tags rather than incorrectly using MDM, so that required no change.
Signed-off-by: Leo Izen <leo.izen@gmail.com>
2023-01-17 18:09:29 +02:00
|
|
|
const AVColorPrimariesDesc *desc = av_csp_primaries_desc_from_id(prim);
|
|
|
|
if (!desc)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
AV_WB32_PNG_D(buf, desc->wp.x);
|
|
|
|
AV_WB32_PNG_D(buf + 4, desc->wp.y);
|
|
|
|
AV_WB32_PNG_D(buf + 8, desc->prim.r.x);
|
|
|
|
AV_WB32_PNG_D(buf + 12, desc->prim.r.y);
|
|
|
|
AV_WB32_PNG_D(buf + 16, desc->prim.g.x);
|
|
|
|
AV_WB32_PNG_D(buf + 20, desc->prim.g.y);
|
|
|
|
AV_WB32_PNG_D(buf + 24, desc->prim.b.x);
|
|
|
|
AV_WB32_PNG_D(buf + 28, desc->prim.b.y);
|
2015-02-27 22:31:38 +02:00
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int png_get_gama(enum AVColorTransferCharacteristic trc, uint8_t *buf)
|
|
|
|
{
|
2023-01-30 18:50:10 +02:00
|
|
|
double gamma = av_csp_approximate_trc_gamma(trc);
|
2015-03-05 12:37:51 +02:00
|
|
|
if (gamma <= 1e-6)
|
|
|
|
return 0;
|
2015-02-27 22:31:38 +02:00
|
|
|
|
|
|
|
AV_WB32_PNG(buf, 1.0 / gamma);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2022-03-11 12:07:14 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2015-03-31 22:58:25 +02:00
|
|
|
static int encode_headers(AVCodecContext *avctx, const AVFrame *pict)
|
2012-02-12 11:32:40 +03:00
|
|
|
{
|
2015-10-22 22:36:52 +02:00
|
|
|
AVFrameSideData *side_data;
|
2015-03-31 22:58:25 +02:00
|
|
|
PNGEncContext *s = avctx->priv_data;
|
2022-03-11 12:07:14 +02:00
|
|
|
int ret;
|
2007-07-15 21:24:26 +03:00
|
|
|
|
|
|
|
/* write png header */
|
|
|
|
AV_WB32(s->buf, avctx->width);
|
|
|
|
AV_WB32(s->buf + 4, avctx->height);
|
2015-03-29 13:05:42 +02:00
|
|
|
s->buf[8] = s->bit_depth;
|
|
|
|
s->buf[9] = s->color_type;
|
2007-07-15 21:24:26 +03:00
|
|
|
s->buf[10] = 0; /* compression type */
|
|
|
|
s->buf[11] = 0; /* filter type */
|
2015-03-29 13:05:42 +02:00
|
|
|
s->buf[12] = s->is_progressive; /* interlace type */
|
2007-07-15 21:24:26 +03:00
|
|
|
png_write_chunk(&s->bytestream, MKTAG('I', 'H', 'D', 'R'), s->buf, 13);
|
|
|
|
|
2015-03-28 17:14:22 +02:00
|
|
|
/* write physical information */
|
2013-05-10 18:45:11 +03:00
|
|
|
if (s->dpm) {
|
|
|
|
AV_WB32(s->buf, s->dpm);
|
|
|
|
AV_WB32(s->buf + 4, s->dpm);
|
|
|
|
s->buf[8] = 1; /* unit specifier is meter */
|
|
|
|
} else {
|
|
|
|
AV_WB32(s->buf, avctx->sample_aspect_ratio.num);
|
|
|
|
AV_WB32(s->buf + 4, avctx->sample_aspect_ratio.den);
|
|
|
|
s->buf[8] = 0; /* unit specifier is unknown */
|
|
|
|
}
|
2012-10-13 12:26:09 +03:00
|
|
|
png_write_chunk(&s->bytestream, MKTAG('p', 'H', 'Y', 's'), s->buf, 9);
|
|
|
|
|
2015-10-22 22:36:52 +02:00
|
|
|
/* write stereoscopic information */
|
|
|
|
side_data = av_frame_get_side_data(pict, AV_FRAME_DATA_STEREO3D);
|
|
|
|
if (side_data) {
|
|
|
|
AVStereo3D *stereo3d = (AVStereo3D *)side_data->data;
|
|
|
|
switch (stereo3d->type) {
|
|
|
|
case AV_STEREO3D_SIDEBYSIDE:
|
|
|
|
s->buf[0] = ((stereo3d->flags & AV_STEREO3D_FLAG_INVERT) == 0) ? 1 : 0;
|
|
|
|
png_write_chunk(&s->bytestream, MKTAG('s', 'T', 'E', 'R'), s->buf, 1);
|
|
|
|
break;
|
|
|
|
case AV_STEREO3D_2D:
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
av_log(avctx, AV_LOG_WARNING, "Only side-by-side stereo3d flag can be defined within sTER chunk\n");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-01 19:04:30 +02:00
|
|
|
side_data = av_frame_get_side_data(pict, AV_FRAME_DATA_ICC_PROFILE);
|
|
|
|
if ((ret = png_write_iccp(s, side_data)))
|
|
|
|
return ret;
|
|
|
|
|
2015-02-27 22:31:38 +02:00
|
|
|
/* write colorspace information */
|
|
|
|
if (pict->color_primaries == AVCOL_PRI_BT709 &&
|
|
|
|
pict->color_trc == AVCOL_TRC_IEC61966_2_1) {
|
|
|
|
s->buf[0] = 1; /* rendering intent, relative colorimetric by default */
|
|
|
|
png_write_chunk(&s->bytestream, MKTAG('s', 'R', 'G', 'B'), s->buf, 1);
|
2023-02-01 19:04:30 +02:00
|
|
|
} else if (pict->color_trc != AVCOL_TRC_UNSPECIFIED && !side_data) {
|
|
|
|
/*
|
|
|
|
* Avoid writing cICP if the transfer is unknown. Known primaries
|
|
|
|
* with unknown transfer can be handled by cHRM.
|
|
|
|
*
|
|
|
|
* We also avoid writing cICP if an ICC Profile is present, because
|
|
|
|
* the standard requires that cICP overrides iCCP.
|
|
|
|
*
|
|
|
|
* These values match H.273 so no translation is needed.
|
|
|
|
*/
|
avcodec/png: support cICP chunks
This commit adds both decode and encode support for cICP chunks, which
allow a PNG image's pixel data to be tagged by any of the enum values in
H.273, without an ICC profile.
Upon decode, if a cICP chunk is present, the PNG decoder will tag output
AVFrames with the resulting enum color, and ignore iCCP, sRGB, gAMA, and
cHRM chunks, as per the spec.
Upon encode, if the color space is known and specified, and it is not sRGB,
the PNG encoder will output a cICP chunk containing the color space. If the
color space is sRGB, then it will output an sRGB chunk instead of a cICP
chunk. If the color space of the input is not unspecified, it will not output
a cICP chunk tagging the PNG as unspecified.
In either the sRGB case or the non-SRGB case, gAMA and cHRM are still written
as fallbacks provided the info is known.
Signed-off-by: Leo Izen <leo.izen@gmail.com>
2023-01-17 20:38:42 +02:00
|
|
|
s->buf[0] = pict->color_primaries;
|
|
|
|
s->buf[1] = pict->color_trc;
|
|
|
|
s->buf[2] = 0; /* colorspace = RGB */
|
|
|
|
s->buf[3] = pict->color_range == AVCOL_RANGE_MPEG ? 0 : 1;
|
|
|
|
png_write_chunk(&s->bytestream, MKTAG('c', 'I', 'C', 'P'), s->buf, 4);
|
2015-02-27 22:31:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (png_get_chrm(pict->color_primaries, s->buf))
|
|
|
|
png_write_chunk(&s->bytestream, MKTAG('c', 'H', 'R', 'M'), s->buf, 32);
|
|
|
|
if (png_get_gama(pict->color_trc, s->buf))
|
|
|
|
png_write_chunk(&s->bytestream, MKTAG('g', 'A', 'M', 'A'), s->buf, 4);
|
|
|
|
|
2022-03-11 12:07:14 +02:00
|
|
|
/* put the palette if needed, must be after colorspace information */
|
2015-03-29 13:05:42 +02:00
|
|
|
if (s->color_type == PNG_COLOR_TYPE_PALETTE) {
|
2007-07-15 21:24:26 +03:00
|
|
|
int has_alpha, alpha, i;
|
|
|
|
unsigned int v;
|
|
|
|
uint32_t *palette;
|
2015-03-31 22:58:25 +02:00
|
|
|
uint8_t *ptr, *alpha_ptr;
|
2007-07-15 21:24:26 +03:00
|
|
|
|
2015-03-31 22:58:25 +02:00
|
|
|
palette = (uint32_t *)pict->data[1];
|
2014-03-14 20:32:00 +03:00
|
|
|
ptr = s->buf;
|
2007-07-15 21:24:26 +03:00
|
|
|
alpha_ptr = s->buf + 256 * 3;
|
|
|
|
has_alpha = 0;
|
2014-03-14 20:32:00 +03:00
|
|
|
for (i = 0; i < 256; i++) {
|
|
|
|
v = palette[i];
|
2007-07-15 21:24:26 +03:00
|
|
|
alpha = v >> 24;
|
2011-12-07 13:31:27 +03:00
|
|
|
if (alpha != 0xff)
|
2007-07-15 21:24:26 +03:00
|
|
|
has_alpha = 1;
|
|
|
|
*alpha_ptr++ = alpha;
|
|
|
|
bytestream_put_be24(&ptr, v);
|
|
|
|
}
|
2014-03-14 20:32:00 +03:00
|
|
|
png_write_chunk(&s->bytestream,
|
|
|
|
MKTAG('P', 'L', 'T', 'E'), s->buf, 256 * 3);
|
2007-07-15 21:24:26 +03:00
|
|
|
if (has_alpha) {
|
2014-03-14 20:32:00 +03:00
|
|
|
png_write_chunk(&s->bytestream,
|
|
|
|
MKTAG('t', 'R', 'N', 'S'), s->buf + 256 * 3, 256);
|
2007-07-15 21:24:26 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-31 22:58:25 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int encode_frame(AVCodecContext *avctx, const AVFrame *pict)
|
|
|
|
{
|
|
|
|
PNGEncContext *s = avctx->priv_data;
|
2022-03-15 19:20:04 +02:00
|
|
|
z_stream *const zstream = &s->zstream.zstream;
|
2015-03-31 22:58:25 +02:00
|
|
|
const AVFrame *const p = pict;
|
|
|
|
int y, len, ret;
|
|
|
|
int row_size, pass_row_size;
|
2022-07-26 09:09:44 +02:00
|
|
|
uint8_t *crow_buf, *crow;
|
2015-03-31 22:58:25 +02:00
|
|
|
uint8_t *crow_base = NULL;
|
|
|
|
uint8_t *progressive_buf = NULL;
|
|
|
|
uint8_t *top_buf = NULL;
|
|
|
|
|
2015-08-19 08:41:23 +02:00
|
|
|
row_size = (pict->width * s->bits_per_pixel + 7) >> 3;
|
2015-03-31 22:58:25 +02:00
|
|
|
|
|
|
|
crow_base = av_malloc((row_size + 32) << (s->filter_type == PNG_FILTER_VALUE_MIXED));
|
|
|
|
if (!crow_base) {
|
|
|
|
ret = AVERROR(ENOMEM);
|
|
|
|
goto the_end;
|
|
|
|
}
|
|
|
|
// pixel data should be aligned, but there's a control byte before it
|
|
|
|
crow_buf = crow_base + 15;
|
|
|
|
if (s->is_progressive) {
|
|
|
|
progressive_buf = av_malloc(row_size + 1);
|
|
|
|
top_buf = av_malloc(row_size + 1);
|
|
|
|
if (!progressive_buf || !top_buf) {
|
|
|
|
ret = AVERROR(ENOMEM);
|
|
|
|
goto the_end;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* put each row */
|
2022-03-15 19:20:04 +02:00
|
|
|
zstream->avail_out = IOBUF_SIZE;
|
|
|
|
zstream->next_out = s->buf;
|
2015-03-29 13:05:42 +02:00
|
|
|
if (s->is_progressive) {
|
2007-07-15 21:24:26 +03:00
|
|
|
int pass;
|
|
|
|
|
2014-03-14 20:32:00 +03:00
|
|
|
for (pass = 0; pass < NB_PASSES; pass++) {
|
2012-12-19 20:48:21 +03:00
|
|
|
/* NOTE: a pass is completely omitted if no pixels would be
|
2014-03-14 20:32:00 +03:00
|
|
|
* output */
|
2015-08-19 08:41:23 +02:00
|
|
|
pass_row_size = ff_png_pass_row_size(pass, s->bits_per_pixel, pict->width);
|
2007-07-15 21:24:26 +03:00
|
|
|
if (pass_row_size > 0) {
|
2022-07-26 09:09:44 +02:00
|
|
|
uint8_t *top = NULL;
|
2015-08-19 08:41:23 +02:00
|
|
|
for (y = 0; y < pict->height; y++)
|
2007-07-15 21:24:26 +03:00
|
|
|
if ((ff_png_pass_ymask[pass] << (y & 7)) & 0x80) {
|
2022-07-26 09:09:44 +02:00
|
|
|
const uint8_t *ptr = p->data[0] + y * p->linesize[0];
|
2014-03-14 20:32:00 +03:00
|
|
|
FFSWAP(uint8_t *, progressive_buf, top_buf);
|
2008-02-25 12:34:23 +02:00
|
|
|
png_get_interlaced_row(progressive_buf, pass_row_size,
|
2015-03-29 13:05:42 +02:00
|
|
|
s->bits_per_pixel, pass,
|
2015-08-19 08:41:23 +02:00
|
|
|
ptr, pict->width);
|
2014-03-14 20:32:00 +03:00
|
|
|
crow = png_choose_filter(s, crow_buf, progressive_buf,
|
2015-03-29 13:05:42 +02:00
|
|
|
top, pass_row_size, s->bits_per_pixel >> 3);
|
2015-03-31 22:58:26 +02:00
|
|
|
png_write_row(avctx, crow, pass_row_size + 1);
|
2008-02-25 12:34:23 +02:00
|
|
|
top = progressive_buf;
|
2007-07-15 21:24:26 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
2022-07-26 09:09:44 +02:00
|
|
|
const uint8_t *top = NULL;
|
2015-08-19 08:41:23 +02:00
|
|
|
for (y = 0; y < pict->height; y++) {
|
2022-07-26 09:09:44 +02:00
|
|
|
const uint8_t *ptr = p->data[0] + y * p->linesize[0];
|
2014-03-14 20:32:00 +03:00
|
|
|
crow = png_choose_filter(s, crow_buf, ptr, top,
|
2015-03-29 13:05:42 +02:00
|
|
|
row_size, s->bits_per_pixel >> 3);
|
2015-03-31 22:58:26 +02:00
|
|
|
png_write_row(avctx, crow, row_size + 1);
|
2008-02-25 12:34:23 +02:00
|
|
|
top = ptr;
|
2007-07-15 21:24:26 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
/* compress last bytes */
|
2014-03-14 20:32:00 +03:00
|
|
|
for (;;) {
|
2022-03-15 19:20:04 +02:00
|
|
|
ret = deflate(zstream, Z_FINISH);
|
2007-07-15 21:24:26 +03:00
|
|
|
if (ret == Z_OK || ret == Z_STREAM_END) {
|
2022-03-15 19:20:04 +02:00
|
|
|
len = IOBUF_SIZE - zstream->avail_out;
|
2007-07-15 21:24:26 +03:00
|
|
|
if (len > 0 && s->bytestream_end - s->bytestream > len + 100) {
|
2015-03-31 22:58:26 +02:00
|
|
|
png_write_image_data(avctx, s->buf, len);
|
2007-07-15 21:24:26 +03:00
|
|
|
}
|
2022-03-15 19:20:04 +02:00
|
|
|
zstream->avail_out = IOBUF_SIZE;
|
|
|
|
zstream->next_out = s->buf;
|
2007-07-15 21:24:26 +03:00
|
|
|
if (ret == Z_STREAM_END)
|
|
|
|
break;
|
|
|
|
} else {
|
2015-03-29 13:05:42 +02:00
|
|
|
ret = -1;
|
|
|
|
goto the_end;
|
2007-07-15 21:24:26 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-31 22:58:25 +02:00
|
|
|
ret = 0;
|
2012-02-12 11:32:40 +03:00
|
|
|
|
2014-03-14 20:32:00 +03:00
|
|
|
the_end:
|
2015-03-28 17:14:21 +02:00
|
|
|
av_freep(&crow_base);
|
|
|
|
av_freep(&progressive_buf);
|
|
|
|
av_freep(&top_buf);
|
2022-03-15 19:20:04 +02:00
|
|
|
deflateReset(zstream);
|
2007-07-15 21:24:26 +03:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2022-03-11 12:07:14 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2015-03-31 22:58:26 +02:00
|
|
|
static int encode_png(AVCodecContext *avctx, AVPacket *pkt,
|
|
|
|
const AVFrame *pict, int *got_packet)
|
2015-03-31 22:58:25 +02:00
|
|
|
{
|
|
|
|
PNGEncContext *s = avctx->priv_data;
|
|
|
|
int ret;
|
|
|
|
int enc_row_size;
|
2022-03-15 08:51:29 +02:00
|
|
|
uint64_t max_packet_size;
|
2015-03-31 22:58:25 +02:00
|
|
|
|
2022-03-15 19:20:04 +02:00
|
|
|
enc_row_size = deflateBound(&s->zstream.zstream,
|
|
|
|
(avctx->width * s->bits_per_pixel + 7) >> 3);
|
2015-03-31 22:58:25 +02:00
|
|
|
max_packet_size =
|
2015-07-27 22:53:16 +02:00
|
|
|
AV_INPUT_BUFFER_MIN_SIZE + // headers
|
2015-03-31 22:58:25 +02:00
|
|
|
avctx->height * (
|
|
|
|
enc_row_size +
|
|
|
|
12 * (((int64_t)enc_row_size + IOBUF_SIZE - 1) / IOBUF_SIZE) // IDAT * ceil(enc_row_size / IOBUF_SIZE)
|
|
|
|
);
|
2022-03-11 12:07:14 +02:00
|
|
|
if ((ret = add_icc_profile_size(avctx, pict, &max_packet_size)))
|
|
|
|
return ret;
|
2021-05-11 15:17:13 +02:00
|
|
|
ret = ff_alloc_packet(avctx, pkt, max_packet_size);
|
2015-03-31 22:58:25 +02:00
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
s->bytestream_start =
|
|
|
|
s->bytestream = pkt->data;
|
|
|
|
s->bytestream_end = pkt->data + pkt->size;
|
|
|
|
|
|
|
|
AV_WB64(s->bytestream, PNGSIG);
|
|
|
|
s->bytestream += 8;
|
|
|
|
|
|
|
|
ret = encode_headers(avctx, pict);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
ret = encode_frame(avctx, pict);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
png_write_chunk(&s->bytestream, MKTAG('I', 'E', 'N', 'D'), NULL, 0);
|
|
|
|
|
|
|
|
pkt->size = s->bytestream - s->bytestream_start;
|
|
|
|
pkt->flags |= AV_PKT_FLAG_KEY;
|
|
|
|
*got_packet = 1;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-08-19 08:41:23 +02:00
|
|
|
static int apng_do_inverse_blend(AVFrame *output, const AVFrame *input,
|
|
|
|
APNGFctlChunk *fctl_chunk, uint8_t bpp)
|
|
|
|
{
|
|
|
|
// output: background, input: foreground
|
|
|
|
// output the image such that when blended with the background, will produce the foreground
|
|
|
|
|
|
|
|
unsigned int x, y;
|
|
|
|
unsigned int leftmost_x = input->width;
|
|
|
|
unsigned int rightmost_x = 0;
|
|
|
|
unsigned int topmost_y = input->height;
|
|
|
|
unsigned int bottommost_y = 0;
|
|
|
|
const uint8_t *input_data = input->data[0];
|
|
|
|
uint8_t *output_data = output->data[0];
|
|
|
|
ptrdiff_t input_linesize = input->linesize[0];
|
|
|
|
ptrdiff_t output_linesize = output->linesize[0];
|
|
|
|
|
|
|
|
// Find bounding box of changes
|
|
|
|
for (y = 0; y < input->height; ++y) {
|
|
|
|
for (x = 0; x < input->width; ++x) {
|
|
|
|
if (!memcmp(input_data + bpp * x, output_data + bpp * x, bpp))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (x < leftmost_x)
|
|
|
|
leftmost_x = x;
|
|
|
|
if (x >= rightmost_x)
|
|
|
|
rightmost_x = x + 1;
|
|
|
|
if (y < topmost_y)
|
|
|
|
topmost_y = y;
|
|
|
|
if (y >= bottommost_y)
|
|
|
|
bottommost_y = y + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
input_data += input_linesize;
|
|
|
|
output_data += output_linesize;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (leftmost_x == input->width && rightmost_x == 0) {
|
|
|
|
// Empty frame
|
|
|
|
// APNG does not support empty frames, so we make it a 1x1 frame
|
|
|
|
leftmost_x = topmost_y = 0;
|
|
|
|
rightmost_x = bottommost_y = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Do actual inverse blending
|
|
|
|
if (fctl_chunk->blend_op == APNG_BLEND_OP_SOURCE) {
|
|
|
|
output_data = output->data[0];
|
|
|
|
for (y = topmost_y; y < bottommost_y; ++y) {
|
|
|
|
memcpy(output_data,
|
|
|
|
input->data[0] + input_linesize * y + bpp * leftmost_x,
|
|
|
|
bpp * (rightmost_x - leftmost_x));
|
|
|
|
output_data += output_linesize;
|
|
|
|
}
|
|
|
|
} else { // APNG_BLEND_OP_OVER
|
|
|
|
size_t transparent_palette_index;
|
|
|
|
uint32_t *palette;
|
|
|
|
|
|
|
|
switch (input->format) {
|
|
|
|
case AV_PIX_FMT_RGBA64BE:
|
|
|
|
case AV_PIX_FMT_YA16BE:
|
|
|
|
case AV_PIX_FMT_RGBA:
|
|
|
|
case AV_PIX_FMT_GRAY8A:
|
|
|
|
break;
|
|
|
|
|
|
|
|
case AV_PIX_FMT_PAL8:
|
|
|
|
palette = (uint32_t*)input->data[1];
|
|
|
|
for (transparent_palette_index = 0; transparent_palette_index < 256; ++transparent_palette_index)
|
|
|
|
if (palette[transparent_palette_index] >> 24 == 0)
|
|
|
|
break;
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
// No alpha, so blending not possible
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (y = topmost_y; y < bottommost_y; ++y) {
|
2022-07-26 09:09:44 +02:00
|
|
|
const uint8_t *foreground = input->data[0] + input_linesize * y + bpp * leftmost_x;
|
2015-08-19 08:41:23 +02:00
|
|
|
uint8_t *background = output->data[0] + output_linesize * y + bpp * leftmost_x;
|
|
|
|
output_data = output->data[0] + output_linesize * (y - topmost_y);
|
|
|
|
for (x = leftmost_x; x < rightmost_x; ++x, foreground += bpp, background += bpp, output_data += bpp) {
|
|
|
|
if (!memcmp(foreground, background, bpp)) {
|
|
|
|
if (input->format == AV_PIX_FMT_PAL8) {
|
|
|
|
if (transparent_palette_index == 256) {
|
|
|
|
// Need fully transparent colour, but none exists
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
*output_data = transparent_palette_index;
|
|
|
|
} else {
|
|
|
|
memset(output_data, 0, bpp);
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check for special alpha values, since full inverse
|
|
|
|
// alpha-on-alpha blending is rarely possible, and when
|
|
|
|
// possible, doesn't compress much better than
|
|
|
|
// APNG_BLEND_OP_SOURCE blending
|
|
|
|
switch (input->format) {
|
|
|
|
case AV_PIX_FMT_RGBA64BE:
|
|
|
|
if (((uint16_t*)foreground)[3] == 0xffff ||
|
|
|
|
((uint16_t*)background)[3] == 0)
|
|
|
|
break;
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
case AV_PIX_FMT_YA16BE:
|
|
|
|
if (((uint16_t*)foreground)[1] == 0xffff ||
|
|
|
|
((uint16_t*)background)[1] == 0)
|
|
|
|
break;
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
case AV_PIX_FMT_RGBA:
|
|
|
|
if (foreground[3] == 0xff || background[3] == 0)
|
|
|
|
break;
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
case AV_PIX_FMT_GRAY8A:
|
|
|
|
if (foreground[1] == 0xff || background[1] == 0)
|
|
|
|
break;
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
case AV_PIX_FMT_PAL8:
|
|
|
|
if (palette[*foreground] >> 24 == 0xff ||
|
|
|
|
palette[*background] >> 24 == 0)
|
|
|
|
break;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
memmove(output_data, foreground, bpp);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
output->width = rightmost_x - leftmost_x;
|
|
|
|
output->height = bottommost_y - topmost_y;
|
|
|
|
fctl_chunk->width = output->width;
|
|
|
|
fctl_chunk->height = output->height;
|
|
|
|
fctl_chunk->x_offset = leftmost_x;
|
|
|
|
fctl_chunk->y_offset = topmost_y;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int apng_encode_frame(AVCodecContext *avctx, const AVFrame *pict,
|
|
|
|
APNGFctlChunk *best_fctl_chunk, APNGFctlChunk *best_last_fctl_chunk)
|
|
|
|
{
|
|
|
|
PNGEncContext *s = avctx->priv_data;
|
|
|
|
int ret;
|
|
|
|
unsigned int y;
|
|
|
|
AVFrame* diffFrame;
|
|
|
|
uint8_t bpp = (s->bits_per_pixel + 7) >> 3;
|
|
|
|
uint8_t *original_bytestream, *original_bytestream_end;
|
|
|
|
uint8_t *temp_bytestream = 0, *temp_bytestream_end;
|
|
|
|
uint32_t best_sequence_number;
|
|
|
|
uint8_t *best_bytestream;
|
|
|
|
size_t best_bytestream_size = SIZE_MAX;
|
|
|
|
APNGFctlChunk last_fctl_chunk = *best_last_fctl_chunk;
|
|
|
|
APNGFctlChunk fctl_chunk = *best_fctl_chunk;
|
|
|
|
|
|
|
|
if (avctx->frame_number == 0) {
|
|
|
|
best_fctl_chunk->width = pict->width;
|
|
|
|
best_fctl_chunk->height = pict->height;
|
|
|
|
best_fctl_chunk->x_offset = 0;
|
|
|
|
best_fctl_chunk->y_offset = 0;
|
|
|
|
best_fctl_chunk->blend_op = APNG_BLEND_OP_SOURCE;
|
|
|
|
return encode_frame(avctx, pict);
|
|
|
|
}
|
|
|
|
|
|
|
|
diffFrame = av_frame_alloc();
|
|
|
|
if (!diffFrame)
|
|
|
|
return AVERROR(ENOMEM);
|
|
|
|
|
|
|
|
diffFrame->format = pict->format;
|
|
|
|
diffFrame->width = pict->width;
|
|
|
|
diffFrame->height = pict->height;
|
2017-02-08 10:51:17 +02:00
|
|
|
if ((ret = av_frame_get_buffer(diffFrame, 0)) < 0)
|
2015-08-19 08:41:23 +02:00
|
|
|
goto fail;
|
|
|
|
|
|
|
|
original_bytestream = s->bytestream;
|
|
|
|
original_bytestream_end = s->bytestream_end;
|
|
|
|
|
|
|
|
temp_bytestream = av_malloc(original_bytestream_end - original_bytestream);
|
|
|
|
if (!temp_bytestream) {
|
|
|
|
ret = AVERROR(ENOMEM);
|
|
|
|
goto fail;
|
|
|
|
}
|
2019-05-10 16:28:38 +02:00
|
|
|
temp_bytestream_end = temp_bytestream + (original_bytestream_end - original_bytestream);
|
2015-08-19 08:41:23 +02:00
|
|
|
|
|
|
|
for (last_fctl_chunk.dispose_op = 0; last_fctl_chunk.dispose_op < 3; ++last_fctl_chunk.dispose_op) {
|
|
|
|
// 0: APNG_DISPOSE_OP_NONE
|
|
|
|
// 1: APNG_DISPOSE_OP_BACKGROUND
|
|
|
|
// 2: APNG_DISPOSE_OP_PREVIOUS
|
|
|
|
|
|
|
|
for (fctl_chunk.blend_op = 0; fctl_chunk.blend_op < 2; ++fctl_chunk.blend_op) {
|
|
|
|
// 0: APNG_BLEND_OP_SOURCE
|
|
|
|
// 1: APNG_BLEND_OP_OVER
|
|
|
|
|
|
|
|
uint32_t original_sequence_number = s->sequence_number, sequence_number;
|
|
|
|
uint8_t *bytestream_start = s->bytestream;
|
|
|
|
size_t bytestream_size;
|
|
|
|
|
|
|
|
// Do disposal
|
|
|
|
if (last_fctl_chunk.dispose_op != APNG_DISPOSE_OP_PREVIOUS) {
|
2016-04-08 12:37:20 +02:00
|
|
|
diffFrame->width = pict->width;
|
|
|
|
diffFrame->height = pict->height;
|
2016-04-08 12:45:40 +02:00
|
|
|
ret = av_frame_copy(diffFrame, s->last_frame);
|
|
|
|
if (ret < 0)
|
|
|
|
goto fail;
|
2015-08-19 08:41:23 +02:00
|
|
|
|
|
|
|
if (last_fctl_chunk.dispose_op == APNG_DISPOSE_OP_BACKGROUND) {
|
|
|
|
for (y = last_fctl_chunk.y_offset; y < last_fctl_chunk.y_offset + last_fctl_chunk.height; ++y) {
|
2016-01-15 01:57:00 +02:00
|
|
|
size_t row_start = diffFrame->linesize[0] * y + bpp * last_fctl_chunk.x_offset;
|
2015-08-19 08:41:23 +02:00
|
|
|
memset(diffFrame->data[0] + row_start, 0, bpp * last_fctl_chunk.width);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (!s->prev_frame)
|
|
|
|
continue;
|
|
|
|
|
2016-04-08 12:37:20 +02:00
|
|
|
diffFrame->width = pict->width;
|
|
|
|
diffFrame->height = pict->height;
|
2016-04-08 12:45:40 +02:00
|
|
|
ret = av_frame_copy(diffFrame, s->prev_frame);
|
|
|
|
if (ret < 0)
|
|
|
|
goto fail;
|
2015-08-19 08:41:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Do inverse blending
|
|
|
|
if (apng_do_inverse_blend(diffFrame, pict, &fctl_chunk, bpp) < 0)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
// Do encoding
|
|
|
|
ret = encode_frame(avctx, diffFrame);
|
|
|
|
sequence_number = s->sequence_number;
|
|
|
|
s->sequence_number = original_sequence_number;
|
|
|
|
bytestream_size = s->bytestream - bytestream_start;
|
|
|
|
s->bytestream = bytestream_start;
|
|
|
|
if (ret < 0)
|
|
|
|
goto fail;
|
|
|
|
|
|
|
|
if (bytestream_size < best_bytestream_size) {
|
|
|
|
*best_fctl_chunk = fctl_chunk;
|
|
|
|
*best_last_fctl_chunk = last_fctl_chunk;
|
|
|
|
|
|
|
|
best_sequence_number = sequence_number;
|
|
|
|
best_bytestream = s->bytestream;
|
|
|
|
best_bytestream_size = bytestream_size;
|
|
|
|
|
|
|
|
if (best_bytestream == original_bytestream) {
|
|
|
|
s->bytestream = temp_bytestream;
|
|
|
|
s->bytestream_end = temp_bytestream_end;
|
|
|
|
} else {
|
|
|
|
s->bytestream = original_bytestream;
|
|
|
|
s->bytestream_end = original_bytestream_end;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
s->sequence_number = best_sequence_number;
|
|
|
|
s->bytestream = original_bytestream + best_bytestream_size;
|
|
|
|
s->bytestream_end = original_bytestream_end;
|
|
|
|
if (best_bytestream != original_bytestream)
|
|
|
|
memcpy(original_bytestream, best_bytestream, best_bytestream_size);
|
|
|
|
|
|
|
|
ret = 0;
|
|
|
|
|
|
|
|
fail:
|
|
|
|
av_freep(&temp_bytestream);
|
|
|
|
av_frame_free(&diffFrame);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2015-03-31 22:58:26 +02:00
|
|
|
static int encode_apng(AVCodecContext *avctx, AVPacket *pkt,
|
|
|
|
const AVFrame *pict, int *got_packet)
|
|
|
|
{
|
|
|
|
PNGEncContext *s = avctx->priv_data;
|
|
|
|
int ret;
|
|
|
|
int enc_row_size;
|
2022-03-15 08:51:29 +02:00
|
|
|
uint64_t max_packet_size;
|
2015-10-07 02:49:34 +02:00
|
|
|
APNGFctlChunk fctl_chunk = {0};
|
2015-03-31 22:58:26 +02:00
|
|
|
|
2021-05-11 10:54:49 +02:00
|
|
|
if (pict && s->color_type == PNG_COLOR_TYPE_PALETTE) {
|
2015-04-02 17:09:18 +02:00
|
|
|
uint32_t checksum = ~av_crc(av_crc_get_table(AV_CRC_32_IEEE_LE), ~0U, pict->data[1], 256 * sizeof(uint32_t));
|
2015-03-31 22:58:26 +02:00
|
|
|
|
|
|
|
if (avctx->frame_number == 0) {
|
|
|
|
s->palette_checksum = checksum;
|
|
|
|
} else if (checksum != s->palette_checksum) {
|
|
|
|
av_log(avctx, AV_LOG_ERROR,
|
|
|
|
"Input contains more than one unique palette. APNG does not support multiple palettes.\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-15 19:20:04 +02:00
|
|
|
enc_row_size = deflateBound(&s->zstream.zstream,
|
|
|
|
(avctx->width * s->bits_per_pixel + 7) >> 3);
|
2015-03-31 22:58:26 +02:00
|
|
|
max_packet_size =
|
2015-07-27 22:53:16 +02:00
|
|
|
AV_INPUT_BUFFER_MIN_SIZE + // headers
|
2015-03-31 22:58:26 +02:00
|
|
|
avctx->height * (
|
|
|
|
enc_row_size +
|
|
|
|
(4 + 12) * (((int64_t)enc_row_size + IOBUF_SIZE - 1) / IOBUF_SIZE) // fdAT * ceil(enc_row_size / IOBUF_SIZE)
|
|
|
|
);
|
2022-03-11 12:07:14 +02:00
|
|
|
if ((ret = add_icc_profile_size(avctx, pict, &max_packet_size)))
|
|
|
|
return ret;
|
2015-03-31 22:58:26 +02:00
|
|
|
if (max_packet_size > INT_MAX)
|
|
|
|
return AVERROR(ENOMEM);
|
|
|
|
|
|
|
|
if (avctx->frame_number == 0) {
|
2015-10-07 04:50:16 +02:00
|
|
|
if (!pict)
|
|
|
|
return AVERROR(EINVAL);
|
|
|
|
|
2017-03-26 02:35:15 +02:00
|
|
|
s->bytestream = s->extra_data = av_malloc(AV_INPUT_BUFFER_MIN_SIZE);
|
2016-10-27 22:34:48 +02:00
|
|
|
if (!s->extra_data)
|
2015-08-19 08:41:23 +02:00
|
|
|
return AVERROR(ENOMEM);
|
|
|
|
|
2015-03-31 22:58:26 +02:00
|
|
|
ret = encode_headers(avctx, pict);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
|
2016-10-27 22:34:48 +02:00
|
|
|
s->extra_data_size = s->bytestream - s->extra_data;
|
2015-08-19 08:41:23 +02:00
|
|
|
|
|
|
|
s->last_frame_packet = av_malloc(max_packet_size);
|
|
|
|
if (!s->last_frame_packet)
|
2015-03-31 22:58:26 +02:00
|
|
|
return AVERROR(ENOMEM);
|
2015-08-19 08:41:23 +02:00
|
|
|
} else if (s->last_frame) {
|
2021-04-25 01:43:26 +02:00
|
|
|
ret = ff_get_encode_buffer(avctx, pkt, s->last_frame_packet_size, 0);
|
2015-08-19 08:41:23 +02:00
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
2015-03-31 22:58:26 +02:00
|
|
|
|
2015-08-19 08:41:23 +02:00
|
|
|
memcpy(pkt->data, s->last_frame_packet, s->last_frame_packet_size);
|
2022-07-12 14:45:59 +02:00
|
|
|
pkt->pts = s->last_frame->pts;
|
2022-07-12 14:37:15 +02:00
|
|
|
pkt->duration = s->last_frame->duration;
|
|
|
|
|
|
|
|
ret = ff_encode_reordered_opaque(avctx, pkt, s->last_frame);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
2015-03-31 22:58:26 +02:00
|
|
|
}
|
|
|
|
|
2015-08-19 08:41:23 +02:00
|
|
|
if (pict) {
|
|
|
|
s->bytestream_start =
|
|
|
|
s->bytestream = s->last_frame_packet;
|
|
|
|
s->bytestream_end = s->bytestream + max_packet_size;
|
2015-03-31 22:58:26 +02:00
|
|
|
|
2015-08-19 08:41:23 +02:00
|
|
|
// We're encoding the frame first, so we have to do a bit of shuffling around
|
|
|
|
// to have the image data write to the correct place in the buffer
|
|
|
|
fctl_chunk.sequence_number = s->sequence_number;
|
|
|
|
++s->sequence_number;
|
2022-07-04 17:13:58 +02:00
|
|
|
s->bytestream += APNG_FCTL_CHUNK_SIZE + 12;
|
2015-03-31 22:58:26 +02:00
|
|
|
|
2015-08-19 08:41:23 +02:00
|
|
|
ret = apng_encode_frame(avctx, pict, &fctl_chunk, &s->last_frame_fctl);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
fctl_chunk.delay_num = 0; // delay filled in during muxing
|
|
|
|
fctl_chunk.delay_den = 0;
|
|
|
|
} else {
|
|
|
|
s->last_frame_fctl.dispose_op = APNG_DISPOSE_OP_NONE;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (s->last_frame) {
|
|
|
|
uint8_t* last_fctl_chunk_start = pkt->data;
|
2022-07-04 17:13:58 +02:00
|
|
|
uint8_t buf[APNG_FCTL_CHUNK_SIZE];
|
2016-10-27 22:34:48 +02:00
|
|
|
if (!s->extra_data_updated) {
|
|
|
|
uint8_t *side_data = av_packet_new_side_data(pkt, AV_PKT_DATA_NEW_EXTRADATA, s->extra_data_size);
|
|
|
|
if (!side_data)
|
|
|
|
return AVERROR(ENOMEM);
|
|
|
|
memcpy(side_data, s->extra_data, s->extra_data_size);
|
|
|
|
s->extra_data_updated = 1;
|
|
|
|
}
|
2015-08-19 08:41:23 +02:00
|
|
|
|
|
|
|
AV_WB32(buf + 0, s->last_frame_fctl.sequence_number);
|
|
|
|
AV_WB32(buf + 4, s->last_frame_fctl.width);
|
|
|
|
AV_WB32(buf + 8, s->last_frame_fctl.height);
|
|
|
|
AV_WB32(buf + 12, s->last_frame_fctl.x_offset);
|
|
|
|
AV_WB32(buf + 16, s->last_frame_fctl.y_offset);
|
|
|
|
AV_WB16(buf + 20, s->last_frame_fctl.delay_num);
|
|
|
|
AV_WB16(buf + 22, s->last_frame_fctl.delay_den);
|
|
|
|
buf[24] = s->last_frame_fctl.dispose_op;
|
|
|
|
buf[25] = s->last_frame_fctl.blend_op;
|
2022-07-04 17:13:58 +02:00
|
|
|
png_write_chunk(&last_fctl_chunk_start, MKTAG('f', 'c', 'T', 'L'), buf, sizeof(buf));
|
2015-08-19 08:41:23 +02:00
|
|
|
|
|
|
|
*got_packet = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pict) {
|
|
|
|
if (!s->last_frame) {
|
|
|
|
s->last_frame = av_frame_alloc();
|
|
|
|
if (!s->last_frame)
|
|
|
|
return AVERROR(ENOMEM);
|
|
|
|
} else if (s->last_frame_fctl.dispose_op != APNG_DISPOSE_OP_PREVIOUS) {
|
|
|
|
if (!s->prev_frame) {
|
|
|
|
s->prev_frame = av_frame_alloc();
|
|
|
|
if (!s->prev_frame)
|
|
|
|
return AVERROR(ENOMEM);
|
|
|
|
|
|
|
|
s->prev_frame->format = pict->format;
|
|
|
|
s->prev_frame->width = pict->width;
|
|
|
|
s->prev_frame->height = pict->height;
|
2017-02-08 10:51:17 +02:00
|
|
|
if ((ret = av_frame_get_buffer(s->prev_frame, 0)) < 0)
|
2015-08-19 08:41:23 +02:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Do disposal, but not blending
|
2016-01-15 01:35:57 +02:00
|
|
|
av_frame_copy(s->prev_frame, s->last_frame);
|
2015-08-19 08:41:23 +02:00
|
|
|
if (s->last_frame_fctl.dispose_op == APNG_DISPOSE_OP_BACKGROUND) {
|
|
|
|
uint32_t y;
|
|
|
|
uint8_t bpp = (s->bits_per_pixel + 7) >> 3;
|
|
|
|
for (y = s->last_frame_fctl.y_offset; y < s->last_frame_fctl.y_offset + s->last_frame_fctl.height; ++y) {
|
2016-01-15 01:57:00 +02:00
|
|
|
size_t row_start = s->prev_frame->linesize[0] * y + bpp * s->last_frame_fctl.x_offset;
|
2015-08-19 08:41:23 +02:00
|
|
|
memset(s->prev_frame->data[0] + row_start, 0, bpp * s->last_frame_fctl.width);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
av_frame_unref(s->last_frame);
|
2022-07-26 20:19:06 +02:00
|
|
|
ret = av_frame_ref(s->last_frame, pict);
|
2015-08-19 08:41:23 +02:00
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
s->last_frame_fctl = fctl_chunk;
|
|
|
|
s->last_frame_packet_size = s->bytestream - s->bytestream_start;
|
|
|
|
} else {
|
|
|
|
av_frame_free(&s->last_frame);
|
|
|
|
}
|
2015-03-31 22:58:26 +02:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-03-14 20:32:00 +03:00
|
|
|
static av_cold int png_enc_init(AVCodecContext *avctx)
|
|
|
|
{
|
2007-07-15 22:23:55 +03:00
|
|
|
PNGEncContext *s = avctx->priv_data;
|
2015-03-29 13:05:43 +02:00
|
|
|
int compression_level;
|
2007-07-15 22:23:55 +03:00
|
|
|
|
2014-03-17 17:24:05 +03:00
|
|
|
switch (avctx->pix_fmt) {
|
2013-02-21 00:54:21 +03:00
|
|
|
case AV_PIX_FMT_RGBA:
|
|
|
|
avctx->bits_per_coded_sample = 32;
|
|
|
|
break;
|
|
|
|
case AV_PIX_FMT_RGB24:
|
|
|
|
avctx->bits_per_coded_sample = 24;
|
|
|
|
break;
|
|
|
|
case AV_PIX_FMT_GRAY8:
|
|
|
|
avctx->bits_per_coded_sample = 0x28;
|
|
|
|
break;
|
|
|
|
case AV_PIX_FMT_MONOBLACK:
|
|
|
|
avctx->bits_per_coded_sample = 1;
|
|
|
|
break;
|
|
|
|
case AV_PIX_FMT_PAL8:
|
|
|
|
avctx->bits_per_coded_sample = 8;
|
|
|
|
}
|
|
|
|
|
2017-01-08 04:13:48 +02:00
|
|
|
ff_llvidencdsp_init(&s->llvidencdsp);
|
2008-02-25 12:34:23 +02:00
|
|
|
|
2014-03-14 20:32:00 +03:00
|
|
|
if (avctx->pix_fmt == AV_PIX_FMT_MONOBLACK)
|
2008-02-25 12:34:23 +02:00
|
|
|
s->filter_type = PNG_FILTER_VALUE_NONE;
|
2007-07-15 22:23:55 +03:00
|
|
|
|
2013-05-10 18:45:11 +03:00
|
|
|
if (s->dpi && s->dpm) {
|
|
|
|
av_log(avctx, AV_LOG_ERROR, "Only one of 'dpi' or 'dpm' options should be set\n");
|
|
|
|
return AVERROR(EINVAL);
|
|
|
|
} else if (s->dpi) {
|
|
|
|
s->dpm = s->dpi * 10000 / 254;
|
|
|
|
}
|
|
|
|
|
2015-07-27 21:14:31 +02:00
|
|
|
s->is_progressive = !!(avctx->flags & AV_CODEC_FLAG_INTERLACED_DCT);
|
2015-03-29 13:05:42 +02:00
|
|
|
switch (avctx->pix_fmt) {
|
|
|
|
case AV_PIX_FMT_RGBA64BE:
|
|
|
|
s->bit_depth = 16;
|
|
|
|
s->color_type = PNG_COLOR_TYPE_RGB_ALPHA;
|
|
|
|
break;
|
|
|
|
case AV_PIX_FMT_RGB48BE:
|
|
|
|
s->bit_depth = 16;
|
|
|
|
s->color_type = PNG_COLOR_TYPE_RGB;
|
|
|
|
break;
|
|
|
|
case AV_PIX_FMT_RGBA:
|
|
|
|
s->bit_depth = 8;
|
|
|
|
s->color_type = PNG_COLOR_TYPE_RGB_ALPHA;
|
|
|
|
break;
|
|
|
|
case AV_PIX_FMT_RGB24:
|
|
|
|
s->bit_depth = 8;
|
|
|
|
s->color_type = PNG_COLOR_TYPE_RGB;
|
|
|
|
break;
|
|
|
|
case AV_PIX_FMT_GRAY16BE:
|
|
|
|
s->bit_depth = 16;
|
|
|
|
s->color_type = PNG_COLOR_TYPE_GRAY;
|
|
|
|
break;
|
|
|
|
case AV_PIX_FMT_GRAY8:
|
|
|
|
s->bit_depth = 8;
|
|
|
|
s->color_type = PNG_COLOR_TYPE_GRAY;
|
|
|
|
break;
|
|
|
|
case AV_PIX_FMT_GRAY8A:
|
|
|
|
s->bit_depth = 8;
|
|
|
|
s->color_type = PNG_COLOR_TYPE_GRAY_ALPHA;
|
|
|
|
break;
|
|
|
|
case AV_PIX_FMT_YA16BE:
|
|
|
|
s->bit_depth = 16;
|
|
|
|
s->color_type = PNG_COLOR_TYPE_GRAY_ALPHA;
|
|
|
|
break;
|
|
|
|
case AV_PIX_FMT_MONOBLACK:
|
|
|
|
s->bit_depth = 1;
|
|
|
|
s->color_type = PNG_COLOR_TYPE_GRAY;
|
|
|
|
break;
|
|
|
|
case AV_PIX_FMT_PAL8:
|
|
|
|
s->bit_depth = 8;
|
|
|
|
s->color_type = PNG_COLOR_TYPE_PALETTE;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
s->bits_per_pixel = ff_png_get_nb_channels(s->color_type) * s->bit_depth;
|
|
|
|
|
2015-03-29 13:05:43 +02:00
|
|
|
compression_level = avctx->compression_level == FF_COMPRESSION_DEFAULT
|
|
|
|
? Z_DEFAULT_COMPRESSION
|
|
|
|
: av_clip(avctx->compression_level, 0, 9);
|
2022-03-15 19:20:04 +02:00
|
|
|
return ff_deflate_init(&s->zstream, compression_level, avctx);
|
2007-07-15 22:23:55 +03:00
|
|
|
}
|
|
|
|
|
2013-11-09 12:14:46 +03:00
|
|
|
static av_cold int png_enc_close(AVCodecContext *avctx)
|
|
|
|
{
|
2015-03-29 13:05:43 +02:00
|
|
|
PNGEncContext *s = avctx->priv_data;
|
|
|
|
|
2022-03-15 19:20:04 +02:00
|
|
|
ff_deflate_end(&s->zstream);
|
2015-08-19 08:41:23 +02:00
|
|
|
av_frame_free(&s->last_frame);
|
|
|
|
av_frame_free(&s->prev_frame);
|
|
|
|
av_freep(&s->last_frame_packet);
|
2016-10-27 22:34:48 +02:00
|
|
|
av_freep(&s->extra_data);
|
|
|
|
s->extra_data_size = 0;
|
2013-11-09 12:14:46 +03:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2013-05-10 18:45:11 +03:00
|
|
|
#define OFFSET(x) offsetof(PNGEncContext, x)
|
|
|
|
#define VE AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_ENCODING_PARAM
|
|
|
|
static const AVOption options[] = {
|
|
|
|
{"dpi", "Set image resolution (in dots per inch)", OFFSET(dpi), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 0x10000, VE},
|
|
|
|
{"dpm", "Set image resolution (in dots per meter)", OFFSET(dpm), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 0x10000, VE},
|
2016-02-03 18:49:19 +02:00
|
|
|
{ "pred", "Prediction method", OFFSET(filter_type), AV_OPT_TYPE_INT, { .i64 = PNG_FILTER_VALUE_NONE }, PNG_FILTER_VALUE_NONE, PNG_FILTER_VALUE_MIXED, VE, "pred" },
|
|
|
|
{ "none", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = PNG_FILTER_VALUE_NONE }, INT_MIN, INT_MAX, VE, "pred" },
|
|
|
|
{ "sub", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = PNG_FILTER_VALUE_SUB }, INT_MIN, INT_MAX, VE, "pred" },
|
|
|
|
{ "up", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = PNG_FILTER_VALUE_UP }, INT_MIN, INT_MAX, VE, "pred" },
|
|
|
|
{ "avg", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = PNG_FILTER_VALUE_AVG }, INT_MIN, INT_MAX, VE, "pred" },
|
|
|
|
{ "paeth", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = PNG_FILTER_VALUE_PAETH }, INT_MIN, INT_MAX, VE, "pred" },
|
|
|
|
{ "mixed", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = PNG_FILTER_VALUE_MIXED }, INT_MIN, INT_MAX, VE, "pred" },
|
2015-11-09 04:15:06 +02:00
|
|
|
{ NULL},
|
2013-05-10 18:45:11 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
static const AVClass pngenc_class = {
|
2021-06-07 16:13:33 +02:00
|
|
|
.class_name = "(A)PNG encoder",
|
2015-03-31 22:58:26 +02:00
|
|
|
.item_name = av_default_item_name,
|
|
|
|
.option = options,
|
|
|
|
.version = LIBAVUTIL_VERSION_INT,
|
|
|
|
};
|
|
|
|
|
2022-03-16 22:09:54 +02:00
|
|
|
const FFCodec ff_png_encoder = {
|
|
|
|
.p.name = "png",
|
2022-08-29 13:38:02 +02:00
|
|
|
CODEC_LONG_NAME("PNG (Portable Network Graphics) image"),
|
2022-03-16 22:09:54 +02:00
|
|
|
.p.type = AVMEDIA_TYPE_VIDEO,
|
|
|
|
.p.id = AV_CODEC_ID_PNG,
|
2022-11-27 14:37:10 +02:00
|
|
|
.p.capabilities = AV_CODEC_CAP_DR1 | AV_CODEC_CAP_FRAME_THREADS |
|
|
|
|
AV_CODEC_CAP_ENCODER_REORDERED_OPAQUE,
|
2011-07-17 13:54:31 +03:00
|
|
|
.priv_data_size = sizeof(PNGEncContext),
|
|
|
|
.init = png_enc_init,
|
2013-11-09 12:14:46 +03:00
|
|
|
.close = png_enc_close,
|
2022-03-30 23:28:24 +02:00
|
|
|
FF_CODEC_ENCODE_CB(encode_png),
|
2022-03-16 22:09:54 +02:00
|
|
|
.p.pix_fmts = (const enum AVPixelFormat[]) {
|
2012-10-08 21:54:00 +03:00
|
|
|
AV_PIX_FMT_RGB24, AV_PIX_FMT_RGBA,
|
|
|
|
AV_PIX_FMT_RGB48BE, AV_PIX_FMT_RGBA64BE,
|
|
|
|
AV_PIX_FMT_PAL8,
|
|
|
|
AV_PIX_FMT_GRAY8, AV_PIX_FMT_GRAY8A,
|
2015-01-22 02:33:05 +02:00
|
|
|
AV_PIX_FMT_GRAY16BE, AV_PIX_FMT_YA16BE,
|
2012-10-06 13:10:34 +03:00
|
|
|
AV_PIX_FMT_MONOBLACK, AV_PIX_FMT_NONE
|
2012-04-06 19:19:39 +03:00
|
|
|
},
|
2022-03-16 22:09:54 +02:00
|
|
|
.p.priv_class = &pngenc_class,
|
2022-06-28 14:25:15 +02:00
|
|
|
.caps_internal = FF_CODEC_CAP_ICC_PROFILES,
|
2007-07-15 21:24:26 +03:00
|
|
|
};
|
2015-03-31 22:58:26 +02:00
|
|
|
|
2022-03-16 22:09:54 +02:00
|
|
|
const FFCodec ff_apng_encoder = {
|
|
|
|
.p.name = "apng",
|
2022-08-29 13:38:02 +02:00
|
|
|
CODEC_LONG_NAME("APNG (Animated Portable Network Graphics) image"),
|
2022-03-16 22:09:54 +02:00
|
|
|
.p.type = AVMEDIA_TYPE_VIDEO,
|
|
|
|
.p.id = AV_CODEC_ID_APNG,
|
2022-07-12 14:37:15 +02:00
|
|
|
.p.capabilities = AV_CODEC_CAP_DR1 | AV_CODEC_CAP_DELAY |
|
|
|
|
AV_CODEC_CAP_ENCODER_REORDERED_OPAQUE,
|
2015-03-31 22:58:26 +02:00
|
|
|
.priv_data_size = sizeof(PNGEncContext),
|
|
|
|
.init = png_enc_init,
|
|
|
|
.close = png_enc_close,
|
2022-03-30 23:28:24 +02:00
|
|
|
FF_CODEC_ENCODE_CB(encode_apng),
|
2022-03-16 22:09:54 +02:00
|
|
|
.p.pix_fmts = (const enum AVPixelFormat[]) {
|
2015-03-31 22:58:26 +02:00
|
|
|
AV_PIX_FMT_RGB24, AV_PIX_FMT_RGBA,
|
|
|
|
AV_PIX_FMT_RGB48BE, AV_PIX_FMT_RGBA64BE,
|
|
|
|
AV_PIX_FMT_PAL8,
|
|
|
|
AV_PIX_FMT_GRAY8, AV_PIX_FMT_GRAY8A,
|
|
|
|
AV_PIX_FMT_GRAY16BE, AV_PIX_FMT_YA16BE,
|
2021-02-14 18:20:03 +02:00
|
|
|
AV_PIX_FMT_NONE
|
2015-03-31 22:58:26 +02:00
|
|
|
},
|
2022-03-16 22:09:54 +02:00
|
|
|
.p.priv_class = &pngenc_class,
|
2022-06-28 14:25:15 +02:00
|
|
|
.caps_internal = FF_CODEC_CAP_ICC_PROFILES,
|
2015-03-31 22:58:26 +02:00
|
|
|
};
|