diff --git a/.gitattributes b/.gitattributes index 2be92952cd..ac9cdcf890 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,15 +1,18 @@ -# Shell scripts sources can't have CRLF line endings -*.sh eol=lf +# Shell scripts can't have CRLF line endings +*.sh eol=lf -# Ignore repository language statistics and hide diffs for generated files -libvips/colour/profiles.c linguist-generated=true +# Excluded from stats, hidden in diffs +libvips/colour/profiles.c linguist-generated + +# Excluded from stats +libvips/foreign/libnsgif/* linguist-vendored # Omit from release tarball -.clang-format export-ignore -.codespellrc export-ignore -.editorconfig export-ignore -.git-blame-ignore-revs export-ignore -.gitattributes export-ignore -.gitignore export-ignore -.gitkeep export-ignore -.github/ export-ignore +.clang-format export-ignore +.codespellrc export-ignore +.editorconfig export-ignore +.git-blame-ignore-revs export-ignore +.gitattributes export-ignore +.gitignore export-ignore +.gitkeep export-ignore +.github/ export-ignore diff --git a/CITATION.cff b/CITATION.cff index 8871e27d43..4a05b19b99 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -22,4 +22,5 @@ preferred-citation: year: 2025 publisher: name: Society for Imaging Science and Technology - url: "https://www.southampton.ac.uk/~km2/papers/2025/vips-ist-preprint.pdf" + doi: "10.2352/EI.2025.37.12.HPCI-178" + url: "https://doi.org/10.2352/EI.2025.37.12.HPCI-178" diff --git a/ChangeLog b/ChangeLog index edf43dc5fd..993764a53c 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,30 @@ master -8.17.0 +- add dcrawload, dcrawload_source, dcrawload_buffer: load raw camera files + using libraw [lxsameer] +- add magickload_source: load from a source with imagemagick +- add vips__worker_exit(): enables fast threadpool shutdown +- larger mmap windows on 64-bit machines improve random access mode for many + file formats +- pdfload: control region to be rendered via `page_box` [lovell] + +8.17.2 + +- rank: fix an off-by-one error [larsmaxfield] + +7/7/25 8.17.1 + +- fix API docs build with meson < 0.60 [lovell] +- improve function checks in meson [kleisauke] +- tiff: use correct log domain in threadsafe warning handlers [kleisauke] +- shift 16-bit output down to 8-bit when unsupported by saver [kleisauke] +- text: prevent use of rgba subpixel anti-aliasing [lovell] +- tiffsave: always apply resolution unit conversion [kleisauke] +- cache: suppress invalidation errors in release builds [kleisauke] +- dzsave: IIIF: use named region of 'full' when no crop takes place [lovell] +- pdfload: fix potential crash with pdfium < 6633 [zhifengzhuang] + +5/6/25 8.17.0 - gifsave: add `keep_duplicate_frames` flag [dloebl] - add Magic Kernel support [akimon658] @@ -38,7 +62,7 @@ master - remove vipsprofile from default install - add ppmload_buffer [kleisauke] -26/3/25 8.16.1 +12/3/25 8.16.1 - support multipage JXL - fix PFM byte order on big-endian machines [agoode] diff --git a/README.md b/README.md index ab9b594d94..5ce53f9f66 100644 --- a/README.md +++ b/README.md @@ -16,11 +16,11 @@ libvips is licensed under the [LGPL-2.1-or-later]( https://spdx.org/licenses/LGPL-2.1-or-later). It has around [300 -operations](https://libvips.github.io/libvips/API/current/func-list.html) +operations](https://www.libvips.org/API/current/function-list.html) covering arithmetic, histograms, convolution, morphological operations, frequency filtering, colour, resampling, statistics and others. It supports a large range of [numeric -types](https://libvips.github.io/libvips/API/current/VipsImage.html#VipsBandFormat), +types](https://www.libvips.org/API/current/enum.BandFormat.html), from 8-bit int to 128-bit complex. Images can have any number of bands. It supports a good range of image formats, including JPEG, JPEG 2000, JPEG XL, TIFF, PNG, WebP, HEIC, AVIF, FITS, Matlab, OpenEXR, PDF, SVG, HDR, PPM / PGM / @@ -29,10 +29,10 @@ images via ImageMagick or GraphicsMagick, letting it work with formats like DICOM. It comes with bindings for -[C](https://libvips.github.io/libvips/API/current/using-from-c.html), -[C++](https://libvips.github.io/libvips/API/current/libvips-from-C++.html), +[C](https://www.libvips.org/API/current/using-from-c.html), +[C++](https://www.libvips.org/API/current/using-from-cplusplus.html), and the -[command-line](https://libvips.github.io/libvips/API/current/using-cli.html). +[command-line](https://www.libvips.org/API/current/function-list.html). Full bindings are available for : | Language | Binding | @@ -73,8 +73,8 @@ macOS. Check your package manager. There are binaries for Windows in [releases](https://github.com/libvips/libvips/releases). -The [libvips website](https://libvips.github.io/libvips) has [detailed -install notes](https://libvips.github.io/libvips/install.html). +The [libvips website](https://www.libvips.org) has [detailed +install notes](https://www.libvips.org/install.html). # Building from source diff --git a/cplusplus/README.md b/cplusplus/README.md index 07d3235ebf..27fa670c9d 100644 --- a/cplusplus/README.md +++ b/cplusplus/README.md @@ -144,7 +144,7 @@ on error. The other writers from the C API are also present: you can write to a memory array, to a formatted image in memory, or to another image. The API docs have a [handy table of all vips -operations](https://libvips.github.io/libvips/API/current/func-list.html), if you want to find out +operations](https://www.libvips.org/API/current/function-list.html), if you want to find out how to do something, try searching that. ### Automatic constant expansion diff --git a/cplusplus/include/vips/VImage8.h b/cplusplus/include/vips/VImage8.h index 58da46420d..745070a433 100644 --- a/cplusplus/include/vips/VImage8.h +++ b/cplusplus/include/vips/VImage8.h @@ -4860,6 +4860,7 @@ class VImage : public VObject { * - **scale** -- Factor to scale by, double. * - **background** -- Background colour, std::vector. * - **password** -- Password to decrypt with, const char *. + * - **page_box** -- The region of the page to render, VipsForeignPdfPageBox. * - **memory** -- Force open via memory, bool. * - **access** -- Required access pattern for this file, VipsAccess. * - **fail_on** -- Error level to fail on, VipsFailOn. @@ -4881,6 +4882,7 @@ class VImage : public VObject { * - **scale** -- Factor to scale by, double. * - **background** -- Background colour, std::vector. * - **password** -- Password to decrypt with, const char *. + * - **page_box** -- The region of the page to render, VipsForeignPdfPageBox. * - **memory** -- Force open via memory, bool. * - **access** -- Required access pattern for this file, VipsAccess. * - **fail_on** -- Error level to fail on, VipsFailOn. @@ -4902,6 +4904,7 @@ class VImage : public VObject { * - **scale** -- Factor to scale by, double. * - **background** -- Background colour, std::vector. * - **password** -- Password to decrypt with, const char *. + * - **page_box** -- The region of the page to render, VipsForeignPdfPageBox. * - **memory** -- Force open via memory, bool. * - **access** -- Required access pattern for this file, VipsAccess. * - **fail_on** -- Error level to fail on, VipsFailOn. diff --git a/doc/meson.build b/doc/meson.build index a14106b33a..c95e9ae8a6 100644 --- a/doc/meson.build +++ b/doc/meson.build @@ -59,7 +59,7 @@ vips_toml = configure_file( fixup_gir_for_doc = find_program('fixup-gir-for-doc.py') -vips_gir_doc = custom_target( +vips_gir_doc = custom_target('vips-gir-docs', input: vips_gir[0], output: 'Vips-@0@.0-doc.gir'.format(version_major), command: [fixup_gir_for_doc, '@INPUT@', '@OUTPUT@'], diff --git a/fuzz/oss_fuzz_build.sh b/fuzz/oss_fuzz_build.sh index 31404267d6..68d5980791 100755 --- a/fuzz/oss_fuzz_build.sh +++ b/fuzz/oss_fuzz_build.sh @@ -94,10 +94,10 @@ cmake \ -DENABLE_STATIC=TRUE \ -DENABLE_SHARED=FALSE \ -DWITH_TURBOJPEG=FALSE \ + -DWITH_TOOLS=FALSE \ + -DWITH_TESTS=FALSE \ . -cmake --build . --target jpeg-static -cmake --install . --component lib -cmake --install . --component include +cmake --build . --target install popd # libspng @@ -188,15 +188,13 @@ cmake \ cmake --build . --target install popd -# FIXME: Workaround for https://github.com/mesonbuild/meson/issues/14640 -export LDFLAGS+=" -Wl,-rpath=\$ORIGIN/lib" - # libvips # Disable building man pages, gettext po files, tools, and tests sed -i "/subdir('man')/{N;N;N;d;}" meson.build meson setup build --prefix=$WORK --libdir=lib --prefer-static --default-library=static --buildtype=debugoptimized \ -Dbackend_max_links=4 -Ddeprecated=false -Dexamples=false -Dcplusplus=false -Dmodules=disabled \ - -Dfuzzing_engine=oss-fuzz -Dfuzzer_ldflags="$LIB_FUZZING_ENGINE" + -Dfuzzing_engine=oss-fuzz -Dfuzzer_ldflags="$LIB_FUZZING_ENGINE" \ + -Dcpp_link_args="$LDFLAGS -Wl,-rpath=\$ORIGIN/lib" meson install -C build --tag devel # Copy fuzz executables to $OUT diff --git a/libvips/conversion/subsample.c b/libvips/conversion/subsample.c index c5a6b06caf..6c6969f8f7 100644 --- a/libvips/conversion/subsample.c +++ b/libvips/conversion/subsample.c @@ -53,6 +53,7 @@ #include #include +#include #include "pconversion.h" @@ -102,7 +103,10 @@ vips_subsample_line_gen(VipsRegion *out_region, VipsPel *q = VIPS_REGION_ADDR(out_region, le, y); VipsPel *p; - /* Loop across the region, in owidth sized pieces. + if (vips__worker_exit()) + return 0; + + /* Loop across the region, in owidth-sized pieces. */ for (x = le; x < ri; x += owidth) { /* How many pixels do we make this time? @@ -165,11 +169,10 @@ vips_subsample_point_gen(VipsRegion *out_region, VipsPel *q = VIPS_REGION_ADDR(out_region, le, y); VipsPel *p; - /* Loop across the region, in owidth sized pieces. - */ + if (vips__worker_exit()) + return 0; + for (x = le; x < ri; x++) { - /* Ask for input. - */ s.left = x * subsample->xfac; s.top = y * subsample->yfac; s.width = 1; @@ -177,8 +180,6 @@ vips_subsample_point_gen(VipsRegion *out_region, if (vips_region_prepare(ir, &s)) return -1; - /* Append new pels to output. - */ p = VIPS_REGION_ADDR(ir, s.left, s.top); for (k = 0; k < ps; k++) q[k] = p[k]; diff --git a/libvips/create/text.c b/libvips/create/text.c index 8d28194af7..a853a80ca9 100644 --- a/libvips/create/text.c +++ b/libvips/create/text.c @@ -416,6 +416,15 @@ vips_text_build(VipsObject *object) text->context = pango_font_map_create_context(vips_text_fontmap); + if (text->rgba) { + /* Prevent use of subpixel anti-aliasing to avoid artefacts. + */ + cairo_font_options_t *opts = cairo_font_options_create(); + cairo_font_options_set_antialias(opts, CAIRO_ANTIALIAS_GRAY); + pango_cairo_context_set_font_options(text->context, opts); + cairo_font_options_destroy(opts); + } + /* Because we set resolution on vips_text_fontmap and that's shared * between all vips_text instances, we must lock all the way to the * end of text rendering. diff --git a/libvips/foreign/dcrawload.c b/libvips/foreign/dcrawload.c new file mode 100644 index 0000000000..a3e86e148e --- /dev/null +++ b/libvips/foreign/dcrawload.c @@ -0,0 +1,694 @@ +/* load RAW camera files with libraw + */ + +/* + + This file is part of VIPS. + + VIPS is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + + */ + +/* + + These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk + + */ + +/* +#define DEBUG + */ + +#ifdef HAVE_CONFIG_H +#include +#endif /*HAVE_CONFIG_H*/ +#include + +#include +#include + +#include + +#ifdef HAVE_LIBRAW + +#include + +#define VIPS_TYPE_FOREIGN_LOAD_DCRAW (vips_foreign_load_dcraw_get_type()) +#define VIPS_FOREIGN_LOAD_DCRAW(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), \ + VIPS_TYPE_FOREIGN_LOAD_DCRAW, VipsForeignLoadDcRaw)) + +static const char *vips_foreign_dcraw_suffs[] = { + ".3fr", ".ari", ".arw", ".cap", ".cin", ".cr2", ".cr3", + ".crw", ".dcr", ".dng", ".erf", ".fff", ".iiq", ".k25", + ".kdc", ".mdc", ".mos", ".mrw", ".nef", ".nrw", ".orf", + ".ori", ".pef", ".pxn", ".raf", ".raw", ".rw2", ".rwl", + ".sr2", ".srf", ".srw", ".x3f", NULL +}; + +typedef struct _VipsForeignLoadDcRaw { + VipsForeignLoad parent_object; + + int bitdepth; + + /* LibRaw processor. + */ + libraw_data_t *raw_processor; + + /* Internal Libraw processed image + */ + libraw_processed_image_t *processed; + + /* Load from this source (set by subclasses). + */ + VipsSource *source; + +} VipsForeignLoadDcRaw; + +typedef VipsForeignLoadClass VipsForeignLoadDcRawClass; + +G_DEFINE_ABSTRACT_TYPE(VipsForeignLoadDcRaw, vips_foreign_load_dcraw, + VIPS_TYPE_FOREIGN_LOAD); + +static void +vips_foreign_load_dcraw_dispose(GObject *gobject) +{ + VipsForeignLoadDcRaw *raw = (VipsForeignLoadDcRaw *) gobject; + + VIPS_FREEF(libraw_dcraw_clear_mem, raw->processed); + VIPS_FREEF(libraw_close, raw->raw_processor); + VIPS_UNREF(raw->source); + + G_OBJECT_CLASS(vips_foreign_load_dcraw_parent_class)->dispose(gobject); +} + +static VipsForeignFlags +vips_foreign_load_dcraw_get_flags(VipsForeignLoad *load) +{ + return 0; +} + +static void +vips_foreign_load_dcraw_error(VipsForeignLoadDcRaw *raw, + const char *message, int code) +{ + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS(raw); + + vips_error(class->nickname, "%s: %s", message, libraw_strerror(code)); +} + +static void +vips_foreign_load_dcraw_close(VipsImage *image, + libraw_processed_image_t *processed) +{ + VIPS_FREEF(libraw_dcraw_clear_mem, processed); +} + +static int +vips_foreign_load_dcraw_set_metadata(VipsForeignLoadDcRaw *raw, + VipsImage *image) +{ + VIPS_SETSTR(image->filename, + vips_connection_filename(VIPS_CONNECTION(raw->source))); + + /* Set custom metadata. + */ + vips_image_set_string(image, "raw-make", + raw->raw_processor->idata.make); + vips_image_set_string(image, "raw-model", + raw->raw_processor->idata.model); + vips_image_set_string(image, "raw-software", + raw->raw_processor->idata.software); + vips_image_set_double(image, "raw-iso", + raw->raw_processor->other.iso_speed); + vips_image_set_double(image, "raw-shutter", + raw->raw_processor->other.shutter); + vips_image_set_double(image, "raw-aperture", + raw->raw_processor->other.aperture); + vips_image_set_double(image, "raw-focal-length", + raw->raw_processor->other.focal_len); + + GDateTime *dt = + g_date_time_new_from_unix_utc(raw->raw_processor->other.timestamp); + if (dt) { + char *str = g_date_time_format_iso8601(dt); + if (str) { + vips_image_set_string(image, "raw-timestamp", str); + + g_free(str); + } + + g_date_time_unref(dt); + } + + if (raw->raw_processor->idata.xmpdata) + vips_image_set_blob_copy(image, VIPS_META_XMP_NAME, + raw->raw_processor->idata.xmpdata, + raw->raw_processor->idata.xmplen); + + vips_image_set_string(image, "raw-lens", + raw->raw_processor->lens.Lens); + + if (raw->raw_processor->color.profile) + vips_image_set_blob_copy(image, VIPS_META_ICC_NAME, + raw->raw_processor->color.profile, + raw->raw_processor->color.profile_length); + + /* Search the available thumbnails for the largest that's smaller than + * the main image and has a known type. + */ + libraw_image_sizes_t *sizes = &raw->raw_processor->sizes; + libraw_thumbnail_list_t *thumbs_list = &raw->raw_processor->thumbs_list; + + int thumb_index; + + thumb_index = -1; + for (int i = 0; i < thumbs_list->thumbcount; i++) { + libraw_thumbnail_item_t *best = thumb_index == -1 ? + NULL : &thumbs_list->thumblist[thumb_index]; + libraw_thumbnail_item_t *this = &thumbs_list->thumblist[i]; + + // only support JPEG thumbnails for now + if (this->tformat != LIBRAW_INTERNAL_THUMBNAIL_JPEG) + continue; + + // useless thumbnails the same size as the main image are very + // common + if (this->twidth >= sizes->iwidth && + this->theight >= sizes->iheight) + continue; + + // must be 8-bit, must match the main image in bands + int bpp = this->tmisc & ((1 << 5) - 1); + int bands = this->tmisc >> 5; + if (bpp != 8 || + bands != raw->raw_processor->idata.colors) + continue; + + // size must be sane (under 1mb). + if (this->tlength > 1024 * 1024) + continue; + + if (!best || + this->twidth > best->twidth || + this->theight > best->theight) + thumb_index = i; + } + + if (thumb_index != -1) { + int result; + result = libraw_unpack_thumb_ex(raw->raw_processor, thumb_index); + if (result != LIBRAW_SUCCESS) { + vips_foreign_load_dcraw_error(raw, + _("unable to unpack thumbnail"), result); + return -1; + } + + vips_image_set_blob_copy(image, "raw-thumbnail-data", + raw->raw_processor->thumbnail.thumb, + raw->raw_processor->thumbnail.tlength); + } + + return 0; +} + +static int +vips_foreign_load_dcraw_header(VipsForeignLoad *load) +{ + VipsForeignLoadDcRaw *raw = (VipsForeignLoadDcRaw *) load; + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS(raw); + + int result; + + raw->raw_processor = libraw_init(0); + if (!raw->raw_processor) { + vips_error(class->nickname, "%s", _("unable to initialize libraw")); + return -1; + } + + if (raw->bitdepth != 8 && + raw->bitdepth != 16) { + vips_error(class->nickname, "%s", _("bad bitdepth")); + return -1; + } + raw->raw_processor->params.output_bps = raw->bitdepth; + + /* Apply camera white balance. + */ + raw->raw_processor->params.use_camera_wb = 1; + + /* We can use the libraw file interface for filename sources. This + * interface can often read more metadata, since it can open secondary + * files. + */ + if (vips_source_is_file(raw->source)) { + const char *filename = + vips_connection_filename(VIPS_CONNECTION(raw->source)); + + result = libraw_open_file(raw->raw_processor, filename); + } + else { + size_t length; + const void *data; + + if (!(data = vips_source_map(raw->source, &length))) + return -1; + result = libraw_open_buffer(raw->raw_processor, data, length); + } + if (result != LIBRAW_SUCCESS) { + vips_foreign_load_dcraw_error(raw, _("unable to read"), result); + return -1; + } + + vips_image_init_fields(load->out, + raw->raw_processor->sizes.iwidth, + raw->raw_processor->sizes.iheight, + raw->raw_processor->idata.colors, + raw->bitdepth > 8 ? + VIPS_FORMAT_USHORT : VIPS_FORMAT_UCHAR, + VIPS_CODING_NONE, + VIPS_INTERPRETATION_ERROR, + 1.0, 1.0); + load->out->Type = vips_image_guess_interpretation(load->out); + + if (vips_foreign_load_dcraw_set_metadata(raw, load->out)) + return -1; + + return 0; +} + +static int +vips_foreign_load_dcraw_load(VipsForeignLoad *load) +{ + VipsForeignLoadDcRaw *raw = (VipsForeignLoadDcRaw *) load; + + int result; + + g_assert(raw->raw_processor); + + result = libraw_unpack(raw->raw_processor); + if (result != LIBRAW_SUCCESS) { + vips_foreign_load_dcraw_error(raw, _("unable to unpack"), result); + return -1; + } + + /* Process the image (demosaicing, white balance, etc.). + */ + result = libraw_dcraw_process(raw->raw_processor); + if (result != LIBRAW_SUCCESS) { + vips_foreign_load_dcraw_error(raw, _("unable to process"), result); + return -1; + } + + if (!(raw->processed = + libraw_dcraw_make_mem_image(raw->raw_processor, &result))) { + vips_foreign_load_dcraw_error(raw, _("unable to build image"), result); + return -1; + } + + VipsImage *image; + if (!(image = vips_image_new_from_memory( + raw->processed->data, raw->processed->data_size, + raw->processed->width, raw->processed->height, + raw->processed->colors, + raw->bitdepth > 8 ? + VIPS_FORMAT_USHORT : VIPS_FORMAT_UCHAR))) + return -1; + image->Type = vips_image_guess_interpretation(image); + + if (vips_foreign_load_dcraw_set_metadata(raw, image)) { + VIPS_UNREF(image); + return -1; + } + + /* We must only free the memory when this image closes. + */ + g_signal_connect(image, "close", + G_CALLBACK(vips_foreign_load_dcraw_close), raw->processed); + raw->processed = NULL; + + if (vips_image_write(image, load->real)) { + VIPS_UNREF(image); + return -1; + } + + VIPS_UNREF(image); + + return 0; +} + +static void +vips_foreign_load_dcraw_class_init(VipsForeignLoadDcRawClass *class) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS(class); + VipsObjectClass *object_class = (VipsObjectClass *) class; + VipsOperationClass *operation_class = VIPS_OPERATION_CLASS(class); + VipsForeignClass *foreign_class = (VipsForeignClass *) class; + VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class; + + gobject_class->dispose = vips_foreign_load_dcraw_dispose; + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + object_class->nickname = "dcrawload_base"; + object_class->description = _("load RAW camera files"); + + operation_class->flags |= VIPS_OPERATION_UNTRUSTED; + + /* We need to be ahead of JPEG and TIFF, since many cameras use those + * formats as containers. We are very slow to open, but we only test the + * filename suffix, so that's fine. + */ + foreign_class->priority = 100; + + load_class->get_flags = vips_foreign_load_dcraw_get_flags; + load_class->header = vips_foreign_load_dcraw_header; + load_class->load = vips_foreign_load_dcraw_load; + + VIPS_ARG_INT(class, "bitdepth", 12, + _("Bit depth"), + _("Number of bits per pixel"), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET(VipsForeignLoadDcRaw, bitdepth), + 8, 16, 8); + +} + +static void +vips_foreign_load_dcraw_init(VipsForeignLoadDcRaw *raw) +{ + raw->bitdepth = 8; +} + +typedef struct _VipsForeignLoadDcRawSource { + VipsForeignLoadDcRaw parent_object; + + /* Load from a source. + */ + VipsSource *source; + +} VipsForeignLoadDcRawSource; + +typedef VipsForeignLoadDcRawClass VipsForeignLoadDcRawSourceClass; + +G_DEFINE_TYPE(VipsForeignLoadDcRawSource, vips_foreign_load_dcraw_source, + vips_foreign_load_dcraw_get_type()); + +static int +vips_foreign_load_dcraw_source_build(VipsObject *object) +{ + VipsForeignLoadDcRaw *dcraw = (VipsForeignLoadDcRaw *) object; + VipsForeignLoadDcRawSource *source = (VipsForeignLoadDcRawSource *) object; + + if (source->source) { + dcraw->source = source->source; + g_object_ref(dcraw->source); + } + + return VIPS_OBJECT_CLASS(vips_foreign_load_dcraw_source_parent_class) + ->build(object); +} + +static gboolean +vips_foreign_load_dcrawload_source_is_a_source(VipsSource *source) +{ + return FALSE; +} + +static void +vips_foreign_load_dcraw_source_class_init( + VipsForeignLoadDcRawSourceClass *class) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS(class); + VipsObjectClass *object_class = (VipsObjectClass *) class; + VipsOperationClass *operation_class = VIPS_OPERATION_CLASS(class); + VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class; + + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + object_class->nickname = "dcrawload_source"; + object_class->build = vips_foreign_load_dcraw_source_build; + + operation_class->flags |= VIPS_OPERATION_NOCACHE; + + load_class->is_a_source = vips_foreign_load_dcrawload_source_is_a_source; + + VIPS_ARG_OBJECT(class, "source", 1, + _("Source"), + _("Source to load from"), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET(VipsForeignLoadDcRawSource, source), + VIPS_TYPE_SOURCE); +} + +static void +vips_foreign_load_dcraw_source_init(VipsForeignLoadDcRawSource *source) +{ +} + +typedef struct _VipsForeignLoadDcRawFile { + VipsForeignLoadDcRaw parent_object; + + /* Filename for load. + */ + char *filename; + +} VipsForeignLoadDcRawFile; + +typedef VipsForeignLoadDcRawClass VipsForeignLoadDcRawFileClass; + +G_DEFINE_TYPE(VipsForeignLoadDcRawFile, vips_foreign_load_dcraw_file, + vips_foreign_load_dcraw_get_type()); + +static int +vips_foreign_load_dcraw_file_build(VipsObject *object) +{ + VipsForeignLoadDcRaw *raw = VIPS_FOREIGN_LOAD_DCRAW(object); + VipsForeignLoadDcRawFile *file = (VipsForeignLoadDcRawFile *) object; + + if (file->filename && + !(raw->source = vips_source_new_from_file(file->filename))) + return -1; + + return VIPS_OBJECT_CLASS(vips_foreign_load_dcraw_file_parent_class) + ->build(object); +} + +static VipsForeignFlags +vips_foreign_load_dcraw_file_get_flags_filename(const char *filename) +{ + return 0; +} + +static void +vips_foreign_load_dcraw_file_class_init(VipsForeignLoadDcRawFileClass *class) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS(class); + VipsObjectClass *object_class = (VipsObjectClass *) class; + VipsForeignClass *foreign_class = (VipsForeignClass *) class; + VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class; + + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + object_class->nickname = "dcrawload"; + object_class->build = vips_foreign_load_dcraw_file_build; + + foreign_class->suffs = vips_foreign_dcraw_suffs; + + load_class->get_flags_filename = + vips_foreign_load_dcraw_file_get_flags_filename; + + VIPS_ARG_STRING(class, "filename", 1, + _("Filename"), + _("Filename to load from"), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET(VipsForeignLoadDcRawFile, filename), + NULL); +} + +static void +vips_foreign_load_dcraw_file_init(VipsForeignLoadDcRawFile *file) +{ +} + +typedef struct _VipsForeignLoadRawBuffer { + VipsForeignLoadDcRaw parent_object; + + /* Load from a buffer. + */ + VipsBlob *blob; + +} VipsForeignLoadDcRawBuffer; + +typedef VipsForeignLoadDcRawClass VipsForeignLoadDcRawBufferClass; + +G_DEFINE_TYPE(VipsForeignLoadDcRawBuffer, vips_foreign_load_dcraw_buffer, + vips_foreign_load_dcraw_get_type()); + +static int +vips_foreign_load_dcraw_buffer_build(VipsObject *object) +{ + VipsForeignLoadDcRaw *raw = (VipsForeignLoadDcRaw *) object; + VipsForeignLoadDcRawBuffer *buffer = (VipsForeignLoadDcRawBuffer *) object; + + if (buffer->blob && + !(raw->source = vips_source_new_from_memory( + VIPS_AREA(buffer->blob)->data, + VIPS_AREA(buffer->blob)->length))) + return -1; + + return VIPS_OBJECT_CLASS(vips_foreign_load_dcraw_buffer_parent_class) + ->build(object); +} + +static gboolean +vips_foreign_load_dcrawload_buffer_is_a_buffer(const void *buf, size_t len) +{ + return FALSE; +} + +static void +vips_foreign_load_dcraw_buffer_class_init( + VipsForeignLoadDcRawBufferClass *class) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS(class); + VipsObjectClass *object_class = (VipsObjectClass *) class; + VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class; + + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + object_class->nickname = "dcrawload_buffer"; + object_class->build = vips_foreign_load_dcraw_buffer_build; + + load_class->is_a_buffer = vips_foreign_load_dcrawload_buffer_is_a_buffer; + + VIPS_ARG_BOXED(class, "buffer", 1, + _("Buffer"), + _("Buffer to load from"), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET(VipsForeignLoadDcRawBuffer, blob), + VIPS_TYPE_BLOB); +} + +static void +vips_foreign_load_dcraw_buffer_init(VipsForeignLoadDcRawBuffer *buffer) +{ +} + +#endif /*HAVE_LIBRAW*/ + +/** + * vips_dcrawload: + * @filename: file to load + * @out: (out): output image + * @...: `NULL`-terminated list of optional named arguments + * + * Read a RAW camera file using LibRaw. + * + * This loader supports the most RAW formats, including + * ARW, CR2, CR3, CRW, DNG, NEF, NRW, ORF, PEF, RAF, RAW, RW2, SRW, X3F, and + * many others. + * + * The loader applies demosaicing and basic processing to produce an RGB or + * grayscale image suitable for further processing. It attaches XMP and ICC + * metadata, if present. + * + * ::: tip "Optional arguments" + * * @bitdepth: `gint`, load as 8 or 16 bit data + * + * Returns: 0 on success, -1 on error. + */ +int +vips_dcrawload(const char *filename, VipsImage **out, ...) +{ + va_list ap; + int result; + + va_start(ap, out); + result = vips_call_split("dcrawload", ap, filename, out); + va_end(ap); + + return result; +} + +/** + * vips_dcrawload_source: + * @source: source to load from + * @out: (out): image to write + * @...: `NULL`-terminated list of optional named arguments + * + * Exactly as [ctor@Image.dcrawload], but read from a source. + * + * ::: tip "Optional arguments" + * * @bitdepth: `gint`, load as 8 or 16 bit data + * + * ::: seealso + * [ctor@Image.dcrawload]. + * + * Returns: 0 on success, -1 on error. + */ +int +vips_dcrawload_source(VipsSource *source, VipsImage **out, ...) +{ + va_list ap; + int result; + + va_start(ap, out); + result = vips_call_split("dcrawload_source", ap, source, out); + va_end(ap); + + return result; +} + +/** + * vips_dcrawload_buffer: + * @buf: (array length=len) (element-type guint8): memory area to load + * @len: (type gsize): size of memory area + * @out: (out): output image + * @...: `NULL`-terminated list of optional named arguments + * + * Exactly as [ctor@Image.dcrawload], but read from a buffer. + * + * ::: tip "Optional arguments" + * * @bitdepth: `gint`, load as 8 or 16 bit data + * + * ::: seealso + * [ctor@Image.dcrawload]. + * + * Returns: 0 on success, -1 on error. + */ +int +vips_dcrawload_buffer(void *buf, size_t len, VipsImage **out, ...) +{ + va_list ap; + VipsBlob *blob; + int result; + + /* We don't take a copy of the data or free it. + */ + blob = vips_blob_new(NULL, buf, len); + + va_start(ap, out); + result = vips_call_split("dcrawload_buffer", ap, blob, out); + va_end(ap); + + vips_area_unref(VIPS_AREA(blob)); + + return result; +} diff --git a/libvips/foreign/dzsave.c b/libvips/foreign/dzsave.c index 6ca34c45f3..1fe0b2d84b 100644 --- a/libvips/foreign/dzsave.c +++ b/libvips/foreign/dzsave.c @@ -1212,6 +1212,9 @@ tile_name(Level *level, int x, int y) save->ready->Xsize - left); int height = VIPS_MIN(dz->tile_size * level->sub, save->ready->Ysize - top); + gboolean is_region_full = left == 0 && top == 0 && + width == save->ready->Xsize && + height == save->ready->Ysize; /* Rotation is always 0. */ @@ -1223,11 +1226,17 @@ tile_name(Level *level, int x, int y) int ysize = VIPS_MIN(dz->tile_size, level->height - y * dz->tile_size); - g_snprintf(subdir, VIPS_PATH_MAX, - "%d,%d,%d,%d" G_DIR_SEPARATOR_S "%d,%d" G_DIR_SEPARATOR_S "%d", - left, top, width, height, - xsize, ysize, - rotation); + if (is_region_full) + g_snprintf(subdir, VIPS_PATH_MAX, + "full" G_DIR_SEPARATOR_S "%d,%d" G_DIR_SEPARATOR_S "%d", + xsize, ysize, + rotation); + else + g_snprintf(subdir, VIPS_PATH_MAX, + "%d,%d,%d,%d" G_DIR_SEPARATOR_S "%d,%d" G_DIR_SEPARATOR_S "%d", + left, top, width, height, + xsize, ysize, + rotation); } else { /* IIIF2 "size" is just real tile width, I think. @@ -1235,11 +1244,17 @@ tile_name(Level *level, int x, int y) int size = VIPS_MIN(dz->tile_size, level->width - x * dz->tile_size); - g_snprintf(subdir, VIPS_PATH_MAX, - "%d,%d,%d,%d" G_DIR_SEPARATOR_S "%d," G_DIR_SEPARATOR_S "%d", - left, top, width, height, - size, - rotation); + if (is_region_full) + g_snprintf(subdir, VIPS_PATH_MAX, + "full" G_DIR_SEPARATOR_S "%d," G_DIR_SEPARATOR_S "%d", + size, + rotation); + else + g_snprintf(subdir, VIPS_PATH_MAX, + "%d,%d,%d,%d" G_DIR_SEPARATOR_S "%d," G_DIR_SEPARATOR_S "%d", + left, top, width, height, + size, + rotation); } g_snprintf(name, VIPS_PATH_MAX, "default%s", dz->file_suffix); diff --git a/libvips/foreign/foreign.c b/libvips/foreign/foreign.c index 84c987a14d..60a049f58c 100644 --- a/libvips/foreign/foreign.c +++ b/libvips/foreign/foreign.c @@ -357,19 +357,19 @@ static GQuark vips__foreign_load_operation = 0; * * Some hints about the image loader. * - * [flags@Vips.ForeignFlags.PARTIAL] means that the image can be read directly from the - * file without needing to be unpacked to a temporary image first. + * [flags@Vips.ForeignFlags.PARTIAL] means that the image can be read directly + * from the file without needing to be unpacked to a temporary image first. * - * [flags@Vips.ForeignFlags.SEQUENTIAL] means that the loader supports lazy reading, but - * only top-to-bottom (sequential) access. Formats like PNG can read sets of - * scanlines, for example, but only in order. + * [flags@Vips.ForeignFlags.SEQUENTIAL] means that the loader supports lazy + * reading, but only top-to-bottom (sequential) access. Formats like PNG can + * read sets of scanlines, for example, but only in order. * * If neither PARTIAL or SEQUENTIAL is set, the loader only supports whole * image read. Setting both PARTIAL and SEQUENTIAL is an error. * - * [flags@Vips.ForeignFlags.BIGENDIAN] means that image pixels are most-significant byte - * first. Depending on the native byte order of the host machine, you may - * need to swap bytes. See [method@Image.copy]. + * [flags@Vips.ForeignFlags.BIGENDIAN] means that image pixels are + * most-significant byte first. Depending on the native byte order of the + * host machine, you may need to swap bytes. See [method@Image.copy]. */ G_DEFINE_ABSTRACT_TYPE(VipsForeign, vips_foreign, VIPS_TYPE_OPERATION); @@ -1643,7 +1643,17 @@ vips__foreign_convert_saveable(VipsImage *in, VipsImage **ready, /* Convert to the format the saver likes. */ if (in->Coding == VIPS_CODING_NONE) { - if (vips_cast(in, &out, format[in->BandFmt], NULL)) { + /* If the saver does not support 16-bit output, automatically + * shift it down. This is the behaviour we want for saving an + * RGB16 image as JPEG, for example. + */ + gboolean needs_shift = + !vips_band_format_is8bit(in->BandFmt) && + vips_band_format_is8bit(format[in->BandFmt]); + + if (vips_cast(in, &out, format[in->BandFmt], + "shift", needs_shift, + NULL)) { g_object_unref(in); return -1; } @@ -2711,6 +2721,9 @@ vips_jxlsave_target(VipsImage *in, VipsTarget *target, ...) * * Use @password to supply a decryption password. * + * When using pdfium, the region of a page to render can be selected with + * @page_box, defaulting to the crop box. + * * The operation fills a number of header fields with metadata, for example * "pdf-author". They may be useful. * @@ -2723,6 +2736,7 @@ vips_jxlsave_target(VipsImage *in, VipsTarget *target, ...) * * @dpi: `gdouble`, render at this DPI * * @scale: `gdouble`, scale render by this factor * * @background: [struct@ArrayDouble], background colour + * * @page_box: [enum@ForeignPdfPageBox], use this page box (pdfium only) * * ::: seealso * [ctor@Image.new_from_file], [ctor@Image.magickload]. @@ -2761,6 +2775,7 @@ vips_pdfload(const char *filename, VipsImage **out, ...) * * @dpi: `gdouble`, render at this DPI * * @scale: `gdouble`, scale render by this factor * * @background: [struct@ArrayDouble], background colour + * * @page_box: [enum@ForeignPdfPageBox], use this page box (pdfium only) * * ::: seealso * [ctor@Image.pdfload]. @@ -2801,6 +2816,7 @@ vips_pdfload_buffer(void *buf, size_t len, VipsImage **out, ...) * * @dpi: `gdouble`, render at this DPI * * @scale: `gdouble`, scale render by this factor * * @background: [struct@ArrayDouble], background colour + * * @page_box: [enum@ForeignPdfPageBox], use this page box (pdfium only) * * ::: seealso * [ctor@Image.pdfload] @@ -2990,8 +3006,10 @@ vips_foreign_operation_init(void) extern GType vips_foreign_load_magick_file_get_type(void); extern GType vips_foreign_load_magick_buffer_get_type(void); + extern GType vips_foreign_load_magick_source_get_type(void); extern GType vips_foreign_load_magick7_file_get_type(void); extern GType vips_foreign_load_magick7_buffer_get_type(void); + extern GType vips_foreign_load_magick7_source_get_type(void); extern GType vips_foreign_save_magick_file_get_type(void); extern GType vips_foreign_save_magick_buffer_get_type(void); @@ -3054,6 +3072,10 @@ vips_foreign_operation_init(void) extern GType vips_foreign_save_cgif_buffer_get_type(void); extern GType vips_foreign_save_cgif_target_get_type(void); + extern GType vips_foreign_load_dcraw_file_get_type(void); + extern GType vips_foreign_load_dcraw_buffer_get_type(void); + extern GType vips_foreign_load_dcraw_source_get_type(void); + vips_foreign_load_csv_file_get_type(); vips_foreign_load_csv_source_get_type(); vips_foreign_save_csv_file_get_type(); @@ -3142,6 +3164,12 @@ vips_foreign_operation_init(void) vips_foreign_load_nsgif_source_get_type(); #endif /*HAVE_NSGIF*/ +#ifdef HAVE_LIBRAW + vips_foreign_load_dcraw_file_get_type(); + vips_foreign_load_dcraw_buffer_get_type(); + vips_foreign_load_dcraw_source_get_type(); +#endif /*HAVE_LIBRAW*/ + #ifdef HAVE_CGIF vips_foreign_save_cgif_file_get_type(); vips_foreign_save_cgif_buffer_get_type(); @@ -3214,11 +3242,13 @@ vips_foreign_operation_init(void) #ifdef HAVE_MAGICK6 vips_foreign_load_magick_file_get_type(); vips_foreign_load_magick_buffer_get_type(); + vips_foreign_load_magick_source_get_type(); #endif /*HAVE_MAGICK6*/ #ifdef HAVE_MAGICK7 vips_foreign_load_magick7_file_get_type(); vips_foreign_load_magick7_buffer_get_type(); + vips_foreign_load_magick7_source_get_type(); #endif /*HAVE_MAGICK7*/ #endif /*defined(ENABLE_MAGICKLOAD) && !defined(MAGICK_MODULE)*/ diff --git a/libvips/foreign/magick.c b/libvips/foreign/magick.c index 1cf7ef1ce9..876cab3f26 100644 --- a/libvips/foreign/magick.c +++ b/libvips/foreign/magick.c @@ -138,6 +138,15 @@ magick_sniff(const unsigned char *bytes, size_t length) (bytes[4] != 0 || bytes[5] != 0)) return "ICO"; + if (length >= 8 && + bytes[1] == 0 && + bytes[2] == 0 && + bytes[5] == 0 && + bytes[6] == 0 && + (bytes[4] == 7 || + bytes[7] == 7)) + return "XWD"; + if (length >= 18 && (bytes[1] == 0 || bytes[1] == 1) && diff --git a/libvips/foreign/magick6load.c b/libvips/foreign/magick6load.c index 265367ed39..b809fae301 100644 --- a/libvips/foreign/magick6load.c +++ b/libvips/foreign/magick6load.c @@ -1074,6 +1074,113 @@ vips_foreign_load_magick_buffer_init(VipsForeignLoadMagickBuffer *buffer) { } +typedef struct _VipsForeignLoadMagickSource { + VipsForeignLoadMagick parent_object; + + VipsSource *source; + +} VipsForeignLoadMagickSource; + +typedef VipsForeignLoadMagickClass VipsForeignLoadMagickSourceClass; + +G_DEFINE_TYPE(VipsForeignLoadMagickSource, vips_foreign_load_magick_source, + vips_foreign_load_magick_get_type()); + +static gboolean +vips_foreign_load_magick_source_is_a_source(VipsSource *source) +{ + const unsigned char *p; + + // just use the first 100 bytes, we don't want to force too much into + // memory + return (p = vips_source_sniff(source, 100)) && + magick_ismagick(p, 100); +} + +/* Unfortunately, libMagick does not support header-only reads very well. See + * + * http://www.imagemagick.org/discourse-server/viewtopic.php?f=1&t=20017 + * + * Test especially with BMP, GIF, TGA. So we are forced to read the entire + * image in the @header() method. + */ +static int +vips_foreign_load_magick_source_header(VipsForeignLoad *load) +{ + VipsForeignLoadMagick *magick = (VipsForeignLoadMagick *) load; + VipsForeignLoadMagickSource *magick_source = + (VipsForeignLoadMagickSource *) load; + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS(magick); + +#ifdef DEBUG + printf("vips_foreign_load_magick_source_header: %p\n", load); +#endif /*DEBUG*/ + + if (vips_source_is_file(magick_source->source)) { + const char *filename = + vips_connection_filename(VIPS_CONNECTION(magick_source->source)); + + g_strlcpy(magick->image_info->filename, filename, MagickPathExtent); + magick_sniff_file(magick->image_info, filename); + magick->image = ReadImage(magick->image_info, magick->exception); + } + else { + size_t length; + const void *data; + + if (!(data = vips_source_map(magick_source->source, &length))) + return -1; + magick_sniff_bytes(magick->image_info, data, length); + magick->image = BlobToImage(magick->image_info, + data, length, magick->exception); + } + + /* It would be great if we could PingImage and just read the header, + * but sadly many IM coders do not support ping. The critical one for + * us is DICOM. TGA also has issues. + */ + if (!magick->image) { + magick_vips_error(class->nickname, magick->exception); + vips_error(class->nickname, _("unable to read source")); + return -1; + } + + if (vips_foreign_load_magick_load(magick)) + return -1; + + return 0; +} + +static void +vips_foreign_load_magick_source_class_init( + VipsForeignLoadMagickSourceClass *class) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS(class); + VipsObjectClass *object_class = (VipsObjectClass *) class; + VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class; + + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + object_class->nickname = "magickload_source"; + object_class->description = _("load source with ImageMagick"); + + load_class->is_a_source = vips_foreign_load_magick_source_is_a_source; + load_class->header = vips_foreign_load_magick_source_header; + + VIPS_ARG_OBJECT(class, "source", 1, + _("Source"), + _("Source to load from"), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET(VipsForeignLoadMagickSource, source), + VIPS_TYPE_SOURCE); +} + +static void +vips_foreign_load_magick_source_init(VipsForeignLoadMagickSource *source) +{ +} + #endif /*HAVE_MAGICK6*/ #endif /*ENABLE_MAGICKLOAD*/ diff --git a/libvips/foreign/magick7load.c b/libvips/foreign/magick7load.c index 62defc0a57..7f6db8fb15 100644 --- a/libvips/foreign/magick7load.c +++ b/libvips/foreign/magick7load.c @@ -939,6 +939,112 @@ vips_foreign_load_magick7_buffer_init(VipsForeignLoadMagick7Buffer *buffer) { } +typedef struct _VipsForeignLoadMagick7Source { + VipsForeignLoadMagick7 parent_object; + + VipsSource *source; + +} VipsForeignLoadMagick7Source; + +typedef VipsForeignLoadMagick7Class VipsForeignLoadMagick7SourceClass; + +G_DEFINE_TYPE(VipsForeignLoadMagick7Source, vips_foreign_load_magick7_source, + vips_foreign_load_magick7_get_type()); + +static gboolean +vips_foreign_load_magick7_source_is_a_source(VipsSource *source) +{ + const unsigned char *p; + + // just use the first 100 bytes, we don't want to force too much into + // memory + return (p = vips_source_sniff(source, 100)) && + magick_ismagick(p, 100); +} + +/* Unfortunately, libMagick7 does not support header-only reads very well. See + * + * http://www.imagemagick7.org/discourse-server/viewtopic.php?f=1&t=20017 + * + * Test especially with BMP, GIF, TGA. So we are forced to read the entire + * image in the @header() method. + */ +static int +vips_foreign_load_magick7_source_header(VipsForeignLoad *load) +{ + VipsForeignLoadMagick7 *magick7 = (VipsForeignLoadMagick7 *) load; + VipsForeignLoadMagick7Source *magick7_source = + (VipsForeignLoadMagick7Source *) load; + +#ifdef DEBUG + printf("vips_foreign_load_magick7_source_header: %p\n", load); +#endif /*DEBUG*/ + + if (vips_source_is_file(magick7_source->source)) { + const char *filename = + vips_connection_filename(VIPS_CONNECTION(magick7_source->source)); + + g_strlcpy(magick7->image_info->filename, filename, MaxTextExtent); + magick_sniff_file(magick7->image_info, filename); + magick7->image = ReadImage(magick7->image_info, magick7->exception); + } + else { + size_t length; + const void *data; + + if (!(data = vips_source_map(magick7_source->source, &length))) + return -1; + + magick_sniff_bytes(magick7->image_info, data, length); + magick7->image = BlobToImage(magick7->image_info, data, length, + magick7->exception); + } + + /* It would be great if we could PingImage and just read the header, + * but sadly many IM coders do not support ping. The critical one for + * us is DICOM. TGA also has issues. + */ + if (!magick7->image) { + vips_foreign_load_magick7_error(magick7); + return -1; + } + + if (vips_foreign_load_magick7_load(magick7)) + return -1; + + return 0; +} + +static void +vips_foreign_load_magick7_source_class_init( + VipsForeignLoadMagick7SourceClass *class) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS(class); + VipsObjectClass *object_class = (VipsObjectClass *) class; + VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class; + + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + object_class->nickname = "magickload_source"; + object_class->description = _("load source with ImageMagick7"); + + load_class->is_a_source = vips_foreign_load_magick7_source_is_a_source; + load_class->header = vips_foreign_load_magick7_source_header; + + VIPS_ARG_OBJECT(class, "source", 1, + _("Source"), + _("Source to load from"), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET(VipsForeignLoadMagick7Source, source), + VIPS_TYPE_SOURCE); +} + +static void +vips_foreign_load_magick7_source_init(VipsForeignLoadMagick7Source *source) +{ +} + #endif /*HAVE_MAGICK7*/ #endif /*ENABLE_MAGICKLOAD*/ diff --git a/libvips/foreign/magickload.c b/libvips/foreign/magickload.c index f0a71b56c5..a2e71a9a2d 100644 --- a/libvips/foreign/magickload.c +++ b/libvips/foreign/magickload.c @@ -156,3 +156,35 @@ vips_magickload_buffer(void *buf, size_t len, VipsImage **out, ...) return result; } + +/** + * vips_magickload_source: + * @source: source to load + * @out: (out): image to write + * @...: `NULL`-terminated list of optional named arguments + * + * Exactly as [ctor@Image.magickload], but read from a source. + * + * ::: tip "Optional arguments" + * * @page: `gint`, load from this page + * * @n: `gint`, load this many pages + * * @density: `gchararray`, canvas resolution for rendering vector formats + * like SVG + * + * ::: seealso + * [ctor@Image.magickload]. + * + * Returns: 0 on success, -1 on error. + */ +int +vips_magickload_source(VipsSource *source, VipsImage **out, ...) +{ + va_list ap; + int result; + + va_start(ap, out); + result = vips_call_split("magickload_source", ap, source, out); + va_end(ap); + + return result; +} diff --git a/libvips/foreign/meson.build b/libvips/foreign/meson.build index 71d55c882d..9f05529eb4 100644 --- a/libvips/foreign/meson.build +++ b/libvips/foreign/meson.build @@ -10,6 +10,7 @@ foreign_sources = files( 'cgifsave.c', 'csvload.c', 'csvsave.c', + 'dcrawload.c', 'dzsave.c', 'exif.c', 'fits.c', diff --git a/libvips/foreign/openslideload.c b/libvips/foreign/openslideload.c index aa450232ab..e12ebe391a 100644 --- a/libvips/foreign/openslideload.c +++ b/libvips/foreign/openslideload.c @@ -1070,6 +1070,7 @@ static const char *vips_foreign_openslide_suffs[] = { ".svslide", /* Sakura */ ".tif", /* Trestle */ ".bif", /* Ventana */ + ".dcm", /* DICOM */ NULL }; diff --git a/libvips/foreign/pdfiumload.c b/libvips/foreign/pdfiumload.c index 146d041995..8c1fdd890b 100644 --- a/libvips/foreign/pdfiumload.c +++ b/libvips/foreign/pdfiumload.c @@ -110,6 +110,7 @@ EOF #include #include #include +#include #define TILE_SIZE (4000) @@ -165,6 +166,10 @@ typedef struct _VipsForeignLoadPdf { */ VipsPel *ink; + /* Render this page box. + */ + VipsForeignPdfPageBox page_box; + } VipsForeignLoadPdf; typedef VipsForeignLoadClass VipsForeignLoadPdfClass; @@ -448,6 +453,49 @@ vips_foreign_load_pdf_set_image(VipsForeignLoadPdf *pdf, VipsImage *out) return 0; } +static void +vips_foreign_load_pdf_apply_page_box(FPDF_PAGE page, VipsForeignPdfPageBox box) +{ + float left, bottom, right, top; + + /* Avoid locking when no change in region to render. + */ + if (box == VIPS_FOREIGN_PDF_PAGE_BOX_CROP) + return; + + g_mutex_lock(&vips_pdfium_mutex); + switch (box) { + case VIPS_FOREIGN_PDF_PAGE_BOX_MEDIA: + if (FPDFPage_GetMediaBox(page, &left, &bottom, &right, &top)) + FPDFPage_SetCropBox(page, left, bottom, right, top); + else + g_warning("missing media box, using default crop box"); + break; + case VIPS_FOREIGN_PDF_PAGE_BOX_TRIM: + if (FPDFPage_GetTrimBox(page, &left, &bottom, &right, &top)) + FPDFPage_SetCropBox(page, left, bottom, right, top); + else + g_warning("missing trim box, using default crop box"); + break; + case VIPS_FOREIGN_PDF_PAGE_BOX_BLEED: + if (FPDFPage_GetBleedBox(page, &left, &bottom, &right, &top)) + FPDFPage_SetCropBox(page, left, bottom, right, top); + else + g_warning("missing bleed box, using default crop box"); + break; + case VIPS_FOREIGN_PDF_PAGE_BOX_ART: + if (FPDFPage_GetArtBox(page, &left, &bottom, &right, &top)) + FPDFPage_SetCropBox(page, left, bottom, right, top); + else + g_warning("missing art box, using default crop box"); + break; + case VIPS_FOREIGN_PDF_PAGE_BOX_CROP: + default: + break; + } + g_mutex_unlock(&vips_pdfium_mutex); +} + static int vips_foreign_load_pdf_header(VipsForeignLoad *load) { @@ -455,7 +503,6 @@ vips_foreign_load_pdf_header(VipsForeignLoad *load) VipsForeignLoadPdf *pdf = (VipsForeignLoadPdf *) load; int top; - int i; #ifdef DEBUG printf("vips_foreign_load_pdf_header: %p\n", pdf); @@ -487,11 +534,17 @@ vips_foreign_load_pdf_header(VipsForeignLoad *load) pdf->image.top = 0; pdf->image.width = 0; pdf->image.height = 0; - for (i = 0; i < pdf->n; i++) { + for (int i = 0; i < pdf->n; i++) { if (vips_foreign_load_pdf_get_page(pdf, pdf->page_no + i)) return -1; pdf->pages[i].left = 0; pdf->pages[i].top = top; + + /* Attempt to apply selected page box using the page coordinate + * system (bottom left) before calculating render dimensions + * using the client coordinate system (top left). */ + vips_foreign_load_pdf_apply_page_box(pdf->page, pdf->page_box); + /* We do round to nearest, in the same way that vips_resize() * does round to nearest. Without this, things like * shrink-on-load will break. @@ -522,7 +575,7 @@ vips_foreign_load_pdf_header(VipsForeignLoad *load) /* If all pages are the same height, we can tag this as a toilet roll * image. */ - for (i = 1; i < pdf->n; i++) + for (int i = 1; i < pdf->n; i++) if (pdf->pages[i].height != pdf->pages[0].height) break; @@ -584,47 +637,53 @@ vips_foreign_load_pdf_generate(VipsRegion *out_region, top = r->top; while (top < VIPS_RECT_BOTTOM(r)) { - VipsRect rect; FPDF_BITMAP bitmap; + /* Is the rect within this page? It might not be if the output is more + * than one tile wide and this page is narrower. + */ + VipsRect rect; vips_rect_intersectrect(r, &pdf->pages[i], &rect); + if (rect.width > 0 && + rect.height > 0) { - if (vips_foreign_load_pdf_get_page(pdf, pdf->page_no + i)) - return -1; + if (vips_foreign_load_pdf_get_page(pdf, pdf->page_no + i)) + return -1; - vips__worker_lock(&vips_pdfium_mutex); + vips__worker_lock(&vips_pdfium_mutex); - /* 4 means RGBA. - */ - bitmap = FPDFBitmap_CreateEx(rect.width, rect.height, 4, - VIPS_REGION_ADDR(out_region, rect.left, rect.top), - VIPS_REGION_LSKIP(out_region)); + /* 4 means RGBA. + */ + bitmap = FPDFBitmap_CreateEx(rect.width, rect.height, 4, + VIPS_REGION_ADDR(out_region, rect.left, rect.top), + VIPS_REGION_LSKIP(out_region)); - /* Only paint the background if there's no transparency. - */ - if (!FPDFPage_HasTransparency(pdf->page)) { - FPDF_DWORD ink = *((guint32 *) pdf->ink); + /* Only paint the background if there's no transparency. + */ + if (!FPDFPage_HasTransparency(pdf->page)) { + FPDF_DWORD ink = *((guint32 *) pdf->ink); - FPDFBitmap_FillRect(bitmap, - 0, 0, rect.width, rect.height, ink); - } + FPDFBitmap_FillRect(bitmap, + 0, 0, rect.width, rect.height, ink); + } - // pdfium writes bgra by default, we need rgba - FPDF_RenderPageBitmap(bitmap, pdf->page, - pdf->pages[i].left - rect.left, - pdf->pages[i].top - rect.top, - pdf->pages[i].width, pdf->pages[i].height, - 0, FPDF_ANNOT | FPDF_REVERSE_BYTE_ORDER); + // pdfium writes bgra by default, we need rgba + FPDF_RenderPageBitmap(bitmap, pdf->page, + pdf->pages[i].left - rect.left, + pdf->pages[i].top - rect.top, + pdf->pages[i].width, pdf->pages[i].height, + 0, FPDF_ANNOT | FPDF_REVERSE_BYTE_ORDER); - FPDF_FFLDraw(pdf->form, bitmap, pdf->page, - pdf->pages[i].left - rect.left, - pdf->pages[i].top - rect.top, - pdf->pages[i].width, pdf->pages[i].height, - 0, FPDF_ANNOT | FPDF_REVERSE_BYTE_ORDER); + FPDF_FFLDraw(pdf->form, bitmap, pdf->page, + pdf->pages[i].left - rect.left, + pdf->pages[i].top - rect.top, + pdf->pages[i].width, pdf->pages[i].height, + 0, FPDF_ANNOT | FPDF_REVERSE_BYTE_ORDER); - FPDFBitmap_Destroy(bitmap); + FPDFBitmap_Destroy(bitmap); - g_mutex_unlock(&vips_pdfium_mutex); + g_mutex_unlock(&vips_pdfium_mutex); + } top += rect.height; i += 1; @@ -730,6 +789,14 @@ vips_foreign_load_pdf_class_init(VipsForeignLoadPdfClass *class) VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET(VipsForeignLoadPdf, password), NULL); + + VIPS_ARG_ENUM(class, "page_box", 26, + _("Page box"), + _("The region of the page to render"), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET(VipsForeignLoadPdf, page_box), + VIPS_TYPE_FOREIGN_PDF_PAGE_BOX, + VIPS_FOREIGN_PDF_PAGE_BOX_CROP); } static void @@ -740,6 +807,7 @@ vips_foreign_load_pdf_init(VipsForeignLoadPdf *pdf) pdf->n = 1; pdf->current_page = -1; pdf->background = vips_array_double_newv(1, 255.0); + pdf->page_box = VIPS_FOREIGN_PDF_PAGE_BOX_CROP; } typedef struct _VipsForeignLoadPdfFile { @@ -798,7 +866,7 @@ vips_foreign_load_pdf_file_class_init( gobject_class->get_property = vips_object_get_property; object_class->nickname = "pdfload"; - object_class->description = _("load PDF from file"); + object_class->description = _("load PDF from file (pdfium)"); object_class->build = vips_foreign_load_pdf_file_build; foreign_class->suffs = vips__pdf_suffs; @@ -861,7 +929,7 @@ vips_foreign_load_pdf_buffer_class_init( gobject_class->get_property = vips_object_get_property; object_class->nickname = "pdfload_buffer"; - object_class->description = _("load PDF from buffer"); + object_class->description = _("load PDF from buffer (pdfium)"); object_class->build = vips_foreign_load_pdf_buffer_build; load_class->is_a_buffer = vips__pdf_is_a_buffer; @@ -919,7 +987,7 @@ vips_foreign_load_pdf_source_class_init( gobject_class->get_property = vips_object_get_property; object_class->nickname = "pdfload_source"; - object_class->description = _("load PDF from source"); + object_class->description = _("load PDF from source (pdfium)"); object_class->build = vips_foreign_load_pdf_source_build; operation_class->flags |= VIPS_OPERATION_NOCACHE; diff --git a/libvips/foreign/popplerload.c b/libvips/foreign/popplerload.c index 160b3a2b58..64e0089b91 100644 --- a/libvips/foreign/popplerload.c +++ b/libvips/foreign/popplerload.c @@ -158,6 +158,10 @@ typedef struct _VipsForeignLoadPdf { */ VipsPel *ink; + /* Render this page box, currently only crop is supported. + */ + VipsForeignPdfPageBox page_box; + } VipsForeignLoadPdf; typedef struct _VipsForeignLoadPdfClass { @@ -337,6 +341,9 @@ vips_foreign_load_pdf_header(VipsForeignLoad *load) if (!(pdf->pages = VIPS_ARRAY(pdf, pdf->n, VipsRect))) return -1; + if (pdf->page_box != VIPS_FOREIGN_PDF_PAGE_BOX_CROP) + g_warning("only crop page box is supported"); + top = 0; pdf->image.left = 0; pdf->image.top = 0; @@ -580,6 +587,14 @@ vips_foreign_load_pdf_class_init(VipsForeignLoadPdfClass *class) VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET(VipsForeignLoadPdf, password), NULL); + + VIPS_ARG_ENUM(class, "page_box", 26, + _("Page box"), + _("The region of the page to render, only crop is supported"), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET(VipsForeignLoadPdf, page_box), + VIPS_TYPE_FOREIGN_PDF_PAGE_BOX, + VIPS_FOREIGN_PDF_PAGE_BOX_CROP); } static void @@ -590,6 +605,7 @@ vips_foreign_load_pdf_init(VipsForeignLoadPdf *pdf) pdf->n = 1; pdf->current_page = -1; pdf->background = vips_array_double_newv(1, 255.0); + pdf->page_box = VIPS_FOREIGN_PDF_PAGE_BOX_CROP; } typedef struct _VipsForeignLoadPdfFile { @@ -675,7 +691,7 @@ vips_foreign_load_pdf_file_class_init( gobject_class->get_property = vips_object_get_property; object_class->nickname = "pdfload"; - object_class->description = _("load PDF from file"); + object_class->description = _("load PDF from file (poppler)"); object_class->build = vips_foreign_load_pdf_file_build; foreign_class->suffs = vips__pdf_suffs; @@ -738,7 +754,7 @@ vips_foreign_load_pdf_buffer_class_init( gobject_class->get_property = vips_object_get_property; object_class->nickname = "pdfload_buffer"; - object_class->description = _("load PDF from buffer"); + object_class->description = _("load PDF from buffer (poppler)"); object_class->build = vips_foreign_load_pdf_buffer_build; load_class->is_a_buffer = vips__pdf_is_a_buffer; @@ -796,7 +812,7 @@ vips_foreign_load_pdf_source_class_init( gobject_class->get_property = vips_object_get_property; object_class->nickname = "pdfload_source"; - object_class->description = _("load PDF from source"); + object_class->description = _("load PDF from source (poppler)"); object_class->build = vips_foreign_load_pdf_source_build; operation_class->flags |= VIPS_OPERATION_NOCACHE; diff --git a/libvips/foreign/svgload.c b/libvips/foreign/svgload.c index 6b5c6b837e..dd042c4051 100644 --- a/libvips/foreign/svgload.c +++ b/libvips/foreign/svgload.c @@ -640,8 +640,9 @@ vips_foreign_load_svg_generate(VipsRegion *out_region, cr = cairo_create(surface); cairo_surface_destroy(surface); -#ifdef HAVE_RSVG_HANDLE_SET_STYLESHEET - if (svg->stylesheet && g_utf8_validate(svg->stylesheet, -1, NULL)) { +#if LIBRSVG_CHECK_VERSION(2, 48, 0) + if (svg->stylesheet && + g_utf8_validate(svg->stylesheet, -1, NULL)) { GError *error = NULL; if (!rsvg_handle_set_stylesheet(svg->page, (const guint8 *) svg->stylesheet, diff --git a/libvips/foreign/tiff2vips.c b/libvips/foreign/tiff2vips.c index f9b7380f6d..cc4432b14d 100644 --- a/libvips/foreign/tiff2vips.c +++ b/libvips/foreign/tiff2vips.c @@ -626,7 +626,8 @@ rtiff_handler_warning(TIFF *tiff, void* user_data, rtiff->failed = TRUE; } } - g_logv("tiff2vips", G_LOG_LEVEL_WARNING, fmt, ap); + + g_logv(G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, fmt, ap); return 1; } diff --git a/libvips/foreign/tiffsave.c b/libvips/foreign/tiffsave.c index d320f0e71d..b23992e42f 100644 --- a/libvips/foreign/tiffsave.c +++ b/libvips/foreign/tiffsave.c @@ -186,22 +186,21 @@ vips_foreign_save_tiff_build(VipsObject *object) vips_isprefix("in", p)) resunit = VIPS_FOREIGN_TIFF_RESUNIT_INCH; - double xres; - xres = ready->Xres; - if (vips_object_argument_isset(object, "xres")) { - if (resunit == VIPS_FOREIGN_TIFF_RESUNIT_INCH) - xres = tiff->xres * 25.4; - else - xres = tiff->xres * 10.0; - } + double xres = vips_object_argument_isset(object, "xres") + ? tiff->xres + : ready->Xres; - double yres; - yres = ready->Yres; - if (vips_object_argument_isset(object, "yres")) { - if (resunit == VIPS_FOREIGN_TIFF_RESUNIT_INCH) - yres = tiff->yres * 25.4; - else - yres = tiff->yres * 10.0; + double yres = vips_object_argument_isset(object, "yres") + ? tiff->yres + : ready->Yres; + + if (tiff->resunit == VIPS_FOREIGN_TIFF_RESUNIT_INCH) { + xres *= 25.4; + yres *= 25.4; + } + else { + xres *= 10.0; + yres *= 10.0; } if (vips__tiff_write_target(ready, tiff->target, diff --git a/libvips/foreign/vips2tiff.c b/libvips/foreign/vips2tiff.c index 4c37b0e4fe..2a3c8c6831 100644 --- a/libvips/foreign/vips2tiff.c +++ b/libvips/foreign/vips2tiff.c @@ -459,7 +459,7 @@ static int wtiff_handler_warning(TIFF *tiff, void* user_data, const char *module, const char *fmt, va_list ap) { - g_logv("vips2tiff", G_LOG_LEVEL_WARNING, fmt, ap); + g_logv(G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, fmt, ap); return 1; } diff --git a/libvips/include/vips/almostdeprecated.h b/libvips/include/vips/almostdeprecated.h index 9a560d4252..fab22ff419 100644 --- a/libvips/include/vips/almostdeprecated.h +++ b/libvips/include/vips/almostdeprecated.h @@ -370,7 +370,7 @@ typedef enum { VIPS_FOREIGN_JPEG_SUBSAMPLE_AUTO, VIPS_FOREIGN_JPEG_SUBSAMPLE_ON, VIPS_FOREIGN_JPEG_SUBSAMPLE_OFF, - VIPS_FOREIGN_JPEG_SUBSAMPLE_LAST + VIPS_FOREIGN_JPEG_SUBSAMPLE_LAST /*< skip >*/ } VipsForeignJpegSubsample; VIPS_DEPRECATED_FOR(vips_rawsave_target) diff --git a/libvips/include/vips/arithmetic.h b/libvips/include/vips/arithmetic.h index 7a039e9726..f7e45fa18f 100644 --- a/libvips/include/vips/arithmetic.h +++ b/libvips/include/vips/arithmetic.h @@ -79,7 +79,7 @@ typedef enum { VIPS_OPERATION_MATH_ASINH, VIPS_OPERATION_MATH_ACOSH, VIPS_OPERATION_MATH_ATANH, - VIPS_OPERATION_MATH_LAST + VIPS_OPERATION_MATH_LAST /*< skip >*/ } VipsOperationMath; /** @@ -94,7 +94,7 @@ typedef enum { VIPS_OPERATION_MATH2_POW, VIPS_OPERATION_MATH2_WOP, VIPS_OPERATION_MATH2_ATAN2, - VIPS_OPERATION_MATH2_LAST + VIPS_OPERATION_MATH2_LAST /*< skip >*/ } VipsOperationMath2; /** @@ -109,7 +109,7 @@ typedef enum { VIPS_OPERATION_ROUND_RINT, VIPS_OPERATION_ROUND_CEIL, VIPS_OPERATION_ROUND_FLOOR, - VIPS_OPERATION_ROUND_LAST + VIPS_OPERATION_ROUND_LAST /*< skip >*/ } VipsOperationRound; /** @@ -130,7 +130,7 @@ typedef enum { VIPS_OPERATION_RELATIONAL_LESSEQ, VIPS_OPERATION_RELATIONAL_MORE, VIPS_OPERATION_RELATIONAL_MOREEQ, - VIPS_OPERATION_RELATIONAL_LAST + VIPS_OPERATION_RELATIONAL_LAST /*< skip >*/ } VipsOperationRelational; /** @@ -149,7 +149,7 @@ typedef enum { VIPS_OPERATION_BOOLEAN_EOR, VIPS_OPERATION_BOOLEAN_LSHIFT, VIPS_OPERATION_BOOLEAN_RSHIFT, - VIPS_OPERATION_BOOLEAN_LAST + VIPS_OPERATION_BOOLEAN_LAST /*< skip >*/ } VipsOperationBoolean; /** @@ -164,7 +164,7 @@ typedef enum { VIPS_OPERATION_COMPLEX_POLAR, VIPS_OPERATION_COMPLEX_RECT, VIPS_OPERATION_COMPLEX_CONJ, - VIPS_OPERATION_COMPLEX_LAST + VIPS_OPERATION_COMPLEX_LAST /*< skip >*/ } VipsOperationComplex; /** @@ -174,8 +174,8 @@ typedef enum { * See also: [method@Image.complex2]. */ typedef enum { - VIPS_OPERATION_COMPLEX2_CROSS_PHASE, - VIPS_OPERATION_COMPLEX2_LAST + VIPS_OPERATION_COMPLEX2_CROSS_PHASE, /*< nick=cross-phase >*/ + VIPS_OPERATION_COMPLEX2_LAST /*< skip >*/ } VipsOperationComplex2; /** @@ -188,7 +188,7 @@ typedef enum { typedef enum { VIPS_OPERATION_COMPLEXGET_REAL, VIPS_OPERATION_COMPLEXGET_IMAG, - VIPS_OPERATION_COMPLEXGET_LAST + VIPS_OPERATION_COMPLEXGET_LAST /*< skip >*/ } VipsOperationComplexget; VIPS_API diff --git a/libvips/include/vips/basic.h b/libvips/include/vips/basic.h index e74deb48df..2f94f03688 100644 --- a/libvips/include/vips/basic.h +++ b/libvips/include/vips/basic.h @@ -97,7 +97,7 @@ typedef enum { VIPS_PRECISION_INTEGER, VIPS_PRECISION_FLOAT, VIPS_PRECISION_APPROXIMATE, - VIPS_PRECISION_LAST + VIPS_PRECISION_LAST /*< skip >*/ } VipsPrecision; #ifndef __GI_SCANNER__ diff --git a/libvips/include/vips/colour.h b/libvips/include/vips/colour.h index 5848249b83..b943e0db14 100644 --- a/libvips/include/vips/colour.h +++ b/libvips/include/vips/colour.h @@ -105,13 +105,13 @@ typedef enum { * four standard ones. */ VIPS_INTENT_AUTO = 32, - VIPS_INTENT_LAST + VIPS_INTENT_LAST /*< skip >*/ } VipsIntent; typedef enum { VIPS_PCS_LAB, VIPS_PCS_XYZ, - VIPS_PCS_LAST + VIPS_PCS_LAST /*< skip >*/ } VipsPCS; VIPS_API diff --git a/libvips/include/vips/conversion.h b/libvips/include/vips/conversion.h index 1eb5ad0690..afe4f1534e 100644 --- a/libvips/include/vips/conversion.h +++ b/libvips/include/vips/conversion.h @@ -45,7 +45,7 @@ typedef enum { VIPS_EXTEND_MIRROR, VIPS_EXTEND_WHITE, VIPS_EXTEND_BACKGROUND, - VIPS_EXTEND_LAST + VIPS_EXTEND_LAST /*< skip >*/ } VipsExtend; typedef enum { @@ -58,20 +58,20 @@ typedef enum { VIPS_COMPASS_DIRECTION_SOUTH_EAST, VIPS_COMPASS_DIRECTION_SOUTH_WEST, VIPS_COMPASS_DIRECTION_NORTH_WEST, - VIPS_COMPASS_DIRECTION_LAST + VIPS_COMPASS_DIRECTION_LAST /*< skip >*/ } VipsCompassDirection; typedef enum { VIPS_DIRECTION_HORIZONTAL, VIPS_DIRECTION_VERTICAL, - VIPS_DIRECTION_LAST + VIPS_DIRECTION_LAST /*< skip >*/ } VipsDirection; typedef enum { VIPS_ALIGN_LOW, VIPS_ALIGN_CENTRE, VIPS_ALIGN_HIGH, - VIPS_ALIGN_LAST + VIPS_ALIGN_LAST /*< skip >*/ } VipsAlign; typedef enum { @@ -79,7 +79,7 @@ typedef enum { VIPS_ANGLE_D90, VIPS_ANGLE_D180, VIPS_ANGLE_D270, - VIPS_ANGLE_LAST + VIPS_ANGLE_LAST /*< skip >*/ } VipsAngle; typedef enum { @@ -91,7 +91,7 @@ typedef enum { VIPS_ANGLE45_D225, VIPS_ANGLE45_D270, VIPS_ANGLE45_D315, - VIPS_ANGLE45_LAST + VIPS_ANGLE45_LAST /*< skip >*/ } VipsAngle45; typedef enum { @@ -102,7 +102,7 @@ typedef enum { VIPS_INTERESTING_LOW, VIPS_INTERESTING_HIGH, VIPS_INTERESTING_ALL, - VIPS_INTERESTING_LAST + VIPS_INTERESTING_LAST /*< skip >*/ } VipsInteresting; typedef enum { @@ -131,7 +131,7 @@ typedef enum { VIPS_BLEND_MODE_SOFT_LIGHT, VIPS_BLEND_MODE_DIFFERENCE, VIPS_BLEND_MODE_EXCLUSION, - VIPS_BLEND_MODE_LAST + VIPS_BLEND_MODE_LAST /*< skip >*/ } VipsBlendMode; VIPS_API diff --git a/libvips/include/vips/convolution.h b/libvips/include/vips/convolution.h index 7141446bf7..6da52f547b 100644 --- a/libvips/include/vips/convolution.h +++ b/libvips/include/vips/convolution.h @@ -42,7 +42,7 @@ typedef enum { VIPS_COMBINE_MAX, VIPS_COMBINE_SUM, VIPS_COMBINE_MIN, - VIPS_COMBINE_LAST + VIPS_COMBINE_LAST /*< skip >*/ } VipsCombine; VIPS_API diff --git a/libvips/include/vips/create.h b/libvips/include/vips/create.h index f7dd5242d5..27db923849 100644 --- a/libvips/include/vips/create.h +++ b/libvips/include/vips/create.h @@ -43,7 +43,7 @@ typedef enum { VIPS_TEXT_WRAP_CHAR, VIPS_TEXT_WRAP_WORD_CHAR, VIPS_TEXT_WRAP_NONE, - VIPS_TEXT_WRAP_LAST + VIPS_TEXT_WRAP_LAST /*< skip >*/ } VipsTextWrap; typedef enum { @@ -51,7 +51,7 @@ typedef enum { VIPS_SDF_SHAPE_BOX, VIPS_SDF_SHAPE_ROUNDED_BOX, VIPS_SDF_SHAPE_LINE, - VIPS_SDF_SHAPE_LAST + VIPS_SDF_SHAPE_LAST /*< skip >*/ } VipsSdfShape; VIPS_API diff --git a/libvips/include/vips/draw.h b/libvips/include/vips/draw.h index 486c90f282..1101b1258d 100644 --- a/libvips/include/vips/draw.h +++ b/libvips/include/vips/draw.h @@ -45,7 +45,7 @@ extern "C" { typedef enum { VIPS_COMBINE_MODE_SET, VIPS_COMBINE_MODE_ADD, - VIPS_COMBINE_MODE_LAST + VIPS_COMBINE_MODE_LAST /*< skip >*/ } VipsCombineMode; VIPS_API diff --git a/libvips/include/vips/foreign.h b/libvips/include/vips/foreign.h index 12b0f51049..c722839d18 100644 --- a/libvips/include/vips/foreign.h +++ b/libvips/include/vips/foreign.h @@ -123,7 +123,7 @@ typedef enum { VIPS_FAIL_ON_TRUNCATED, VIPS_FAIL_ON_ERROR, VIPS_FAIL_ON_WARNING, - VIPS_FAIL_ON_LAST + VIPS_FAIL_ON_LAST /*< skip >*/ } VipsFailOn; #define VIPS_TYPE_FOREIGN_LOAD (vips_foreign_load_get_type()) @@ -495,7 +495,7 @@ typedef enum { VIPS_FOREIGN_SUBSAMPLE_AUTO, VIPS_FOREIGN_SUBSAMPLE_ON, VIPS_FOREIGN_SUBSAMPLE_OFF, - VIPS_FOREIGN_SUBSAMPLE_LAST + VIPS_FOREIGN_SUBSAMPLE_LAST /*< skip >*/ } VipsForeignSubsample; VIPS_API @@ -539,7 +539,7 @@ typedef enum { VIPS_FOREIGN_WEBP_PRESET_DRAWING, VIPS_FOREIGN_WEBP_PRESET_ICON, VIPS_FOREIGN_WEBP_PRESET_TEXT, - VIPS_FOREIGN_WEBP_PRESET_LAST + VIPS_FOREIGN_WEBP_PRESET_LAST /*< skip >*/ } VipsForeignWebpPreset; VIPS_API @@ -597,7 +597,7 @@ typedef enum { VIPS_FOREIGN_TIFF_COMPRESSION_WEBP, VIPS_FOREIGN_TIFF_COMPRESSION_ZSTD, VIPS_FOREIGN_TIFF_COMPRESSION_JP2K, - VIPS_FOREIGN_TIFF_COMPRESSION_LAST + VIPS_FOREIGN_TIFF_COMPRESSION_LAST /*< skip >*/ } VipsForeignTiffCompression; /** @@ -613,7 +613,7 @@ typedef enum { VIPS_FOREIGN_TIFF_PREDICTOR_NONE = 1, VIPS_FOREIGN_TIFF_PREDICTOR_HORIZONTAL = 2, VIPS_FOREIGN_TIFF_PREDICTOR_FLOAT = 3, - VIPS_FOREIGN_TIFF_PREDICTOR_LAST + VIPS_FOREIGN_TIFF_PREDICTOR_LAST /*< skip >*/ } VipsForeignTiffPredictor; /** @@ -626,7 +626,7 @@ typedef enum { typedef enum { VIPS_FOREIGN_TIFF_RESUNIT_CM, VIPS_FOREIGN_TIFF_RESUNIT_INCH, - VIPS_FOREIGN_TIFF_RESUNIT_LAST + VIPS_FOREIGN_TIFF_RESUNIT_LAST /*< skip >*/ } VipsForeignTiffResunit; VIPS_API @@ -716,6 +716,9 @@ VIPS_API int vips_magickload_buffer(void *buf, size_t len, VipsImage **out, ...) G_GNUC_NULL_TERMINATED; VIPS_API +int vips_magickload_source(VipsSource *source, VipsImage **out, ...) + G_GNUC_NULL_TERMINATED; +VIPS_API int vips_magicksave(VipsImage *in, const char *filename, ...) G_GNUC_NULL_TERMINATED; VIPS_API @@ -790,7 +793,7 @@ typedef enum { VIPS_FOREIGN_PPM_FORMAT_PPM, VIPS_FOREIGN_PPM_FORMAT_PFM, VIPS_FOREIGN_PPM_FORMAT_PNM, - VIPS_FOREIGN_PPM_FORMAT_LAST + VIPS_FOREIGN_PPM_FORMAT_LAST /*< skip >*/ } VipsForeignPpmFormat; VIPS_API @@ -832,6 +835,29 @@ VIPS_API int vips_radsave_target(VipsImage *in, VipsTarget *target, ...) G_GNUC_NULL_TERMINATED; +/** + * VipsForeignPdfPageBox: + * @VIPS_FOREIGN_PDF_PAGE_BOX_MEDIA + * @VIPS_FOREIGN_PDF_PAGE_BOX_CROP + * @VIPS_FOREIGN_PDF_PAGE_BOX_TRIM + * @VIPS_FOREIGN_PDF_PAGE_BOX_BLEED + * @VIPS_FOREIGN_PDF_PAGE_BOX_ART + * + * Each page of a PDF document can contain multiple page boxes, + * also known as boundary boxes or print marks. + * + * Each page box defines a region of the complete page that + * should be rendered. The default region is the crop box. + */ +typedef enum { + VIPS_FOREIGN_PDF_PAGE_BOX_MEDIA, + VIPS_FOREIGN_PDF_PAGE_BOX_CROP, + VIPS_FOREIGN_PDF_PAGE_BOX_TRIM, + VIPS_FOREIGN_PDF_PAGE_BOX_BLEED, + VIPS_FOREIGN_PDF_PAGE_BOX_ART, + VIPS_FOREIGN_PDF_PAGE_BOX_LAST /*< skip >*/ +} VipsForeignPdfPageBox; + VIPS_API int vips_pdfload(const char *filename, VipsImage **out, ...) G_GNUC_NULL_TERMINATED; @@ -875,6 +901,16 @@ VIPS_API int vips_gifsave_target(VipsImage *in, VipsTarget *target, ...) G_GNUC_NULL_TERMINATED; +VIPS_API +int vips_dcrawload(const char *filename, VipsImage **out, ...) + G_GNUC_NULL_TERMINATED; +VIPS_API +int vips_dcrawload_buffer(void *buf, size_t len, VipsImage **out, ...) + G_GNUC_NULL_TERMINATED; +VIPS_API +int vips_dcrawload_source(VipsSource *source, VipsImage **out, ...) + G_GNUC_NULL_TERMINATED; + VIPS_API int vips_heifload(const char *filename, VipsImage **out, ...) G_GNUC_NULL_TERMINATED; @@ -958,7 +994,7 @@ typedef enum { VIPS_FOREIGN_DZ_LAYOUT_GOOGLE, VIPS_FOREIGN_DZ_LAYOUT_IIIF, VIPS_FOREIGN_DZ_LAYOUT_IIIF3, - VIPS_FOREIGN_DZ_LAYOUT_LAST + VIPS_FOREIGN_DZ_LAYOUT_LAST /*< skip >*/ } VipsForeignDzLayout; /** @@ -973,7 +1009,7 @@ typedef enum { VIPS_FOREIGN_DZ_DEPTH_ONEPIXEL, VIPS_FOREIGN_DZ_DEPTH_ONETILE, VIPS_FOREIGN_DZ_DEPTH_ONE, - VIPS_FOREIGN_DZ_DEPTH_LAST + VIPS_FOREIGN_DZ_DEPTH_LAST /*< skip >*/ } VipsForeignDzDepth; /** @@ -982,13 +1018,13 @@ typedef enum { * @VIPS_FOREIGN_DZ_CONTAINER_ZIP: write tiles to a zip file * @VIPS_FOREIGN_DZ_CONTAINER_SZI: write to a szi file * - * How many pyramid layers to create. + * What container format to use. */ typedef enum { VIPS_FOREIGN_DZ_CONTAINER_FS, VIPS_FOREIGN_DZ_CONTAINER_ZIP, VIPS_FOREIGN_DZ_CONTAINER_SZI, - VIPS_FOREIGN_DZ_CONTAINER_LAST + VIPS_FOREIGN_DZ_CONTAINER_LAST /*< skip >*/ } VipsForeignDzContainer; VIPS_API @@ -1017,7 +1053,7 @@ typedef enum { VIPS_FOREIGN_HEIF_COMPRESSION_AVC = 2, VIPS_FOREIGN_HEIF_COMPRESSION_JPEG = 3, VIPS_FOREIGN_HEIF_COMPRESSION_AV1 = 4, - VIPS_FOREIGN_HEIF_COMPRESSION_LAST + VIPS_FOREIGN_HEIF_COMPRESSION_LAST /*< skip >*/ } VipsForeignHeifCompression; /** @@ -1039,7 +1075,7 @@ typedef enum { VIPS_FOREIGN_HEIF_ENCODER_RAV1E, VIPS_FOREIGN_HEIF_ENCODER_SVT, VIPS_FOREIGN_HEIF_ENCODER_X265, - VIPS_FOREIGN_HEIF_ENCODER_LAST + VIPS_FOREIGN_HEIF_ENCODER_LAST /*< skip >*/ } VipsForeignHeifEncoder; #ifdef __cplusplus diff --git a/libvips/include/vips/image.h b/libvips/include/vips/image.h index f9b91241c1..59c3d53a2f 100644 --- a/libvips/include/vips/image.h +++ b/libvips/include/vips/image.h @@ -112,7 +112,7 @@ typedef enum { VIPS_INTERPRETATION_MATRIX = 27, VIPS_INTERPRETATION_scRGB = 28, VIPS_INTERPRETATION_HSV = 29, - VIPS_INTERPRETATION_LAST = 30 + VIPS_INTERPRETATION_LAST = 30 /*< skip >*/ } VipsInterpretation; typedef enum { @@ -127,7 +127,7 @@ typedef enum { VIPS_FORMAT_COMPLEX = 7, VIPS_FORMAT_DOUBLE = 8, VIPS_FORMAT_DPCOMPLEX = 9, - VIPS_FORMAT_LAST = 10 + VIPS_FORMAT_LAST = 10 /*< skip >*/ } VipsBandFormat; typedef enum { @@ -135,14 +135,14 @@ typedef enum { VIPS_CODING_NONE = 0, VIPS_CODING_LABQ = 2, VIPS_CODING_RAD = 6, - VIPS_CODING_LAST = 7 + VIPS_CODING_LAST = 7 /*< skip >*/ } VipsCoding; typedef enum { VIPS_ACCESS_RANDOM, VIPS_ACCESS_SEQUENTIAL, VIPS_ACCESS_SEQUENTIAL_UNBUFFERED, - VIPS_ACCESS_LAST + VIPS_ACCESS_LAST /*< skip >*/ } VipsAccess; typedef void *(*VipsStartFn)(VipsImage *out, void *a, void *b); diff --git a/libvips/include/vips/internal.h b/libvips/include/vips/internal.h index f141fa51b2..d1c700dada 100644 --- a/libvips/include/vips/internal.h +++ b/libvips/include/vips/internal.h @@ -166,6 +166,7 @@ void vips_threadset_free(VipsThreadset *set); VIPS_API void vips__worker_lock(GMutex *mutex); VIPS_API void vips__worker_cond_wait(GCond *cond, GMutex *mutex); +gboolean vips__worker_exit(void); void vips__cache_init(void); diff --git a/libvips/include/vips/morphology.h b/libvips/include/vips/morphology.h index 918b0ce3d4..7731120d05 100644 --- a/libvips/include/vips/morphology.h +++ b/libvips/include/vips/morphology.h @@ -41,7 +41,7 @@ extern "C" { typedef enum { VIPS_OPERATION_MORPHOLOGY_ERODE, VIPS_OPERATION_MORPHOLOGY_DILATE, - VIPS_OPERATION_MORPHOLOGY_LAST + VIPS_OPERATION_MORPHOLOGY_LAST /*< skip >*/ } VipsOperationMorphology; VIPS_API diff --git a/libvips/include/vips/private.h b/libvips/include/vips/private.h index 19cbf15f42..6a72511324 100644 --- a/libvips/include/vips/private.h +++ b/libvips/include/vips/private.h @@ -44,16 +44,6 @@ extern "C" { #define VIPS_SPARE (8) -/* Private to iofuncs: the minimum number of scanlines we add above and below - * the window as a margin for slop. - */ -#define VIPS__WINDOW_MARGIN_PIXELS (128) - -/* Private to iofuncs: add at least this many bytes above and below the window. - * There's no point mapping just a few KB of a small image. - */ -#define VIPS__WINDOW_MARGIN_BYTES (1024 * 1024 * 10) - /* sizeof() a VIPS header on disc. */ #define VIPS_SIZEOF_HEADER (64) diff --git a/libvips/include/vips/region.h b/libvips/include/vips/region.h index d7f06c5746..b3b61e4291 100644 --- a/libvips/include/vips/region.h +++ b/libvips/include/vips/region.h @@ -80,7 +80,7 @@ typedef enum { VIPS_REGION_SHRINK_MAX, VIPS_REGION_SHRINK_MIN, VIPS_REGION_SHRINK_NEAREST, - VIPS_REGION_SHRINK_LAST + VIPS_REGION_SHRINK_LAST /*< skip >*/ } VipsRegionShrink; /* Sub-area of image. diff --git a/libvips/include/vips/resample.h b/libvips/include/vips/resample.h index d71a8de509..6f688488dd 100644 --- a/libvips/include/vips/resample.h +++ b/libvips/include/vips/resample.h @@ -47,7 +47,7 @@ typedef enum { VIPS_KERNEL_LANCZOS3, VIPS_KERNEL_MKS2013, VIPS_KERNEL_MKS2021, - VIPS_KERNEL_LAST + VIPS_KERNEL_LAST /*< skip >*/ } VipsKernel; typedef enum { @@ -55,7 +55,7 @@ typedef enum { VIPS_SIZE_UP, VIPS_SIZE_DOWN, VIPS_SIZE_FORCE, - VIPS_SIZE_LAST + VIPS_SIZE_LAST /*< skip >*/ } VipsSize; VIPS_API diff --git a/libvips/include/vips/semaphore.h b/libvips/include/vips/semaphore.h index d548c397c0..f695ff6686 100644 --- a/libvips/include/vips/semaphore.h +++ b/libvips/include/vips/semaphore.h @@ -53,11 +53,7 @@ typedef struct { int v; GMutex mutex; - - /* FIXME: sizeof(GCond) != sizeof(GCond *) - * https://gitlab.gnome.org/GNOME/glib/-/issues/1256 - */ - GCond *cond; + GCond cond; } VipsSemaphore; VIPS_API diff --git a/libvips/include/vips/threadpool.h b/libvips/include/vips/threadpool.h index 9f31bb17ed..a75e99e9d5 100644 --- a/libvips/include/vips/threadpool.h +++ b/libvips/include/vips/threadpool.h @@ -96,7 +96,6 @@ typedef struct _VipsThreadState { * debugging race conditions. */ gboolean stall; - } VipsThreadState; typedef struct _VipsThreadStateClass { diff --git a/libvips/iofuncs/cache.c b/libvips/iofuncs/cache.c index 0a23084d8b..19ea9c8a70 100644 --- a/libvips/iofuncs/cache.c +++ b/libvips/iofuncs/cache.c @@ -431,6 +431,7 @@ vips_operation_equal(VipsOperation *a, VipsOperation *b) return FALSE; } +#ifdef DEBUG_LEAK static void * vips_operation_copy_argument(VipsObject *object, GParamSpec *pspec, @@ -467,6 +468,7 @@ vips_operation_copy(VipsOperation *operation) return new; } +#endif /*DEBUG_LEAK*/ static void * vips_object_unref_arg(VipsObject *object, @@ -890,6 +892,7 @@ vips_cache_operation_buildp(VipsOperation **operation) * it to the cache, if appropriate. */ if (!hit) { +#ifdef DEBUG_LEAK unsigned int hash_before = 0; VipsOperation *operation_before = NULL; @@ -900,10 +903,12 @@ vips_cache_operation_buildp(VipsOperation **operation) hash_before = vips_operation_hash(*operation); operation_before = vips_operation_copy(*operation); } +#endif /*DEBUG_LEAK*/ if (vips_object_build(VIPS_OBJECT(*operation))) return -1; +#ifdef DEBUG_LEAK if (vips__leak && !(flags & VIPS_OPERATION_NOCACHE) && hash_before != vips_operation_hash(*operation)) { @@ -929,6 +934,7 @@ vips_cache_operation_buildp(VipsOperation **operation) } VIPS_UNREF(operation_before); +#endif /*DEBUG_LEAK*/ /* Retrieve the flags again, as vips_foreign_load_build() may * set load->nocache. diff --git a/libvips/iofuncs/image.c b/libvips/iofuncs/image.c index 3fea2dc83b..25dbe41538 100644 --- a/libvips/iofuncs/image.c +++ b/libvips/iofuncs/image.c @@ -832,9 +832,6 @@ vips_image_build(VipsObject *object) */ switch (mode[0]) { case 'v': - /* Used by 'r' for native open of vips, see below. Also by - * vips_image_rewind_output(). - */ if (vips_image_open_input(image)) return -1; @@ -857,8 +854,7 @@ vips_image_build(VipsObject *object) /* Open the image in t, then byteswap to this * image. */ - if (!(t = vips_image_new_mode(filename, - "v"))) + if (!(t = vips_image_new_mode(filename, "v"))) return -1; if (vips_byteswap(t, &t2, NULL)) { @@ -918,9 +914,8 @@ vips_image_build(VipsObject *object) image->dtype = VIPS_IMAGE_OPENOUT; else { image->dtype = VIPS_IMAGE_PARTIAL; - g_signal_connect(image, "written", - G_CALLBACK(vips_image_save_cb), - NULL); + g_signal_connect(image, + "written", G_CALLBACK(vips_image_save_cb), NULL); } } break; @@ -958,8 +953,7 @@ vips_image_build(VipsObject *object) image->sizeof_header; if (image->file_length < sizeof_image) { vips_error("VipsImage", - _("unable to open \"%s\", file too short"), - image->filename); + _("unable to open \"%s\", file too short"), image->filename); return -1; } @@ -967,8 +961,7 @@ vips_image_build(VipsObject *object) * still be able to process it without coredumps. */ if (image->file_length > sizeof_image) - g_warning("%s is longer than expected", - image->filename); + g_warning("%s is longer than expected", image->filename); break; case 'm': @@ -986,7 +979,6 @@ vips_image_build(VipsObject *object) default: vips_error("VipsImage", _("bad mode \"%s\""), mode); - return -1; } @@ -1759,8 +1751,8 @@ vips_image_new_mode(const char *filename, const char *mode) /** * vips_image_new_memory: (skip) * - * [ctor@Image.new_memory] creates a new [class@Image] which, when written to, will - * create a memory image. + * [ctor@Image.new_memory] creates a new [class@Image] which, when written to, + * will create a memory image. * * ::: seealso * [ctor@Image.new]. @@ -3309,8 +3301,7 @@ vips_image_rewind_output(VipsImage *image) NULL); if (vips_object_build(VIPS_OBJECT(image))) { vips_error("VipsImage", - _("auto-rewind for %s failed"), - image->filename); + _("auto-rewind for %s failed"), image->filename); return -1; } @@ -3657,8 +3648,7 @@ vips_image_pio_input(VipsImage *image) /* Should have been written to. */ if (!image->data) { - vips_error("vips_image_pio_input", - "%s", _("no image data")); + vips_error("vips_image_pio_input", "%s", _("no image data")); return -1; } @@ -3683,7 +3673,6 @@ vips_image_pio_input(VipsImage *image) break; case VIPS_IMAGE_OPENOUT: - /* Free any resources the image holds and reset to a base * state. */ @@ -3693,8 +3682,7 @@ vips_image_pio_input(VipsImage *image) break; default: - vips_error("vips_image_pio_input", - "%s", _("image not readable")); + vips_error("vips_image_pio_input", "%s", _("image not readable")); return -1; } diff --git a/libvips/iofuncs/mapfile.c b/libvips/iofuncs/mapfile.c index e8cc976252..75b3dd7582 100644 --- a/libvips/iofuncs/mapfile.c +++ b/libvips/iofuncs/mapfile.c @@ -202,6 +202,14 @@ vips__mmap(int fd, int writeable, size_t length, gint64 offset) FIXME ... is this a performance problem? + see https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createfilemappinga + + We should probably have a small cache from hFile -> hMMFile and + reuse mappings between vips__mmap calls + + keep a ref count for each mapping and kick the hMMFile out of + cache on the final munmap + */ CloseHandle(hMMFile); } diff --git a/libvips/iofuncs/operation.c b/libvips/iofuncs/operation.c index 2d7383feb2..f8adf74802 100644 --- a/libvips/iofuncs/operation.c +++ b/libvips/iofuncs/operation.c @@ -292,14 +292,9 @@ vips_operation_class_usage_classify(VipsArgumentClass *argument_class) /* Display a set of flags as "a:b:c" */ static void -vips__flags_to_str(VipsBuf *buf, GType type, guint value) +vips__flags_to_str(VipsBuf *buf, GFlagsClass *flags, guint value) { - GTypeClass *class = g_type_class_ref(type); - GFlagsClass *flags = G_FLAGS_CLASS(class); - - gboolean first; - - first = TRUE; + gboolean first = TRUE; for (int i = 0; i < flags->n_values; i++) // can't be 0 (would match everything), and all bits // should match all bits in the value, or "all" would always match @@ -321,18 +316,14 @@ vips_operation_pspec_usage(VipsBuf *buf, GParamSpec *pspec) /* These are the pspecs that vips uses that have interesting values. */ if (G_IS_PARAM_SPEC_ENUM(pspec)) { - GTypeClass *class = g_type_class_ref(type); + /* GParamSpecEnum holds a ref on the class so we just peek. + */ + GEnumClass *genum = g_type_class_peek(type); GParamSpecEnum *pspec_enum = (GParamSpecEnum *) pspec; - GEnumClass *genum; int i; - /* Should be impossible, no need to warn. - */ - if (!class) - return; - - genum = G_ENUM_CLASS(class); + g_assert(genum); vips_buf_appendf(buf, "\t\t\t"); vips_buf_appendf(buf, "%s", _("default enum")); @@ -342,9 +333,7 @@ vips_operation_pspec_usage(VipsBuf *buf, GParamSpec *pspec) vips_buf_appendf(buf, "%s", _("allowed enums")); vips_buf_appendf(buf, ": "); - /* -1 since we always have a "last" member. - */ - for (i = 0; i < genum->n_values - 1; i++) { + for (i = 0; i < genum->n_values; i++) { if (i > 0) vips_buf_appends(buf, ", "); vips_buf_appends(buf, genum->values[i].value_nick); @@ -353,23 +342,19 @@ vips_operation_pspec_usage(VipsBuf *buf, GParamSpec *pspec) vips_buf_appendf(buf, "\n"); } if (G_IS_PARAM_SPEC_FLAGS(pspec)) { - GTypeClass *class = g_type_class_ref(type); + /* GParamSpecFlags holds a ref on the class so we just peek. + */ + GFlagsClass *gflags = g_type_class_peek(type); GParamSpecFlags *pspec_flags = (GParamSpecFlags *) pspec; - GFlagsClass *gflags; int i; - /* Should be impossible, no need to warn. - */ - if (!class) - return; - - gflags = G_FLAGS_CLASS(class); + g_assert(gflags); vips_buf_appendf(buf, "\t\t\t"); vips_buf_appendf(buf, "%s", _("default flags")); vips_buf_appendf(buf, ": "); - vips__flags_to_str(buf, type, pspec_flags->default_value); + vips__flags_to_str(buf, gflags, pspec_flags->default_value); vips_buf_appendf(buf, "\n"); vips_buf_appendf(buf, "\t\t\t"); vips_buf_appendf(buf, "%s", _("allowed flags")); diff --git a/libvips/iofuncs/semaphore.c b/libvips/iofuncs/semaphore.c index aaf5dbf613..3b2995a01b 100644 --- a/libvips/iofuncs/semaphore.c +++ b/libvips/iofuncs/semaphore.c @@ -59,16 +59,14 @@ vips_semaphore_init(VipsSemaphore *s, int v, char *name) s->v = v; s->name = name; g_mutex_init(&s->mutex); - s->cond = g_new(GCond, 1); - g_cond_init(s->cond); + g_cond_init(&s->cond); } void vips_semaphore_destroy(VipsSemaphore *s) { g_mutex_clear(&s->mutex); - g_cond_clear(s->cond); - g_free(s->cond); + g_cond_clear(&s->cond); } /* Add n to the semaphore and signal any threads that are blocked waiting @@ -87,9 +85,9 @@ vips_semaphore_upn(VipsSemaphore *s, int n) * thread. If we are incrementing by a lot, we must wake all threads. */ if (n == 1) - g_cond_signal(s->cond); + g_cond_signal(&s->cond); else - g_cond_broadcast(s->cond); + g_cond_broadcast(&s->cond); g_mutex_unlock(&s->mutex); #ifdef DEBUG_IO @@ -124,8 +122,8 @@ vips__semaphore_downn_until(VipsSemaphore *s, int n, gint64 end_time) while (s->v < n) { if (end_time == -1) - vips__worker_cond_wait(s->cond, &s->mutex); - else if (!g_cond_wait_until(s->cond, &s->mutex, end_time)) { + vips__worker_cond_wait(&s->cond, &s->mutex); + else if (!g_cond_wait_until(&s->cond, &s->mutex, end_time)) { /* timeout has passed. */ g_mutex_unlock(&s->mutex); diff --git a/libvips/iofuncs/sinkscreen.c b/libvips/iofuncs/sinkscreen.c index 5e9ade24bc..65837fc5da 100644 --- a/libvips/iofuncs/sinkscreen.c +++ b/libvips/iofuncs/sinkscreen.c @@ -115,10 +115,10 @@ typedef struct _Render { /* Parameters. */ - VipsImage *in; /* Image we render */ - VipsImage *out; /* Write tiles here on demand */ - VipsImage *mask; /* Set valid pixels here */ - int tile_width; /* Tile size */ + VipsImage *in; /* Image we render */ + VipsImage *out; /* Write tiles here on demand */ + VipsImage *mask; /* Set valid pixels here */ + int tile_width; /* Tile size */ int tile_height; int max_tiles; /* Maximum number of tiles */ int priority; /* Larger numbers done sooner */ @@ -139,9 +139,9 @@ typedef struct _Render { /* Tile cache. */ - GSList *all; /* All our tiles */ - int ntiles; /* Number of tiles */ - int ticks; /* Inc. on each access ... used for LRU */ + GSList *all; /* All our tiles */ + int ntiles; /* Number of tiles */ + int ticks; /* Inc. on each access ... used for LRU */ /* List of dirty tiles. Most recent at the front. */ @@ -194,6 +194,18 @@ static VipsSemaphore n_render_dirty_sem; */ static gboolean render_reschedule = FALSE; +/* Set this GPrivate to link a thread back to its Render struct. + */ +static GPrivate render_worker_key; + +gboolean +vips__worker_exit(void) +{ + Render *render = (Render *) g_private_get(&render_worker_key); + + return render && render->shutdown; +} + static void render_thread_state_class_init(RenderThreadStateClass *class) { @@ -269,6 +281,7 @@ render_free(Render *render) #ifdef VIPS_DEBUG_AMBER render_num_renders -= 1; + printf("%d active renders\n", render_num_renders); #endif /*VIPS_DEBUG_AMBER*/ return 0; @@ -295,7 +308,7 @@ render_ref(Render *render) static int render_unref(Render *render) { - int kill; + gboolean kill; #if GLIB_CHECK_VERSION(2, 58, 0) g_assert(!g_atomic_ref_count_compare(&render->ref_count, 0)); @@ -426,10 +439,16 @@ render_work(VipsThreadState *state, void *a) VIPS_DEBUG_MSG("calculating tile %p %dx%d\n", tile, tile->area.left, tile->area.top); + /* vips__worker_exit() uses this to find the render to check for + * shutdown. + */ + g_private_set(&render_worker_key, render); + if (vips_region_prepare_to(state->reg, tile->region, &tile->area, tile->area.left, tile->area.top)) { VIPS_DEBUG_MSG_RED("render_work: vips_region_prepare_to() failed: %s\n", vips_error_buffer()); + g_private_set(&render_worker_key, NULL); return -1; } tile->painted = TRUE; @@ -438,6 +457,8 @@ render_work(VipsThreadState *state, void *a) render->notify) render->notify(render->out, &tile->area, render->a); + g_private_set(&render_worker_key, NULL); + return 0; } @@ -620,6 +641,7 @@ render_new(VipsImage *in, VipsImage *out, VipsImage *mask, #ifdef VIPS_DEBUG_AMBER render_num_renders += 1; + printf("%d active renders\n", render_num_renders); #endif /*VIPS_DEBUG_AMBER*/ return render; @@ -1094,6 +1116,11 @@ render_work_private(void *data, void *null) Render *render = (Render *) data; + /* vips__worker_exit() uses this to find the render to check for + * shutdown. + */ + g_private_set(&render_worker_key, render); + // this will quit on ->shutdown == TRUE if (vips_threadpool_run(render->in, render_thread_state_new, @@ -1103,6 +1130,8 @@ render_work_private(void *data, void *null) render)) VIPS_DEBUG_MSG_RED("render_work_private: threadpool_run failed\n"); + g_private_set(&render_worker_key, NULL); + render_unref(render); VIPS_DEBUG_MSG_AMBER("render_work_private: stop\n"); @@ -1144,11 +1173,11 @@ vips__sink_screen_once(void *data) * uchar image and has 255 for pixels which are currently in cache and 0 * for uncalculated pixels. * - * Renders with a positive priority are assumed to be large, gh-priority, + * Renders with a positive priority are assumed to be large, high-priority, * foreground images. Although there can be many of these, only one is ever * active, to avoid overcommitting threads. * - * Renders with a negative priority are assumed to be small, thumbnail images + * Renders with a negative priority are assumed to be small, thumbnail images, * consisting of a single tile. Single tile images are effectively * single-threaded, so all these renders are evaluated together. * diff --git a/libvips/iofuncs/threadpool.c b/libvips/iofuncs/threadpool.c index 4d9d687720..8d8bb60b0a 100644 --- a/libvips/iofuncs/threadpool.c +++ b/libvips/iofuncs/threadpool.c @@ -379,8 +379,6 @@ vips_thread_main_loop(void *a, void *b) VipsWorker *worker = (VipsWorker *) a; VipsThreadpool *pool = worker->pool; - g_assert(pool == worker->pool); - VIPS_GATE_START("vips_thread_main_loop: thread"); g_private_set(&worker_key, worker); @@ -481,9 +479,6 @@ vips_threadpool_wait(VipsThreadpool *pool) static void vips_threadpool_free(VipsThreadpool *pool) { - VIPS_DEBUG_MSG("vips_threadpool_free: \"%s\" (%p)\n", - pool->im->filename, pool); - vips_threadpool_wait(pool); g_mutex_clear(&pool->allocate_lock); @@ -532,9 +527,6 @@ vips_threadpool_new(VipsImage *im) */ pool->max_workers = vips_image_get_concurrency(im, pool->max_workers); - VIPS_DEBUG_MSG("vips_threadpool_new: \"%s\" (%p), with %d threads\n", - im->filename, pool, pool->max_workers); - return pool; } diff --git a/libvips/iofuncs/util.c b/libvips/iofuncs/util.c index 60fbf449aa..ed9f38cf3e 100644 --- a/libvips/iofuncs/util.c +++ b/libvips/iofuncs/util.c @@ -1765,9 +1765,15 @@ vips_enum_from_nick(const char *domain, GType type, const char *nick) if ((enum_value = g_enum_get_value_by_nick(genum, nick))) return enum_value->value; - /* -1 since we always have a "last" member. + /* Compat for "last" members. Assumes all enums define a `_LAST` value; + * behaviour is undefined otherwise. Note that there could be potential + * gaps in enum values (e.g. VipsInterpretation), so we cannot return + * `genum->n_values` directly. */ - for (i = 0; i < genum->n_values - 1; i++) { + if (nick && g_str_equal(nick, "last")) + return genum->values[genum->n_values - 1].value + 1; + + for (i = 0; i < genum->n_values; i++) { if (i > 0) vips_buf_appends(&buf, ", "); vips_buf_appends(&buf, genum->values[i].value_nick); diff --git a/libvips/iofuncs/vips.c b/libvips/iofuncs/vips.c index cd80f57c7c..001f859207 100644 --- a/libvips/iofuncs/vips.c +++ b/libvips/iofuncs/vips.c @@ -359,13 +359,11 @@ vips__read_header_bytes(VipsImage *im, unsigned char *from) */ value = g_enum_get_value(g_type_class_ref(VIPS_TYPE_INTERPRETATION), im->Type); - if (!value || - strcmp(value->value_nick, "last") == 0) + if (!value) im->Type = VIPS_INTERPRETATION_ERROR; value = g_enum_get_value(g_type_class_ref(VIPS_TYPE_CODING), im->Coding); - if (!value || - strcmp(value->value_nick, "last") == 0) + if (!value) im->Coding = VIPS_CODING_ERROR; /* Offset, Res, etc. don't affect vips file layout, just @@ -1024,8 +1022,7 @@ vips_image_open_input(VipsImage *image) VIPS_SIZEOF_HEADER || vips__read_header_bytes(image, header)) { vips_error_system(errno, "VipsImage", - _("unable to read header for \"%s\""), - image->filename); + _("unable to read header for \"%s\""), image->filename); return -1; } @@ -1050,8 +1047,7 @@ vips_image_open_input(VipsImage *image) * harmless. */ if (readhist(image)) { - g_warning("error reading vips image metadata: %s", - vips_error_buffer()); + g_warning("error reading vips image metadata: %s", vips_error_buffer()); vips_error_clear(); } diff --git a/libvips/iofuncs/window.c b/libvips/iofuncs/window.c index 216b83547d..5b62102f9f 100644 --- a/libvips/iofuncs/window.c +++ b/libvips/iofuncs/window.c @@ -4,6 +4,8 @@ * - from region.c * 19/3/09 * - block mmaps of nodata images + * 6/7/25 + * - use much larger mmap windows to limit scrolling */ /* @@ -66,14 +68,14 @@ */ int vips__read_test; -/* Add this many lines above and below the mmap() window. - */ -int vips__window_margin_pixels = VIPS__WINDOW_MARGIN_PIXELS; - -/* Always map at least this many bytes. There's no point making tiny windows - * on small files. +/* The window size we aim for. + * + * Large enough to hold the needed pixels, then expanded up to this size. 10MB + * on a 32-bit machine (since VMEM is limited), 10gb on a 64-bit machine (since + * the VMEM limit will be extremely large). */ -int vips__window_margin_bytes = VIPS__WINDOW_MARGIN_BYTES; +static gint64 vips__window_bytes = + (gint64) 1024 * 1024 * (sizeof(size_t) > 4 ? 10000 : 10); /* Track global mmap usage. */ @@ -343,8 +345,6 @@ vips_window_find(VipsImage *im, int top, int height) VipsWindow * vips_window_take(VipsWindow *window, VipsImage *im, int top, int height) { - int margin; - /* We have a window and it has the pixels we need. */ if (window && @@ -384,16 +384,25 @@ vips_window_take(VipsWindow *window, VipsImage *im, int top, int height) return window; } - /* We have to make a new window. Make it a bit bigger than strictly - * necessary. + /* Add a margin around our window to try to reduce window scrolling. + * + * This will be very large on 64-bit machines, but rather small on 32-bits. */ - margin = VIPS_MIN(vips__window_margin_pixels, - vips__window_margin_bytes / VIPS_IMAGE_SIZEOF_LINE(im)); - top -= margin; - height += margin * 2; + gint64 line_bytes = VIPS_IMAGE_SIZEOF_LINE(im); + gint64 window_bytes = height * line_bytes; + gint64 margin_bytes = + (VIPS_MAX(window_bytes, vips__window_bytes) - window_bytes) / 2; + gint64 margin_lines = VIPS_MAX(0, margin_bytes / line_bytes); + + top -= margin_lines; + height += margin_lines * 2; top = VIPS_CLIP(0, top, im->Ysize - 1); height = VIPS_CLIP(0, height, im->Ysize - top); +#ifdef DEBUG + printf("vips_window_take: top = %d, height = %d\n", top, height); +#endif /*DEBUG*/ + if (!(window = vips_window_new(im, top, height))) { g_mutex_unlock(&im->sslock); return NULL; diff --git a/libvips/module/magick.c b/libvips/module/magick.c index 19f33d22a4..37d43463b1 100644 --- a/libvips/module/magick.c +++ b/libvips/module/magick.c @@ -60,8 +60,10 @@ g_module_check_init(GModule *module) extern GType vips_foreign_load_magick_file_get_type(void); extern GType vips_foreign_load_magick_buffer_get_type(void); + extern GType vips_foreign_load_magick_source_get_type(void); extern GType vips_foreign_load_magick7_file_get_type(void); extern GType vips_foreign_load_magick7_buffer_get_type(void); + extern GType vips_foreign_load_magick7_source_get_type(void); extern GType vips_foreign_save_magick_file_get_type(void); extern GType vips_foreign_save_magick_bmp_file_get_type(void); extern GType vips_foreign_save_magick_buffer_get_type(void); @@ -71,11 +73,13 @@ g_module_check_init(GModule *module) #ifdef HAVE_MAGICK6 vips_foreign_load_magick_file_get_type(); vips_foreign_load_magick_buffer_get_type(); + vips_foreign_load_magick_source_get_type(); #endif /*HAVE_MAGICK6*/ #ifdef HAVE_MAGICK7 vips_foreign_load_magick7_file_get_type(); vips_foreign_load_magick7_buffer_get_type(); + vips_foreign_load_magick7_source_get_type(); #endif /*HAVE_MAGICK7*/ #endif /*ENABLE_MAGICKLOAD*/ diff --git a/libvips/morphology/morphology.c b/libvips/morphology/morphology.c index 7704cd5e18..92b51cc9d0 100644 --- a/libvips/morphology/morphology.c +++ b/libvips/morphology/morphology.c @@ -57,6 +57,7 @@ vips_morphology_class_init(VipsMorphologyClass *class) { GObjectClass *gobject_class = G_OBJECT_CLASS(class); VipsObjectClass *vobject_class = VIPS_OBJECT_CLASS(class); + VipsOperationClass *operation_class = VIPS_OPERATION_CLASS(class); gobject_class->set_property = vips_object_set_property; gobject_class->get_property = vips_object_get_property; @@ -64,6 +65,8 @@ vips_morphology_class_init(VipsMorphologyClass *class) vobject_class->nickname = "morphology"; vobject_class->description = _("morphological operations"); + operation_class->flags = VIPS_OPERATION_SEQUENTIAL; + /* Inputs set by subclassess. */ diff --git a/libvips/morphology/rank.c b/libvips/morphology/rank.c index 430b21580f..94f2e189aa 100644 --- a/libvips/morphology/rank.c +++ b/libvips/morphology/rank.c @@ -115,12 +115,9 @@ vips_rank_stop(void *vseq, void *a, void *b) VIPS_FREE(seq->sort); if (seq->hist && - in) { - int i; - - for (i = 0; i < in->Bands; i++) + in) + for (int i = 0; i < in->Bands; i++) VIPS_FREE(seq->hist[i]); - } VIPS_FREE(seq->hist); return 0; @@ -147,17 +144,13 @@ vips_rank_start(VipsImage *out, void *a, void *b) } if (rank->hist_path) { - int i; - - if (!(seq->hist = - VIPS_ARRAY(NULL, in->Bands, unsigned int *))) { + if (!(seq->hist = VIPS_ARRAY(NULL, in->Bands, unsigned int *))) { vips_rank_stop(seq, in, rank); return NULL; } - for (i = 0; i < in->Bands; i++) - if (!(seq->hist[i] = - VIPS_ARRAY(NULL, 256, unsigned int))) { + for (int i = 0; i < in->Bands; i++) + if (!(seq->hist[i] = VIPS_ARRAY(NULL, 256, unsigned int))) { vips_rank_stop(seq, in, rank); return NULL; } @@ -175,29 +168,26 @@ vips_rank_generate_uchar(VipsRegion *out_region, VipsImage *in = seq->ir->im; VipsRect *r = &out_region->valid; const int bands = in->Bands; - const int last = bands * (rank->width - 1); + const int lsk = VIPS_REGION_LSKIP(seq->ir); /* Get input and output pointers for this line. */ - VipsPel *restrict p = - VIPS_REGION_ADDR(seq->ir, r->left, r->top + y); - VipsPel *restrict q = - VIPS_REGION_ADDR(out_region, r->left, r->top + y); + VipsPel *restrict p = VIPS_REGION_ADDR(seq->ir, r->left, r->top + y); + VipsPel *restrict q = VIPS_REGION_ADDR(out_region, r->left, r->top + y); VipsPel *restrict p1; - int lsk; - int x, i, j, b; - - lsk = VIPS_REGION_LSKIP(seq->ir); /* Find histogram for the first output pixel. */ - for (b = 0; b < bands; b++) + for (int b = 0; b < bands; b++) memset(seq->hist[b], 0, 256 * sizeof(unsigned int)); p1 = p; - for (j = 0; j < rank->height; j++) { - for (i = 0, x = 0; x < rank->width; x++) - for (b = 0; b < bands; b++, i++) + for (int j = 0; j < rank->height; j++) { + int i; + + i = 0; + for (int x = 0; x < rank->width; x++) + for (int b = 0; b < bands; b++, i++) seq->hist[b][p1[i]] += 1; p1 += lsk; @@ -205,32 +195,31 @@ vips_rank_generate_uchar(VipsRegion *out_region, /* Loop for output pels. */ - for (x = 0; x < r->width; x++) { - for (b = 0; b < bands; b++) { + for (int x = 0; x < r->width; x++) { + for (int b = 0; b < bands; b++) { /* Calculate cumulative histogram -- the value is the * index at which we pass the rank. */ unsigned int *restrict hist = seq->hist[b]; int sum; - int i; - + int value; sum = 0; - for (i = 0; i < 256; i++) { - sum += hist[i]; + for (value = 0; value < 256; value++) { + sum += hist[value]; if (sum > rank->index) break; } - q[b] = i; + q[b] = value; - /* Adapt histogram -- remove the pels from - * the left hand column, add in pels for a - * new right-hand column. + /* Adapt histogram -- remove the pels from the left hand column, + * add in pels for a new right-hand column. */ + const int next = bands * rank->width; p1 = p + b; - for (j = 0; j < rank->height; j++) { + for (int j = 0; j < rank->height; j++) { hist[p1[0]] -= 1; - hist[p1[last]] += 1; + hist[p1[next]] += 1; p1 += lsk; } @@ -437,7 +426,7 @@ vips_rank_generate(VipsRegion *out_region, VipsRect s; int ls; - int x, y; + int x; int i, j, k; int upper, lower, mid; @@ -451,7 +440,7 @@ vips_rank_generate(VipsRegion *out_region, return -1; ls = VIPS_REGION_LSKIP(ir) / VIPS_IMAGE_SIZEOF_ELEMENT(in); - for (y = 0; y < r->height; y++) { + for (int y = 0; y < r->height; y++) { if (rank->hist_path) vips_rank_generate_uchar(out_region, seq, rank, y); else if (rank->index == 0) @@ -492,7 +481,8 @@ vips_rank_build(VipsObject *object) return -1; } rank->n = rank->width * rank->height; - if (rank->index < 0 || rank->index > rank->n - 1) { + if (rank->index < 0 || + rank->index > rank->n - 1) { vips_error(class->nickname, "%s", _("index out of range")); return -1; } diff --git a/libvips/resample/reduce.c b/libvips/resample/reduce.c index d45c7258e4..fc5f8b80b1 100644 --- a/libvips/resample/reduce.c +++ b/libvips/resample/reduce.c @@ -60,14 +60,14 @@ /** * VipsKernel: - * @VIPS_KERNEL_NEAREST: The nearest pixel to the point. - * @VIPS_KERNEL_LINEAR: Convolve with a triangle filter. - * @VIPS_KERNEL_CUBIC: Convolve with a cubic filter. - * @VIPS_KERNEL_MITCHELL: Convolve with a Mitchell kernel. - * @VIPS_KERNEL_LANCZOS2: Convolve with a two-lobe Lanczos kernel. - * @VIPS_KERNEL_LANCZOS3: Convolve with a three-lobe Lanczos kernel. - * @VIPS_KERNEL_MKS2013: Convolve with Magic Kernel Sharp 2013. - * @VIPS_KERNEL_MKS2021: Convolve with Magic Kernel Sharp 2021. + * @VIPS_KERNEL_NEAREST: the nearest pixel to the point + * @VIPS_KERNEL_LINEAR: convolve with a triangle filter + * @VIPS_KERNEL_CUBIC: convolve with a cubic filter + * @VIPS_KERNEL_MITCHELL: convolve with a Mitchell kernel + * @VIPS_KERNEL_LANCZOS2: convolve with a two-lobe Lanczos kernel + * @VIPS_KERNEL_LANCZOS3: convolve with a three-lobe Lanczos kernel + * @VIPS_KERNEL_MKS2013: convolve with Magic Kernel Sharp 2013 + * @VIPS_KERNEL_MKS2021: convolve with Magic Kernel Sharp 2021 * * The resampling kernels vips supports. See [method@Image.reduce], for example. */ diff --git a/meson.build b/meson.build index 470788e7cf..a6a93a1e0f 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('vips', 'c', 'cpp', - version: '8.17.0', + version: '8.18.0', meson_version: '>=0.55', default_options: [ # this is what glib uses (one of our required deps), so we use it too @@ -24,8 +24,8 @@ version_patch = version_parts[2] # binary interface changes backwards compatible?: increment age # binary interface changes not backwards compatible?: reset age to 0 library_revision = 0 -library_current = 61 -library_age = 19 +library_current = 62 +library_age = 20 library_version = '@0@.@1@.@2@'.format(library_current - library_age, library_age, library_revision) darwin_versions = [library_current + 1, '@0@.@1@'.format(library_current + 1, library_revision)] @@ -388,10 +388,8 @@ if librsvg_found external_deps += librsvg_dep external_deps += cairo_dep cfg_var.set('HAVE_RSVG', true) - # rsvg_handle_set_stylesheet added in librsvg 2.48.0 - cfg_var.set('HAVE_RSVG_HANDLE_SET_STYLESHEET', cc.has_function('rsvg_handle_set_stylesheet', dependencies: librsvg_dep)) # CAIRO_FORMAT_RGBA128F added in cairo 1.17.2 - cfg_var.set('HAVE_CAIRO_FORMAT_RGBA128F', cc.has_type('CAIRO_FORMAT_RGBA128F', dependencies: cairo_dep, prefix: '#include ')) + cfg_var.set('HAVE_CAIRO_FORMAT_RGBA128F', cc.has_type('CAIRO_FORMAT_RGBA128F', prefix: '#include ', dependencies: cairo_dep)) endif openslide_dep = dependency('openslide', version: '>=3.4.0', required: get_option('openslide')) @@ -405,8 +403,8 @@ if openslide_dep.found() external_deps += openslide_dep endif cfg_var.set('HAVE_OPENSLIDE', true) - cfg_var.set('HAVE_OPENSLIDE_ICC', cc.has_function('openslide_get_icc_profile_size', dependencies: openslide_dep)) - cfg_var.set('HAVE_OPENSLIDE_CACHE_CREATE', cc.has_function('openslide_cache_create', dependencies: openslide_dep)) + cfg_var.set('HAVE_OPENSLIDE_ICC', cc.has_function('openslide_get_icc_profile_size', prefix: '#include ', dependencies: openslide_dep)) + cfg_var.set('HAVE_OPENSLIDE_CACHE_CREATE', cc.has_function('openslide_cache_create', prefix: '#include ', dependencies: openslide_dep)) endif matio_dep = dependency('matio', required: get_option('matio')) @@ -429,6 +427,12 @@ if openexr_dep.found() cfg_var.set('HAVE_OPENEXR', true) endif +libraw_dep = dependency('libraw_r', required: get_option('raw')) +if libraw_dep.found() + external_deps += libraw_dep + cfg_var.set('HAVE_LIBRAW', true) +endif + # 2.4 is the first one to have working threading and tiling libopenjp2_dep = dependency('libopenjp2', version: '>=2.4', required: get_option('openjpeg')) if libopenjp2_dep.found() @@ -696,6 +700,7 @@ build_features = { 'NIfTI load/save': ['libnifti', libnifti_found ? libnifti_dep : disabler()], 'FITS load/save': ['cfitsio', cfitsio_dep], 'GIF save': ['cgif', cgif_dep], + 'RAW load': ['libraw', libraw_dep], 'Magick @0@'.format('/'.join(get_option('magick-features'))): [get_option('magick-package'), magick_found ? magick_dep : disabler(), magick_module], }, } diff --git a/meson_options.txt b/meson_options.txt index 175a46c5e6..2de9efc281 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -203,6 +203,11 @@ option('quantizr', value: 'auto', description: 'Build with quantizr') +option('raw', + type: 'feature', + value: 'auto', + description: 'Build with libraw') + option('rsvg', type: 'feature', value: 'auto', diff --git a/test/test-suite/helpers/helpers.py b/test/test-suite/helpers/helpers.py index 82e318d4a2..8f98c12be6 100644 --- a/test/test-suite/helpers/helpers.py +++ b/test/test-suite/helpers/helpers.py @@ -33,6 +33,7 @@ FITS_FILE = os.path.join(IMAGES, "WFPC2u5780205r_c0fx.fits") OPENSLIDE_FILE = os.path.join(IMAGES, "CMU-1-Small-Region.svs") PDF_FILE = os.path.join(IMAGES, "ISO_12233-reschart.pdf") +PDF_PAGE_BOX_FILE = os.path.join(IMAGES, "page-box.pdf") CMYK_PDF_FILE = os.path.join(IMAGES, "cmyktest.pdf") SVG_FILE = os.path.join(IMAGES, "logo.svg") SVGZ_FILE = os.path.join(IMAGES, "logo.svgz") diff --git a/test/test-suite/images/page-box.pdf b/test/test-suite/images/page-box.pdf new file mode 100644 index 0000000000..0f2e93282a Binary files /dev/null and b/test/test-suite/images/page-box.pdf differ diff --git a/test/test-suite/test_foreign.py b/test/test-suite/test_foreign.py index b8822ebada..1cef2b44b5 100644 --- a/test/test-suite/test_foreign.py +++ b/test/test-suite/test_foreign.py @@ -509,16 +509,24 @@ def png_indexed_valid(im): assert (self.rgba - after).abs().max() == 0 # we should be able to save an 8-bit image as a 16-bit PNG - rgb = pyvips.Image.new_from_file(JPEG_FILE) - data = rgb.pngsave_buffer(bitdepth=16) + data = self.colour.pngsave_buffer(bitdepth=16) rgb16 = pyvips.Image.pngload_buffer(data) assert rgb16.format == "ushort" - # we should be able to save a 16-bit image as a 8-bit PNG + # we should be able to save a 16-bit image as an 8-bit PNG data = rgb16.pngsave_buffer(bitdepth=8) rgb = pyvips.Image.pngload_buffer(data) assert rgb.format == "uchar" + # we should be able to save a 16-bit image as an 8-bit WebP + if have("webpsave"): + data = rgb16.webpsave_buffer(lossless=True) + rgb = pyvips.Image.webpload_buffer(data) + assert rgb.format == "uchar" + # ... and check if it was correctly shifted down + # https://github.com/libvips/libvips/issues/4568 + assert (self.colour - rgb).abs().max() == 0 + @skip_if_no("tiffload") def test_tiff(self): def tiff_valid(im): @@ -798,6 +806,9 @@ def bmp_valid(im): self.file_loader("magickload", BMP_FILE, bmp_valid) self.buffer_loader("magickload_buffer", BMP_FILE, bmp_valid) + source = pyvips.Source.new_from_file(BMP_FILE) + x = pyvips.Image.new_from_source(source, "") + bmp_valid(x) # we should have rgb or rgba for svg files ... different versions of # IM handle this differently. GM even gives 1 band. @@ -869,11 +880,12 @@ def bmp_valid(im): assert im.width == 433 assert im.height == 433 - # load should see metadata like eg. icc profiles im = pyvips.Image.magickload(JPEG_FILE) assert len(im.get("icc-profile-data")) == 564 + im = pyvips.Image.magickload(JPEG_FILE) + # added in 8.7 @skip_if_no("magicksave") def test_magicksave(self): @@ -1088,6 +1100,46 @@ def pdf_valid(im): assert abs(im.width * 2 - x.width) < 2 assert abs(im.height * 2 - x.height) < 2 + im = pyvips.Image.new_from_file(PDF_PAGE_BOX_FILE) + assert im.width == 709 + assert im.height == 955 + assert im.get("pdf-creator") == "Adobe InDesign 20.4 (Windows)" + assert im.get("pdf-producer") == "Adobe PDF Library 17.0" + + pdfloadOp = pyvips.Operation.new_from_name("pdfload").get_description() + + if "poppler" in pdfloadOp: + # only crop is implemented, ignore requested page box + im = pyvips.Image.new_from_file(PDF_FILE, page_box="art") + assert im.width == 1134 + assert im.height == 680 + im = pyvips.Image.new_from_file(PDF_PAGE_BOX_FILE, page_box="art") + assert im.width == 709 + assert im.height == 955 + + if "pdfium" in pdfloadOp: + im = pyvips.Image.new_from_file(PDF_FILE, page_box="art") + assert im.width == 1121 + assert im.height == 680 + im = pyvips.Image.new_from_file(PDF_FILE, page_box="trim") # missing, will fallback to crop + assert im.width == 1134 + assert im.height == 680 + im = pyvips.Image.new_from_file(PDF_PAGE_BOX_FILE, page_box="media") + assert im.width == 822 + assert im.height == 1069 + im = pyvips.Image.new_from_file(PDF_PAGE_BOX_FILE, page_box="crop") + assert im.width == 709 + assert im.height == 955 + im = pyvips.Image.new_from_file(PDF_PAGE_BOX_FILE, page_box="bleed") + assert im.width == 652 + assert im.height == 899 + im = pyvips.Image.new_from_file(PDF_PAGE_BOX_FILE, page_box="trim") + assert im.width == 595 + assert im.height == 842 + im = pyvips.Image.new_from_file(PDF_PAGE_BOX_FILE, page_box="art") + assert im.width == 539 + assert im.height == 785 + @skip_if_no("gifload") def test_gifload(self): def gif_valid(im): @@ -1399,6 +1451,27 @@ def test_dzsave(self): assert x.width == 256 assert x.height == 256 + # IIIF v2 + im = pyvips.Image.black(3509, 2506, bands=3) + filename = temp_filename(self.tempdir, '') + im.dzsave(filename, layout="iiif") + assert os.path.exists(filename + "/info.json") + assert os.path.exists(filename + "/0,0,512,512/512,/0/default.jpg") + assert os.path.exists(filename + "/2560,2048,512,458/512,/0/default.jpg") + x = pyvips.Image.new_from_file(filename + "/full/439,/0/default.jpg") + assert x.width == 439 + assert x.height == 314 + + # IIIF v3 + filename = temp_filename(self.tempdir, '') + im.dzsave(filename, layout="iiif3") + assert os.path.exists(filename + "/info.json") + assert os.path.exists(filename + "/0,0,512,512/512,512/0/default.jpg") + assert os.path.exists(filename + "/2560,2048,512,458/512,458/0/default.jpg") + x = pyvips.Image.new_from_file(filename + "/full/439,314/0/default.jpg") + assert x.width == 439 + assert x.height == 314 + # test zip output filename = temp_filename(self.tempdir, '.zip') self.colour.dzsave(filename) diff --git a/tools/vips.c b/tools/vips.c index a8fa16c43d..5ea7200e85 100644 --- a/tools/vips.c +++ b/tools/vips.c @@ -216,23 +216,17 @@ list_operation_arg(VipsObjectClass *object_class, /* These are the pspecs that vips uses that have interesting values. */ if (G_IS_PARAM_SPEC_ENUM(pspec)) { - GTypeClass *class = g_type_class_ref(type); + /* GParamSpecEnum holds a ref on the class so we just peek. + */ + GEnumClass *genum = g_type_class_peek(type); - GEnumClass *genum; int i; - /* Should be impossible, no need to warn. - */ - if (!class) - return NULL; - - genum = G_ENUM_CLASS(class); + g_assert(genum); printf("word:"); - /* -1 since we always have a "last" member. - */ - for (i = 0; i < genum->n_values - 1; i++) { + for (i = 0; i < genum->n_values; i++) { if (i > 0) printf("|"); printf("%s", genum->values[i].value_nick);