From 94502d8928f6838d0af57f2fa63bb54d51a85fc9 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 31 Oct 2024 15:17:45 +0000 Subject: [PATCH 01/43] add support for multipage jxl (#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 --------- Co-authored-by: Kleis Auke Wolthuizen --- ChangeLog | 4 ++ libvips/foreign/jxlload.c | 131 +++++++++++++++++--------------------- libvips/foreign/jxlsave.c | 24 +++++-- meson.build | 4 +- 4 files changed, 82 insertions(+), 81 deletions(-) diff --git a/ChangeLog b/ChangeLog index a604cfec50..bc230b2ef7 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ +8.16.1 + +- support multipage JXL + 10/10/24 8.16.0 - allow small offsets for the PDF magic string [project0] diff --git a/libvips/foreign/jxlload.c b/libvips/foreign/jxlload.c index 8e4bd0b538..59c09f189c 100644 --- a/libvips/foreign/jxlload.c +++ b/libvips/foreign/jxlload.c @@ -104,8 +104,12 @@ typedef struct _VipsForeignLoadJxl { uint8_t *xmp_data; int frame_count; - int *delay; - int delay_count; + GArray *delay; + + /* JXL multipage and animated images are the same, but multipage has + * all the frame delays set to -1 (duration 0xffffffff). + */ + gboolean is_animated; /* The current accumulated frame as a VipsImage. These are the pixels * we send to the output. It's a info->xsize * info->ysize memory @@ -166,7 +170,7 @@ vips_foreign_load_jxl_dispose(GObject *gobject) VIPS_FREE(jxl->icc_data); VIPS_FREE(jxl->exif_data); VIPS_FREE(jxl->xmp_data); - VIPS_FREE(jxl->delay); + VIPS_FREEF(g_array_unref, jxl->delay); VIPS_UNREF(jxl->frame); VIPS_UNREF(jxl->source); @@ -492,8 +496,7 @@ vips_foreign_load_jxl_read_frame(VipsForeignLoadJxl *jxl, VipsImage *frame, int skip = frame_no - jxl->frame_no - 1; if (skip > 0) { #ifdef DEBUG_VERBOSE - printf("vips_foreign_load_jxl_read_frame: skipping %d frames\n", - skip); + printf("vips_foreign_load_jxl_read_frame: skipping %d frames\n", skip); #endif /*DEBUG_VERBOSE*/ JxlDecoderSkipFrames(jxl->decoder, skip); jxl->frame_no += skip; @@ -504,8 +507,7 @@ vips_foreign_load_jxl_read_frame(VipsForeignLoadJxl *jxl, VipsImage *frame, do { switch ((status = vips_foreign_load_jxl_process(jxl))) { case JXL_DEC_ERROR: - vips_foreign_load_jxl_error(jxl, - "JxlDecoderProcessInput"); + vips_foreign_load_jxl_error(jxl, "JxlDecoderProcessInput"); return -1; case JXL_DEC_FRAME: @@ -514,24 +516,19 @@ vips_foreign_load_jxl_read_frame(VipsForeignLoadJxl *jxl, VipsImage *frame, case JXL_DEC_NEED_IMAGE_OUT_BUFFER: if (JxlDecoderImageOutBufferSize(jxl->decoder, - &jxl->format, - &buffer_size)) { + &jxl->format, &buffer_size)) { vips_foreign_load_jxl_error(jxl, "JxlDecoderImageOutBufferSize"); return -1; } - if (buffer_size != - VIPS_IMAGE_SIZEOF_IMAGE(frame)) { - vips_error(class->nickname, - "%s", _("bad buffer size")); + if (buffer_size != VIPS_IMAGE_SIZEOF_IMAGE(frame)) { + vips_error(class->nickname, "%s", _("bad buffer size")); return -1; } - if (JxlDecoderSetImageOutBuffer(jxl->decoder, - &jxl->format, + if (JxlDecoderSetImageOutBuffer(jxl->decoder, &jxl->format, VIPS_IMAGE_ADDR(frame, 0, 0), VIPS_IMAGE_SIZEOF_IMAGE(frame))) { - vips_foreign_load_jxl_error(jxl, - "JxlDecoderSetImageOutBuffer"); + vips_foreign_load_jxl_error(jxl, "JxlDecoderSetImageOutBuffer"); return -1; } break; @@ -551,8 +548,7 @@ vips_foreign_load_jxl_read_frame(VipsForeignLoadJxl *jxl, VipsImage *frame, /* We didn't find the required frame */ - vips_error(class->nickname, - "%s", _("not enough frames")); + vips_error(class->nickname, "%s", _("not enough frames")); return -1; } @@ -633,8 +629,7 @@ vips_foreign_load_jxl_set_header(VipsForeignLoadJxl *jxl, VipsImage *out) if (jxl->info.xsize >= VIPS_MAX_COORD || jxl->info.ysize >= VIPS_MAX_COORD) { - vips_error(class->nickname, - "%s", _("image size out of bounds")); + vips_error(class->nickname, "%s", _("image size out of bounds")); return -1; } @@ -704,27 +699,26 @@ vips_foreign_load_jxl_set_header(VipsForeignLoadJxl *jxl, VipsImage *out) if (jxl->page < 0 || jxl->n <= 0 || jxl->page + jxl->n > jxl->frame_count) { - vips_error(class->nickname, - "%s", _("bad page number")); + vips_error(class->nickname, "%s", _("bad page number")); return -1; } vips_image_set_int(out, VIPS_META_N_PAGES, jxl->frame_count); if (jxl->n > 1) - vips_image_set_int(out, - VIPS_META_PAGE_HEIGHT, jxl->info.ysize); + vips_image_set_int(out, VIPS_META_PAGE_HEIGHT, jxl->info.ysize); - g_assert(jxl->delay_count >= jxl->frame_count); - vips_image_set_array_int(out, - "delay", jxl->delay, jxl->frame_count); + if (jxl->is_animated) { + int *delay = (int *) jxl->delay->data; - /* gif uses centiseconds for delays - */ - vips_image_set_int(out, "gif-delay", - VIPS_RINT(jxl->delay[0] / 10.0)); + vips_image_set_array_int(out, "delay", delay, jxl->frame_count); + + /* gif uses centiseconds for delays + */ + vips_image_set_int(out, "gif-delay", VIPS_RINT(delay[0] / 10.0)); - vips_image_set_int(out, "loop", jxl->info.animation.num_loops); + vips_image_set_int(out, "loop", jxl->info.animation.num_loops); + } } else { jxl->n = 1; @@ -759,8 +753,7 @@ vips_foreign_load_jxl_set_header(VipsForeignLoadJxl *jxl, VipsImage *out) if (jxl->icc_data && jxl->icc_size > 0) { vips_image_set_blob(out, VIPS_META_ICC_NAME, - (VipsCallbackFn) vips_area_free_cb, - jxl->icc_data, jxl->icc_size); + (VipsCallbackFn) vips_area_free_cb, jxl->icc_data, jxl->icc_size); jxl->icc_data = NULL; jxl->icc_size = 0; } @@ -768,8 +761,7 @@ vips_foreign_load_jxl_set_header(VipsForeignLoadJxl *jxl, VipsImage *out) if (jxl->exif_data && jxl->exif_size > 0) { vips_image_set_blob(out, VIPS_META_EXIF_NAME, - (VipsCallbackFn) vips_area_free_cb, - jxl->exif_data, jxl->exif_size); + (VipsCallbackFn) vips_area_free_cb, jxl->exif_data, jxl->exif_size); jxl->exif_data = NULL; jxl->exif_size = 0; } @@ -777,14 +769,12 @@ vips_foreign_load_jxl_set_header(VipsForeignLoadJxl *jxl, VipsImage *out) if (jxl->xmp_data && jxl->xmp_size > 0) { vips_image_set_blob(out, VIPS_META_XMP_NAME, - (VipsCallbackFn) vips_area_free_cb, - jxl->xmp_data, jxl->xmp_size); + (VipsCallbackFn) vips_area_free_cb, jxl->xmp_data, jxl->xmp_size); jxl->xmp_data = NULL; jxl->xmp_size = 0; } - vips_image_set_int(out, - VIPS_META_ORIENTATION, jxl->info.orientation); + vips_image_set_int(out, VIPS_META_ORIENTATION, jxl->info.orientation); vips_image_set_int(out, VIPS_META_BITS_PER_SAMPLE, jxl->info.bits_per_sample); @@ -795,7 +785,6 @@ vips_foreign_load_jxl_set_header(VipsForeignLoadJxl *jxl, VipsImage *out) static int vips_foreign_load_jxl_header(VipsForeignLoad *load) { - VipsObjectClass *class = VIPS_OBJECT_GET_CLASS(load); VipsForeignLoadJxl *jxl = (VipsForeignLoadJxl *) load; JxlDecoderStatus status; @@ -815,8 +804,7 @@ vips_foreign_load_jxl_header(VipsForeignLoad *load) JXL_DEC_BASIC_INFO | JXL_DEC_BOX | JXL_DEC_FRAME)) { - vips_foreign_load_jxl_error(jxl, - "JxlDecoderSubscribeEvents"); + vips_foreign_load_jxl_error(jxl, "JxlDecoderSubscribeEvents"); return -1; } @@ -825,8 +813,7 @@ vips_foreign_load_jxl_header(VipsForeignLoad *load) if (vips_foreign_load_jxl_fill_input(jxl, 0) < 0) return -1; - JxlDecoderSetInput(jxl->decoder, - jxl->input_buffer, jxl->bytes_in_buffer); + JxlDecoderSetInput(jxl->decoder, jxl->input_buffer, jxl->bytes_in_buffer); jxl->frame_count = 0; @@ -919,21 +906,17 @@ vips_foreign_load_jxl_header(VipsForeignLoad *load) #ifndef HAVE_LIBJXL_0_9 &jxl->format, #endif - JXL_COLOR_PROFILE_TARGET_DATA, - &jxl->icc_size)) { + JXL_COLOR_PROFILE_TARGET_DATA, &jxl->icc_size)) { vips_foreign_load_jxl_error(jxl, "JxlDecoderGetICCProfileSize"); return -1; } #ifdef DEBUG - printf( - "vips_foreign_load_jxl_header: " - "%zd byte profile\n", + printf("vips_foreign_load_jxl_header: %zd byte profile\n", jxl->icc_size); #endif /*DEBUG*/ - if (!(jxl->icc_data = vips_malloc(NULL, - jxl->icc_size))) + if (!(jxl->icc_data = vips_malloc(NULL, jxl->icc_size))) return -1; if (JxlDecoderGetColorAsICCProfile(jxl->decoder, @@ -950,35 +933,25 @@ vips_foreign_load_jxl_header(VipsForeignLoad *load) case JXL_DEC_FRAME: if (JxlDecoderGetFrameHeader(jxl->decoder, &h) != JXL_DEC_SUCCESS) { - vips_foreign_load_jxl_error(jxl, - "JxlDecoderGetFrameHeader"); + vips_foreign_load_jxl_error(jxl, "JxlDecoderGetFrameHeader"); return -1; } if (jxl->info.have_animation) { - if (jxl->delay_count <= jxl->frame_count) { - jxl->delay_count += 128; - int *new_delay = g_try_realloc(jxl->delay, - jxl->delay_count * sizeof(int)); - if (!new_delay) { - vips_error(class->nickname, "%s", _("out of memory")); - return -1; - } - jxl->delay = new_delay; - } - - jxl->delay[jxl->frame_count] = VIPS_RINT(1000.0 * h.duration * - jxl->info.animation.tps_denominator / - jxl->info.animation.tps_numerator); + // tick duration in seconds + double tick = (double) jxl->info.animation.tps_denominator / + jxl->info.animation.tps_numerator; + // this duration in ms + int ms = VIPS_RINT(1000.0 * h.duration * tick); + // h.duration of 0xffffffff is used for multipage JXL ... map + // this to -1 in delay + int duration = h.duration == 0xffffffff ? -1 : ms; + + jxl->delay = g_array_append_vals(jxl->delay, &duration, 1); } jxl->frame_count++; - /* This is the last frame, we can stop right here - */ - if (h.is_last || !jxl->info.have_animation) - status = JXL_DEC_SUCCESS; - break; default: @@ -986,6 +959,15 @@ vips_foreign_load_jxl_header(VipsForeignLoad *load) } } while (status != JXL_DEC_SUCCESS); + /* Detect JXL multipage (rather than animated). + */ + int *delay = (int *) jxl->delay->data; + for (int i = 0; i < jxl->delay->len; i++) + if (delay[i] != -1) { + jxl->is_animated = TRUE; + break; + } + /* Flush box data if any */ if (vips_foreign_load_jxl_release_box_buffer(jxl)) @@ -1113,6 +1095,7 @@ static void vips_foreign_load_jxl_init(VipsForeignLoadJxl *jxl) { jxl->n = 1; + jxl->delay = g_array_new(FALSE, FALSE, sizeof(int)); } typedef struct _VipsForeignLoadJxlFile { diff --git a/libvips/foreign/jxlsave.c b/libvips/foreign/jxlsave.c index de32dc40b5..83011cbd67 100644 --- a/libvips/foreign/jxlsave.c +++ b/libvips/foreign/jxlsave.c @@ -87,6 +87,11 @@ typedef struct _VipsForeignSaveJxl { gboolean lossless; int Q; + /* JXL multipage and animated images are the same, but multipage has + * all the frame delays set to -1 (duration 0xffffffff). + */ + gboolean is_animated; + /* Animated jxl options. */ int gif_delay; @@ -319,7 +324,9 @@ vips_foreign_save_jxl_add_frame(VipsForeignSaveJxl *jxl) JxlFrameHeader header; memset(&header, 0, sizeof(JxlFrameHeader)); - if (jxl->delay && jxl->page_number < jxl->delay_length) + if (!jxl->is_animated) + header.duration = 0xffffffff; + else if (jxl->delay && jxl->page_number < jxl->delay_length) header.duration = jxl->delay[jxl->page_number]; else header.duration = jxl->gif_delay * 10; @@ -593,6 +600,8 @@ vips_foreign_save_jxl_build(VipsObject *object) if (vips_image_get_typeof(in, "loop")) vips_image_get_int(in, "loop", &num_loops); + // libjxl uses "have_animation" for multipage images too, but sets + // duration to 0xffffffff jxl->info.have_animation = TRUE; jxl->info.animation.tps_numerator = 1000; jxl->info.animation.tps_denominator = 1; @@ -670,10 +679,8 @@ vips_foreign_save_jxl_build(VipsObject *object) jxl->format.num_channels < 3); } - if (JxlEncoderSetColorEncoding(jxl->encoder, - &jxl->color_encoding)) { - vips_foreign_save_jxl_error(jxl, - "JxlEncoderSetColorEncoding"); + if (JxlEncoderSetColorEncoding(jxl->encoder, &jxl->color_encoding)) { + vips_foreign_save_jxl_error(jxl, "JxlEncoderSetColorEncoding"); return -1; } } @@ -703,6 +710,13 @@ vips_foreign_save_jxl_build(VipsObject *object) &jxl->delay, &jxl->delay_length)) return -1; + /* If there's delay metadata, this is an animated image (as opposed to + * a multipage one). + */ + if (vips_image_get_typeof(save->ready, "delay") || + vips_image_get_typeof(save->ready, "gif-delay")) + jxl->is_animated = TRUE; + /* Force frames with a small or no duration to 100ms * to be consistent with web browsers and other * transcoding tools. diff --git a/meson.build b/meson.build index 8a7be996f0..21797c1b6f 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('vips', 'c', 'cpp', - version: '8.16.0', + version: '8.16.1', meson_version: '>=0.55', default_options: [ # this is what glib uses (one of our required deps), so we use it too @@ -23,7 +23,7 @@ version_patch = version_parts[2] # binary interface changed: increment current, reset revision to 0 # binary interface changes backwards compatible?: increment age # binary interface changes not backwards compatible?: reset age to 0 -library_revision = 0 +library_revision = 1 library_current = 60 library_age = 18 library_version = '@0@.@1@.@2@'.format(library_current - library_age, library_age, library_revision) From 2ad79077b74f6f0a5801ad23a9b8ddfb7fc6f12a Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Sat, 2 Nov 2024 11:06:17 +0100 Subject: [PATCH 02/43] Improve `--vips-config` for internal dependencies (#4236) Dependencies created using `declare_dependency()` does not have a version number or name. Mirrors commit ec432a5. --- libvips/include/vips/meson.build | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/libvips/include/vips/meson.build b/libvips/include/vips/meson.build index 31dd7adf7e..17bf777c79 100644 --- a/libvips/include/vips/meson.build +++ b/libvips/include/vips/meson.build @@ -106,8 +106,11 @@ foreach _, section : build_summary endforeach foreach _, section : build_features foreach key, arr : section + dep_name = arr[0] found = arr[1].found() - dep_name = found ? arr[1].name() : arr[0] + if found and arr[1].type_name() != 'internal' + dep_name = arr[1].name() + endif dynamic_module = arr.length() > 2 ? ' (dynamic module: @0@)'.format(arr[2]) : '' vips_verbose_config += '@0@ with @1@: @2@@3@'.format(key, dep_name, found, dynamic_module) endforeach From 2aca66dbccb0cf5cecd8272b6065fc0f4691ce2b Mon Sep 17 00:00:00 2001 From: Adam Goode Date: Sun, 3 Nov 2024 07:08:02 -0500 Subject: [PATCH 03/43] Never set the PFM header to little endian (#4242) Never set the PFM header to little endian PFM files are always written in big endian: https://github.com/libvips/libvips/blob/master/libvips/foreign/ppmsave.c#L459 But the header is set to little endian if on a big endian machine (!) https://github.com/libvips/libvips/blob/master/libvips/foreign/ppmsave.c#L414-L415 Fixes #4241. --- ChangeLog | 4 ++ libvips/foreign/jxlload.c | 68 ++++++++++++++++++++++++++++---- libvips/foreign/ppmsave.c | 3 -- libvips/include/vips/resample.h | 2 + libvips/resample/reduce.c | 2 + libvips/resample/reduceh.cpp | 6 +++ libvips/resample/templates.h | 52 ++++++++++++++++++++++++ meson.build | 8 ++-- test/test-suite/test_resample.py | 6 ++- 9 files changed, 134 insertions(+), 17 deletions(-) diff --git a/ChangeLog b/ChangeLog index bc230b2ef7..05927e3190 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ +8.17.0 + +- add Magic Kernel support [akimon658] + 8.16.1 - support multipage JXL diff --git a/libvips/foreign/jxlload.c b/libvips/foreign/jxlload.c index 59c09f189c..c53e8f5ab2 100644 --- a/libvips/foreign/jxlload.c +++ b/libvips/foreign/jxlload.c @@ -439,6 +439,56 @@ vips_foreign_load_jxl_print_format(JxlPixelFormat *format) printf(" endianness = %d\n", format->endianness); printf(" align = %zd\n", format->align); } + +static const char * +vips_foreign_load_jxl_blend_mode(JxlBlendMode blendmode) +{ + switch (blendmode) { + case JXL_BLEND_REPLACE: + return "JXL_BLEND_REPLACE"; + + case JXL_BLEND_ADD: + return "JXL_BLEND_ADD"; + + case JXL_BLEND_BLEND: + return "JXL_BLEND_BLEND"; + + case JXL_BLEND_MULADD: + return "JXL_BLEND_MULADD"; + + case JXL_BLEND_MUL: + return "JXL_BLEND_MUL"; + + default: + return "duration); + printf(" timecode = %u\n", h->timecode); + printf(" name_length = %u\n", h->name_length); + printf(" is_last = %s\n", h->is_last ? "TRUE" : "FALSE"); + printf(" layer_info.have_crop = %s\n", + h->layer_info.have_crop ? "TRUE" : "FALSE"); + printf(" layer_info.crop_x0 = %d\n", h->layer_info.crop_x0); + printf(" layer_info.crop_y0 = %d\n", h->layer_info.crop_y0); + printf(" layer_info.xsize = %u\n", h->layer_info.xsize); + printf(" layer_info.ysize = %u\n", h->layer_info.ysize); + printf(" layer_info.blend_info.blendmode = %s\n", + vips_foreign_load_jxl_blend_mode(h->layer_info.blend_info.blendmode)); + printf(" layer_info.blend_info.source = %u\n", + h->layer_info.blend_info.source); + printf(" layer_info.blend_info.alpha = %u\n", + h->layer_info.blend_info.alpha); + printf(" layer_info.blend_info.clamp = %s\n", + h->layer_info.blend_info.clamp ? "TRUE" : "FALSE"); + printf(" layer_info.save_as_reference = %u\n", + h->layer_info.save_as_reference); +} #endif /*DEBUG*/ static JxlDecoderStatus @@ -455,9 +505,9 @@ vips_foreign_load_jxl_process(VipsForeignLoadJxl *jxl) size_t bytes_remaining; int bytes_read; -#ifdef DEBUG +#ifdef DEBUG_VERBOSE printf("vips_foreign_load_jxl_process: reading ...\n"); -#endif /*DEBUG*/ +#endif /*DEBUG_VERBOSE*/ bytes_remaining = JxlDecoderReleaseInput(jxl->decoder); bytes_read = vips_foreign_load_jxl_fill_input(jxl, bytes_remaining); @@ -498,6 +548,7 @@ vips_foreign_load_jxl_read_frame(VipsForeignLoadJxl *jxl, VipsImage *frame, #ifdef DEBUG_VERBOSE printf("vips_foreign_load_jxl_read_frame: skipping %d frames\n", skip); #endif /*DEBUG_VERBOSE*/ + JxlDecoderSkipFrames(jxl->decoder, skip); jxl->frame_no += skip; } @@ -822,8 +873,7 @@ vips_foreign_load_jxl_header(VipsForeignLoad *load) do { switch ((status = vips_foreign_load_jxl_process(jxl))) { case JXL_DEC_ERROR: - vips_foreign_load_jxl_error(jxl, - "JxlDecoderProcessInput"); + vips_foreign_load_jxl_error(jxl, "JxlDecoderProcessInput"); return -1; case JXL_DEC_BOX: @@ -907,8 +957,7 @@ vips_foreign_load_jxl_header(VipsForeignLoad *load) &jxl->format, #endif JXL_COLOR_PROFILE_TARGET_DATA, &jxl->icc_size)) { - vips_foreign_load_jxl_error(jxl, - "JxlDecoderGetICCProfileSize"); + vips_foreign_load_jxl_error(jxl, "JxlDecoderGetICCProfileSize"); return -1; } @@ -937,6 +986,10 @@ vips_foreign_load_jxl_header(VipsForeignLoad *load) return -1; } +#ifdef DEBUG + vips_foreign_load_jxl_print_frame_header(&h); +#endif /*DEBUG*/ + if (jxl->info.have_animation) { // tick duration in seconds double tick = (double) jxl->info.animation.tps_denominator / @@ -1010,8 +1063,7 @@ vips_foreign_load_jxl_load(VipsForeignLoad *load) JxlDecoderRewind(jxl->decoder); if (JxlDecoderSubscribeEvents(jxl->decoder, - JXL_DEC_FRAME | - JXL_DEC_FULL_IMAGE)) { + JXL_DEC_FRAME | JXL_DEC_FULL_IMAGE)) { vips_foreign_load_jxl_error(jxl, "JxlDecoderSubscribeEvents"); return -1; diff --git a/libvips/foreign/ppmsave.c b/libvips/foreign/ppmsave.c index da3f4dff99..3461a1bc45 100644 --- a/libvips/foreign/ppmsave.c +++ b/libvips/foreign/ppmsave.c @@ -411,9 +411,6 @@ vips_foreign_save_ppm_build(VipsObject *object) !vips_image_get_double(image, "pfm-scale", &scale)) ; - if (vips_amiMSBfirst()) - scale *= -1; - /* Need to be locale independent. */ g_ascii_dtostr(buf, G_ASCII_DTOSTR_BUF_SIZE, scale); diff --git a/libvips/include/vips/resample.h b/libvips/include/vips/resample.h index 7a87972f95..d71a8de509 100644 --- a/libvips/include/vips/resample.h +++ b/libvips/include/vips/resample.h @@ -45,6 +45,8 @@ typedef enum { VIPS_KERNEL_MITCHELL, VIPS_KERNEL_LANCZOS2, VIPS_KERNEL_LANCZOS3, + VIPS_KERNEL_MKS2013, + VIPS_KERNEL_MKS2021, VIPS_KERNEL_LAST } VipsKernel; diff --git a/libvips/resample/reduce.c b/libvips/resample/reduce.c index dd3c1566dd..94fb8088b6 100644 --- a/libvips/resample/reduce.c +++ b/libvips/resample/reduce.c @@ -66,6 +66,8 @@ * @VIPS_KERNEL_MITCHELL: Convolve with a Mitchell kernel. * @VIPS_KERNEL_LANCZOS2: Convolve with a two-lobe Lanczos kernel. * @VIPS_KERNEL_LANCZOS3: Convolve with a three-lobe Lanczos kernel. + * @VIPS_KERNEL_MKS2013: Convolve with Magic Kernel Sharp 2013. + * @VIPS_KERNEL_MKS2021: Convolve with Magic Kernel Sharp 2021. * * The resampling kernels vips supports. See vips_reduce(), for example. */ diff --git a/libvips/resample/reduceh.cpp b/libvips/resample/reduceh.cpp index 3258eea708..4e369c92e9 100644 --- a/libvips/resample/reduceh.cpp +++ b/libvips/resample/reduceh.cpp @@ -125,6 +125,12 @@ vips_reduce_get_points(VipsKernel kernel, double shrink) case VIPS_KERNEL_LANCZOS3: return 2 * rint(3 * shrink) + 1; + case VIPS_KERNEL_MKS2013: + return 2 * rint(3 * shrink) + 1; + + case VIPS_KERNEL_MKS2021: + return 2 * rint(5 * shrink) + 1; + default: g_assert_not_reached(); return 0; diff --git a/libvips/resample/templates.h b/libvips/resample/templates.h index 49a56b5ae5..30e28af739 100644 --- a/libvips/resample/templates.h +++ b/libvips/resample/templates.h @@ -402,6 +402,48 @@ double inline filter(double x) return 0.0; } +template <> +double inline filter(double x) +{ + if (x < 0.0) + x = -x; + + if (x >= 2.5) + return 0.0; + + if (x >= 1.5) + return (x - 5.0 / 2.0) * (x - 5.0 / 2.0) / -8.0; + + if (x >= 0.5) + return (4.0 * x * x - 11.0 * x + 7.0) / 4.0; + + return 17.0 / 16.0 - 7.0 * x * x / 4.0; +} + +template <> +double inline filter(double x) +{ + if (x < 0.0) + x = -x; + + if (x >= 4.5) + return 0.0; + + if (x >= 3.5) + return (4.0 * x * x - 36.0 * x + 81.0) / -1152.0; + + if (x >= 2.5) + return (4.0 * x * x - 27.0 * x + 45.0) / 144.0; + + if (x >= 1.5) + return (24.0 * x * x - 113.0 * x + 130.0) / -144.0; + + if (x >= 0.5) + return (140.0 * x * x - 379.0 * x + 239.0) / 144.0; + + return 577.0 / 576.0 - 239.0 * x * x / 144.0; +} + /* Given an x in [0,1] (we can have x == 1 when building tables), * calculate c0 .. c(@n_points), the coefficients. This is called * from the interpolator as well as from the table builder. @@ -469,6 +511,16 @@ vips_reduce_make_mask(T *c, VipsKernel kernel, const int n_points, filter, shrink, x); break; + case VIPS_KERNEL_MKS2013: + calculate_coefficients(c, n_points, + filter, shrink, x); + break; + + case VIPS_KERNEL_MKS2021: + calculate_coefficients(c, n_points, + filter, shrink, x); + break; + default: g_assert_not_reached(); break; diff --git a/meson.build b/meson.build index 21797c1b6f..7f9a94d30d 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('vips', 'c', 'cpp', - version: '8.16.1', + version: '8.17.0', meson_version: '>=0.55', default_options: [ # this is what glib uses (one of our required deps), so we use it too @@ -23,9 +23,9 @@ version_patch = version_parts[2] # binary interface changed: increment current, reset revision to 0 # binary interface changes backwards compatible?: increment age # binary interface changes not backwards compatible?: reset age to 0 -library_revision = 1 -library_current = 60 -library_age = 18 +library_revision = 0 +library_current = 61 +library_age = 19 library_version = '@0@.@1@.@2@'.format(library_current - library_age, library_age, library_revision) darwin_versions = [library_current + 1, '@0@.@1@'.format(library_current + 1, library_revision)] diff --git a/test/test-suite/test_resample.py b/test/test-suite/test_resample.py index 7bb65b2fc5..55c92eb53c 100644 --- a/test/test-suite/test_resample.py +++ b/test/test-suite/test_resample.py @@ -83,7 +83,8 @@ def test_reduce(self): for fac in [1, 1.1, 1.5, 1.999]: for fmt in all_formats: for kernel in ["nearest", "linear", - "cubic", "lanczos2", "lanczos3"]: + "cubic", "lanczos2", + "lanczos3", "mks2013", "mks2021"]: x = im.cast(fmt) r = x.reduce(fac, fac, kernel=kernel) d = abs(r.avg() - im.avg()) @@ -93,7 +94,8 @@ def test_reduce(self): for const in [0, 1, 2, 254, 255]: im = (pyvips.Image.black(10, 10) + const).cast("uchar") for kernel in ["nearest", "linear", - "cubic", "lanczos2", "lanczos3"]: + "cubic", "lanczos2", + "lanczos3", "mks2013", "mks2021"]: # print "testing kernel =", kernel # print "testing const =", const shr = im.reduce(2, 2, kernel=kernel) From 9e82dc63df95f80fe64e5f31d91e4e07914c19ed Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sun, 3 Nov 2024 12:11:59 +0000 Subject: [PATCH 04/43] Revert "Never set the PFM header to little endian (#4242)" This reverts commit 2aca66dbccb0cf5cecd8272b6065fc0f4691ce2b. --- ChangeLog | 4 -- libvips/foreign/jxlload.c | 68 ++++---------------------------- libvips/foreign/ppmsave.c | 3 ++ libvips/include/vips/resample.h | 2 - libvips/resample/reduce.c | 2 - libvips/resample/reduceh.cpp | 6 --- libvips/resample/templates.h | 52 ------------------------ meson.build | 8 ++-- test/test-suite/test_resample.py | 6 +-- 9 files changed, 17 insertions(+), 134 deletions(-) diff --git a/ChangeLog b/ChangeLog index 05927e3190..bc230b2ef7 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,7 +1,3 @@ -8.17.0 - -- add Magic Kernel support [akimon658] - 8.16.1 - support multipage JXL diff --git a/libvips/foreign/jxlload.c b/libvips/foreign/jxlload.c index c53e8f5ab2..59c09f189c 100644 --- a/libvips/foreign/jxlload.c +++ b/libvips/foreign/jxlload.c @@ -439,56 +439,6 @@ vips_foreign_load_jxl_print_format(JxlPixelFormat *format) printf(" endianness = %d\n", format->endianness); printf(" align = %zd\n", format->align); } - -static const char * -vips_foreign_load_jxl_blend_mode(JxlBlendMode blendmode) -{ - switch (blendmode) { - case JXL_BLEND_REPLACE: - return "JXL_BLEND_REPLACE"; - - case JXL_BLEND_ADD: - return "JXL_BLEND_ADD"; - - case JXL_BLEND_BLEND: - return "JXL_BLEND_BLEND"; - - case JXL_BLEND_MULADD: - return "JXL_BLEND_MULADD"; - - case JXL_BLEND_MUL: - return "JXL_BLEND_MUL"; - - default: - return "duration); - printf(" timecode = %u\n", h->timecode); - printf(" name_length = %u\n", h->name_length); - printf(" is_last = %s\n", h->is_last ? "TRUE" : "FALSE"); - printf(" layer_info.have_crop = %s\n", - h->layer_info.have_crop ? "TRUE" : "FALSE"); - printf(" layer_info.crop_x0 = %d\n", h->layer_info.crop_x0); - printf(" layer_info.crop_y0 = %d\n", h->layer_info.crop_y0); - printf(" layer_info.xsize = %u\n", h->layer_info.xsize); - printf(" layer_info.ysize = %u\n", h->layer_info.ysize); - printf(" layer_info.blend_info.blendmode = %s\n", - vips_foreign_load_jxl_blend_mode(h->layer_info.blend_info.blendmode)); - printf(" layer_info.blend_info.source = %u\n", - h->layer_info.blend_info.source); - printf(" layer_info.blend_info.alpha = %u\n", - h->layer_info.blend_info.alpha); - printf(" layer_info.blend_info.clamp = %s\n", - h->layer_info.blend_info.clamp ? "TRUE" : "FALSE"); - printf(" layer_info.save_as_reference = %u\n", - h->layer_info.save_as_reference); -} #endif /*DEBUG*/ static JxlDecoderStatus @@ -505,9 +455,9 @@ vips_foreign_load_jxl_process(VipsForeignLoadJxl *jxl) size_t bytes_remaining; int bytes_read; -#ifdef DEBUG_VERBOSE +#ifdef DEBUG printf("vips_foreign_load_jxl_process: reading ...\n"); -#endif /*DEBUG_VERBOSE*/ +#endif /*DEBUG*/ bytes_remaining = JxlDecoderReleaseInput(jxl->decoder); bytes_read = vips_foreign_load_jxl_fill_input(jxl, bytes_remaining); @@ -548,7 +498,6 @@ vips_foreign_load_jxl_read_frame(VipsForeignLoadJxl *jxl, VipsImage *frame, #ifdef DEBUG_VERBOSE printf("vips_foreign_load_jxl_read_frame: skipping %d frames\n", skip); #endif /*DEBUG_VERBOSE*/ - JxlDecoderSkipFrames(jxl->decoder, skip); jxl->frame_no += skip; } @@ -873,7 +822,8 @@ vips_foreign_load_jxl_header(VipsForeignLoad *load) do { switch ((status = vips_foreign_load_jxl_process(jxl))) { case JXL_DEC_ERROR: - vips_foreign_load_jxl_error(jxl, "JxlDecoderProcessInput"); + vips_foreign_load_jxl_error(jxl, + "JxlDecoderProcessInput"); return -1; case JXL_DEC_BOX: @@ -957,7 +907,8 @@ vips_foreign_load_jxl_header(VipsForeignLoad *load) &jxl->format, #endif JXL_COLOR_PROFILE_TARGET_DATA, &jxl->icc_size)) { - vips_foreign_load_jxl_error(jxl, "JxlDecoderGetICCProfileSize"); + vips_foreign_load_jxl_error(jxl, + "JxlDecoderGetICCProfileSize"); return -1; } @@ -986,10 +937,6 @@ vips_foreign_load_jxl_header(VipsForeignLoad *load) return -1; } -#ifdef DEBUG - vips_foreign_load_jxl_print_frame_header(&h); -#endif /*DEBUG*/ - if (jxl->info.have_animation) { // tick duration in seconds double tick = (double) jxl->info.animation.tps_denominator / @@ -1063,7 +1010,8 @@ vips_foreign_load_jxl_load(VipsForeignLoad *load) JxlDecoderRewind(jxl->decoder); if (JxlDecoderSubscribeEvents(jxl->decoder, - JXL_DEC_FRAME | JXL_DEC_FULL_IMAGE)) { + JXL_DEC_FRAME | + JXL_DEC_FULL_IMAGE)) { vips_foreign_load_jxl_error(jxl, "JxlDecoderSubscribeEvents"); return -1; diff --git a/libvips/foreign/ppmsave.c b/libvips/foreign/ppmsave.c index 3461a1bc45..da3f4dff99 100644 --- a/libvips/foreign/ppmsave.c +++ b/libvips/foreign/ppmsave.c @@ -411,6 +411,9 @@ vips_foreign_save_ppm_build(VipsObject *object) !vips_image_get_double(image, "pfm-scale", &scale)) ; + if (vips_amiMSBfirst()) + scale *= -1; + /* Need to be locale independent. */ g_ascii_dtostr(buf, G_ASCII_DTOSTR_BUF_SIZE, scale); diff --git a/libvips/include/vips/resample.h b/libvips/include/vips/resample.h index d71a8de509..7a87972f95 100644 --- a/libvips/include/vips/resample.h +++ b/libvips/include/vips/resample.h @@ -45,8 +45,6 @@ typedef enum { VIPS_KERNEL_MITCHELL, VIPS_KERNEL_LANCZOS2, VIPS_KERNEL_LANCZOS3, - VIPS_KERNEL_MKS2013, - VIPS_KERNEL_MKS2021, VIPS_KERNEL_LAST } VipsKernel; diff --git a/libvips/resample/reduce.c b/libvips/resample/reduce.c index 94fb8088b6..dd3c1566dd 100644 --- a/libvips/resample/reduce.c +++ b/libvips/resample/reduce.c @@ -66,8 +66,6 @@ * @VIPS_KERNEL_MITCHELL: Convolve with a Mitchell kernel. * @VIPS_KERNEL_LANCZOS2: Convolve with a two-lobe Lanczos kernel. * @VIPS_KERNEL_LANCZOS3: Convolve with a three-lobe Lanczos kernel. - * @VIPS_KERNEL_MKS2013: Convolve with Magic Kernel Sharp 2013. - * @VIPS_KERNEL_MKS2021: Convolve with Magic Kernel Sharp 2021. * * The resampling kernels vips supports. See vips_reduce(), for example. */ diff --git a/libvips/resample/reduceh.cpp b/libvips/resample/reduceh.cpp index 4e369c92e9..3258eea708 100644 --- a/libvips/resample/reduceh.cpp +++ b/libvips/resample/reduceh.cpp @@ -125,12 +125,6 @@ vips_reduce_get_points(VipsKernel kernel, double shrink) case VIPS_KERNEL_LANCZOS3: return 2 * rint(3 * shrink) + 1; - case VIPS_KERNEL_MKS2013: - return 2 * rint(3 * shrink) + 1; - - case VIPS_KERNEL_MKS2021: - return 2 * rint(5 * shrink) + 1; - default: g_assert_not_reached(); return 0; diff --git a/libvips/resample/templates.h b/libvips/resample/templates.h index 30e28af739..49a56b5ae5 100644 --- a/libvips/resample/templates.h +++ b/libvips/resample/templates.h @@ -402,48 +402,6 @@ double inline filter(double x) return 0.0; } -template <> -double inline filter(double x) -{ - if (x < 0.0) - x = -x; - - if (x >= 2.5) - return 0.0; - - if (x >= 1.5) - return (x - 5.0 / 2.0) * (x - 5.0 / 2.0) / -8.0; - - if (x >= 0.5) - return (4.0 * x * x - 11.0 * x + 7.0) / 4.0; - - return 17.0 / 16.0 - 7.0 * x * x / 4.0; -} - -template <> -double inline filter(double x) -{ - if (x < 0.0) - x = -x; - - if (x >= 4.5) - return 0.0; - - if (x >= 3.5) - return (4.0 * x * x - 36.0 * x + 81.0) / -1152.0; - - if (x >= 2.5) - return (4.0 * x * x - 27.0 * x + 45.0) / 144.0; - - if (x >= 1.5) - return (24.0 * x * x - 113.0 * x + 130.0) / -144.0; - - if (x >= 0.5) - return (140.0 * x * x - 379.0 * x + 239.0) / 144.0; - - return 577.0 / 576.0 - 239.0 * x * x / 144.0; -} - /* Given an x in [0,1] (we can have x == 1 when building tables), * calculate c0 .. c(@n_points), the coefficients. This is called * from the interpolator as well as from the table builder. @@ -511,16 +469,6 @@ vips_reduce_make_mask(T *c, VipsKernel kernel, const int n_points, filter, shrink, x); break; - case VIPS_KERNEL_MKS2013: - calculate_coefficients(c, n_points, - filter, shrink, x); - break; - - case VIPS_KERNEL_MKS2021: - calculate_coefficients(c, n_points, - filter, shrink, x); - break; - default: g_assert_not_reached(); break; diff --git a/meson.build b/meson.build index 7f9a94d30d..21797c1b6f 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('vips', 'c', 'cpp', - version: '8.17.0', + version: '8.16.1', meson_version: '>=0.55', default_options: [ # this is what glib uses (one of our required deps), so we use it too @@ -23,9 +23,9 @@ version_patch = version_parts[2] # binary interface changed: increment current, reset revision to 0 # binary interface changes backwards compatible?: increment age # binary interface changes not backwards compatible?: reset age to 0 -library_revision = 0 -library_current = 61 -library_age = 19 +library_revision = 1 +library_current = 60 +library_age = 18 library_version = '@0@.@1@.@2@'.format(library_current - library_age, library_age, library_revision) darwin_versions = [library_current + 1, '@0@.@1@'.format(library_current + 1, library_revision)] diff --git a/test/test-suite/test_resample.py b/test/test-suite/test_resample.py index 55c92eb53c..7bb65b2fc5 100644 --- a/test/test-suite/test_resample.py +++ b/test/test-suite/test_resample.py @@ -83,8 +83,7 @@ def test_reduce(self): for fac in [1, 1.1, 1.5, 1.999]: for fmt in all_formats: for kernel in ["nearest", "linear", - "cubic", "lanczos2", - "lanczos3", "mks2013", "mks2021"]: + "cubic", "lanczos2", "lanczos3"]: x = im.cast(fmt) r = x.reduce(fac, fac, kernel=kernel) d = abs(r.avg() - im.avg()) @@ -94,8 +93,7 @@ def test_reduce(self): for const in [0, 1, 2, 254, 255]: im = (pyvips.Image.black(10, 10) + const).cast("uchar") for kernel in ["nearest", "linear", - "cubic", "lanczos2", - "lanczos3", "mks2013", "mks2021"]: + "cubic", "lanczos2", "lanczos3"]: # print "testing kernel =", kernel # print "testing const =", const shr = im.reduce(2, 2, kernel=kernel) From f06db0f9161aa98a026dc7705eef3e33874316d6 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sun, 3 Nov 2024 12:13:53 +0000 Subject: [PATCH 05/43] oop, fix the bad merge --- ChangeLog | 1 + libvips/foreign/ppmsave.c | 3 --- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/ChangeLog b/ChangeLog index bc230b2ef7..fad4829179 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,7 @@ 8.16.1 - support multipage JXL +- fix PFM byte order on little-endian machines [agoode] 10/10/24 8.16.0 diff --git a/libvips/foreign/ppmsave.c b/libvips/foreign/ppmsave.c index da3f4dff99..3461a1bc45 100644 --- a/libvips/foreign/ppmsave.c +++ b/libvips/foreign/ppmsave.c @@ -411,9 +411,6 @@ vips_foreign_save_ppm_build(VipsObject *object) !vips_image_get_double(image, "pfm-scale", &scale)) ; - if (vips_amiMSBfirst()) - scale *= -1; - /* Need to be locale independent. */ g_ascii_dtostr(buf, G_ASCII_DTOSTR_BUF_SIZE, scale); From b0e8d2a1a051f91230efa5e748bf653580554edb Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sun, 3 Nov 2024 15:58:43 +0000 Subject: [PATCH 06/43] use VIPS_FMAX()/FMIN() in maxpair/minpair (#4244) These macros are faster and more accurate than VIPS_MAX() and VIPS_MIN for float values. See https://github.com/libvips/libvips/pull/4243 --- libvips/arithmetic/maxpair.c | 14 ++++++++++++-- libvips/arithmetic/minpair.c | 14 ++++++++++++-- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/libvips/arithmetic/maxpair.c b/libvips/arithmetic/maxpair.c index 8668bdfb59..85112cdb14 100644 --- a/libvips/arithmetic/maxpair.c +++ b/libvips/arithmetic/maxpair.c @@ -63,6 +63,16 @@ G_DEFINE_TYPE(VipsMaxpair, vips_maxpair, VIPS_TYPE_BINARY); q[x] = VIPS_MAX(left[x], right[x]); \ } +#define FLOOP(TYPE) \ + { \ + TYPE *restrict left = (TYPE *) in[0]; \ + TYPE *restrict right = (TYPE *) in[1]; \ + TYPE *restrict q = (TYPE *) out; \ +\ + for (int x = 0; x < sz; x++) \ + q[x] = VIPS_FMAX(left[x], right[x]); \ + } + static void maxpair_buffer(VipsArithmetic *arithmetic, VipsPel *out, VipsPel **in, int width) @@ -102,12 +112,12 @@ maxpair_buffer(VipsArithmetic *arithmetic, case VIPS_FORMAT_FLOAT: case VIPS_FORMAT_COMPLEX: - LOOP(float); + FLOOP(float); break; case VIPS_FORMAT_DOUBLE: case VIPS_FORMAT_DPCOMPLEX: - LOOP(double); + FLOOP(double); break; default: diff --git a/libvips/arithmetic/minpair.c b/libvips/arithmetic/minpair.c index 168fc942ca..6b4c653f20 100644 --- a/libvips/arithmetic/minpair.c +++ b/libvips/arithmetic/minpair.c @@ -63,6 +63,16 @@ G_DEFINE_TYPE(VipsMinpair, vips_minpair, VIPS_TYPE_BINARY); q[x] = VIPS_MIN(left[x], right[x]); \ } +#define FLOOP(TYPE) \ + { \ + TYPE *restrict left = (TYPE *) in[0]; \ + TYPE *restrict right = (TYPE *) in[1]; \ + TYPE *restrict q = (TYPE *) out; \ +\ + for (int x = 0; x < sz; x++) \ + q[x] = VIPS_FMIN(left[x], right[x]); \ + } + static void minpair_buffer(VipsArithmetic *arithmetic, VipsPel *out, VipsPel **in, int width) @@ -102,12 +112,12 @@ minpair_buffer(VipsArithmetic *arithmetic, case VIPS_FORMAT_FLOAT: case VIPS_FORMAT_COMPLEX: - LOOP(float); + FLOOP(float); break; case VIPS_FORMAT_DOUBLE: case VIPS_FORMAT_DPCOMPLEX: - LOOP(double); + FLOOP(double); break; default: From 9fe7ac3c07ff0dd64b3c4c4ef74dac361814bed1 Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Mon, 4 Nov 2024 13:32:24 +0100 Subject: [PATCH 07/43] morph: fix regressions after Highway implementation (#4240) * morph: fix erode Highway path * morph: sync C-paths with the Highway implementation Previously, `seq->coff` was used both for storing offsets to clear values (zero values in masks) and as an array for non-128 mask coefficients. However, in commit 40e2884 (PR #3618), `seq->coff` was restricted to `guint8` values, making it incompatible for storing offsets. Fix this by syncing the C-paths with the Highway implementation. * morph: prefer bitwise NOT over bitwise XOR `~p` and `p ^ 255` produce the same result on uchar images, as XOR affects only the lowest 8 bits. --- ChangeLog | 2 + libvips/morphology/morph.c | 201 ++++++++++--------------------- libvips/morphology/morph_hwy.cpp | 13 +- 3 files changed, 72 insertions(+), 144 deletions(-) diff --git a/ChangeLog b/ChangeLog index fad4829179..98aab5a735 100644 --- a/ChangeLog +++ b/ChangeLog @@ -2,6 +2,8 @@ - support multipage JXL - fix PFM byte order on little-endian machines [agoode] +- morph: fix erode Highway path [kleisauke] +- morph: fix C-paths with masks containing zero [kleisauke] 10/10/24 8.16.0 diff --git a/libvips/morphology/morph.c b/libvips/morphology/morph.c index c7637177cd..6f4570e6da 100644 --- a/libvips/morphology/morph.c +++ b/libvips/morphology/morph.c @@ -149,10 +149,9 @@ typedef struct { VipsMorph *morph; VipsRegion *ir; /* Input region */ - int *soff; /* Offsets we check for set */ - int ss; /* ... and number we check for set */ - guint8 *coff; /* Offsets we check for clear */ - int cs; /* ... and number we check for clear */ + int *off; /* Offsets for each non-128 matrix element */ + int nn128; /* Number of non-128 mask elements */ + guint8 *coeff; /* Array of non-128 mask coefficients */ int last_bpl; /* Avoid recalcing offsets, if we can */ @@ -212,10 +211,9 @@ vips_morph_start(VipsImage *out, void *a, void *b) */ seq->morph = morph; seq->ir = NULL; - seq->soff = NULL; - seq->ss = 0; - seq->coff = NULL; - seq->cs = 0; + seq->off = NULL; + seq->nn128 = 0; + seq->coeff = NULL; seq->last_bpl = -1; #ifdef HAVE_ORC seq->t1 = NULL; @@ -224,11 +222,11 @@ vips_morph_start(VipsImage *out, void *a, void *b) seq->ir = vips_region_new(in); - seq->soff = VIPS_ARRAY(out, morph->n_point, int); - seq->coff = VIPS_ARRAY(out, morph->n_point, guint8); + seq->off = VIPS_ARRAY(out, morph->n_point, int); + seq->coeff = VIPS_ARRAY(out, morph->n_point, guint8); - if (!seq->soff || - !seq->coff) { + if (!seq->off || + !seq->coeff) { vips_morph_stop(seq, in, morph); return NULL; } @@ -263,13 +261,8 @@ vips_dilate_vector_gen(VipsRegion *out_region, VipsImage *M = morph->M; VipsRegion *ir = seq->ir; - /* Offsets for each non-128 matrix element. - */ - int *soff = seq->soff; - - /* Array of non-128 mask coefficients. - */ - guint8 *coff = seq->coff; + int *off = seq->off; + guint8 *coeff = seq->coeff; VipsRect *r = &out_region->valid; int sz = VIPS_REGION_N_ELEMENTS(out_region); @@ -298,9 +291,7 @@ vips_dilate_vector_gen(VipsRegion *out_region, if (seq->last_bpl != VIPS_REGION_LSKIP(ir)) { seq->last_bpl = VIPS_REGION_LSKIP(ir); - /* Number of non-128 mask elements. - */ - seq->ss = 0; + seq->nn128 = 0; for (t = morph->coeff, y = 0; y < M->Ysize; y++) for (x = 0; x < M->Xsize; x++, t++) { /* Exclude don't-care elements. @@ -308,19 +299,18 @@ vips_dilate_vector_gen(VipsRegion *out_region, if (*t == 128) continue; - soff[seq->ss] = - VIPS_REGION_ADDR(ir, - x + r->left, y + r->top) - + off[seq->nn128] = + VIPS_REGION_ADDR(ir, x + r->left, y + r->top) - VIPS_REGION_ADDR(ir, r->left, r->top); - coff[seq->ss] = *t; - seq->ss++; + coeff[seq->nn128] = *t; + seq->nn128++; } } VIPS_GATE_START("vips_dilate_vector_gen: work"); vips_dilate_uchar_hwy(out_region, ir, r, - sz, seq->ss, soff, coff); + sz, seq->nn128, off, coeff); VIPS_GATE_STOP("vips_dilate_vector_gen: work"); @@ -338,13 +328,8 @@ vips_erode_vector_gen(VipsRegion *out_region, VipsImage *M = morph->M; VipsRegion *ir = seq->ir; - /* Offsets for each non-128 matrix element. - */ - int *soff = seq->soff; - - /* Array of non-128 mask coefficients. - */ - guint8 *coff = seq->coff; + int *off = seq->off; + guint8 *coeff = seq->coeff; VipsRect *r = &out_region->valid; int sz = VIPS_REGION_N_ELEMENTS(out_region); @@ -373,9 +358,7 @@ vips_erode_vector_gen(VipsRegion *out_region, if (seq->last_bpl != VIPS_REGION_LSKIP(ir)) { seq->last_bpl = VIPS_REGION_LSKIP(ir); - /* Number of non-128 mask elements. - */ - seq->ss = 0; + seq->nn128 = 0; for (t = morph->coeff, y = 0; y < M->Ysize; y++) for (x = 0; x < M->Xsize; x++, t++) { /* Exclude don't-care elements. @@ -383,19 +366,18 @@ vips_erode_vector_gen(VipsRegion *out_region, if (*t == 128) continue; - soff[seq->ss] = - VIPS_REGION_ADDR(ir, - x + r->left, y + r->top) - + off[seq->nn128] = + VIPS_REGION_ADDR(ir, x + r->left, y + r->top) - VIPS_REGION_ADDR(ir, r->left, r->top); - coff[seq->ss] = *t; - seq->ss++; + coeff[seq->nn128] = *t; + seq->nn128++; } } VIPS_GATE_START("vips_erode_vector_gen: work"); vips_erode_uchar_hwy(out_region, ir, r, - sz, seq->ss, soff, coff); + sz, seq->nn128, off, coeff); VIPS_GATE_STOP("vips_erode_vector_gen: work"); @@ -667,8 +649,8 @@ vips_dilate_gen(VipsRegion *out_region, VipsImage *M = morph->M; VipsRegion *ir = seq->ir; - int *soff = seq->soff; - guint8 *coff = seq->coff; + int *off = seq->off; + guint8 *coeff = seq->coeff; VipsRect *r = &out_region->valid; int le = r->left; @@ -701,37 +683,24 @@ vips_dilate_gen(VipsRegion *out_region, if (seq->last_bpl != VIPS_REGION_LSKIP(ir)) { seq->last_bpl = VIPS_REGION_LSKIP(ir); - seq->ss = 0; - seq->cs = 0; + seq->nn128 = 0; for (t = morph->coeff, y = 0; y < M->Ysize; y++) - for (x = 0; x < M->Xsize; x++, t++) - switch (*t) { - case 255: - soff[seq->ss++] = - VIPS_REGION_ADDR(ir, - x + le, y + to) - - VIPS_REGION_ADDR(ir, le, to); - break; - - case 128: - break; - - case 0: - coff[seq->cs++] = - VIPS_REGION_ADDR(ir, - x + le, y + to) - - VIPS_REGION_ADDR(ir, le, to); - break; - - default: - g_assert_not_reached(); - } + for (x = 0; x < M->Xsize; x++, t++) { + /* Exclude don't-care elements. + */ + if (*t == 128) + continue; + + off[seq->nn128] = + VIPS_REGION_ADDR(ir, x + le, y + to) - + VIPS_REGION_ADDR(ir, le, to); + coeff[seq->nn128] = *t; + seq->nn128++; + } } VIPS_GATE_START("vips_dilate_gen: work"); - /* Dilate! - */ for (y = to; y < bo; y++) { VipsPel *p = VIPS_REGION_ADDR(ir, le, y); VipsPel *q = VIPS_REGION_ADDR(out_region, le, y); @@ -739,28 +708,11 @@ vips_dilate_gen(VipsRegion *out_region, /* Loop along line. */ for (x = 0; x < sz; x++, q++, p++) { - /* Search for a hit on the set list. + /* Dilate! */ result = 0; - for (i = 0; i < seq->ss; i++) - if (p[soff[i]]) { - /* Found a match! - */ - result = 255; - break; - } - - /* No set pixels ... search for a hit in the clear - * pixels. - */ - if (!result) - for (i = 0; i < seq->cs; i++) - if (!p[coff[i]]) { - /* Found a match! - */ - result = 255; - break; - } + for (i = 0; i < seq->nn128; i++) + result |= !coeff[i] ? ~p[off[i]] : p[off[i]]; *q = result; } @@ -784,8 +736,8 @@ vips_erode_gen(VipsRegion *out_region, VipsImage *M = morph->M; VipsRegion *ir = seq->ir; - int *soff = seq->soff; - guint8 *coff = seq->coff; + int *off = seq->off; + guint8 *coeff = seq->coeff; VipsRect *r = &out_region->valid; int le = r->left; @@ -818,37 +770,24 @@ vips_erode_gen(VipsRegion *out_region, if (seq->last_bpl != VIPS_REGION_LSKIP(ir)) { seq->last_bpl = VIPS_REGION_LSKIP(ir); - seq->ss = 0; - seq->cs = 0; + seq->nn128 = 0; for (t = morph->coeff, y = 0; y < M->Ysize; y++) - for (x = 0; x < M->Xsize; x++, t++) - switch (*t) { - case 255: - soff[seq->ss++] = - VIPS_REGION_ADDR(ir, - x + le, y + to) - - VIPS_REGION_ADDR(ir, le, to); - break; - - case 128: - break; - - case 0: - coff[seq->cs++] = - VIPS_REGION_ADDR(ir, - x + le, y + to) - - VIPS_REGION_ADDR(ir, le, to); - break; - - default: - g_assert_not_reached(); - } + for (x = 0; x < M->Xsize; x++, t++) { + /* Exclude don't-care elements. + */ + if (*t == 128) + continue; + + off[seq->nn128] = + VIPS_REGION_ADDR(ir, x + le, y + to) - + VIPS_REGION_ADDR(ir, le, to); + coeff[seq->nn128] = *t; + seq->nn128++; + } } VIPS_GATE_START("vips_erode_gen: work"); - /* Erode! - */ for (y = to; y < bo; y++) { VipsPel *p = VIPS_REGION_ADDR(ir, le, y); VipsPel *q = VIPS_REGION_ADDR(out_region, le, y); @@ -856,25 +795,11 @@ vips_erode_gen(VipsRegion *out_region, /* Loop along line. */ for (x = 0; x < sz; x++, q++, p++) { - /* Check all set pixels are set. + /* Erode! */ result = 255; - for (i = 0; i < seq->ss; i++) - if (!p[soff[i]]) { - /* Found a mismatch! - */ - result = 0; - break; - } - - /* Check all clear pixels are clear. - */ - if (result) - for (i = 0; i < seq->cs; i++) - if (p[coff[i]]) { - result = 0; - break; - } + for (i = 0; i < seq->nn128; i++) + result &= !coeff[i] ? ~p[off[i]] : p[off[i]]; *q = result; } @@ -950,7 +875,7 @@ vips_morph_build(VipsObject *object) coeff[i]); return -1; } - morph->coeff[i] = coeff[i]; + morph->coeff[i] = (guint8) coeff[i]; } /* Try to make a vector path. diff --git a/libvips/morphology/morph_hwy.cpp b/libvips/morphology/morph_hwy.cpp index 1acdf2cb19..e8c64c8704 100644 --- a/libvips/morphology/morph_hwy.cpp +++ b/libvips/morphology/morph_hwy.cpp @@ -89,7 +89,7 @@ vips_dilate_uchar_hwy(VipsRegion *out_region, VipsRegion *ir, VipsRect *r, */ auto pix = LoadU(du8, p + offsets[i]); - pix = IfThenElse(Ne(mmk, one), Xor(pix, one), pix); + pix = IfThenElse(Ne(mmk, one), AndNot(pix, one), pix); sum = Or(sum, pix); } @@ -109,7 +109,7 @@ vips_dilate_uchar_hwy(VipsRegion *out_region, VipsRegion *ir, VipsRect *r, auto pix = LoadU(du8, p + offsets[i]); if (!coeff[i]) - pix = Xor(pix, one); + pix = AndNot(pix, one); sum = Or(sum, pix); } @@ -146,9 +146,8 @@ vips_erode_uchar_hwy(VipsRegion *out_region, VipsRegion *ir, VipsRect *r, */ auto pix = LoadU(du8, p + offsets[i]); - sum = IfThenElse(Ne(mmk, one), - AndNot(pix, one), - And(sum, pix)); + pix = IfThenElse(Ne(mmk, one), AndNot(pix, one), pix); + sum = And(sum, pix); } StoreU(sum, du8, q + x); @@ -166,7 +165,9 @@ vips_erode_uchar_hwy(VipsRegion *out_region, VipsRegion *ir, VipsRect *r, */ auto pix = LoadU(du8, p + offsets[i]); - sum = !coeff[i] ? AndNot(pix, one) : And(sum, pix); + if (!coeff[i]) + pix = AndNot(pix, one); + sum = And(sum, pix); } q[x] = GetLane(sum); From d91f7b06defc2ad4b64e164307dbcf728af5139d Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Mon, 4 Nov 2024 13:33:02 +0100 Subject: [PATCH 08/43] reduce: prefer use of `VIPS_FABS()` (#4243) To ensure we handle negative zero (-0.0) correctly. (cherry picked from commit 852fd7c69ef6903be838fb10937994388fd81263) --- libvips/resample/templates.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libvips/resample/templates.h b/libvips/resample/templates.h index 49a56b5ae5..e652149aa3 100644 --- a/libvips/resample/templates.h +++ b/libvips/resample/templates.h @@ -361,8 +361,7 @@ static double inline filter(double x); template <> double inline filter(double x) { - if (x < 0.0) - x = -x; + x = VIPS_FABS(x); if (x < 1.0) return 1.0 - x; From a26dce4a01472e77ccdb8c5cb316e1fc5303072e Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Tue, 5 Nov 2024 11:33:54 +0100 Subject: [PATCH 09/43] Revise ChangeLog note (#4246) --- ChangeLog | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index 98aab5a735..4b99abe5f0 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,7 +1,7 @@ 8.16.1 - support multipage JXL -- fix PFM byte order on little-endian machines [agoode] +- fix PFM byte order on big-endian machines [agoode] - morph: fix erode Highway path [kleisauke] - morph: fix C-paths with masks containing zero [kleisauke] From f6aa2bd0a497124666a98c754ccabe3f24ea6c58 Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Tue, 12 Nov 2024 21:00:04 +0100 Subject: [PATCH 10/43] Fix `--vips-info` CLI flag with GLib >= 2.80 (#4251) Use `g_log_writer_default_set_debug_domains()` since resetting `G_MESSAGES_DEBUG` at runtime has no effect for GLib >= 2.80. Also, remove any checks for the old `G_MESSAGES_DEBUG` env variable and overwrite it directly instead. This means the `--vips-info` CLI flag and the `VIPS_INFO=1` env variable now take precedence over `G_MESSAGES_DEBUG`. See: https://gitlab.gnome.org/GNOME/glib/-/merge_requests/3710. --- ChangeLog | 1 + libvips/iofuncs/init.c | 24 +++++++++--------------- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/ChangeLog b/ChangeLog index 4b99abe5f0..97cf6f8fd5 100644 --- a/ChangeLog +++ b/ChangeLog @@ -4,6 +4,7 @@ - fix PFM byte order on big-endian machines [agoode] - morph: fix erode Highway path [kleisauke] - morph: fix C-paths with masks containing zero [kleisauke] +- fix `--vips-info` CLI flag with GLib >= 2.80 [kleisauke] 10/10/24 8.16.0 diff --git a/libvips/iofuncs/init.c b/libvips/iofuncs/init.c index 5df713b8c6..b6d0db3784 100644 --- a/libvips/iofuncs/init.c +++ b/libvips/iofuncs/init.c @@ -363,24 +363,18 @@ set_stacksize(guint64 size) #endif /*HAVE_PTHREAD_DEFAULT_NP*/ } +/** + * Equivalent to setting the `G_MESSAGES_DEBUG=VIPS` environment variable. + */ static void vips_verbose(void) { - const char *old; - - old = g_getenv("G_MESSAGES_DEBUG"); - - if (!old) - g_setenv("G_MESSAGES_DEBUG", G_LOG_DOMAIN, TRUE); - else if (!g_str_equal(old, "all") && - !g_strrstr(old, G_LOG_DOMAIN)) { - char *new; - - new = g_strconcat(old, " ", G_LOG_DOMAIN, NULL); - g_setenv("G_MESSAGES_DEBUG", new, TRUE); - - g_free(new); - } +#if GLIB_CHECK_VERSION(2, 80, 0) + const char *domains[] = { G_LOG_DOMAIN, NULL }; + g_log_writer_default_set_debug_domains(domains); +#else + g_setenv("G_MESSAGES_DEBUG", G_LOG_DOMAIN, TRUE); +#endif } static int From 4ece8726d204b4581cd5e896a7012ab005d9367b Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Sun, 17 Nov 2024 13:55:47 +0100 Subject: [PATCH 11/43] Make `subsample-mode=on` and `lossless=true` mutually exclusive (#4263) i.e. always disable chroma subsampling when saving lossless. Resolves: #4232. --- ChangeLog | 1 + libvips/foreign/heifsave.c | 6 ++---- libvips/foreign/jp2ksave.c | 9 ++++++--- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/ChangeLog b/ChangeLog index 97cf6f8fd5..b3b9b92255 100644 --- a/ChangeLog +++ b/ChangeLog @@ -5,6 +5,7 @@ - morph: fix erode Highway path [kleisauke] - morph: fix C-paths with masks containing zero [kleisauke] - fix `--vips-info` CLI flag with GLib >= 2.80 [kleisauke] +- make `subsample-mode=on` and `lossless=true` mutually exclusive [kleisauke] 10/10/24 8.16.0 diff --git a/libvips/foreign/heifsave.c b/libvips/foreign/heifsave.c index e1040650ac..cb1ca66bb4 100644 --- a/libvips/foreign/heifsave.c +++ b/libvips/foreign/heifsave.c @@ -526,11 +526,9 @@ vips_foreign_save_heif_build(VipsObject *object) !vips_object_argument_isset(object, "effort")) heif->effort = 9 - heif->speed; - /* Disable chroma subsampling by default when the "lossless" param - * is being used. + /* The "lossless" param implies no chroma subsampling. */ - if (vips_object_argument_isset(object, "lossless") && - !vips_object_argument_isset(object, "subsample_mode")) + if (heif->lossless) heif->subsample_mode = VIPS_FOREIGN_SUBSAMPLE_OFF; /* Default 12 bit save for 16-bit images. diff --git a/libvips/foreign/jp2ksave.c b/libvips/foreign/jp2ksave.c index e23fd14ff3..bbdc2025ae 100644 --- a/libvips/foreign/jp2ksave.c +++ b/libvips/foreign/jp2ksave.c @@ -819,11 +819,14 @@ vips_foreign_save_jp2k_build(VipsObject *object) return -1; } + /* The "lossless" param implies no chroma subsampling. + */ + if (jp2k->lossless) + jp2k->subsample_mode = VIPS_FOREIGN_SUBSAMPLE_OFF; + switch (jp2k->subsample_mode) { case VIPS_FOREIGN_SUBSAMPLE_AUTO: - jp2k->subsample = - !jp2k->lossless && - jp2k->Q < 90 && + jp2k->subsample = jp2k->Q < 90 && (save->ready->Type == VIPS_INTERPRETATION_sRGB || save->ready->Type == VIPS_INTERPRETATION_RGB16) && save->ready->Bands == 3; From c3abf9f4250138d6b4e2a4cef355d9fa239e327c Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Sun, 17 Nov 2024 18:08:14 +0100 Subject: [PATCH 12/43] Fix GIR error after #4251 (#4265) --- libvips/iofuncs/init.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libvips/iofuncs/init.c b/libvips/iofuncs/init.c index b6d0db3784..75a81abfb2 100644 --- a/libvips/iofuncs/init.c +++ b/libvips/iofuncs/init.c @@ -363,8 +363,7 @@ set_stacksize(guint64 size) #endif /*HAVE_PTHREAD_DEFAULT_NP*/ } -/** - * Equivalent to setting the `G_MESSAGES_DEBUG=VIPS` environment variable. +/* Equivalent to setting the `G_MESSAGES_DEBUG=VIPS` environment variable. */ static void vips_verbose(void) From c1a42ac523ccf9362dd76028115bf9ba4532a0d8 Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Sun, 17 Nov 2024 19:15:00 +0100 Subject: [PATCH 13/43] Avoid using `vips_object_argument_isset()` to check for flags (#4264) --- libvips/foreign/dzsave.c | 2 +- libvips/foreign/ppmsave.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libvips/foreign/dzsave.c b/libvips/foreign/dzsave.c index 922f10776a..19561cf306 100644 --- a/libvips/foreign/dzsave.c +++ b/libvips/foreign/dzsave.c @@ -1955,7 +1955,7 @@ vips_foreign_save_dz_build(VipsObject *object) * or the deprecated "no_strip" turns this off. */ if (!vips_object_argument_isset(object, "keep") && - !vips_object_argument_isset(object, "no_strip")) + !dz->no_strip) save->keep = VIPS_FOREIGN_KEEP_NONE; /* Google, zoomify and iiif default to zero overlap, ".jpg". diff --git a/libvips/foreign/ppmsave.c b/libvips/foreign/ppmsave.c index 3461a1bc45..57e6cc5631 100644 --- a/libvips/foreign/ppmsave.c +++ b/libvips/foreign/ppmsave.c @@ -324,7 +324,7 @@ vips_foreign_save_ppm_build(VipsObject *object) /* Handle the deprecated squash parameter. */ - if (vips_object_argument_isset(object, "squash")) + if (ppm->squash) ppm->bitdepth = 1; if (vips_check_uintorf("vips2ppm", image) || From a901cacfddb8f4edb70ace1a7d4b893d52caacc0 Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Mon, 25 Nov 2024 12:50:27 +0100 Subject: [PATCH 14/43] Disable `unlimited` flag in fuzzing builds (#4266) The load-specific `unlimited` flag is known to be fuzzing-unfriendly. --- libvips/foreign/heifload.c | 2 ++ libvips/foreign/jpegload.c | 2 ++ libvips/foreign/pngload.c | 2 ++ libvips/foreign/spngload.c | 2 ++ libvips/foreign/svgload.c | 2 ++ test/test-suite/test_foreign.py | 3 --- 6 files changed, 10 insertions(+), 3 deletions(-) diff --git a/libvips/foreign/heifload.c b/libvips/foreign/heifload.c index d50fa045da..3872811abf 100644 --- a/libvips/foreign/heifload.c +++ b/libvips/foreign/heifload.c @@ -1107,12 +1107,14 @@ vips_foreign_load_heif_class_init(VipsForeignLoadHeifClass *class) G_STRUCT_OFFSET(VipsForeignLoadHeif, autorotate), FALSE); +#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION VIPS_ARG_BOOL(class, "unlimited", 22, _("Unlimited"), _("Remove all denial of service limits"), VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET(VipsForeignLoadHeif, unlimited), FALSE); +#endif } static gint64 diff --git a/libvips/foreign/jpegload.c b/libvips/foreign/jpegload.c index bb448a778f..f42dfdf33b 100644 --- a/libvips/foreign/jpegload.c +++ b/libvips/foreign/jpegload.c @@ -201,12 +201,14 @@ vips_foreign_load_jpeg_class_init(VipsForeignLoadJpegClass *class) G_STRUCT_OFFSET(VipsForeignLoadJpeg, autorotate), FALSE); +#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION VIPS_ARG_BOOL(class, "unlimited", 22, _("Unlimited"), _("Remove all denial of service limits"), VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET(VipsForeignLoadJpeg, unlimited), FALSE); +#endif } static void diff --git a/libvips/foreign/pngload.c b/libvips/foreign/pngload.c index 1eb0b6c58e..1ecadb98cc 100644 --- a/libvips/foreign/pngload.c +++ b/libvips/foreign/pngload.c @@ -166,12 +166,14 @@ vips_foreign_load_png_class_init(VipsForeignLoadPngClass *class) load_class->header = vips_foreign_load_png_header; load_class->load = vips_foreign_load_png_load; +#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION VIPS_ARG_BOOL(class, "unlimited", 23, _("Unlimited"), _("Remove all denial of service limits"), VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET(VipsForeignLoadPng, unlimited), FALSE); +#endif } static void diff --git a/libvips/foreign/spngload.c b/libvips/foreign/spngload.c index 9d3a48259f..e4b2d5015b 100644 --- a/libvips/foreign/spngload.c +++ b/libvips/foreign/spngload.c @@ -673,12 +673,14 @@ vips_foreign_load_png_class_init(VipsForeignLoadPngClass *class) load_class->header = vips_foreign_load_png_header; load_class->load = vips_foreign_load_png_load; +#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION VIPS_ARG_BOOL(class, "unlimited", 23, _("Unlimited"), _("Remove all denial of service limits"), VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET(VipsForeignLoadPng, unlimited), FALSE); +#endif } static void diff --git a/libvips/foreign/svgload.c b/libvips/foreign/svgload.c index 040cce9ee4..76c21990ed 100644 --- a/libvips/foreign/svgload.c +++ b/libvips/foreign/svgload.c @@ -727,12 +727,14 @@ vips_foreign_load_svg_class_init(VipsForeignLoadSvgClass *class) G_STRUCT_OFFSET(VipsForeignLoadSvg, scale), 0.0, 100000.0, 1.0); +#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION VIPS_ARG_BOOL(class, "unlimited", 23, _("Unlimited"), _("Allow SVG of any size"), VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET(VipsForeignLoadSvg, unlimited), FALSE); +#endif } static void diff --git a/test/test-suite/test_foreign.py b/test/test-suite/test_foreign.py index 57c8b79d1c..2dbd05f712 100644 --- a/test/test-suite/test_foreign.py +++ b/test/test-suite/test_foreign.py @@ -1426,9 +1426,6 @@ def heif_valid(im): im = pyvips.Image.heifload(AVIF_FILE_HUGE) assert im.avg() == 0.0 - im = pyvips.Image.heifload(AVIF_FILE_HUGE, unlimited=True) - assert im.avg() == 0.0 - @skip_if_no("heifsave") def test_avifsave(self): self.save_load_buffer("heifsave_buffer", "heifload_buffer", From e8564ae523ea801305227116c3eeb56093f8d39f Mon Sep 17 00:00:00 2001 From: Lovell Fuller Date: Mon, 25 Nov 2024 11:58:21 +0000 Subject: [PATCH 15/43] CI: Homebrew has switched from pkg-config to pkgconf (#4285) --- .github/workflows/ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 764008045b..0121c95a2a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -54,8 +54,9 @@ jobs: if: runner.os == 'macOS' run: | pip3 install meson --break-system-packages + brew unlink pkg-config@0.29.2 brew install \ - ninja pkg-config \ + ninja pkgconf \ cfitsio cgif fftw fontconfig glib \ highway jpeg-xl libarchive libexif \ libheif libimagequant libmatio librsvg \ From 2d182fd16c8986bc99738a937b9fc87e91677c19 Mon Sep 17 00:00:00 2001 From: Lovell Fuller Date: Tue, 26 Nov 2024 09:43:27 +0000 Subject: [PATCH 16/43] heifsave: prevent use of AV1 intra block copy feature (#4284) Helps ensure encoding time is more predictable/consistent --- ChangeLog | 1 + libvips/foreign/heifsave.c | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/ChangeLog b/ChangeLog index b3b9b92255..6ab3082fc2 100644 --- a/ChangeLog +++ b/ChangeLog @@ -6,6 +6,7 @@ - morph: fix C-paths with masks containing zero [kleisauke] - fix `--vips-info` CLI flag with GLib >= 2.80 [kleisauke] - make `subsample-mode=on` and `lossless=true` mutually exclusive [kleisauke] +- heifsave: prevent use of AV1 intra block copy feature [lovell] 10/10/24 8.16.0 diff --git a/libvips/foreign/heifsave.c b/libvips/foreign/heifsave.c index cb1ca66bb4..6eab243806 100644 --- a/libvips/foreign/heifsave.c +++ b/libvips/foreign/heifsave.c @@ -657,6 +657,17 @@ vips_foreign_save_heif_build(VipsObject *object) return -1; } + /* Try to prevent the AVIF encoder from using intra block copy, + * helps ensure encoding time is more predictable. + */ + error = heif_encoder_set_parameter_boolean(heif->encoder, + "intra-block-copy", FALSE); + if (error.code && + error.subcode != heif_suberror_Unsupported_parameter) { + vips__heif_error(&error); + return -1; + } + /* TODO .. support extra per-encoder params with * heif_encoder_list_parameters(). */ From c0c6546a6544b3cc6e4ec6a4a73db7e6c929a6c7 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Tue, 26 Nov 2024 12:56:49 +0000 Subject: [PATCH 17/43] fix dzsave associated images with openslide4 (#4286) openslide4 adds some new metadata items (eg. openslide.associated.label.width) which confuse the test for associated images. This PR adds another check to ensure only image-valued tags are written. Test with eg.: vips dzsave CMU-1.svs[attach-associated] x.szi unzip the SZI and verify that the associated images are correct: $ unzip -qq ../x.szi $ ls x/associated_images/ label.jpg macro.jpg thumbnail.jpg Thanks to @goran-hc See https://github.com/libvips/libvips/issues/4278 --- ChangeLog | 1 + libvips/foreign/dzsave.c | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index 6ab3082fc2..65724b098c 100644 --- a/ChangeLog +++ b/ChangeLog @@ -6,6 +6,7 @@ - morph: fix C-paths with masks containing zero [kleisauke] - fix `--vips-info` CLI flag with GLib >= 2.80 [kleisauke] - make `subsample-mode=on` and `lossless=true` mutually exclusive [kleisauke] +- fix SZI write with openslide4 [goran-hc] - heifsave: prevent use of AV1 intra block copy feature [lovell] 10/10/24 8.16.0 diff --git a/libvips/foreign/dzsave.c b/libvips/foreign/dzsave.c index 19561cf306..223a3ee566 100644 --- a/libvips/foreign/dzsave.c +++ b/libvips/foreign/dzsave.c @@ -970,7 +970,8 @@ write_associated_images(VipsImage *image, { VipsForeignSaveDz *dz = (VipsForeignSaveDz *) a; - if (vips_isprefix("openslide.associated.", field)) { + if (vips_isprefix("openslide.associated.", field) && + vips_image_get_typeof(image, field) == VIPS_TYPE_IMAGE) { VipsImage *associated; const char *p; const char *q; From 0c7ba7ba03bca5dee197b7f1c8eb092c586170c9 Mon Sep 17 00:00:00 2001 From: Lovell Fuller Date: Wed, 27 Nov 2024 12:44:23 +0000 Subject: [PATCH 18/43] CI: macOS runners no longer include deprecated pkg-config (#4287) --- .github/workflows/ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0121c95a2a..b7c155afee 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -54,7 +54,6 @@ jobs: if: runner.os == 'macOS' run: | pip3 install meson --break-system-packages - brew unlink pkg-config@0.29.2 brew install \ ninja pkgconf \ cfitsio cgif fftw fontconfig glib \ From a379089f4798da362972e504b9e9c0d79856f3ed Mon Sep 17 00:00:00 2001 From: Lovell Fuller Date: Wed, 27 Nov 2024 13:38:09 +0000 Subject: [PATCH 19/43] heifsave: rename intrabc parameter to match upstream (#4288) The previous name was not part of any published release --- libvips/foreign/heifsave.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libvips/foreign/heifsave.c b/libvips/foreign/heifsave.c index 6eab243806..a4806e58ea 100644 --- a/libvips/foreign/heifsave.c +++ b/libvips/foreign/heifsave.c @@ -661,7 +661,7 @@ vips_foreign_save_heif_build(VipsObject *object) * helps ensure encoding time is more predictable. */ error = heif_encoder_set_parameter_boolean(heif->encoder, - "intra-block-copy", FALSE); + "enable-intrabc", FALSE); if (error.code && error.subcode != heif_suberror_Unsupported_parameter) { vips__heif_error(&error); From c9d16c873501ea78eb3353f5a5af7a4302b96eec Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Sat, 30 Nov 2024 16:38:54 +0100 Subject: [PATCH 20/43] threadpool: improve cooperative downsizing (#4293) Turn the exit flag back into a proper count. Fixes a regression introduced in commit 27229aa. --- ChangeLog | 1 + libvips/iofuncs/threadpool.c | 22 ++++++++++++++-------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/ChangeLog b/ChangeLog index 65724b098c..c9e81033b2 100644 --- a/ChangeLog +++ b/ChangeLog @@ -8,6 +8,7 @@ - make `subsample-mode=on` and `lossless=true` mutually exclusive [kleisauke] - fix SZI write with openslide4 [goran-hc] - heifsave: prevent use of AV1 intra block copy feature [lovell] +- threadpool: improve cooperative downsizing [kleisauke] 10/10/24 8.16.0 diff --git a/libvips/iofuncs/threadpool.c b/libvips/iofuncs/threadpool.c index 4706545d92..5d6e7f007a 100644 --- a/libvips/iofuncs/threadpool.c +++ b/libvips/iofuncs/threadpool.c @@ -270,6 +270,11 @@ typedef struct _VipsThreadpool { */ int n_waiting; // (atomic) + /* Increment this and the next worker will decrement and exit if needed + * (used to downsize the threadpool). + */ + int exit; // (atomic) + /* Set this to abort evaluation early with an error. */ gboolean error; @@ -277,11 +282,6 @@ typedef struct _VipsThreadpool { /* Ask threads to exit, either set by allocate, or on free. */ gboolean stop; - - /* Set this and the next worker to see it will clear the flag and exit - * (used to downsize the threadpool). - */ - gboolean exit; // (atomic) } VipsThreadpool; static int @@ -325,7 +325,7 @@ vips_worker_work_unit(VipsWorker *worker) /* Has a thread been asked to exit? Volunteer if yes. */ - if (g_atomic_int_compare_and_exchange(&pool->exit, TRUE, FALSE)) { + if (g_atomic_int_add(&pool->exit, -1) > 0) { /* A thread had been asked to exit, and we've grabbed the * flag. */ @@ -333,6 +333,12 @@ vips_worker_work_unit(VipsWorker *worker) g_mutex_unlock(pool->allocate_lock); return; } + else { + /* No one had been asked to exit and we've mistakenly taken + * the exit count below zero. Put it back up again. + */ + g_atomic_int_inc(&pool->exit); + } if (vips_worker_allocate(worker)) { pool->error = TRUE; @@ -513,7 +519,7 @@ vips_threadpool_new(VipsImage *im) vips_semaphore_init(&pool->tick, 0, "tick"); pool->error = FALSE; pool->stop = FALSE; - pool->exit = FALSE; + pool->exit = 0; /* If this is a tiny image, we won't need all max_workers threads. * Guess how @@ -696,7 +702,7 @@ vips_threadpool_run(VipsImage *im, if (n_waiting > 3 && n_working > 1) { VIPS_DEBUG_MSG("shrinking thread pool\n"); - g_atomic_int_set(&pool->exit, TRUE); + g_atomic_int_inc(&pool->exit); n_working -= 1; } else if (n_waiting < 2 && From 3ba36cedcbec93283c88d2458ef030dea6becd9a Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Wed, 4 Dec 2024 14:44:08 +0000 Subject: [PATCH 21/43] fix alpha shift during colourspace conversion (#4302) * fix alpha shift during colourspace conversion The colour functions were not shifting alpha channels, just casting them, so operations which changed depth, like `icc_transform --depth 8` on a 16-bit image, could have an incorrectly scaled alpha. See https://github.com/libvips/libvips/issues/4301 Thanks frederikrosenberg * credit in changelog --- ChangeLog | 1 + libvips/colour/colour.c | 1 + 2 files changed, 2 insertions(+) diff --git a/ChangeLog b/ChangeLog index c9e81033b2..e13b37c190 100644 --- a/ChangeLog +++ b/ChangeLog @@ -9,6 +9,7 @@ - fix SZI write with openslide4 [goran-hc] - heifsave: prevent use of AV1 intra block copy feature [lovell] - threadpool: improve cooperative downsizing [kleisauke] +- fix alpha shift during colourspace conversions [frederikrosenberg] 10/10/24 8.16.0 diff --git a/libvips/colour/colour.c b/libvips/colour/colour.c index c42dcba8ef..b9e0fa92c0 100644 --- a/libvips/colour/colour.c +++ b/libvips/colour/colour.c @@ -371,6 +371,7 @@ vips_colour_build(VipsObject *object) */ if (vips_cast(extra_bands[i], &t1, out->BandFmt, + "shift", TRUE, NULL)) { g_object_unref(out); return -1; From 33ab2ccfaa81e999bd7e2bf0375d461cef895279 Mon Sep 17 00:00:00 2001 From: Lovell Fuller Date: Mon, 16 Dec 2024 21:27:19 +0000 Subject: [PATCH 22/43] heifsave: set image orientation using irot and imir (#4314) --- ChangeLog | 1 + libvips/foreign/heifsave.c | 8 ++++++++ meson.build | 4 ++++ 3 files changed, 13 insertions(+) diff --git a/ChangeLog b/ChangeLog index e13b37c190..8ff41c5cc9 100644 --- a/ChangeLog +++ b/ChangeLog @@ -10,6 +10,7 @@ - heifsave: prevent use of AV1 intra block copy feature [lovell] - threadpool: improve cooperative downsizing [kleisauke] - fix alpha shift during colourspace conversions [frederikrosenberg] +- heifsave: set image orientation using irot and imir transformations [lovell] 10/10/24 8.16.0 diff --git a/libvips/foreign/heifsave.c b/libvips/foreign/heifsave.c index a4806e58ea..ddb1a40c25 100644 --- a/libvips/foreign/heifsave.c +++ b/libvips/foreign/heifsave.c @@ -311,6 +311,14 @@ vips_foreign_save_heif_write_page(VipsForeignSaveHeif *heif, int page) } #endif /*HAVE_HEIF_ENCODING_OPTIONS_OUTPUT_NCLX_PROFILE*/ +#ifdef HAVE_HEIF_ENCODING_OPTIONS_IMAGE_ORIENTATION + /* EXIF orientation is informational in the HEIF specification. + * Orientation is defined using irot and imir transformations. + */ + options->image_orientation = vips_image_get_orientation(save->ready); + vips_autorot_remove_angle(save->ready); +#endif + #ifdef DEBUG { GTimer *timer = g_timer_new(); diff --git a/meson.build b/meson.build index 21797c1b6f..86ef36bb87 100644 --- a/meson.build +++ b/meson.build @@ -561,6 +561,10 @@ if libheif_dep.found() if libheif_dep.version().version_compare('>=1.13.0') cfg_var.set('HAVE_HEIF_INIT', '1') endif + # heif_encoding_options.image_orientation added in 1.14.0 + if cpp.has_member('struct heif_encoding_options', 'image_orientation', prefix: '#include ', dependencies: libheif_dep) + cfg_var.set('HAVE_HEIF_ENCODING_OPTIONS_IMAGE_ORIENTATION', '1') + endif # heif_error_success added in 1.17.0 if libheif_dep.version().version_compare('>=1.17.0') cfg_var.set('HAVE_HEIF_ERROR_SUCCESS', '1') From 10d28ce95ea55a391be8eb8fa55806d24bc53238 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 26 Dec 2024 13:46:28 +0000 Subject: [PATCH 23/43] guard against /0 in XYZ2Yxy (#4323) Before this PR, converting to Yxy could leave -nan in pixels, eg.: $ vips colourspace k2.jpg x.v yxy $ vips avg x.v (vips:231364): GLib-GObject-CRITICAL **: 16:02:54.674: value "-nan" of type 'gdouble' is invalid or out of range for property 'out' of type 'gdouble' avg: parameter out not set This PR adds a check for 0 before divide. --- ChangeLog | 1 + libvips/colour/XYZ2Yxy.c | 10 ++++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/ChangeLog b/ChangeLog index 8ff41c5cc9..e21ddb51cf 100644 --- a/ChangeLog +++ b/ChangeLog @@ -11,6 +11,7 @@ - threadpool: improve cooperative downsizing [kleisauke] - fix alpha shift during colourspace conversions [frederikrosenberg] - heifsave: set image orientation using irot and imir transformations [lovell] +- XYZ2Yxy: guard against divide by zero 10/10/24 8.16.0 diff --git a/libvips/colour/XYZ2Yxy.c b/libvips/colour/XYZ2Yxy.c index c46e4a74a9..ffc30c3b87 100644 --- a/libvips/colour/XYZ2Yxy.c +++ b/libvips/colour/XYZ2Yxy.c @@ -72,8 +72,14 @@ vips_XYZ2Yxy_line(VipsColour *colour, VipsPel *out, VipsPel **in, int width) p += 3; - x = X / total; - y = Y / total; + if (total == 0.0) { + x = 0; + y = 0; + } + else { + x = X / total; + y = Y / total; + } q[0] = Y; q[1] = x; From 9db1f677c4d84adbc559f5eb518a6d358389d354 Mon Sep 17 00:00:00 2001 From: "k. Naka" <100704180+na-trium-144@users.noreply.github.com> Date: Thu, 2 Jan 2025 00:24:27 +0900 Subject: [PATCH 24/43] workaround C2124 error on MSVC (#4332) * Fix cross-phase operation for real numbers * workaround for MSVC C2142 error --- libvips/arithmetic/complex.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/libvips/arithmetic/complex.c b/libvips/arithmetic/complex.c index 60010d994a..fe62ee04fb 100644 --- a/libvips/arithmetic/complex.c +++ b/libvips/arithmetic/complex.c @@ -449,12 +449,14 @@ G_DEFINE_TYPE(VipsComplex2, vips_complex2, VIPS_TYPE_BINARY); #define CROSS(Q, X1, Y1, X2, Y2) \ { \ if (((X1) == 0.0 && (Y1) == 0.0) || \ - ((X2) == 0.0 && (Y2) == 0.0)) { \ + ((X2) == 0.0 && (Y2) == 0.0) || \ + ((Y1) == 0.0 && (Y2) == 0.0)) { \ Q[0] = 0.0; \ Q[1] = 0.0; \ } \ else if (ABS(Y1) > ABS(Y2)) { \ - double a = Y2 / Y1; \ + double y1 = Y1; /* this suppress C2142 (division by zero) error on MSVC */ \ + double a = Y2 / y1; \ double b = Y1 + Y2 * a; \ double re = (X1 + X2 * a) / b; \ double im = (X2 - X1 * a) / b; \ @@ -464,7 +466,8 @@ G_DEFINE_TYPE(VipsComplex2, vips_complex2, VIPS_TYPE_BINARY); Q[1] = im / mod; \ } \ else { \ - double a = Y1 / Y2; \ + double y2 = Y2; \ + double a = Y1 / y2; \ double b = Y2 + Y1 * a; \ double re = (X1 * a + X2) / b; \ double im = (X2 * a - X1) / b; \ From 07df472140baa9eb8a5f4790cde2ce28135586ea Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 2 Jan 2025 01:07:52 +0000 Subject: [PATCH 25/43] note MSVC compile fix in changelog --- ChangeLog | 1 + 1 file changed, 1 insertion(+) diff --git a/ChangeLog b/ChangeLog index e21ddb51cf..474bc61696 100644 --- a/ChangeLog +++ b/ChangeLog @@ -12,6 +12,7 @@ - fix alpha shift during colourspace conversions [frederikrosenberg] - heifsave: set image orientation using irot and imir transformations [lovell] - XYZ2Yxy: guard against divide by zero +- fix MSVC compile error [na-trium-144] 10/10/24 8.16.0 From 71602f24bf555c59f1fe3efcf99d69e1acc28698 Mon Sep 17 00:00:00 2001 From: Lovell Fuller Date: Sat, 11 Jan 2025 15:42:15 +0000 Subject: [PATCH 26/43] exif: ensure enum entries can be converted to string (#4339) Enumerated entries are often 1 byte in length but as a string are up to around 30 chars in length. Support this common case by reducing the multiplier and adding a constant when calculating the max length of the string. --- ChangeLog | 1 + libvips/foreign/exif.c | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index 474bc61696..540c2fbcff 100644 --- a/ChangeLog +++ b/ChangeLog @@ -13,6 +13,7 @@ - heifsave: set image orientation using irot and imir transformations [lovell] - XYZ2Yxy: guard against divide by zero - fix MSVC compile error [na-trium-144] +- exif: ensure enumerated entries can to converted to string values [lovell] 10/10/24 8.16.0 diff --git a/libvips/foreign/exif.c b/libvips/foreign/exif.c index 3868583134..a73afbf85e 100644 --- a/libvips/foreign/exif.c +++ b/libvips/foreign/exif.c @@ -86,7 +86,7 @@ entry_to_s(ExifEntry *entry) * for formats like float. Ban crazy size values. */ int size = VIPS_MIN(entry->size, 10000); - int max_size = size * 5; + int max_size = size * 3 + 32; char *text = VIPS_MALLOC(NULL, max_size + 1); // this renders floats as eg. "12.2345", enums as "Inch", etc. From dbe46d13db44a0e2baa703cd560d3f5661ab5c01 Mon Sep 17 00:00:00 2001 From: Lovell Fuller Date: Mon, 13 Jan 2025 11:41:21 +0000 Subject: [PATCH 27/43] gifsave: ensure return code is propagated (#4344) - checks for error after final sink disc write - adds support for eval callbacks - adds GIF timeout test --- ChangeLog | 1 + libvips/foreign/cgifsave.c | 7 ++++++ libvips/iofuncs/sinkdisc.c | 4 +++ test/meson.build | 11 +++++++++ test/test_timeout_gifsave.c | 49 +++++++++++++++++++++++++++++++++++++ 5 files changed, 72 insertions(+) create mode 100644 test/test_timeout_gifsave.c diff --git a/ChangeLog b/ChangeLog index 540c2fbcff..b788a87725 100644 --- a/ChangeLog +++ b/ChangeLog @@ -14,6 +14,7 @@ - XYZ2Yxy: guard against divide by zero - fix MSVC compile error [na-trium-144] - exif: ensure enumerated entries can to converted to string values [lovell] +- gifsave: add support for eval callback, ensure correct return code [lovell] 10/10/24 8.16.0 diff --git a/libvips/foreign/cgifsave.c b/libvips/foreign/cgifsave.c index db7e70bffe..c28d5e5f9c 100644 --- a/libvips/foreign/cgifsave.c +++ b/libvips/foreign/cgifsave.c @@ -48,6 +48,7 @@ #include #include +#include #include "pforeign.h" #include "quantise.h" @@ -589,6 +590,12 @@ vips_foreign_save_cgif_write_frame(VipsForeignSaveCgif *cgif) VIPS_FREEF(vips__quantise_image_destroy, image); + /* Remapping is relatively slow, trigger eval callbacks. + */ + vips_image_eval(cgif->in, n_pels); + if (vips_image_iskilled(cgif->in)) + return -1; + /* Set up cgif on first use. */ if (!cgif->cgif_context) { diff --git a/libvips/iofuncs/sinkdisc.c b/libvips/iofuncs/sinkdisc.c index 236c271ac3..86a2549837 100644 --- a/libvips/iofuncs/sinkdisc.c +++ b/libvips/iofuncs/sinkdisc.c @@ -533,6 +533,10 @@ vips_sink_disc(VipsImage *im, VipsRegionWrite write_fn, void *a) vips_image_posteval(im); + /* The final write might have failed, pick up any error code. + */ + result |= write.buf->write_errno; + write_free(&write); vips_image_minimise_all(im); diff --git a/test/meson.build b/test/meson.build index 92b79d782e..90dc7e7779 100644 --- a/test/meson.build +++ b/test/meson.build @@ -89,3 +89,14 @@ test('webpsave_timeout', depends: test_timeout_webpsave, workdir: meson.current_build_dir(), ) + +test_timeout_gifsave = executable('test_timeout_gifsave', + 'test_timeout_gifsave.c', + dependencies: libvips_dep, +) + +test('gifsave_timeout', + test_timeout_gifsave, + depends: test_timeout_gifsave, + workdir: meson.current_build_dir(), +) diff --git a/test/test_timeout_gifsave.c b/test/test_timeout_gifsave.c new file mode 100644 index 0000000000..a47f0cd88e --- /dev/null +++ b/test/test_timeout_gifsave.c @@ -0,0 +1,49 @@ +#include + +#define TIMEOUT_SECONDS 2 + +static void +eval_callback(VipsImage *image, VipsProgress *progress, gboolean *is_killed) +{ + if (progress->run >= TIMEOUT_SECONDS) { + *is_killed = TRUE; + vips_image_set_kill(image, TRUE); + } +} + +int +main(int argc, char **argv) +{ + VipsImage *im; + void *buf; + size_t len; + gboolean is_killed = FALSE; + int ret; + + if (VIPS_INIT(argv[0])) + vips_error_exit(NULL); + + if (!vips_type_find("VipsOperation", "gifsave")) + /* gifsave not available, skip test with return code 77. + */ + return 77; + + if (vips_gaussnoise(&im, 8192, 8192, NULL)) + vips_error_exit(NULL); + + vips_image_set_progress(im, TRUE); + g_signal_connect(im, "eval", + G_CALLBACK(eval_callback), &is_killed); + + buf = NULL; + ret = vips_gifsave_buffer(im, &buf, &len, NULL); + if (!ret) + printf("expected error return from vips_gifsave_buffer()\n"); + + g_object_unref(im); + if (buf) + g_free(buf); + g_assert(is_killed); + + return !ret; +} From d7cc7b98fed1ded6222e617341a6c95417e2045d Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 23 Jan 2025 13:51:43 +0000 Subject: [PATCH 28/43] fix a small leak in fill_nearest --- libvips/morphology/nearest.c | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/libvips/morphology/nearest.c b/libvips/morphology/nearest.c index 7ba764a165..e213e1e66e 100644 --- a/libvips/morphology/nearest.c +++ b/libvips/morphology/nearest.c @@ -85,12 +85,6 @@ vips_fill_nearest_finalize(GObject *gobject) { VipsFillNearest *nearest = (VipsFillNearest *) gobject; -#ifdef DEBUG - printf("vips_fill_nearest_finalize: "); - vips_object_print_name(VIPS_OBJECT(gobject)); - printf("\n"); -#endif /*DEBUG*/ - VIPS_FREEF(g_array_unref, nearest->seeds); G_OBJECT_CLASS(vips_fill_nearest_parent_class)->finalize(gobject); @@ -244,8 +238,7 @@ vips_fill_nearest_build(VipsObject *object) if (i != ps) { Seed *seed; - g_array_set_size(nearest->seeds, - nearest->seeds->len + 1); + g_array_set_size(nearest->seeds, nearest->seeds->len + 1); seed = &g_array_index(nearest->seeds, Seed, nearest->seeds->len - 1); seed->x = x; @@ -261,9 +254,9 @@ vips_fill_nearest_build(VipsObject *object) /* Create the output and distance images in memory. */ g_object_set(object, "distance", vips_image_new_memory(), NULL); - if (vips_black(&t[1], nearest->width, nearest->height, NULL) || - vips_cast(t[1], &t[2], VIPS_FORMAT_FLOAT, NULL) || - vips_image_write(t[2], nearest->distance)) + if (vips_black(&t[0], nearest->width, nearest->height, NULL) || + vips_cast(t[0], &t[1], VIPS_FORMAT_FLOAT, NULL) || + vips_image_write(t[1], nearest->distance)) return -1; g_object_set(object, "out", vips_image_new_memory(), NULL); From f24c62e66657dd28d979e0ed61070978655d8b1d Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 23 Jan 2025 13:52:41 +0000 Subject: [PATCH 29/43] note leak fix in changelog --- ChangeLog | 1 + 1 file changed, 1 insertion(+) diff --git a/ChangeLog b/ChangeLog index b788a87725..53f3711f22 100644 --- a/ChangeLog +++ b/ChangeLog @@ -15,6 +15,7 @@ - fix MSVC compile error [na-trium-144] - exif: ensure enumerated entries can to converted to string values [lovell] - gifsave: add support for eval callback, ensure correct return code [lovell] +- fill_nearest: fix a leak 10/10/24 8.16.0 From 4811f1e4a8a3d89c91b34a90cc8bbb856534b51f Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Thu, 23 Jan 2025 15:01:33 +0100 Subject: [PATCH 30/43] tiffsave: honor disc threshold during pyramid save (#4349) See: https://github.com/kleisauke/net-vips/issues/245. Co-authored-by: John Cupitt --- ChangeLog | 1 + libvips/foreign/vips2tiff.c | 13 ++++++++++--- libvips/iofuncs/target.c | 2 +- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/ChangeLog b/ChangeLog index 53f3711f22..f6ec1fb46b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -15,6 +15,7 @@ - fix MSVC compile error [na-trium-144] - exif: ensure enumerated entries can to converted to string values [lovell] - gifsave: add support for eval callback, ensure correct return code [lovell] +- tiffsave: honor disc threshold during pyramid save [kleisauke] - fill_nearest: fix a leak 10/10/24 8.16.0 diff --git a/libvips/foreign/vips2tiff.c b/libvips/foreign/vips2tiff.c index 7a93d0f3d2..9a85861409 100644 --- a/libvips/foreign/vips2tiff.c +++ b/libvips/foreign/vips2tiff.c @@ -475,9 +475,16 @@ wtiff_layer_init(Wtiff *wtiff, Layer **layer, Layer *above, (*layer)->target = wtiff->target; g_object_ref((*layer)->target); } - else - (*layer)->target = - vips_target_new_temp(wtiff->target); + else { + const guint64 disc_threshold = vips_get_disc_threshold(); + const guint64 layer_size = + VIPS_IMAGE_SIZEOF_PEL(wtiff->ready) * width * height; + + if (layer_size > disc_threshold) + (*layer)->target = vips_target_new_temp(wtiff->target); + else + (*layer)->target = vips_target_new_to_memory(); + } /* printf("wtiff_layer_init: sub = %d, width = %d, height = %d\n", diff --git a/libvips/iofuncs/target.c b/libvips/iofuncs/target.c index df5a409512..66145fbc4a 100644 --- a/libvips/iofuncs/target.c +++ b/libvips/iofuncs/target.c @@ -382,7 +382,7 @@ vips_target_new_to_file(const char *filename) * * See also: vips_target_new_to_file(). * - * Returns: a new #VipsConnection + * Returns: a new target. */ VipsTarget * vips_target_new_to_memory(void) From 35cc76e9eb364725827499ba79171f27f9c9e61a Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Thu, 23 Jan 2025 16:28:52 +0100 Subject: [PATCH 31/43] Fix typo (#4357) --- libvips/foreign/fits.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libvips/foreign/fits.c b/libvips/foreign/fits.c index 03008c3c30..49f1ff958d 100644 --- a/libvips/foreign/fits.c +++ b/libvips/foreign/fits.c @@ -575,7 +575,7 @@ vips_fits_new_write(VipsImage *in, const char *filename) VIPS_IMAGE_SIZEOF_ELEMENT(in) * in->Xsize, VipsPel))) return NULL; - /* fits_create_file() will fail if there's a file of thet name, unless + /* fits_create_file() will fail if there's a file of that name, unless * we put a "!" in front of the filename. This breaks conventions with * the rest of vips, so just unlink explicitly. */ From 3848af5c96fb82bd748fd089e1cddf23e93f97ce Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Fri, 24 Jan 2025 19:22:56 +0100 Subject: [PATCH 32/43] colour: use suggested rendering intent as fallback (#4347) When the requested rendering intent is not supported by the profile, fallback to the profile's suggested intent. An error will only be raised if the suggested intent is also unsupported. Resolves: #3475. --- ChangeLog | 1 + libvips/colour/icc_transform.c | 38 ++++++++++++++++++++++---------- libvips/include/vips/colour.h | 2 ++ test/test-suite/test_resample.py | 2 +- 4 files changed, 30 insertions(+), 13 deletions(-) diff --git a/ChangeLog b/ChangeLog index f6ec1fb46b..0383a9c26c 100644 --- a/ChangeLog +++ b/ChangeLog @@ -17,6 +17,7 @@ - gifsave: add support for eval callback, ensure correct return code [lovell] - tiffsave: honor disc threshold during pyramid save [kleisauke] - fill_nearest: fix a leak +- colour: use suggested rendering intent as fallback [kleisauke] 10/10/24 8.16.0 diff --git a/libvips/colour/icc_transform.c b/libvips/colour/icc_transform.c index 28ef2eae8d..d8263642bb 100644 --- a/libvips/colour/icc_transform.c +++ b/libvips/colour/icc_transform.c @@ -159,6 +159,8 @@ typedef struct _VipsIcc { int depth; gboolean black_point_compensation; + VipsIntent selected_intent; + VipsBlob *in_blob; cmsHPROFILE in_profile; VipsBlob *out_blob; @@ -446,7 +448,7 @@ vips_icc_build(VipsObject *object) if (!(icc->trans = cmsCreateTransform( icc->in_profile, icc->in_icc_format, icc->out_profile, icc->out_icc_format, - icc->intent, flags))) + icc->selected_intent, flags))) return -1; if (VIPS_OBJECT_CLASS(vips_icc_parent_class)->build(object)) @@ -596,8 +598,8 @@ vips_image_is_profile_compatible(VipsImage *image, int profile_bands) * Don't set any errors since this is used to test compatibility. */ static cmsHPROFILE -vips_icc_load_profile_blob(VipsBlob *blob, - VipsImage *image, VipsIntent intent, int direction) +vips_icc_load_profile_blob(VipsIcc *icc, VipsBlob *blob, + VipsImage *image, int direction) { const void *data; size_t size; @@ -607,7 +609,7 @@ vips_icc_load_profile_blob(VipsBlob *blob, #ifdef DEBUG printf("loading %s profile, intent %s, from blob %p\n", direction == LCMS_USED_AS_INPUT ? _("input") : _("output"), - vips_enum_nick(VIPS_TYPE_INTENT, intent), + vips_enum_nick(VIPS_TYPE_INTENT, icc->intent), blob); #endif /*DEBUG*/ @@ -617,6 +619,18 @@ vips_icc_load_profile_blob(VipsBlob *blob, return NULL; } + icc->selected_intent = icc->intent; + if (!cmsIsIntentSupported(profile, icc->intent, direction)) { + icc->selected_intent = (VipsIntent) cmsGetHeaderRenderingIntent( + profile); + + g_warning(_("fallback to suggested %s intent, as profile " + "does not support %s %s intent"), + vips_enum_nick(VIPS_TYPE_INTENT, icc->selected_intent), + vips_enum_nick(VIPS_TYPE_INTENT, icc->intent), + direction == LCMS_USED_AS_INPUT ? _("input") : _("output")); + } + #ifdef DEBUG vips_icc_print_profile("loaded from blob to make", profile); #endif /*DEBUG*/ @@ -634,10 +648,10 @@ vips_icc_load_profile_blob(VipsBlob *blob, return NULL; } - if (!cmsIsIntentSupported(profile, intent, direction)) { + if (!cmsIsIntentSupported(profile, icc->selected_intent, direction)) { VIPS_FREEF(cmsCloseProfile, profile); g_warning(_("profile does not support %s %s intent"), - vips_enum_nick(VIPS_TYPE_INTENT, intent), + vips_enum_nick(VIPS_TYPE_INTENT, icc->selected_intent), direction == LCMS_USED_AS_INPUT ? _("input") : _("output")); return NULL; } @@ -654,8 +668,8 @@ vips_icc_verify_blob(VipsIcc *icc, VipsBlob **blob) { if (*blob) { VipsColourCode *code = (VipsColourCode *) icc; - cmsHPROFILE profile = vips_icc_load_profile_blob(*blob, - code->in, icc->intent, LCMS_USED_AS_INPUT); + cmsHPROFILE profile = vips_icc_load_profile_blob(icc, *blob, + code->in, LCMS_USED_AS_INPUT); if (!profile) { vips_area_unref((VipsArea *) *blob); @@ -1024,8 +1038,8 @@ vips_icc_export_build(VipsObject *object) } if (icc->out_blob && - !(icc->out_profile = vips_icc_load_profile_blob(icc->out_blob, - NULL, icc->intent, LCMS_USED_AS_OUTPUT))) { + !(icc->out_profile = vips_icc_load_profile_blob(icc, icc->out_blob, + NULL, LCMS_USED_AS_OUTPUT))) { vips_error(class->nickname, "%s", _("no output profile")); return -1; } @@ -1188,8 +1202,8 @@ vips_icc_transform_build(VipsObject *object) } if (icc->out_blob) - icc->out_profile = vips_icc_load_profile_blob(icc->out_blob, - NULL, icc->intent, LCMS_USED_AS_OUTPUT); + icc->out_profile = vips_icc_load_profile_blob(icc, icc->out_blob, + NULL, LCMS_USED_AS_OUTPUT); if (!icc->out_profile) { vips_error(class->nickname, "%s", _("no output profile")); diff --git a/libvips/include/vips/colour.h b/libvips/include/vips/colour.h index 895f25e1dd..ed52ccfe56 100644 --- a/libvips/include/vips/colour.h +++ b/libvips/include/vips/colour.h @@ -93,6 +93,8 @@ extern "C" { #define VIPS_D3250_Y0 (100.0) #define VIPS_D3250_Z0 (45.8501) +/* Note: constants align with those defined in lcms2.h. + */ typedef enum { VIPS_INTENT_PERCEPTUAL = 0, VIPS_INTENT_RELATIVE, diff --git a/test/test-suite/test_resample.py b/test/test-suite/test_resample.py index 7bb65b2fc5..51b41072ab 100644 --- a/test/test-suite/test_resample.py +++ b/test/test-suite/test_resample.py @@ -239,7 +239,7 @@ def test_thumbnail(self): @pytest.mark.skipif(not pyvips.at_least_libvips(8, 5), reason="requires libvips >= 8.5") def test_thumbnail_icc(self): - im = pyvips.Image.thumbnail(JPEG_FILE_XYB, 442, export_profile="srgb", intent="perceptual") + im = pyvips.Image.thumbnail(JPEG_FILE_XYB, 442, export_profile="srgb") assert im.width == 290 assert im.height == 442 From 516fee518ef310af7c1d28e979f1f4db0d20e3e9 Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Tue, 28 Jan 2025 11:50:29 +0100 Subject: [PATCH 33/43] morph: fix Orc path with large masks (#4365) Resolves: #4363. --- ChangeLog | 1 + libvips/morphology/morph.c | 13 ++++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index 0383a9c26c..ae0098ca47 100644 --- a/ChangeLog +++ b/ChangeLog @@ -18,6 +18,7 @@ - tiffsave: honor disc threshold during pyramid save [kleisauke] - fill_nearest: fix a leak - colour: use suggested rendering intent as fallback [kleisauke] +- morph: fix Orc path with large masks [kleisauke] 10/10/24 8.16.0 diff --git a/libvips/morphology/morph.c b/libvips/morphology/morph.c index 6f4570e6da..59f0f59c15 100644 --- a/libvips/morphology/morph.c +++ b/libvips/morphology/morph.c @@ -94,6 +94,7 @@ typedef struct { int r; /* Set previous result in this var */ int d1; /* The destination var */ + int n_const; int n_scanline; /* The associated line corresponding to the scanline. @@ -426,6 +427,7 @@ vips_morph_compile_section(VipsMorph *morph, Pass *pass, gboolean first_pass) CONST("zero", 0, 1); CONST("one", 255, 1); + pass->n_const += 2; /* Init the sum. If this is the first pass, it's a constant. If this * is a later pass, we have to init the sum from the result @@ -465,8 +467,10 @@ vips_morph_compile_section(VipsMorph *morph, Pass *pass, gboolean first_pass) */ if (x > 0) { g_snprintf(offset, 256, "c%db", x); - if (orc_program_find_var_by_name(p, offset) == -1) + if (orc_program_find_var_by_name(p, offset) == -1) { CONST(offset, morphology->in->Bands * x, 1); + pass->n_const++; + } ASM3("loadoffb", "value", source, offset); } else @@ -493,6 +497,12 @@ vips_morph_compile_section(VipsMorph *morph, Pass *pass, gboolean first_pass) ASM3("andb", "sum", "sum", "value"); } + /* orc allows up to 8 constants, so break early once we + * approach this limit. + */ + if (pass->n_const >= 7 /*ORC_MAX_CONST_VARS - 1*/) + break; + /* You can have 8 sources, and pass->r counts as one of them, * so +1 there. */ @@ -553,6 +563,7 @@ vips_morph_compile(VipsMorph *morph) pass->first = i; pass->last = i; pass->r = -1; + pass->n_const = 0; pass->n_scanline = 0; if (vips_morph_compile_section(morph, pass, morph->n_pass == 1)) From 0bf64e9b123da13eee4c81e19d5f1b9c7bce8277 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 31 Jan 2025 18:59:55 +0000 Subject: [PATCH 34/43] fix matrixload sniff for some matrix files (#4372) matrixload is_a could fail to detect files like this: ``` 2 2 0 0 1 1 ``` The header parser was not stopping at EOL and thought that this file had scale 0, which it would then reject. --- ChangeLog | 1 + libvips/foreign/matrixload.c | 23 +++++++++++------------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/ChangeLog b/ChangeLog index ae0098ca47..e08152e76e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -19,6 +19,7 @@ - fill_nearest: fix a leak - colour: use suggested rendering intent as fallback [kleisauke] - morph: fix Orc path with large masks [kleisauke] +- matrixload: fix file format detect for some matrix types 10/10/24 8.16.0 diff --git a/libvips/foreign/matrixload.c b/libvips/foreign/matrixload.c index 651e5e9bfe..e27917176a 100644 --- a/libvips/foreign/matrixload.c +++ b/libvips/foreign/matrixload.c @@ -119,13 +119,15 @@ parse_matrix_header(char *line, char *p, *q; int i; - for (i = 0, p = line; - (q = vips_break_token(p, " \t")) && - i < 4; - i++, p = q) + /* Stop at newline. + */ + if ((p = strchr(line, '\r')) || + ((p = strchr(line, '\n')))) + *p = '\0'; + + for (i = 0, p = line; (q = vips_break_token(p, " \t")) && i < 4; i++, p = q) if (vips_strtod(p, &header[i])) { - vips_error("matload", - _("bad number \"%s\""), p); + vips_error("matload", _("bad number \"%s\""), p); return -1; } @@ -152,8 +154,7 @@ parse_matrix_header(char *line, *width > 100000 || *height <= 0 || *height > 100000) { - vips_error("mask2vips", - "%s", _("width / height out of range")); + vips_error("mask2vips", "%s", _("width / height out of range")); return -1; } if (header[2] == 0.0) { @@ -426,14 +427,12 @@ vips_foreign_load_matrix_source_is_a_source(VipsSource *source) double offset; int result; - if ((bytes_read = vips_source_sniff_at_most(source, - &data, 79)) <= 0) + if ((bytes_read = vips_source_sniff_at_most(source, &data, 79)) <= 0) return FALSE; g_strlcpy(line, (const char *) data, 80); vips_error_freeze(); - result = parse_matrix_header(line, - &width, &height, &scale, &offset); + result = parse_matrix_header(line, &width, &height, &scale, &offset); vips_error_thaw(); return result == 0; From 45315458c05ae70f34810932c78adda4593bbce6 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 31 Jan 2025 19:00:31 +0000 Subject: [PATCH 35/43] fix invertlut in some cases (#4373) If the measurements filled the entire x range, we were not writing the final value. For example: ``` 2 2 0 0 1 1 ``` ``` $ vips invertlut linear.mat x2.v $ vips getpoint x2.v 255 0 -nan ``` With this PR you get 1, as expected. --- ChangeLog | 1 + libvips/create/invertlut.c | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index e08152e76e..2e9350d650 100644 --- a/ChangeLog +++ b/ChangeLog @@ -19,6 +19,7 @@ - fill_nearest: fix a leak - colour: use suggested rendering intent as fallback [kleisauke] - morph: fix Orc path with large masks [kleisauke] +- invertlut: fix final value in some cases - matrixload: fix file format detect for some matrix types 10/10/24 8.16.0 diff --git a/libvips/create/invertlut.c b/libvips/create/invertlut.c index ff24aab7eb..02a89baa4c 100644 --- a/libvips/create/invertlut.c +++ b/libvips/create/invertlut.c @@ -212,7 +212,7 @@ vips_invertlut_build_create(VipsInvertlut *lut) /* Interpolate the data sections. */ - for (k = first; k < last; k++) { + for (k = first; k <= last; k++) { /* Where we're at in the [0,1] range. */ double ki = (double) k / (lut->size - 1); From beddac12972a0dbd9664995d51a7910e38dd10e2 Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Fri, 7 Feb 2025 08:44:30 +0100 Subject: [PATCH 36/43] Fix typo in ChangeLog (#4378) --- ChangeLog | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index 2e9350d650..1b8ad61b34 100644 --- a/ChangeLog +++ b/ChangeLog @@ -13,7 +13,7 @@ - heifsave: set image orientation using irot and imir transformations [lovell] - XYZ2Yxy: guard against divide by zero - fix MSVC compile error [na-trium-144] -- exif: ensure enumerated entries can to converted to string values [lovell] +- exif: ensure enumerated entries can be converted to string values [lovell] - gifsave: add support for eval callback, ensure correct return code [lovell] - tiffsave: honor disc threshold during pyramid save [kleisauke] - fill_nearest: fix a leak From ae14d974fe15fb788850db28cb28c318244e9763 Mon Sep 17 00:00:00 2001 From: Lovell Fuller Date: Tue, 11 Feb 2025 11:52:49 +0000 Subject: [PATCH 37/43] radload: improve sanity check of colour-related headers (#4384) Ensure PRIMARIES and COLORCORR have expected number of components --- ChangeLog | 1 + libvips/foreign/radiance.c | 10 ++++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/ChangeLog b/ChangeLog index 1b8ad61b34..79bff1e643 100644 --- a/ChangeLog +++ b/ChangeLog @@ -21,6 +21,7 @@ - morph: fix Orc path with large masks [kleisauke] - invertlut: fix final value in some cases - matrixload: fix file format detect for some matrix types +- radload: improve sanity check of colour-related headers [lovell] 10/10/24 8.16.0 diff --git a/libvips/foreign/radiance.c b/libvips/foreign/radiance.c index 3039d56191..828ce3c2ba 100644 --- a/libvips/foreign/radiance.c +++ b/libvips/foreign/radiance.c @@ -229,8 +229,8 @@ typedef float RGBPRIMS[4][2]; /* (x,y) chromaticities for RGBW */ #define COLCORSTR "COLORCORR=" #define LCOLCORSTR 10 #define iscolcor(hl) (!strncmp(hl, COLCORSTR, LCOLCORSTR)) -#define colcorval(cc, hl) sscanf((hl) + LCOLCORSTR, "%f %f %f", \ - &(cc)[RED], &(cc)[GRN], &(cc)[BLU]) +#define colcorval(cc, hl) (sscanf((hl) + LCOLCORSTR, "%f %f %f", \ + &(cc)[RED], &(cc)[GRN], &(cc)[BLU]) == 3) #define MINELEN 8 /* minimum scanline length for encoding */ #define MAXELEN 0x7fff /* maximum scanline length for encoding */ @@ -643,7 +643,8 @@ rad2vips_process_line(char *line, Read *read) COLOR cc; int i; - (void) colcorval(cc, line); + if (!colcorval(cc, line)) + return -1; for (i = 0; i < 3; i++) read->colcor[i] *= cc[i]; } @@ -651,7 +652,8 @@ rad2vips_process_line(char *line, Read *read) read->aspect *= aspectval(line); } else if (isprims(line)) { - (void) primsval(read->prims, line); + if (!primsval(read->prims, line)) + return -1; } return 0; From 9ab6784f693de50b00fa535b9efbbe9d2cbf71f2 Mon Sep 17 00:00:00 2001 From: Lovell Fuller Date: Wed, 19 Feb 2025 08:06:38 +0000 Subject: [PATCH 38/43] heifsave: reject multiband images (#4392) --- ChangeLog | 1 + libvips/foreign/heifsave.c | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/ChangeLog b/ChangeLog index 79bff1e643..3dbc4c35d2 100644 --- a/ChangeLog +++ b/ChangeLog @@ -22,6 +22,7 @@ - invertlut: fix final value in some cases - matrixload: fix file format detect for some matrix types - radload: improve sanity check of colour-related headers [lovell] +- heifsave: reject multiband images [lovell] 10/10/24 8.16.0 diff --git a/libvips/foreign/heifsave.c b/libvips/foreign/heifsave.c index ddb1a40c25..589b39f055 100644 --- a/libvips/foreign/heifsave.c +++ b/libvips/foreign/heifsave.c @@ -689,6 +689,15 @@ vips_foreign_save_heif_build(VipsObject *object) return -1; } + /* Reject multiband images. + */ + if (save->ready->Type == VIPS_INTERPRETATION_MULTIBAND) { + vips_error("heifsave", _("Unsupported interpretation: %s"), + vips_enum_nick(VIPS_TYPE_INTERPRETATION, + save->ready->Type)); + return -1; + } + /* Make a heif image the size of a page. We send sink_disc() output * here and write a frame each time it fills. */ From 1beb5dd241b27f8fb2608a220db5e51b4e4776cb Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Tue, 25 Feb 2025 18:27:06 +0100 Subject: [PATCH 39/43] heifload: prevent possible int overflow for large images (#4399) i.e. when the `unlimited` flag is set (> 16384x16384). --- ChangeLog | 1 + libvips/foreign/heifload.c | 5 ++++- libvips/foreign/heifsave.c | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/ChangeLog b/ChangeLog index 3dbc4c35d2..796ac7dcb3 100644 --- a/ChangeLog +++ b/ChangeLog @@ -23,6 +23,7 @@ - matrixload: fix file format detect for some matrix types - radload: improve sanity check of colour-related headers [lovell] - heifsave: reject multiband images [lovell] +- heifload: prevent possible int overflow for large images [kleisauke] 10/10/24 8.16.0 diff --git a/libvips/foreign/heifload.c b/libvips/foreign/heifload.c index 3872811abf..f65ed5cb61 100644 --- a/libvips/foreign/heifload.c +++ b/libvips/foreign/heifload.c @@ -351,6 +351,9 @@ vips_foreign_load_heif_build(VipsObject *object) heif->ctx = heif_context_alloc(); #ifdef HAVE_HEIF_SET_MAX_IMAGE_SIZE_LIMIT + /* heifsave is limited to a maximum image size of 16384x16384, + * so align the heifload defaults accordingly. + */ heif_context_set_maximum_image_size_limit(heif->ctx, heif->unlimited ? USHRT_MAX : 0x4000); #endif /* HAVE_HEIF_SET_MAX_IMAGE_SIZE_LIMIT */ @@ -993,7 +996,7 @@ vips_foreign_load_heif_generate(VipsRegion *out_region, } memcpy(VIPS_REGION_ADDR(out_region, 0, r->top), - heif->data + heif->stride * line, + heif->data + (size_t) heif->stride * line, VIPS_IMAGE_SIZEOF_LINE(out_region->im)); /* We may need to swap bytes and shift to fill 16 bits. diff --git a/libvips/foreign/heifsave.c b/libvips/foreign/heifsave.c index 589b39f055..851812e795 100644 --- a/libvips/foreign/heifsave.c +++ b/libvips/foreign/heifsave.c @@ -471,7 +471,7 @@ vips_foreign_save_heif_write_block(VipsRegion *region, VipsRect *area, int page = (area->top + y) / heif->page_height; int line = (area->top + y) % heif->page_height; VipsPel *p = VIPS_REGION_ADDR(region, 0, area->top + y); - VipsPel *q = heif->data + line * heif->stride; + VipsPel *q = heif->data + (size_t) heif->stride * line; if (vips_foreign_save_heif_pack(heif, q, p, VIPS_REGION_N_ELEMENTS(region))) From cce727b4abdb1dca7e6f0ed00989de78255787dc Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Fri, 28 Feb 2025 19:51:07 +0100 Subject: [PATCH 40/43] tiffload: add missing read loop (#4403) We weren't looping on `vips_source_read()` in tiffload, which could cause failures when reading from pipes. Resolves: #4400. --- ChangeLog | 1 + libvips/foreign/tiff.c | 19 ++++++++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index 796ac7dcb3..8b5d4bf15e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -24,6 +24,7 @@ - radload: improve sanity check of colour-related headers [lovell] - heifsave: reject multiband images [lovell] - heifload: prevent possible int overflow for large images [kleisauke] +- tiffload: add missing read loop [kleisauke] 10/10/24 8.16.0 diff --git a/libvips/foreign/tiff.c b/libvips/foreign/tiff.c index 2cf2761384..6668ba9882 100644 --- a/libvips/foreign/tiff.c +++ b/libvips/foreign/tiff.c @@ -98,7 +98,24 @@ openin_source_read(thandle_t st, tdata_t data, tsize_t size) { VipsSource *source = VIPS_SOURCE(st); - return vips_source_read(source, data, size); + gint64 total_read; + + total_read = 0; + + while (total_read < size) { + gint64 bytes_read; + + bytes_read = vips_source_read(source, data, size - total_read); + if (bytes_read == -1) + return -1; + if (bytes_read == 0) + break; + + total_read += bytes_read; + data = (char *) data + bytes_read; + } + + return total_read; } static tsize_t From abcb97c0e5c483f8dd8b07d1124e3548c4bb6bfb Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Wed, 5 Mar 2025 14:49:51 +0100 Subject: [PATCH 41/43] test: reduce severity of `gifsave_timeout` (#4407) --- test/test_timeout_gifsave.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/test/test_timeout_gifsave.c b/test/test_timeout_gifsave.c index a47f0cd88e..7fa1253411 100644 --- a/test/test_timeout_gifsave.c +++ b/test/test_timeout_gifsave.c @@ -18,7 +18,6 @@ main(int argc, char **argv) void *buf; size_t len; gboolean is_killed = FALSE; - int ret; if (VIPS_INIT(argv[0])) vips_error_exit(NULL); @@ -36,14 +35,13 @@ main(int argc, char **argv) G_CALLBACK(eval_callback), &is_killed); buf = NULL; - ret = vips_gifsave_buffer(im, &buf, &len, NULL); - if (!ret) - printf("expected error return from vips_gifsave_buffer()\n"); + if (vips_gifsave_buffer(im, &buf, &len, NULL)) + printf("error return from vips_gifsave_buffer()\n"); g_object_unref(im); if (buf) g_free(buf); g_assert(is_killed); - return !ret; + return 0; } From d0b9bf8a6054c909729bc679aee06ea6a3c4fb75 Mon Sep 17 00:00:00 2001 From: Lovell Fuller Date: Sat, 8 Mar 2025 14:16:29 +0000 Subject: [PATCH 42/43] Prevent possible use-after-free when debugging via --vips-leak flag (#4411) --- ChangeLog | 1 + libvips/iofuncs/type.c | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/ChangeLog b/ChangeLog index 8b5d4bf15e..3b4f347b7e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -25,6 +25,7 @@ - heifsave: reject multiband images [lovell] - heifload: prevent possible int overflow for large images [kleisauke] - tiffload: add missing read loop [kleisauke] +- prevent possible use-after-free when debugging via `--vips-leak` flag [lovell] 10/10/24 8.16.0 diff --git a/libvips/iofuncs/type.c b/libvips/iofuncs/type.c index d6797ae661..8c31240a47 100644 --- a/libvips/iofuncs/type.c +++ b/libvips/iofuncs/type.c @@ -198,14 +198,14 @@ vips_area_unref(VipsArea *area) VIPS_FREEF(vips_g_mutex_free, area->lock); - g_free(area); - if (vips__leak) { g_mutex_lock(vips__global_lock); vips_area_all = g_slist_remove(vips_area_all, area); g_mutex_unlock(vips__global_lock); } + g_free(area); + #ifdef DEBUG g_mutex_lock(vips__global_lock); printf("vips_area_unref: free .. total = %d\n", From 82c7c05cb02a52750251bb4cc69d67f40568cf98 Mon Sep 17 00:00:00 2001 From: Lovell Fuller Date: Sun, 9 Mar 2025 11:13:50 +0000 Subject: [PATCH 43/43] Avoid possible overflow when multiplication result is cast up/down (#4412) --- ChangeLog | 1 + libvips/arithmetic/hist_find_indexed.c | 4 ++-- libvips/arithmetic/project.c | 4 ++-- libvips/colour/LCh2UCS.c | 4 ++-- libvips/conversion/bandfold.c | 2 +- libvips/conversion/bandunfold.c | 2 +- libvips/conversion/composite.cpp | 2 +- libvips/conversion/embed.c | 2 +- libvips/foreign/jp2ksave.c | 2 +- libvips/foreign/nsgifload.c | 2 +- libvips/foreign/tiff2vips.c | 4 ++-- libvips/foreign/vips2tiff.c | 2 +- libvips/foreign/webp2vips.c | 2 +- libvips/foreign/webpsave.c | 2 +- libvips/iofuncs/image.c | 2 +- libvips/iofuncs/sink.c | 2 +- libvips/iofuncs/sinkdisc.c | 2 +- libvips/iofuncs/sinkmemory.c | 2 +- libvips/mosaicing/matrixinvert.c | 2 +- 19 files changed, 23 insertions(+), 22 deletions(-) diff --git a/ChangeLog b/ChangeLog index 3b4f347b7e..5c14bf7d54 100644 --- a/ChangeLog +++ b/ChangeLog @@ -26,6 +26,7 @@ - heifload: prevent possible int overflow for large images [kleisauke] - tiffload: add missing read loop [kleisauke] - prevent possible use-after-free when debugging via `--vips-leak` flag [lovell] +- avoid possible overflow when multiplication result is cast up [lovell] 10/10/24 8.16.0 diff --git a/libvips/arithmetic/hist_find_indexed.c b/libvips/arithmetic/hist_find_indexed.c index 9c61ab7684..e6436d7294 100644 --- a/libvips/arithmetic/hist_find_indexed.c +++ b/libvips/arithmetic/hist_find_indexed.c @@ -117,8 +117,8 @@ histogram_new(VipsHistFindIndexed *indexed) !(hist->reg = vips_region_new(indexed->index_ready))) return NULL; - memset(hist->bins, 0, bands * hist->size * sizeof(double)); - memset(hist->init, 0, hist->size * sizeof(int)); + memset(hist->bins, 0, (size_t) bands * hist->size * sizeof(double)); + memset(hist->init, 0, (size_t) hist->size * sizeof(int)); return hist; } diff --git a/libvips/arithmetic/project.c b/libvips/arithmetic/project.c index e2f7f240df..9585cb0426 100644 --- a/libvips/arithmetic/project.c +++ b/libvips/arithmetic/project.c @@ -109,8 +109,8 @@ histogram_new(VipsProject *project) !hist->row_sums) return NULL; - memset(hist->column_sums, 0, psize * in->Xsize); - memset(hist->row_sums, 0, psize * in->Ysize); + memset(hist->column_sums, 0, (size_t) psize * in->Xsize); + memset(hist->row_sums, 0, (size_t) psize * in->Ysize); return hist; } diff --git a/libvips/colour/LCh2UCS.c b/libvips/colour/LCh2UCS.c index eeb2fe669a..1d3fa92873 100644 --- a/libvips/colour/LCh2UCS.c +++ b/libvips/colour/LCh2UCS.c @@ -165,9 +165,9 @@ vips_col_Ch2hcmc(float C, float h) } P = cos(VIPS_RAD(k7 * h + k8)); - D = k4 + k5 * P * pow(VIPS_FABS(P), k6); + D = k4 + k5 * P * powf(fabsf(P), k6); g = C * C * C * C; - f = sqrt(g / (g + 1900.0)); + f = sqrtf(g / (g + 1900.0F)); hcmc = h + D * f; return hcmc; diff --git a/libvips/conversion/bandfold.c b/libvips/conversion/bandfold.c index 707e6d3187..33dbee4323 100644 --- a/libvips/conversion/bandfold.c +++ b/libvips/conversion/bandfold.c @@ -96,7 +96,7 @@ vips_bandfold_gen(VipsRegion *out_region, /* We can't use vips_region_region() since we change pixel * coordinates. */ - memcpy(q, p, psize * r->width); + memcpy(q, p, (size_t) psize * r->width); } return 0; diff --git a/libvips/conversion/bandunfold.c b/libvips/conversion/bandunfold.c index 6bec18c835..cfc4f9df31 100644 --- a/libvips/conversion/bandunfold.c +++ b/libvips/conversion/bandunfold.c @@ -99,7 +99,7 @@ vips_bandunfold_gen(VipsRegion *out_region, /* We can't use vips_region_region() since we change pixel * coordinates. */ - memcpy(q, p, r->width * psize); + memcpy(q, p, (size_t) r->width * psize); } return 0; diff --git a/libvips/conversion/composite.cpp b/libvips/conversion/composite.cpp index 2c5bafd66f..86f83b4c1a 100644 --- a/libvips/conversion/composite.cpp +++ b/libvips/conversion/composite.cpp @@ -899,7 +899,7 @@ vips_composite_base_blend3(VipsCompositeSequence *seq, /* You can't sqrt a vector, so we must loop. */ for (int b = 0; b < 3; b++) { - double g; + float g; if (B[b] <= 0.25) g = ((16 * B[b] - 12) * B[b] + 4) * B[b]; diff --git a/libvips/conversion/embed.c b/libvips/conversion/embed.c index c1cbf181de..5d4b96b0dd 100644 --- a/libvips/conversion/embed.c +++ b/libvips/conversion/embed.c @@ -217,7 +217,7 @@ vips_embed_base_paint_edge(VipsEmbedBase *base, */ for (y = 0; y < todo.height; y++) { q = VIPS_REGION_ADDR(out_region, todo.left, todo.top + y); - memcpy(q, p, bs * todo.width); + memcpy(q, p, (size_t) bs * todo.width); } } diff --git a/libvips/foreign/jp2ksave.c b/libvips/foreign/jp2ksave.c index bbdc2025ae..58205af906 100644 --- a/libvips/foreign/jp2ksave.c +++ b/libvips/foreign/jp2ksave.c @@ -482,7 +482,7 @@ vips_foreign_save_jp2k_sizeof_tile(VipsForeignSaveJp2k *jp2k, VipsRect *tile) (double) tile->height / comp->dy); ; - size += output_width * output_height * sizeof_element; + size += (size_t) output_width * output_height * sizeof_element; } return size; diff --git a/libvips/foreign/nsgifload.c b/libvips/foreign/nsgifload.c index ba24ff2b8c..366a3f6837 100644 --- a/libvips/foreign/nsgifload.c +++ b/libvips/foreign/nsgifload.c @@ -512,7 +512,7 @@ vips_foreign_load_nsgif_generate(VipsRegion *out_region, gif->frame_number = page; } - p = (VipsPel *) gif->bitmap + line * gif->info->width * sizeof(int); + p = (VipsPel *) gif->bitmap + (size_t) line * gif->info->width * sizeof(int); q = VIPS_REGION_ADDR(out_region, 0, r->top + y); if (gif->has_transparency) memcpy(q, p, VIPS_REGION_SIZEOF_LINE(out_region)); diff --git a/libvips/foreign/tiff2vips.c b/libvips/foreign/tiff2vips.c index 3977031811..dbdfe679f7 100644 --- a/libvips/foreign/tiff2vips.c +++ b/libvips/foreign/tiff2vips.c @@ -1659,7 +1659,7 @@ static void rtiff_memcpy_f16_line(Rtiff *rtiff, VipsPel *q, VipsPel *p, int n, void *client) { VipsImage *im = (VipsImage *) client; - size_t len = n * im->Bands; + size_t len = (size_t) n * im->Bands; if (im->BandFmt == VIPS_FORMAT_COMPLEX || im->BandFmt == VIPS_FORMAT_DPCOMPLEX) @@ -2107,7 +2107,7 @@ rtiff_decompress_jpeg_run(Rtiff *rtiff, j_decompress_ptr cinfo, } jpeg_calc_output_dimensions(cinfo); - bytes_per_scanline = cinfo->output_width * bytes_per_pixel; + bytes_per_scanline = (size_t) cinfo->output_width * bytes_per_pixel; /* Double-check tile dimensions. */ diff --git a/libvips/foreign/vips2tiff.c b/libvips/foreign/vips2tiff.c index 9a85861409..afad50b8bc 100644 --- a/libvips/foreign/vips2tiff.c +++ b/libvips/foreign/vips2tiff.c @@ -2302,7 +2302,7 @@ wtiff_copy_tiles(Wtiff *wtiff, TIFF *out, TIFF *in) * simpler than searching every page for the largest tile with * TIFFTAG_TILEBYTECOUNTS. */ - tile_size = 2 * wtiff->tls * wtiff->tileh; + tile_size = (tsize_t) 2 * wtiff->tls * wtiff->tileh; buf = vips_malloc(NULL, tile_size); diff --git a/libvips/foreign/webp2vips.c b/libvips/foreign/webp2vips.c index 03cd483c13..2844dae8d4 100644 --- a/libvips/foreign/webp2vips.c +++ b/libvips/foreign/webp2vips.c @@ -305,7 +305,7 @@ vips_image_paint_image(VipsImage *frame, } else memcpy((char *) q, (char *) p, - ovl.width * ps); + (size_t) ovl.width * ps); p += VIPS_IMAGE_SIZEOF_LINE(sub); q += VIPS_IMAGE_SIZEOF_LINE(frame); diff --git a/libvips/foreign/webpsave.c b/libvips/foreign/webpsave.c index 452a77d7cc..4a44954e9f 100644 --- a/libvips/foreign/webpsave.c +++ b/libvips/foreign/webpsave.c @@ -355,7 +355,7 @@ vips_foreign_save_webp_sink_disc(VipsRegion *region, VipsRect *area, void *a) memcpy(webp->frame_bytes + area->width * webp->write_y * save->ready->Bands, VIPS_REGION_ADDR(region, 0, area->top + i), - area->width * save->ready->Bands); + (size_t) area->width * save->ready->Bands); webp->write_y += 1; diff --git a/libvips/iofuncs/image.c b/libvips/iofuncs/image.c index 7794a0bf1c..e6bcc4c05d 100644 --- a/libvips/iofuncs/image.c +++ b/libvips/iofuncs/image.c @@ -3241,7 +3241,7 @@ vips_image_write_line(VipsImage *image, int ypos, VipsPel *linebuffer) /* Trigger evaluation callbacks for this image. */ - vips_image_eval(image, ypos * image->Xsize); + vips_image_eval(image, (guint64) ypos * image->Xsize); if (vips_image_iskilled(image)) return -1; diff --git a/libvips/iofuncs/sink.c b/libvips/iofuncs/sink.c index 5ae8ea1e92..80821855fe 100644 --- a/libvips/iofuncs/sink.c +++ b/libvips/iofuncs/sink.c @@ -238,7 +238,7 @@ sink_area_allocate_fn(VipsThreadState *state, void *a, gboolean *stop) /* Add the number of pixels we've just allocated to progress. */ - sink_base->processed += state->pos.width * state->pos.height; + sink_base->processed += (guint64) state->pos.width * state->pos.height; return 0; } diff --git a/libvips/iofuncs/sinkdisc.c b/libvips/iofuncs/sinkdisc.c index 86a2549837..718734fe3e 100644 --- a/libvips/iofuncs/sinkdisc.c +++ b/libvips/iofuncs/sinkdisc.c @@ -410,7 +410,7 @@ wbuffer_allocate_fn(VipsThreadState *state, void *a, gboolean *stop) /* Add the number of pixels we've just allocated to progress. */ - sink_base->processed += state->pos.width * state->pos.height; + sink_base->processed += (guint64) state->pos.width * state->pos.height; return 0; } diff --git a/libvips/iofuncs/sinkmemory.c b/libvips/iofuncs/sinkmemory.c index 7808fc95f6..ba8994f47b 100644 --- a/libvips/iofuncs/sinkmemory.c +++ b/libvips/iofuncs/sinkmemory.c @@ -244,7 +244,7 @@ sink_memory_area_allocate_fn(VipsThreadState *state, void *a, gboolean *stop) /* Add the number of pixels we've just allocated to progress. */ - sink_base->processed += state->pos.width * state->pos.height; + sink_base->processed += (guint64) state->pos.width * state->pos.height; return 0; } diff --git a/libvips/mosaicing/matrixinvert.c b/libvips/mosaicing/matrixinvert.c index 22bbb1cc4a..65303b9fa6 100644 --- a/libvips/mosaicing/matrixinvert.c +++ b/libvips/mosaicing/matrixinvert.c @@ -129,7 +129,7 @@ lu_decomp(VipsImage *mat) /* copy all coefficients and then perform decomposition in-place */ memcpy(VIPS_MATRIX(lu, 0, 0), VIPS_MATRIX(mat, 0, 0), - mat->Xsize * mat->Xsize * sizeof(double)); + (size_t) mat->Xsize * mat->Xsize * sizeof(double)); for (i = 0; i < mat->Xsize; ++i) { row_scale[i] = 0.0;