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.