Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 94502d8

Browse files
jcupittkleisauke
andauthored
add support for multipage jxl (libvips#4231)
* add support for multipage jxl Multipage JXL images are just animations with the duration param set to 0xffffffff. This PR detects animations with this magick value for all frames and represents them as libvips multipage images (no duration metadata). A matching change in jxlsave sets duration to 0xffffffff for multipage images. * Update libvips/foreign/jxlsave.c Co-authored-by: Kleis Auke Wolthuizen <[email protected]> --------- Co-authored-by: Kleis Auke Wolthuizen <[email protected]>
1 parent 8dd0510 commit 94502d8

File tree

4 files changed

+82
-81
lines changed

4 files changed

+82
-81
lines changed

ChangeLog

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
8.16.1
2+
3+
- support multipage JXL
4+
15
10/10/24 8.16.0
26

37
- allow small offsets for the PDF magic string [project0]

libvips/foreign/jxlload.c

Lines changed: 57 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,12 @@ typedef struct _VipsForeignLoadJxl {
104104
uint8_t *xmp_data;
105105

106106
int frame_count;
107-
int *delay;
108-
int delay_count;
107+
GArray *delay;
108+
109+
/* JXL multipage and animated images are the same, but multipage has
110+
* all the frame delays set to -1 (duration 0xffffffff).
111+
*/
112+
gboolean is_animated;
109113

110114
/* The current accumulated frame as a VipsImage. These are the pixels
111115
* we send to the output. It's a info->xsize * info->ysize memory
@@ -166,7 +170,7 @@ vips_foreign_load_jxl_dispose(GObject *gobject)
166170
VIPS_FREE(jxl->icc_data);
167171
VIPS_FREE(jxl->exif_data);
168172
VIPS_FREE(jxl->xmp_data);
169-
VIPS_FREE(jxl->delay);
173+
VIPS_FREEF(g_array_unref, jxl->delay);
170174
VIPS_UNREF(jxl->frame);
171175
VIPS_UNREF(jxl->source);
172176

@@ -492,8 +496,7 @@ vips_foreign_load_jxl_read_frame(VipsForeignLoadJxl *jxl, VipsImage *frame,
492496
int skip = frame_no - jxl->frame_no - 1;
493497
if (skip > 0) {
494498
#ifdef DEBUG_VERBOSE
495-
printf("vips_foreign_load_jxl_read_frame: skipping %d frames\n",
496-
skip);
499+
printf("vips_foreign_load_jxl_read_frame: skipping %d frames\n", skip);
497500
#endif /*DEBUG_VERBOSE*/
498501
JxlDecoderSkipFrames(jxl->decoder, skip);
499502
jxl->frame_no += skip;
@@ -504,8 +507,7 @@ vips_foreign_load_jxl_read_frame(VipsForeignLoadJxl *jxl, VipsImage *frame,
504507
do {
505508
switch ((status = vips_foreign_load_jxl_process(jxl))) {
506509
case JXL_DEC_ERROR:
507-
vips_foreign_load_jxl_error(jxl,
508-
"JxlDecoderProcessInput");
510+
vips_foreign_load_jxl_error(jxl, "JxlDecoderProcessInput");
509511
return -1;
510512

511513
case JXL_DEC_FRAME:
@@ -514,24 +516,19 @@ vips_foreign_load_jxl_read_frame(VipsForeignLoadJxl *jxl, VipsImage *frame,
514516

515517
case JXL_DEC_NEED_IMAGE_OUT_BUFFER:
516518
if (JxlDecoderImageOutBufferSize(jxl->decoder,
517-
&jxl->format,
518-
&buffer_size)) {
519+
&jxl->format, &buffer_size)) {
519520
vips_foreign_load_jxl_error(jxl,
520521
"JxlDecoderImageOutBufferSize");
521522
return -1;
522523
}
523-
if (buffer_size !=
524-
VIPS_IMAGE_SIZEOF_IMAGE(frame)) {
525-
vips_error(class->nickname,
526-
"%s", _("bad buffer size"));
524+
if (buffer_size != VIPS_IMAGE_SIZEOF_IMAGE(frame)) {
525+
vips_error(class->nickname, "%s", _("bad buffer size"));
527526
return -1;
528527
}
529-
if (JxlDecoderSetImageOutBuffer(jxl->decoder,
530-
&jxl->format,
528+
if (JxlDecoderSetImageOutBuffer(jxl->decoder, &jxl->format,
531529
VIPS_IMAGE_ADDR(frame, 0, 0),
532530
VIPS_IMAGE_SIZEOF_IMAGE(frame))) {
533-
vips_foreign_load_jxl_error(jxl,
534-
"JxlDecoderSetImageOutBuffer");
531+
vips_foreign_load_jxl_error(jxl, "JxlDecoderSetImageOutBuffer");
535532
return -1;
536533
}
537534
break;
@@ -551,8 +548,7 @@ vips_foreign_load_jxl_read_frame(VipsForeignLoadJxl *jxl, VipsImage *frame,
551548

552549
/* We didn't find the required frame
553550
*/
554-
vips_error(class->nickname,
555-
"%s", _("not enough frames"));
551+
vips_error(class->nickname, "%s", _("not enough frames"));
556552
return -1;
557553
}
558554

@@ -633,8 +629,7 @@ vips_foreign_load_jxl_set_header(VipsForeignLoadJxl *jxl, VipsImage *out)
633629

634630
if (jxl->info.xsize >= VIPS_MAX_COORD ||
635631
jxl->info.ysize >= VIPS_MAX_COORD) {
636-
vips_error(class->nickname,
637-
"%s", _("image size out of bounds"));
632+
vips_error(class->nickname, "%s", _("image size out of bounds"));
638633
return -1;
639634
}
640635

@@ -704,27 +699,26 @@ vips_foreign_load_jxl_set_header(VipsForeignLoadJxl *jxl, VipsImage *out)
704699
if (jxl->page < 0 ||
705700
jxl->n <= 0 ||
706701
jxl->page + jxl->n > jxl->frame_count) {
707-
vips_error(class->nickname,
708-
"%s", _("bad page number"));
702+
vips_error(class->nickname, "%s", _("bad page number"));
709703
return -1;
710704
}
711705

712706
vips_image_set_int(out, VIPS_META_N_PAGES, jxl->frame_count);
713707

714708
if (jxl->n > 1)
715-
vips_image_set_int(out,
716-
VIPS_META_PAGE_HEIGHT, jxl->info.ysize);
709+
vips_image_set_int(out, VIPS_META_PAGE_HEIGHT, jxl->info.ysize);
717710

718-
g_assert(jxl->delay_count >= jxl->frame_count);
719-
vips_image_set_array_int(out,
720-
"delay", jxl->delay, jxl->frame_count);
711+
if (jxl->is_animated) {
712+
int *delay = (int *) jxl->delay->data;
721713

722-
/* gif uses centiseconds for delays
723-
*/
724-
vips_image_set_int(out, "gif-delay",
725-
VIPS_RINT(jxl->delay[0] / 10.0));
714+
vips_image_set_array_int(out, "delay", delay, jxl->frame_count);
715+
716+
/* gif uses centiseconds for delays
717+
*/
718+
vips_image_set_int(out, "gif-delay", VIPS_RINT(delay[0] / 10.0));
726719

727-
vips_image_set_int(out, "loop", jxl->info.animation.num_loops);
720+
vips_image_set_int(out, "loop", jxl->info.animation.num_loops);
721+
}
728722
}
729723
else {
730724
jxl->n = 1;
@@ -759,32 +753,28 @@ vips_foreign_load_jxl_set_header(VipsForeignLoadJxl *jxl, VipsImage *out)
759753
if (jxl->icc_data &&
760754
jxl->icc_size > 0) {
761755
vips_image_set_blob(out, VIPS_META_ICC_NAME,
762-
(VipsCallbackFn) vips_area_free_cb,
763-
jxl->icc_data, jxl->icc_size);
756+
(VipsCallbackFn) vips_area_free_cb, jxl->icc_data, jxl->icc_size);
764757
jxl->icc_data = NULL;
765758
jxl->icc_size = 0;
766759
}
767760

768761
if (jxl->exif_data &&
769762
jxl->exif_size > 0) {
770763
vips_image_set_blob(out, VIPS_META_EXIF_NAME,
771-
(VipsCallbackFn) vips_area_free_cb,
772-
jxl->exif_data, jxl->exif_size);
764+
(VipsCallbackFn) vips_area_free_cb, jxl->exif_data, jxl->exif_size);
773765
jxl->exif_data = NULL;
774766
jxl->exif_size = 0;
775767
}
776768

777769
if (jxl->xmp_data &&
778770
jxl->xmp_size > 0) {
779771
vips_image_set_blob(out, VIPS_META_XMP_NAME,
780-
(VipsCallbackFn) vips_area_free_cb,
781-
jxl->xmp_data, jxl->xmp_size);
772+
(VipsCallbackFn) vips_area_free_cb, jxl->xmp_data, jxl->xmp_size);
782773
jxl->xmp_data = NULL;
783774
jxl->xmp_size = 0;
784775
}
785776

786-
vips_image_set_int(out,
787-
VIPS_META_ORIENTATION, jxl->info.orientation);
777+
vips_image_set_int(out, VIPS_META_ORIENTATION, jxl->info.orientation);
788778

789779
vips_image_set_int(out, VIPS_META_BITS_PER_SAMPLE,
790780
jxl->info.bits_per_sample);
@@ -795,7 +785,6 @@ vips_foreign_load_jxl_set_header(VipsForeignLoadJxl *jxl, VipsImage *out)
795785
static int
796786
vips_foreign_load_jxl_header(VipsForeignLoad *load)
797787
{
798-
VipsObjectClass *class = VIPS_OBJECT_GET_CLASS(load);
799788
VipsForeignLoadJxl *jxl = (VipsForeignLoadJxl *) load;
800789

801790
JxlDecoderStatus status;
@@ -815,8 +804,7 @@ vips_foreign_load_jxl_header(VipsForeignLoad *load)
815804
JXL_DEC_BASIC_INFO |
816805
JXL_DEC_BOX |
817806
JXL_DEC_FRAME)) {
818-
vips_foreign_load_jxl_error(jxl,
819-
"JxlDecoderSubscribeEvents");
807+
vips_foreign_load_jxl_error(jxl, "JxlDecoderSubscribeEvents");
820808
return -1;
821809
}
822810

@@ -825,8 +813,7 @@ vips_foreign_load_jxl_header(VipsForeignLoad *load)
825813

826814
if (vips_foreign_load_jxl_fill_input(jxl, 0) < 0)
827815
return -1;
828-
JxlDecoderSetInput(jxl->decoder,
829-
jxl->input_buffer, jxl->bytes_in_buffer);
816+
JxlDecoderSetInput(jxl->decoder, jxl->input_buffer, jxl->bytes_in_buffer);
830817

831818
jxl->frame_count = 0;
832819

@@ -919,21 +906,17 @@ vips_foreign_load_jxl_header(VipsForeignLoad *load)
919906
#ifndef HAVE_LIBJXL_0_9
920907
&jxl->format,
921908
#endif
922-
JXL_COLOR_PROFILE_TARGET_DATA,
923-
&jxl->icc_size)) {
909+
JXL_COLOR_PROFILE_TARGET_DATA, &jxl->icc_size)) {
924910
vips_foreign_load_jxl_error(jxl,
925911
"JxlDecoderGetICCProfileSize");
926912
return -1;
927913
}
928914

929915
#ifdef DEBUG
930-
printf(
931-
"vips_foreign_load_jxl_header: "
932-
"%zd byte profile\n",
916+
printf("vips_foreign_load_jxl_header: %zd byte profile\n",
933917
jxl->icc_size);
934918
#endif /*DEBUG*/
935-
if (!(jxl->icc_data = vips_malloc(NULL,
936-
jxl->icc_size)))
919+
if (!(jxl->icc_data = vips_malloc(NULL, jxl->icc_size)))
937920
return -1;
938921

939922
if (JxlDecoderGetColorAsICCProfile(jxl->decoder,
@@ -950,42 +933,41 @@ vips_foreign_load_jxl_header(VipsForeignLoad *load)
950933

951934
case JXL_DEC_FRAME:
952935
if (JxlDecoderGetFrameHeader(jxl->decoder, &h) != JXL_DEC_SUCCESS) {
953-
vips_foreign_load_jxl_error(jxl,
954-
"JxlDecoderGetFrameHeader");
936+
vips_foreign_load_jxl_error(jxl, "JxlDecoderGetFrameHeader");
955937
return -1;
956938
}
957939

958940
if (jxl->info.have_animation) {
959-
if (jxl->delay_count <= jxl->frame_count) {
960-
jxl->delay_count += 128;
961-
int *new_delay = g_try_realloc(jxl->delay,
962-
jxl->delay_count * sizeof(int));
963-
if (!new_delay) {
964-
vips_error(class->nickname, "%s", _("out of memory"));
965-
return -1;
966-
}
967-
jxl->delay = new_delay;
968-
}
969-
970-
jxl->delay[jxl->frame_count] = VIPS_RINT(1000.0 * h.duration *
971-
jxl->info.animation.tps_denominator /
972-
jxl->info.animation.tps_numerator);
941+
// tick duration in seconds
942+
double tick = (double) jxl->info.animation.tps_denominator /
943+
jxl->info.animation.tps_numerator;
944+
// this duration in ms
945+
int ms = VIPS_RINT(1000.0 * h.duration * tick);
946+
// h.duration of 0xffffffff is used for multipage JXL ... map
947+
// this to -1 in delay
948+
int duration = h.duration == 0xffffffff ? -1 : ms;
949+
950+
jxl->delay = g_array_append_vals(jxl->delay, &duration, 1);
973951
}
974952

975953
jxl->frame_count++;
976954

977-
/* This is the last frame, we can stop right here
978-
*/
979-
if (h.is_last || !jxl->info.have_animation)
980-
status = JXL_DEC_SUCCESS;
981-
982955
break;
983956

984957
default:
985958
break;
986959
}
987960
} while (status != JXL_DEC_SUCCESS);
988961

962+
/* Detect JXL multipage (rather than animated).
963+
*/
964+
int *delay = (int *) jxl->delay->data;
965+
for (int i = 0; i < jxl->delay->len; i++)
966+
if (delay[i] != -1) {
967+
jxl->is_animated = TRUE;
968+
break;
969+
}
970+
989971
/* Flush box data if any
990972
*/
991973
if (vips_foreign_load_jxl_release_box_buffer(jxl))
@@ -1113,6 +1095,7 @@ static void
11131095
vips_foreign_load_jxl_init(VipsForeignLoadJxl *jxl)
11141096
{
11151097
jxl->n = 1;
1098+
jxl->delay = g_array_new(FALSE, FALSE, sizeof(int));
11161099
}
11171100

11181101
typedef struct _VipsForeignLoadJxlFile {

libvips/foreign/jxlsave.c

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,11 @@ typedef struct _VipsForeignSaveJxl {
8787
gboolean lossless;
8888
int Q;
8989

90+
/* JXL multipage and animated images are the same, but multipage has
91+
* all the frame delays set to -1 (duration 0xffffffff).
92+
*/
93+
gboolean is_animated;
94+
9095
/* Animated jxl options.
9196
*/
9297
int gif_delay;
@@ -319,7 +324,9 @@ vips_foreign_save_jxl_add_frame(VipsForeignSaveJxl *jxl)
319324
JxlFrameHeader header;
320325
memset(&header, 0, sizeof(JxlFrameHeader));
321326

322-
if (jxl->delay && jxl->page_number < jxl->delay_length)
327+
if (!jxl->is_animated)
328+
header.duration = 0xffffffff;
329+
else if (jxl->delay && jxl->page_number < jxl->delay_length)
323330
header.duration = jxl->delay[jxl->page_number];
324331
else
325332
header.duration = jxl->gif_delay * 10;
@@ -593,6 +600,8 @@ vips_foreign_save_jxl_build(VipsObject *object)
593600
if (vips_image_get_typeof(in, "loop"))
594601
vips_image_get_int(in, "loop", &num_loops);
595602

603+
// libjxl uses "have_animation" for multipage images too, but sets
604+
// duration to 0xffffffff
596605
jxl->info.have_animation = TRUE;
597606
jxl->info.animation.tps_numerator = 1000;
598607
jxl->info.animation.tps_denominator = 1;
@@ -670,10 +679,8 @@ vips_foreign_save_jxl_build(VipsObject *object)
670679
jxl->format.num_channels < 3);
671680
}
672681

673-
if (JxlEncoderSetColorEncoding(jxl->encoder,
674-
&jxl->color_encoding)) {
675-
vips_foreign_save_jxl_error(jxl,
676-
"JxlEncoderSetColorEncoding");
682+
if (JxlEncoderSetColorEncoding(jxl->encoder, &jxl->color_encoding)) {
683+
vips_foreign_save_jxl_error(jxl, "JxlEncoderSetColorEncoding");
677684
return -1;
678685
}
679686
}
@@ -703,6 +710,13 @@ vips_foreign_save_jxl_build(VipsObject *object)
703710
&jxl->delay, &jxl->delay_length))
704711
return -1;
705712

713+
/* If there's delay metadata, this is an animated image (as opposed to
714+
* a multipage one).
715+
*/
716+
if (vips_image_get_typeof(save->ready, "delay") ||
717+
vips_image_get_typeof(save->ready, "gif-delay"))
718+
jxl->is_animated = TRUE;
719+
706720
/* Force frames with a small or no duration to 100ms
707721
* to be consistent with web browsers and other
708722
* transcoding tools.

meson.build

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
project('vips', 'c', 'cpp',
2-
version: '8.16.0',
2+
version: '8.16.1',
33
meson_version: '>=0.55',
44
default_options: [
55
# this is what glib uses (one of our required deps), so we use it too
@@ -23,7 +23,7 @@ version_patch = version_parts[2]
2323
# binary interface changed: increment current, reset revision to 0
2424
# binary interface changes backwards compatible?: increment age
2525
# binary interface changes not backwards compatible?: reset age to 0
26-
library_revision = 0
26+
library_revision = 1
2727
library_current = 60
2828
library_age = 18
2929
library_version = '@0@.@1@.@2@'.format(library_current - library_age, library_age, library_revision)

0 commit comments

Comments
 (0)