diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 764008045b..b7c155afee 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -55,7 +55,7 @@ jobs: run: | pip3 install meson --break-system-packages brew install \ - ninja pkg-config \ + ninja pkgconf \ cfitsio cgif fftw fontconfig glib \ highway jpeg-xl libarchive libexif \ libheif libimagequant libmatio librsvg \ diff --git a/ChangeLog b/ChangeLog index a604cfec50..5c14bf7d54 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,33 @@ +8.16.1 + +- support multipage JXL +- 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] +- 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] +- 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] +- 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 +- 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 +- 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] +- 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 - allow small offsets for the PDF magic string [project0] 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; \ 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/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: 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/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; 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; 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/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/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); 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/foreign/dzsave.c b/libvips/foreign/dzsave.c index 922f10776a..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; @@ -1955,7 +1956,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/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. 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. */ diff --git a/libvips/foreign/heifload.c b/libvips/foreign/heifload.c index d50fa045da..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. @@ -1107,12 +1110,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/heifsave.c b/libvips/foreign/heifsave.c index e1040650ac..851812e795 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(); @@ -463,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))) @@ -526,11 +534,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. @@ -659,6 +665,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, + "enable-intrabc", 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(). */ @@ -672,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. */ diff --git a/libvips/foreign/jp2ksave.c b/libvips/foreign/jp2ksave.c index e23fd14ff3..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; @@ -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; 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/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/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; 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/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/ppmsave.c b/libvips/foreign/ppmsave.c index da3f4dff99..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) || @@ -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/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; 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/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 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 7a93d0f3d2..afad50b8bc 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", @@ -2295,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/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/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 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/init.c b/libvips/iofuncs/init.c index 5df713b8c6..75a81abfb2 100644 --- a/libvips/iofuncs/init.c +++ b/libvips/iofuncs/init.c @@ -363,24 +363,17 @@ 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 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 236c271ac3..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; } @@ -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/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/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) 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 && 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", diff --git a/libvips/morphology/morph.c b/libvips/morphology/morph.c index c7637177cd..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. @@ -149,10 +150,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 +212,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 +223,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 +262,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 +292,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 +300,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 +329,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 +359,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 +367,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"); @@ -444,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 @@ -483,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 @@ -511,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. */ @@ -571,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)) @@ -667,8 +660,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 +694,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 +719,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 +747,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 +781,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 +806,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 +886,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); 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); 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; 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; diff --git a/meson.build b/meson.build index 8a7be996f0..86ef36bb87 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) @@ -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') 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-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", 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 diff --git a/test/test_timeout_gifsave.c b/test/test_timeout_gifsave.c new file mode 100644 index 0000000000..7fa1253411 --- /dev/null +++ b/test/test_timeout_gifsave.c @@ -0,0 +1,47 @@ +#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; + + 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; + 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 0; +}