diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ad1e466908..1d0e5a1d3a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,6 +1,8 @@ name: CI -on: [ push, pull_request, workflow_dispatch ] +on: [push, pull_request, workflow_dispatch] + +permissions: {} jobs: CI: @@ -15,13 +17,13 @@ jobs: build: { cc: gcc, cxx: g++, linker: ld } shell: bash - - name: "Linux x64 (Ubuntu 22.04) - Clang 14 with ASan and UBSan" + - name: "Linux x64 (Ubuntu 22.04) - Clang 15 with ASan and UBSan" os: ubuntu-22.04 - build: { cc: clang-14, cxx: clang++-14, linker: ld.lld-14, sanitize: true } + build: { cc: clang-15, cxx: clang++-15, linker: ld.lld-15, sanitize: true } shell: bash - - name: "macOS (12.6) - Xcode 14.2" - os: macos-12 + - name: "macOS (13.6) - Xcode 15.2" + os: macos-13 build: { cc: clang, cxx: clang++, linker: ld.lld } shell: bash @@ -43,7 +45,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Update apt if: runner.os == 'Linux' @@ -71,11 +73,11 @@ jobs: brew install meson ninja fftw fontconfig glib libexif libarchive little-cms2 highway pango pkg-config brew install cfitsio cgif jpeg-xl libheif libimagequant mozjpeg libmatio librsvg libspng libtiff openexr openjpeg openslide poppler webp - - name: Install Clang 14 - if: runner.os == 'Linux' && matrix.build.cc == 'clang-14' + - name: Install Clang 15 + if: runner.os == 'Linux' && matrix.build.cc == 'clang-15' run: sudo apt-get install -fqq - clang-14 libomp-14-dev lld-14 llvm-14 + clang-15 libomp-15-dev lld-15 llvm-15 - name: Prepare macOS environment if: runner.os == 'macOS' @@ -85,7 +87,7 @@ jobs: - name: Prepare sanitizers if: matrix.build.sanitize env: - LLVM_PREFIX: /usr/lib/llvm-14 + LLVM_PREFIX: /usr/lib/llvm-15 run: | ASAN_DSO=`$CC -print-file-name=libclang_rt.asan-x86_64.so` echo "LDSHARED=$CC -shared" >> $GITHUB_ENV @@ -94,8 +96,9 @@ jobs: echo "ASAN_DSO=$ASAN_DSO" >> $GITHUB_ENV # Glib is built without -fno-omit-frame-pointer. We need # to disable the fast unwinder to get full stacktraces. - # FIXME: remove `intercept_tls_get_addr=0` + # FIXME: remove `intercept_tls_get_addr=0` once we update to LLVM 17 # https://github.com/google/sanitizers/issues/1322 + # https://github.com/llvm/llvm-project/commit/b1bd52cd0d8627df1187448b8247a9c7a4675019 echo "ASAN_OPTIONS=suppressions=${{ github.workspace }}/suppressions/asan.supp:fast_unwind_on_malloc=0:allocator_may_return_null=1:intercept_tls_get_addr=0" >> $GITHUB_ENV echo "LSAN_OPTIONS=suppressions=${{ github.workspace }}/suppressions/lsan.supp:fast_unwind_on_malloc=0" >> $GITHUB_ENV echo "TSAN_OPTIONS=suppressions=${{ github.workspace }}/suppressions/tsan.supp" >> $GITHUB_ENV @@ -113,6 +116,7 @@ jobs: meson setup build -Ddebug=true -Ddeprecated=false + -Dmagick=disabled || (cat build/meson-logs/meson-log.txt && exit 1) - name: Build libvips diff --git a/.github/workflows/cifuzz.yml b/.github/workflows/cifuzz.yml index f335c0361e..46d1d0229f 100644 --- a/.github/workflows/cifuzz.yml +++ b/.github/workflows/cifuzz.yml @@ -1,5 +1,6 @@ name: CIFuzz on: [pull_request] +permissions: {} jobs: Fuzzing: runs-on: ubuntu-latest @@ -17,7 +18,7 @@ jobs: fuzz-seconds: 600 dry-run: false - name: Upload Crash - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v4 if: failure() && steps.build.outcome == 'success' with: name: artifacts diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml index f4e36c376b..a877b95d88 100644 --- a/.github/workflows/codespell.yml +++ b/.github/workflows/codespell.yml @@ -11,6 +11,6 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Codespell uses: codespell-project/actions-codespell@v2 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index ed9dacdd0c..6ce65cefbc 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -2,16 +2,18 @@ name: Lint on: [pull_request] +permissions: {} + jobs: cpp-linter: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: cpp-linter/cpp-linter-action@v2 id: linter with: style: file - version: 14 # Ubuntu 22.04 provides clang-format-14 + version: 15 # Ubuntu 22.04 provides clang-format-15 tidy-checks: '-*' # disable clang-tidy lines-changed-only: true # ignore bundled files diff --git a/ChangeLog b/ChangeLog index 5059f23180..717dbb7f89 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,59 @@ +4/10/24 8.15.5 + +- version bump, revise C++ soname + +3/10/24 8.15.4 + +- fix an off-by-one error in vips__token_get() +- heifsave: fix crash when passing an invalid bitdepth [kleisauke] +- webpsave: fix memory leak on error [dloebl] +- heifsave: ensure NCLX profile is freed in lossless mode [kleisauke] +- threadpool: fix a race condition in error handling [kleisauke] +- disable GLib cast checks and asserts for plain builds [kleisauke] +- fix jpeg in tiff for high Q [nahilsobh] +- threadset: fix a race condition during thread exit [kleisauke] +- fix compatibility with MSVC [Julianiolo] + +11/8/24 8.15.3 + +- fix dzsave of >8-bit images to JPEG +- jpegsave: fix chrominance subsampling mode with jpegli [kleisauke] +- pngload: disable ADLER32/CRC checking in non-fail mode [kleisauke] +- improve target_clones support check [kleisauke] +- fix pipe read limit +- fix a rare crash on Windows in highly threaded applications [Julianiolo] +- vipssave: fix infinite loop on Windows with large images [pdbourke] +- conva: fix a crash with {u,}{short,int} images [erdmann] +- fix vips_image_get_string +- heifsave: fix lossless mode [kleisauke] +- composite: fix dest-atop blend mode [kleisauke] +- fix vips_source_map for zero-length sources [kleisauke] + +12/3/24 8.15.2 + +- fix deflate compression of tiff pyramids [manthey] +- thumbnail always writes 8-bit thumbnails [turtletowerz] +- lower min scale factor to 0.0 in svgload and pdfload [lovell] +- heifload: don't warn on images with nclx profiles [kleisauke] +- ppmload: ensure multi-line comments are skipped [lovell] +- fix arrayjoin with some pipelines [TheEssem] +- fix high Q mono JPEG TIFF write with mozjpeg [cavenel] +- tiffsave: ensure large file support (>2GB) on MSVC [kleisauke] +- check linker for target_clones support [lovell] + +18/12/23 8.15.1 + +- reduceh: fix Highway path on SSE2 [DarthSim] +- fix JPEG in TIFF colourspace for Q >= 90 [heman1-test] +- fix build with upcoming libjxl 0.9 [kleisauke] +- jxlsave: lower min effort value to 1 [DarthSim] +- fix build without libjpeg [ionenwks] +- fix vips7 plugin load [jcupitt] +- allow ".jfif" as a suffix for jpegsave [casperbrike] +- don't let the magick sniffer hijack TIFF [kleisauke] +- ignore BLOCKED classes in foreign map [jcupitt] +- add locks to fftw3 calls [akash-akya] + 11/11/23 8.15.0 - add support for target_clones attribute [lovell] @@ -1660,7 +1716,7 @@ _ add vips_foreign_get_suffixes() im_vips2raw(), im_magick2vips(), im_png2vips(), im_png2*(), im_ppm2vips(), im_vips2ppm(), im_mat2vips(), im_rad2vips(), im_vips2rad() redone as classes -- added argument priorites to help control arg ordering +- added argument priorities to help control arg ordering - generate has a 'stop' param to signal successful early termination - added optional output args, eg. x/y for min - CLI supports optional output args diff --git a/doc/meson.build b/doc/meson.build index d9767ccf6c..b870444570 100644 --- a/doc/meson.build +++ b/doc/meson.build @@ -51,7 +51,7 @@ private_libvips_headers = [ private_headers = [] foreach private_libvips_header : private_libvips_headers - private_headers += [join_paths(project_source_root, 'libvips', private_libvips_header)] + private_headers += [project_source_root / 'libvips' / private_libvips_header] endforeach markdown_content_files = files( @@ -73,7 +73,7 @@ if pandoc.found() output: '@BASENAME@.xml', arguments: [ '@INPUT@', - '--template=@0@'.format(join_paths(meson.current_source_dir(), 'pandoc-docbook-template.docbook')), + '--template=@0@'.format(meson.current_source_dir() / 'pandoc-docbook-template.docbook'), '--wrap=none', '-V', 'title=@BASENAME@', '-f', 'markdown+smart', @@ -134,25 +134,21 @@ version_xml = configure_file( ) glib_prefix = glib_dep.get_variable(pkgconfig: 'prefix') -glib_docpath = glib_prefix / 'share' / 'gtk-doc' / 'html' docpath = get_option('prefix') / get_option('datadir') / 'gtk-doc' / 'html' gnome.gtkdoc('libvips', - mode: 'none', main_xml: 'libvips-docs.xml', - src_dir: [ - join_paths(project_source_root, 'libvips'), - join_paths(project_build_root, 'libvips'), - ], + namespace: 'vips', + src_dir: 'libvips', dependencies: libvips_dep, ignore_headers: private_headers, - scan_args: '--rebuild-types', + scan_args: [ '--rebuild-types' ], mkdb_args: [ '--default-includes=vips/vips.h' ], fixxref_args: [ '--html-dir=@0@'.format(docpath), - '--extra-dir=@0@'.format(join_paths(glib_docpath, 'glib')), - '--extra-dir=@0@'.format(join_paths(glib_docpath, 'gobject')), - '--extra-dir=@0@'.format(join_paths(glib_docpath, 'gio')), + '--extra-dir=@0@'.format(glib_prefix / gnome.gtkdoc_html_dir('glib')), + '--extra-dir=@0@'.format(glib_prefix / gnome.gtkdoc_html_dir('gobject')), + '--extra-dir=@0@'.format(glib_prefix / gnome.gtkdoc_html_dir('gio')), ], content_files: [ content_files, markdown_content_files_docbook, version_xml ], expand_content_files: [ content_files ], diff --git a/fuzz/oss_fuzz_build.sh b/fuzz/oss_fuzz_build.sh index 8dfec97c53..be2c94b4cb 100755 --- a/fuzz/oss_fuzz_build.sh +++ b/fuzz/oss_fuzz_build.sh @@ -89,18 +89,6 @@ make -j$(nproc) make install popd -# libpng -pushd $SRC/libpng -sed -ie 's/option WARNING /& disabled/' scripts/pnglibconf.dfa -autoreconf -fi -./configure \ - --prefix=$WORK \ - --disable-shared \ - --disable-dependency-tracking -make -j$(nproc) -make install -popd - # libspng pushd $SRC/libspng meson setup build --prefix=$WORK --libdir=lib --default-library=static --buildtype=debugoptimized \ @@ -127,7 +115,7 @@ make -j$(nproc) make install popd -# libtiff ... a bug in libtiff master as of 20 Nov 2019 means we have to +# libtiff ... a bug in libtiff master as of 20 Nov 2019 means we have to # explicitly disable lzma pushd $SRC/libtiff autoreconf -fi diff --git a/libvips/conversion/arrayjoin.c b/libvips/conversion/arrayjoin.c index af86529e43..500dbe945e 100644 --- a/libvips/conversion/arrayjoin.c +++ b/libvips/conversion/arrayjoin.c @@ -6,6 +6,8 @@ * - minmise inputs once we've used them * 29/12/22 * - much faster with large arrays + * 29/1/24 + * - render and don't forward pixels for complete subregions */ /* @@ -112,16 +114,24 @@ vips_arrayjoin_gen(VipsRegion *out_region, * forward the request. */ if (width == 1 && height == 1) { + VipsRect need; + i = VIPS_MIN(n - 1, left + top * join->across); - reg = vips_region_new(in[i]); + /* The part of in[i] we need. + */ + need = out_region->valid; + need.left -= join->rects[i].left; + need.top -= join->rects[i].top; - if (vips__insert_just_one(out_region, reg, - join->rects[i].left, join->rects[i].top)) { + /* And render into out_region. We can't just forward a pointer since + * we are about to unref reg. + */ + reg = vips_region_new(in[i]); + if (vips_region_prepare_to(reg, out_region, &need, r->left, r->top)) { g_object_unref(reg); return -1; } - g_object_unref(reg); } else { @@ -132,8 +142,7 @@ vips_arrayjoin_gen(VipsRegion *out_region, for (y = 0; y < height; y++) for (x = 0; x < width; x++) { - i = VIPS_MIN(n - 1, - x + left + (y + top) * join->across); + i = VIPS_MIN(n - 1, x + left + (y + top) * join->across); reg = vips_region_new(in[i]); @@ -147,16 +156,15 @@ vips_arrayjoin_gen(VipsRegion *out_region, } } + /* In sequential mode, we can minimise an input once our generate point + * is well past the end of it. This can save a lot of memory and file + * descriptors on large image arrays. + * + * minimise_all is quite expensive, so only trigger once for each input. + * + * We don't lock for minimised[], but it's harmless. + */ if (vips_image_is_sequential(conversion->out)) - /* In sequential mode, we can minimise an input once our - * generate point is well past the end of it. This can save a - * lot of memory and file descriptors on large image arrays. - * - * minimise_all is quite expensive, so only trigger once for - * each input. - * - * We don't lock for minimised[], but it's harmless. - */ for (i = 0; i < n; i++) { int bottom_edge = VIPS_RECT_BOTTOM(&join->rects[i]); @@ -199,6 +207,11 @@ vips_arrayjoin_build(VipsObject *object) if (n == 0) return -1; + for (i = 0; i < n; i++) + if (vips_image_pio_input(in[i]) || + vips_check_coding_known(class->nickname, in[i])) + return -1; + /* Move all input images to a common format and number of bands. */ format = (VipsImage **) vips_object_local_array(object, n); diff --git a/libvips/conversion/composite.cpp b/libvips/conversion/composite.cpp index 849ca91237..c368469c12 100644 --- a/libvips/conversion/composite.cpp +++ b/libvips/conversion/composite.cpp @@ -583,7 +583,7 @@ vips_composite_base_blend(VipsCompositeBase *composite, case VIPS_BLEND_MODE_DEST_ATOP: aR = aA; - t1 = 1 - aA; + t1 = 1 - aB; for (int b = 0; b < bands; b++) B[b] = t1 * A[b] + B[b]; break; @@ -827,7 +827,7 @@ vips_composite_base_blend3(VipsCompositeSequence *seq, case VIPS_BLEND_MODE_DEST_ATOP: aR = aA; - t1 = 1 - aA; + t1 = 1 - aB; B = t1 * A + B; break; diff --git a/libvips/conversion/insert.c b/libvips/conversion/insert.c index 2493423e6f..f2fa42c92b 100644 --- a/libvips/conversion/insert.c +++ b/libvips/conversion/insert.c @@ -116,10 +116,8 @@ typedef VipsConversionClass VipsInsertClass; G_DEFINE_TYPE(VipsInsert, vips_insert, VIPS_TYPE_CONVERSION); /* Trivial case: we just need pels from one of the inputs. - * - * Also used by vips_arrayjoin. */ -int +static int vips__insert_just_one(VipsRegion *out_region, VipsRegion *ir, int x, int y) { VipsRect need; diff --git a/libvips/convolution/conva.c b/libvips/convolution/conva.c index 717505fa2e..fcb6f81178 100644 --- a/libvips/convolution/conva.c +++ b/libvips/convolution/conva.c @@ -177,7 +177,7 @@ typedef struct { /* The horizontal lines we gather. hline[3] writes to band 3 in the * intermediate image. max_line is the length of the longest hline: - * over 256 and we need to use an int intermediate for 8-bit images. + * over 256 and we need to use an int intermediate for integer images. */ int n_hline; HLine hline[MAX_LINES]; @@ -933,33 +933,39 @@ vips_conva_hgenerate(VipsRegion *out_region, break; case VIPS_FORMAT_USHORT: - HCONV(unsigned short, unsigned int); + if (conva->max_line < 256) + HCONV(unsigned short, unsigned short); + else + HCONV(unsigned short, unsigned int); break; case VIPS_FORMAT_SHORT: - HCONV(signed short, signed int); + if (conva->max_line < 256) + HCONV(signed short, signed short); + else + HCONV(signed short, signed int); break; case VIPS_FORMAT_UINT: - HCONV(unsigned int, unsigned int); + if (conva->max_line < 256) + HCONV(unsigned int, unsigned short); + else + HCONV(unsigned int, unsigned int); break; case VIPS_FORMAT_INT: - HCONV(signed int, signed int); + if (conva->max_line < 256) + HCONV(signed int, signed short); + else + HCONV(signed int, signed int); break; case VIPS_FORMAT_FLOAT: - HCONV(float, float); - break; - - case VIPS_FORMAT_DOUBLE: - HCONV(double, double); - break; - case VIPS_FORMAT_COMPLEX: HCONV(float, float); break; + case VIPS_FORMAT_DOUBLE: case VIPS_FORMAT_DPCOMPLEX: HCONV(double, double); break; @@ -993,7 +999,7 @@ vips_conva_horizontal(VipsConva *conva, VipsImage *in, VipsImage **out) } (*out)->Bands *= conva->n_hline; - /* Short u?char lines can use u?short intermediate. + /* Short {u,}{char,short,int} lines can use {u,}short as intermediate. */ if (vips_band_format_isuint(in->BandFmt)) (*out)->BandFmt = conva->max_line < 256 @@ -1175,34 +1181,41 @@ vips_conva_vgenerate(VipsRegion *out_region, break; case VIPS_FORMAT_USHORT: - VCONV(unsigned int, - unsigned int, unsigned short, CLIP_USHORT); + if (conva->max_line < 256) + VCONV(unsigned int, + unsigned short, unsigned short, CLIP_USHORT); + else + VCONV(unsigned int, + unsigned int, unsigned short, CLIP_USHORT); break; case VIPS_FORMAT_SHORT: - VCONV(signed int, signed int, signed short, CLIP_SHORT); + if (conva->max_line < 256) + VCONV(signed int, signed short, signed short, CLIP_SHORT); + else + VCONV(signed int, signed int, signed short, CLIP_SHORT); break; case VIPS_FORMAT_UINT: - VCONV(unsigned int, unsigned int, unsigned int, CLIP_NONE); + if (conva->max_line < 256) + VCONV(unsigned int, unsigned short, unsigned int, CLIP_SHORT); + else + VCONV(unsigned int, unsigned int, unsigned int, CLIP_NONE); break; case VIPS_FORMAT_INT: - VCONV(signed int, signed int, signed int, CLIP_NONE); + if (conva->max_line < 256) + VCONV(signed int, signed short, signed int, CLIP_NONE); + else + VCONV(signed int, signed int, signed int, CLIP_NONE); break; case VIPS_FORMAT_FLOAT: - VCONV(float, float, float, CLIP_NONE); - break; - - case VIPS_FORMAT_DOUBLE: - VCONV(double, double, double, CLIP_NONE); - break; - case VIPS_FORMAT_COMPLEX: VCONV(float, float, float, CLIP_NONE); break; + case VIPS_FORMAT_DOUBLE: case VIPS_FORMAT_DPCOMPLEX: VCONV(double, double, double, CLIP_NONE); break; diff --git a/libvips/create/buildlut.c b/libvips/create/buildlut.c index c84ba6bc15..d2405d61c2 100644 --- a/libvips/create/buildlut.c +++ b/libvips/create/buildlut.c @@ -284,29 +284,47 @@ vips_buildlut_init(VipsBuildlut *lut) * * For example, consider this 2 x 2 matrix of (x, y) coordinates: * - * |[ - * |-------|-------| - * | 0 | 0 | - * |-------|-------| - * | 255 | 100 | - * |-------|-------| - * ]| + * + * + * + * 0 + * 0 + * + * + * 255 + * 100 + * + * + * * * We then generate a 1 x 256 element LUT like this: * - * |[ - * |-------|-------| - * | Index | Value | - * |-------|-------| - * | 0 | 0 | - * |-------|-------| - * | 1 | 0.4 | - * |-------|-------| - * | etc. | 0.4 | - * |-------|-------| - * | 255 | 100 | - * |-------|-------| - * ]| + * + * + * + * Index + * Value + * + * + * + * + * 0 + * 0 + * + * + * 1 + * 0.4 + * + * + * etc. + * 0.4 + * + * + * 255 + * 100 + * + * + * * * This is then written as the output image, with the left column giving the * index in the image to place the value. diff --git a/libvips/create/invertlut.c b/libvips/create/invertlut.c index 9c6fd76bf2..ff24aab7eb 100644 --- a/libvips/create/invertlut.c +++ b/libvips/create/invertlut.c @@ -321,15 +321,32 @@ vips_invertlut_init(VipsInvertlut *lut) * * Eg. input like this: * - * |[ - * |-------|-------|-------|-------| - * | 0.1 | 0.2 | 0.3 | 0.1 | - * |-------|-------|-------|-------| - * | 0.2 | 0.4 | 0.4 | 0.2 | - * |-------|-------|-------|-------| - * | 0.7 | 0.5 | 0.6 | 0.3 | - * |-------|-------|-------|-------| - * ]| + * + * + * + * 4 + * 3 + * + * + * 0.1 + * 0.2 + * 0.3 + * 0.1 + * + * + * 0.2 + * 0.4 + * 0.4 + * 0.2 + * + * + * 0.7 + * 0.5 + * 0.6 + * 0.3 + * + * + * * * Means a patch with 10% reflectance produces an image with 20% in * channel 1, 30% in channel 2, and 10% in channel 3, and so on. diff --git a/libvips/deprecated/package.c b/libvips/deprecated/package.c index 98b0d628d9..1f27e5ce19 100644 --- a/libvips/deprecated/package.c +++ b/libvips/deprecated/package.c @@ -323,7 +323,7 @@ static int getext_vec(im_object *argv) { void **out = (void **) &argv[1]; - int size; + size_t size; /* void/char confusion is fine. */ @@ -662,9 +662,7 @@ im_load_plugin(const char *name) #ifdef ENABLE_MODULES Plugin *plug; -#ifdef DEBUG - printf("im_load_plugin: \"%s\"\n", name); -#endif /*DEBUG*/ + g_info("im_load_plugin: loading \"%s\" ...", name); if (!g_module_supported()) { vips_error("plugin", @@ -718,9 +716,7 @@ im_load_plugin(const char *name) return NULL; } -#ifdef DEBUG - printf("added package \"%s\"\n", plug->pack->name); -#endif /*DEBUG*/ + g_info("im_load_plugin: added package \"%s\"", plug->pack->name); return plug->pack; #else /*!ENABLE_MODULES*/ @@ -753,9 +749,7 @@ im_load_plugins(const char *fmt, ...) (void) im_vsnprintf(dir_name, VIPS_PATH_MAX - 1, fmt, ap); va_end(ap); -#ifdef DEBUG - printf("im_load_plugins: searching \"%s\"\n", dir_name); -#endif /*DEBUG*/ + g_info("im_load_plugins: searching \"%s\"\n", dir_name); if (!(dir = g_dir_open(dir_name, 0, NULL))) /* Silent success for dir not there. diff --git a/libvips/foreign/dzsave.c b/libvips/foreign/dzsave.c index 8acbc07373..40d0416f8a 100644 --- a/libvips/foreign/dzsave.c +++ b/libvips/foreign/dzsave.c @@ -1930,6 +1930,15 @@ pyramid_strip(VipsRegion *region, VipsRect *area, void *a) return 0; } +#define UC VIPS_FORMAT_UCHAR + +/* We force all types to uchar for save. + */ +static VipsBandFormat bandfmt_dzsave[10] = { + /* Band format: UC C US S UI I F X D DX */ + /* Promotion: */ UC, UC, UC, UC, UC, UC, UC, UC, UC, UC +}; + static int vips_foreign_save_dz_build(VipsObject *object) { @@ -2050,6 +2059,26 @@ vips_foreign_save_dz_build(VipsObject *object) save->ready = z; } + /* If we're saving to direct JPEG, we need to convert to 8-bit RGB | + * mono | cmyk. + */ + if (dz->direct) { + VipsImage *z; + gboolean coding[VIPS_CODING_LAST]; + + for (int i = 0; i < VIPS_CODING_LAST; i++) + coding[i] = FALSE; + coding[VIPS_CODING_NONE] = TRUE; + + if (vips__foreign_convert_saveable(save->ready, &z, + VIPS_SAVEABLE_RGB_CMYK, bandfmt_dzsave, coding, + save->background)) + return -1; + + VIPS_UNREF(save->ready); + save->ready = z; + } + /* We use ink to check for blank tiles. */ if (dz->skip_blanks >= 0) { @@ -2073,7 +2102,6 @@ vips_foreign_save_dz_build(VipsObject *object) * again. */ if (dz->layout == VIPS_FOREIGN_DZ_LAYOUT_GOOGLE) { - VipsImage *z; Level *level; Level *p; diff --git a/libvips/foreign/foreign.c b/libvips/foreign/foreign.c index 6c30abc7b2..c99662a240 100644 --- a/libvips/foreign/foreign.c +++ b/libvips/foreign/foreign.c @@ -421,13 +421,20 @@ vips_foreign_init(VipsForeign *object) static void * file_add_class(VipsForeignClass *class, GSList **files) { - /* We exclude "rawload" as it has a different API. + VipsOperationClass *operation_class = VIPS_OPERATION_CLASS(class); + + // don't consider blocked classes ... we don't want eg. sniffers to run + if (operation_class->flags & VIPS_OPERATION_BLOCKED) + return NULL; + + // exclude "rawload" as it has a different API. + if (vips_isprefix("rawload", VIPS_OBJECT_CLASS(class)->nickname)) + return NULL; + + /* Append so we don't reverse the list of files. Sort will + * not reorder items of equal priority. */ - if (!vips_isprefix("rawload", VIPS_OBJECT_CLASS(class)->nickname)) - /* Append so we don't reverse the list of files. Sort will - * not reorder items of equal priority. - */ - *files = g_slist_append(*files, class); + *files = g_slist_append(*files, class); return NULL; } @@ -1022,7 +1029,7 @@ vips_foreign_load_start(VipsImage *out, void *a, void *b) * Some versions of ImageMagick give different results between * Ping and Load for some formats, for example. * - * If the load fails, we need to stop + * If the load fails, we need to stop. */ if (class->load(load) || vips_image_pio_input(load->real) || @@ -1794,8 +1801,7 @@ vips_foreign_save_build(VipsObject *object) save->keep |= VIPS_FOREIGN_KEEP_ICC; if (save->in) { - VipsForeignSaveClass *class = - VIPS_FOREIGN_SAVE_GET_CLASS(save); + VipsForeignSaveClass *class = VIPS_FOREIGN_SAVE_GET_CLASS(save); VipsImage *ready; VipsImage *x; diff --git a/libvips/foreign/heifload.c b/libvips/foreign/heifload.c index b1e0581fca..b596d7b397 100644 --- a/libvips/foreign/heifload.c +++ b/libvips/foreign/heifload.c @@ -708,7 +708,7 @@ vips_foreign_load_heif_set_header(VipsForeignLoadHeif *heif, VipsImage *out) (VipsCallbackFn) vips_area_free_cb, data, length); } else if (profile_type == heif_color_profile_type_nclx) { - g_warning("heifload: ignoring nclx profile"); + g_info("heifload: ignoring nclx profile"); } #endif /*HAVE_HEIF_COLOR_PROFILE*/ diff --git a/libvips/foreign/heifsave.c b/libvips/foreign/heifsave.c index ffd50a6c2b..5dc9310250 100644 --- a/libvips/foreign/heifsave.c +++ b/libvips/foreign/heifsave.c @@ -270,6 +270,9 @@ vips_foreign_save_heif_write_page(VipsForeignSaveHeif *heif, int page) struct heif_error error; struct heif_encoding_options *options; +#ifdef HAVE_HEIF_ENCODING_OPTIONS_OUTPUT_NCLX_PROFILE + struct heif_color_profile_nclx *nclx = NULL; +#endif #ifdef HAVE_HEIF_COLOR_PROFILE /* A profile supplied as an argument overrides an embedded @@ -289,6 +292,24 @@ vips_foreign_save_heif_write_page(VipsForeignSaveHeif *heif, int page) if (vips_image_hasalpha(save->ready)) options->save_alpha_channel = 1; +#ifdef HAVE_HEIF_ENCODING_OPTIONS_OUTPUT_NCLX_PROFILE + /* Matrix coefficients have to be identity (CICP x/y/0) in lossless mode. + */ + if (heif->lossless) { + if (!(nclx = heif_nclx_color_profile_alloc())) { + heif_encoding_options_free(options); + return -1; + } + + nclx->matrix_coefficients = heif_matrix_coefficients_RGB_GBR; + options->output_nclx_profile = nclx; + + /* Ensure nclx profile is actually written with libheif < v1.17.2. + */ + options->macOS_compatibility_workaround_no_nclx_profile = 0; + } +#endif /*HAVE_HEIF_ENCODING_OPTIONS_OUTPUT_NCLX_PROFILE*/ + #ifdef DEBUG { GTimer *timer = g_timer_new(); @@ -307,6 +328,9 @@ vips_foreign_save_heif_write_page(VipsForeignSaveHeif *heif, int page) #endif /*DEBUG*/ heif_encoding_options_free(options); +#ifdef HAVE_HEIF_ENCODING_OPTIONS_OUTPUT_NCLX_PROFILE + VIPS_FREEF(heif_nclx_color_profile_free, nclx); +#endif if (error.code) { vips__heif_error(&error); @@ -409,10 +433,10 @@ vips_foreign_save_heif_pack(VipsForeignSaveHeif *heif, } } else { - VipsObjectClass *class = VIPS_OBJECT_CLASS(heif); + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS(heif); vips_error(class->nickname, - "%s", _("unimplemeted format conversion")); + "%s", _("unimplemented format conversion")); return -1; } @@ -501,8 +525,14 @@ vips_foreign_save_heif_build(VipsObject *object) !vips_object_argument_isset(object, "effort")) heif->effort = 9 - heif->speed; - /* Default 12 bit save for 16-bit images. HEIC (for example) implements - * 8 / 10 / 12. + /* Disable chroma subsampling by default when the "lossless" param + * is being used. + */ + if (vips_object_argument_isset(object, "lossless") && + !vips_object_argument_isset(object, "subsample_mode")) + heif->subsample_mode = VIPS_FOREIGN_SUBSAMPLE_OFF; + + /* Default 12 bit save for 16-bit images. */ if (!vips_object_argument_isset(object, "bitdepth")) heif->bitdepth = @@ -511,6 +541,16 @@ vips_foreign_save_heif_build(VipsObject *object) ? 12 : 8; + /* HEIC and AVIF only implements 8 / 10 / 12 bit depth. + */ + if (heif->bitdepth != 12 && + heif->bitdepth != 10 && + heif->bitdepth != 8) { + vips_error("heifsave", _("%d-bit colour depth not supported"), + heif->bitdepth); + return -1; + } + /* Try to find the selected encoder. */ if (heif->selected_encoder != VIPS_FOREIGN_HEIF_ENCODER_AUTO) { @@ -693,7 +733,7 @@ vips_foreign_save_heif_class_init(VipsForeignSaveHeifClass *class) _("Number of bits per pixel"), VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET(VipsForeignSaveHeif, bitdepth), - 1, 16, 12); + 8, 12, 12); VIPS_ARG_BOOL(class, "lossless", 13, _("Lossless"), diff --git a/libvips/foreign/jxlload.c b/libvips/foreign/jxlload.c index 9af2d6e767..a5babcdb4a 100644 --- a/libvips/foreign/jxlload.c +++ b/libvips/foreign/jxlload.c @@ -564,7 +564,9 @@ vips_foreign_load_jxl_header(VipsForeignLoad *load) case JXL_DEC_COLOR_ENCODING: if (JxlDecoderGetICCProfileSize(jxl->decoder, +#ifndef HAVE_LIBJXL_0_9 &jxl->format, +#endif JXL_COLOR_PROFILE_TARGET_DATA, &jxl->icc_size)) { vips_foreign_load_jxl_error(jxl, @@ -583,7 +585,9 @@ vips_foreign_load_jxl_header(VipsForeignLoad *load) return -1; if (JxlDecoderGetColorAsICCProfile(jxl->decoder, +#ifndef HAVE_LIBJXL_0_9 &jxl->format, +#endif JXL_COLOR_PROFILE_TARGET_DATA, jxl->icc_data, jxl->icc_size)) { vips_foreign_load_jxl_error(jxl, diff --git a/libvips/foreign/jxlsave.c b/libvips/foreign/jxlsave.c index 19230e6e53..00319fc5ff 100644 --- a/libvips/foreign/jxlsave.c +++ b/libvips/foreign/jxlsave.c @@ -243,13 +243,16 @@ vips_foreign_save_jxl_build(VipsObject *object) return -1; /* If Q is set and distance is not, use Q to set a rough distance - * value. Formula stolen from cjxl.c and very roughly approximates - * libjpeg values. + * value. */ if (!vips_object_argument_isset(object, "distance")) +#ifdef HAVE_LIBJXL_0_9 + jxl->distance = JxlEncoderDistanceFromQuality((float) jxl->Q); +#else jxl->distance = jxl->Q >= 30 ? 0.1 + (100 - jxl->Q) * 0.09 : 53.0 / 3000.0 * jxl->Q * jxl->Q - 23.0 / 20.0 * jxl->Q + 25.0; +#endif /* Distance 0 is lossless. libjxl will fail for lossy distance 0. */ @@ -558,7 +561,7 @@ vips_foreign_save_jxl_class_init(VipsForeignSaveJxlClass *class) _("Encoding effort"), VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET(VipsForeignSaveJxl, effort), - 3, 9, 7); + 1, 9, 7); VIPS_ARG_BOOL(class, "lossless", 13, _("Lossless"), diff --git a/libvips/foreign/magick.c b/libvips/foreign/magick.c index 1af3fdcb42..6f4ffba8a2 100644 --- a/libvips/foreign/magick.c +++ b/libvips/foreign/magick.c @@ -52,7 +52,7 @@ #if defined(HAVE_MAGICK6) || defined(HAVE_MAGICK7) -/* ImageMagick can't detect some formats, like ICO and TGA, by examining the +/* ImageMagick can't detect some formats (eg. ICO and TGA) by examining the * contents -- ico.c and tga.c simply do not have recognisers. * * For these formats, do the detection ourselves. @@ -115,8 +115,17 @@ magick_sniff(const unsigned char *bytes, size_t length) const MagicInfo *magic_info = GetMagicInfo(bytes, length, exception); magick_destroy_exception(exception); - if (magic_info) - return GetMagicName(magic_info); + if (magic_info) { + const char *magic_name = GetMagicName(magic_info); + + /* Avoid using TIFF as a format hint since RAW/DNG images often + * share the same magic signature as TIFF. + */ + if (magic_name && + g_ascii_strcasecmp(magic_name, "TIFF") != 0) { + return magic_name; + } + } #endif return NULL; diff --git a/libvips/foreign/pdfiumload.c b/libvips/foreign/pdfiumload.c index 09f74b1773..47ea3ec48c 100644 --- a/libvips/foreign/pdfiumload.c +++ b/libvips/foreign/pdfiumload.c @@ -739,7 +739,7 @@ vips_foreign_load_pdf_class_init(VipsForeignLoadPdfClass *class) _("Factor to scale by"), VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET(VipsForeignLoadPdf, scale), - 0.001, 100000.0, 1.0); + 0.0, 100000.0, 1.0); VIPS_ARG_BOXED(class, "background", 14, _("Background"), diff --git a/libvips/foreign/popplerload.c b/libvips/foreign/popplerload.c index 6aafc9e93b..848997234e 100644 --- a/libvips/foreign/popplerload.c +++ b/libvips/foreign/popplerload.c @@ -567,7 +567,7 @@ vips_foreign_load_pdf_class_init(VipsForeignLoadPdfClass *class) _("Factor to scale by"), VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET(VipsForeignLoadPdf, scale), - 0.001, 100000.0, 1.0); + 0.0, 100000.0, 1.0); VIPS_ARG_BOXED(class, "background", 24, _("Background"), diff --git a/libvips/foreign/rawsave.c b/libvips/foreign/rawsave.c index 1088ead88e..50bbe516c2 100644 --- a/libvips/foreign/rawsave.c +++ b/libvips/foreign/rawsave.c @@ -93,16 +93,12 @@ vips_foreign_save_raw_write(VipsRegion *region, VipsRect *area, void *a) { VipsForeignSave *save = (VipsForeignSave *) a; VipsForeignSaveRaw *raw = (VipsForeignSaveRaw *) a; - int i; - for (i = 0; i < area->height; i++) { - VipsPel *p = - VIPS_REGION_ADDR(region, area->left, area->top + i); - - if (vips__write(raw->fd, p, - VIPS_IMAGE_SIZEOF_PEL(save->in) * area->width)) + for (int i = 0; i < area->height; i++) + if (vips__write(raw->fd, + VIPS_REGION_ADDR(region, area->left, area->top + i), + VIPS_IMAGE_SIZEOF_PEL(save->in) * area->width)) return -1; - } return 0; } @@ -207,16 +203,12 @@ vips_foreign_save_raw_fd_write(VipsRegion *region, VipsRect *area, void *a) { VipsForeignSave *save = (VipsForeignSave *) a; VipsForeignSaveRawFd *fd = (VipsForeignSaveRawFd *) a; - int i; - - for (i = 0; i < area->height; i++) { - VipsPel *p = - VIPS_REGION_ADDR(region, area->left, area->top + i); - if (vips__write(fd->fd, p, - VIPS_IMAGE_SIZEOF_PEL(save->in) * area->width)) + for (int i = 0; i < area->height; i++) + if (vips__write(fd->fd, + VIPS_REGION_ADDR(region, area->left, area->top + i), + VIPS_IMAGE_SIZEOF_PEL(save->in) * area->width)) return -1; - } return 0; } @@ -231,8 +223,7 @@ vips_foreign_save_raw_fd_build(VipsObject *object) return -1; if (vips_image_pio_input(save->in) || - vips_sink_disc(save->in, - vips_foreign_save_raw_fd_write, fd)) + vips_sink_disc(save->in, vips_foreign_save_raw_fd_write, fd)) return -1; return 0; diff --git a/libvips/foreign/spngload.c b/libvips/foreign/spngload.c index 574736004c..8328c14588 100644 --- a/libvips/foreign/spngload.c +++ b/libvips/foreign/spngload.c @@ -337,10 +337,10 @@ vips_foreign_load_png_header(VipsForeignLoad *load) /* In non-fail mode, ignore CRC errors. */ flags = 0; - if (load->fail_on >= VIPS_FAIL_ON_ERROR) + if (load->fail_on < VIPS_FAIL_ON_ERROR) flags |= SPNG_CTX_IGNORE_ADLER32; png->ctx = spng_ctx_new(flags); - if (load->fail_on >= VIPS_FAIL_ON_ERROR) + if (load->fail_on < VIPS_FAIL_ON_ERROR) /* Ignore and don't calculate checksums. */ spng_set_crc_action(png->ctx, SPNG_CRC_USE, SPNG_CRC_USE); diff --git a/libvips/foreign/svgload.c b/libvips/foreign/svgload.c index 5a654a5749..040cce9ee4 100644 --- a/libvips/foreign/svgload.c +++ b/libvips/foreign/svgload.c @@ -725,7 +725,7 @@ vips_foreign_load_svg_class_init(VipsForeignLoadSvgClass *class) _("Scale output by this factor"), VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET(VipsForeignLoadSvg, scale), - 0.001, 100000.0, 1.0); + 0.0, 100000.0, 1.0); VIPS_ARG_BOOL(class, "unlimited", 23, _("Unlimited"), diff --git a/libvips/foreign/vips2jpeg.c b/libvips/foreign/vips2jpeg.c index 6a125fb782..db6ff29ed2 100644 --- a/libvips/foreign/vips2jpeg.c +++ b/libvips/foreign/vips2jpeg.c @@ -667,12 +667,26 @@ set_cinfo(struct jpeg_compress_struct *cinfo, if (progressive) jpeg_simple_progression(cinfo); - if (subsample_mode == VIPS_FOREIGN_SUBSAMPLE_OFF || + if (in->Bands != 3 || + subsample_mode == VIPS_FOREIGN_SUBSAMPLE_OFF || (subsample_mode == VIPS_FOREIGN_SUBSAMPLE_AUTO && - qfac >= 90)) { - int i; + qfac >= 90)) + /* No chroma subsample. + */ + for (int i = 0; i < in->Bands; i++) { + cinfo->comp_info[i].h_samp_factor = 1; + cinfo->comp_info[i].v_samp_factor = 1; + } + else { + /* Use 4:2:0 subsampling, we must set this explicitly, since some + * jpeg libraries do not enable chroma subsample by default. + */ + cinfo->comp_info[0].h_samp_factor = 2; + cinfo->comp_info[0].v_samp_factor = 2; - for (i = 0; i < in->Bands; i++) { + /* Rest should have sampling factors 1,1. + */ + for (int i = 1; i < in->Bands; i++) { cinfo->comp_info[i].h_samp_factor = 1; cinfo->comp_info[i].v_samp_factor = 1; } @@ -904,7 +918,12 @@ vips__jpeg_write_target(VipsImage *in, VipsTarget *target, return 0; } -const char *vips__jpeg_suffs[] = { ".jpg", ".jpeg", ".jpe", NULL }; +/* Some people want to be able to save as xxx.jfif. libjpeg will write as + * JFIF if it can, but if you use features like CMYK or YCCK, you'll get a + * regular JPEG. So saving as .jfif won't (by itself) guarantee strict JFIF + * conformance. + */ +const char *vips__jpeg_suffs[] = { ".jpg", ".jpeg", ".jpe", ".jfif", NULL }; /* Write a region to a JPEG compress struct. */ @@ -1035,7 +1054,7 @@ vips__jpeg_region_write_target(VipsRegion *region, VipsRect *rect, VipsTarget *target, int Q, const char *profile, gboolean optimize_coding, gboolean progressive, - gboolean strip, gboolean trellis_quant, + VipsForeignKeep keep, gboolean trellis_quant, gboolean overshoot_deringing, gboolean optimize_scans, int quant_table, VipsForeignSubsample subsample_mode, int restart_interval) diff --git a/libvips/foreign/vips2tiff.c b/libvips/foreign/vips2tiff.c index c193d440c3..2cc7497582 100644 --- a/libvips/foreign/vips2tiff.c +++ b/libvips/foreign/vips2tiff.c @@ -660,9 +660,8 @@ wtiff_compress_jpeg_header(Wtiff *wtiff, cinfo->input_components = image->Bands; if (image->Bands == 4 && - image->Type == VIPS_INTERPRETATION_CMYK) { + image->Type == VIPS_INTERPRETATION_CMYK) space = JCS_CMYK; - } else if (image->Bands == 3) space = JCS_RGB; else if (image->Bands == 1) @@ -683,19 +682,30 @@ wtiff_compress_jpeg_header(Wtiff *wtiff, jpeg_set_defaults(cinfo); - /* Set compression quality. Must be called after setting params above. + /* Set compression quality. */ jpeg_set_quality(cinfo, wtiff->Q, TRUE); - // disable chroma subsample for high Q - if (wtiff->Q >= 90) { - int i; - - for (i = 0; i < image->Bands; i++) { - cinfo->comp_info[i].h_samp_factor = 1; - cinfo->comp_info[i].v_samp_factor = 1; - } + /* We must set chroma subsampling explicitly since some libjpegs do not + * enable this by default. + */ + for (int i = 0; i < image->Bands; i++) { + cinfo->comp_info[i].h_samp_factor = 1; + cinfo->comp_info[i].v_samp_factor = 1; } + if (image->Bands == 3 && + wtiff->Q < 90) { + cinfo->comp_info[0].h_samp_factor = 2; + cinfo->comp_info[0].v_samp_factor = 2; + } + + /* For low Q, we write YCbCr, for high Q, RGB. The jpeg coeffs don't + * encode the photometric interpretation, the tiff header does that, + * so this code must be kept synced with wtiff_write_header(). + */ + if (image->Bands == 3 && + wtiff->Q >= 90) + jpeg_set_colorspace(cinfo, JCS_RGB); // Avoid writing the JFIF APP0 marker. cinfo->write_JFIF_header = FALSE; @@ -922,9 +932,8 @@ wtiff_write_header(Wtiff *wtiff, Layer *layer) wtiff->ready->Bands < 3) { /* Mono or mono + alpha. */ - photometric = wtiff->miniswhite - ? PHOTOMETRIC_MINISWHITE - : PHOTOMETRIC_MINISBLACK; + photometric = wtiff->miniswhite ? + PHOTOMETRIC_MINISWHITE : PHOTOMETRIC_MINISBLACK; colour_bands = 1; } else if (wtiff->ready->Type == VIPS_INTERPRETATION_LAB || @@ -938,12 +947,10 @@ wtiff_write_header(Wtiff *wtiff, Layer *layer) photometric = PHOTOMETRIC_LOGLUV; /* Tell libtiff we will write as float XYZ. */ - TIFFSetField(tif, - TIFFTAG_SGILOGDATAFMT, SGILOGDATAFMT_FLOAT); + TIFFSetField(tif, TIFFTAG_SGILOGDATAFMT, SGILOGDATAFMT_FLOAT); stonits = 1.0; if (vips_image_get_typeof(wtiff->ready, "stonits")) - vips_image_get_double(wtiff->ready, - "stonits", &stonits); + vips_image_get_double(wtiff->ready, "stonits", &stonits); TIFFSetField(tif, TIFFTAG_STONITS, stonits); colour_bands = 3; } @@ -962,8 +969,7 @@ wtiff_write_header(Wtiff *wtiff, Layer *layer) * that we will supply the image as YCbCr. */ photometric = PHOTOMETRIC_YCBCR; - TIFFSetField(tif, TIFFTAG_JPEGCOLORMODE, - JPEGCOLORMODE_RGB); + TIFFSetField(tif, TIFFTAG_JPEGCOLORMODE, JPEGCOLORMODE_RGB); colour_bands = 3; } else { @@ -989,11 +995,9 @@ wtiff_write_header(Wtiff *wtiff, Layer *layer) * we are premultiplying. */ for (i = 0; i < alpha_bands; i++) - v[i] = i == 0 && wtiff->premultiply - ? EXTRASAMPLE_ASSOCALPHA - : EXTRASAMPLE_UNASSALPHA; - TIFFSetField(tif, - TIFFTAG_EXTRASAMPLES, alpha_bands, v); + v[i] = i == 0 && wtiff->premultiply ? + EXTRASAMPLE_ASSOCALPHA : EXTRASAMPLE_UNASSALPHA; + TIFFSetField(tif, TIFFTAG_EXTRASAMPLES, alpha_bands, v); } TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, photometric); @@ -2325,6 +2329,7 @@ wtiff_copy_tiff(Wtiff *wtiff, TIFF *out, TIFF *in) CopyField(TIFFTAG_TILELENGTH, ui32); CopyField(TIFFTAG_ROWSPERSTRIP, ui32); CopyField(TIFFTAG_SUBFILETYPE, ui32); + CopyField(TIFFTAG_PREDICTOR, ui16); if (TIFFGetField(in, TIFFTAG_EXTRASAMPLES, &ui16, &a)) TIFFSetField(out, TIFFTAG_EXTRASAMPLES, ui16, a); @@ -2332,58 +2337,14 @@ wtiff_copy_tiff(Wtiff *wtiff, TIFF *out, TIFF *in) if (TIFFGetField(in, TIFFTAG_PAGENUMBER, &ui16, &ui16_2)) TIFFSetField(out, TIFFTAG_PAGENUMBER, ui16, ui16_2); - /* TIFFTAG_JPEGQUALITY is a pesudo-tag, so we can't copy it. - * Set explicitly from Wtiff. - */ - if (wtiff->compression == COMPRESSION_JPEG) { - unsigned char *buffer; - guint32 length; - - TIFFSetField(out, TIFFTAG_JPEGQUALITY, wtiff->Q); - - /* Only for three-band, 8-bit images. - */ - if (wtiff->ready->Bands == 3 && - wtiff->ready->BandFmt == VIPS_FORMAT_UCHAR) { - /* Enable rgb->ycbcr conversion in the jpeg write. - */ - if (!wtiff->rgbjpeg && - wtiff->Q < 90) - TIFFSetField(out, - TIFFTAG_JPEGCOLORMODE, - JPEGCOLORMODE_RGB); - - /* And we want ycbcr expanded to rgb on read. Otherwise - * TIFFTileSize() will give us the size of a chrominance - * subsampled tile. - */ - TIFFSetField(in, - TIFFTAG_JPEGCOLORMODE, JPEGCOLORMODE_RGB); - } - - if (TIFFGetField(in, TIFFTAG_JPEGTABLES, &length, &buffer)) - TIFFSetField(out, TIFFTAG_JPEGTABLES, length, buffer); - } + unsigned char *buffer; + guint32 length; + if (TIFFGetField(in, TIFFTAG_JPEGTABLES, &length, &buffer)) + TIFFSetField(out, TIFFTAG_JPEGTABLES, length, buffer); -#ifdef HAVE_TIFF_COMPRESSION_WEBP - /* More pseudotags we can't copy. + /* Other compression tags are just pseudotags and don't need to be set + * because we copy raw tiles. */ - if (wtiff->compression == COMPRESSION_WEBP) { - TIFFSetField(out, TIFFTAG_WEBP_LEVEL, wtiff->Q); - TIFFSetField(out, TIFFTAG_WEBP_LOSSLESS, wtiff->lossless); - } - if (wtiff->compression == COMPRESSION_ZSTD) { - TIFFSetField(out, TIFFTAG_ZSTD_LEVEL, wtiff->level); - if (wtiff->predictor != VIPS_FOREIGN_TIFF_PREDICTOR_NONE) - TIFFSetField(out, - TIFFTAG_PREDICTOR, wtiff->predictor); - } -#endif /*HAVE_TIFF_COMPRESSION_WEBP*/ - - if ((wtiff->compression == COMPRESSION_ADOBE_DEFLATE || - wtiff->compression == COMPRESSION_LZW) && - wtiff->predictor != VIPS_FOREIGN_TIFF_PREDICTOR_NONE) - TIFFSetField(out, TIFFTAG_PREDICTOR, wtiff->predictor); /* We can't copy profiles or xmp :( Set again from wtiff. */ @@ -2422,8 +2383,7 @@ wtiff_gather(Wtiff *wtiff) printf("appending layer sub = %d ...\n", layer->sub); #endif /*DEBUG*/ - if (!(source = - vips_source_new_from_target(layer->target))) + if (!(source = vips_source_new_from_target(layer->target))) return -1; if (!(in = vips__tiff_openin_source(source))) { diff --git a/libvips/foreign/vipspng.c b/libvips/foreign/vipspng.c index 8477ca6ac4..f2c506404d 100644 --- a/libvips/foreign/vipspng.c +++ b/libvips/foreign/vipspng.c @@ -305,20 +305,18 @@ read_new(VipsSource *source, VipsImage *out, PNG_SKIP_sRGB_CHECK_PROFILE, PNG_OPTION_ON); #endif /*PNG_SKIP_sRGB_CHECK_PROFILE*/ - /* Don't verify ADLER32 checksums (this can produce a lot of - * warnings). + /* In non-fail mode, ignore CRC errors. */ + if (read->fail_on < VIPS_FAIL_ON_ERROR) { #ifdef PNG_IGNORE_ADLER32 - png_set_option(read->pPng, PNG_IGNORE_ADLER32, PNG_OPTION_ON); + png_set_option(read->pPng, PNG_IGNORE_ADLER32, PNG_OPTION_ON); #endif /*PNG_IGNORE_ADLER32*/ - /* Disable CRC checking in fuzzing mode. Most fuzzed images will have - * bad CRCs so this check would break fuzzing. - */ -#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION - png_set_crc_action(read->pPng, - PNG_CRC_QUIET_USE, PNG_CRC_QUIET_USE); -#endif /*FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION*/ + /* Ignore and don't calculate checksums. + */ + png_set_crc_action(read->pPng, + PNG_CRC_QUIET_USE, PNG_CRC_QUIET_USE); + } /* libpng has a default soft limit of 1m pixels per axis. */ diff --git a/libvips/foreign/webpsave.c b/libvips/foreign/webpsave.c index f51de188a8..72092f59d3 100644 --- a/libvips/foreign/webpsave.c +++ b/libvips/foreign/webpsave.c @@ -191,8 +191,8 @@ vips_foreign_save_webp_progress_hook(int percent, const WebPPicture *picture) { VipsImage *in = (VipsImage *) picture->user_data; - /* Trigger any eval callbacks on the image and - * check if we need to abort the WebP encoding. + /* Trigger any eval callbacks on the image and check if we need to abort + * the WebP encoding. */ vips_image_eval(in, VIPS_IMAGE_N_PELS(in)); @@ -205,11 +205,11 @@ vips_foreign_save_webp_progress_hook(int percent, const WebPPicture *picture) } static void -vips_foreign_save_webp_unset(VipsForeignSaveWebp *write) +vips_foreign_save_webp_unset(VipsForeignSaveWebp *webp) { - WebPMemoryWriterClear(&write->memory_writer); - VIPS_FREEF(WebPAnimEncoderDelete, write->enc); - VIPS_FREEF(WebPMuxDelete, write->mux); + WebPMemoryWriterClear(&webp->memory_writer); + VIPS_FREEF(WebPAnimEncoderDelete, webp->enc); + VIPS_FREEF(WebPMuxDelete, webp->mux); } static void @@ -217,33 +217,33 @@ vips_foreign_save_webp_dispose(GObject *gobject) { VipsForeignSaveWebp *webp = (VipsForeignSaveWebp *) gobject; + vips_foreign_save_webp_unset(webp); VIPS_UNREF(webp->target); - VIPS_FREE(webp->frame_bytes); G_OBJECT_CLASS(vips_foreign_save_webp_parent_class)->dispose(gobject); } static gboolean -vips_foreign_save_webp_pic_init(VipsForeignSaveWebp *write, WebPPicture *pic) +vips_foreign_save_webp_pic_init(VipsForeignSaveWebp *webp, WebPPicture *pic) { - VipsForeignSave *save = (VipsForeignSave *) write; + VipsForeignSave *save = (VipsForeignSave *) webp; if (!WebPPictureInit(pic)) { vips_error("webpsave", "%s", _("picture version error")); return FALSE; } pic->writer = WebPMemoryWrite; - pic->custom_ptr = (void *) &write->memory_writer; + pic->custom_ptr = (void *) &webp->memory_writer; pic->progress_hook = vips_foreign_save_webp_progress_hook; pic->user_data = (void *) save->in; /* Smart subsampling needs use_argb because it is applied during * RGB to YUV conversion. */ - pic->use_argb = write->lossless || - write->near_lossless || - write->smart_subsample; + pic->use_argb = webp->lossless || + webp->near_lossless || + webp->smart_subsample; return TRUE; } @@ -251,15 +251,15 @@ vips_foreign_save_webp_pic_init(VipsForeignSaveWebp *write, WebPPicture *pic) /* Write a VipsImage into an uninitialised pic. */ static int -vips_foreign_save_webp_write_webp_image(VipsForeignSaveWebp *write, +vips_foreign_save_webp_write_webp_image(VipsForeignSaveWebp *webp, const VipsPel *imagedata, WebPPicture *pic) { - VipsForeignSave *save = (VipsForeignSave *) write; + VipsForeignSave *save = (VipsForeignSave *) webp; + int page_height = vips_image_get_page_height(save->ready); webp_import import; - int page_height = vips_image_get_page_height(save->ready); - if (!vips_foreign_save_webp_pic_init(write, pic)) + if (!vips_foreign_save_webp_pic_init(webp, pic)) return -1; pic->width = save->ready->Xsize; @@ -270,8 +270,7 @@ vips_foreign_save_webp_write_webp_image(VipsForeignSaveWebp *write, else import = WebPPictureImportRGB; - if (!import(pic, imagedata, - save->ready->Xsize * save->ready->Bands)) { + if (!import(pic, imagedata, save->ready->Xsize * save->ready->Bands)) { WebPPictureFree(pic); vips_error("webpsave", "%s", _("picture memory error")); return -1; @@ -285,11 +284,11 @@ vips_foreign_save_webp_write_webp_image(VipsForeignSaveWebp *write, static int vips_foreign_save_webp_write_frame(VipsForeignSaveWebp *webp) { - WebPPicture pic; VipsObjectClass *class = VIPS_OBJECT_GET_CLASS(webp); - if (vips_foreign_save_webp_write_webp_image(webp, webp->frame_bytes, - &pic)) + WebPPicture pic; + + if (vips_foreign_save_webp_write_webp_image(webp, webp->frame_bytes, &pic)) return -1; /* Animated write @@ -298,10 +297,10 @@ vips_foreign_save_webp_write_frame(VipsForeignSaveWebp *webp) if (!WebPAnimEncoderAdd(webp->enc, &pic, webp->timestamp_ms, &webp->config)) { WebPPictureFree(&pic); - vips_error(class->nickname, - "%s", _("anim add error")); + vips_error(class->nickname, "%s", _("anim add error")); return -1; } + /* Adjust current timestamp */ if (webp->delay && @@ -315,8 +314,7 @@ vips_foreign_save_webp_write_frame(VipsForeignSaveWebp *webp) */ if (!WebPEncode(&webp->config, &pic)) { WebPPictureFree(&pic); - vips_error("webpsave", "%s", - _("unable to encode")); + vips_error("webpsave", "%s", _("unable to encode")); return -1; } } @@ -334,12 +332,11 @@ vips_foreign_save_webp_sink_disc(VipsRegion *region, VipsRect *area, void *a) { VipsForeignSave *save = (VipsForeignSave *) a; VipsForeignSaveWebp *webp = (VipsForeignSaveWebp *) a; - int i; int page_height = vips_image_get_page_height(save->ready); /* Write the new pixels into the frame. */ - for (i = 0; i < area->height; i++) { + for (int i = 0; i < area->height; i++) { memcpy(webp->frame_bytes + area->width * webp->write_y * save->ready->Bands, VIPS_REGION_ADDR(region, 0, area->top + i), @@ -388,24 +385,23 @@ get_preset(VipsForeignWebpPreset preset) } static void -vips_webp_set_count(VipsForeignSaveWebp *write, int loop_count) +vips_webp_set_count(VipsForeignSaveWebp *webp, int loop_count) { uint32_t features; - if (WebPMuxGetFeatures(write->mux, &features) == WEBP_MUX_OK && + if (WebPMuxGetFeatures(webp->mux, &features) == WEBP_MUX_OK && (features & ANIMATION_FLAG)) { WebPMuxAnimParams params; - if (WebPMuxGetAnimationParams(write->mux, ¶ms) == - WEBP_MUX_OK) { + if (WebPMuxGetAnimationParams(webp->mux, ¶ms) == WEBP_MUX_OK) { params.loop_count = loop_count; - WebPMuxSetAnimationParams(write->mux, ¶ms); + WebPMuxSetAnimationParams(webp->mux, ¶ms); } } } static int -vips_webp_set_chunk(VipsForeignSaveWebp *write, +vips_webp_set_chunk(VipsForeignSaveWebp *webp, const char *webp_name, const void *data, size_t length) { WebPData chunk; @@ -413,10 +409,8 @@ vips_webp_set_chunk(VipsForeignSaveWebp *write, chunk.bytes = data; chunk.size = length; - if (WebPMuxSetChunk(write->mux, webp_name, &chunk, 1) != - WEBP_MUX_OK) { - vips_error("webpsave", - "%s", _("chunk add error")); + if (WebPMuxSetChunk(webp->mux, webp_name, &chunk, 1) != WEBP_MUX_OK) { + vips_error("webpsave", "%s", _("chunk add error")); return -1; } @@ -424,15 +418,15 @@ vips_webp_set_chunk(VipsForeignSaveWebp *write, } static int -vips_webp_add_original_meta(VipsForeignSaveWebp *write) +vips_webp_add_original_meta(VipsForeignSaveWebp *webp) { - VipsForeignSave *save = (VipsForeignSave *) write; + VipsForeignSave *save = (VipsForeignSave *) webp; for (int i = 0; i < vips__n_webp_names; i++) { const char *vips_name = vips__webp_names[i].vips; const char *webp_name = vips__webp_names[i].webp; - if (strcmp(vips_name, VIPS_META_ICC_NAME) == 0) + if (g_str_equal(vips_name, VIPS_META_ICC_NAME)) continue; if (vips_image_get_typeof(save->ready, vips_name)) { @@ -440,7 +434,7 @@ vips_webp_add_original_meta(VipsForeignSaveWebp *write) size_t length; if (vips_image_get_blob(save->ready, vips_name, &data, &length) || - vips_webp_set_chunk(write, webp_name, data, length)) + vips_webp_set_chunk(webp, webp_name, data, length)) return -1; } } @@ -452,7 +446,7 @@ static const char * vips_webp_get_webp_name(const char *vips_name) { for (int i = 0; i < vips__n_webp_names; i++) - if (strcmp(vips_name, vips__webp_names[i].vips) == 0) + if (g_str_equal(vips_name, vips__webp_names[i].vips)) return vips__webp_names[i].webp; return ""; @@ -583,9 +577,7 @@ vips_foreign_save_webp_init_config(VipsForeignSaveWebp *webp) */ WebPMemoryWriterInit(&webp->memory_writer); if (!WebPConfigInit(&webp->config)) { - vips_foreign_save_webp_unset(webp); - vips_error("webpsave", - "%s", _("config version error")); + vips_error("webpsave", "%s", _("config version error")); return -1; } @@ -594,9 +586,7 @@ vips_foreign_save_webp_init_config(VipsForeignSaveWebp *webp) * WebPConfigLosslessPreset(). */ if (!(webp->lossless || webp->near_lossless) && - !WebPConfigPreset(&webp->config, get_preset(webp->preset), - webp->Q)) { - vips_foreign_save_webp_unset(webp); + !WebPConfigPreset(&webp->config, get_preset(webp->preset), webp->Q)) { vips_error("webpsave", "%s", _("config version error")); return -1; } @@ -613,7 +603,6 @@ vips_foreign_save_webp_init_config(VipsForeignSaveWebp *webp) webp->config.use_sharp_yuv = 1; if (!WebPValidateConfig(&webp->config)) { - vips_foreign_save_webp_unset(webp); vips_error("webpsave", "%s", _("invalid configuration")); return -1; } @@ -625,16 +614,15 @@ static int vips_foreign_save_webp_init_anim_enc(VipsForeignSaveWebp *webp) { VipsForeignSave *save = (VipsForeignSave *) webp; + int page_height = vips_image_get_page_height(save->ready); WebPAnimEncoderOptions anim_config; int i; - int page_height = vips_image_get_page_height(save->ready); /* Init config for animated write */ if (!WebPAnimEncoderOptionsInit(&anim_config)) { - vips_error("webpsave", - "%s", _("config version error")); + vips_error("webpsave", "%s", _("config version error")); return -1; } @@ -645,8 +633,7 @@ vips_foreign_save_webp_init_anim_enc(VipsForeignSaveWebp *webp) webp->enc = WebPAnimEncoderNew(save->ready->Xsize, page_height, &anim_config); if (!webp->enc) { - vips_error("webpsave", - "%s", _("unable to init animation")); + vips_error("webpsave", "%s", _("unable to init animation")); return -1; } @@ -656,8 +643,7 @@ vips_foreign_save_webp_init_anim_enc(VipsForeignSaveWebp *webp) */ webp->gif_delay = 10; if (vips_image_get_typeof(save->ready, "gif-delay") && - vips_image_get_int(save->ready, "gif-delay", - &webp->gif_delay)) + vips_image_get_int(save->ready, "gif-delay", &webp->gif_delay)) return -1; /* New images have an array of ints instead. @@ -689,16 +675,13 @@ vips_foreign_save_webp_finish_anim(VipsForeignSaveWebp *webp) /* Closes animated encoder and adds last frame delay. */ - if (!WebPAnimEncoderAdd(webp->enc, - NULL, webp->timestamp_ms, NULL)) { - vips_error("webpsave", - "%s", _("anim close error")); + if (!WebPAnimEncoderAdd(webp->enc, NULL, webp->timestamp_ms, NULL)) { + vips_error("webpsave", "%s", _("anim close error")); return -1; } if (!WebPAnimEncoderAssemble(webp->enc, &webp_data)) { - vips_error("webpsave", - "%s", _("anim build error")); + vips_error("webpsave", "%s", _("anim build error")); return -1; } @@ -730,7 +713,6 @@ vips_foreign_save_webp_build(VipsObject *object) page_height = vips_image_get_page_height(save->ready); if (save->ready->Xsize > 16383 || page_height > 16383) { vips_error("webpsave", _("image too large")); - vips_foreign_save_webp_unset(webp); return -1; } @@ -740,9 +722,7 @@ vips_foreign_save_webp_build(VipsObject *object) (size_t) save->ready->Bands * save->ready->Xsize * page_height; webp->frame_bytes = g_try_malloc(frame_size); if (webp->frame_bytes == NULL) { - vips_error("webpsave", - _("failed to allocate %zu bytes"), frame_size); - vips_foreign_save_webp_unset(webp); + vips_error("webpsave", _("failed to allocate %zu bytes"), frame_size); return -1; } @@ -763,8 +743,7 @@ vips_foreign_save_webp_build(VipsObject *object) if (vips_foreign_save_webp_init_anim_enc(webp)) return -1; - if (vips_sink_disc(save->ready, - vips_foreign_save_webp_sink_disc, webp)) + if (vips_sink_disc(save->ready, vips_foreign_save_webp_sink_disc, webp)) return -1; /* Finish animated write @@ -773,16 +752,12 @@ vips_foreign_save_webp_build(VipsObject *object) if (vips_foreign_save_webp_finish_anim(webp)) return -1; - if (vips_webp_add_metadata(webp)) { - vips_foreign_save_webp_unset(webp); + if (vips_webp_add_metadata(webp)) return -1; - } if (vips_target_write(webp->target, - webp->memory_writer.mem, webp->memory_writer.size)) { - vips_foreign_save_webp_unset(webp); + webp->memory_writer.mem, webp->memory_writer.size)) return -1; - } if (vips_target_end(webp->target)) return -1; @@ -938,8 +913,7 @@ static int vips_foreign_save_webp_target_build(VipsObject *object) { VipsForeignSaveWebp *webp = (VipsForeignSaveWebp *) object; - VipsForeignSaveWebpTarget *target = - (VipsForeignSaveWebpTarget *) object; + VipsForeignSaveWebpTarget *target = (VipsForeignSaveWebpTarget *) object; webp->target = target->target; g_object_ref(webp->target); @@ -1042,8 +1016,7 @@ static int vips_foreign_save_webp_buffer_build(VipsObject *object) { VipsForeignSaveWebp *webp = (VipsForeignSaveWebp *) object; - VipsForeignSaveWebpBuffer *buffer = - (VipsForeignSaveWebpBuffer *) object; + VipsForeignSaveWebpBuffer *buffer = (VipsForeignSaveWebpBuffer *) object; VipsBlob *blob; diff --git a/libvips/freqfilt/fwfft.c b/libvips/freqfilt/fwfft.c index b86010bd6a..daadc29271 100644 --- a/libvips/freqfilt/fwfft.c +++ b/libvips/freqfilt/fwfft.c @@ -37,6 +37,8 @@ * - reduce memuse * 3/1/14 * - redone as a class + * 15/12/23 [akash-akya] + * - add locks */ /* @@ -91,6 +93,26 @@ typedef VipsFreqfiltClass VipsFwfftClass; G_DEFINE_TYPE(VipsFwfft, vips_fwfft, VIPS_TYPE_FREQFILT); +/* Everything in fftw3 except execute has to be behind a mutex. + */ +GMutex *vips__fft_lock = NULL; + +static void * +vips__fft_thread_init(void *data) +{ + vips__fft_lock = vips_g_mutex_new(); + + return NULL; +} + +void +vips__fft_init(void) +{ + static GOnce once = G_ONCE_INIT; + + VIPS_ONCE(&once, vips__fft_thread_init, NULL); +} + /* Real to complex forward transform. */ static int @@ -130,18 +152,23 @@ rfwfft1(VipsObject *object, VipsImage *in, VipsImage **out) if (!(half_complex = VIPS_ARRAY(fwfft, in->Ysize * half_width * 2, double))) return -1; + g_mutex_lock(vips__fft_lock); if (!(plan = fftw_plan_dft_r2c_2d(in->Ysize, in->Xsize, planner_scratch, (fftw_complex *) half_complex, 0))) { + g_mutex_unlock(vips__fft_lock); vips_error(class->nickname, "%s", _("unable to create transform plan")); return -1; } + g_mutex_unlock(vips__fft_lock); fftw_execute_dft_r2c(plan, (double *) t[1]->data, (fftw_complex *) half_complex); + g_mutex_lock(vips__fft_lock); fftw_destroy_plan(plan); + g_mutex_unlock(vips__fft_lock); /* Write to out as another memory buffer. */ @@ -244,20 +271,25 @@ cfwfft1(VipsObject *object, VipsImage *in, VipsImage **out) /* Make the plan for the transform. */ + g_mutex_lock(vips__fft_lock); if (!(plan = fftw_plan_dft_2d(in->Ysize, in->Xsize, (fftw_complex *) planner_scratch, (fftw_complex *) planner_scratch, FFTW_FORWARD, 0))) { + g_mutex_unlock(vips__fft_lock); vips_error(class->nickname, "%s", _("unable to create transform plan")); return -1; } + g_mutex_unlock(vips__fft_lock); fftw_execute_dft(plan, (fftw_complex *) t[1]->data, (fftw_complex *) t[1]->data); + g_mutex_lock(vips__fft_lock); fftw_destroy_plan(plan); + g_mutex_unlock(vips__fft_lock); /* Write to out as another memory buffer. */ @@ -300,6 +332,8 @@ vips_fwfft_build(VipsObject *object) VipsImage *in; + vips__fft_init(); + if (VIPS_OBJECT_CLASS(vips_fwfft_parent_class)->build(object)) return -1; diff --git a/libvips/freqfilt/invfft.c b/libvips/freqfilt/invfft.c index 404bceee47..874df553c5 100644 --- a/libvips/freqfilt/invfft.c +++ b/libvips/freqfilt/invfft.c @@ -26,6 +26,8 @@ * - reduce memuse * 3/1/14 * - redone as a class + * 15/12/23 [akash-akya] + * - add locks */ /* @@ -111,20 +113,25 @@ cinvfft1(VipsObject *object, VipsImage *in, VipsImage **out) if (!(planner_scratch = VIPS_ARRAY(invfft, VIPS_IMAGE_N_PELS(in) * 2, double))) return -1; + g_mutex_lock(vips__fft_lock); if (!(plan = fftw_plan_dft_2d(in->Ysize, in->Xsize, (fftw_complex *) planner_scratch, (fftw_complex *) planner_scratch, FFTW_BACKWARD, 0))) { + g_mutex_unlock(vips__fft_lock); vips_error(class->nickname, "%s", _("unable to create transform plan")); return -1; } + g_mutex_unlock(vips__fft_lock); fftw_execute_dft(plan, (fftw_complex *) (*out)->data, (fftw_complex *) (*out)->data); + g_mutex_lock(vips__fft_lock); fftw_destroy_plan(plan); + g_mutex_unlock(vips__fft_lock); (*out)->Type = VIPS_INTERPRETATION_B_W; @@ -187,18 +194,23 @@ rinvfft1(VipsObject *object, VipsImage *in, VipsImage **out) if (!(planner_scratch = VIPS_ARRAY(invfft, t[1]->Ysize * half_width * 2, double))) return -1; + g_mutex_lock(vips__fft_lock); if (!(plan = fftw_plan_dft_c2r_2d(t[1]->Ysize, t[1]->Xsize, (fftw_complex *) planner_scratch, (double *) (*out)->data, 0))) { + g_mutex_unlock(vips__fft_lock); vips_error(class->nickname, "%s", _("unable to create transform plan")); return -1; } + g_mutex_unlock(vips__fft_lock); fftw_execute_dft_c2r(plan, (fftw_complex *) half_complex, (double *) (*out)->data); + g_mutex_lock(vips__fft_lock); fftw_destroy_plan(plan); + g_mutex_unlock(vips__fft_lock); return 0; } @@ -212,6 +224,8 @@ vips_invfft_build(VipsObject *object) VipsImage *in; + vips__fft_init(); + if (VIPS_OBJECT_CLASS(vips_invfft_parent_class)->build(object)) return -1; diff --git a/libvips/freqfilt/pfreqfilt.h b/libvips/freqfilt/pfreqfilt.h index 7f439a6c09..768b1e316f 100644 --- a/libvips/freqfilt/pfreqfilt.h +++ b/libvips/freqfilt/pfreqfilt.h @@ -35,6 +35,12 @@ extern "C" { #endif /*__cplusplus*/ +/* All fftw3 calls except execute() need to be locked. + */ +extern GMutex *vips__fft_lock; + +void vips__fft_init(void); + #define VIPS_TYPE_FREQFILT (vips_freqfilt_get_type()) #define VIPS_FREQFILT(obj) \ (G_TYPE_CHECK_INSTANCE_CAST((obj), \ diff --git a/libvips/include/vips/connection.h b/libvips/include/vips/connection.h index 681e7f38e6..93d7f24323 100644 --- a/libvips/include/vips/connection.h +++ b/libvips/include/vips/connection.h @@ -428,8 +428,11 @@ struct _VipsTarget { int write_point; /* Write position in memory_buffer. + * + * off_t can be 32 bits on some platforms, so make sure we have a + * full 64. */ - off_t position; + gint64 position; /* Temp targets on the filesystem need deleting, sometimes. */ @@ -466,8 +469,11 @@ typedef struct _VipsTargetClass { gint64 (*read)(VipsTarget *, void *, size_t); /* Seek output. Args exactly as lseek(2). + * + * We have to use int64 rather than off_t, since we must work on + * Windows, where off_t can be 32-bits. */ - off_t (*seek)(VipsTarget *, off_t offset, int whence); + gint64 (*seek)(VipsTarget *, gint64 offset, int whence); /* Output has been generated, so do any clearing up, * eg. copy the bytes we saved in memory to the target blob. @@ -492,7 +498,7 @@ int vips_target_write(VipsTarget *target, const void *data, size_t length); VIPS_API gint64 vips_target_read(VipsTarget *target, void *buffer, size_t length); VIPS_API -off_t vips_target_seek(VipsTarget *target, off_t offset, int whence); +gint64 vips_target_seek(VipsTarget *target, gint64 offset, int whence); VIPS_API int vips_target_end(VipsTarget *target); VIPS_DEPRECATED_FOR(vips_target_end) diff --git a/libvips/include/vips/dispatch.h b/libvips/include/vips/dispatch.h index f152223595..bbe59542e6 100644 --- a/libvips/include/vips/dispatch.h +++ b/libvips/include/vips/dispatch.h @@ -184,50 +184,50 @@ typedef struct { /* Built-in VIPS types. */ -extern im_type_desc im__input_int; -extern im_type_desc im__input_intvec; -extern im_type_desc im__input_imask; -extern im_type_desc im__output_int; -extern im_type_desc im__output_intvec; -extern im_type_desc im__output_imask; +VIPS_DEPRECATED im_type_desc im__input_int; +VIPS_DEPRECATED im_type_desc im__input_intvec; +VIPS_DEPRECATED im_type_desc im__input_imask; +VIPS_DEPRECATED im_type_desc im__output_int; +VIPS_DEPRECATED im_type_desc im__output_intvec; +VIPS_DEPRECATED im_type_desc im__output_imask; -extern im_type_desc im__input_double; -extern im_type_desc im__input_doublevec; -extern im_type_desc im__input_dmask; -extern im_type_desc im__output_double; -extern im_type_desc im__output_doublevec; -extern im_type_desc im__output_dmask; -extern im_type_desc im__output_dmask_screen; +VIPS_DEPRECATED im_type_desc im__input_double; +VIPS_DEPRECATED im_type_desc im__input_doublevec; +VIPS_DEPRECATED im_type_desc im__input_dmask; +VIPS_DEPRECATED im_type_desc im__output_double; +VIPS_DEPRECATED im_type_desc im__output_doublevec; +VIPS_DEPRECATED im_type_desc im__output_dmask; +VIPS_DEPRECATED im_type_desc im__output_dmask_screen; -extern im_type_desc im__output_complex; +VIPS_DEPRECATED im_type_desc im__output_complex; -extern im_type_desc im__input_string; -extern im_type_desc im__output_string; +VIPS_DEPRECATED im_type_desc im__input_string; +VIPS_DEPRECATED im_type_desc im__output_string; -extern im_type_desc im__input_imagevec; -extern im_type_desc im__input_image; -extern im_type_desc im__output_image; -extern im_type_desc im__rw_image; +VIPS_DEPRECATED im_type_desc im__input_imagevec; +VIPS_DEPRECATED im_type_desc im__input_image; +VIPS_DEPRECATED im_type_desc im__output_image; +VIPS_DEPRECATED im_type_desc im__rw_image; -extern im_type_desc im__input_display; -extern im_type_desc im__output_display; +VIPS_DEPRECATED im_type_desc im__input_display; +VIPS_DEPRECATED im_type_desc im__output_display; -extern im_type_desc im__input_gvalue; -extern im_type_desc im__output_gvalue; +VIPS_DEPRECATED im_type_desc im__input_gvalue; +VIPS_DEPRECATED im_type_desc im__output_gvalue; -extern im_type_desc im__input_interpolate; +VIPS_DEPRECATED im_type_desc im__input_interpolate; /* VIPS print functions. */ -int im__iprint(im_object obj); /* int */ -int im__ivprint(im_object obj); /* intvec */ -int im__dprint(im_object obj); /* double */ -int im__dvprint(im_object obj); /* doublevec */ -int im__dmsprint(im_object obj); /* DOUBLEMASK as stats */ -int im__cprint(im_object obj); /* complex */ -int im__sprint(im_object obj); /* string */ -int im__displayprint(im_object obj); /* im_col_display */ -int im__gprint(im_object obj); /* GValue */ +VIPS_DEPRECATED int im__iprint(im_object obj); /* int */ +VIPS_DEPRECATED int im__ivprint(im_object obj); /* intvec */ +VIPS_DEPRECATED int im__dprint(im_object obj); /* double */ +VIPS_DEPRECATED int im__dvprint(im_object obj); /* doublevec */ +VIPS_DEPRECATED int im__dmsprint(im_object obj); /* DOUBLEMASK as stats */ +VIPS_DEPRECATED int im__cprint(im_object obj); /* complex */ +VIPS_DEPRECATED int im__sprint(im_object obj); /* string */ +VIPS_DEPRECATED int im__displayprint(im_object obj);/* im_col_display */ +VIPS_DEPRECATED int im__gprint(im_object obj); /* GValue */ /* Macros for convenient creation. */ diff --git a/libvips/include/vips/internal.h b/libvips/include/vips/internal.h index 9ccbaec3c9..4eb6070d4a 100644 --- a/libvips/include/vips/internal.h +++ b/libvips/include/vips/internal.h @@ -151,7 +151,6 @@ extern gboolean vips__cache_trace; void vips__thread_init(void); void vips__threadpool_init(void); void vips__threadpool_shutdown(void); -int vips__thread_execute(const char *name, GFunc func, gpointer data); VIPS_API void vips__worker_lock(GMutex *mutex); void vips__cache_init(void); @@ -222,11 +221,11 @@ int vips__has_extension_block(VipsImage *im); /* TODO(kleisauke): VIPS_API is required by vipsheader. */ VIPS_API -void *vips__read_extension_block(VipsImage *im, int *size); +void *vips__read_extension_block(VipsImage *im, size_t *size); /* TODO(kleisauke): VIPS_API is required by vipsedit. */ VIPS_API -int vips__write_extension_block(VipsImage *im, void *buf, int size); +int vips__write_extension_block(VipsImage *im, void *buf, size_t size); int vips__writehist(VipsImage *image); /* TODO(kleisauke): VIPS_API is required by vipsedit. */ @@ -291,7 +290,6 @@ void vips__draw_line_direct(VipsImage *image, int x1, int y1, int x2, int y2, void vips__draw_circle_direct(VipsImage *image, int cx, int cy, int r, VipsDrawScanline draw_scanline, void *client); -int vips__insert_just_one(VipsRegion *out, VipsRegion *in, int x, int y); int vips__insert_paste_region(VipsRegion *out, VipsRegion *in, VipsRect *pos); /* Register base vips interpolators, called during startup. diff --git a/libvips/include/vips/util.h b/libvips/include/vips/util.h index d6e4f8e45e..78caecc2b2 100644 --- a/libvips/include/vips/util.h +++ b/libvips/include/vips/util.h @@ -164,15 +164,6 @@ extern "C" { } \ G_STMT_END -/* The g_info() macro was added in 2.40. - */ -#ifndef g_info -/* Hopefully we have varargs macros. Maybe revisit this. - */ -#define g_info(...) \ - g_log(G_LOG_DOMAIN, G_LOG_LEVEL_INFO, __VA_ARGS__) -#endif - /* Various integer range clips. Record over/under flows. */ #define VIPS_CLIP_UCHAR(V, SEQ) \ @@ -344,8 +335,7 @@ int vips__file_write(void *data, size_t size, size_t nmemb, FILE *stream); /* TODO(kleisauke): VIPS_API is required by the magick module. */ VIPS_API -gint64 vips__get_bytes(const char *filename, - unsigned char buf[], gint64 len); +gint64 vips__get_bytes(const char *filename, unsigned char buf[], gint64 len); int vips__fgetc(FILE *fp); GValue *vips__gvalue_ref_string_new(const char *text); diff --git a/libvips/iofuncs/buffer.c b/libvips/iofuncs/buffer.c index 78acf8b12b..3b0acac999 100644 --- a/libvips/iofuncs/buffer.c +++ b/libvips/iofuncs/buffer.c @@ -89,7 +89,9 @@ static const int buffer_cache_max_reserve = 2; /* Workers have a BufferThread (and BufferCache) in a GPrivate they have * exclusive access to. */ -static GPrivate *buffer_thread_key = NULL; +static void buffer_thread_destroy_notify(gpointer data); +static GPrivate buffer_thread_key = + G_PRIVATE_INIT(buffer_thread_destroy_notify); void vips_buffer_print(VipsBuffer *buffer) @@ -321,9 +323,9 @@ buffer_thread_get(void) * will be calling vips_thread_shutdown() on thread * termination. */ - if (!(buffer_thread = g_private_get(buffer_thread_key))) { + if (!(buffer_thread = g_private_get(&buffer_thread_key))) { buffer_thread = buffer_thread_new(); - g_private_set(buffer_thread_key, buffer_thread); + g_private_set(&buffer_thread_key, buffer_thread); } g_assert(buffer_thread->thread == g_thread_self()); @@ -657,7 +659,7 @@ vips_buffer_unref_ref(VipsBuffer *old_buffer, VipsImage *im, VipsRect *area) } static void -buffer_thread_destroy_notify(VipsBufferThread *buffer_thread) +buffer_thread_destroy_notify(gpointer data) { /* We only come here if vips_thread_shutdown() was not called for this * thread. Do our best to clean up. @@ -665,7 +667,7 @@ buffer_thread_destroy_notify(VipsBufferThread *buffer_thread) * GPrivate has stopped working by this point in destruction, be * careful not to touch that. */ - buffer_thread_free(buffer_thread); + buffer_thread_free(data); } /* Init the buffer cache system. This is called during vips_init. @@ -673,11 +675,6 @@ buffer_thread_destroy_notify(VipsBufferThread *buffer_thread) void vips__buffer_init(void) { - static GPrivate private = - G_PRIVATE_INIT((GDestroyNotify) buffer_thread_destroy_notify); - - buffer_thread_key = &private; - if (buffer_cache_max_reserve < 1) printf("vips__buffer_init: buffer reserve disabled\n"); @@ -695,8 +692,8 @@ vips__buffer_shutdown(void) { VipsBufferThread *buffer_thread; - if ((buffer_thread = g_private_get(buffer_thread_key))) { + if ((buffer_thread = g_private_get(&buffer_thread_key))) { buffer_thread_free(buffer_thread); - g_private_set(buffer_thread_key, NULL); + g_private_set(&buffer_thread_key, NULL); } } diff --git a/libvips/iofuncs/gate.c b/libvips/iofuncs/gate.c index 1a5b0b85dc..ea9f914b90 100644 --- a/libvips/iofuncs/gate.c +++ b/libvips/iofuncs/gate.c @@ -80,7 +80,9 @@ typedef struct _VipsThreadProfile { gboolean vips__thread_profile = FALSE; -static GPrivate *vips_thread_profile_key = NULL; +static void thread_profile_destroy_notify(gpointer data); +static GPrivate vips_thread_profile_key = + G_PRIVATE_INIT(thread_profile_destroy_notify); static FILE *vips__thread_fp = NULL; @@ -191,8 +193,10 @@ vips__thread_profile_stop(void) } static void -vips__thread_profile_init_cb(VipsThreadProfile *profile) +thread_profile_destroy_notify(gpointer data) { + VipsThreadProfile *profile = data; + /* We only come here if vips_thread_shutdown() was not called for this * thread. Do our best to clean up. * @@ -210,17 +214,6 @@ vips__thread_profile_init_cb(VipsThreadProfile *profile) vips_thread_profile_free(profile); } -static void * -vips__thread_profile_init(void *data) -{ - static GPrivate private = - G_PRIVATE_INIT((GDestroyNotify) vips__thread_profile_init_cb); - - vips_thread_profile_key = &private; - - return NULL; -} - static VipsThreadGate * vips_thread_gate_new(const char *gate_name) { @@ -237,12 +230,8 @@ vips_thread_gate_new(const char *gate_name) void vips__thread_profile_attach(const char *thread_name) { - static GOnce once = G_ONCE_INIT; - VipsThreadProfile *profile; - VIPS_ONCE(&once, vips__thread_profile_init, NULL); - VIPS_DEBUG_MSG("vips__thread_profile_attach: %s\n", thread_name); profile = g_new(VipsThreadProfile, 1); @@ -251,18 +240,17 @@ vips__thread_profile_attach(const char *thread_name) g_direct_hash, g_str_equal, NULL, (GDestroyNotify) vips_thread_gate_free); profile->memory = vips_thread_gate_new("memory"); - g_private_replace(vips_thread_profile_key, profile); + g_private_replace(&vips_thread_profile_key, profile); } static VipsThreadProfile * vips_thread_profile_get(void) { - return g_private_get(vips_thread_profile_key); + return g_private_get(&vips_thread_profile_key); } -/* This usually happens automatically when a thread shuts down, see - * vips__thread_profile_init() where we set a GDestroyNotify, but will not - * happen for the main thread. +/* This usually happens automatically when a thread shuts down, but that will + * not happen for the main thread. * * Shut down any stats on the main thread with this, see vips_shutdown() */ @@ -278,7 +266,7 @@ vips__thread_profile_detach(void) vips_thread_profile_save(profile); vips_thread_profile_free(profile); - g_private_set(vips_thread_profile_key, NULL); + g_private_set(&vips_thread_profile_key, NULL); } } diff --git a/libvips/iofuncs/generate.c b/libvips/iofuncs/generate.c index b4f4ff2f9a..8941762f75 100644 --- a/libvips/iofuncs/generate.c +++ b/libvips/iofuncs/generate.c @@ -628,15 +628,22 @@ vips_allocate_input_array(VipsImage *out, ...) static int write_vips(VipsRegion *region, VipsRect *area, void *a) { - size_t nwritten, count; + size_t count; void *buf; count = (size_t) region->bpl * area->height; buf = VIPS_REGION_ADDR(region, 0, area->top); do { - nwritten = write(region->im->fd, buf, count); - if (nwritten == (size_t) -1) + // write() uses int not size_t on windows, so we need to chunk + // ... max 1gb, why not + int chunk_size = VIPS_MIN(1024 * 1024 * 1024, count); + gint64 nwritten = write(region->im->fd, buf, chunk_size); + + /* n == 0 isn't strictly an error, but we treat it as + * one to make sure we don't get stuck in this loop. + */ + if (nwritten <= 0) return errno; buf = (void *) ((char *) buf + nwritten); diff --git a/libvips/iofuncs/header.c b/libvips/iofuncs/header.c index fa4164afbf..20a8bcf943 100644 --- a/libvips/iofuncs/header.c +++ b/libvips/iofuncs/header.c @@ -1295,7 +1295,10 @@ vips_set_value_from_pointer(GValue *value, void *data) else if (fundamental == G_TYPE_ENUM) g_value_set_enum(value, *((int *) data)); else if (fundamental == G_TYPE_STRING) - g_value_set_string(value, *((char **) data)); + // we don't want to copy the string (ie. the filename, usually) since + // it'll then be freed when the value is unset ... instead we must + // directly use the pointer owned by the VipsImage + g_value_set_static_string(value, *((char **) data)); else g_warning("%s: unimplemented vips_set_value_from_pointer() type %s", G_STRLOC, @@ -1360,6 +1363,7 @@ vips_image_get(const VipsImage *image, const char *name, GValue *value_copy) g_value_init(value_copy, gtype); vips_set_value_from_pointer(value_copy, G_STRUCT_MEMBER_P(image, field->offset)); + return 0; } } @@ -1373,6 +1377,7 @@ vips_image_get(const VipsImage *image, const char *name, GValue *value_copy) g_value_init(value_copy, gtype); vips_set_value_from_pointer(value_copy, G_STRUCT_MEMBER_P(image, field->offset)); + return 0; } } diff --git a/libvips/iofuncs/image.c b/libvips/iofuncs/image.c index e2cadfbce4..cb114e7cf7 100644 --- a/libvips/iofuncs/image.c +++ b/libvips/iofuncs/image.c @@ -2194,8 +2194,7 @@ VipsImage * vips_image_new_from_source(VipsSource *source, const char *option_string, ...) { - const char *filename = - vips_connection_filename(VIPS_CONNECTION(source)); + const char *filename = vips_connection_filename(VIPS_CONNECTION(source)); const char *operation_name; va_list ap; @@ -2250,8 +2249,7 @@ vips_image_new_from_source(VipsSource *source, vips_area_unref(VIPS_AREA(blob)); } else { - vips_error("VipsImage", - "%s", _("unable to load source")); + vips_error("VipsImage", "%s", _("unable to load source")); result = -1; } @@ -3196,7 +3194,7 @@ vips_image_write_prepare(VipsImage *image) int vips_image_write_line(VipsImage *image, int ypos, VipsPel *linebuffer) { - int linesize = VIPS_IMAGE_SIZEOF_LINE(image); + guint64 linesize = VIPS_IMAGE_SIZEOF_LINE(image); /* Is this the start of eval? */ @@ -3217,8 +3215,7 @@ vips_image_write_line(VipsImage *image, int ypos, VipsPel *linebuffer) switch (image->dtype) { case VIPS_IMAGE_SETBUF: case VIPS_IMAGE_SETBUF_FOREIGN: - memcpy(VIPS_IMAGE_ADDR(image, 0, ypos), - linebuffer, linesize); + memcpy(VIPS_IMAGE_ADDR(image, 0, ypos), linebuffer, linesize); break; case VIPS_IMAGE_OPENOUT: @@ -3229,10 +3226,8 @@ vips_image_write_line(VipsImage *image, int ypos, VipsPel *linebuffer) break; default: - vips_error("VipsImage", - _("unable to output to a %s image"), - vips_enum_string(VIPS_TYPE_IMAGE_TYPE, - image->dtype)); + vips_error("VipsImage", _("unable to output to a %s image"), + vips_enum_string(VIPS_TYPE_IMAGE_TYPE, image->dtype)); return -1; } diff --git a/libvips/iofuncs/init.c b/libvips/iofuncs/init.c index 87eb5ff01c..d49487f5c6 100644 --- a/libvips/iofuncs/init.c +++ b/libvips/iofuncs/init.c @@ -135,8 +135,6 @@ int vips__leak = 0; GQuark vips__image_pixels_quark = 0; #endif /*DEBUG_LEAK*/ -static gint64 vips_pipe_read_limit = 1024 * 1024 * 1024; - /** * vips_get_argv0: * @@ -468,11 +466,10 @@ vips_init(const char *argv0) vips_leak_set(TRUE); if (g_getenv("VIPS_TRACE")) vips_cache_set_trace(TRUE); - if (g_getenv("VIPS_PIPE_READ_LIMIT")) - vips_pipe_read_limit = - g_ascii_strtoll(g_getenv("VIPS_PIPE_READ_LIMIT"), - NULL, 10); - vips_pipe_read_limit_set(vips_pipe_read_limit); + + const char *pipe_read_limit; + if ((pipe_read_limit = g_getenv("VIPS_PIPE_READ_LIMIT"))) + vips_pipe_read_limit_set(vips__parse_size(pipe_read_limit)); #ifdef G_OS_WIN32 /* Windows has a limit of 512 files open at once for the fopen() family @@ -586,12 +583,12 @@ vips_init(const char *argv0) libdir, VIPS_MAJOR_VERSION, VIPS_MINOR_VERSION); #if ENABLE_DEPRECATED - /* Load any vips8 plugins from the vips libdir. + /* We had vips8 plugins for a while. */ vips_load_plugins("%s/vips-plugins-%d.%d", libdir, VIPS_MAJOR_VERSION, VIPS_MINOR_VERSION); - /* Load up any vips7 plugins in the vips libdir. We don't error on + /* Load up any vips7 plugins. We don't error on * failure, it's too annoying to have VIPS refuse to start because of * a broken plugin. */ @@ -601,8 +598,7 @@ vips_init(const char *argv0) vips_error_clear(); } - /* Also load from libdir. This is old and slightly broken behaviour - * :-( kept for back compat convenience. + /* Also load from libdir :-( kept for back compat convenience. */ if (im_load_plugins("%s", libdir)) { g_warning("%s", vips_error_buffer()); @@ -823,6 +819,15 @@ vips_cache_max_files_cb(const gchar *option_name, const gchar *value, return TRUE; } +static gboolean +vips_pipe_read_limit_cb(const gchar *option_name, const gchar *value, + gpointer data, GError **error) +{ + vips_pipe_read_limit_set(vips__parse_size(value)); + + return TRUE; +} + static GOptionEntry option_entries[] = { { "vips-info", 0, G_OPTION_FLAG_HIDDEN | G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, (gpointer) &vips_lib_info_cb, @@ -882,7 +887,7 @@ static GOptionEntry option_entries[] = { G_OPTION_ARG_CALLBACK, (gpointer) &vips_lib_config_cb, N_("print libvips config"), NULL }, { "vips-pipe-read-limit", 0, 0, - G_OPTION_ARG_INT64, (gpointer) &vips_pipe_read_limit, + G_OPTION_ARG_CALLBACK, (gpointer) &vips_pipe_read_limit_cb, N_("read at most this many bytes from a pipe"), NULL }, { NULL } }; diff --git a/libvips/iofuncs/sbuf.c b/libvips/iofuncs/sbuf.c index 2d29a6411b..f58a09202f 100644 --- a/libvips/iofuncs/sbuf.c +++ b/libvips/iofuncs/sbuf.c @@ -522,7 +522,7 @@ vips_sbuf_skip_whitespace(VipsSbuf *sbuf) /* # skip comments too. */ - if (ch == '#') { + while (ch == '#') { /* Probably EOF. */ if (!vips_sbuf_get_line(sbuf)) diff --git a/libvips/iofuncs/sinkdisc.c b/libvips/iofuncs/sinkdisc.c index e761de27d3..236c271ac3 100644 --- a/libvips/iofuncs/sinkdisc.c +++ b/libvips/iofuncs/sinkdisc.c @@ -371,10 +371,11 @@ wbuffer_allocate_fn(VipsThreadState *state, void *a, gboolean *stop) return -1; } - /* This will be the first tile of a new buffer ... - * stall for a moment to stress the caching system. + /* This will be the first tile of a new buffer ... mark this as a + * good place to stall for a moment if we want to stress the + * caching system. See threadpool.c. */ - state->stall = TRUE; + state->stall = TRUE; } } diff --git a/libvips/iofuncs/source.c b/libvips/iofuncs/source.c index a5f2b7ad95..3de317dd10 100644 --- a/libvips/iofuncs/source.c +++ b/libvips/iofuncs/source.c @@ -899,8 +899,7 @@ vips_source_pipe_read_to_position(VipsSource *source, gint64 target) break; } - if (target == -1 && - vips__pipe_read_limit != -1 && + if (vips__pipe_read_limit != -1 && source->read_position > vips__pipe_read_limit) { vips_error(nick, "%s", _("pipe too long")); return -1; @@ -1085,7 +1084,7 @@ vips_source_map(VipsSource *source, size_t *length_out) /* We don't know the length and must read and assemble in chunks. */ - if (!source->data && + if (source->is_pipe && vips_source_pipe_read_to_position(source, -1)) return NULL; diff --git a/libvips/iofuncs/target.c b/libvips/iofuncs/target.c index 7d13d54c05..5f315c1853 100644 --- a/libvips/iofuncs/target.c +++ b/libvips/iofuncs/target.c @@ -175,15 +175,17 @@ vips_target_write_real(VipsTarget *target, const void *data, size_t length) return result; } -static off_t -vips_target_seek_real(VipsTarget *target, off_t offset, int whence) +static gint64 +vips_target_seek_real(VipsTarget *target, gint64 offset, int whence) { VipsConnection *connection = VIPS_CONNECTION(target); const char *nick = vips_connection_nick(connection); - off_t new_position; + gint64 new_position; - VIPS_DEBUG_MSG("vips_target_seek_real: offset = %ld, whence = %d\n", + VIPS_DEBUG_MSG( + "vips_target_seek_real: offset = %" G_GINT64_FORMAT + ", whence = %d\n", offset, whence); if (target->memory_buffer) { @@ -212,7 +214,11 @@ vips_target_seek_real(VipsTarget *target, off_t offset, int whence) target->position = new_position; } else - new_position = lseek(connection->descriptor, offset, whence); + /* We need to use the vips__seek() wrapper so we can seek long + * files on Windows. + */ + new_position = vips__seek_no_error(connection->descriptor, + offset, whence); return new_position; } @@ -453,9 +459,10 @@ vips_target_write_unbuffered(VipsTarget *target, return 0; while (length > 0) { - gint64 bytes_written; - - bytes_written = class->write(target, data, length); + // write() uses int not size_t on windows, so we need to chunk + // ... max 1gb, why not + int chunk_size = VIPS_MIN(1024 * 1024 * 1024, length); + gint64 bytes_written = class->write(target, data, chunk_size); /* n == 0 isn't strictly an error, but we treat it as * one to make sure we don't get stuck in this loop. @@ -539,7 +546,7 @@ vips_target_write(VipsTarget *target, const void *buffer, size_t length) * * Arguments exactly as read(2). * - * Reading froma target sounds weird, but libtiff needs this for + * Reading from a target sounds weird, but libtiff needs this for * multi-page writes. This method will fail for targets like pipes. * * Returns: the number of bytes read, 0 on end of file, -1 on error. @@ -570,14 +577,16 @@ vips_target_read(VipsTarget *target, void *buffer, size_t length) * * Returns: the new seek position, -1 on error. */ -off_t -vips_target_seek(VipsTarget *target, off_t position, int whence) +gint64 +vips_target_seek(VipsTarget *target, gint64 position, int whence) { VipsTargetClass *class = VIPS_TARGET_GET_CLASS(target); - off_t new_position; + gint64 new_position; - VIPS_DEBUG_MSG("vips_target_seek: pos = %ld, whence = %d\n", + VIPS_DEBUG_MSG( + "vips_target_seek: pos = %" G_GINT64_FORMAT + ", whence = %d\n", position, whence); if (vips_target_flush(target)) @@ -585,7 +594,8 @@ vips_target_seek(VipsTarget *target, off_t position, int whence) new_position = class->seek(target, position, whence); - VIPS_DEBUG_MSG("vips_target_seek: new_position = %ld\n", + VIPS_DEBUG_MSG( + "vips_target_seek: new_position = %" G_GINT64_FORMAT "\n", new_position); return new_position; diff --git a/libvips/iofuncs/thread.c b/libvips/iofuncs/thread.c index acb6923632..80a43f38de 100644 --- a/libvips/iofuncs/thread.c +++ b/libvips/iofuncs/thread.c @@ -75,7 +75,7 @@ int vips__thinstrip_height = VIPS__THINSTRIP_HEIGHT; /* Set this GPrivate to indicate that is a libvips thread. */ -static GPrivate *is_vips_thread_key = NULL; +static GPrivate is_vips_thread_key; /* TRUE if we are a vips thread. We sometimes manage resource allocation * differently for vips threads since we can cheaply free stuff on thread @@ -84,7 +84,7 @@ static GPrivate *is_vips_thread_key = NULL; gboolean vips_thread_isvips(void) { - return g_private_get(is_vips_thread_key) != NULL; + return g_private_get(&is_vips_thread_key) != NULL; } /* Glib 2.32 revised the thread API. We need some compat functions. @@ -143,7 +143,7 @@ vips_thread_run(gpointer data) * worker. No need to call g_private_replace as there is no * GDestroyNotify handler associated with a worker. */ - g_private_set(is_vips_thread_key, info); + g_private_set(&is_vips_thread_key, info); result = info->func(info->data); @@ -361,10 +361,6 @@ vips_get_tile_size(VipsImage *im, void vips__thread_init(void) { - static GPrivate private = G_PRIVATE_INIT(NULL); - - is_vips_thread_key = &private; - if (vips__concurrency == 0) vips__concurrency = vips__concurrency_get_default(); } diff --git a/libvips/iofuncs/threadpool.c b/libvips/iofuncs/threadpool.c index 7bafd3c862..43765e7fde 100644 --- a/libvips/iofuncs/threadpool.c +++ b/libvips/iofuncs/threadpool.c @@ -102,7 +102,7 @@ static VipsThreadset *vips__threadset = NULL; /* Set this GPrivate to link a thread back to its VipsWorker struct. */ -static GPrivate *worker_key = NULL; +static GPrivate worker_key; /* Maximum value we allow for VIPS_CONCURRENCY. We need to stop huge values * killing the system. @@ -114,8 +114,6 @@ static GPrivate *worker_key = NULL; void vips__threadpool_init(void) { - static GPrivate private = G_PRIVATE_INIT(NULL); - /* 3 is the useful minimum, and huge values can crash the machine. */ const char *max_threads_env = g_getenv("VIPS_MAX_THREADS"); @@ -123,8 +121,6 @@ vips__threadpool_init(void) ? VIPS_CLIP(3, atoi(max_threads_env), MAX_THREADS) : 0; - worker_key = &private; - if (g_getenv("VIPS_STALL")) vips__stall = TRUE; @@ -395,7 +391,7 @@ vips_thread_main_loop(void *a, void *b) VIPS_GATE_START("vips_thread_main_loop: thread"); - g_private_set(worker_key, worker); + g_private_set(&worker_key, worker); /* Process work units! Always tick, even if we are stopping, so the * main thread will wake up for exit. @@ -421,7 +417,7 @@ vips_thread_main_loop(void *a, void *b) g_mutex_unlock(pool->allocate_lock); VIPS_FREE(worker); - g_private_set(worker_key, NULL); + g_private_set(&worker_key, NULL); /* We are done: tell the main thread. */ @@ -460,7 +456,7 @@ vips_worker_new(VipsThreadpool *pool) void vips__worker_lock(GMutex *mutex) { - VipsWorker *worker = (VipsWorker *) g_private_get(worker_key); + VipsWorker *worker = (VipsWorker *) g_private_get(&worker_key); if (worker) g_atomic_int_add(&worker->pool->n_waiting, 1); @@ -470,15 +466,21 @@ vips__worker_lock(GMutex *mutex) } static void -vips_threadpool_free(VipsThreadpool *pool) +vips_threadpool_wait(VipsThreadpool *pool) { - VIPS_DEBUG_MSG("vips_threadpool_free: \"%s\" (%p)\n", - pool->im->filename, pool); - /* Wait for them all to exit. */ pool->stop = TRUE; vips_semaphore_downn(&pool->n_workers, 0); +} + +static void +vips_threadpool_free(VipsThreadpool *pool) +{ + VIPS_DEBUG_MSG("vips_threadpool_free: \"%s\" (%p)\n", + pool->im->filename, pool); + + vips_threadpool_wait(pool); VIPS_FREEF(vips_g_mutex_free, pool->allocate_lock); vips_semaphore_destroy(&pool->n_workers); @@ -705,12 +707,14 @@ vips_threadpool_run(VipsImage *im, } } + /* This will block until the last worker completes. + */ + vips_threadpool_wait(pool); + /* Return 0 for success. */ result = pool->error ? -1 : 0; - /* This will block until the last worker completes. - */ vips_threadpool_free(pool); if (!vips_image_get_concurrency(im, 0)) diff --git a/libvips/iofuncs/threadset.c b/libvips/iofuncs/threadset.c index 153fd2b43e..10b48a332b 100644 --- a/libvips/iofuncs/threadset.c +++ b/libvips/iofuncs/threadset.c @@ -52,48 +52,40 @@ #include #include -typedef struct _VipsThreadsetMember { - /* The set we are part of. - */ - VipsThreadset *set; - - /* The underlying glib thread object. - */ - GThread *thread; - - /* The task the thread should run next. +typedef struct _VipsThreadExec { + /* The source of this function. */ const char *domain; - GFunc func; - void *data; - void *user_data; - /* The thread waits on this when it's free. + /* The function to execute within the thread. */ - VipsSemaphore idle; + GFunc func; - /* Set by our controller to request exit. + /* User data that is handed over to func when it is called. */ - gboolean kill; -} VipsThreadsetMember; + gpointer data; +} VipsThreadExec; struct _VipsThreadset { - GMutex *lock; - - /* All the VipsThreadsetMember we have created. + /* An asynchronous queue of tasks. */ - GSList *members; + GAsyncQueue *queue; - /* The set of currently idle threads. + /* Idle threads wait on this semaphore. */ - GSList *free; + VipsSemaphore idle; - /* The current number of threads, the highwater mark, and - * the max we allow before blocking thread creation. + /* The current number of (idle-)threads, the highwater mark, + * and the max we allow before blocking thread creation. */ int n_threads; int n_threads_highwater; + int n_idle_threads; int max_threads; + + /* Set by our controller to request exit. + */ + gboolean exit; }; /* The maximum relative time (in microseconds) that a thread waits @@ -101,113 +93,157 @@ struct _VipsThreadset { */ static const gint64 max_idle_time = 15 * G_TIME_SPAN_SECOND; +/* The maximum number of idle threads. + */ +static const int max_idle_threads = 2; + +static gboolean +vips_threadset_reuse_wait(VipsThreadset *set) +{ + int result; + + /* A superfluous thread? Leave this thread. + */ + if (++set->n_idle_threads > max_idle_threads) + return FALSE; + + g_async_queue_unlock(set->queue); + + /* Wait for at least 15 seconds before leaving this thread. + */ + result = vips_semaphore_down_timeout(&set->idle, max_idle_time); + + g_async_queue_lock(set->queue); + + return result != -1; +} + +static void +vips_threadset_free_internal(VipsThreadset *set) +{ + VIPS_FREEF(g_async_queue_unref, set->queue); + vips_semaphore_destroy(&set->idle); + VIPS_FREE(set); +} + /* The thread work function. */ static void * vips_threadset_work(void *pointer) { - VipsThreadsetMember *member = (VipsThreadsetMember *) pointer; - VipsThreadset *set = member->set; + VipsThreadset *set = (VipsThreadset *) pointer; + gboolean cleanup = FALSE; - VIPS_DEBUG_MSG("vips_threadset_work: starting %p\n", member); + VIPS_DEBUG_MSG("vips_threadset_work: starting %p\n", g_thread_self()); + + g_async_queue_lock(set->queue); for (;;) { - /* Wait for at least 15 seconds to be given work. + /* Pop a task from the queue. If the number of threads is limited, + * this will block until a task becomes available. Otherwise, it + * waits for at least 1/2 second before being marked as idle. */ - if (vips_semaphore_down_timeout(&member->idle, - max_idle_time) == -1) - break; + VipsThreadExec *task = set->max_threads > 0 + ? g_async_queue_pop_unlocked(set->queue) + : g_async_queue_timeout_pop_unlocked(set->queue, + G_USEC_PER_SEC / 2); - /* Killed or no task available? Leave this thread. + /* Request to exit? Leave this thread. */ - if (member->kill || - !member->func) + if (set->exit) { + /* The last thread should cleanup the set. + */ + cleanup = set->n_threads == 1; break; + } + + /* No task available? Wait for being reused. + */ + if (task == NULL) { + if (!vips_threadset_reuse_wait(set)) { + set->n_idle_threads--; + break; + } + + continue; + } + + /* A task was received and there was no request to exit. + */ + g_async_queue_unlock(set->queue); /* If we're profiling, attach a prof struct to this thread. */ if (vips__thread_profile) - vips__thread_profile_attach(member->domain); + vips__thread_profile_attach(task->domain); /* Execute the task. */ - member->func(member->data, member->user_data); + task->func(task->data, NULL); /* Free any thread-private resources -- they will not be * useful for the next task to use this thread. */ vips_thread_shutdown(); + VIPS_FREE(task); - member->domain = NULL; - member->func = NULL; - member->data = NULL; - member->user_data = NULL; - - /* We are free ... back on the free list! - */ - g_mutex_lock(set->lock); - set->free = g_slist_prepend(set->free, member); - g_mutex_unlock(set->lock); + g_async_queue_lock(set->queue); } - /* Timed-out or kill has been requested ... remove from both free - * and member list. + /* Timed-out or exit has been requested, decrement number of threads. */ - g_mutex_lock(set->lock); - set->free = g_slist_remove(set->free, member); - set->members = g_slist_remove(set->members, member); - set->n_threads -= 1; - VIPS_DEBUG_MSG("vips_threadset_work: stopping %p (%d remaining)\n", - member, set->n_threads); - g_mutex_unlock(set->lock); + set->n_threads--; + VIPS_DEBUG_MSG( + "vips_threadset_work: stopping %p (%d remaining, %d idle)\n", + g_thread_self(), set->n_threads, set->n_idle_threads); - vips_semaphore_destroy(&member->idle); + g_async_queue_unlock(set->queue); - VIPS_FREE(member); + if (cleanup) + vips_threadset_free_internal(set); return NULL; } -/* Create a new idle member for the set. +/* Add a new thread to the set. */ -static VipsThreadsetMember * -vips_threadset_add(VipsThreadset *set) +static gboolean +vips_threadset_add_thread(VipsThreadset *set) { - VipsThreadsetMember *member; + gboolean reused = FALSE; - if (set->max_threads && - set->n_threads >= set->max_threads) { - vips_error("VipsThreadset", - "%s", _("threadset is exhausted")); - return NULL; - } + /* There are already sufficient threads running. + */ + if (set->max_threads > 0 && + set->n_threads >= set->max_threads) + return TRUE; - member = g_new0(VipsThreadsetMember, 1); - member->set = set; + if (set->n_idle_threads > 0) { + vips_semaphore_up(&set->idle); - vips_semaphore_init(&member->idle, 0, "idle"); + set->n_idle_threads--; + reused = TRUE; + } - if (!(member->thread = vips_g_thread_new("libvips worker", - vips_threadset_work, member))) { - vips_semaphore_destroy(&member->idle); - VIPS_FREE(member); + if (!reused) { + /* No idle thread was found, we have to start a new one. + */ + GThread *thread; - return NULL; - } + if (!(thread = vips_g_thread_new("libvips worker", + vips_threadset_work, set))) + return FALSE; - /* Ensure idle threads are freed on exit, this - * ref is increased before the thread is joined. - */ - g_thread_unref(member->thread); + /* Ensure threads are freed on exit. + */ + g_thread_unref(thread); - g_mutex_lock(set->lock); - set->members = g_slist_prepend(set->members, member); - set->n_threads += 1; - set->n_threads_highwater = - VIPS_MAX(set->n_threads_highwater, set->n_threads); - g_mutex_unlock(set->lock); + set->n_threads++; + set->n_threads_highwater = + VIPS_MAX(set->n_threads_highwater, set->n_threads); + } - return member; + return TRUE; } /** @@ -220,8 +256,8 @@ vips_threadset_add(VipsThreadset *set) * vips_threadset_run(), with no limit on the number of threads. * * If @max_threads is > 0, then that many threads will be created by - * vips_threadset_new() during startup and vips_threadset_run() will fail if - * no free threads are available. + * vips_threadset_new() during startup and vips_threadset_run() will + * not spawn any additional threads. * * Returns: the new threadset. */ @@ -231,19 +267,16 @@ vips_threadset_new(int max_threads) VipsThreadset *set; set = g_new0(VipsThreadset, 1); - set->lock = vips_g_mutex_new(); + set->queue = g_async_queue_new(); + vips_semaphore_init(&set->idle, 0, "idle"); set->max_threads = max_threads; if (set->max_threads > 0) for (int i = 0; i < set->max_threads; i++) { - VipsThreadsetMember *member; - - if (!(member = vips_threadset_add(set))) { + if (!vips_threadset_add_thread(set)) { vips_threadset_free(set); return NULL; } - - set->free = g_slist_prepend(set->free, member); } return set; @@ -256,8 +289,9 @@ vips_threadset_new(int max_threads) * @func: the task to execute * @data: the task's data * - * Execute a task in a thread. If there are no idle threads, create a new one, - * provided we are under @max_threads. + * Execute a task in a thread. If there are no idle threads and the maximum + * thread limit specified by @max_threads has not been reached, a new thread + * will be spawned. * * See also: vips_threadset_new(). * @@ -267,91 +301,71 @@ int vips_threadset_run(VipsThreadset *set, const char *domain, GFunc func, gpointer data) { - VipsThreadsetMember *member; + VipsThreadExec *task; - member = NULL; + g_async_queue_lock(set->queue); - /* Try to get an idle thread. + /* Create a new thread if there are no waiting threads in the queue. */ - g_mutex_lock(set->lock); - if (set->free) { - member = (VipsThreadsetMember *) set->free->data; - set->free = g_slist_remove(set->free, member); - } - g_mutex_unlock(set->lock); + if (g_async_queue_length_unlocked(set->queue) >= 0) + if (!vips_threadset_add_thread(set)) { + g_async_queue_unlock(set->queue); - /* None? Make a new idle but not free member. - */ - if (!member) - member = vips_threadset_add(set); + /* Thread create has failed. + */ + return -1; + } - /* Still nothing? Thread create has failed. + /* Allocate the task and push it into the queue. */ - if (!member) - return -1; + task = g_new0(VipsThreadExec, 1); + task->domain = domain; + task->func = func; + task->data = data; - /* Allocate the task and set it going. - */ - member->domain = domain; - member->func = func; - member->data = data; - member->user_data = NULL; - vips_semaphore_up(&member->idle); + g_async_queue_push_unlocked(set->queue, task); + g_async_queue_unlock(set->queue); return 0; } -/* Kill a member. - */ -static void -vips_threadset_kill_member(VipsThreadsetMember *member) -{ - GThread *thread; - - thread = g_thread_ref(member->thread); - member->kill = TRUE; - - vips_semaphore_up(&member->idle); - - (void) g_thread_join(thread); - - /* member is freed on thread exit. - */ -} - /** * vips_threadset_free: * @set: the threadset to free * - * Free a threadset. This call will block until all pending tasks are - * finished. + * Free a threadset. This call returns immediately. */ void vips_threadset_free(VipsThreadset *set) { VIPS_DEBUG_MSG("vips_threadset_free: %p\n", set); - /* Try to get and finish a thread. - */ - for (;;) { - VipsThreadsetMember *member; + g_async_queue_lock(set->queue); - member = NULL; - g_mutex_lock(set->lock); - if (set->members) - member = (VipsThreadsetMember *) set->members->data; - g_mutex_unlock(set->lock); + if (vips__leak) + printf("vips_threadset_free: peak of %d threads\n", + set->n_threads_highwater); - if (!member) - break; + set->exit = TRUE; - vips_threadset_kill_member(member); + /* No threads left, we cleanup. + */ + if (set->n_threads == 0) { + g_async_queue_unlock(set->queue); + vips_threadset_free_internal(set); + return; } - if (vips__leak) - printf("vips_threadset_free: peak of %d threads\n", - set->n_threads_highwater); + /* Wake up idle threads, if any. + */ + if (set->n_idle_threads > 0) + vips_semaphore_upn(&set->idle, set->n_idle_threads); - VIPS_FREEF(vips_g_mutex_free, set->lock); - VIPS_FREE(set); + /* Send dummy data to the queue, causing threads to wake up and check + * the above set->exit condition. + */ + for (int i = 0; i < set->n_threads; i++) + g_async_queue_push_unlocked(set->queue, GUINT_TO_POINTER(1)); + + g_async_queue_unlock(set->queue); } diff --git a/libvips/iofuncs/util.c b/libvips/iofuncs/util.c index ef5a9c41cd..d6367e1bf2 100644 --- a/libvips/iofuncs/util.c +++ b/libvips/iofuncs/util.c @@ -460,28 +460,8 @@ vips_vsnprintf(char *str, size_t size, const char *format, va_list ap) { #ifdef HAVE_VSNPRINTF return vsnprintf(str, size, format, ap); -#else /*HAVE_VSNPRINTF*/ - /* Bleurg! - */ - int n; - static char buf[MAX_BUF]; - - /* We can't return an error code, we may already have trashed the - * stack. We must stop immediately. - */ - if (size > MAX_BUF) - vips_error_exit("panic: buffer overflow " - "(request to write %lu bytes to buffer of %d bytes)", - (unsigned long) size, MAX_BUF); - n = vsprintf(buf, format, ap); - if (n > MAX_BUF) - vips_error_exit("panic: buffer overflow " - "(%d bytes written to buffer of %d bytes)", - n, MAX_BUF); - - vips_strncpy(str, buf, size); - - return n; +#else /*!HAVE_VSNPRINTF*/ + return g_vsnprintf(str, size, format, ap); #endif /*HAVE_VSNPRINTF*/ } @@ -558,11 +538,16 @@ int vips__write(int fd, const void *buf, size_t count) { do { - size_t nwritten = write(fd, buf, count); + // write() uses int not size_t on windows, so we need to chunk + // ... max 1gb, why not + int chunk_size = VIPS_MIN(1024 * 1024 * 1024, count); + gint64 nwritten = write(fd, buf, chunk_size); - if (nwritten == (size_t) -1) { - vips_error_system(errno, "vips__write", - "%s", _("write failed")); + /* n == 0 isn't strictly an error, but we treat it as + * one to make sure we don't get stuck in this loop. + */ + if (nwritten <= 0) { + vips_error_system(errno, "vips__write", "%s", _("write failed")); return -1; } @@ -747,8 +732,7 @@ vips__file_read(FILE *fp, const char *filename, size_t *length_out) if (len > 1024 * 1024 * 1024) { /* Over a gb? Seems crazy! */ - vips_error("vips__file_read", - _("\"%s\" too long"), filename); + vips_error("vips__file_read", _("\"%s\" too long"), filename); return NULL; } @@ -770,8 +754,7 @@ vips__file_read(FILE *fp, const char *filename, size_t *length_out) if (size > 1024 * 1024 * 1024 || !(str2 = realloc(str, size))) { free(str); - vips_error("vips__file_read", - "%s", _("out of memory")); + vips_error("vips__file_read", "%s", _("out of memory")); return NULL; } str = str2; @@ -779,8 +762,7 @@ vips__file_read(FILE *fp, const char *filename, size_t *length_out) /* -1 to allow space for an extra NULL we add later. */ read = fread(str + len, sizeof(char), - (size - len - 1) / sizeof(char), - fp); + (size - len - 1) / sizeof(char), fp); len += read; } while (!feof(fp)); @@ -798,8 +780,7 @@ vips__file_read(FILE *fp, const char *filename, size_t *length_out) if (read != (size_t) len) { g_free(str); vips_error("vips__file_read", - _("error reading from file \"%s\""), - filename); + _("error reading from file \"%s\""), filename); return NULL; } } @@ -1260,7 +1241,11 @@ vips__token_get(const char *p, VipsToken *token, char *string, int size) const char *q; int ch; int n; - int i; + + /* string return defaults to "". + */ + if (size > 0) + string[0] = '\0'; /* Parse this token with p. */ @@ -1296,61 +1281,57 @@ vips__token_get(const char *p, VipsToken *token, char *string, int size) case '"': case '\'': - /* Parse a quoted string. Copy up to ", interpret any \", - * error if no closing ". + /* Parse a quoted string. Copy up to " or end of string, interpret + * any \", */ *token = VIPS_TOKEN_STRING; do { - /* Number of characters until the next quote - * character or end of string. + /* Move q to the next matching quote, or the end of the string. */ - if ((q = strchr(p + 1, ch))) - n = q - p + 1; - else - n = strlen(p + 1); + if (!(q = strchr(p + 1, ch))) + q = p + strlen(p); - /* How much can we copy to the buffer? - */ - i = VIPS_MIN(n, size); - vips_strncpy(string, p + 1, i); + // number of characters we copy to the output + n = VIPS_MIN(q - p - 1, size - 1); + vips_strncpy(string, p + 1, n + 1); /* We might have stopped at an escaped quote. If the - * string was not truncated, swap the preceding - * backslash for a quote. + * char before the end is a backslash, swap it for a quote. */ - if (p[n + 1] == ch && p[n] == '\\' && i == n) - string[i - 1] = ch; + if (q[-1] == '\\') + string[n - 1] = ch; - string += i; - size -= i; - p += n + 1; + string += n; + size -= n; + p = q; } while (p[0] && p[-1] == '\\'); - p += 1; + // step over the terminating quote, if we hit one + if (p[0] == ch) + p += 1; break; default: - /* It's an unquoted string: read up to the next non-string - * character. We don't allow two strings next to each other, - * so the next break must be brackets, equals, comma. + /* It's an unquoted string: read up to the next non-string character. + * We don't allow two strings next to each other, so the next break + * must be brackets, equals, comma. */ *token = VIPS_TOKEN_STRING; q = p + strcspn(p, "[]=,"); - i = VIPS_MIN(q - p, size); - vips_strncpy(string, p, i + 1); + n = VIPS_MIN(q - p, size); + vips_strncpy(string, p, n + 1); p = q; - /* We remove leading whitespace, so we trim trailing - * whitespace from unquoted strings too. Only if the string - * hasn't been truncated. + /* We remove leading whitespace, so we trim trailing whitespace from + * unquoted strings too. Only if the string hasn't been truncated. */ - if (i != size) - while (i > 0 && isspace(string[i - 1])) { - string[i - 1] = '\0'; - i--; + if (n != size) + while (n > 0 && isspace(string[n - 1])) { + string[n - 1] = '\0'; + n--; } break; diff --git a/libvips/iofuncs/vips.c b/libvips/iofuncs/vips.c index 39f84b1645..b3a83b638a 100644 --- a/libvips/iofuncs/vips.c +++ b/libvips/iofuncs/vips.c @@ -506,7 +506,7 @@ vips__has_extension_block(VipsImage *im) /* Read everything after the pixels into memory. */ void * -vips__read_extension_block(VipsImage *im, int *size) +vips__read_extension_block(VipsImage *im, size_t *size) { gint64 psize; void *buf; @@ -515,8 +515,7 @@ vips__read_extension_block(VipsImage *im, int *size) g_assert(im->file_length > 0); if (im->file_length - psize > 100 * 1024 * 1024) { vips_error("VipsImage", - "%s", _("more than 100 megabytes of XML? " - "sufferin' succotash!")); + "%s", _("more than 100 megabytes of XML? sufferin' succotash!")); return NULL; } if (im->file_length - psize == 0) @@ -774,7 +773,7 @@ readhist(VipsImage *im) } int -vips__write_extension_block(VipsImage *im, void *buf, int size) +vips__write_extension_block(VipsImage *im, void *buf, size_t size) { gint64 length; gint64 psize; @@ -794,7 +793,7 @@ vips__write_extension_block(VipsImage *im, void *buf, int size) return -1; #ifdef DEBUG - printf("vips__write_extension_block: written %d bytes of XML to %s\n", + printf("vips__write_extension_block: written %zd bytes of XML to %s\n", size, im->filename); #endif /*DEBUG*/ diff --git a/libvips/resample/nohalo.cpp b/libvips/resample/nohalo.cpp index 90104079c2..8096a2aefc 100644 --- a/libvips/resample/nohalo.cpp +++ b/libvips/resample/nohalo.cpp @@ -917,7 +917,7 @@ lbbicubic(const double c00, * const double m13 = NOHALO_MIN(m7, qua_fou); * const double M13 = NOHALO_MAX(M7, qua_fou); * - * This also allows reodering the comparisons to put space between + * This also allows reordering the comparisons to put space between * the computation of a result and its use. */ const double m9 = NOHALO_MIN(m5, m4); diff --git a/libvips/resample/reduceh_hwy.cpp b/libvips/resample/reduceh_hwy.cpp index 371850cda5..234bc90138 100644 --- a/libvips/resample/reduceh_hwy.cpp +++ b/libvips/resample/reduceh_hwy.cpp @@ -135,12 +135,12 @@ vips_reduceh_uchar_hwy(VipsPel *pout, VipsPel *pin, auto source = LoadU(du8, p); p += bands * 4; - auto pix = TableLookupBytes(source, shuf_lo); + auto pix = TableLookupBytesOr0(source, shuf_lo); sum0 = ReorderWidenMulAccumulate(di32, pix, mmk_lo, sum0, /* byref */ sum1); - pix = TableLookupBytes(source, shuf_hi); + pix = TableLookupBytesOr0(source, shuf_hi); sum0 = ReorderWidenMulAccumulate(di32, pix, mmk_hi, sum0, /* byref */ sum1); @@ -153,7 +153,7 @@ vips_reduceh_uchar_hwy(VipsPel *pout, VipsPel *pin, auto source = LoadU(du8, p); p += bands * 2; - auto pix = TableLookupBytes(source, shuf_lo); + auto pix = TableLookupBytesOr0(source, shuf_lo); sum0 = ReorderWidenMulAccumulate(di32, pix, mmk_lo, sum0, /* byref */ sum1); diff --git a/libvips/resample/thumbnail.c b/libvips/resample/thumbnail.c index 4637586fd1..57d86fa197 100644 --- a/libvips/resample/thumbnail.c +++ b/libvips/resample/thumbnail.c @@ -39,6 +39,8 @@ * - add fail_on * 1/3/23 kleisauke * - skip colourspace conversion when needed + * 27/1/24 + * - make icc profile transforms always write 8 bits */ /* @@ -245,14 +247,10 @@ vips_thumbnail_read_header(VipsThumbnail *thumbnail, VipsImage *image) for (level = 0; level < level_count; level++) { char name[256]; - vips_snprintf(name, 256, - "openslide.level[%d].width", level); - thumbnail->level_width[level] = - get_int(image, name, 0); - vips_snprintf(name, 256, - "openslide.level[%d].height", level); - thumbnail->level_height[level] = - get_int(image, name, 0); + vips_snprintf(name, 256, "openslide.level[%d].width", level); + thumbnail->level_width[level] = get_int(image, name, 0); + vips_snprintf(name, 256, "openslide.level[%d].height", level); + thumbnail->level_height[level] = get_int(image, name, 0); } } } @@ -402,15 +400,9 @@ vips_thumbnail_calculate_shrink(VipsThumbnail *thumbnail, { /* If we will be rotating, swap the target width and height. */ - gboolean rotate = - thumbnail->swap && - thumbnail->auto_rotate; - int target_width = rotate - ? thumbnail->height - : thumbnail->width; - int target_height = rotate - ? thumbnail->width - : thumbnail->height; + gboolean rotate = thumbnail->swap && thumbnail->auto_rotate; + int target_width = rotate ? thumbnail->height : thumbnail->width; + int target_height = rotate ? thumbnail->width : thumbnail->height; VipsDirection direction; @@ -600,8 +592,7 @@ vips_thumbnail_open(VipsThumbnail *thumbnail) thumbnail->input_width, thumbnail->input_height); else if (vips_isprefix("VipsForeignLoadTiff", thumbnail->loader) || vips_isprefix("VipsForeignLoadJp2k", thumbnail->loader) || - vips_isprefix("VipsForeignLoadOpenslide", - thumbnail->loader)) { + vips_isprefix("VipsForeignLoadOpenslide", thumbnail->loader)) { if (thumbnail->level_count > 0) factor = vips_thumbnail_find_pyrlevel(thumbnail, thumbnail->input_width, @@ -735,8 +726,7 @@ vips_thumbnail_build(VipsObject *object) thumbnail->import_profile)) { g_info("importing to XYZ PCS"); if (thumbnail->import_profile) - g_info("fallback input profile %s", - thumbnail->import_profile); + g_info("fallback input profile %s", thumbnail->import_profile); if (vips_icc_import(in, &t[2], "input_profile", thumbnail->import_profile, @@ -760,10 +750,8 @@ vips_thumbnail_build(VipsObject *object) interpretation = VIPS_INTERPRETATION_scRGB; g_info("converting to processing space %s", - vips_enum_nick(VIPS_TYPE_INTERPRETATION, - interpretation)); - if (vips_colourspace(in, &t[2], interpretation, - NULL)) + vips_enum_nick(VIPS_TYPE_INTERPRETATION, interpretation)); + if (vips_colourspace(in, &t[2], interpretation, NULL)) return -1; in = t[2]; } @@ -780,10 +768,8 @@ vips_thumbnail_build(VipsObject *object) interpretation = VIPS_INTERPRETATION_sRGB; g_info("converting to processing space %s", - vips_enum_nick(VIPS_TYPE_INTERPRETATION, - interpretation)); - if (vips_colourspace(in, &t[2], interpretation, - NULL)) + vips_enum_nick(VIPS_TYPE_INTERPRETATION, interpretation)); + if (vips_colourspace(in, &t[2], interpretation, NULL)) return -1; in = t[2]; } @@ -797,10 +783,9 @@ vips_thumbnail_build(VipsObject *object) * page_height or we'll have pixels straddling page boundaries. */ if (in->Ysize > preshrunk_page_height) { - int target_page_height = VIPS_RINT( - preshrunk_page_height / vshrink); - int target_image_height = target_page_height * - thumbnail->n_loaded_pages; + int target_page_height = VIPS_RINT(preshrunk_page_height / vshrink); + int target_image_height = + target_page_height * thumbnail->n_loaded_pages; vshrink = (double) in->Ysize / target_image_height; } @@ -826,9 +811,7 @@ vips_thumbnail_build(VipsObject *object) in = t[4]; } - if (vips_resize(in, &t[5], 1.0 / hshrink, - "vscale", 1.0 / vshrink, - NULL)) + if (vips_resize(in, &t[5], 1.0 / hshrink, "vscale", 1.0 / vshrink, NULL)) return -1; in = t[5]; @@ -844,18 +827,19 @@ vips_thumbnail_build(VipsObject *object) * accidentally turn into an animated image later. */ if (thumbnail->n_loaded_pages > 1) { - int output_page_height = - VIPS_RINT(preshrunk_page_height / vshrink); + int output_page_height = VIPS_RINT(preshrunk_page_height / vshrink); if (vips_copy(in, &t[8], NULL)) return -1; in = t[8]; - vips_image_set_int(in, - VIPS_META_PAGE_HEIGHT, output_page_height); + vips_image_set_int(in, VIPS_META_PAGE_HEIGHT, output_page_height); } /* Colour management. + * + * We always export as depth 8, to match the no profile case which + * uses vips_colourspace(sRGB|B_W). */ if (have_imported) { /* We are in PCS. Export with the output profile, if any (this @@ -866,6 +850,7 @@ vips_thumbnail_build(VipsObject *object) if (vips_icc_export(in, &t[9], "output_profile", thumbnail->export_profile, "intent", thumbnail->intent, + "depth", 8, NULL)) return -1; in = t[9]; @@ -874,11 +859,11 @@ vips_thumbnail_build(VipsObject *object) /* We can transform to the output with a pair of ICC profiles. */ g_info("transforming with supplied profiles"); - if (vips_icc_transform(in, &t[9], - thumbnail->export_profile, + if (vips_icc_transform(in, &t[9], thumbnail->export_profile, "input_profile", thumbnail->import_profile, "intent", thumbnail->intent, "embedded", TRUE, + "depth", 8, NULL)) return -1; @@ -889,11 +874,11 @@ vips_thumbnail_build(VipsObject *object) * and need to go to PCS, then export. */ g_info("exporting with %s", thumbnail->export_profile); - if (vips_colourspace(in, &t[9], - VIPS_INTERPRETATION_XYZ, NULL) || + if (vips_colourspace(in, &t[9], VIPS_INTERPRETATION_XYZ, NULL) || vips_icc_export(t[9], &t[10], "output_profile", thumbnail->export_profile, "intent", thumbnail->intent, + "depth", 8, NULL)) return -1; in = t[10]; @@ -910,18 +895,15 @@ vips_thumbnail_build(VipsObject *object) interpretation = VIPS_INTERPRETATION_sRGB; g_info("converting to output space %s", - vips_enum_nick(VIPS_TYPE_INTERPRETATION, - interpretation)); - if (vips_colourspace(in, &t[9], interpretation, - NULL)) + vips_enum_nick(VIPS_TYPE_INTERPRETATION, interpretation)); + if (vips_colourspace(in, &t[9], interpretation, NULL)) return -1; in = t[9]; } if (thumbnail->auto_rotate && thumbnail->orientation != 1) { - g_info("rotating by EXIF orientation %d", - thumbnail->orientation); + g_info("rotating by EXIF orientation %d", thumbnail->orientation); /* Need to copy to memory, we have to stay seq. */ if (!(t[11] = vips_image_copy_memory(in)) || @@ -946,8 +928,7 @@ vips_thumbnail_build(VipsObject *object) * FIXME ... could skip the copy if we've rotated. */ if (!(t[13] = vips_image_copy_memory(in)) || - vips_smartcrop(t[13], &t[14], - crop_width, crop_height, + vips_smartcrop(t[13], &t[14], crop_width, crop_height, "interesting", thumbnail->crop, NULL)) return -1; @@ -1130,8 +1111,7 @@ vips_thumbnail_file_open(VipsThumbnail *thumbnail, double factor) "shrink", (int) factor, NULL); } - else if (vips_isprefix("VipsForeignLoadOpenslide", - thumbnail->loader)) { + else if (vips_isprefix("VipsForeignLoadOpenslide", thumbnail->loader)) { return vips_image_new_from_file(file->filename, "access", VIPS_ACCESS_SEQUENTIAL, "fail_on", thumbnail->fail_on, @@ -1553,8 +1533,7 @@ vips_thumbnail_source_get_info(VipsThumbnail *thumbnail) g_info("thumbnailing source"); - if (!(thumbnail->loader = vips_foreign_find_load_source( - source->source)) || + if (!(thumbnail->loader = vips_foreign_find_load_source( source->source)) || !(image = vips_image_new_from_source(source->source, source->option_string, NULL))) return -1; diff --git a/meson.build b/meson.build index 6992e29821..f3a7294d72 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('vips', 'c', 'cpp', - version: '8.15.0', + version: '8.15.5', 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 = 5 library_current = 59 library_age = 17 library_version = '@0@.@1@.@2@'.format(library_current - library_age, library_age, library_revision) @@ -35,7 +35,7 @@ pkg = import('pkgconfig') i18n = import('i18n') # if we're optimising (eg. release mode) we turn off cast checks and g_asserts -if get_option('optimization') in ['2', '3', 's'] +if get_option('optimization') not in ['0', 'g'] add_project_arguments('-DG_DISABLE_CAST_CHECKS', language : ['cpp', 'c']) add_project_arguments('-DG_DISABLE_CHECKS', language : ['cpp', 'c']) add_project_arguments('-DG_DISABLE_ASSERT', language : ['cpp', 'c']) @@ -141,15 +141,35 @@ static int __attribute__((target_clones("default,avx"))) has_target_clones(void) { return 0; } + int main(void) { - return has_target_clones(); + int (*func)(void) = has_target_clones; + return func(); } ''' -if cc.compiles(target_clones_check, args: '-Werror', name: 'Has target_clones attribute') +if meson.can_run_host_binaries() + rres = cc.run(target_clones_check, args: '-Werror', name: 'Has target_clones attribute') + have_target_clones = rres.compiled() and rres.returncode() == 0 +else + have_target_clones = cc.links(target_clones_check, args: '-Werror', name: 'Has target_clones attribute') +endif +if have_target_clones cfg_var.set('HAVE_TARGET_CLONES', '1') endif -func_names = [ 'vsnprintf', '_aligned_malloc', 'posix_memalign', 'memalign', 'cbrt', 'hypot', 'atan2', 'asinh' ] +# windows needs to include stdio.h to find vsnprintf() +if cc.has_function('vsnprintf', prefix: '#include ') + cfg_var.set('HAVE_VSNPRINTF', '1') +endif + +func_names = [ '_aligned_malloc', 'posix_memalign', 'memalign' ] +foreach func_name : func_names + if cc.has_function(func_name) + cfg_var.set('HAVE_' + func_name.to_upper(), '1') + endif +endforeach + +func_names = [ 'cbrt', 'hypot', 'atan2', 'asinh' ] foreach func_name : func_names if cc.has_function(func_name, dependencies: m_dep) cfg_var.set('HAVE_' + func_name.to_upper(), '1') @@ -500,8 +520,12 @@ if libheif_dep.found() if libheif_dep.version().version_compare('>=1.7.0') cfg_var.set('HAVE_HEIF_AVIF', '1') endif + # added in 1.11.0 + if cpp.has_member('struct heif_encoding_options', 'output_nclx_profile', prefix: '#include ', dependencies: libheif_dep) + cfg_var.set('HAVE_HEIF_ENCODING_OPTIONS_OUTPUT_NCLX_PROFILE', '1') + endif - # heif_init in 1.13 + # heif_init added in 1.13.0 if libheif_dep.version().version_compare('>=1.13.0') cfg_var.set('HAVE_HEIF_INIT', '1') endif @@ -527,6 +551,9 @@ if libjxl_found if libjxl_dep.version().version_compare('>=0.7') cfg_var.set('HAVE_LIBJXL_0_7', '1') endif + if libjxl_dep.version().version_compare('>=0.9') + cfg_var.set('HAVE_LIBJXL_0_9', '1') + endif endif libpoppler_dep = dependency('poppler-glib', version: '>=0.16.0', required: get_option('poppler')) diff --git a/suppressions/lsan.supp b/suppressions/lsan.supp index b5e83a9f8f..8ffe8a9ff8 100644 --- a/suppressions/lsan.supp +++ b/suppressions/lsan.supp @@ -5,4 +5,3 @@ leak:libIlmImf-2_5.so leak:libIlmThread-2_5.so leak:libMagickCore-6.Q16.so leak:libx265.so -leak:libheif.so diff --git a/test/test-suite/helpers/helpers.py b/test/test-suite/helpers/helpers.py index 7d8e9ce1c2..454126c055 100644 --- a/test/test-suite/helpers/helpers.py +++ b/test/test-suite/helpers/helpers.py @@ -33,6 +33,7 @@ SVGZ_FILE = os.path.join(IMAGES, "logo.svgz") SVG_GZ_FILE = os.path.join(IMAGES, "logo.svg.gz") GIF_ANIM_FILE = os.path.join(IMAGES, "cogs.gif") +GIF_ANIM_FILE_INVALID = os.path.join(IMAGES, "invalid_multiframe.gif") GIF_ANIM_EXPECTED_PNG_FILE = os.path.join(IMAGES, "cogs.png") GIF_ANIM_DISPOSE_BACKGROUND_FILE = os.path.join(IMAGES, "dispose-background.gif") GIF_ANIM_DISPOSE_BACKGROUND_EXPECTED_PNG_FILE = os.path.join(IMAGES, "dispose-background.png") @@ -46,7 +47,7 @@ TGA_FILE = os.path.join(IMAGES, "targa.tga") SGI_FILE = os.path.join(IMAGES, "silicongraphics.sgi") AVIF_FILE = os.path.join(IMAGES, "avif-orientation-6.avif") -AVIF_FILE_HUGE = os.path.join(IMAGES, "16x17000.avif") +AVIF_FILE_HUGE = os.path.join(IMAGES, "17000x17000.avif") HEIC_FILE = os.path.join(IMAGES, "heic-orientation-6.heic") RGBA_FILE = os.path.join(IMAGES, "rgba.png") RGBA_CORRECT_FILE = os.path.join(IMAGES, "rgba-correct.ppm") diff --git a/test/test-suite/images/16x17000.avif b/test/test-suite/images/16x17000.avif deleted file mode 100644 index d584d442eb..0000000000 Binary files a/test/test-suite/images/16x17000.avif and /dev/null differ diff --git a/test/test-suite/images/17000x17000.avif b/test/test-suite/images/17000x17000.avif new file mode 100644 index 0000000000..f5e6fdf183 Binary files /dev/null and b/test/test-suite/images/17000x17000.avif differ diff --git a/test/test-suite/images/big-height.webp b/test/test-suite/images/big-height.webp index 752a169641..2e0700c9d5 100644 Binary files a/test/test-suite/images/big-height.webp and b/test/test-suite/images/big-height.webp differ diff --git a/test/test-suite/images/invalid_multiframe.gif b/test/test-suite/images/invalid_multiframe.gif new file mode 100644 index 0000000000..057440f5ac Binary files /dev/null and b/test/test-suite/images/invalid_multiframe.gif differ diff --git a/test/test-suite/test_foreign.py b/test/test-suite/test_foreign.py index 4f5a32dab7..5b8d947b88 100644 --- a/test/test-suite/test_foreign.py +++ b/test/test-suite/test_foreign.py @@ -15,6 +15,7 @@ GIF_ANIM_EXPECTED_PNG_FILE, GIF_ANIM_DISPOSE_BACKGROUND_FILE, \ GIF_ANIM_DISPOSE_BACKGROUND_EXPECTED_PNG_FILE, \ GIF_ANIM_DISPOSE_PREVIOUS_FILE, \ + GIF_ANIM_FILE_INVALID, \ GIF_ANIM_DISPOSE_PREVIOUS_EXPECTED_PNG_FILE, \ temp_filename, assert_almost_equal_objects, have, skip_if_no, \ TIF1_FILE, TIF2_FILE, TIF4_FILE, WEBP_LOOKS_LIKE_SVG_FILE, \ @@ -847,7 +848,7 @@ def webp_valid(im): im = pyvips.Image.new_from_file(WEBP_FILE) buf = im.webpsave_buffer(lossless=True) im2 = pyvips.Image.new_from_buffer(buf, "") - assert abs(im.avg() - im2.avg()) < 1 + assert (im - im2).abs().max() == 0 # higher Q should mean a bigger buffer b1 = im.webpsave_buffer(Q=10) @@ -864,7 +865,7 @@ def webp_valid(im): p2 = im.get("icc-profile-data") assert p1 == p2 - # add tests for exif, xmp, ipct + # add tests for exif, xmp, iptc # the exif test will need us to be able to walk the header, # we can't just check exif-data @@ -902,7 +903,7 @@ def webp_valid(im): # Animated WebP roundtrip x = pyvips.Image.new_from_file(WEBP_ANIMATED_FILE, n=-1) assert x.width == 13 - assert x.height == 16393 + assert x.height == 16731 buf = x.webpsave_buffer() @skip_if_no("analyzeload") @@ -1049,6 +1050,10 @@ def gif_valid(im): x2 = pyvips.Image.new_from_file(GIF_ANIM_FILE, page=1, n=-1) assert x2.height == 4 * x1.height + with pytest.raises(pyvips.error.Error): + x1 = pyvips.Image.new_from_file(GIF_ANIM_FILE_INVALID, n=-1) + x1.avg() + @skip_if_no("gifload") def test_gifload_animation_dispose_background(self): x1 = pyvips.Image.new_from_file(GIF_ANIM_DISPOSE_BACKGROUND_FILE, n=-1) @@ -1135,6 +1140,18 @@ def svg_valid(im): assert im.width == 1 assert im.height == 1 + # scale up + svg = b'' + im = pyvips.Image.new_from_buffer(svg, "", scale=10000) + assert im.width == 10000 + assert im.height == 10000 + + # scale down + svg = b'' + im = pyvips.Image.new_from_buffer(svg, "", scale=0.0001) + assert im.width == 10 + assert im.height == 10 + def test_csv(self): self.save_load("%s.csv", self.mono) @@ -1160,6 +1177,11 @@ def test_ppm(self): self.save_load_file("%s.ppm", "[ascii]", grey16, 0) self.save_load_file("%s.ppm", "[ascii]", rgb16, 0) + source = pyvips.Source.new_from_memory(b'P1\n#\n#\n1 1\n0\n') + im = pyvips.Image.ppmload_source(source) + assert im.height == 1 + assert im.width == 1 + @skip_if_no("radload") def test_rad(self): self.save_load("%s.hdr", self.colour) @@ -1323,7 +1345,7 @@ def test_dzsave(self): # test keep=pyvips.ForeignKeep.ICC ... icc profiles should be # passed down filename = temp_filename(self.tempdir, '') - self.colour.dzsave(filename, keep=1 << 3) # pyvips.ForeignKeep.ICC - https://github.com/libvips/pyvips/pull/429 + self.colour.dzsave(filename, keep=1 << 3) # pyvips.ForeignKeep.ICC y = pyvips.Image.new_from_file(filename + "_files/0/0_0.jpeg") assert y.get_typeof("icc-profile-data") != 0 @@ -1350,27 +1372,21 @@ def heif_valid(im): assert im.avg() == 0.0 @skip_if_no("heifsave") - @pytest.mark.skipif(sys.platform == "darwin", reason="fails with latest libheif/aom from Homebrew") def test_avifsave(self): - # TODO: Reduce the threshold once https://github.com/strukturag/libheif/issues/533 is resolved. self.save_load_buffer("heifsave_buffer", "heifload_buffer", - self.colour, 80, compression="av1", - lossless=True) + self.colour, compression="av1", lossless=True) self.save_load("%s.avif", self.colour) @skip_if_no("heifsave") - @pytest.mark.skipif(sys.platform == "darwin", reason="fails with latest libheif/aom from Homebrew") - @pytest.mark.skip() def test_avifsave_lossless(self): - # this takes FOREVER im = pyvips.Image.new_from_file(AVIF_FILE) - buf = im.heifsave_buffer(lossless=True, compression="av1") + buf = im.heifsave_buffer(effort=0, lossless=True, compression="av1") im2 = pyvips.Image.new_from_buffer(buf, "") - # not in fact quite lossless - assert abs(im.avg() - im2.avg()) < 3 + # requires libheif >= 1.13.0 for true lossless: + # see: https://github.com/strukturag/libheif/commit/b2612dd9c63f8835cf2047960b8cacd464a325a4 + assert (im - im2).abs().max() <= 1.0 @skip_if_no("heifsave") - @pytest.mark.skipif(sys.platform == "darwin", reason="fails with latest libheif/aom from Homebrew") def test_avifsave_Q(self): # higher Q should mean a bigger buffer, needs libheif >= v1.8.0, # see: https://github.com/libvips/libvips/issues/1757 @@ -1379,7 +1395,6 @@ def test_avifsave_Q(self): assert len(b2) > len(b1) @skip_if_no("heifsave") - @pytest.mark.skipif(sys.platform == "darwin", reason="fails with latest libheif/aom from Homebrew") def test_avifsave_chroma(self): # Chroma subsampling should produce smaller file size for same Q b1 = self.colour.heifsave_buffer(compression="av1", subsample_mode="on") @@ -1387,7 +1402,6 @@ def test_avifsave_chroma(self): assert len(b2) > len(b1) @skip_if_no("heifsave") - @pytest.mark.skipif(sys.platform == "darwin", reason="fails with latest libheif/aom from Homebrew") def test_avifsave_icc(self): # try saving an image with an ICC profile and reading it back # not all libheif have profile support, so put it in an if @@ -1398,12 +1412,11 @@ def test_avifsave_icc(self): p2 = im.get("icc-profile-data") assert p1 == p2 - # add tests for xmp, ipct + # add tests for xmp, iptc # the exif test will need us to be able to walk the header, # we can't just check exif-data @skip_if_no("heifsave") - @pytest.mark.skipif(sys.platform == "darwin", reason="fails with latest libheif/aom from Homebrew") def test_avifsave_exif(self): # first make sure we have exif support x = pyvips.Image.new_from_file(JPEG_FILE) @@ -1415,7 +1428,6 @@ def test_avifsave_exif(self): assert y.get("exif-ifd0-XPComment").startswith("banana") @skip_if_no("heifsave") - @pytest.mark.skipif(sys.platform == "darwin", reason="fails with latest libheif/aom from Homebrew") def test_heicsave_16_to_12(self): rgb16 = self.colour.colourspace("rgb16") data = rgb16.heifsave_buffer(lossless=True) @@ -1429,7 +1441,6 @@ def test_heicsave_16_to_12(self): assert((im - rgb16).abs().max() < 4500) @skip_if_no("heifsave") - @pytest.mark.skipif(sys.platform == "darwin", reason="fails with latest libheif/aom from Homebrew") def test_heicsave_16_to_8(self): rgb16 = self.colour.colourspace("rgb16") data = rgb16.heifsave_buffer(lossless=True, bitdepth=8) @@ -1443,7 +1454,6 @@ def test_heicsave_16_to_8(self): assert((im - rgb16 / 256).abs().max() < 80) @skip_if_no("heifsave") - @pytest.mark.skipif(sys.platform == "darwin", reason="fails with latest libheif/aom from Homebrew") def test_heicsave_8_to_16(self): data = self.colour.heifsave_buffer(lossless=True, bitdepth=12) im = pyvips.Image.heifload_buffer(data) @@ -1533,7 +1543,7 @@ def test_jxlsave(self): # remove the ICC profile: the RGB one will no longer be appropriate rgb16.remove("icc-profile-data") self.save_load_buffer("jxlsave_buffer", "jxlload_buffer", - rgb16, 10700) + rgb16, 12000) # repeat for lossless mode self.save_load_buffer("jxlsave_buffer", "jxlload_buffer", diff --git a/test/test-suite/test_resample.py b/test/test-suite/test_resample.py index 9d16eea3de..f7be77d07b 100644 --- a/test/test-suite/test_resample.py +++ b/test/test-suite/test_resample.py @@ -200,7 +200,7 @@ def test_thumbnail(self): im2 = pyvips.Image.thumbnail(OME_FILE + "[page=1]", 100) assert im2.width == 100 assert im2.height == 38 - assert (im1 - im2).abs().max() != 0 + assert (im1 - im2).abs().max() != 0 # should be able to thumbnail entire many-page tiff as a toilet-roll # image @@ -220,12 +220,20 @@ def test_thumbnail(self): im2 = pyvips.Image.new_from_file(RGBA_CORRECT_FILE) assert abs(im1.flatten(background=255).avg() - im2.avg()) < 1 + # thumbnailing a 16-bit image should always make an 8-bit image + rgb16_buffer = pyvips.Image \ + .new_from_file(JPEG_FILE) \ + .colourspace("rgb16") \ + .write_to_buffer(".png") + thumb = pyvips.Image.thumbnail_buffer(rgb16_buffer, 128) + assert thumb.format == "uchar" + if have("heifload"): # this image is orientation 6 ... thumbnail should flip it im = pyvips.Image.new_from_file(AVIF_FILE) thumb = pyvips.Image.thumbnail(AVIF_FILE, 100) - # thumb should be portrait + # thumb should be portrait assert thumb.width < thumb.height assert thumb.height == 100 diff --git a/tools/vipsheader.c b/tools/vipsheader.c index adaca1428b..14b8cc8b62 100644 --- a/tools/vipsheader.c +++ b/tools/vipsheader.c @@ -98,14 +98,6 @@ static GOptionEntry main_option[] = { { NULL } }; -/* A non-fatal error. Print the vips error buffer and continue. - */ -static void -print_error(void) -{ - fprintf(stderr, "%s: %s", g_get_prgname(), vips_error_buffer()); - vips_error_clear(); -} static void * print_field_fn(VipsImage *image, const char *field, GValue *value, void *a) @@ -143,10 +135,9 @@ print_header(VipsImage *image, gboolean many) else if (strcmp(main_option_field, "getext") == 0) { if (vips__has_extension_block(image)) { void *buf; - int size; + size_t size; - if (!(buf = - vips__read_extension_block(image, &size))) + if (!(buf = vips__read_extension_block(image, &size))) return -1; printf("%s", (char *) buf); g_free(buf); @@ -227,7 +218,7 @@ main(int argc, char *argv[]) result = 0; for (i = 1; argv[i]; i++) { - VipsImage *image; + VipsImage *image = NULL; char filename[VIPS_PATH_MAX]; char option_string[VIPS_PATH_MAX]; @@ -235,31 +226,28 @@ main(int argc, char *argv[]) if (strcmp(filename, "stdin") == 0) { VipsSource *source; - if (!(source = vips_source_new_from_descriptor(0))) - return -1; - if (!(image = vips_image_new_from_source(source, - option_string, NULL))) { - VIPS_UNREF(source); - return -1; - } + if (!(source = vips_source_new_from_descriptor(0)) || + !(image = vips_image_new_from_source(source, + option_string, NULL))) + result = 1; + VIPS_UNREF(source); } else { - if (!(image = - vips_image_new_from_file(argv[i], NULL))) { - print_error(); + if (!(image = vips_image_new_from_file(argv[i], NULL))) result = 1; - } } if (image && - print_header(image, argv[2] != NULL)) { - print_error(); + print_header(image, argv[2] != NULL)) result = 1; - } - if (image) - g_object_unref(image); + VIPS_UNREF(image); + } + + if (result) { + fprintf(stderr, "%s: %s", g_get_prgname(), vips_error_buffer()); + vips_error_clear(); } /* We don't free this on error exit, sadly.