[FFmpeg-devel] [PATCH v5 2/2] avformat/image2: add JPEG XL image2 demuxer and muxer
Leo Izen
leo.izen at gmail.com
Tue Nov 2 16:44:44 EET 2021
Add JPEG XL image demuxer and muxer to image2 to allow
JPEG XL files to be read/written by libavformat.
---
libavformat/allformats.c | 1 +
libavformat/img2.c | 1 +
libavformat/img2dec.c | 336 +++++++++++++++++++++++++++++++++++++++
libavformat/img2enc.c | 6 +-
libavformat/mov.c | 1 +
5 files changed, 342 insertions(+), 3 deletions(-)
diff --git a/libavformat/allformats.c b/libavformat/allformats.c
index cbfadcb639..fedce9493c 100644
--- a/libavformat/allformats.c
+++ b/libavformat/allformats.c
@@ -505,6 +505,7 @@ extern const AVInputFormat ff_image_gif_pipe_demuxer;
extern const AVInputFormat ff_image_j2k_pipe_demuxer;
extern const AVInputFormat ff_image_jpeg_pipe_demuxer;
extern const AVInputFormat ff_image_jpegls_pipe_demuxer;
+extern const AVInputFormat ff_image_jpegxl_pipe_demuxer;
extern const AVInputFormat ff_image_pam_pipe_demuxer;
extern const AVInputFormat ff_image_pbm_pipe_demuxer;
extern const AVInputFormat ff_image_pcx_pipe_demuxer;
diff --git a/libavformat/img2.c b/libavformat/img2.c
index 4153102c92..d8751d66bf 100644
--- a/libavformat/img2.c
+++ b/libavformat/img2.c
@@ -31,6 +31,7 @@ const IdStrMap ff_img_tags[] = {
{ AV_CODEC_ID_MJPEG, "mpo" },
{ AV_CODEC_ID_LJPEG, "ljpg" },
{ AV_CODEC_ID_JPEGLS, "jls" },
+ { AV_CODEC_ID_JPEGXL, "jxl" },
{ AV_CODEC_ID_PNG, "png" },
{ AV_CODEC_ID_PNG, "pns" },
{ AV_CODEC_ID_PNG, "mng" },
diff --git a/libavformat/img2dec.c b/libavformat/img2dec.c
index b535831e1c..3c8834527c 100644
--- a/libavformat/img2dec.c
+++ b/libavformat/img2dec.c
@@ -830,6 +830,341 @@ static int jpegls_probe(const AVProbeData *p)
return 0;
}
+#define jxl_bits(bc) (tbits += (bc), ((p->buf_size + AVPROBE_PADDING_SIZE) * 8 < tbits) ? 0 : (AV_RL64(p->buf + (tbits-(bc))/8) >> ((tbits-(bc)) % 8)) & ~(~(uint64_t)0 << (bc)))
+
+static uint32_t jxl_ratio(uint32_t h, uint32_t ratio){
+ switch (ratio){
+ case 1:
+ return h;
+ case 2:
+ return (uint32_t)(((uint64_t)h * 12) / 10);
+ case 3:
+ return (uint32_t)(((uint64_t)h * 4) / 3);
+ case 4:
+ return (uint32_t)(((uint64_t)h * 3) / 2);
+ case 5:
+ return (uint32_t)(((uint64_t)h * 16) / 9);
+ case 6:
+ return (uint32_t)(((uint64_t)h * 5) / 4);
+ case 7:
+ return (uint32_t)(((uint64_t)h * 2) / 1);
+ default:
+ /* width coded separately */
+ return 0;
+ }
+}
+
+static uint32_t jxl_u32(const AVProbeData *p, size_t *tbitsp, uint32_t *c, uint32_t *u)
+{
+ size_t tbits;
+ uint32_t bits, ret;
+ tbits = *tbitsp;
+ bits = jxl_bits(2);
+ ret = c[bits];
+ if (u[bits])
+ ret += jxl_bits(u[bits]);
+ *tbitsp = tbits;
+ return ret;
+}
+
+static uint64_t jxl_u64(const AVProbeData *p, size_t *tbitsp)
+{
+ size_t tbits;
+ uint64_t bits, ret, shift = 12;
+ tbits = *tbitsp;
+ bits = jxl_bits(2);
+ switch (bits) {
+ case 0:
+ ret = 0;
+ break;
+ case 1:
+ ret = 1 + jxl_bits(4);
+ break;
+ case 2:
+ ret = 17 + jxl_bits(8);
+ break;
+ case 3:
+ ret = jxl_bits(12);
+ while (jxl_bits(1)){
+ if (shift < 60) {
+ ret |= jxl_bits(8) << shift;
+ shift += 8;
+ } else {
+ ret |= jxl_bits(4) << shift;
+ break;
+ }
+ }
+ break;
+ }
+ *tbitsp = tbits;
+ return ret;
+}
+
+static uint32_t jxl_bit_depth(const AVProbeData *p, size_t *tbitsp){
+ size_t tbits;
+ uint32_t ret;
+ tbits = *tbitsp;
+ if (jxl_bits(1)) {
+ /* float */
+ ret = jxl_u32(p, &tbits, (uint32_t[]){32, 16, 24, 1}, (uint32_t[]){0, 0, 0, 6});
+ tbits += 4;
+ } else {
+ /* integer */
+ ret = jxl_u32(p, &tbits, (uint32_t[]){8, 10, 12, 1}, (uint32_t[]){0, 0, 0, 6});
+ }
+ *tbitsp = tbits;
+ return ret;
+}
+
+static size_t jxl_size_header(const AVProbeData *p, size_t start_bits, uint32_t *w, uint32_t *h)
+{
+ size_t tbits;
+ uint32_t wr = 0, hr;
+ tbits = start_bits;
+ if (jxl_bits(1)) {
+ /* small size header */
+ hr = (jxl_bits(5) + 1) << 3;
+ /* ratio */
+ wr = jxl_ratio(hr, jxl_bits(3));
+ if (!wr){
+ wr = (jxl_bits(5) + 1) << 3;
+ }
+ } else {
+ /* full size header */
+ hr = 1 + jxl_u32(p, &tbits, (uint32_t[]){0, 0, 0, 0}, (uint32_t[]){9, 13, 18, 30});
+ /* ratio */
+ wr = jxl_ratio(hr, jxl_bits(3));
+ if (!wr){
+ wr = 1 + jxl_u32(p, &tbits, (uint32_t[]){0, 0, 0, 0}, (uint32_t[]){9, 13, 18, 30});
+ }
+ }
+ if (hr > (1 << 18) || wr > (1 << 18))
+ /*
+ * raw JXL codestream files capped at level 5
+ * a violation is a false positive
+ * level 5 mandates that w, h <= 2^18
+ */
+ return 0;
+ if ((hr >> 4) * (wr >> 4) > (1 << 20))
+ /*
+ * (h >> 4) * (w >> 4) avoids uint32_t overflow
+ * level 5 also mandates that w * h <= 2^28
+ */
+ return 0;
+ *h = hr;
+ *w = wr;
+ return tbits - start_bits;
+}
+
+static int jpegxl_probe(const AVProbeData *p)
+{
+ const uint8_t *b = p->buf;
+ uint32_t w, h;
+ int all_default, extra_fields = 0, xyb = 1;
+ size_t tbits = 16, bits, pbits, bonus;
+ uint64_t count;
+
+ /* ISOBMFF-based container */
+ /* 0x4a584c20 == "JXL " */
+ if (AV_RB64(b) == 0x0000000c4a584c20)
+ return AVPROBE_SCORE_EXTENSION + 1;
+ /* Codestreams all start with 0xff0a */
+ if (AV_RB16(b) != 0xff0a)
+ return 0;
+ pbits = jxl_size_header(p, tbits, &w, &h);
+ if (pbits)
+ tbits += pbits;
+ else
+ /* invalid size header */
+ return 0;
+
+ /* all_default */
+ all_default = jxl_bits(1);
+ if (!all_default)
+ extra_fields = jxl_bits(1);
+
+ if (extra_fields) {
+ /* orientation */
+ tbits += 3;
+ /* have intrinstic size */
+ if (jxl_bits(1)) {
+ pbits = jxl_size_header(p, tbits, &w, &h);
+ if (pbits)
+ tbits += pbits;
+ else
+ return 0;
+ }
+ /* have preview */
+ if (jxl_bits(1)) {
+ /* div8 */
+ bits = jxl_bits(1);
+ if (bits)
+ h = jxl_u32(p, &tbits, (uint32_t[]){16, 32, 1, 33}, (uint32_t[]){0, 0, 5, 9});
+ else
+ h = jxl_u32(p, &tbits, (uint32_t[]){1, 65, 321, 1345}, (uint32_t[]){6, 8, 10, 12});
+ if (h > 4096)
+ /* invalid for preview headers */
+ return 0;
+ /* ratio */
+ w = jxl_ratio(h, jxl_bits(3));
+ if (!w){
+ if (bits)
+ w = jxl_u32(p, &tbits, (uint32_t[]){16, 32, 1, 33}, (uint32_t[]){0, 0, 5, 9});
+ else
+ w = jxl_u32(p, &tbits, (uint32_t[]){1, 65, 321, 1345}, (uint32_t[]){6, 8, 10, 12});
+ }
+ if (w > 4096)
+ /* invalid for preview headers */
+ return 0;
+ }
+ /* have animation */
+ if (jxl_bits(1)) {
+ /* timebase */
+ jxl_u32(p, &tbits, (uint32_t[]){100, 1000, 1, 1}, (uint32_t[]){0, 0, 10, 30});
+ jxl_u32(p, &tbits, (uint32_t[]){1, 1001, 1, 1}, (uint32_t[]){0, 0, 8, 10});
+ /* loopcount */
+ jxl_u32(p, &tbits, (uint32_t[]){0, 0, 0, 0}, (uint32_t[]){0, 3, 16, 32});
+ /* PTS present */
+ tbits += 1;
+ }
+ }
+
+ if (!all_default) {
+ /* bit depth */
+ jxl_bit_depth(p, &tbits);
+ /* modular 16-bit */
+ tbits += 1;
+ /* extra channel count */
+ count = jxl_u32(p, &tbits, (uint32_t[]){0, 1, 2, 1}, (uint32_t[]){0, 0, 4, 12});
+ for (int i = 0; i < count; i++){
+ if (!jxl_bits(1)){
+ /* type */
+ bits = jxl_u32(p, &tbits, (uint32_t[]){0, 1, 2, 18}, (uint32_t[]){0, 0, 4, 6});
+ if (bits > 63)
+ /* enum types cannot be 64+ */
+ return 0;
+ /* bit depth */
+ jxl_bit_depth(p, &tbits);
+ /* dimension shift */
+ jxl_u32(p, &tbits, (uint32_t[]){0, 3, 4, 1}, (uint32_t[]){0, 0, 0, 3});
+ /* name len */
+ pbits = jxl_u32(p, &tbits, (uint32_t[]){0, 0, 16, 48}, (uint32_t[]){0, 4, 5, 10});
+ /* name, UTF-8 (not parsing it) */
+ tbits += 8 * pbits;
+ /* alpha channel */
+ if (bits == 1)
+ /* alpha premultiplied */
+ tbits += 1;
+ /* spot color channel */
+ if (bits == 2)
+ /* RGB+S */
+ tbits += 16 * 4;
+ /* color filter array channel */
+ if (bits == 5)
+ jxl_u32(p, &tbits, (uint32_t[]){1, 0, 3, 19}, (uint32_t[]){0, 2, 4, 8});
+ }
+ }
+ xyb = jxl_bits(1);
+ /* color encoding */
+ if (!jxl_bits(1)) {
+ /* icc profile */
+ bonus = jxl_bits(1);
+ bits = jxl_u32(p, &tbits, (uint32_t[]){0, 1, 2, 18}, (uint32_t[]){0, 0, 4, 6});
+ if (bits > 63)
+ return 0;
+ if (!bonus){
+ /* bits == 2 -> XYB color space */
+ if (bits != 2)
+ /* white point */
+ pbits = jxl_u32(p, &tbits, (uint32_t[]){0, 1, 2, 18}, (uint32_t[]){0, 0, 4, 6});
+ else
+ pbits = 1;
+ if (pbits > 63)
+ return 0;
+ /* custom white point */
+ if (pbits == 2) {
+ for (int i = 0; i < 2; i++)
+ jxl_u32(p, &tbits, (uint32_t[]){0, 524288, 1048576, 2097152}, (uint32_t[]){19, 19, 20, 21});
+ }
+ /* bits == 1 -> grayscale */
+ if (bits != 2 && bits != 1)
+ /* primaries */
+ pbits = jxl_u32(p, &tbits, (uint32_t[]){0, 1, 2, 18}, (uint32_t[]){0, 0, 4, 6});
+ if (pbits > 63)
+ return 0;
+ /* custom primaries */
+ if (pbits == 2){
+ for (int i = 0; i < 6; i++)
+ jxl_u32(p, &tbits, (uint32_t[]){0, 524288, 1048576, 2097152}, (uint32_t[]){19, 19, 20, 21});
+ }
+ /* gamma is present */
+ if (jxl_bits(1)){
+ /* gamma */
+ tbits += 24;
+ } else {
+ /* transfer function */
+ if (jxl_u32(p, &tbits, (uint32_t[]){0, 1, 2, 18}, (uint32_t[]){0, 0, 4, 6}) > 63)
+ return 0;
+ }
+ /* rendering intent */
+ if (jxl_u32(p, &tbits, (uint32_t[]){0, 1, 2, 18}, (uint32_t[]){0, 0, 4, 6}) > 63)
+ return 0;
+ }
+ }
+ }
+
+ /* tone mapping */
+ if (extra_fields){
+ /* everything default */
+ if (!jxl_bits(1)){
+ /* three 16-bit fields and a bool */
+ tbits += 16 * 3 + 1;
+ }
+ }
+
+ /* extensions */
+ if (!all_default){
+ count = jxl_u64(p, &tbits);
+ while (count){
+ if (count & 1)
+ jxl_u64(p, &tbits);
+ count >>= 1;
+ }
+ }
+
+ /* default transform */
+ bonus = jxl_bits(1);
+ if (!bonus && xyb){
+ /* opsin inverse matrix */
+ bits = jxl_bits(1);
+ if (!bits){
+ /* 16 fields, 16 bits each */
+ tbits += 16 * 16;
+ }
+ bits = jxl_bits(3);
+ } else {
+ bits = 0;
+ }
+ if (bits & 1)
+ tbits += 16 * 15;
+ if (bits & 2)
+ tbits += 16 * 55;
+ if (bits & 4)
+ tbits += 16 * 210;
+ bits = tbits % 8;
+ if (bits)
+ bits = jxl_bits(8 - bits);
+ if (bits)
+ /* header is zero padded to the byte */
+ return 0;
+
+ if (p->buf_size < (tbits - 1) / 8 + 2)
+ /* file too small */
+ return 0;
+ return AVPROBE_SCORE_EXTENSION + 1;
+}
+#undef jxl_bits
+
static int pcx_probe(const AVProbeData *p)
{
const uint8_t *b = p->buf;
@@ -1149,6 +1484,7 @@ IMAGEAUTO_DEMUXER(gif, AV_CODEC_ID_GIF)
IMAGEAUTO_DEMUXER(j2k, AV_CODEC_ID_JPEG2000)
IMAGEAUTO_DEMUXER(jpeg, AV_CODEC_ID_MJPEG)
IMAGEAUTO_DEMUXER(jpegls, AV_CODEC_ID_JPEGLS)
+IMAGEAUTO_DEMUXER(jpegxl, AV_CODEC_ID_JPEGXL)
IMAGEAUTO_DEMUXER(pam, AV_CODEC_ID_PAM)
IMAGEAUTO_DEMUXER(pbm, AV_CODEC_ID_PBM)
IMAGEAUTO_DEMUXER(pcx, AV_CODEC_ID_PCX)
diff --git a/libavformat/img2enc.c b/libavformat/img2enc.c
index 62202de9f4..72e64da984 100644
--- a/libavformat/img2enc.c
+++ b/libavformat/img2enc.c
@@ -259,9 +259,9 @@ static const AVClass img2mux_class = {
const AVOutputFormat ff_image2_muxer = {
.name = "image2",
.long_name = NULL_IF_CONFIG_SMALL("image2 sequence"),
- .extensions = "bmp,dpx,exr,jls,jpeg,jpg,ljpg,pam,pbm,pcx,pfm,pgm,pgmyuv,png,"
- "ppm,sgi,tga,tif,tiff,jp2,j2c,j2k,xwd,sun,ras,rs,im1,im8,im24,"
- "sunras,xbm,xface,pix,y",
+ .extensions = "bmp,dpx,exr,jls,jpeg,jpg,jxl,ljpg,pam,pbm,pcx,pfm,pgm,pgmyuv,"
+ "png,ppm,sgi,tga,tif,tiff,jp2,j2c,j2k,xwd,sun,ras,rs,im1,im8,"
+ "im24,sunras,xbm,xface,pix,y",
.priv_data_size = sizeof(VideoMuxData),
.video_codec = AV_CODEC_ID_MJPEG,
.write_header = write_header,
diff --git a/libavformat/mov.c b/libavformat/mov.c
index 57c67e3aac..3a23f5dad8 100644
--- a/libavformat/mov.c
+++ b/libavformat/mov.c
@@ -7437,6 +7437,7 @@ static int mov_probe(const AVProbeData *p)
if (tag == MKTAG('f','t','y','p') &&
( AV_RL32(p->buf + offset + 8) == MKTAG('j','p','2',' ')
|| AV_RL32(p->buf + offset + 8) == MKTAG('j','p','x',' ')
+ || AV_RL32(p->buf + offset + 8) == MKTAG('j','x','l',' ')
)) {
score = FFMAX(score, 5);
} else {
--
2.33.1
More information about the ffmpeg-devel
mailing list