diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..c11c6bfe --- /dev/null +++ b/.editorconfig @@ -0,0 +1,23 @@ +# http://editorconfig.org + +[*] +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = false +insert_final_newline = true + +[*.{cpp,c,h}] +indent_style = tab +indent_size = 8 + +[*.py] +indent_style = space +indent_size = 4 + +[Makefile.am] +indent_style = tab +indent_size = 8 + +[configure.ac] +indent_style = space +indent_size = 2 diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..c7fe901e --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +open_collective: libvips diff --git a/.gitignore b/.gitignore index c7101f3a..40dc96a5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ -.pytest_cache compile .pytest_cache a.out diff --git a/.travis.yml b/.travis.yml index 83d301af..23bd711d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,118 +1,50 @@ language: cpp -before_script: - - $PYTHON -m pip download --no-deps https://github.com/libvips/pyvips/archive/$PYVIPS_VERSION.tar.gz - - tar xf $PYVIPS_VERSION.tar.gz - - $PYTHON -m pip install --user --upgrade pyvips-$PYVIPS_VERSION/[test] - - ./autogen.sh - --disable-dependency-tracking - --with-jpeg-includes=$JPEG/include - --with-jpeg-libraries=$JPEG/lib - --with-magick=$WITH_MAGICK - - make -j$JOBS -s -script: - - make -j$JOBS -s -k V=0 VERBOSE=1 check - - LD_LIBRARY_PATH=$PWD/libvips/.libs - DYLD_LIBRARY_PATH=$PWD/libvips/.libs - LD_PRELOAD=$ASAN_DSO - $PYTHON -m pytest -v test/test-suite - -matrix: - allow_failures: - - os: osx - fast_finish: true - include: - - os: linux - sudo: required - dist: xenial - compiler: gcc - env: - - PYTHON=python2 - - PYVIPS_VERSION=master - - JPEG=/usr - - JOBS=`nproc` - - WITH_MAGICK=yes - cache: ccache - - - os: linux - sudo: required - dist: xenial - compiler: clang - env: - - PYTHON=python2 - - PYVIPS_VERSION=master - - JPEG=/usr - - JOBS=`nproc` - - WITH_MAGICK=no - - CFLAGS="-fsanitize=address,undefined -fno-omit-frame-pointer -fopenmp -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION" - - LDFLAGS="-fsanitize=address,undefined -dynamic-asan -fopenmp=libiomp5" - - ASAN_DSO=/usr/local/clang-7.0.0/lib/clang/7.0.0/lib/linux/libclang_rt.asan-x86_64.so - - LSAN_OPTIONS="suppressions=$TRAVIS_BUILD_DIR/suppressions/lsan.supp" - - UBSAN_OPTIONS="suppressions=$TRAVIS_BUILD_DIR/suppressions/ubsan.supp" - # comment these out, I get strange parse errors from asan for some - # reason - # - # ASAN_OPTIONS="suppressions=$TRAVIS_BUILD_DIR/suppressions/asan.supp" - install: - # add support for WebP - - wget http://archive.ubuntu.com/ubuntu/pool/main/libw/libwebp/libwebp-dev_0.6.1-2_amd64.deb - - wget http://archive.ubuntu.com/ubuntu/pool/main/libw/libwebp/libwebpdemux2_0.6.1-2_amd64.deb - - wget http://archive.ubuntu.com/ubuntu/pool/main/libw/libwebp/libwebpmux3_0.6.1-2_amd64.deb - - wget http://archive.ubuntu.com/ubuntu/pool/main/libw/libwebp/libwebp6_0.6.1-2_amd64.deb - - sudo dpkg -i *.deb - cache: ccache - - - os: osx - osx_image: xcode10.1 - env: - - PYTHON=python2 - - PYVIPS_VERSION=master - - JPEG=/usr/local - - JOBS="`sysctl -n hw.ncpu`" - - WITH_MAGICK=yes - - PATH="/usr/local/opt/ccache/libexec:$PATH" - - HOMEBREW_NO_AUTO_UPDATE=1 - cache: ccache +env: + global: + - PYTHON=python3 + - PYVIPS_VERSION=master addons: apt: update: true - sources: + sources: &common_sources # use a more recent imagemagick instead of 6.8.9-9 - - sourceline: 'ppa:opencpu/imagemagick' + - sourceline: 'ppa:cran/imagemagick' # add support for HEIF files - sourceline: 'ppa:strukturag/libheif' - sourceline: 'ppa:strukturag/libde265' - packages: - - automake - - gtk-doc-tools + packages: &common_packages + - automake + - gtk-doc-tools - gobject-introspection - - libfftw3-dev - - libexif-dev + - python3-pip + - python3-setuptools + - python3-wheel + - libfftw3-dev + - libexif-dev - libjpeg-turbo8-dev - - libpng12-dev + - libpng12-dev - libwebp-dev # missing on xenial, unfortunately # - libwebpmux2 - - libtiff5-dev + - libtiff5-dev - libheif-dev - libexpat1-dev - - libmagick++-dev - - bc + - libmagick++-dev - libcfitsio3-dev - - libgsl0-dev + - libgsl0-dev - libmatio-dev - liborc-0.4-dev - liblcms2-dev - libpoppler-glib-dev - - librsvg2-dev + - librsvg2-dev - libgif-dev - libopenexr-dev - - libpango1.0-dev - - libgsf-1-dev + - libpango1.0-dev + - libgsf-1-dev - libopenslide-dev - libffi-dev - - libiomp-dev homebrew: packages: - ccache @@ -120,6 +52,7 @@ addons: - gobject-introspection - fftw - libexif + - libjpeg-turbo - webp - imagemagick - cfitsio @@ -133,3 +66,91 @@ addons: - pango - libgsf - openslide + +jobs: + allow_failures: + - os: osx + fast_finish: true + include: + - os: linux + sudo: required + dist: xenial + compiler: gcc + env: + - JPEG=/usr + - JOBS=`nproc` + - WITH_MAGICK=yes + cache: ccache + - os: linux + sudo: required + dist: xenial + compiler: clang + addons: + apt: + sources: + - *common_sources + - sourceline: deb https://apt.llvm.org/xenial/ llvm-toolchain-xenial-10 main + key_url: https://apt.llvm.org/llvm-snapshot.gpg.key + packages: + - *common_packages + - clang-10 + - libomp-10-dev + env: + - JPEG=/usr + - JOBS=`nproc` + - WITH_MAGICK=no + - LDSHARED="clang -shared" + - CFLAGS="-fsanitize=address,undefined -fno-omit-frame-pointer -fopenmp -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION" + - LDFLAGS="-fsanitize=address,undefined -shared-libasan -fopenmp=libomp" + - ASAN_DSO=`clang-10 -print-file-name=libclang_rt.asan-x86_64.so` + - ASAN_SYMBOLIZER_PATH=`which llvm-symbolizer-10` + - ASAN_OPTIONS="suppressions=$TRAVIS_BUILD_DIR/suppressions/asan.supp" + - LSAN_OPTIONS="suppressions=$TRAVIS_BUILD_DIR/suppressions/lsan.supp" + - UBSAN_OPTIONS="suppressions=$TRAVIS_BUILD_DIR/suppressions/ubsan.supp" + - LD_LIBRARY_PATH="`dirname $ASAN_DSO`:/usr/lib/llvm-10/lib:$LD_LIBRARY_PATH" + - DLCLOSE_PRELOAD="$TRAVIS_BUILD_DIR/dlclose.so" + install: + # add support for WebP + - wget http://archive.ubuntu.com/ubuntu/pool/main/libw/libwebp/libwebp-dev_0.6.1-2_amd64.deb + - wget http://archive.ubuntu.com/ubuntu/pool/main/libw/libwebp/libwebpdemux2_0.6.1-2_amd64.deb + - wget http://archive.ubuntu.com/ubuntu/pool/main/libw/libwebp/libwebpmux3_0.6.1-2_amd64.deb + - wget http://archive.ubuntu.com/ubuntu/pool/main/libw/libwebp/libwebp6_0.6.1-2_amd64.deb + - sudo dpkg -i *.deb + # switch to Clang 10 using update-alternatives + - sudo update-alternatives --install /usr/bin/clang clang /usr/bin/clang-10 100 + --slave /usr/bin/clang++ clang++ /usr/bin/clang++-10 + # workaround for https://github.com/google/sanitizers/issues/89 + # otherwise libIlmImf-2_2.so ends up as + - echo -e '#include \nint dlclose(void*handle){return 0;}' | clang -shared -xc -odlclose.so - + cache: ccache + - os: osx + osx_image: xcode11 + env: + - JPEG=/usr/local/opt/jpeg-turbo + - JOBS="`sysctl -n hw.ncpu`" + - WITH_MAGICK=yes + - PATH="/usr/local/opt/ccache/libexec:$PATH" + - PKG_CONFIG_PATH="/usr/local/opt/jpeg-turbo/lib/pkgconfig:/usr/local/opt/libxml2/lib/pkgconfig:$PKG_CONFIG_PATH" + - HOMEBREW_NO_AUTO_UPDATE=1 + - CC="gcc-9" + - CXX="g++-9" + cache: ccache + +before_script: + - $PYTHON -m pip download --no-deps https://github.com/libvips/pyvips/archive/$PYVIPS_VERSION.tar.gz + - tar xf $PYVIPS_VERSION.tar.gz + - $PYTHON -m pip install --user --upgrade pyvips-$PYVIPS_VERSION/[test] + - ./autogen.sh + --disable-dependency-tracking + --disable-deprecated + --with-jpeg-includes=$JPEG/include + --with-jpeg-libraries=$JPEG/lib + --with-magick=$WITH_MAGICK + - make -j$JOBS -s + +script: + - make -j$JOBS -s -k V=0 VERBOSE=1 check + - LD_LIBRARY_PATH=$PWD/libvips/.libs + DYLD_LIBRARY_PATH=$PWD/libvips/.libs + LD_PRELOAD="$ASAN_DSO $DLCLOSE_PRELOAD" + $PYTHON -m pytest -sv --log-cli-level=WARNING test/test-suite diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..8d6f0de4 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,66 @@ +libvips Code of Conduct +======================= + +libvips, is developed and maintained by a mixed group of professionals and +volunteers from all over the world. + +We ask people to adhere to a few ground rules. They apply equally to founders, +maintainers, contributors and those seeking help and guidance. + +This is not meant to be an exhaustive list of things you are not allowed to do. +We rather would like you to think of it as a guide to enrich our community and +the technical community in general with new knowledge and perspectives by +allowing everyone to participate. + +This code of conduct applies to all spaces managed by the libvips +community. This includes the mailing list, our GitHub projects, face to face +events, and any other forums created by the community for communication +within the community. In addition, violations of this code outside these +spaces may also affect a person's ability to participate within them. + +If you believe someone is violating the code of conduct, we ask that you +report it. + +- **Be friendly and patient.** +- **Be welcoming.** We strive to be a community that welcomes and supports + people of all backgrounds. +- **Be considerate.** Your work will be used by other people, and you in turn + will depend on the work of others. Any decision you take will affect users + and colleagues, and you should take those consequences into account when + making decisions. Remember that we're a world-wide community, so you might + not be communicating in someone else's primary language. +- **Be respectful.** Not all of us will agree all the time, but disagreement + is no excuse for poor behavior and poor manners. We might all experience + some frustration now and then, but we cannot allow that frustration to turn + into a personal attack. It’s important to remember that a community where + people feel uncomfortable or threatened is not a productive one. Members of + our community should be respectful when dealing with other members as + well as with people outside the our community. +- **Be careful in the words that you choose.** We are a community of + professionals, and we conduct ourselves professionally. Be kind to others. + Do not insult or put down other participants. Harassment and other + exclusionary behavior aren't acceptable. This includes, but is not limited + to: + - Violent threats or language directed against another person. + - Discriminatory jokes and language. + - Posting sexually explicit or violent material. + - Posting (or threatening to post) other people's personally identifying + information ("doxing"). + - Personal insults, especially those using racist or sexist terms. + - Unwelcome sexual attention. + - Advocating for, or encouraging, any of the above behavior. + - Repeated harassment of others. In general, if someone asks you to stop, + then stop. +- **When we disagree, try to understand why.** Disagreements, both social and + technical, happen all the time. It is important that we resolve + disagreements and differing views constructively. Remember that we’re + different. The strength of group software development comes from its + varied community, people from a wide range of backgrounds. Different + people have different perspectives on issues. Being unable to understand + why someone holds a viewpoint doesn’t mean that they’re wrong. Don’t + forget we all make mistakes and blaming each other doesn’t get us + anywhere. Instead, focus on helping to resolve issues and learning + from mistakes. + +Text based on the Code of Conduct of the [Django +community](https://www.djangoproject.com/conduct/). diff --git a/ChangeLog b/ChangeLog index 634d4f8a..96bed143 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,68 @@ +24/1/20 started 8.10.0 +- more conformat IIIF output from dzsave [regisrob] +- add @id to dzsave to set IIIF id property [regisrob] +- add max and min to region shrink [rgluskin] +- allow \ as an escape character in vips_break_token() [akemrir] +- tiffsave has a "depth" param to set max pyr depth +- libtiff LOGLUV images load and save as libvips XYZ +- add gifload_source, csvload_source, csvsave_target, matrixload_source, + matrixsave_source, pdfload_source, heifload_source, heifsave_target, + ppmload_source, ppmsave_target +- revise vipsthumbnail flags +- add VIPS_LEAK env var +- add vips_pipe_read_limit_set(), --vips-pipe-read-limit, + VIPS_PIPE_READ_LIMIT +- revise gifload to fix BACKGROUND and PREVIOUS dispose [alon-ne] +- add subsample_mode, deprecate no_subsample in jpegsave [Elad-Laufer] +- add vips_isdirf() +- add PAGENUMBER support to tiff write [jclavoie-jive] +- add "all" mode to smartcrop +- flood fill could stop half-way for some very complex shapes +- better handling of unaligned reads in multipage tiffs [petoor] +- add experimental libspng reader +- mark old --delete option to vipsthumbnail as deprecated [UweOhse] +- png save with a bad ICC profile just gives a warning +- add "premultipled" option to vips_affine(), clarified vips_resize() + behaviour with alpha channels +- improve bioformats support with read and write of tiff subifd pyramids +- thumbnail exploits subifd pyramids +- handle all EXIF orientation cases, deprecate + vips_autorot_get_angle() [Elad-Laufer] +- load PNGs with libspng, if possible +- deprecate heifload autorotate -- it's now always on +- revised resize improves accuracy [kleisauke] +- add --vips-config flag to show configuration info +- add "bitdepth" param to tiff save, deprecate "squash" [MathemanFlo] +- tiff load and save now supports 2 and 4 bit data [MathemanFlo] +- pngsave @bitdepth parameter lets you write 1, 2 and 4 bit PNGs +- ppmsave also uses "bitdepth" now, for consistency +- reduce operation cache max to 100 +- rework the final bits of vips7 for vips8 [kleisauke] +- --disable-deprecated now works [kleisauke] +- vipsheader allows "stdin" as a filename + +24/4/20 started 8.9.3 +- better iiif tile naming [IllyaMoskvin] + +31/1/19 started 8.9.2 +- fix a deadlock with --vips-leak [DarthSim] +- better gifload behaviour for DISPOSAL_UNSPECIFIED [DarthSim] +- ban ppm max_value < 0 +- add fuzz corpus to dist +- detect read errors correctly in source_sniff +- fix regression in autorot [malomalo] +- thumbnail on HEIC images could select the thumbnail incorrectly under some + size modes [ZorinArsenij] + +20/6/19 started 8.9.1 +- don't use the new source loaders for new_from_file or new_from_buffer, it + will break the loader priority system +- fix thumbnail autorot [janko] +- fix a warning with magicksave with no delay array [chregu] +- fix a race in tiled tiff load [kleisauke] +- better imagemagick init [LebronCurry] +- lock for metadata changes [jcupitt] + 20/6/19 started 8.9.0 - add vips_image_get/set_array_int() - disable webp alpha output if all frame fill the canvas and are solid @@ -16,6 +81,21 @@ - add @interpretation and @format to rawload - nifti load/save uses double for all floating point metadata - add vips_error_buffer_copy() +- add VipsSource and VipsTarget: a universal IO class for loaders and savers +- jpeg, png, tiff (though not tiffsave), rad, svg, ppm and webp use the + new IO class +- rewritten ppm load/save is faster and uses less memory +- add @no_strip option to dzsave [kalozka1] +- add iiif layout to dzsave +- fix use of resolution-unit metadata on tiff save [kayarre] +- support TIFF CIELAB images with alpha [angelmixu] +- support TIFF with premultiplied alpha in any band +- block metadata changes on shared images [pvdz] +- RGB and sRGB are synonmous + +17/9/19 started 8.8.4 +- improve compatibility with older imagemagick versions +- remove realpath, since it can fail on systems with grsec 31/8/19 started 8.8.3 - revert sharpen restoring the input colourspace diff --git a/Makefile.am b/Makefile.am index 4b622241..e5f6203c 100644 --- a/Makefile.am +++ b/Makefile.am @@ -14,8 +14,7 @@ EXTRA_DIST = \ autogen.sh \ vips.pc.in \ vips-cpp.pc.in \ - libvips.supp \ - lsan.supp \ + suppressions \ depcomp \ README.md diff --git a/README.md b/README.md index 08fcf90c..89ae80a5 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,56 @@ # libvips : an image processing library [![Build Status](https://travis-ci.org/libvips/libvips.svg?branch=master)](https://travis-ci.org/libvips/libvips) -[![OSS-fuzz Status](https://oss-fuzz-build-logs.storage.googleapis.com/badges/libvips.png)](https://oss-fuzz.com/coverage-report/job/libfuzzer_asan_libvips/latest) +[![Fuzzing Status](https://oss-fuzz-build-logs.storage.googleapis.com/badges/libvips.svg)](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=2&q=proj:libvips) [![Coverity Status](https://scan.coverity.com/projects/6503/badge.svg)](https://scan.coverity.com/projects/jcupitt-libvips) +# This branch + +Is for experiemtning with [libspng](https://github.com/randy408/libspng). + +## Notes + +Build libspng: + +``` +cd libspng +meson build --prefix=/home/john/vips --libdir=/home/john/vips/lib \ + --buildtype=release +cd build +ninja +ninja install +``` + +Installs `spng.pc`. + +Sample code: + +https://github.com/randy408/libspng/blob/master/examples/example.c + +libspng benchmark: + +``` +$ time vips avg wtc.png +117.065766 + +real 0m2.972s +user 0m3.376s +sys 0m0.197s +``` + +And for libpng: + +``` +$ time vips avg wtc.png +117.065766 + +real 0m3.816s +user 0m4.177s +sys 0m0.221s +``` + +# Introduction + libvips is a [demand-driven, horizontally threaded](https://github.com/libvips/libvips/wiki/Why-is-libvips-quick) image processing library. Compared to similar @@ -67,7 +114,7 @@ Untar, then in the libvips directory you should just be able to do: ./configure Check the summary at the end of `configure` carefully. libvips must have -`build-essential`, `pkg-config`, `glib2.0-dev`, `libexpat1-dev`. +`build-essential`, `pkg-config`, `libglib2.0-dev`, `libexpat1-dev`. You'll need the dev packages for the file format support you want. For basic jpeg and tiff support, you'll need `libtiff5-dev`, `libjpeg-turbo8-dev`, @@ -89,7 +136,6 @@ Run the test suite with: Run a specific test with: - pytest --verbose pytest test/test-suite/test_foreign.py -k test_tiff # Building libvips from git @@ -103,7 +149,7 @@ and `gobject-introspection`, see the dependencies section below. For example: brew install gtk-doc -Then build the build system with: +Then generate the build system with: ./autogen.sh --prefix=/home/john/vips @@ -114,62 +160,12 @@ Debug build: make make install -Leak check. Use the suppressions file `supp/valgrind.supp`. - - export G_DEBUG=gc-friendly - valgrind --suppressions=vips-x.y.z/supp/valgrind.supp \ - --leak-check=yes \ - vips ... > vips-vg.log 2>&1 - -Memory error debug: - - valgrind --vgdb=yes --vgdb-error=0 vips ... - -valgrind threading check: - - valgrind --tool=helgrind vips ... > vips-vg.log 2>&1 - -Clang build: - - CC=clang CXX=clang++ ./configure --prefix=/home/john/vips - -Clang static analysis: - - scan-build ./configure --disable-introspection --disable-debug - scan-build -o scan -v make - scan-view scan/2013-11-22-2 - -Clang dynamic analysis: - - FLAGS="-g -O1 -fno-omit-frame-pointer" - CC=clang CXX=clang++ LD=clang \ - CFLAGS="$FLAGS" CXXFLAGS="$FLAGS" LDFLAGS=-fsanitize=address \ - ./configure --prefix=/home/john/vips - - FLAGS="-O1 -g -fsanitize=thread" - FLAGS="$FLAGS -fPIC" - FLAGS="$FLAGS -fno-omit-frame-pointer -fno-optimize-sibling-calls" - CC=clang CXX=clang++ LD=clang \ - CFLAGS="$FLAGS" CXXFLAGS="$FLAGS" \ - LDFLAGS="-fsanitize=thread -fPIC" \ - ./configure --prefix=/home/john/vips \ - --without-magick \ - --disable-introspection - G_DEBUG=gc-friendly vips copy ~/pics/k2.jpg x.jpg >& log - -Build with the GCC auto-vectorizer and diagnostics (or just -O3): - - FLAGS="-O2 -march=native -ffast-math" - FLAGS="$FLAGS -ftree-vectorize -fdump-tree-vect-details" - CFLAGS="$FLAGS" CXXFLAGS="$FLAGS" \ - ./configure --prefix=/home/john/vips - # Dependencies -libvips has to have `glib2.0-dev` and `libexpat1-dev`. Other dependencies -are optional, see below. +libvips has to have `libglib2.0-dev` and `libexpat1-dev`. Other dependencies +are optional. -# Optional dependencies +## Optional dependencies If suitable versions are found, libvips will add support for the following libraries automatically. See `./configure --help` for a set of flags to @@ -284,7 +280,7 @@ If available, vips can load and save WebP images. ### libniftiio -If available, vips can load and save NIFTI images. +If available, vips can load and save NIfTI images. ### OpenEXR @@ -300,9 +296,25 @@ files: Aperio, Hamamatsu, Leica, MIRAX, Sakura, Trestle, and Ventana. If available, libvips can load and save HEIC images. -# Disclaimer +# Contributors -No guarantees of performance accompany this software, nor is any -responsibility assumed on the part of the authors. Please read the licence -agreement. +### Code Contributors +This project exists thanks to all the people who contribute. + + + +### Organizations + +Support this project with your organization. Your logo will show up here with a link to your website. + + + + + + + + + + + diff --git a/TODO b/TODO deleted file mode 100644 index 967dcc6b..00000000 --- a/TODO +++ /dev/null @@ -1,439 +0,0 @@ -- try - - $ vips gaussmat x.mat 0.1 0.1 - (vips:28376): GLib-GObject-WARNING **: value "-1" of type 'gint' is invalid or out of range for property 'width' of type 'gint' - (vips:28376): GLib-GObject-WARNING **: value "-1" of type 'gint' is invalid or out of range for property 'height' of type 'gint' - $ more x.mat - 1 1 0 0 - 5.55604e+180 - - check numeric range of SIGMA args, we should standardize - -- rewind should break more things ... does it remove upsteam/downstream? does it - just need to remove reorder links? - - perhaps reorder should use upstream/downstream, then it will be broken anyway - -- not sure about utf8 error messages on win - -- strange: - - $ vips similarity --scale 0.33 k2.jpg x.v - $ vipsheader k2.jpg - k2.jpg: 1450x2048 uchar, 3 bands, srgb, jpegload - $ vipsheader x.v - x.v: 478x676 uchar, 3 bands, srgb, jpegload - - 1450 * 0.33 = 478.5 ... this was rounded down - 2048 * 0.33 = 675.84 ... this was rounded up - -- add APPROX convsep test? - -- add more webp tests to py suite - -- try moving some more of the CLI tests to py - -- colour needs to split _build() into preprocess / process / postprocess - phases - - in icc_import, for example, we want to check that the supplied profile is - compatible with the input image as it will be when unpacked and ready for - process_line - - see vips_image_expected_bands() in icc_transform.c for the current hacky - solution - - - -- use the incremental webp decoding api to support seq for webp images - - https://developers.google.com/speed/webp/docs/api#decodingadvancedapi - - doesn't seem to be possible - -- does ruby need to unpack RefString as well? what about C++? - -- are the mosaic functions calling vips_fastcor()? it must be very slow - - add vips_fastcor_direct() - - nope .. it's im_chkpair.c:im_correl() - - im_extract_area(main) - im_extract_area(sub) - im_extract_band(main) - im_extract_band(sub) - im_spcor(sub) - im_maxpos(sub) - - then im__chkpair() runs that 20 times, then loops ... oh dear - -- perhaps im_maxpos_subpel() / im_minpos_subpel() should be undeprecated, - useful with vips_fastcor() - - - -- why can't we do - - im = Vips.Image.new_from_file("/data/john/pics/k2.jpg", access = "sequential") - - no idea ... this works fine: - - im.embed(10, 10, 100, 100, extend = "copy") - - test: - - op = Vips.Operation.new("embed") - op.props.__setattr__("extend", "copy") - op = Vips.Operation.new("jpegload") - op.props.__setattr__("access", "sequential") - - first setattr works fine, second fails with invalid literal - - - - -- test other cpp arg types - - input int works - input double - input enum works - input image works - input doublevec - input imagevec - input blob - - output int - output double works - output enum - output image works - output doublevec - output imagevec - output blob - - we probably need to unpack the ink back to double before blending - - - -- ink to vec etc must have a way to give a complex constant - - eg. drawink needs a --ink_imag option with the imaginary components of the - ink - - look for uses of vips__vector_to_ink() and add extra params to other places - too, eg. vips_embed(), vips_insert() etc. - -- vips__ink_to_vector() needs an optional imag return - -- vips_getpoint() needs an optional imag return - -- add porter-duff compositing, see - - https://github.com/libvips/ruby-vips/issues/28 - -- now vips_linear() has uchar output, can we do something with orc? - -- do restrict on some more packages, we've just done arithmetic so far - - also resample, colour, some of conversion, create, - -- maybe avg? - - but avg doesn't subclass arithmetic, so we can't - -- for interpolate, we'd need to be able to unroll the vector, so the - interpolator would need to be built for the bands / stride / type of the - image - - need new API For this since interpolators currently work for any image - -- vips_gaussblur() should switch to float prec if given a float image? - - same for vips_conv()? - - maybe precision is a dumb thing - -- support --strip for other writers - -- vipsthumbnail could shrink-on-load openslide and pyr tiff as well? - - we have "shrink" for jpegload, move this into the base loader - - support it for tiff and openslide as well - - use it from nip2 for zooming? only if the partial flag is set though, we - don't want to use it on jpg files - - - -- quadratic doesn't work for order 3 - - start to get jaggies on lines --- the 3rd differential isn't being - initialised correctly for the sub-region? - - seems fine vertically, only get errors on horizontal tile boundaries - - because we step across tiles left to right: y doesn't change, only x does - - not sure it works for order 2 either, we are seeing interpolation errors - on image edges - - -mosaic -====== - -- balance should use new meta stuff - -- histogram balance option? - - -resample -======== - -- check mosaic1, global_balance, similarity etc. use of im__affine - - how can we move them to im_affinei ? - -- perspective transform with a matrix ... base it on the Lenz transformer, but - partial - - -foreign -======= - -- magick2vips should spot ICC profiles and attach them as meta - -- interlaced jpg needs massive memory, we should have two jpg read modes, like - png - -- add more sequential mode readers - - $ grep -l write_line *.c - csv.c - matlab.c - openexr2vips.c - ppm.c - radiance.c - -- foreign docs come up as "VipsForeignSave", annoying, why? - -- add nifti support - - http://niftilib.sourceforge.net/ - -- add matlab write - -- im_exr2vips can now use c++ api - - see TODO notes in openexr read (though they all need more openexr C API) - - consider openexr write - -- magick should set some header field for n_frames and frame_height? see also - analyze - -- im_csv2vips() could use "-" for filename to mean stdin - - but then we'd have to read to a malloced buffer of some sort rather than an - image, since we might need to grow it during the read, since we couldn't - then seek - - -packaging -========= - -- test _O_TEMPORARY thing on Windows - - -convolution -=========== - -- revisit orc conv - - use an 8.8 accumulator ... build the scale into the 8.8 coeffs ... no div at - the end, just a shift - - need 8 x 8.8 -> 8.8 for each coeff though - -- im_conv()/im_morph() could have more than 10 programs? try 20 and see if we - still have a speedup - - make a base class for vector area operations with a matrix with three vfuncs - for init / generate code for one element / end and a gslist of programs, use - that as the base for morph and conv - - wait for vipsobject for this - -- we have aconv and aconvsep - - test timing, make sure it;s worth having a separate aconvsep version - - if it is, make im_aconvsep an optimisation: call im_aconvsep_raw() from - vips_conv() if mask width or height == 1 and prec == APPROX - - now we can get rid of im_aconvsep() since it's just vips_convsep() with prec - set to approx - - aconv needs some more work, get it going at least with gaussian - - -arithmetic -========== - -- HAVE_HYPOT could define a hypot() macro? - -- fix a better NaN policy - - should we not generate images containing NaN (eg. divide tries to avoid /0), - or should vips_max() etc. try to avoid NaN in images (eg. vips_max() takes a - lot a care to skip NaN, though vips_stats() does not)? - - -iofuncs -======= - -- need vips_image_invalidate_area() - -- look at libpeas for plugin support - - http://live.gnome.org/Libpeas - -- how about - - vips max add[babe.jpg,babe2.jpg] - - does that make any sense? - - vips copy add[babe.jpg,add[babe2.jpg,babe3.jpg]] sum.v - - perhaps use curly brackets for code? - - vips max add{babe.jpg,babe2.jpg} - - no brackets or square brackets for options - -- transform_g_string_array_image() can't handle quoted strings, so filenames - with spaces will break - - is there an easy fix? can we reuse code from the csv parser? - - the csv parser just parses FILE* streams, we'd need to break it out - -- note member free stuff in vipsobject docs - - should boxed get freed in finalise rather than dispose? - - vipsobject has few docs atm :( - -- vips_object_set_argument_from_string() needs more arg types - - must be some way to make this more automatic - -- generate the code for vips_add() etc. automatically? it might be - nice to have them all in one place at least - -- what does G_UNLIKELY() do? can we use it? - -- look into G_GNUC_DEPRECATED for back compat in vips8 - -- should im_rwcheck() copy to disc? - - maybe im_rwcheck_disc() copies to im->filename and maps that - - rather awkward to do atm with the way check.c is structured - - -swig -==== - -- swig is not wrapping im_project() correctly ... returns an extra VImage via - a param - -- doc strings would be nice, read the SWIG notes on this - - -new bindings -============ - -- new binding is still missing constants - - how do boxed types work? confusing - - we need to be able to make a VipsArrayDouble - -- Vips.Image has members like chain, __subclasshook__ etc etc, are we - really subclassing it correctly? - -- add __add__ etc overloads - - -freq_filt -========= - -- fft with odd width or height is broken ... DC ends up in the wrong place - - -libvipsCC -========= - -- need new C++ API - -- need an im_init_world() for C++ which does cmd-line args too, so C++ progs - can get --vips-progress and stuff automatically - - -tools -===== - -- need a way to make the vips.1 etc. man pages - - gtk has things like docs/reference/gtk/gtk-update-icon-cache.xml and man - pages are made from that with xslt - -- get rid of a lot of the command-line programs, who wants to write a man page - for batch_image_convert etc yuk - -- can we make man pages for the API as well? probably not from googling a bit - -- rename header, edvips as vipsheader, vipsedit - - maybe have back compat links? - - -new operations -============== - -- bilateral filtering, see: - - http://en.wikipedia.org/wiki/Bilateral_filter - http://www.shellandslate.com/fastmedian.html - http://people.csail.mit.edu/sparis/bf_course/ - - also a mail from Martin Breidt has links to several fast free C - implementations - -- http://en.wikipedia.org/wiki/Otsu%27s_method - -- non-linear sharpen: replace each pixel by the lightest or darkest neighbour - depending on which is closer in value - -- look at - - There is an order 1 algorithm for doing medians over boxes (truly O(1) - per pixel: I checked it carefully; it's like doing means over boxes in - order 1 per pixel) in OpenCV since February 2012 I think, due to - Perreault (and Hebert). - - It appears to be well respected, at least for 8-bit medians. Very - memory intensive. Simple and elegant. No clue if it fits VIPS well - (probably not?). - - Article: nomis80.org/ctmf.pdf - -- see - - http://www.dentistry.bham.ac.uk/landinig/software/cdeconv/cdeconv.html - - http://www.nature.com/srep/2015/150730/srep12096/full/srep12096.html - - sounds useful for BM? diff --git a/autogen.sh b/autogen.sh index af5c3d1b..607b795b 100755 --- a/autogen.sh +++ b/autogen.sh @@ -61,4 +61,6 @@ autoheader $LIBTOOLIZE --copy --force --automake automake --add-missing --copy -./configure $* +if test -z "$NOCONFIGURE"; then + ./configure $* +fi diff --git a/configure.ac b/configure.ac index b6f1fb90..b6b1ba7c 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # also update the version number in the m4 macros below -AC_INIT([vips], [8.9.0], [vipsip@jiscmail.ac.uk]) +AC_INIT([vips], [8.10.0], [vipsip@jiscmail.ac.uk]) # required for gobject-introspection AC_PREREQ(2.62) @@ -17,7 +17,7 @@ AC_CONFIG_MACRO_DIR([m4]) # user-visible library versioning m4_define([vips_major_version], [8]) -m4_define([vips_minor_version], [9]) +m4_define([vips_minor_version], [10]) m4_define([vips_micro_version], [0]) m4_define([vips_version], [vips_major_version.vips_minor_version.vips_micro_version]) @@ -38,7 +38,7 @@ VIPS_VERSION_STRING=$VIPS_VERSION-`date -u -r ChangeLog` # binary interface changes not backwards compatible?: reset age to 0 LIBRARY_CURRENT=54 -LIBRARY_REVISION=0 +LIBRARY_REVISION=3 LIBRARY_AGE=12 # patched into include/vips/version.h @@ -450,33 +450,12 @@ AC_CHECK_LIB(m,cbrt,[AC_DEFINE(HAVE_CBRT,1,[have cbrt() in libm.])]) AC_CHECK_LIB(m,hypot,[AC_DEFINE(HAVE_HYPOT,1,[have hypot() in libm.])]) AC_CHECK_LIB(m,atan2,[AC_DEFINE(HAVE_ATAN2,1,[have atan2() in libm.])]) -# have to have these -# need glib 2.6 for GOption -PKG_CHECK_MODULES(REQUIRED, glib-2.0 >= 2.6 gmodule-2.0 gobject-2.0) -PACKAGES_USED="$PACKAGES_USED glib-2.0 gmodule-2.0 gobject-2.0" - -# from 2.12 we have g_base64_encode() -PKG_CHECK_MODULES(BASE64_ENCODE, glib-2.0 >= 2.12, - [AC_DEFINE(HAVE_BASE64_ENCODE,1, - [define if your glib has g_base64_encode().] - ) - ], - [: - ] -) - -# from 2.14 we have g_option_context_get_help() -PKG_CHECK_MODULES(CONTEXT_GET_HELP, glib-2.0 >= 2.14, - [AC_DEFINE(HAVE_CONTEXT_GET_HELP,1, - [define if your glib has g_option_context_get_help().] - ) - ], - [: - ] -) +# have to have these parts of glib ... we need glib 2.15 for gio +PKG_CHECK_MODULES(REQUIRED, glib-2.0 >= 2.15 gmodule-2.0 gobject-2.0 gio-2.0) +PACKAGES_USED="$PACKAGES_USED glib-2.0 gmodule-2.0 gobject-2.0 gio-2.0" # from 2.28 we have a monotonic timer -PKG_CHECK_MODULES(MONOTONIC, glib-2.0 >= 2.28, +PKG_CHECK_MODULES(MONOTONIC_TIME, glib-2.0 >= 2.28, [AC_DEFINE(HAVE_MONOTONIC_TIME,1, [define if your glib has g_get_monotonic_time().] ) @@ -485,11 +464,18 @@ PKG_CHECK_MODULES(MONOTONIC, glib-2.0 >= 2.28, ] ) -# the old threading system -PKG_CHECK_MODULES(GTHREAD, gthread-2.0) -PACKAGES_USED="$PACKAGES_USED gthread-2.0" +# from 2.62 we have datetime +PKG_CHECK_MODULES(DATE_TIME_FORMAT_ISO8601, glib-2.0 >= 2.62, + [AC_DEFINE(HAVE_DATE_TIME_FORMAT_ISO8601,1, + [define if your glib has g_date_time_format_iso8601().] + ) + ], + [: + ] +) -# from 2.32 there are a new set of thread functions, annoyingly +# from 2.32 there are a new set of thread functions, it is no longer +# necessary to use g_thread_init() or to link against libgthread PKG_CHECK_MODULES(THREADS, glib-2.0 >= 2.32, [AC_DEFINE(HAVE_MUTEX_INIT,1,[define if your glib has g_mutex_init().]) AC_DEFINE(HAVE_COND_INIT,1,[define if your glib has g_cond_init().]) @@ -498,6 +484,9 @@ PKG_CHECK_MODULES(THREADS, glib-2.0 >= 2.32, AC_DEFINE(HAVE_VALUE_GET_SCHAR,1, [define if your glib has g_value_get_schar().] ) + ], + [PKG_CHECK_MODULES(GTHREAD, gthread-2.0) + PACKAGES_USED="$PACKAGES_USED gthread-2.0" ] ) @@ -553,9 +542,16 @@ PKG_CHECK_MODULES(HAVE_CHECKED_MUL, glib-2.0 >= 2.48, # check for gtk-doc GTK_DOC_CHECK([1.14],[--flavour no-tmpl]) -# we need expat ... we'd love to use expat.pc, but sadly this is only available -# for recent linuxes, so we have to use the old and horrible expat.m4 -AM_WITH_EXPAT +# we need expat ... the .pc file for expat is only available +# for recent linuxes, so we fall back to AM_WITH_EXPAT +PKG_CHECK_MODULES(EXPAT, expat, + [expat_found=yes + PACKAGES_USED="$PACKAGES_USED expat" + ], + [AM_WITH_EXPAT + ] +) + if test x"$expat_found" = x"no"; then exit 1 fi @@ -699,6 +695,16 @@ if test x"$magick6" = x"yes"; then ] ) + AC_COMPILE_IFELSE( + [AC_LANG_PROGRAM( + [#include ], + [ColorspaceType colorspace = HCLpColorspace] + )], + [AC_DEFINE(HAVE_HCLPCOLORSPACE,1, + [define if your Magick has HCLpColorspace.]) + ] + ) + # GetImageMagick() takes two args under GM, three under IM AC_COMPILE_IFELSE( [AC_LANG_PROGRAM( @@ -778,6 +784,19 @@ if test x"$with_orc" != x"no"; then ) fi +# orc 0.4.30+ works with cf-protection, but 0.4.30 has a bug with multiple +# definitions of OrcTargetPowerPCFlags, so insist on 0.4.31 +if test x"$with_orc" = x"yes"; then + PKG_CHECK_MODULES(ORC_CF_PROTECTION, orc-0.4 >= 0.4.31, + [AC_DEFINE(HAVE_ORC_CF_PROTECTION,1, + [define if your orc works with cf-protection.] + ) + ], + [: + ] + ) +fi + # lcms ... refuse to use lcms1 AC_ARG_WITH([lcms], AS_HELP_STRING([--without-lcms], [build without lcms (default: test)])) @@ -836,14 +855,16 @@ if test x"$with_heif" != x"no"; then PKG_CHECK_MODULES(HEIF, libheif, [with_heif=yes have_h265_decoder=`$PKG_CONFIG libheif --variable builtin_h265_decoder` + have_avif_decoder=`$PKG_CONFIG libheif --variable builtin_avif_decoder` # test for !=no so that we work for older libheif which does not have # this variable - if test x"$have_h265_decoder" != x"no"; then + if test x"$have_h265_decoder" != x"no" -o x"$have_avif_decoder" = x"yes"; then AC_DEFINE(HAVE_HEIF_DECODER,1, [define if your libheif has decode support.]) fi have_h265_encoder=`$PKG_CONFIG libheif --variable builtin_h265_encoder` - if test x"$have_h265_encoder" != x"no"; then + have_avif_encoder=`$PKG_CONFIG libheif --variable builtin_avif_encoder` + if test x"$have_h265_encoder" != x"no" -o x"$have_avif_encoder" = x"yes"; then AC_DEFINE(HAVE_HEIF_ENCODER,1, [define if your libheif has encode support.]) fi @@ -853,6 +874,8 @@ if test x"$with_heif" != x"no"; then with_heif=no have_h265_decoder= have_h265_encoder= + have_avif_decoder= + have_avif_encoder= ] ) fi @@ -905,16 +928,16 @@ if test x"$with_heif" = x"yes"; then LIBS="$save_LIBS" fi -# fetch untransformed width/height added in 1.3.4 +# heif_decoding_options.convert_hdr_to_8bit added in 1.7.0 if test x"$with_heif" = x"yes"; then - save_LIBS="$LIBS" - LIBS="$LIBS $HEIF_LIBS" - AC_CHECK_FUNCS(heif_image_handle_get_ispe_width,[ - AC_DEFINE(HAVE_HEIF_IMAGE_HANDLE_GET_ISPE_WIDTH,1, - [define if you have heif_image_handle_get_ispe_width.]) - ],[] - ) - LIBS="$save_LIBS" + save_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS $HEIF_CFLAGS" + AC_CHECK_MEMBER([struct heif_decoding_options.convert_hdr_to_8bit],[ + AC_DEFINE(HAVE_HEIF_DECODING_OPTIONS_CONVERT_HDR_TO_8BIT,1, + [define if you have heif_decoding_options.convert_hdr_to_8bit]) + ],[], + [#include ]) + CFLAGS="$save_CFLAGS" fi # pdfium @@ -1176,6 +1199,21 @@ fi ) #fi +# Look for libspng first +AC_ARG_WITH([libspng], + AS_HELP_STRING([--without-libspng], [build without libspng (default: test)])) + +if test x"$with_libspng" != x"no"; then + PKG_CHECK_MODULES(SPNG, spng >= 0.6, + [AC_DEFINE(HAVE_SPNG,1,[define if you have libspng installed.]) + with_libspng=yes + PACKAGES_USED="$PACKAGES_USED spng" + ], + [with_libspng=no + ] + ) +fi + # look for PNG with pkg-config ... fall back to our tester AC_ARG_WITH([png], AS_HELP_STRING([--without-png], [build without libpng (default: test)])) @@ -1298,23 +1336,21 @@ if test x"$LIB_FUZZING_ENGINE" = x; then LIB_FUZZING_ENGINE="libstandaloneengine.a" fi -# Gather all up for VIPS_CFLAGS, VIPS_INCLUDES, VIPS_LIBS -# sort includes to get longer, more specific dirs first -# helps, for example, selecting graphicsmagick over imagemagick -VIPS_CFLAGS=`for i in $VIPS_CFLAGS $GTHREAD_CFLAGS $REQUIRED_CFLAGS $EXPAT_CFLAGS $ZLIB_CFLAGS $PANGOFT2_CFLAGS $GSF_CFLAGS $FFTW_CFLAGS $MAGICK_CFLAGS $JPEG_CFLAGS $PNG_CFLAGS $IMAGEQUANT_CFLAGS $EXIF_CFLAGS $MATIO_CFLAGS $CFITSIO_CFLAGS $LIBWEBP_CFLAGS $LIBWEBPMUX_CFLAGS $LIBNSGIF_CFLAGS $GIFLIB_INCLUDES $RSVG_CFLAGS $PDFIUM_INCLUDES $POPPLER_CFLAGS $OPENEXR_CFLAGS $OPENSLIDE_CFLAGS $ORC_CFLAGS $TIFF_CFLAGS $LCMS_CFLAGS $HEIF_CFLAGS -do - echo $i -done | sort -ru` -VIPS_CFLAGS=`echo $VIPS_CFLAGS` +# Gather all up for VIPS_CFLAGS, VIPS_INCLUDES, VIPS_LIBS +VIPS_CFLAGS="$VIPS_CFLAGS $GTHREAD_CFLAGS $GIO_CFLAGS $REQUIRED_CFLAGS $EXPAT_CFLAGS $ZLIB_CFLAGS $PANGOFT2_CFLAGS $GSF_CFLAGS $FFTW_CFLAGS $MAGICK_CFLAGS $JPEG_CFLAGS $SPNG_CFLAGS $PNG_CFLAGS $IMAGEQUANT_CFLAGS $EXIF_CFLAGS $MATIO_CFLAGS $CFITSIO_CFLAGS $LIBWEBP_CFLAGS $LIBWEBPMUX_CFLAGS $LIBNSGIF_CFLAGS $GIFLIB_INCLUDES $RSVG_CFLAGS $PDFIUM_INCLUDES $POPPLER_CFLAGS $OPENEXR_CFLAGS $OPENSLIDE_CFLAGS $ORC_CFLAGS $TIFF_CFLAGS $LCMS_CFLAGS $HEIF_CFLAGS" VIPS_CFLAGS="$VIPS_DEBUG_FLAGS $VIPS_CFLAGS" VIPS_INCLUDES="$ZLIB_INCLUDES $PNG_INCLUDES $TIFF_INCLUDES $JPEG_INCLUDES $NIFTI_INCLUDES" -VIPS_LIBS="$ZLIB_LIBS $HEIF_LIBS $MAGICK_LIBS $PNG_LIBS $IMAGEQUANT_LIBS $TIFF_LIBS $JPEG_LIBS $GTHREAD_LIBS $REQUIRED_LIBS $EXPAT_LIBS $PANGOFT2_LIBS $GSF_LIBS $FFTW_LIBS $ORC_LIBS $LCMS_LIBS $LIBNSGIF_LIBS $GIFLIB_LIBS $RSVG_LIBS $NIFTI_LIBS $PDFIUM_LIBS $POPPLER_LIBS $OPENEXR_LIBS $OPENSLIDE_LIBS $CFITSIO_LIBS $LIBWEBP_LIBS $LIBWEBPMUX_LIBS $MATIO_LIBS $EXIF_LIBS -lm" +VIPS_LIBS="$ZLIB_LIBS $HEIF_LIBS $MAGICK_LIBS $SPNG_LIBS $PNG_LIBS $IMAGEQUANT_LIBS $TIFF_LIBS $JPEG_LIBS $GTHREAD_LIBS $GIO_LIBS $REQUIRED_LIBS $EXPAT_LIBS $PANGOFT2_LIBS $GSF_LIBS $FFTW_LIBS $ORC_LIBS $LCMS_LIBS $LIBNSGIF_LIBS $GIFLIB_LIBS $RSVG_LIBS $NIFTI_LIBS $PDFIUM_LIBS $POPPLER_LIBS $OPENEXR_LIBS $OPENSLIDE_LIBS $CFITSIO_LIBS $LIBWEBP_LIBS $LIBWEBPMUX_LIBS $MATIO_LIBS $EXIF_LIBS -lm" +# autoconf hates multi-line AC_SUBST so we have to have another copy of this +# thing +VIPS_CONFIG="native win32: $vips_os_win32, native OS X: $vips_os_darwin, open files in binary mode: $vips_binary_open, enable debug: $enable_debug, enable deprecated library components: $enable_deprecated, enable docs with gtkdoc: $enable_gtk_doc, gobject introspection: $found_introspection, enable radiance support: $with_radiance, enable analyze support: $with_analyze, enable PPM support: $with_ppm, use fftw3 for FFT: $with_fftw, Magick package: $with_magickpackage, Magick API version: $magick_version, load with libMagick: $enable_magickload, save with libMagick: $enable_magicksave, accelerate loops with orc: $with_orc, ICC profile support with lcms: $with_lcms, file import with niftiio: $with_nifti, file import with libheif: $with_heif, file import with OpenEXR: $with_OpenEXR, file import with OpenSlide: $with_openslide, file import with matio: $with_matio, PDF import with PDFium: $with_pdfium, PDF import with poppler-glib: $with_poppler, SVG import with librsvg-2.0: $with_rsvg, zlib: $with_zlib, file import with cfitsio: $with_cfitsio, file import/export with libwebp: $with_libwebp, text rendering with pangoft2: $with_pangoft2, file import/export with libpng: $with_png, support 8bpp PNG quantisation: $with_imagequant, file import/export with libtiff: $with_tiff, file import/export with giflib: $with_giflib, file import/export with libjpeg: $with_jpeg, image pyramid export: $with_gsf, use libexif to load/save JPEG metadata: $with_libexif, file import/export with libspng: $with_libspng, file import/export with libnsgif: $with_libnsgif" AC_SUBST(VIPS_LIBDIR) AC_SUBST(VIPS_CFLAGS) AC_SUBST(VIPS_INCLUDES) AC_SUBST(VIPS_LIBS) +AC_SUBST(VIPS_CONFIG) AC_SUBST(PACKAGES_USED) AC_SUBST(EXTRA_LIBS_USED) @@ -1333,6 +1369,7 @@ AC_OUTPUT([ libvips/Makefile libvips/arithmetic/Makefile libvips/colour/Makefile + libvips/colour/profiles/Makefile libvips/conversion/Makefile libvips/convolution/Makefile libvips/deprecated/Makefile @@ -1365,6 +1402,7 @@ AC_OUTPUT([ fuzz/Makefile ]) +# also add any new items to VIPS_CONFIG above AC_MSG_RESULT([dnl * build options native win32: $vips_os_win32 @@ -1393,7 +1431,7 @@ file import with OpenEXR: $with_OpenEXR file import with OpenSlide: $with_openslide (requires openslide-3.3.0 or later) file import with matio: $with_matio -PDF import with PDFium $with_pdfium +PDF import with PDFium: $with_pdfium PDF import with poppler-glib: $with_poppler (requires poppler-glib 0.16.0 or later) SVG import with librsvg-2.0: $with_rsvg @@ -1403,6 +1441,8 @@ file import with cfitsio: $with_cfitsio file import/export with libwebp: $with_libwebp (requires libwebp, libwebpmux, libwebpdemux 0.6.0 or later) text rendering with pangoft2: $with_pangoft2 +file import/export with libspng: $with_libspng + (requires libspng-0.6 or later) file import/export with libpng: $with_png (requires libpng-1.2.9 or later) support 8bpp PNG quantisation: $with_imagequant diff --git a/cplusplus/Makefile.am b/cplusplus/Makefile.am index cdaf8921..3033efde 100644 --- a/cplusplus/Makefile.am +++ b/cplusplus/Makefile.am @@ -11,6 +11,7 @@ lib_LTLIBRARIES = libvips-cpp.la libvips_cpp_la_SOURCES = \ VImage.cpp \ VInterpolate.cpp \ + VConnection.cpp \ VError.cpp libvips_cpp_la_LDFLAGS = \ diff --git a/cplusplus/VConnection.cpp b/cplusplus/VConnection.cpp new file mode 100644 index 00000000..be7afc5a --- /dev/null +++ b/cplusplus/VConnection.cpp @@ -0,0 +1,178 @@ +/* Object part of the VSource and VTarget class + */ + +/* + + Copyright (C) 1991-2001 The National Gallery + + This program 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 + + */ + +#ifdef HAVE_CONFIG_H +#include +#endif /*HAVE_CONFIG_H*/ +#include + +#include + +#include + +/* +#define VIPS_DEBUG +#define VIPS_DEBUG_VERBOSE + */ + +VIPS_NAMESPACE_START + +VSource +VSource::new_from_descriptor( int descriptor ) +{ + VipsSource *input; + + if( !(input = vips_source_new_from_descriptor( descriptor )) ) + throw VError(); + + VSource out( input ); + + return( out ); +} + +VSource +VSource::new_from_file( const char *filename ) +{ + VipsSource *input; + + if( !(input = vips_source_new_from_file( filename )) ) + throw VError(); + + VSource out( input ); + + return( out ); +} + +VSource +VSource::new_from_blob( VipsBlob *blob ) +{ + VipsSource *input; + + if( !(input = vips_source_new_from_blob( blob )) ) + throw VError(); + + VSource out( input ); + + return( out ); +} + +VSource +VSource::new_from_memory( const void *data, + size_t size ) +{ + VipsSource *input; + + if( !(input = vips_source_new_from_memory( data, size )) ) + throw VError(); + + VSource out( input ); + + return( out ); +} + +VSource +VSource::new_from_options( const char *options ) +{ + VipsSource *input; + + if( !(input = vips_source_new_from_options( options )) ) + throw VError(); + + VSource out( input ); + + return( out ); +} + +VOption * +VOption::set( const char *name, const VSource value ) +{ + Pair *pair = new Pair( name ); + + pair->input = true; + g_value_init( &pair->value, VIPS_TYPE_SOURCE ); + g_value_set_object( &pair->value, value.get_source() ); + options.push_back( pair ); + + return( this ); +} + +VTarget +VTarget::new_to_descriptor( int descriptor ) +{ + VipsTarget *output; + + if( !(output = vips_target_new_to_descriptor( descriptor )) ) + throw VError(); + + VTarget out( output ); + + return( out ); +} + +VTarget +VTarget::new_to_file( const char *filename ) +{ + VipsTarget *output; + + if( !(output = vips_target_new_to_file( filename )) ) + throw VError(); + + VTarget out( output ); + + return( out ); +} + +VTarget +VTarget::new_to_memory() +{ + VipsTarget *output; + + if( !(output = vips_target_new_to_memory()) ) + throw VError(); + + VTarget out( output ); + + return( out ); +} + +VOption * +VOption::set( const char *name, const VTarget value ) +{ + Pair *pair = new Pair( name ); + + pair->input = true; + g_value_init( &pair->value, VIPS_TYPE_TARGET ); + g_value_set_object( &pair->value, value.get_target() ); + options.push_back( pair ); + + return( this ); +} + +VIPS_NAMESPACE_END diff --git a/cplusplus/VImage.cpp b/cplusplus/VImage.cpp index 205b46cb..27654f22 100644 --- a/cplusplus/VImage.cpp +++ b/cplusplus/VImage.cpp @@ -5,7 +5,7 @@ * 10/6/16 * - missing implementation of VImage::write() * 11/6/16 - * - added arithmetic assignment overloads, += etc. + * - added arithmetic assignment overloads, += etc. */ /* @@ -51,7 +51,7 @@ VIPS_NAMESPACE_START -std::vector +std::vector to_vectorv( int n, ... ) { std::vector vector( n ); @@ -65,13 +65,13 @@ to_vectorv( int n, ... ) return( vector ); } -std::vector +std::vector to_vector( double value ) { return( to_vectorv( 1, value ) ); } -std::vector +std::vector to_vector( int n, double array[] ) { std::vector vector( n ); @@ -82,10 +82,10 @@ to_vector( int n, double array[] ) return( vector ); } -std::vector +std::vector negate( std::vector vector ) { - std::vector new_vector( vector.size() ); + std::vector new_vector( vector.size() ); for( unsigned int i = 0; i < vector.size(); i++ ) new_vector[i] = vector[i] * -1; @@ -93,7 +93,7 @@ negate( std::vector vector ) return( new_vector ); } -std::vector +std::vector invert( std::vector vector ) { std::vector new_vector( vector.size() ); @@ -108,7 +108,7 @@ VOption::~VOption() { std::list::iterator i; - for( i = options.begin(); i != options.end(); ++i ) + for( i = options.begin(); i != options.end(); ++i ) delete *i; } @@ -140,7 +140,7 @@ VOption::set( const char *name, int value ) return( this ); } -// input double +// input double VOption * VOption::set( const char *name, double value ) { @@ -169,7 +169,7 @@ VOption::set( const char *name, const char *value ) // input image VOption * -VOption::set( const char *name, VImage value ) +VOption::set( const char *name, const VImage value ) { Pair *pair = new Pair( name ); @@ -188,17 +188,17 @@ VOption::set( const char *name, std::vector value ) Pair *pair = new Pair( name ); double *array; - unsigned int i; + unsigned int i; pair->input = true; g_value_init( &pair->value, VIPS_TYPE_ARRAY_DOUBLE ); vips_value_set_array_double( &pair->value, NULL, static_cast< int >( value.size() ) ); - array = vips_value_get_array_double( &pair->value, NULL ); + array = vips_value_get_array_double( &pair->value, NULL ); - for( i = 0; i < value.size(); i++ ) - array[i] = value[i]; + for( i = 0; i < value.size(); i++ ) + array[i] = value[i]; options.push_back( pair ); @@ -212,17 +212,17 @@ VOption::set( const char *name, std::vector value ) Pair *pair = new Pair( name ); int *array; - unsigned int i; + unsigned int i; pair->input = true; g_value_init( &pair->value, VIPS_TYPE_ARRAY_INT ); vips_value_set_array_int( &pair->value, NULL, static_cast< int >( value.size() ) ); - array = vips_value_get_array_int( &pair->value, NULL ); + array = vips_value_get_array_int( &pair->value, NULL ); - for( i = 0; i < value.size(); i++ ) - array[i] = value[i]; + for( i = 0; i < value.size(); i++ ) + array[i] = value[i]; options.push_back( pair ); @@ -236,7 +236,7 @@ VOption::set( const char *name, std::vector value ) Pair *pair = new Pair( name ); VipsImage **array; - unsigned int i; + unsigned int i; pair->input = true; @@ -245,11 +245,11 @@ VOption::set( const char *name, std::vector value ) static_cast< int >( value.size() ) ); array = vips_value_get_array_image( &pair->value, NULL ); - for( i = 0; i < value.size(); i++ ) { + for( i = 0; i < value.size(); i++ ) { VipsImage *vips_image = value[i].get_image(); - array[i] = vips_image; - g_object_ref( vips_image ); + array[i] = vips_image; + g_object_ref( vips_image ); } options.push_back( pair ); @@ -279,7 +279,7 @@ VOption::set( const char *name, bool *value ) pair->input = false; pair->vbool = value; - g_value_init( &pair->value, G_TYPE_BOOLEAN ); + g_value_init( &pair->value, G_TYPE_BOOLEAN ); options.push_back( pair ); @@ -294,7 +294,7 @@ VOption::set( const char *name, int *value ) pair->input = false; pair->vint = value; - g_value_init( &pair->value, G_TYPE_INT ); + g_value_init( &pair->value, G_TYPE_INT ); options.push_back( pair ); @@ -309,7 +309,7 @@ VOption::set( const char *name, double *value ) pair->input = false; pair->vdouble = value; - g_value_init( &pair->value, G_TYPE_DOUBLE ); + g_value_init( &pair->value, G_TYPE_DOUBLE ); options.push_back( pair ); @@ -339,7 +339,7 @@ VOption::set( const char *name, std::vector *value ) pair->input = false; pair->vvector = value; - g_value_init( &pair->value, VIPS_TYPE_ARRAY_DOUBLE ); + g_value_init( &pair->value, VIPS_TYPE_ARRAY_DOUBLE ); options.push_back( pair ); @@ -354,7 +354,7 @@ VOption::set( const char *name, VipsBlob **value ) pair->input = false; pair->vblob = value; - g_value_init( &pair->value, VIPS_TYPE_BLOB ); + g_value_init( &pair->value, VIPS_TYPE_BLOB ); options.push_back( pair ); @@ -362,7 +362,7 @@ VOption::set( const char *name, VipsBlob **value ) } // just g_object_set_property(), except we allow set enum from string -static void +static void set_property( VipsObject *object, const char *name, const GValue *value ) { VipsObjectClass *object_class = VIPS_OBJECT_GET_CLASS( object ); @@ -372,7 +372,7 @@ set_property( VipsObject *object, const char *name, const GValue *value ) VipsArgumentClass *argument_class; VipsArgumentInstance *argument_instance; - if( vips_object_get_argument( object, name, + if( vips_object_get_argument( object, name, &pspec, &argument_class, &argument_instance ) ) { g_warning( "%s", vips_error_buffer() ); vips_error_clear(); @@ -384,17 +384,17 @@ set_property( VipsObject *object, const char *name, const GValue *value ) GType pspec_type = G_PARAM_SPEC_VALUE_TYPE( pspec ); int enum_value; - GValue value2 = { 0 }; + GValue value2 = { 0 }; - if( (enum_value = vips_enum_from_nick( object_class->nickname, + if( (enum_value = vips_enum_from_nick( object_class->nickname, pspec_type, g_value_get_string( value ) )) < 0 ) { g_warning( "%s", vips_error_buffer() ); vips_error_clear(); return; } - g_value_init( &value2, pspec_type ); - g_value_set_enum( &value2, enum_value ); + g_value_init( &value2, pspec_type ); + g_value_set_enum( &value2, enum_value ); g_object_set_property( G_OBJECT( object ), name, &value2 ); g_value_unset( &value2 ); } @@ -402,18 +402,19 @@ set_property( VipsObject *object, const char *name, const GValue *value ) g_object_set_property( G_OBJECT( object ), name, value ); } -// walk the options and set props on the operation -void +// walk the options and set props on the operation +void VOption::set_operation( VipsOperation *operation ) { std::list::iterator i; - for( i = options.begin(); i != options.end(); ++i ) + for( i = options.begin(); i != options.end(); ++i ) if( (*i)->input ) { #ifdef VIPS_DEBUG_VERBOSE printf( "set_operation: " ); vips_object_print_name( VIPS_OBJECT( operation ) ); - char *str_value = g_strdup_value_contents( &(*i)->value ); + char *str_value = + g_strdup_value_contents( &(*i)->value ); printf( ".%s = %s\n", (*i)->name, str_value ); g_free( str_value ); #endif /*VIPS_DEBUG_VERBOSE*/ @@ -424,12 +425,12 @@ VOption::set_operation( VipsOperation *operation ) } // walk the options and fetch any requested outputs -void +void VOption::get_operation( VipsOperation *operation ) { std::list::iterator i; - for( i = options.begin(); i != options.end(); ++i ) + for( i = options.begin(); i != options.end(); ++i ) if( ! (*i)->input ) { const char *name = (*i)->name; @@ -439,7 +440,7 @@ VOption::get_operation( VipsOperation *operation ) #ifdef VIPS_DEBUG_VERBOSE printf( "get_operation: " ); vips_object_print_name( VIPS_OBJECT( operation ) ); - char *str_value = g_strdup_value_contents( + char *str_value = g_strdup_value_contents( &(*i)->value ); printf( ".%s = %s\n", name, str_value ); g_free( str_value ); @@ -450,59 +451,59 @@ VOption::get_operation( VipsOperation *operation ) if( type == VIPS_TYPE_IMAGE ) { // rebox object - VipsImage *image = VIPS_IMAGE( - g_value_get_object( value ) ); - *((*i)->vimage) = VImage( image ); + VipsImage *image = VIPS_IMAGE( + g_value_get_object( value ) ); + *((*i)->vimage) = VImage( image ); } - else if( type == G_TYPE_INT ) - *((*i)->vint) = g_value_get_int( value ); - else if( type == G_TYPE_BOOLEAN ) - *((*i)->vbool) = g_value_get_boolean( value ); - else if( type == G_TYPE_DOUBLE ) - *((*i)->vdouble) = g_value_get_double( value ); + else if( type == G_TYPE_INT ) + *((*i)->vint) = g_value_get_int( value ); + else if( type == G_TYPE_BOOLEAN ) + *((*i)->vbool) = g_value_get_boolean( value ); + else if( type == G_TYPE_DOUBLE ) + *((*i)->vdouble) = g_value_get_double( value ); else if( type == VIPS_TYPE_ARRAY_DOUBLE ) { int length; - double *array = - vips_value_get_array_double( value, + double *array = + vips_value_get_array_double( value, &length ); int j; - ((*i)->vvector)->resize( length ); + ((*i)->vvector)->resize( length ); for( j = 0; j < length; j++ ) (*((*i)->vvector))[j] = array[j]; } else if( type == VIPS_TYPE_BLOB ) { // our caller gets a reference - *((*i)->vblob) = + *((*i)->vblob) = (VipsBlob *) g_value_dup_boxed( value ); } } } -void -VImage::call_option_string( const char *operation_name, - const char *option_string, VOption *options ) +void +VImage::call_option_string( const char *operation_name, + const char *option_string, VOption *options ) { VipsOperation *operation; - VIPS_DEBUG_MSG( "call_option_string: starting for %s ...\n", + VIPS_DEBUG_MSG( "call_option_string: starting for %s ...\n", operation_name ); if( !(operation = vips_operation_new( operation_name )) ) { delete options; - throw( VError() ); + throw( VError() ); } - /* Set str options before vargs options, so the user can't + /* Set str options before vargs options, so the user can't * override things we set deliberately. */ if( option_string && - vips_object_set_from_string( VIPS_OBJECT( operation ), + vips_object_set_from_string( VIPS_OBJECT( operation ), option_string ) ) { vips_object_unref_outputs( VIPS_OBJECT( operation ) ); - g_object_unref( operation ); - delete options; - throw( VError() ); + g_object_unref( operation ); + delete options; + throw( VError() ); } if( options ) @@ -512,9 +513,9 @@ VImage::call_option_string( const char *operation_name, */ if( vips_cache_operation_buildp( &operation ) ) { vips_object_unref_outputs( VIPS_OBJECT( operation ) ); - g_object_unref( operation ); - delete options; - throw( VError() ); + g_object_unref( operation ); + delete options; + throw( VError() ); } /* Walk args again, writing output. @@ -524,46 +525,46 @@ VImage::call_option_string( const char *operation_name, /* We're done with options! */ - delete options; + delete options; - /* The operation we have built should now have been reffed by - * one of its arguments or have finished its work. Either + /* The operation we have built should now have been reffed by + * one of its arguments or have finished its work. Either * way, we can unref. */ g_object_unref( operation ); } -void -VImage::call( const char *operation_name, VOption *options ) +void +VImage::call( const char *operation_name, VOption *options ) { - call_option_string( operation_name, NULL, options ); + call_option_string( operation_name, NULL, options ); } -VImage +VImage VImage::new_from_file( const char *name, VOption *options ) { char filename[VIPS_PATH_MAX]; char option_string[VIPS_PATH_MAX]; const char *operation_name; - VImage out; + VImage out; vips__filename_split8( name, filename, option_string ); if( !(operation_name = vips_foreign_find_load( filename )) ) { - delete options; - throw VError(); + delete options; + throw VError(); } call_option_string( operation_name, option_string, - (options ? options : VImage::option())-> + (options ? options : VImage::option())-> set( "filename", filename )-> set( "out", &out ) ); - return( out ); + return( out ); } -VImage -VImage::new_from_buffer( const void *buf, size_t len, const char *option_string, +VImage +VImage::new_from_buffer( const void *buf, size_t len, const char *option_string, VOption *options ) { const char *operation_name; @@ -571,64 +572,87 @@ VImage::new_from_buffer( const void *buf, size_t len, const char *option_string, VImage out; if( !(operation_name = vips_foreign_find_load_buffer( buf, len )) ) { - delete options; - throw( VError() ); + delete options; + throw( VError() ); } /* We don't take a copy of the data or free it. */ blob = vips_blob_new( NULL, buf, len ); - options = (options ? options : VImage::option())-> + options = (options ? options : VImage::option())-> set( "buffer", blob )-> set( "out", &out ); vips_area_unref( VIPS_AREA( blob ) ); - call_option_string( operation_name, option_string, options ); + call_option_string( operation_name, option_string, options ); return( out ); } -VImage -VImage::new_from_buffer( const std::string &buf, const char *option_string, +VImage +VImage::new_from_buffer( const std::string &buf, const char *option_string, VOption *options ) { - return( new_from_buffer( buf.c_str(), buf.size(), option_string, options ) ); + return( new_from_buffer( buf.c_str(), buf.size(), + option_string, options ) ); } -VImage -VImage::new_matrix( int width, int height ) +VImage +VImage::new_from_source( VSource source, const char *option_string, + VOption *options ) { - return( VImage( vips_image_new_matrix( width, height ) ) ); + const char *operation_name; + VImage out; + + if( !(operation_name = vips_foreign_find_load_source( + source.get_source() )) ) { + delete options; + throw( VError() ); + } + + options = (options ? options : VImage::option())-> + set( "source", source )-> + set( "out", &out ); + + call_option_string( operation_name, option_string, options ); + + return( out ); } -VImage +VImage +VImage::new_matrix( int width, int height ) +{ + return( VImage( vips_image_new_matrix( width, height ) ) ); +} + +VImage VImage::new_matrixv( int width, int height, ... ) { VImage matrix = new_matrix( width, height ); - VipsImage *vips_matrix = matrix.get_image(); + VipsImage *vips_matrix = matrix.get_image(); va_list ap; va_start( ap, height ); for( int y = 0; y < height; y++ ) for( int x = 0; x < width; x++ ) - *VIPS_MATRIX( vips_matrix, x, y ) = + *VIPS_MATRIX( vips_matrix, x, y ) = va_arg( ap, double ); va_end( ap ); - return( matrix ); + return( matrix ); } VImage VImage::write( VImage out ) const { if( vips_image_write( this->get_image(), out.get_image() ) ) - throw VError(); + throw VError(); - return( out ); + return( out ); } -void +void VImage::write_to_file( const char *name, VOption *options ) const { char filename[VIPS_PATH_MAX]; @@ -637,18 +661,18 @@ VImage::write_to_file( const char *name, VOption *options ) const vips__filename_split8( name, filename, option_string ); if( !(operation_name = vips_foreign_find_save( filename )) ) { - delete options; - throw VError(); + delete options; + throw VError(); } - call_option_string( operation_name, option_string, - (options ? options : VImage::option())-> + call_option_string( operation_name, option_string, + (options ? options : VImage::option())-> set( "in", *this )-> set( "filename", filename ) ); } -void -VImage::write_to_buffer( const char *suffix, void **buf, size_t *size, +void +VImage::write_to_buffer( const char *suffix, void **buf, size_t *size, VOption *options ) const { char filename[VIPS_PATH_MAX]; @@ -658,16 +682,16 @@ VImage::write_to_buffer( const char *suffix, void **buf, size_t *size, vips__filename_split8( suffix, filename, option_string ); if( !(operation_name = vips_foreign_find_save_buffer( filename )) ) { - delete options; - throw VError(); + delete options; + throw VError(); } - call_option_string( operation_name, option_string, - (options ? options : VImage::option())-> + call_option_string( operation_name, option_string, + (options ? options : VImage::option())-> set( "in", *this )-> set( "buffer", &blob ) ); - if( blob ) { + if( blob ) { if( buf ) { *buf = VIPS_AREA( blob )->data; VIPS_AREA( blob )->free_fn = NULL; @@ -679,745 +703,765 @@ VImage::write_to_buffer( const char *suffix, void **buf, size_t *size, } } +void +VImage::write_to_target( const char *suffix, VTarget target, + VOption *options ) const +{ + char filename[VIPS_PATH_MAX]; + char option_string[VIPS_PATH_MAX]; + const char *operation_name; + + vips__filename_split8( suffix, filename, option_string ); + if( !(operation_name = vips_foreign_find_save_target( filename )) ) { + delete options; + throw VError(); + } + + call_option_string( operation_name, option_string, + (options ? options : VImage::option())-> + set( "in", *this )-> + set( "target", target ) ); +} + #include "vips-operators.cpp" -std::vector +std::vector VImage::bandsplit( VOption *options ) const { - std::vector b; + std::vector b; for( int i = 0; i < bands(); i++ ) - b.push_back( extract_band( i ) ); + b.push_back( extract_band( i ) ); - return( b ); + return( b ); } -VImage +VImage VImage::bandjoin( VImage other, VOption *options ) const { - VImage v[2] = { *this, other }; + VImage v[2] = { *this, other }; std::vector vec( v, v + VIPS_NUMBER( v ) ); - return( bandjoin( vec, options ) ); + return( bandjoin( vec, options ) ); } -VImage +VImage VImage::composite( VImage other, VipsBlendMode mode, VOption *options ) const { - VImage v[2] = { *this, other }; + VImage v[2] = { *this, other }; std::vector ivec( v, v + VIPS_NUMBER( v ) ); - int m[1] = { static_cast( mode ) }; + int m[1] = { static_cast( mode ) }; std::vector mvec( m, m + VIPS_NUMBER( m ) ); - return( composite( ivec, mvec, options ) ); + return( composite( ivec, mvec, options ) ); } -std::complex +std::complex VImage::minpos( VOption *options ) const { double x, y; - (void) min( + (void) min( (options ? options : VImage::option()) -> set( "x", &x ) -> set( "y", &y ) ); - return( std::complex( x, y ) ); + return( std::complex( x, y ) ); } -std::complex +std::complex VImage::maxpos( VOption *options ) const { double x, y; - (void) max( + (void) max( (options ? options : VImage::option()) -> set( "x", &x ) -> set( "y", &y ) ); - return( std::complex( x, y ) ); + return( std::complex( x, y ) ); } // Operator overloads -VImage +VImage VImage::operator[]( int index ) const { - return( this->extract_band( index ) ); + return( this->extract_band( index ) ); } -std::vector +std::vector VImage::operator()( int x, int y ) const { - return( this->getpoint( x, y ) ); + return( this->getpoint( x, y ) ); } -VImage +VImage operator+( const VImage a, const VImage b ) { return( a.add( b ) ); } -VImage -operator+( double a, const VImage b ) +VImage +operator+( double a, const VImage b ) { - return( b.linear( 1.0, a ) ); + return( b.linear( 1.0, a ) ); } -VImage -operator+( const VImage a, double b ) -{ - return( a.linear( 1.0, b ) ); -} - -VImage -operator+( const std::vector a, const VImage b ) +VImage +operator+( const VImage a, double b ) { - return( b.linear( 1.0, a ) ); + return( a.linear( 1.0, b ) ); } -VImage -operator+( const VImage a, const std::vector b ) -{ - return( a.linear( 1.0, b ) ); +VImage +operator+( const std::vector a, const VImage b ) +{ + return( b.linear( 1.0, a ) ); } -VImage & +VImage +operator+( const VImage a, const std::vector b ) +{ + return( a.linear( 1.0, b ) ); +} + +VImage & operator+=( VImage &a, const VImage b ) { - return( a = a + b ); + return( a = a + b ); } -VImage & +VImage & operator+=( VImage &a, const double b ) { - return( a = a + b ); + return( a = a + b ); } -VImage & +VImage & operator+=( VImage &a, const std::vector b ) { - return( a = a + b ); + return( a = a + b ); } -VImage -operator-( const VImage a, const VImage b ) +VImage +operator-( const VImage a, const VImage b ) { return( a.subtract( b ) ); } -VImage -operator-( double a, const VImage b ) +VImage +operator-( double a, const VImage b ) { - return( b.linear( -1.0, a ) ); + return( b.linear( -1.0, a ) ); } -VImage -operator-( const VImage a, double b ) -{ - return( a.linear( 1.0, -b ) ); -} - -VImage -operator-( const std::vector a, const VImage b ) +VImage +operator-( const VImage a, double b ) { - return( b.linear( -1.0, a ) ); + return( a.linear( 1.0, -b ) ); } -VImage -operator-( const VImage a, const std::vector b ) -{ - return( a.linear( 1.0, vips::negate( b ) ) ); +VImage +operator-( const std::vector a, const VImage b ) +{ + return( b.linear( -1.0, a ) ); } -VImage & +VImage +operator-( const VImage a, const std::vector b ) +{ + return( a.linear( 1.0, vips::negate( b ) ) ); +} + +VImage & operator-=( VImage &a, const VImage b ) { - return( a = a - b ); + return( a = a - b ); } -VImage & +VImage & operator-=( VImage &a, const double b ) { - return( a = a - b ); + return( a = a - b ); } -VImage & +VImage & operator-=( VImage &a, const std::vector b ) { - return( a = a - b ); + return( a = a - b ); } -VImage -operator-( const VImage a ) -{ +VImage +operator-( const VImage a ) +{ return( a * -1 ); } -VImage -operator*( const VImage a, const VImage b ) +VImage +operator*( const VImage a, const VImage b ) { return( a.multiply( b ) ); } -VImage -operator*( double a, const VImage b ) +VImage +operator*( double a, const VImage b ) { - return( b.linear( a, 0.0 ) ); + return( b.linear( a, 0.0 ) ); } -VImage -operator*( const VImage a, double b ) -{ - return( a.linear( b, 0.0 ) ); -} - -VImage -operator*( const std::vector a, const VImage b ) +VImage +operator*( const VImage a, double b ) { - return( b.linear( a, 0.0 ) ); + return( a.linear( b, 0.0 ) ); } -VImage -operator*( const VImage a, const std::vector b ) -{ - return( a.linear( b, 0.0 ) ); +VImage +operator*( const std::vector a, const VImage b ) +{ + return( b.linear( a, 0.0 ) ); } -VImage & +VImage +operator*( const VImage a, const std::vector b ) +{ + return( a.linear( b, 0.0 ) ); +} + +VImage & operator*=( VImage &a, const VImage b ) { - return( a = a * b ); + return( a = a * b ); } -VImage & +VImage & operator*=( VImage &a, const double b ) { - return( a = a * b ); + return( a = a * b ); } -VImage & +VImage & operator*=( VImage &a, const std::vector b ) { - return( a = a * b ); + return( a = a * b ); } -VImage -operator/( const VImage a, const VImage b ) +VImage +operator/( const VImage a, const VImage b ) { return( a.divide( b ) ); } -VImage -operator/( double a, const VImage b ) +VImage +operator/( double a, const VImage b ) { - return( b.pow( -1.0 ).linear( a, 0.0 ) ); + return( b.pow( -1.0 ).linear( a, 0.0 ) ); } -VImage -operator/( const VImage a, double b ) -{ - return( a.linear( 1.0 / b, 0.0 ) ); -} - -VImage -operator/( const std::vector a, const VImage b ) +VImage +operator/( const VImage a, double b ) { - return( b.pow( -1.0 ).linear( a, 0.0 ) ); + return( a.linear( 1.0 / b, 0.0 ) ); } -VImage -operator/( const VImage a, const std::vector b ) -{ - return( a.linear( vips::invert( b ), 0.0 ) ); +VImage +operator/( const std::vector a, const VImage b ) +{ + return( b.pow( -1.0 ).linear( a, 0.0 ) ); } -VImage & +VImage +operator/( const VImage a, const std::vector b ) +{ + return( a.linear( vips::invert( b ), 0.0 ) ); +} + +VImage & operator/=( VImage &a, const VImage b ) { - return( a = a / b ); + return( a = a / b ); } -VImage & +VImage & operator/=( VImage &a, const double b ) { - return( a = a / b ); + return( a = a / b ); } -VImage & +VImage & operator/=( VImage &a, const std::vector b ) { - return( a = a / b ); + return( a = a / b ); } -VImage -operator%( const VImage a, const VImage b ) +VImage +operator%( const VImage a, const VImage b ) { return( a.remainder( b ) ); } -VImage -operator%( const VImage a, const double b ) -{ - return( a.remainder_const( to_vector( b ) ) ); +VImage +operator%( const VImage a, const double b ) +{ + return( a.remainder_const( to_vector( b ) ) ); } -VImage -operator%( const VImage a, const std::vector b ) -{ - return( a.remainder_const( b ) ); +VImage +operator%( const VImage a, const std::vector b ) +{ + return( a.remainder_const( b ) ); } -VImage & +VImage & operator%=( VImage &a, const VImage b ) { - return( a = a % b ); + return( a = a % b ); } -VImage & +VImage & operator%=( VImage &a, const double b ) { - return( a = a % b ); + return( a = a % b ); } -VImage & +VImage & operator%=( VImage &a, const std::vector b ) { - return( a = a % b ); + return( a = a % b ); } -VImage -operator<( const VImage a, const VImage b ) +VImage +operator<( const VImage a, const VImage b ) { return( a.relational( b, VIPS_OPERATION_RELATIONAL_LESS ) ); } -VImage -operator<( const double a, const VImage b ) +VImage +operator<( const double a, const VImage b ) { - return( b.relational_const( VIPS_OPERATION_RELATIONAL_MORE, - to_vector( a ) ) ); + return( b.relational_const( VIPS_OPERATION_RELATIONAL_MORE, + to_vector( a ) ) ); } -VImage -operator<( const VImage a, const double b ) -{ - return( a.relational_const( VIPS_OPERATION_RELATIONAL_LESS, - to_vector( b ) ) ); -} - -VImage -operator<( const std::vector a, const VImage b ) +VImage +operator<( const VImage a, const double b ) { - return( b.relational_const( VIPS_OPERATION_RELATIONAL_MORE, - a ) ); + return( a.relational_const( VIPS_OPERATION_RELATIONAL_LESS, + to_vector( b ) ) ); } -VImage -operator<( const VImage a, const std::vector b ) -{ - return( a.relational_const( VIPS_OPERATION_RELATIONAL_LESS, - b ) ); +VImage +operator<( const std::vector a, const VImage b ) +{ + return( b.relational_const( VIPS_OPERATION_RELATIONAL_MORE, + a ) ); } -VImage -operator<=( const VImage a, const VImage b ) +VImage +operator<( const VImage a, const std::vector b ) +{ + return( a.relational_const( VIPS_OPERATION_RELATIONAL_LESS, + b ) ); +} + +VImage +operator<=( const VImage a, const VImage b ) { return( a.relational( b, VIPS_OPERATION_RELATIONAL_LESSEQ ) ); } -VImage -operator<=( const double a, const VImage b ) +VImage +operator<=( const double a, const VImage b ) { - return( b.relational_const( VIPS_OPERATION_RELATIONAL_MOREEQ, - to_vector( a ) ) ); + return( b.relational_const( VIPS_OPERATION_RELATIONAL_MOREEQ, + to_vector( a ) ) ); } -VImage -operator<=( const VImage a, const double b ) -{ - return( a.relational_const( VIPS_OPERATION_RELATIONAL_LESSEQ, - to_vector( b ) ) ); +VImage +operator<=( const VImage a, const double b ) +{ + return( a.relational_const( VIPS_OPERATION_RELATIONAL_LESSEQ, + to_vector( b ) ) ); } -VImage -operator<=( const std::vector a, const VImage b ) +VImage +operator<=( const std::vector a, const VImage b ) { - return( b.relational_const( VIPS_OPERATION_RELATIONAL_MOREEQ, + return( b.relational_const( VIPS_OPERATION_RELATIONAL_MOREEQ, a ) ); } -VImage -operator<=( const VImage a, const std::vector b ) -{ +VImage +operator<=( const VImage a, const std::vector b ) +{ return( a.relational_const( VIPS_OPERATION_RELATIONAL_LESSEQ, b ) ); } -VImage -operator>( const VImage a, const VImage b ) +VImage +operator>( const VImage a, const VImage b ) { return( a.relational( b, VIPS_OPERATION_RELATIONAL_MORE ) ); } -VImage -operator>( const double a, const VImage b ) +VImage +operator>( const double a, const VImage b ) { - return( b.relational_const( VIPS_OPERATION_RELATIONAL_LESS, - to_vector( a ) ) ); + return( b.relational_const( VIPS_OPERATION_RELATIONAL_LESS, + to_vector( a ) ) ); } -VImage -operator>( const VImage a, const double b ) -{ - return( a.relational_const( VIPS_OPERATION_RELATIONAL_MORE, +VImage +operator>( const VImage a, const double b ) +{ + return( a.relational_const( VIPS_OPERATION_RELATIONAL_MORE, to_vector( b ) ) ); } -VImage -operator>( const std::vector a, const VImage b ) +VImage +operator>( const std::vector a, const VImage b ) { - return( b.relational_const( VIPS_OPERATION_RELATIONAL_LESS, + return( b.relational_const( VIPS_OPERATION_RELATIONAL_LESS, a ) ); } -VImage -operator>( const VImage a, const std::vector b ) -{ - return( a.relational_const( VIPS_OPERATION_RELATIONAL_MORE, +VImage +operator>( const VImage a, const std::vector b ) +{ + return( a.relational_const( VIPS_OPERATION_RELATIONAL_MORE, b ) ); } -VImage -operator>=( const VImage a, const VImage b ) +VImage +operator>=( const VImage a, const VImage b ) { return( a.relational( b, VIPS_OPERATION_RELATIONAL_MOREEQ ) ); } -VImage -operator>=( const double a, const VImage b ) +VImage +operator>=( const double a, const VImage b ) { - return( b.relational_const( VIPS_OPERATION_RELATIONAL_LESSEQ, - to_vector( a ) ) ); + return( b.relational_const( VIPS_OPERATION_RELATIONAL_LESSEQ, + to_vector( a ) ) ); } -VImage -operator>=( const VImage a, const double b ) -{ - return( a.relational_const( VIPS_OPERATION_RELATIONAL_MOREEQ, - to_vector( b ) ) ); +VImage +operator>=( const VImage a, const double b ) +{ + return( a.relational_const( VIPS_OPERATION_RELATIONAL_MOREEQ, + to_vector( b ) ) ); } -VImage -operator>=( const std::vector a, const VImage b ) +VImage +operator>=( const std::vector a, const VImage b ) { - return( b.relational_const( VIPS_OPERATION_RELATIONAL_LESSEQ, + return( b.relational_const( VIPS_OPERATION_RELATIONAL_LESSEQ, a ) ); } -VImage -operator>=( const VImage a, const std::vector b ) -{ - return( a.relational_const( VIPS_OPERATION_RELATIONAL_MOREEQ, +VImage +operator>=( const VImage a, const std::vector b ) +{ + return( a.relational_const( VIPS_OPERATION_RELATIONAL_MOREEQ, b ) ); } -VImage -operator==( const VImage a, const VImage b ) +VImage +operator==( const VImage a, const VImage b ) { return( a.relational( b, VIPS_OPERATION_RELATIONAL_EQUAL ) ); } -VImage -operator==( const double a, const VImage b ) +VImage +operator==( const double a, const VImage b ) { - return( b.relational_const( VIPS_OPERATION_RELATIONAL_EQUAL, + return( b.relational_const( VIPS_OPERATION_RELATIONAL_EQUAL, to_vector( a ) ) ); } -VImage -operator==( const VImage a, const double b ) -{ - return( a.relational_const( VIPS_OPERATION_RELATIONAL_EQUAL, +VImage +operator==( const VImage a, const double b ) +{ + return( a.relational_const( VIPS_OPERATION_RELATIONAL_EQUAL, to_vector( b ) ) ); } -VImage -operator==( const std::vector a, const VImage b ) +VImage +operator==( const std::vector a, const VImage b ) { - return( b.relational_const( VIPS_OPERATION_RELATIONAL_EQUAL, + return( b.relational_const( VIPS_OPERATION_RELATIONAL_EQUAL, a ) ); } -VImage -operator==( const VImage a, const std::vector b ) -{ - return( a.relational_const( VIPS_OPERATION_RELATIONAL_EQUAL, +VImage +operator==( const VImage a, const std::vector b ) +{ + return( a.relational_const( VIPS_OPERATION_RELATIONAL_EQUAL, b ) ); } -VImage -operator!=( const VImage a, const VImage b ) +VImage +operator!=( const VImage a, const VImage b ) { return( a.relational( b, VIPS_OPERATION_RELATIONAL_NOTEQ ) ); } -VImage -operator!=( const double a, const VImage b ) +VImage +operator!=( const double a, const VImage b ) { return( b.relational_const( VIPS_OPERATION_RELATIONAL_NOTEQ, - to_vector( a ) ) ); + to_vector( a ) ) ); } -VImage -operator!=( const VImage a, const double b ) -{ - return( a.relational_const( VIPS_OPERATION_RELATIONAL_NOTEQ, +VImage +operator!=( const VImage a, const double b ) +{ + return( a.relational_const( VIPS_OPERATION_RELATIONAL_NOTEQ, to_vector( b ) ) ); } -VImage -operator!=( const std::vector a, const VImage b ) +VImage +operator!=( const std::vector a, const VImage b ) { return( b.relational_const( VIPS_OPERATION_RELATIONAL_NOTEQ, a ) ); } -VImage -operator!=( const VImage a, const std::vector b ) -{ +VImage +operator!=( const VImage a, const std::vector b ) +{ return( a.relational_const( VIPS_OPERATION_RELATIONAL_NOTEQ, b ) ); } -VImage -operator&( const VImage a, const VImage b ) +VImage +operator&( const VImage a, const VImage b ) { return( a.boolean( b, VIPS_OPERATION_BOOLEAN_AND ) ); } -VImage -operator&( const double a, const VImage b ) +VImage +operator&( const double a, const VImage b ) { - return( b.boolean_const( VIPS_OPERATION_BOOLEAN_AND, - to_vector( a ) ) ); + return( b.boolean_const( VIPS_OPERATION_BOOLEAN_AND, + to_vector( a ) ) ); } -VImage -operator&( const VImage a, const double b ) -{ - return( a.boolean_const( VIPS_OPERATION_BOOLEAN_AND, +VImage +operator&( const VImage a, const double b ) +{ + return( a.boolean_const( VIPS_OPERATION_BOOLEAN_AND, to_vector( b ) ) ); } -VImage -operator&( const std::vector a, const VImage b ) +VImage +operator&( const std::vector a, const VImage b ) { return( b.boolean_const( VIPS_OPERATION_BOOLEAN_AND, a ) ); } -VImage -operator&( const VImage a, const std::vector b ) -{ +VImage +operator&( const VImage a, const std::vector b ) +{ return( a.boolean_const( VIPS_OPERATION_BOOLEAN_AND, b ) ); } -VImage & +VImage & operator&=( VImage &a, const VImage b ) { - return( a = a & b ); + return( a = a & b ); } -VImage & +VImage & operator&=( VImage &a, const double b ) { - return( a = a & b ); + return( a = a & b ); } -VImage & +VImage & operator&=( VImage &a, const std::vector b ) { - return( a = a & b ); + return( a = a & b ); } -VImage -operator|( const VImage a, const VImage b ) +VImage +operator|( const VImage a, const VImage b ) { return( a.boolean( b, VIPS_OPERATION_BOOLEAN_OR ) ); } -VImage -operator|( const double a, const VImage b ) +VImage +operator|( const double a, const VImage b ) { - return( b.boolean_const( VIPS_OPERATION_BOOLEAN_OR, - to_vector( a ) ) ); + return( b.boolean_const( VIPS_OPERATION_BOOLEAN_OR, + to_vector( a ) ) ); } -VImage -operator|( const VImage a, const double b ) -{ - return( a.boolean_const( VIPS_OPERATION_BOOLEAN_OR, +VImage +operator|( const VImage a, const double b ) +{ + return( a.boolean_const( VIPS_OPERATION_BOOLEAN_OR, to_vector( b ) ) ); } -VImage -operator|( const std::vector a, const VImage b ) +VImage +operator|( const std::vector a, const VImage b ) { - return( b.boolean_const( VIPS_OPERATION_BOOLEAN_OR, + return( b.boolean_const( VIPS_OPERATION_BOOLEAN_OR, a ) ); } -VImage -operator|( const VImage a, const std::vector b ) -{ - return( a.boolean_const( VIPS_OPERATION_BOOLEAN_OR, +VImage +operator|( const VImage a, const std::vector b ) +{ + return( a.boolean_const( VIPS_OPERATION_BOOLEAN_OR, b ) ); } -VImage & +VImage & operator|=( VImage &a, const VImage b ) { - return( a = a | b ); + return( a = a | b ); } -VImage & +VImage & operator|=( VImage &a, const double b ) { - return( a = a | b ); + return( a = a | b ); } -VImage & +VImage & operator|=( VImage &a, const std::vector b ) { - return( a = a | b ); + return( a = a | b ); } -VImage -operator^( const VImage a, const VImage b ) +VImage +operator^( const VImage a, const VImage b ) { return( a.boolean( b, VIPS_OPERATION_BOOLEAN_EOR ) ); } -VImage -operator^( const double a, const VImage b ) +VImage +operator^( const double a, const VImage b ) { - return( b.boolean_const( VIPS_OPERATION_BOOLEAN_EOR, - to_vector( a ) ) ); + return( b.boolean_const( VIPS_OPERATION_BOOLEAN_EOR, + to_vector( a ) ) ); } -VImage -operator^( const VImage a, const double b ) -{ - return( a.boolean_const( VIPS_OPERATION_BOOLEAN_EOR, - to_vector( b ) ) ); +VImage +operator^( const VImage a, const double b ) +{ + return( a.boolean_const( VIPS_OPERATION_BOOLEAN_EOR, + to_vector( b ) ) ); } -VImage -operator^( const std::vector a, const VImage b ) +VImage +operator^( const std::vector a, const VImage b ) { return( b.boolean_const( VIPS_OPERATION_BOOLEAN_EOR, a ) ); } -VImage -operator^( const VImage a, const std::vector b ) -{ +VImage +operator^( const VImage a, const std::vector b ) +{ return( a.boolean_const( VIPS_OPERATION_BOOLEAN_EOR, b ) ); } -VImage & +VImage & operator^=( VImage &a, const VImage b ) { - return( a = a ^ b ); + return( a = a ^ b ); } -VImage & +VImage & operator^=( VImage &a, const double b ) { - return( a = a ^ b ); + return( a = a ^ b ); } -VImage & +VImage & operator^=( VImage &a, const std::vector b ) { - return( a = a ^ b ); + return( a = a ^ b ); } -VImage -operator<<( const VImage a, const VImage b ) +VImage +operator<<( const VImage a, const VImage b ) { return( a.boolean( b, VIPS_OPERATION_BOOLEAN_LSHIFT ) ); } -VImage -operator<<( const VImage a, const double b ) -{ - return( a.boolean_const( VIPS_OPERATION_BOOLEAN_LSHIFT, - to_vector( b ) ) ); +VImage +operator<<( const VImage a, const double b ) +{ + return( a.boolean_const( VIPS_OPERATION_BOOLEAN_LSHIFT, + to_vector( b ) ) ); } -VImage -operator<<( const VImage a, const std::vector b ) -{ - return( a.boolean_const( VIPS_OPERATION_BOOLEAN_LSHIFT, - b ) ); +VImage +operator<<( const VImage a, const std::vector b ) +{ + return( a.boolean_const( VIPS_OPERATION_BOOLEAN_LSHIFT, + b ) ); } -VImage & +VImage & operator<<=( VImage &a, const VImage b ) { - return( a = a << b ); + return( a = a << b ); } -VImage & +VImage & operator<<=( VImage &a, const double b ) { - return( a = a << b ); + return( a = a << b ); } -VImage & +VImage & operator<<=( VImage &a, const std::vector b ) { - return( a = a << b ); + return( a = a << b ); } -VImage -operator>>( const VImage a, const VImage b ) +VImage +operator>>( const VImage a, const VImage b ) { return( a.boolean( b, VIPS_OPERATION_BOOLEAN_RSHIFT ) ); } -VImage -operator>>( const VImage a, const double b ) -{ - return( a.boolean_const( VIPS_OPERATION_BOOLEAN_RSHIFT, - to_vector( b ) ) ); -} - -VImage -operator>>( const VImage a, const std::vector b ) -{ +VImage +operator>>( const VImage a, const double b ) +{ return( a.boolean_const( VIPS_OPERATION_BOOLEAN_RSHIFT, - b ) ); + to_vector( b ) ) ); } -VImage & +VImage +operator>>( const VImage a, const std::vector b ) +{ + return( a.boolean_const( VIPS_OPERATION_BOOLEAN_RSHIFT, + b ) ); +} + +VImage & operator>>=( VImage &a, const VImage b ) { - return( a = a << b ); + return( a = a << b ); } -VImage & +VImage & operator>>=( VImage &a, const double b ) { - return( a = a << b ); + return( a = a << b ); } -VImage & +VImage & operator>>=( VImage &a, const std::vector b ) { - return( a = a << b ); + return( a = a << b ); } VIPS_NAMESPACE_END diff --git a/cplusplus/VInterpolate.cpp b/cplusplus/VInterpolate.cpp index 265bf66e..cb59715c 100644 --- a/cplusplus/VInterpolate.cpp +++ b/cplusplus/VInterpolate.cpp @@ -61,7 +61,7 @@ VInterpolate::new_from_name( const char *name, VOption *options ) } VOption * -VOption::set( const char *name, VInterpolate value ) +VOption::set( const char *name, const VInterpolate value ) { Pair *pair = new Pair( name ); diff --git a/cplusplus/gen-operators.py b/cplusplus/gen-operators.py index 03a63f63..cf3b3c75 100755 --- a/cplusplus/gen-operators.py +++ b/cplusplus/gen-operators.py @@ -25,7 +25,7 @@ import argparse -from pyvips import Operation, GValue, Error, \ +from pyvips import Introspect, Operation, GValue, Error, \ ffi, gobject_lib, type_map, type_from_name, nickname_find, type_name # turn a GType into a C++ type @@ -37,12 +37,17 @@ gtype_to_cpp = { GValue.refstr_type: 'char *', GValue.gflags_type: 'int', GValue.image_type: 'VImage', + GValue.source_type: 'VSource', + GValue.target_type: 'VTarget', GValue.array_int_type: 'std::vector', GValue.array_double_type: 'std::vector', GValue.array_image_type: 'std::vector', GValue.blob_type: 'VipsBlob *' } +cplusplus_suffixes = ('*', '&') +cplusplus_keywords = ('case', 'switch') + # values for VipsArgumentFlags _REQUIRED = 1 _INPUT = 16 @@ -78,67 +83,43 @@ def cppize(name): def generate_operation(operation_name, declaration_only=False): - op = Operation.new_from_name(operation_name) + intro = Introspect.get(operation_name) - # we are only interested in non-deprecated args - args = [[name, flags] for name, flags in op.get_args() - if not flags & _DEPRECATED] - - # find the first required input image arg, if any ... that will be self - member_x = None - for name, flags in args: - if ((flags & _INPUT) != 0 and - (flags & _REQUIRED) != 0 and - op.get_typeof(name) == GValue.image_type): - member_x = name - break - - required_input = [name for name, flags in args - if (flags & _INPUT) != 0 and - (flags & _REQUIRED) != 0 and - name != member_x] - - required_output = [name for name, flags in args - if ((flags & _OUTPUT) != 0 and - (flags & _REQUIRED) != 0) or - ((flags & _INPUT) != 0 and - (flags & _REQUIRED) != 0 and - (flags & _MODIFY) != 0) and - name != member_x] + required_output = [name for name in intro.required_output if name != intro.member_x] has_output = len(required_output) >= 1 - # Add a C++ style comment block with some additional markings (@param, + # Add a C++ style comment block with some additional markings (@param, # @return) if declaration_only: - result = '\n/**\n * {}.'.format(op.get_description().capitalize()) + result = '\n/**\n * {}.'.format(intro.description.capitalize()) - for name in required_input: + for name in intro.method_args: result += '\n * @param {} {}.' \ - .format(cppize(name), op.get_blurb(name)) + .format(cppize(name), intro.details[name]['blurb']) if has_output: # skip the first element for name in required_output[1:]: result += '\n * @param {} {}.' \ - .format(cppize(name), op.get_blurb(name)) + .format(cppize(name), intro.details[name]['blurb']) result += '\n * @param options Optional options.' if has_output: result += '\n * @return {}.' \ - .format(op.get_blurb(required_output[0])) + .format(intro.details[required_output[0]]['blurb']) result += '\n */\n' else: result = '\n' - if member_x is None and declaration_only: + if intro.member_x is None and declaration_only: result += 'static ' if has_output: # the first output arg will be used as the result - cpp_type = get_cpp_type(op.get_typeof(required_output[0])) - spacing = '' if cpp_type.endswith('*') else ' ' + cpp_type = get_cpp_type(intro.details[required_output[0]]['type']) + spacing = '' if cpp_type.endswith(cplusplus_suffixes) else ' ' result += '{0}{1}'.format(cpp_type, spacing) else: result += 'void ' @@ -146,26 +127,32 @@ def generate_operation(operation_name, declaration_only=False): if not declaration_only: result += 'VImage::' - result += '{0}( '.format(operation_name) - for name in required_input: - gtype = op.get_typeof(name) + cplusplus_operation = operation_name + if operation_name in cplusplus_keywords: + cplusplus_operation += '_image' + + result += '{0}( '.format(cplusplus_operation) + for name in intro.method_args: + details = intro.details[name] + gtype = details['type'] cpp_type = get_cpp_type(gtype) - spacing = '' if cpp_type.endswith('*') else ' ' + spacing = '' if cpp_type.endswith(cplusplus_suffixes) else ' ' result += '{0}{1}{2}, '.format(cpp_type, spacing, cppize(name)) # output params are passed by reference if has_output: # skip the first element for name in required_output[1:]: - gtype = op.get_typeof(name) + details = intro.details[name] + gtype = details['type'] cpp_type = get_cpp_type(gtype) - spacing = '' if cpp_type.endswith('*') else ' ' + spacing = '' if cpp_type.endswith(cplusplus_suffixes) else ' ' result += '{0}{1}*{2}, '.format(cpp_type, spacing, cppize(name)) result += 'VOption *options {0})'.format('= 0 ' if declaration_only else '') # if no 'this' available, it's a class method and they are all const - if member_x is not None: + if intro.member_x is not None: result += ' const' if declaration_only: @@ -178,17 +165,17 @@ def generate_operation(operation_name, declaration_only=False): if has_output: # the first output arg will be used as the result name = required_output[0] - cpp_type = get_cpp_type(op.get_typeof(name)) - spacing = '' if cpp_type.endswith('*') else ' ' + cpp_type = get_cpp_type(intro.details[name]['type']) + spacing = '' if cpp_type.endswith(cplusplus_suffixes) else ' ' result += ' {0}{1}{2};\n\n'.format(cpp_type, spacing, cppize(name)) result += ' call( "{0}",\n'.format(operation_name) result += ' (options ? options : VImage::option())' - if member_x is not None: + if intro.member_x is not None: result += '->\n' - result += ' set( "{0}", *this )'.format(member_x) + result += ' set( "{0}", *this )'.format(intro.member_x) - all_required = required_input + all_required = intro.method_args if has_output: # first element needs to be passed by reference @@ -223,10 +210,10 @@ def generate_operators(declarations_only=False): nickname = nickname_find(gtype) try: # can fail for abstract types - op = Operation.new_from_name(nickname) + intro = Introspect.get(nickname) # we are only interested in non-deprecated operations - if (op.get_flags() & _OPERATION_DEPRECATED) == 0: + if (intro.flags & _OPERATION_DEPRECATED) == 0: all_nicknames.append(nickname) except Error: pass diff --git a/cplusplus/include/vips/Makefile.am b/cplusplus/include/vips/Makefile.am index 36ea1eb3..52ccfd21 100644 --- a/cplusplus/include/vips/Makefile.am +++ b/cplusplus/include/vips/Makefile.am @@ -2,6 +2,7 @@ pkginclude_HEADERS = \ VError8.h \ VImage8.h \ VInterpolate8.h \ + VConnection8.h \ vips8 \ vips-operators.h diff --git a/cplusplus/include/vips/VConnection8.h b/cplusplus/include/vips/VConnection8.h new file mode 100644 index 00000000..15d4c902 --- /dev/null +++ b/cplusplus/include/vips/VConnection8.h @@ -0,0 +1,96 @@ +// VIPS connection wrapper + +/* + + 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 + + */ + +#ifndef VIPS_VCONNECTION_H +#define VIPS_VCONNECTION_H + +#include + +VIPS_NAMESPACE_START + +class VSource : VObject +{ +public: + VSource( VipsSource *input, VSteal steal = STEAL ) : + VObject( (VipsObject *) input, steal ) + { + } + + static + VSource new_from_descriptor( int descriptor ); + + static + VSource new_from_file( const char *filename ); + + static + VSource new_from_blob( VipsBlob *blob ); + + static + VSource new_from_memory( const void *data, + size_t size ); + + static + VSource new_from_options( const char *options ); + + VipsSource * + get_source() const + { + return( (VipsSource *) VObject::get_object() ); + } + +}; + +class VTarget : VObject +{ +public: + VTarget( VipsTarget *output, VSteal steal = STEAL ) : + VObject( (VipsObject *) output, steal ) + { + } + + static + VTarget new_to_descriptor( int descriptor ); + + static + VTarget new_to_file( const char *filename ); + + static + VTarget new_to_memory(); + + VipsTarget * + get_target() const + { + return( (VipsTarget *) VObject::get_object() ); + } + +}; + +VIPS_NAMESPACE_END + +#endif /*VIPS_VCONNECTION_H*/ diff --git a/cplusplus/include/vips/VImage8.h b/cplusplus/include/vips/VImage8.h index 3e67896b..99b61a3b 100644 --- a/cplusplus/include/vips/VImage8.h +++ b/cplusplus/include/vips/VImage8.h @@ -81,7 +81,7 @@ public: } #endif /*VIPS_DEBUG_VERBOSE*/ - if( !steal ) { + if( !steal && vobject ) { #ifdef VIPS_DEBUG_VERBOSE printf( " reffing object\n" ); #endif /*VIPS_DEBUG_VERBOSE*/ @@ -98,22 +98,21 @@ public: VObject( const VObject &a ) : vobject( a.vobject ) { - g_assert( VIPS_IS_OBJECT( a.vobject ) ); + g_assert( !vobject || + VIPS_IS_OBJECT( vobject ) ); #ifdef VIPS_DEBUG_VERBOSE printf( "VObject copy constructor, obj = %p\n", vobject ); printf( " reffing object\n" ); #endif /*VIPS_DEBUG_VERBOSE*/ - g_object_ref( vobject ); + if( vobject ) + g_object_ref( vobject ); } // assignment ... we must delete the old ref - // old can be NULL, new must not be NULL VObject &operator=( const VObject &a ) { - VipsObject *old_vobject; - #ifdef VIPS_DEBUG_VERBOSE printf( "VObject assignment\n" ); printf( " reffing %p\n", a.vobject ); @@ -122,16 +121,16 @@ public: g_assert( !vobject || VIPS_IS_OBJECT( vobject ) ); - g_assert( a.vobject && + g_assert( !a.vobject || VIPS_IS_OBJECT( a.vobject ) ); // delete the old ref at the end ... otherwise "a = a;" could // unref before reffing again - old_vobject = vobject; + if( a.vobject ) + g_object_ref( a.vobject ); + if( vobject ) + g_object_unref( vobject ); vobject = a.vobject; - g_object_ref( vobject ); - if( old_vobject ) - g_object_unref( old_vobject ); return( *this ); } @@ -169,6 +168,8 @@ public: class VIPS_CPLUSPLUS_API VImage; class VIPS_CPLUSPLUS_API VInterpolate; +class VIPS_CPLUSPLUS_API VSource; +class VIPS_CPLUSPLUS_API VTarget; class VIPS_CPLUSPLUS_API VOption; class VOption @@ -220,8 +221,10 @@ public: VOption *set( const char *name, int value ); VOption *set( const char *name, double value ); VOption *set( const char *name, const char *value ); - VOption *set( const char *name, VImage value ); - VOption *set( const char *name, VInterpolate value ); + VOption *set( const char *name, const VImage value ); + VOption *set( const char *name, const VInterpolate value ); + VOption *set( const char *name, const VSource value ); + VOption *set( const char *name, const VTarget value ); VOption *set( const char *name, std::vector value ); VOption *set( const char *name, std::vector value ); VOption *set( const char *name, std::vector value ); @@ -510,6 +513,9 @@ public: static VImage new_from_buffer( const std::string &buf, const char *option_string, VOption *options = 0 ); + static VImage new_from_source( VSource source, + const char *option_string, VOption *options = 0 ); + static VImage new_matrix( int width, int height ); static VImage @@ -562,6 +568,9 @@ public: void write_to_buffer( const char *suffix, void **buf, size_t *size, VOption *options = 0 ) const; + void write_to_target( const char *suffix, VTarget target, + VOption *options = 0 ) const; + void * write_to_memory( size_t *size ) const { diff --git a/cplusplus/include/vips/VInterpolate8.h b/cplusplus/include/vips/VInterpolate8.h index 8ef6f99e..43ef2429 100644 --- a/cplusplus/include/vips/VInterpolate8.h +++ b/cplusplus/include/vips/VInterpolate8.h @@ -30,12 +30,6 @@ #ifndef VIPS_VINTERPOLATE_H #define VIPS_VINTERPOLATE_H -#include -#include -#include - -#include - #include VIPS_NAMESPACE_START @@ -61,4 +55,4 @@ public: VIPS_NAMESPACE_END -#endif /*VIPS_VIMAGE_H*/ +#endif /*VIPS_VINTERPOLATE_H*/ diff --git a/cplusplus/include/vips/vips-operators.h b/cplusplus/include/vips/vips-operators.h index f59ce970..481600da 100644 --- a/cplusplus/include/vips/vips-operators.h +++ b/cplusplus/include/vips/vips-operators.h @@ -1,5 +1,5 @@ // headers for vips operations -// Wed Apr 24 15:50:21 CEST 2019 +// Thu 18 Jun 2020 01:19:31 PM CEST // this file is generated automatically, do not edit! /** @@ -296,6 +296,14 @@ VImage cache( VOption *options = 0 ) const; */ VImage canny( VOption *options = 0 ) const; +/** + * Use pixel values to pick cases from an array of images. + * @param cases Array of case images. + * @param options Optional options. + * @return Output image. + */ +VImage case_image( std::vector cases, VOption *options = 0 ) const; + /** * Cast an image. * @param format Format to cast to. @@ -446,7 +454,7 @@ double countlines( VipsDirection direction, VOption *options = 0 ) const; VImage crop( int left, int top, int width, int height, VOption *options = 0 ) const; /** - * Load csv from file. + * Load csv. * @param filename Filename to load from. * @param options Optional options. * @return Output image. @@ -454,12 +462,27 @@ VImage crop( int left, int top, int width, int height, VOption *options = 0 ) co static VImage csvload( const char *filename, VOption *options = 0 ); /** - * Save image to csv file. + * Load csv. + * @param source Source to load from. + * @param options Optional options. + * @return Output image. + */ +static VImage csvload_source( VSource source, VOption *options = 0 ); + +/** + * Save image to csv. * @param filename Filename to save to. * @param options Optional options. */ void csvsave( const char *filename, VOption *options = 0 ) const; +/** + * Save image to csv. + * @param target Target to save to. + * @param options Optional options. + */ +void csvsave_target( VTarget target, VOption *options = 0 ) const; + /** * Calculate de00. * @param right Right-hand input image. @@ -774,6 +797,14 @@ static VImage gifload( const char *filename, VOption *options = 0 ); */ static VImage gifload_buffer( VipsBlob *buffer, VOption *options = 0 ); +/** + * Load gif with giflib. + * @param source Source to load from. + * @param options Optional options. + * @return Output image. + */ +static VImage gifload_source( VSource source, VOption *options = 0 ); + /** * Global balance an image mosaic. * @param options Optional options. @@ -826,6 +857,14 @@ static VImage heifload( const char *filename, VOption *options = 0 ); */ static VImage heifload_buffer( VipsBlob *buffer, VOption *options = 0 ); +/** + * Load a heif image. + * @param source Source to load from. + * @param options Optional options. + * @return Output image. + */ +static VImage heifload_source( VSource source, VOption *options = 0 ); + /** * Save image in heif format. * @param filename Filename to load from. @@ -840,6 +879,13 @@ void heifsave( const char *filename, VOption *options = 0 ) const; */ VipsBlob *heifsave_buffer( VOption *options = 0 ) const; +/** + * Save image in heif format. + * @param target Target to save to. + * @param options Optional options. + */ +void heifsave_target( VTarget target, VOption *options = 0 ) const; + /** * Form cumulative histogram. * @param options Optional options. @@ -1029,6 +1075,14 @@ static VImage jpegload( const char *filename, VOption *options = 0 ); */ static VImage jpegload_buffer( VipsBlob *buffer, VOption *options = 0 ); +/** + * Load image from jpeg source. + * @param source Source to load from. + * @param options Optional options. + * @return Output image. + */ +static VImage jpegload_source( VSource source, VOption *options = 0 ); + /** * Save image to jpeg file. * @param filename Filename to save to. @@ -1049,6 +1103,13 @@ VipsBlob *jpegsave_buffer( VOption *options = 0 ) const; */ void jpegsave_mime( VOption *options = 0 ) const; +/** + * Save image to jpeg target. + * @param target Target to save to. + * @param options Optional options. + */ +void jpegsave_target( VTarget target, VOption *options = 0 ) const; + /** * Label regions in an image. * @param options Optional options. @@ -1112,7 +1173,7 @@ void magicksave( const char *filename, VOption *options = 0 ) const; VipsBlob *magicksave_buffer( VOption *options = 0 ) const; /** - * Resample with an mapim image. + * Resample with a map image. * @param index Index pixels with this. * @param options Optional options. * @return Output image. @@ -1296,13 +1357,28 @@ VImage math2_const( VipsOperationMath2 math2, std::vector c, VOption *op static VImage matload( const char *filename, VOption *options = 0 ); /** - * Load matrix from file. + * Invert an matrix. + * @param options Optional options. + * @return Output image. + */ +VImage matrixinvert( VOption *options = 0 ) const; + +/** + * Load matrix. * @param filename Filename to load from. * @param options Optional options. * @return Output image. */ static VImage matrixload( const char *filename, VOption *options = 0 ); +/** + * Load matrix. + * @param source Source to load from. + * @param options Optional options. + * @return Output image. + */ +static VImage matrixload_source( VSource source, VOption *options = 0 ); + /** * Print matrix. * @param options Optional options. @@ -1310,12 +1386,19 @@ static VImage matrixload( const char *filename, VOption *options = 0 ); void matrixprint( VOption *options = 0 ) const; /** - * Save image to matrix file. + * Save image to matrix. * @param filename Filename to save to. * @param options Optional options. */ void matrixsave( const char *filename, VOption *options = 0 ) const; +/** + * Save image to matrix. + * @param target Target to save to. + * @param options Optional options. + */ +void matrixsave_target( VTarget target, VOption *options = 0 ) const; + /** * Find image maximum. * @param options Optional options. @@ -1436,7 +1519,7 @@ static VImage openexrload( const char *filename, VOption *options = 0 ); static VImage openslideload( const char *filename, VOption *options = 0 ); /** - * Load pdf with libpoppler. + * Load pdf from file. * @param filename Filename to load from. * @param options Optional options. * @return Output image. @@ -1444,13 +1527,21 @@ static VImage openslideload( const char *filename, VOption *options = 0 ); static VImage pdfload( const char *filename, VOption *options = 0 ); /** - * Load pdf with libpoppler. + * Load pdf from buffer. * @param buffer Buffer to load from. * @param options Optional options. * @return Output image. */ static VImage pdfload_buffer( VipsBlob *buffer, VOption *options = 0 ); +/** + * Load pdf from source. + * @param source Source to load from. + * @param options Optional options. + * @return Output image. + */ +static VImage pdfload_source( VSource source, VOption *options = 0 ); + /** * Find threshold for percent of pixels. * @param percent Percent of pixels. @@ -1492,6 +1583,14 @@ static VImage pngload( const char *filename, VOption *options = 0 ); */ static VImage pngload_buffer( VipsBlob *buffer, VOption *options = 0 ); +/** + * Load png from source. + * @param source Source to load from. + * @param options Optional options. + * @return Output image. + */ +static VImage pngload_source( VSource source, VOption *options = 0 ); + /** * Save image to png file. * @param filename Filename to save to. @@ -1506,6 +1605,13 @@ void pngsave( const char *filename, VOption *options = 0 ) const; */ VipsBlob *pngsave_buffer( VOption *options = 0 ) const; +/** + * Save image to target as png. + * @param target Target to save to. + * @param options Optional options. + */ +void pngsave_target( VTarget target, VOption *options = 0 ) const; + /** * Load ppm from file. * @param filename Filename to load from. @@ -1575,6 +1681,22 @@ VImage rad2float( VOption *options = 0 ) const; */ static VImage radload( const char *filename, VOption *options = 0 ); +/** + * Load rad from buffer. + * @param buffer Buffer to load from. + * @param options Optional options. + * @return Output image. + */ +static VImage radload_buffer( VipsBlob *buffer, VOption *options = 0 ); + +/** + * Load rad from source. + * @param source Source to load from. + * @param options Optional options. + * @return Output image. + */ +static VImage radload_source( VSource source, VOption *options = 0 ); + /** * Save image to radiance file. * @param filename Filename to save to. @@ -1589,6 +1711,13 @@ void radsave( const char *filename, VOption *options = 0 ) const; */ VipsBlob *radsave_buffer( VOption *options = 0 ) const; +/** + * Save image to radiance target. + * @param target Target to save to. + * @param options Optional options. + */ +void radsave_target( VTarget target, VOption *options = 0 ) const; + /** * Rank filter. * @param width Window width in pixels. @@ -1931,6 +2060,22 @@ static VImage svgload( const char *filename, VOption *options = 0 ); */ static VImage svgload_buffer( VipsBlob *buffer, VOption *options = 0 ); +/** + * Load svg from source. + * @param source Source to load from. + * @param options Optional options. + * @return Output image. + */ +static VImage svgload_source( VSource source, VOption *options = 0 ); + +/** + * Find the index of the first non-zero pixel in tests. + * @param tests Table of images to test. + * @param options Optional options. + * @return Output image. + */ +static VImage switch_image( std::vector tests, VOption *options = 0 ); + /** * Run an external command. * @param cmd_format Command to run. @@ -1972,6 +2117,15 @@ static VImage thumbnail_buffer( VipsBlob *buffer, int width, VOption *options = */ VImage thumbnail_image( int width, VOption *options = 0 ) const; +/** + * Generate thumbnail from source. + * @param source Source to load from. + * @param width Size to this width. + * @param options Optional options. + * @return Output image. + */ +static VImage thumbnail_source( VSource source, int width, VOption *options = 0 ); + /** * Load tiff from file. * @param filename Filename to load from. @@ -1988,6 +2142,14 @@ static VImage tiffload( const char *filename, VOption *options = 0 ); */ static VImage tiffload_buffer( VipsBlob *buffer, VOption *options = 0 ); +/** + * Load tiff from source. + * @param source Source to load from. + * @param options Optional options. + * @return Output image. + */ +static VImage tiffload_source( VSource source, VOption *options = 0 ); + /** * Save image to tiff file. * @param filename Filename to save to. @@ -2061,6 +2223,14 @@ static VImage webpload( const char *filename, VOption *options = 0 ); */ static VImage webpload_buffer( VipsBlob *buffer, VOption *options = 0 ); +/** + * Load webp from source. + * @param source Source to load from. + * @param options Optional options. + * @return Output image. + */ +static VImage webpload_source( VSource source, VOption *options = 0 ); + /** * Save image to webp file. * @param filename Filename to save to. @@ -2075,6 +2245,13 @@ void webpsave( const char *filename, VOption *options = 0 ) const; */ VipsBlob *webpsave_buffer( VOption *options = 0 ) const; +/** + * Save image to webp target. + * @param target Target to save to. + * @param options Optional options. + */ +void webpsave_target( VTarget target, VOption *options = 0 ) const; + /** * Make a worley noise image. * @param width Image width in pixels. diff --git a/cplusplus/include/vips/vips8 b/cplusplus/include/vips/vips8 index 98f67926..a10ecf1e 100644 --- a/cplusplus/include/vips/vips8 +++ b/cplusplus/include/vips/vips8 @@ -52,5 +52,6 @@ #include "VError8.h" #include "VImage8.h" #include "VInterpolate8.h" +#include "VConnection8.h" #endif /*VIPS_CPLUSPLUS*/ diff --git a/cplusplus/vips-operators.cpp b/cplusplus/vips-operators.cpp index beae5057..aca3a206 100644 --- a/cplusplus/vips-operators.cpp +++ b/cplusplus/vips-operators.cpp @@ -1,5 +1,5 @@ // bodies for vips operations -// Wed Apr 24 15:50:21 CEST 2019 +// Thu 18 Jun 2020 01:19:31 PM CEST // this file is generated automatically, do not edit! VImage VImage::CMC2LCh( VOption *options ) const @@ -491,6 +491,19 @@ VImage VImage::canny( VOption *options ) const return( out ); } +VImage VImage::case_image( std::vector cases, VOption *options ) const +{ + VImage out; + + call( "case", + (options ? options : VImage::option())-> + set( "index", *this )-> + set( "out", &out )-> + set( "cases", cases ) ); + + return( out ); +} + VImage VImage::cast( VipsBandFormat format, VOption *options ) const { VImage out; @@ -741,6 +754,18 @@ VImage VImage::csvload( const char *filename, VOption *options ) return( out ); } +VImage VImage::csvload_source( VSource source, VOption *options ) +{ + VImage out; + + call( "csvload_source", + (options ? options : VImage::option())-> + set( "out", &out )-> + set( "source", source ) ); + + return( out ); +} + void VImage::csvsave( const char *filename, VOption *options ) const { call( "csvsave", @@ -749,6 +774,14 @@ void VImage::csvsave( const char *filename, VOption *options ) const set( "filename", filename ) ); } +void VImage::csvsave_target( VTarget target, VOption *options ) const +{ + call( "csvsave_target", + (options ? options : VImage::option())-> + set( "in", *this )-> + set( "target", target ) ); +} + VImage VImage::dE00( VImage right, VOption *options ) const { VImage out; @@ -1205,6 +1238,18 @@ VImage VImage::gifload_buffer( VipsBlob *buffer, VOption *options ) return( out ); } +VImage VImage::gifload_source( VSource source, VOption *options ) +{ + VImage out; + + call( "gifload_source", + (options ? options : VImage::option())-> + set( "out", &out )-> + set( "source", source ) ); + + return( out ); +} + VImage VImage::globalbalance( VOption *options ) const { VImage out; @@ -1284,6 +1329,18 @@ VImage VImage::heifload_buffer( VipsBlob *buffer, VOption *options ) return( out ); } +VImage VImage::heifload_source( VSource source, VOption *options ) +{ + VImage out; + + call( "heifload_source", + (options ? options : VImage::option())-> + set( "out", &out )-> + set( "source", source ) ); + + return( out ); +} + void VImage::heifsave( const char *filename, VOption *options ) const { call( "heifsave", @@ -1304,6 +1361,14 @@ VipsBlob *VImage::heifsave_buffer( VOption *options ) const return( buffer ); } +void VImage::heifsave_target( VTarget target, VOption *options ) const +{ + call( "heifsave_target", + (options ? options : VImage::option())-> + set( "in", *this )-> + set( "target", target ) ); +} + VImage VImage::hist_cum( VOption *options ) const { VImage out; @@ -1615,6 +1680,18 @@ VImage VImage::jpegload_buffer( VipsBlob *buffer, VOption *options ) return( out ); } +VImage VImage::jpegload_source( VSource source, VOption *options ) +{ + VImage out; + + call( "jpegload_source", + (options ? options : VImage::option())-> + set( "out", &out )-> + set( "source", source ) ); + + return( out ); +} + void VImage::jpegsave( const char *filename, VOption *options ) const { call( "jpegsave", @@ -1642,6 +1719,14 @@ void VImage::jpegsave_mime( VOption *options ) const set( "in", *this ) ); } +void VImage::jpegsave_target( VTarget target, VOption *options ) const +{ + call( "jpegsave_target", + (options ? options : VImage::option())-> + set( "in", *this )-> + set( "target", target ) ); +} + VImage VImage::labelregions( VOption *options ) const { VImage mask; @@ -1995,6 +2080,18 @@ VImage VImage::matload( const char *filename, VOption *options ) return( out ); } +VImage VImage::matrixinvert( VOption *options ) const +{ + VImage out; + + call( "matrixinvert", + (options ? options : VImage::option())-> + set( "in", *this )-> + set( "out", &out ) ); + + return( out ); +} + VImage VImage::matrixload( const char *filename, VOption *options ) { VImage out; @@ -2007,6 +2104,18 @@ VImage VImage::matrixload( const char *filename, VOption *options ) return( out ); } +VImage VImage::matrixload_source( VSource source, VOption *options ) +{ + VImage out; + + call( "matrixload_source", + (options ? options : VImage::option())-> + set( "out", &out )-> + set( "source", source ) ); + + return( out ); +} + void VImage::matrixprint( VOption *options ) const { call( "matrixprint", @@ -2022,6 +2131,14 @@ void VImage::matrixsave( const char *filename, VOption *options ) const set( "filename", filename ) ); } +void VImage::matrixsave_target( VTarget target, VOption *options ) const +{ + call( "matrixsave_target", + (options ? options : VImage::option())-> + set( "in", *this )-> + set( "target", target ) ); +} + double VImage::max( VOption *options ) const { double out; @@ -2223,6 +2340,18 @@ VImage VImage::pdfload_buffer( VipsBlob *buffer, VOption *options ) return( out ); } +VImage VImage::pdfload_source( VSource source, VOption *options ) +{ + VImage out; + + call( "pdfload_source", + (options ? options : VImage::option())-> + set( "out", &out )-> + set( "source", source ) ); + + return( out ); +} + int VImage::percent( double percent, VOption *options ) const { int threshold; @@ -2286,6 +2415,18 @@ VImage VImage::pngload_buffer( VipsBlob *buffer, VOption *options ) return( out ); } +VImage VImage::pngload_source( VSource source, VOption *options ) +{ + VImage out; + + call( "pngload_source", + (options ? options : VImage::option())-> + set( "out", &out )-> + set( "source", source ) ); + + return( out ); +} + void VImage::pngsave( const char *filename, VOption *options ) const { call( "pngsave", @@ -2306,6 +2447,14 @@ VipsBlob *VImage::pngsave_buffer( VOption *options ) const return( buffer ); } +void VImage::pngsave_target( VTarget target, VOption *options ) const +{ + call( "pngsave_target", + (options ? options : VImage::option())-> + set( "in", *this )-> + set( "target", target ) ); +} + VImage VImage::ppmload( const char *filename, VOption *options ) { VImage out; @@ -2413,6 +2562,30 @@ VImage VImage::radload( const char *filename, VOption *options ) return( out ); } +VImage VImage::radload_buffer( VipsBlob *buffer, VOption *options ) +{ + VImage out; + + call( "radload_buffer", + (options ? options : VImage::option())-> + set( "out", &out )-> + set( "buffer", buffer ) ); + + return( out ); +} + +VImage VImage::radload_source( VSource source, VOption *options ) +{ + VImage out; + + call( "radload_source", + (options ? options : VImage::option())-> + set( "out", &out )-> + set( "source", source ) ); + + return( out ); +} + void VImage::radsave( const char *filename, VOption *options ) const { call( "radsave", @@ -2433,6 +2606,14 @@ VipsBlob *VImage::radsave_buffer( VOption *options ) const return( buffer ); } +void VImage::radsave_target( VTarget target, VOption *options ) const +{ + call( "radsave_target", + (options ? options : VImage::option())-> + set( "in", *this )-> + set( "target", target ) ); +} + VImage VImage::rank( int width, int height, int index, VOption *options ) const { VImage out; @@ -2977,6 +3158,30 @@ VImage VImage::svgload_buffer( VipsBlob *buffer, VOption *options ) return( out ); } +VImage VImage::svgload_source( VSource source, VOption *options ) +{ + VImage out; + + call( "svgload_source", + (options ? options : VImage::option())-> + set( "out", &out )-> + set( "source", source ) ); + + return( out ); +} + +VImage VImage::switch_image( std::vector tests, VOption *options ) +{ + VImage out; + + call( "switch", + (options ? options : VImage::option())-> + set( "out", &out )-> + set( "tests", tests ) ); + + return( out ); +} + void VImage::system( const char *cmd_format, VOption *options ) { call( "system", @@ -3035,6 +3240,19 @@ VImage VImage::thumbnail_image( int width, VOption *options ) const return( out ); } +VImage VImage::thumbnail_source( VSource source, int width, VOption *options ) +{ + VImage out; + + call( "thumbnail_source", + (options ? options : VImage::option())-> + set( "out", &out )-> + set( "source", source )-> + set( "width", width ) ); + + return( out ); +} + VImage VImage::tiffload( const char *filename, VOption *options ) { VImage out; @@ -3059,6 +3277,18 @@ VImage VImage::tiffload_buffer( VipsBlob *buffer, VOption *options ) return( out ); } +VImage VImage::tiffload_source( VSource source, VOption *options ) +{ + VImage out; + + call( "tiffload_source", + (options ? options : VImage::option())-> + set( "out", &out )-> + set( "source", source ) ); + + return( out ); +} + void VImage::tiffsave( const char *filename, VOption *options ) const { call( "tiffsave", @@ -3170,6 +3400,18 @@ VImage VImage::webpload_buffer( VipsBlob *buffer, VOption *options ) return( out ); } +VImage VImage::webpload_source( VSource source, VOption *options ) +{ + VImage out; + + call( "webpload_source", + (options ? options : VImage::option())-> + set( "out", &out )-> + set( "source", source ) ); + + return( out ); +} + void VImage::webpsave( const char *filename, VOption *options ) const { call( "webpsave", @@ -3190,6 +3432,14 @@ VipsBlob *VImage::webpsave_buffer( VOption *options ) const return( buffer ); } +void VImage::webpsave_target( VTarget target, VOption *options ) const +{ + call( "webpsave_target", + (options ? options : VImage::option())-> + set( "in", *this )-> + set( "target", target ) ); +} + VImage VImage::worley( int width, int height, VOption *options ) { VImage out; diff --git a/doc/How-it-opens-files.md b/doc/How-it-opens-files.md index 59104f20..70e06caf 100644 --- a/doc/How-it-opens-files.md +++ b/doc/How-it-opens-files.md @@ -1,13 +1,13 @@ - - Opening files - 3 - libvips - + + Opening files + 3 + libvips + - - Opening - How libvips opens files - + + Opening + How libvips opens files + libvips now has at least four different ways of opening image files, each best for different file types, file sizes and image use cases. libvips tries diff --git a/doc/How-it-opens-files.xml b/doc/How-it-opens-files.xml index 30388045..c79b13ad 100644 --- a/doc/How-it-opens-files.xml +++ b/doc/How-it-opens-files.xml @@ -16,7 +16,7 @@ This page tries to explain what the different strategies are and when each is used. If you are running into unexpected memory, disc or CPU use, this might be helpful. vips_image_new_from_file() has the official documentation. - +
Direct access This is the fastest and simplest one. The file is mapped directly into the process’s address space and can be read with ordinary pointer access. Small files are completely mapped; large files are mapped in a series of small windows that are shared and which scroll about as pixels are read. Files which are accessed like this can be read by many threads at once, making them especially quick. They also interact well with the computer’s operating system: your OS will use spare memory to cache recently used chunks of the file. @@ -25,10 +25,10 @@ For this to be possible, the file format needs to be a simple dump of a memory array. libvips supports direct access for vips, 8-bit binary ppm/pbm/pnm, analyse and raw. - libvips has a special direct write mode where pixels can be written directly to the file image. This is used for the draw operators. + libvips has a special direct write mode where pixels can be written directly to the file image. This is used for the draw operators. - - +
+
Random access via load library Some image file formats have libraries which allow true random access to image pixels. For example, libtiff lets you read any tile out of a tiled tiff image very quickly. Because the libraries allow true random access, libvips can simply hook the image load library up to the input of the operation pipeline. @@ -39,8 +39,8 @@ libvips can load tiled tiff, tiled OpenEXR, FITS and OpenSlide images in this manner. - - +
+
Full decompression Many image load libraries do not support random access. In order to use images of this type as inputs to pipelines, libvips has to convert them to a random access format first. @@ -54,8 +54,8 @@ This is the slowest and most memory-hungry way to read files, but it’s unavoidable for many file formats. Unless you can use the next one! - - +
+
Sequential access This a fairly recent addition to libvips and is a hybrid of the previous two. @@ -90,7 +90,7 @@ $ vips shrink fred.png jim.png 10 10 This is done automatically in command-line operation. In programs, you need to set access to #VIPS_ACCESS_SEQUENTIAL in calls to functions like vips_image_new_from_file(). - +
diff --git a/doc/Using-vipsthumbnail.md b/doc/Using-vipsthumbnail.md index b29b65be..80c987de 100644 --- a/doc/Using-vipsthumbnail.md +++ b/doc/Using-vipsthumbnail.md @@ -1,13 +1,13 @@ - - Using `vipsthumbnail` - 3 - libvips - + + Using `vipsthumbnail` + 3 + libvips + - - `vipsthumbnail` - Introduction to `vipsthumbnail`, with examples - + + `vipsthumbnail` + Introduction to `vipsthumbnail`, with examples + libvips ships with a handy command-line image thumbnailer, `vipsthumbnail`. This page introduces it, with some examples. @@ -267,15 +267,19 @@ $ ls -l tn_shark.jpg -rw-r–r– 1 john john 7295 Nov  9 14:33 tn_shark.jpg ``` -Now encode with sRGB and delete any embedded profile: +Now transform to sRGB and don't attach a profile (you can also use `strip`, +though that will remove *all* metadata from the image): ``` -$ vipsthumbnail shark.jpg --eprofile /usr/share/color/icc/sRGB.icc --delete +$ vipsthumbnail shark.jpg --eprofile srgb -o tn_shark.jpg[profile=none] $ ls -l tn_shark.jpg -rw-r–r– 1 john john 4229 Nov  9 14:33 tn_shark.jpg ``` -It’ll look identical to a user, but be almost half the size. +(You can use the filename of any RGB profile. The magic string `srgb` selects a +high-quality sRGB profile that's built into libvips.) + +`tn_shark.jpg` will look identical to a user, but it's almost half the size. You can also specify a fallback input profile to use if the image has no embedded one. This can often happen with CMYK images, producing an error @@ -291,23 +295,11 @@ If you supply a CMYK profile, it will be able to convert the image, for example: ``` -$ vipsthumbnail kgdev.jpg --iprofile /usr/share/color/icc/colord/FOGRA28L_webcoated.icc +$ vipsthumbnail kgdev.jpg --iprofile cmyk ``` -I've had good results with this profile: - -https://github.com/libvips/nip2/blob/master/share/nip2/data/cmyk.icm - -It makes nice-looking images from most CMYK files, and is completely free. - -# Auto-rotate - -Many JPEG files have a hint set in the header giving the image orientation. If -you strip out the metadata, this hint will be lost, and the image will appear -to be rotated. - -If you use the `--rotate` option, `vipsthumbnail` examines the image header and -if there's an orientation tag, applies and removes it. +(As before, the magic string `cmyk` selects a high-quality CMYK profile that's +built into libvips, but you can use any CMYK profile you like.) # Final suggestion @@ -316,7 +308,7 @@ Putting all this together, I suggest this as a sensible set of options: ``` $ vipsthumbnail fred.jpg \ --size 128 \ + --eprofile srgb \ -o tn_%s.jpg[optimize_coding,strip] \ - --eprofile /usr/share/color/icc/sRGB.icc \ - --rotate + --eprofile srgb ``` diff --git a/doc/Using-vipsthumbnail.xml b/doc/Using-vipsthumbnail.xml index d50b150c..a72b1d58 100644 --- a/doc/Using-vipsthumbnail.xml +++ b/doc/Using-vipsthumbnail.xml @@ -21,7 +21,7 @@ $filename = "image.jpg"; $image = Vips\Image::thumbnail($filename, 200, ["height" => 200]); $image->writeToFile("my-thumbnail.jpg"); - +
libvips options vipsthumbnail supports the usual range of vips command-line options. A few of them are useful: @@ -38,8 +38,8 @@ $image->writeToFile("my-thumbnail.jpg"); --vips-info shows a higher level view of the operations that vipsthumbnail is running.  - - +
+
Looping vipsthumbnail can process many images in one command. For example: @@ -56,8 +56,8 @@ $ vipsthumbnail *.jpg $ parallel vipsthumbnail ::: *.jpg - - +
+
Thumbnail size You can set the bounding box of the generated thumbnail with the --size option. For example: @@ -80,8 +80,8 @@ $ vipsthumbnail shark.jpg --size 200x You can append ! to force a resize to the exact target size, breaking the aspect ratio. - - +
+
Cropping vipsthumbnail normally shrinks images to fit within the box set by --size. You can use the --smartcrop option to crop to fill the box instead. Excess pixels are trimmed away using the strategy you set. For example: @@ -92,30 +92,28 @@ $ vipsthumbnail owl.jpg --smartcrop attention -s 128 Where owl.jpg is an off-centre composition: -
- + + - - -
+ +
Gives this result: -
- + + - - -
+ + First it shrinks the image to get the vertical axis to 128 pixels, then crops down to 128 pixels across using the attention strategy. This one searches the image for features which might catch a human eye, see vips_smartcrop() for details. - - +
+
Linear light Shrinking images involves combining many pixels into one. Arithmetic averaging really ought to be in terms of the number of photons, but (for historical reasons) the values stored in image files are usually related to the voltage that should be applied to the electron gun in a CRT display. @@ -142,8 +140,8 @@ real 0m4.660s user 0m4.640s sys 0m0.016s - - +
+
Output directory You set the thumbnail write parameters with the -o option. This is a pattern which the input filename is pasted into to produce the output filename. For example: @@ -172,8 +170,8 @@ $ vipsthumbnail fred.jpg ../jim.tif -o mythumbs/tn_%s.jpg Now both input files will have thumbnails written to a subdirectory of their current directory. - - +
+
Output format and options You can use -o to specify the thumbnail image format too. For example:  @@ -247,8 +245,8 @@ $ vipsthumbnail 42-32157534.jpg -o x.jpg[optimize_coding,strip] $ ls -l x.jpg -rw-r–r– 1 john john 3600 Nov 12 21:27 x.jpg - - +
+
Colour management vipsthumbnail will optionally put images through LittleCMS for you. You can use this to move all thumbnails to the same colour space. All web browsers assume that images without an ICC profile are in sRGB colourspace, so if you move your thumbnails to sRGB, you can strip all the embedded profiles. This can save several kb per thumbnail. @@ -262,15 +260,18 @@ $ ls -l tn_shark.jpg -rw-r–r– 1 john john 7295 Nov  9 14:33 tn_shark.jpg - Now encode with sRGB and delete any embedded profile: + Now transform to sRGB and don’t attach a profile (you can also use strip, though that will remove all metadata from the image): -$ vipsthumbnail shark.jpg --eprofile /usr/share/color/icc/sRGB.icc --delete +$ vipsthumbnail shark.jpg --eprofile srgb -o tn_shark.jpg[profile=none] $ ls -l tn_shark.jpg -rw-r–r– 1 john john 4229 Nov  9 14:33 tn_shark.jpg - It’ll look identical to a user, but be almost half the size. + (You can use the filename of any RGB profile. The magic string srgb selects a high-quality sRGB profile that’s built into libvips.) + + + tn_shark.jpg will look identical to a user, but it’s almost half the size. You can also specify a fallback input profile to use if the image has no embedded one. This can often happen with CMYK images, producing an error message like: @@ -284,28 +285,13 @@ vips_colourspace: no known route from 'cmyk' to 'srgb' If you supply a CMYK profile, it will be able to convert the image, for example: -$ vipsthumbnail kgdev.jpg --iprofile /usr/share/color/icc/colord/FOGRA28L_webcoated.icc +$ vipsthumbnail kgdev.jpg --iprofile cmyk - I’ve had good results with this profile: + (As before, the magic string cmyk selects a high-quality CMYK profile that’s built into libvips, but you can use any CMYK profile you like.) - - https://github.com/libvips/nip2/blob/master/share/nip2/data/cmyk.icm - - - It makes nice-looking images from most CMYK files, and is completely free. - - - - Auto-rotate - - Many JPEG files have a hint set in the header giving the image orientation. If you strip out the metadata, this hint will be lost, and the image will appear to be rotated. - - - If you use the --rotate option, vipsthumbnail examines the image header and if there’s an orientation tag, applies and removes it. - - - +
+
Final suggestion Putting all this together, I suggest this as a sensible set of options: @@ -313,11 +299,11 @@ $ vipsthumbnail kgdev.jpg --iprofile /usr/share/color/icc/colord/FOGRA28L_webcoa $ vipsthumbnail fred.jpg \ --size 128 \ + --eprofile srgb \ -o tn_%s.jpg[optimize_coding,strip] \ - --eprofile /usr/share/color/icc/sRGB.icc \ - --rotate + --eprofile srgb - +
diff --git a/doc/binding.md b/doc/binding.md index fc0e9609..a2b2c7a1 100644 --- a/doc/binding.md +++ b/doc/binding.md @@ -1,13 +1,13 @@ - - How to write bindings - 3 - libvips - + + How to write bindings + 3 + libvips + - - Binding - Writing bindings for libvips - + + Binding + Writing bindings for libvips + There are full libvips bindings for quite a few environments now: C, C++, command-line, Ruby, PHP, Lua, Python and JavaScript (node). @@ -80,7 +80,7 @@ main( int argc, char **argv ) /* Call the operation. This will look up the operation+args in the vips * operation cache and either return a previous operation, or build - * this one. In either case, we have a new ref we mst release. + * this one. In either case, we have a new ref we must release. */ if( !(new_op = vips_cache_operation_build( op )) ) { g_object_unref( op ); @@ -114,7 +114,7 @@ main( int argc, char **argv ) ``` libvips has a couple of extra things to let you examine the arguments and -types of an operator at runtime. Use vips_lib.vips_argument_map() to loop +types of an operator at runtime. Use vips_argument_map() to loop over all the arguments of an operator, and vips_object_get_argument() to fetch the type and flags of a specific argument. @@ -151,7 +151,7 @@ operator overloads, and various other useful features. # Dynamic language with FFI -Languages like Ruby, Python, JavaScript and Lua can't call C directly, but +Languages like Ruby, Python, JavaScript and LuaJIT can't call C directly, but they do support FFI. The bindings for these languages work rather like C++, but use FFI to call into libvips and run operations. @@ -216,4 +216,4 @@ $ yelp-build html . ``` To make HTML docs. This is an easy way to see what you can call in the -library. +library. diff --git a/doc/binding.xml b/doc/binding.xml index 1215134f..68419e52 100644 --- a/doc/binding.xml +++ b/doc/binding.xml @@ -16,7 +16,7 @@ This chapter runs through the four main styles that have been found to work well. If you want to write a new binding, one of these should be close to what you need. - +
Don’t bind the top-level C API The libvips C API (vips_add() and so on) is very inconvenient and dangerous to use from other languages due to its heavy use of varargs. @@ -79,7 +79,7 @@ main( int argc, char **argv ) /* Call the operation. This will look up the operation+args in the vips * operation cache and either return a previous operation, or build - * this one. In either case, we have a new ref we mst release. + * this one. In either case, we have a new ref we must release. */ if( !(new_op = vips_cache_operation_build( op )) ) { g_object_unref( op ); @@ -112,13 +112,13 @@ main( int argc, char **argv ) } - libvips has a couple of extra things to let you examine the arguments and types of an operator at runtime. Use vips_lib.vips_argument_map() to loop over all the arguments of an operator, and vips_object_get_argument() to fetch the type and flags of a specific argument. + libvips has a couple of extra things to let you examine the arguments and types of an operator at runtime. Use vips_argument_map() to loop over all the arguments of an operator, and vips_object_get_argument() to fetch the type and flags of a specific argument. Use vips_operation_get_flags() to get general information about an operator. - - +
+
Compiled language which can call C The C++ binding uses this lower layer to define a function called VImage::call() which can call any libvips operator with a not-varargs set of variable arguments. @@ -144,23 +144,23 @@ VImage VImage::invert( VOption *options ) The VImage class also adds automatic reference counting, constant expansion, operator overloads, and various other useful features. - - +
+
Dynamic language with FFI - Languages like Ruby, Python, JavaScript and Lua can’t call C directly, but they do support FFI. The bindings for these languages work rather like C++, but use FFI to call into libvips and run operations. + Languages like Ruby, Python, JavaScript and LuaJIT can’t call C directly, but they do support FFI. The bindings for these languages work rather like C++, but use FFI to call into libvips and run operations. Since these languages are dynamic, they can add another trick: they intercept the method-missing hook and attempt to run any method calls not implemented by the Image class as libvips operators. This makes these bindings self-writing: they only contain a small amount of code and just expose everything they find in the libvips class hierarchy. - - +
+
Dynamic langauge without FFI PHP does not have FFI, unfortunately, so for this language a small native module implements the general vips_call() function for PHP language types, and a larger pure PHP layer makes it convenient to use. - - +
+
gobject-introspection The C source code to libvips has been marked up with special comments describing the interface in a standard way. These comments are read by the gobject-introspection package when libvips is compiled and used to generate a typelib, a description of how to call the library. Many languages have gobject-introspection packages: all you need to do to call libvips from your favorite language is to start g-o-i, load the libvips typelib, and you should have the whole library available. For example, from Python it’s as simple as: @@ -177,8 +177,8 @@ from gi.repository import Vips If you have a choice, I would recommend simply using FFI. - - +
+
Documentation You can generate searchable docs from a .gir (the thing that is built from scanning libvips and which in turn turn the typelib is made from) with g-ir-doc-tool, for example: @@ -202,7 +202,7 @@ $ yelp-build html . To make HTML docs. This is an easy way to see what you can call in the library. - +
diff --git a/doc/function-list.xml b/doc/function-list.xml index 6529d16c..4c14b3cc 100644 --- a/doc/function-list.xml +++ b/doc/function-list.xml @@ -72,1119 +72,199 @@ - system - run an external command - vips_system() + CMC2LCh + Transform lch to cmc + vips_CMC2LCh() - add - add two images - vips_add() + CMYK2XYZ + Transform cmyk to xyz + vips_CMYK2XYZ() - subtract - subtract two images - vips_subtract() + HSV2sRGB + Transform hsv to srgb + vips_HSV2sRGB() - multiply - multiply two images - vips_multiply() + LCh2CMC + Transform lch to cmc + vips_LCh2CMC() - divide - divide two images - vips_divide() + LCh2Lab + Transform lch to lab + vips_LCh2Lab() - relational - relational operation on two images - vips_relational(), vips_equal(), vips_notequal(), vips_less(), - vips_lesseq(), vips_more(), vips_moreeq() + Lab2LCh + Transform lab to lch + vips_Lab2LCh() - remainder - remainder after integer division of two images - vips_remainder() + Lab2LabQ + Transform float lab to labq coding + vips_Lab2LabQ() - boolean - boolean operation on two images - vips_boolean(), vips_andimage(), vips_orimage(), vips_eorimage(), - vips_lshift(), vips_rshift() + Lab2LabS + Transform float lab to signed short + vips_Lab2LabS() - math2 - binary math operations - vips_math2(), vips_pow(), vips_wop() + Lab2XYZ + Transform cielab to xyz + vips_Lab2XYZ() - complex2 - complex binary operations on two images - vips_complex2(), vips_cross_phase() + LabQ2Lab + Unpack a labq image to float lab + vips_LabQ2Lab() - complexform - form a complex image from two real images - vips_complexform() + LabQ2LabS + Unpack a labq image to short lab + vips_LabQ2LabS() - sum - sum an array of images - vips_sum() + LabQ2sRGB + Convert a labq image to srgb + vips_LabQ2sRGB() - invert - invert an image - vips_invert() + LabS2Lab + Transform signed short lab to float + vips_LabS2Lab() - linear - calculate (a * in + b) - vips_linear(), vips_linear1() + LabS2LabQ + Transform short lab to labq coding + vips_LabS2LabQ() - math - apply a math operation to an image - vips_math(), vips_sin(), vips_cos(), vips_tan(), vips_asin(), - vips_acos(), vips_atan(), vips_exp(), vips_exp10(), vips_log(), - vips_log10() + XYZ2CMYK + Transform xyz to cmyk + vips_XYZ2CMYK() + + + XYZ2Lab + Transform xyz to lab + vips_XYZ2Lab() + + + XYZ2Yxy + Transform xyz to yxy + vips_XYZ2Yxy() + + + XYZ2scRGB + Transform xyz to scrgb + vips_XYZ2scRGB() + + + Yxy2XYZ + Transform yxy to xyz + vips_Yxy2XYZ() abs - absolute value of an image + Absolute value of an image vips_abs() - sign - unit vector of pixel - vips_sign() + add + Add two images + vips_add() - round - perform a round function on an image - vips_round(), vips_floor(), vips_ceil(), vips_rint() + affine + Affine transform of an image + vips_affine() - relational_const - relational operations against a constant - vips_relational_const(), vips_equal_const(), vips_notequal_const(), - vips_less_const(), vips_lesseq_const(), vips_more_const(), - vips_moreeq_const(), vips_relational_const1(), vips_equal_const1(), - vips_notequal_const1(), vips_less_const1(), vips_lesseq_const1(), - vips_more_const1(), vips_moreeq_const1() - - - remainder_const - remainder after integer division of an image and a constant - vips_remainder_const(), vips_remainder_const1() - - - boolean_const - boolean operations against a constant - vips_boolean_const(), vips_andimage_const(), vips_orimage_const(), - vips_eorimage_const(), vips_lshift_const(), vips_rshift_const(), - vips_boolean_const1(), vips_andimage_const1(), vips_orimage_const1(), - vips_eorimage_const1(), vips_lshift_const1(), vips_rshift_const1() - - - math2_const - pow( @in, @c ) - vips_math2_const(), vips_pow_const(), vips_wop_const(), - vips_math2_const1(), vips_pow_const1(), vips_wop_const1() - - - complex - perform a complex operation on an image - vips_complex(), vips_polar(), vips_rect(), vips_conj() - - - complexget - get a component from a complex image - vips_complexget(), vips_real(), vips_imag() - - - avg - find image average - vips_avg() - - - min - find image minimum - vips_min() - - - max - find image maximum - vips_max() - - - deviate - find image standard deviation - vips_deviate() - - - stats - find image average - vips_stats() - - - hist_find - find image histogram - vips_hist_find() - - - hist_find_ndim - find n-dimensional image histogram - vips_hist_find_ndim() - - - hist_find_indexed - find indexed image histogram - vips_hist_find_indexed() - - - hough_line - find hough line transform - vips_hough_line() - - - hough_circle - find hough circle transform - vips_hough_circle() - - - project - find image projections - vips_project() - - - profile - find image profiles - vips_profile() - - - measure - measure a set of patches on a color chart - vips_measure() - - - getpoint - read a point from an image - vips_getpoint() - - - copy - copy an image - vips_copy() - - - tilecache - cache an image as a set of tiles - vips_tilecache() - - - linecache - cache an image as a set of lines - vips_linecache() - - - sequential - check sequential access - vips_sequential() - - - cache - cache an image - vips_cache() - - - embed - embed an image in a larger image - vips_embed() - - - gravity - expand an image with a specified gravity - vips_gravity() - - - flip - flip an image - vips_flip() - - - transpose3d - transpose a volumetric image - vips_transpose3d() - - - insert - insert image @sub into @main at @x, @y - vips_insert() - - - join - join a pair of images - vips_join() + analyzeload + Load an analyze6 image + vips_analyzeload() arrayjoin - join an array of images + Join an array of images vips_arrayjoin() - extract_area - extract an area from an image - vips_extract_area(), vips_crop() + autorot + Autorotate image by exif tag + vips_autorot() - smartcrop - extract an area from an image - vips_smartcrop() + avg + Find image average + vips_avg() - find_trim - search an image for non-background pixels - vips_find_trim() + bandbool + Boolean operation across image bands + vips_bandbool(), vips_bandand(), vips_bandor(), vips_bandeor(), vips_bandmean() - extract_band - extract band from an image - vips_extract_band() + bandfold + Fold up x axis into bands + vips_bandfold() bandjoin - bandwise join a set of images + Bandwise join a set of images vips_bandjoin(), vips_bandjoin2() bandjoin_const - append a constant band to an image + Append a constant band to an image vips_bandjoin_const(), vips_bandjoin_const1() - - bandrank - band-wise rank of a set of images - vips_bandrank() - bandmean - band-wise average + Band-wise average vips_bandmean() - bandbool - boolean operation across image bands - vips_bandbool(), vips_bandand(), vips_bandor(), vips_bandeor(), - vips_bandmean() - - - replicate - replicate an image - vips_replicate() - - - cast - cast an image - vips_cast(), vips_cast_uchar(), vips_cast_char(), vips_cast_ushort(), - vips_cast_short(), vips_cast_uint(), vips_cast_int(), vips_cast_float(), - vips_cast_double(), vips_cast_complex(), vips_cast_dpcomplex() - - - rot - rotate an image - vips_rot() - - - rot45 - rotate an image - vips_rot45() - - - autorot - autorotate image by exif tag - vips_autorot() - - - ifthenelse - ifthenelse an image - vips_ifthenelse() - - - recomb - linear recombination with matrix - vips_recomb() - - - bandfold - fold up x axis into bands - vips_bandfold() - - - composite - composite a set of images with a PDF blend mode - vips_composite() + bandrank + Band-wise rank of a set of images + vips_bandrank() bandunfold - unfold image bands into x axis + Unfold image bands into x axis vips_bandunfold() - - flatten - flatten alpha out of an image - vips_flatten() - - - premultiply - premultiply image alpha - vips_premultiply() - - - unpremultiply - unpremultiply image alpha - vips_unpremultiply() - - - grid - grid an image - vips_grid() - - - scale - scale an image to uchar - vips_scale() - - - wrap - wrap image origin - vips_wrap() - - - zoom - zoom an image - vips_zoom() - - - subsample - subsample an image - vips_subsample() - - - msb - pick most-significant byte from an image - vips_msb() - - - byteswap - byteswap an image - vips_byteswap() - - - falsecolour - false colour an image - vips_falsecolour() - - - gamma - gamma an image - vips_gamma() - black - make a black image + Make a black image vips_black() - gaussnoise - make a gaussnoise image - vips_gaussnoise() + boolean + Boolean operation on two images + vips_boolean(), vips_andimage(), vips_orimage(), vips_eorimage(), vips_lshift(), vips_rshift() - text - make a text image - vips_text() - - - xyz - make an image where pixel values are coordinates - vips_xyz() - - - gaussmat - make a gaussian image - vips_gaussmat() - - - logmat - make a laplacian of gaussian image - vips_logmat() - - - eye - make an image showing the eye's spatial response - vips_eye() - - - grey - make a grey ramp image - vips_grey() - - - zone - make a zone plate - vips_zone() - - - sines - make a 2D sine wave - vips_sines() - - - mask_ideal - make an ideal filter - vips_mask_ideal() - - - mask_ideal_ring - make an ideal ring filter - vips_mask_ideal_ring() - - - mask_ideal_band - make an ideal band filter - vips_mask_ideal_band() - - - mask_butterworth - make a butterworth filter - vips_mask_butterworth() - - - mask_butterworth_ring - make a butterworth ring filter - vips_mask_butterworth_ring() - - - mask_butterworth_band - make a butterworth_band filter - vips_mask_butterworth_band() - - - mask_gaussian - make a gaussian filter - vips_mask_gaussian() - - - mask_gaussian_ring - make a gaussian ring filter - vips_mask_gaussian_ring() - - - mask_gaussian_band - make a gaussian filter - vips_mask_gaussian_band() - - - mask_fractal - make fractal filter - vips_mask_fractal() + boolean_const + Boolean operations against a constant + vips_boolean_const(), vips_andimage_const(), vips_orimage_const(), vips_eorimage_const(), vips_lshift_const(), vips_rshift_const(), vips_boolean_const1(), vips_andimage_const1(), vips_orimage_const1(), vips_eorimage_const1(), vips_lshift_const1(), vips_rshift_const1() buildlut - build a look-up table + Build a look-up table vips_buildlut() - invertlut - build an inverted look-up table - vips_invertlut() + byteswap + Byteswap an image + vips_byteswap() - tonelut - build a look-up table - vips_tonelut() - - - identity - make a 1D image where pixel values are indexes - vips_identity() - - - fractsurf - make a fractal surface - vips_fractsurf() - - - worley - make a worley noise image - vips_worley() - - - perlin - make a perlin noise image - vips_perlin() - - - radload - load a Radiance image from a file - vips_radload() - - - pdfload - load PDF with libpoppler - vips_pdfload() - - - pdfload_buffer - load PDF with libpoppler - vips_pdfload_buffer() - - - svgload - load SVG with rsvg - vips_svgload() - - - svgload_buffer - load SVG with rsvg - vips_svgload_buffer() - - - gifload - load GIF with giflib - vips_gifload() - - - gifload_buffer - load GIF with giflib - vips_gifload_buffer() - - - ppmload - load ppm from file - vips_ppmload() - - - csvload - load csv from file - vips_csvload() - - - matrixload - load matrix from file - vips_matrixload() - - - analyzeload - load an Analyze6 image - vips_analyzeload() - - - rawload - load raw data from a file - vips_rawload() - - - pngload - load png from file - vips_pngload() - - - pngload_buffer - load png from buffer - vips_pngload_buffer() - - - matload - load mat from file - vips_matload() - - - jpegload - load jpeg from file - vips_jpegload() - - - jpegload_buffer - load jpeg from buffer - vips_jpegload_buffer() - - - webpload - load webp from file - vips_webpload() - - - webpload_buffer - load webp from buffer - vips_webpload_buffer() - - - tiffload - load tiff from file - vips_tiffload() - - - tiffload_buffer - load tiff from buffer - vips_tiffload_buffer() - - - openslideload - load file with OpenSlide - vips_openslideload() - - - magickload - load file with ImageMagick - vips_magickload() - - - magickload_buffer - load image from buffer with ImageMagick - vips_magickload_buffer() - - - magicksave - save file with ImageMagick - vips_magicksave() - - - niftiload - load a NIfTI image - vips_niftiload() - - - niftisave - save image in NIfTI format - vips_niftisave() - - - fitsload - load a FITS image - vips_fitsload() - - - openexrload - load an OpenEXR image - vips_openexrload() - - - radsave - save image to Radiance file - vips_radsave() - - - ppmsave - save image to ppm file - vips_ppmsave() - - - csvsave - save image to csv file - vips_csvsave() - - - matrixsave - save image to matrix file - vips_matrixsave() - - - matrixprint - print matrix - vips_matrixprint() - - - rawsave - save image to raw file - vips_rawsave() - - - rawsave_fd - write raw image to file descriptor - vips_rawsave_fd() - - - dzsave - save image to deep zoom format - vips_dzsave() - - - dzsave_buffer - save image to dz buffer - vips_dzsave_buffer() - - - pngsave - save image to png file - vips_pngsave() - - - pngsave_buffer - save image to png buffer - vips_pngsave_buffer() - - - jpegsave - save image to jpeg file - vips_jpegsave() - - - jpegsave_buffer - save image to jpeg buffer - vips_jpegsave_buffer() - - - jpegsave_mime - save image to jpeg mime - vips_jpegsave_mime() - - - webpsave - save image to webp file - vips_webpsave() - - - webpsave_buffer - save image to webp buffer - vips_webpsave_buffer() - - - tiffsave - save image to tiff file - vips_tiffsave() - - - tiffsave_buffer - save image to tiff buffer - vips_tiffsave_buffer() - - - fitssave - save image to fits file - vips_fitssave() - - - shrink - shrink an image - vips_shrink() - - - shrinkh - shrink an image horizontally - vips_shrinkh() - - - shrinkv - shrink an image vertically - vips_shrinkv() - - - reduceh - shrink an image horizontally - vips_reduceh() - - - reducev - shrink an image vertically - vips_reducev() - - - reduce - reduce an image - vips_reduce() - - - thumbnail - generate thumbnail from file - vips_thumbnail() - - - thumbnail_buffer - generate thumbnail from buffer - vips_thumbnail_buffer() - - - thumbnail_image - generate thumbnail from image - vips_thumbnail_image() - - - mapim - resample an image with an arbitrary warp - vips_mapim() - - - affine - affine transform of an image - vips_affine() - - - similarity - similarity transform of an image - vips_similarity() - - - rotate - rotate an image by a number of degrees - vips_rotate() - - - resize - resize an image - vips_resize() - - - colourspace - convert to a new colourspace - vips_colourspace() - - - Lab2XYZ - transform CIELAB to XYZ - vips_Lab2XYZ() - - - XYZ2Lab - transform XYZ to Lab - vips_XYZ2Lab() - - - Lab2LCh - transform Lab to LCh - vips_Lab2LCh() - - - LCh2Lab - transform LCh to Lab - vips_LCh2Lab() - - - LCh2CMC - transform LCh to CMC - vips_LCh2CMC() - - - CMC2LCh - transform LCh to CMC - vips_CMC2LCh() - - - XYZ2Yxy - transform XYZ to Yxy - vips_XYZ2Yxy() - - - Yxy2XYZ - transform Yxy to XYZ - vips_Yxy2XYZ() - - - scRGB2XYZ - transform scRGB to XYZ - vips_scRGB2XYZ() - - - XYZ2scRGB - transform XYZ to scRGB - vips_XYZ2scRGB() - - - LabQ2Lab - unpack a LabQ image to float Lab - vips_LabQ2Lab() - - - Lab2LabQ - transform float Lab to LabQ coding - vips_Lab2LabQ() - - - LabQ2LabS - unpack a LabQ image to short Lab - vips_LabQ2LabS() - - - LabS2LabQ - transform short Lab to LabQ coding - vips_LabS2LabQ() - - - LabS2Lab - transform signed short Lab to float - vips_LabS2Lab() - - - Lab2LabS - transform float Lab to signed short - vips_Lab2LabS() - - - rad2float - unpack Radiance coding to float RGB - vips_rad2float() - - - float2rad - transform float RGB to Radiance coding - vips_float2rad() - - - LabQ2sRGB - unpack a LabQ image to short Lab - vips_LabQ2sRGB() - - - sRGB2HSV - transform sRGB to HSV - vips_sRGB2HSV() - - - HSV2sRGB - transform HSV to sRGB - vips_HSV2sRGB() - - - sRGB2scRGB - convert an sRGB image to scRGB - vips_sRGB2scRGB() - - - scRGB2BW - convert scRGB to BW - vips_scRGB2BW() - - - scRGB2sRGB - convert an scRGB image to sRGB - vips_scRGB2sRGB() - - - icc_import - import from device with ICC profile - vips_icc_import() - - - icc_export - output to device with ICC profile - vips_icc_export() - - - icc_transform - transform between devices with ICC profiles - vips_icc_transform() - - - dE76 - calculate dE76 - vips_dE76() - - - dE00 - calculate dE00 - vips_dE00() - - - dECMC - calculate dECMC - vips_dECMC() - - - maplut - map an image though a lut - vips_maplut() - - - percent - find threshold for percent of pixels - vips_percent() - - - stdif - statistical difference - vips_stdif() - - - hist_cum - form cumulative histogram - vips_hist_cum() - - - hist_match - match two histograms - vips_hist_match() - - - hist_norm - normalise histogram - vips_hist_norm() - - - hist_equal - histogram equalisation - vips_hist_equal() - - - hist_plot - plot histogram - vips_hist_plot() - - - hist_local - local histogram equalisation - vips_hist_local() - - - hist_ismonotonic - test for monotonicity - vips_hist_ismonotonic() - - - hist_entropy - estimate image entropy - vips_hist_entropy() - - - conv - convolution operation - vips_conv() - - - conva - approximate integer convolution - vips_conva() - - - convf - float convolution operation - vips_convf() - - - convi - int convolution operation - vips_convi() - - - compass - convolve with rotating mask - vips_compass() - - - convsep - seperable convolution operation - vips_convsep() - - - convasep - approximate separable integer convolution - vips_convasep() - - - sobel - Sobel edge detector - vips_sobel() + cache + Cache an image + vips_cache() canny @@ -1192,135 +272,1219 @@ vips_canny() - fastcor - fast correlation - vips_fastcor() + case + Use pixel values to pick cases from an array of images + vips_case() - spcor - spatial correlation - vips_spcor() + cast + Cast an image + vips_cast(), vips_cast_uchar(), vips_cast_char(), vips_cast_ushort(), vips_cast_shortcast_uint(), vips_cast_int(), vips_cast_float(), vips_cast_double(), vips_cast_complex(), vips_cast_dpcomplex() - sharpen - unsharp masking for print - vips_sharpen() + colourspace + Convert to a new colorspace + vips_colourspace() - gaussblur - gaussian blur - vips_gaussblur() + compass + Convolve with rotating mask + vips_compass() - fwfft - forward FFT - vips_fwfft() + complex + Perform a complex operation on an image + vips_complex(), vips_polar(), vips_rect(), vips_conj() - invfft - inverse FFT - vips_invfft() + complex2 + Complex binary operations on two images + vips_complex2(), vips_cross_phase() - freqmult - frequency-domain filtering - vips_freqmult() + complexform + Form a complex image from two real images + vips_complexform() - spectrum - make displayable power spectrum - vips_spectrum() + complexget + Get a component from a complex image + vips_complexget(), vips_real(), vips_imag() - phasecor - calculate phase correlation - vips_phasecor() + composite + Blend an array of images with an array of blend modes + vips_composite() - morph - morphology operation: dilate, erode, hitmiss - vips_morph() + composite2 + Blend a pair of images with a blend mode + vips_composite2() - rank - rank filter - vips_rank(), vips_median() + conv + Convolution operation + vips_conv() + + + conva + Approximate integer convolution + vips_conva() + + + convasep + Approximate separable integer convolution + vips_convasep() + + + convf + Float convolution operation + vips_convf() + + + convi + Int convolution operation + vips_convi() + + + convsep + Seperable convolution operation + vips_convsep() + + + copy + Copy an image + vips_copy() countlines - count lines in an image + Count lines in an image vips_countlines() - labelregions - label regions in an image - vips_labelregions() + csvload + Load csv + vips_csvload() - fill_nearest - replace each zero pixel with the nearest non-zero pixel - vips_fill_nearest() + csvload_source + Load csv + vips_csvload_source() - draw_rect - paint a rectangle on an image - vips_draw_rect(), vips_draw_rect1(), vips_draw_point(), - vips_draw_point1() + csvsave + Save image to csv + vips_csvsave() - draw_mask - draw a mask on an image - vips_draw_mask(), vips_draw_mask1() + csvsave_target + Save image to csv + vips_csvsave_target() - draw_line - draw a line on an image - vips_draw_line(), vips_draw_line1() + dE00 + Calculate de00 + vips_dE00() + + + dE76 + Calculate de76 + vips_dE76() + + + dECMC + Calculate decmc + vips_dECMC() + + + deviate + Find image standard deviation + vips_deviate() + + + divide + Divide two images + vips_divide() draw_circle - draw a circle on an image + Draw a circle on an image vips_draw_circle(), vips_draw_circle1() draw_flood - flood-fill an area + Flood-fill an area vips_draw_flood(), vips_draw_flood1() draw_image - paint an image into another image + Paint an image into another image vips_draw_image() + + draw_line + Draw a line on an image + vips_draw_line(), vips_draw_line1() + + + draw_mask + Draw a mask on an image + vips_draw_mask(), vips_draw_mask1() + + + draw_rect + Paint a rectangle on an image + vips_draw_rect(), vips_draw_rect1(), vips_draw_point(), vips_draw_point1() + draw_smudge - blur a rectangle on an image + Blur a rectangle on an image vips_draw_smudge() + + dzsave + Save image to deepzoom file + vips_dzsave() + + + dzsave_buffer + Save image to dz buffer + vips_dzsave_buffer() + + + embed + Embed an image in a larger image + vips_embed() + + + extract_area + Extract an area from an image + vips_extract_area(), vips_crop() + + + extract_band + Extract band from an image + vips_extract_band() + + + eye + Make an image showing the eye's spatial response + vips_eye() + + + falsecolour + False-color an image + vips_falsecolour() + + + fastcor + Fast correlation + vips_fastcor() + + + fill_nearest + Fill image zeros with nearest non-zero pixel + vips_fill_nearest() + + + find_trim + Search an image for non-edge areas + vips_find_trim() + + + fitsload + Load a fits image + vips_fitsload() + + + fitssave + Save image to fits file + vips_fitssave() + + + flatten + Flatten alpha out of an image + vips_flatten() + + + flip + Flip an image + vips_flip() + + + float2rad + Transform float rgb to radiance coding + vips_float2rad() + + + fractsurf + Make a fractal surface + vips_fractsurf() + + + freqmult + Frequency-domain filtering + vips_freqmult() + + + fwfft + Forward fft + vips_fwfft() + + + gamma + Gamma an image + vips_gamma() + + + gaussblur + Gaussian blur + vips_gaussblur() + + + gaussmat + Make a gaussian image + vips_gaussmat() + + + gaussnoise + Make a gaussnoise image + vips_gaussnoise() + + + getpoint + Read a point from an image + vips_getpoint() + + + gifload + Load gif with giflib + vips_gifload() + + + gifload_buffer + Load gif with giflib + vips_gifload_buffer() + + + gifload_source + Load gif with giflib + vips_gifload_source() + + + globalbalance + Global balance an image mosaic + vips_globalbalance() + + + gravity + Place an image within a larger image with a certain gravity + vips_gravity() + + + grey + Make a grey ramp image + vips_grey() + + + grid + Grid an image + vips_grid() + + + heifload + Load a heif image + vips_heifload() + + + heifload_buffer + Load a heif image + vips_heifload_buffer() + + + heifload_source + Load a heif image + vips_heifload_source() + + + heifsave + Save image in heif format + vips_heifsave() + + + heifsave_buffer + Save image in heif format + vips_heifsave_buffer() + + + heifsave_target + Save image in heif format + vips_heifsave_target() + + + hist_cum + Form cumulative histogram + vips_hist_cum() + + + hist_entropy + Estimate image entropy + vips_hist_entropy() + + + hist_equal + Histogram equalisation + vips_hist_equal() + + + hist_find + Find image histogram + vips_hist_find() + + + hist_find_indexed + Find indexed image histogram + vips_hist_find_indexed() + + + hist_find_ndim + Find n-dimensional image histogram + vips_hist_find_ndim() + + + hist_ismonotonic + Test for monotonicity + vips_hist_ismonotonic() + + + hist_local + Local histogram equalisation + vips_hist_local() + + + hist_match + Match two histograms + vips_hist_match() + + + hist_norm + Normalise histogram + vips_hist_norm() + + + hist_plot + Plot histogram + vips_hist_plot() + + + hough_circle + Find hough circle transform + vips_hough_circle() + + + hough_line + Find hough line transform + vips_hough_line() + + + icc_export + Output to device with icc profile + vips_icc_export() + + + icc_import + Import from device with icc profile + vips_icc_import() + + + icc_transform + Transform between devices with icc profiles + vips_icc_transform() + + + identity + Make a 1d image where pixel values are indexes + vips_identity() + + + ifthenelse + Ifthenelse an image + vips_ifthenelse() + + + insert + Insert image @sub into @main at @x, @y + vips_insert() + + + invert + Invert an image + vips_invert() + + + invertlut + Build an inverted look-up table + vips_invertlut() + + + invfft + Inverse fft + vips_invfft() + + + join + Join a pair of images + vips_join() + + + jpegload + Load jpeg from file + vips_jpegload() + + + jpegload_buffer + Load jpeg from buffer + vips_jpegload_buffer() + + + jpegload_source + Load image from jpeg source + vips_jpegload_source() + + + jpegsave + Save image to jpeg file + vips_jpegsave() + + + jpegsave_buffer + Save image to jpeg buffer + vips_jpegsave_buffer() + + + jpegsave_mime + Save image to jpeg mime + vips_jpegsave_mime() + + + jpegsave_target + Save image to jpeg target + vips_jpegsave_target() + + + labelregions + Label regions in an image + vips_labelregions() + + + linear + Calculate (a * in + b) + vips_linear(), vips_linear1() + + + linecache + Cache an image as a set of lines + vips_linecache() + + + logmat + Make a laplacian of gaussian image + vips_logmat() + + + magickload + Load file with imagemagick + vips_magickload() + + + magickload_buffer + Load buffer with imagemagick + vips_magickload_buffer() + + + magicksave + Save file with imagemagick + vips_magicksave() + + + magicksave_buffer + Save image to magick buffer + vips_magicksave_buffer() + + + mapim + Resample with a map image + vips_mapim() + + + maplut + Map an image though a lut + vips_maplut() + + + mask_butterworth + Make a butterworth filter + vips_mask_butterworth() + + + mask_butterworth_band + Make a butterworth_band filter + vips_mask_butterworth_band() + + + mask_butterworth_ring + Make a butterworth ring filter + vips_mask_butterworth_ring() + + + mask_fractal + Make fractal filter + vips_mask_fractal() + + + mask_gaussian + Make a gaussian filter + vips_mask_gaussian() + + + mask_gaussian_band + Make a gaussian filter + vips_mask_gaussian_band() + + + mask_gaussian_ring + Make a gaussian ring filter + vips_mask_gaussian_ring() + + + mask_ideal + Make an ideal filter + vips_mask_ideal() + + + mask_ideal_band + Make an ideal band filter + vips_mask_ideal_band() + + + mask_ideal_ring + Make an ideal ring filter + vips_mask_ideal_ring() + + + match + First-order match of two images + vips_match() + + + math + Apply a math operation to an image + vips_math(), vips_sin(), vips_cos(), vips_tan(), vips_asin(), vips_acos(), vips_atan(), vips_exp(), vips_exp10(), vips_log(), vips_log10() + + + math2 + Binary math operations + vips_math2(), vips_pow(), vips_wop() + + + math2_const + Binary math operations with a constant + vips_math2_const(), vips_andimage_const(), vips_orimage_const(), vips_eorimage_const(), vips_lshift_const(), vips_rshift_const(), vips_math2_const1(), vips_andimage_const1(), vips_orimage_const1(), vips_eorimage_const1(), vips_lshift_const1(), vips_rshift_const1() + + + matload + Load mat from file + vips_matload() + + + matrixinvert + Invert an matrix + vips_matrixinvert() + + + matrixload + Load matrix + vips_matrixload() + + + matrixload_source + Load matrix + vips_matrixload_source() + + + matrixprint + Print matrix + vips_matrixprint() + + + matrixsave + Save image to matrix + vips_matrixsave() + + + matrixsave_target + Save image to matrix + vips_matrixsave_target() + + + max + Find image maximum + vips_max() + + + measure + Measure a set of patches on a color chart + vips_measure() + merge - merge two images + Merge two images vips_merge() + + min + Find image minimum + vips_min() + + + morph + Morphology operation + vips_morph() + mosaic - mosaic two images + Mosaic two images vips_mosaic() mosaic1 - first-order mosaic of two images + First-order mosaic of two images vips_mosaic1() - match - first-order match of two images - vips_match() + msb + Pick most-significant byte from an image + vips_msb() - globalbalance - global balance an image mosaic - vips_globalbalance() + multiply + Multiply two images + vips_multiply() + + + niftiload + Load a nifti image + vips_niftiload() + + + niftisave + Save image to nifti file + vips_niftisave() + + + openexrload + Load an openexr image + vips_openexrload() + + + openslideload + Load file with openslide + vips_openslideload() + + + pdfload + Load pdf from file + vips_pdfload() + + + pdfload_buffer + Load pdf from buffer + vips_pdfload_buffer() + + + pdfload_source + Load pdf from source + vips_pdfload_source() + + + percent + Find threshold for percent of pixels + vips_percent() + + + perlin + Make a perlin noise image + vips_perlin() + + + phasecor + Calculate phase correlation + vips_phasecor() + + + pngload + Load png from file + vips_pngload() + + + pngload_buffer + Load png from buffer + vips_pngload_buffer() + + + pngload_source + Load png from source + vips_pngload_source() + + + pngsave + Save image to png file + vips_pngsave() + + + pngsave_buffer + Save image to png buffer + vips_pngsave_buffer() + + + pngsave_target + Save image to target as png + vips_pngsave_target() + + + ppmload + Load ppm from file + vips_ppmload() + + + ppmsave + Save image to ppm file + vips_ppmsave() + + + premultiply + Premultiply image alpha + vips_premultiply() + + + profile + Find image profiles + vips_profile() + + + profile_load + Load named icc profile + vips_profile_load() + + + project + Find image projections + vips_project() + + + quadratic + Resample an image with a quadratic transform + vips_quadratic() + + + rad2float + Unpack radiance coding to float rgb + vips_rad2float() + + + radload + Load a radiance image from a file + vips_radload() + + + radload_buffer + Load rad from buffer + vips_radload_buffer() + + + radload_source + Load rad from source + vips_radload_source() + + + radsave + Save image to radiance file + vips_radsave() + + + radsave_buffer + Save image to radiance buffer + vips_radsave_buffer() + + + radsave_target + Save image to radiance target + vips_radsave_target() + + + rank + Rank filter + vips_rank(), vips_median() + + + rawload + Load raw data from a file + vips_rawload() + + + rawsave + Save image to raw file + vips_rawsave() + + + rawsave_fd + Write raw image to file descriptor + vips_rawsave_fd() + + + recomb + Linear recombination with matrix + vips_recomb() + + + reduce + Reduce an image + vips_reduce() + + + reduceh + Shrink an image horizontally + vips_reduceh() + + + reducev + Shrink an image vertically + vips_reducev() + + + relational + Relational operation on two images + vips_relational(), vips_equal(), vips_notequal(), vips_less(), vips_lesseq(), vips_more(), vips_moreeq() + + + relational_const + Relational operations against a constant + vips_relational_const(), vips_equal_const(), vips_notequal_const(), vips_less_const(), vips_lesseq_const(), vips_more_const(), vips_moreeq_const(), vips_relational_const1(), vips_equal_const1(), vips_notequal_const1(), vips_less_const1(), vips_lesseq_const1(), vips_more_const1(), vips_moreeq_const1() + + + remainder + Remainder after integer division of two images + vips_remainder() + + + remainder_const + Remainder after integer division of an image and a constant + vips_remainder_const(), vips_remainder_const1() + + + replicate + Replicate an image + vips_replicate() + + + resize + Resize an image + vips_resize() + + + rot + Rotate an image + vips_rot() + + + rot45 + Rotate an image + vips_rot45() + + + rotate + Rotate an image by a number of degrees + vips_rotate() + + + round + Perform a round function on an image + vips_round(), vips_floor(), vips_ceil(), vips_rint() + + + sRGB2HSV + Transform srgb to hsv + vips_sRGB2HSV() + + + sRGB2scRGB + Convert an srgb image to scrgb + vips_sRGB2scRGB() + + + scRGB2BW + Convert scrgb to bw + vips_scRGB2BW() + + + scRGB2XYZ + Transform scrgb to xyz + vips_scRGB2XYZ() + + + scRGB2sRGB + Convert an scrgb image to srgb + vips_scRGB2sRGB() + + + scale + Scale an image to uchar + vips_scale() + + + sequential + Check sequential access + vips_sequential() + + + sharpen + Unsharp masking for print + vips_sharpen() + + + shrink + Shrink an image + vips_shrink() + + + shrinkh + Shrink an image horizontally + vips_shrinkh() + + + shrinkv + Shrink an image vertically + vips_shrinkv() + + + sign + Unit vector of pixel + vips_sign() + + + similarity + Similarity transform of an image + vips_similarity() + + + sines + Make a 2d sine wave + vips_sines() + + + smartcrop + Extract an area from an image + vips_smartcrop() + + + sobel + Sobel edge detector + vips_sobel() + + + spcor + Spatial correlation + vips_spcor() + + + spectrum + Make displayable power spectrum + vips_spectrum() + + + stats + Find many image stats + vips_stats() + + + stdif + Statistical difference + vips_stdif() + + + subsample + Subsample an image + vips_subsample() + + + subtract + Subtract two images + vips_subtract() + + + sum + Sum an array of images + vips_sum() + + + svgload + Load svg with rsvg + vips_svgload() + + + svgload_buffer + Load svg with rsvg + vips_svgload_buffer() + + + svgload_source + Load svg from source + vips_svgload_source() + + + switch + Find the index of the first non-zero pixel in tests + vips_switch() + + + system + Run an external command + vips_system() + + + text + Make a text image + vips_text() + + + thumbnail + Generate thumbnail from file + vips_thumbnail() + + + thumbnail_buffer + Generate thumbnail from buffer + vips_thumbnail_buffer() + + + thumbnail_image + Generate thumbnail from image + vips_thumbnail_image() + + + thumbnail_source + Generate thumbnail from source + vips_thumbnail_source() + + + tiffload + Load tiff from file + vips_tiffload() + + + tiffload_buffer + Load tiff from buffer + vips_tiffload_buffer() + + + tiffload_source + Load tiff from source + vips_tiffload_source() + + + tiffsave + Save image to tiff file + vips_tiffsave() + + + tiffsave_buffer + Save image to tiff buffer + vips_tiffsave_buffer() + + + tilecache + Cache an image as a set of tiles + vips_tilecache() + + + tonelut + Build a look-up table + vips_tonelut() + + + transpose3d + Transpose3d an image + vips_transpose3d() + + + unpremultiply + Unpremultiply image alpha + vips_unpremultiply() + + + vipsload + Load vips from file + vips_vipsload() + + + vipssave + Save image to vips file + vips_vipssave() + + + webpload + Load webp from file + vips_webpload() + + + webpload_buffer + Load webp from buffer + vips_webpload_buffer() + + + webpload_source + Load webp from source + vips_webpload_source() + + + webpsave + Save image to webp file + vips_webpsave() + + + webpsave_buffer + Save image to webp buffer + vips_webpsave_buffer() + + + webpsave_target + Save image to webp target + vips_webpsave_target() + + + worley + Make a worley noise image + vips_worley() + + + wrap + Wrap image origin + vips_wrap() + + + xyz + Make an image where pixel values are coordinates + vips_xyz() + + + zone + Make a zone plate + vips_zone() + + + zoom + Zoom an image + vips_zoom() diff --git a/doc/gen-function-list.py b/doc/gen-function-list.py old mode 100755 new mode 100644 index d1394b32..5ad30e33 --- a/doc/gen-function-list.py +++ b/doc/gen-function-list.py @@ -15,21 +15,28 @@ # vips_gamma() # -from pyvips import Operation, Error, \ +from pyvips import Introspect, Operation, Error, \ ffi, type_map, type_from_name, nickname_find # for VipsOperationFlags _OPERATION_DEPRECATED = 8 -def gen_function(operation_name): - op = Operation.new_from_name(operation_name) +def gen_function(operation_name, overloads): + intro = Introspect.get(operation_name) - print('') - print(' {}'.format(operation_name)) - print(' {}'.format(op.get_description().capitalize())) - print(' vips_{}()'.format(operation_name)) - print('') + c_operations = 'vips_{}()'.format(operation_name) + + if overloads: + c_operations += ', ' + (', '.join('vips_{}()'.format(n) for n in overloads)) + + result = '\n' + result += ' {}\n'.format(operation_name) + result += ' {}\n'.format(intro.description.capitalize()) + result += ' {}\n'.format(c_operations) + result += '' + + return result def gen_function_list(): @@ -39,10 +46,10 @@ def gen_function_list(): nickname = nickname_find(gtype) try: # can fail for abstract types - op = Operation.new_from_name(nickname) + intro = Introspect.get(nickname) # we are only interested in non-deprecated operations - if (op.get_flags() & _OPERATION_DEPRECATED) == 0: + if (intro.flags & _OPERATION_DEPRECATED) == 0: all_nicknames.append(nickname) except Error: pass @@ -53,15 +60,48 @@ def gen_function_list(): type_map(type_from_name('VipsOperation'), add_nickname) - # add 'missing' synonyms by hand - all_nicknames.append('crop') - # make list unique and sort all_nicknames = list(set(all_nicknames)) all_nicknames.sort() + # make dict with overloads + overloads = { + 'bandbool': ['bandand', 'bandor', 'bandeor', 'bandmean'], + 'bandjoin': ['bandjoin2'], + 'bandjoin_const': ['bandjoin_const1'], + 'boolean': ['andimage', 'orimage', 'eorimage', 'lshift', 'rshift'], + 'cast': ['cast_uchar', 'cast_char', 'cast_ushort', 'cast_short' 'cast_uint', 'cast_int', 'cast_float', + 'cast_double', 'cast_complex', 'cast_dpcomplex'], + 'complex': ['polar', 'rect', 'conj'], + 'complex2': ['cross_phase'], + 'complexget': ['real', 'imag'], + 'draw_circle': ['draw_circle1'], + 'draw_flood': ['draw_flood1'], + 'draw_line': ['draw_line1'], + 'draw_mask': ['draw_mask1'], + 'draw_rect': ['draw_rect1', 'draw_point', 'draw_point1'], + 'extract_area': ['crop'], + 'linear': ['linear1'], + 'math': ['sin', 'cos', 'tan', 'asin', 'acos', 'atan', 'exp', 'exp10', 'log', 'log10'], + 'math2': ['pow', 'wop'], + 'rank': ['median'], + 'relational': ['equal', 'notequal', 'less', 'lesseq', 'more', 'moreeq'], + 'remainder_const': ['remainder_const1'], + 'round': ['floor', 'ceil', 'rint'], + } + + overloads['boolean_const'] = [o + '_const' for o in overloads['boolean']] + ['boolean_const1'] + \ + [o + '_const1' for o in overloads['boolean']] + + overloads['math2_const'] = [o + '_const' for o in overloads['boolean']] + ['math2_const1'] + \ + [o + '_const1' for o in overloads['boolean']] + + overloads['relational_const'] = [o + '_const' for o in overloads['relational']] + ['relational_const1'] + \ + [o + '_const1' for o in overloads['relational']] + for nickname in all_nicknames: - gen_function(nickname) + result = gen_function(nickname, overloads[nickname] if nickname in overloads else None) + print(result) if __name__ == '__main__': diff --git a/doc/libvips-docs.xml.in b/doc/libvips-docs.xml.in index 3bbcc671..6bcd510b 100644 --- a/doc/libvips-docs.xml.in +++ b/doc/libvips-docs.xml.in @@ -61,6 +61,8 @@ + + diff --git a/fuzz/Makefile.am b/fuzz/Makefile.am index edee8cbd..9effbde2 100644 --- a/fuzz/Makefile.am +++ b/fuzz/Makefile.am @@ -25,4 +25,6 @@ libstandaloneengine_a_SOURCES = StandaloneFuzzTargetMain.c check_PROGRAMS = $(FUZZPROGS) noinst_LIBRARIES = $(FUZZLIBS) -EXTRA_DIST = $(TESTS) +EXTRA_DIST = \ + $(TESTS) \ + common_fuzzer_corpus diff --git a/fuzz/common_fuzzer_corpus/clusterfuzz-testcase-minimized-jpegsave_buffer_fuzzer-5658586599915520 b/fuzz/common_fuzzer_corpus/jpegsave_buffer_fuzzer-5658586599915520 similarity index 100% rename from fuzz/common_fuzzer_corpus/clusterfuzz-testcase-minimized-jpegsave_buffer_fuzzer-5658586599915520 rename to fuzz/common_fuzzer_corpus/jpegsave_buffer_fuzzer-5658586599915520 diff --git a/fuzz/common_fuzzer_corpus/clusterfuzz-testcase-minimized-jpegsave_buffer_fuzzer-5673786296238080 b/fuzz/common_fuzzer_corpus/jpegsave_buffer_fuzzer-5673786296238080 similarity index 100% rename from fuzz/common_fuzzer_corpus/clusterfuzz-testcase-minimized-jpegsave_buffer_fuzzer-5673786296238080 rename to fuzz/common_fuzzer_corpus/jpegsave_buffer_fuzzer-5673786296238080 diff --git a/fuzz/common_fuzzer_corpus/clusterfuzz-testcase-minimized-jpegsave_buffer_fuzzer-5759265708441600 b/fuzz/common_fuzzer_corpus/jpegsave_buffer_fuzzer-5759265708441600 similarity index 100% rename from fuzz/common_fuzzer_corpus/clusterfuzz-testcase-minimized-jpegsave_buffer_fuzzer-5759265708441600 rename to fuzz/common_fuzzer_corpus/jpegsave_buffer_fuzzer-5759265708441600 diff --git a/fuzz/common_fuzzer_corpus/clusterfuzz-testcase-minimized-jpegsave_file_fuzzer-5662041322291200 b/fuzz/common_fuzzer_corpus/jpegsave_file_fuzzer-5662041322291200 similarity index 100% rename from fuzz/common_fuzzer_corpus/clusterfuzz-testcase-minimized-jpegsave_file_fuzzer-5662041322291200 rename to fuzz/common_fuzzer_corpus/jpegsave_file_fuzzer-5662041322291200 diff --git a/fuzz/common_fuzzer_corpus/clusterfuzz-testcase-minimized-pngsave_buffer_fuzzer-5078454764044288 b/fuzz/common_fuzzer_corpus/pngsave_buffer_fuzzer-5078454764044288 similarity index 100% rename from fuzz/common_fuzzer_corpus/clusterfuzz-testcase-minimized-pngsave_buffer_fuzzer-5078454764044288 rename to fuzz/common_fuzzer_corpus/pngsave_buffer_fuzzer-5078454764044288 diff --git a/fuzz/common_fuzzer_corpus/clusterfuzz-testcase-minimized-sharpen_fuzzer-5203581631725568 b/fuzz/common_fuzzer_corpus/sharpen_fuzzer-5203581631725568 similarity index 100% rename from fuzz/common_fuzzer_corpus/clusterfuzz-testcase-minimized-sharpen_fuzzer-5203581631725568 rename to fuzz/common_fuzzer_corpus/sharpen_fuzzer-5203581631725568 diff --git a/fuzz/common_fuzzer_corpus/clusterfuzz-testcase-minimized-sharpen_fuzzer-5678720198639616 b/fuzz/common_fuzzer_corpus/sharpen_fuzzer-5678720198639616 similarity index 100% rename from fuzz/common_fuzzer_corpus/clusterfuzz-testcase-minimized-sharpen_fuzzer-5678720198639616 rename to fuzz/common_fuzzer_corpus/sharpen_fuzzer-5678720198639616 diff --git a/fuzz/common_fuzzer_corpus/clusterfuzz-testcase-minimized-sharpen_fuzzer-5691855517253632 b/fuzz/common_fuzzer_corpus/sharpen_fuzzer-5691855517253632 similarity index 100% rename from fuzz/common_fuzzer_corpus/clusterfuzz-testcase-minimized-sharpen_fuzzer-5691855517253632 rename to fuzz/common_fuzzer_corpus/sharpen_fuzzer-5691855517253632 diff --git a/fuzz/common_fuzzer_corpus/clusterfuzz-testcase-minimized-smartcrop_fuzzer-5687924892368896 b/fuzz/common_fuzzer_corpus/smartcrop_fuzzer-5687924892368896 similarity index 100% rename from fuzz/common_fuzzer_corpus/clusterfuzz-testcase-minimized-smartcrop_fuzzer-5687924892368896 rename to fuzz/common_fuzzer_corpus/smartcrop_fuzzer-5687924892368896 diff --git a/fuzz/common_fuzzer_corpus/clusterfuzz-testcase-minimized-thumbnail_fuzzer-5676300823429120 b/fuzz/common_fuzzer_corpus/thumbnail_fuzzer-5676300823429120 similarity index 100% rename from fuzz/common_fuzzer_corpus/clusterfuzz-testcase-minimized-thumbnail_fuzzer-5676300823429120 rename to fuzz/common_fuzzer_corpus/thumbnail_fuzzer-5676300823429120 diff --git a/fuzz/common_fuzzer_corpus/clusterfuzz-testcase-minimized-thumbnail_fuzzer-5718717719117824 b/fuzz/common_fuzzer_corpus/thumbnail_fuzzer-5718717719117824 similarity index 100% rename from fuzz/common_fuzzer_corpus/clusterfuzz-testcase-minimized-thumbnail_fuzzer-5718717719117824 rename to fuzz/common_fuzzer_corpus/thumbnail_fuzzer-5718717719117824 diff --git a/fuzz/common_fuzzer_corpus/clusterfuzz-testcase-minimized-thumbnail_fuzzer-5741423734816768 b/fuzz/common_fuzzer_corpus/thumbnail_fuzzer-5741423734816768 similarity index 100% rename from fuzz/common_fuzzer_corpus/clusterfuzz-testcase-minimized-thumbnail_fuzzer-5741423734816768 rename to fuzz/common_fuzzer_corpus/thumbnail_fuzzer-5741423734816768 diff --git a/fuzz/common_fuzzer_corpus/clusterfuzz-testcase-minimized-webpsave_buffer_fuzzer-5207224829345792 b/fuzz/common_fuzzer_corpus/webpsave_buffer_fuzzer-5207224829345792 similarity index 100% rename from fuzz/common_fuzzer_corpus/clusterfuzz-testcase-minimized-webpsave_buffer_fuzzer-5207224829345792 rename to fuzz/common_fuzzer_corpus/webpsave_buffer_fuzzer-5207224829345792 diff --git a/fuzz/common_fuzzer_corpus/clusterfuzz-testcase-minimized-webpsave_buffer_fuzzer-5674696418263040 b/fuzz/common_fuzzer_corpus/webpsave_buffer_fuzzer-5674696418263040 similarity index 100% rename from fuzz/common_fuzzer_corpus/clusterfuzz-testcase-minimized-webpsave_buffer_fuzzer-5674696418263040 rename to fuzz/common_fuzzer_corpus/webpsave_buffer_fuzzer-5674696418263040 diff --git a/fuzz/common_fuzzer_corpus/x-ascii.ppm b/fuzz/common_fuzzer_corpus/x-ascii.ppm deleted file mode 100644 index aa81a14b..00000000 --- a/fuzz/common_fuzzer_corpus/x-ascii.ppm +++ /dev/null @@ -1,15 +0,0 @@ -P2 -#vips2ppm - Fri Aug 23 12:48:07 2019 - -10 10 -255 -96 101 113 118 124 130 136 141 147 150 -81 87 98 101 107 112 117 123 130 135 -73 78 85 90 95 99 103 110 118 124 -46 51 60 67 73 81 87 94 103 109 -34 35 40 48 60 69 77 81 85 88 -28 26 31 36 45 54 59 64 69 72 -32 31 41 39 39 40 45 52 61 66 -38 38 47 42 38 36 38 43 49 53 -37 38 39 39 37 37 37 36 34 32 -36 36 38 36 35 34 35 32 28 25 diff --git a/fuzz/common_fuzzer_corpus/x.csv b/fuzz/common_fuzzer_corpus/x.csv deleted file mode 100644 index 640265ec..00000000 --- a/fuzz/common_fuzzer_corpus/x.csv +++ /dev/null @@ -1,10 +0,0 @@ -96 101 113 118 124 130 136 141 147 150 -81 87 98 101 107 112 117 123 130 135 -73 78 85 90 95 99 103 110 118 124 -46 51 60 67 73 81 87 94 103 109 -34 35 40 48 60 69 77 81 85 88 -28 26 31 36 45 54 59 64 69 72 -32 31 41 39 39 40 45 52 61 66 -38 38 47 42 38 36 38 43 49 53 -37 38 39 39 37 37 37 36 34 32 -36 36 38 36 35 34 35 32 28 25 diff --git a/fuzz/common_fuzzer_corpus/x.mat b/fuzz/common_fuzzer_corpus/x.mat deleted file mode 100644 index bed7279e..00000000 --- a/fuzz/common_fuzzer_corpus/x.mat +++ /dev/null @@ -1,11 +0,0 @@ -10 10 -96 101 113 118 124 130 136 141 147 150 -81 87 98 101 107 112 117 123 130 135 -73 78 85 90 95 99 103 110 118 124 -46 51 60 67 73 81 87 94 103 109 -34 35 40 48 60 69 77 81 85 88 -28 26 31 36 45 54 59 64 69 72 -32 31 41 39 39 40 45 52 61 66 -38 38 47 42 38 36 38 43 49 53 -37 38 39 39 37 37 37 36 34 32 -36 36 38 36 35 34 35 32 28 25 diff --git a/fuzz/common_fuzzer_corpus/x.ppm b/fuzz/common_fuzzer_corpus/x.ppm deleted file mode 100644 index cc0b6072..00000000 --- a/fuzz/common_fuzzer_corpus/x.ppm +++ /dev/null @@ -1,6 +0,0 @@ -P5 -#vips2ppm - Fri Aug 23 12:47:38 2019 - -10 10 -255 -`eqv|QWbekpu{INUZ_cgnv|.3LQDUWL]\SfcZolbzri|.(/)3#.9+8E7FM@QSHYVM`ZQf]Tg' %*$- *6(5=1?C6HG;OL@VOCW,!)!3$+2",0#-1#05(9>/B.""/##0$&/#'.!(.!*. --.+.)-," ,"!/##-!#,&+'-*+*'(#% \ No newline at end of file diff --git a/fuzz/jpegsave_buffer_fuzzer.cc b/fuzz/jpegsave_buffer_fuzzer.cc index 2b297f2b..6c7a5fdf 100644 --- a/fuzz/jpegsave_buffer_fuzzer.cc +++ b/fuzz/jpegsave_buffer_fuzzer.cc @@ -17,11 +17,9 @@ LLVMFuzzerTestOneInput( const guint8 *data, size_t size ) if( !(image = vips_image_new_from_buffer( data, size, "", NULL )) ) return( 0 ); - /* Skip big images. They are likely to timeout. - */ - if( image->Xsize > 1024 || - image->Ysize > 1024 || - image->Bands > 10 ) { + if( image->Xsize > 100 || + image->Ysize > 100 || + image->Bands > 4 ) { g_object_unref( image ); return( 0 ); } diff --git a/fuzz/jpegsave_file_fuzzer.cc b/fuzz/jpegsave_file_fuzzer.cc index 878dd9e6..7b31fedc 100644 --- a/fuzz/jpegsave_file_fuzzer.cc +++ b/fuzz/jpegsave_file_fuzzer.cc @@ -19,11 +19,9 @@ test_one_file( const char *name ) NULL )) ) return( 0 ); - /* Skip big images. They are likely to timeout. - */ - if( image->Xsize > 1024 || - image->Ysize > 1024 || - image->Bands > 10 ) { + if( image->Xsize > 100 || + image->Ysize > 100 || + image->Bands > 4 ) { g_object_unref( image ); return( 0 ); } diff --git a/fuzz/mosaic_fuzzer.cc b/fuzz/mosaic_fuzzer.cc index a7b7a12d..7f3c82b8 100644 --- a/fuzz/mosaic_fuzzer.cc +++ b/fuzz/mosaic_fuzzer.cc @@ -22,17 +22,15 @@ LLVMFuzzerTestOneInput( const guint8 *data, size_t size ) struct mosaic_opt *opt; double d; - if( size < sizeof(struct mosaic_opt) ) + if( size < sizeof( struct mosaic_opt ) ) return( 0 ); if( !(ref = vips_image_new_from_buffer( data, size, "", NULL )) ) return( 0 ); - /* Skip big images. They are likely to timeout. - */ - if( ref->Xsize > 1024 || - ref->Ysize > 1024 || - ref->Bands > 10 ) { + if( ref->Xsize > 100 || + ref->Ysize > 100 || + ref->Bands > 4 ) { g_object_unref( ref ); return( 0 ); } @@ -44,10 +42,10 @@ LLVMFuzzerTestOneInput( const guint8 *data, size_t size ) /* Extract some bytes from the tail to fuzz the arguments of the API. */ - opt = (struct mosaic_opt *) (data + size - sizeof(struct mosaic_opt)); + opt = (struct mosaic_opt *) (data + size - sizeof( struct mosaic_opt )); if( vips_mosaic( ref, sec, &out, (VipsDirection) opt->dir, - opt->xref, opt->yref, opt->xsec, opt->ysec, NULL ) ) { + opt->xref, opt->yref, opt->xsec, opt->ysec, NULL ) ) { g_object_unref( sec ); g_object_unref( ref ); return( 0 ); diff --git a/fuzz/pngsave_buffer_fuzzer.cc b/fuzz/pngsave_buffer_fuzzer.cc index 95726518..099c5d41 100644 --- a/fuzz/pngsave_buffer_fuzzer.cc +++ b/fuzz/pngsave_buffer_fuzzer.cc @@ -17,11 +17,9 @@ LLVMFuzzerTestOneInput( const guint8 *data, size_t size ) if( !(image = vips_image_new_from_buffer( data, size, "", NULL )) ) return( 0 ); - /* Skip big images. They are likely to timeout. - */ - if( image->Xsize > 1024 || - image->Ysize > 1024 || - image->Bands > 10 ) { + if( image->Xsize > 100 || + image->Ysize > 100 || + image->Bands > 4 ) { g_object_unref( image ); return( 0 ); } diff --git a/fuzz/sharpen_fuzzer.cc b/fuzz/sharpen_fuzzer.cc index 2bf887c9..fffb1d0a 100644 --- a/fuzz/sharpen_fuzzer.cc +++ b/fuzz/sharpen_fuzzer.cc @@ -16,11 +16,9 @@ LLVMFuzzerTestOneInput( const guint8 *data, size_t size ) if( !(image = vips_image_new_from_buffer( data, size, "", NULL )) ) return( 0 ); - /* Skip big images. They are likely to timeout. - */ - if( image->Xsize > 1024 || - image->Ysize > 1024 || - image->Bands > 10 ) { + if( image->Xsize > 100 || + image->Ysize > 100 || + image->Bands > 4 ) { g_object_unref( image ); return( 0 ); } diff --git a/fuzz/smartcrop_fuzzer.cc b/fuzz/smartcrop_fuzzer.cc index 30e349d1..06828b10 100644 --- a/fuzz/smartcrop_fuzzer.cc +++ b/fuzz/smartcrop_fuzzer.cc @@ -16,11 +16,9 @@ LLVMFuzzerTestOneInput( const guint8 *data, size_t size ) if( !(image = vips_image_new_from_buffer( data, size, "", NULL )) ) return( 0 ); - /* Skip big images. They are likely to timeout. - */ - if( image->Xsize > 1024 || - image->Ysize > 1024 || - image->Bands > 10 ) { + if( image->Xsize > 100 || + image->Ysize > 100 || + image->Bands > 4 ) { g_object_unref( image ); return( 0 ); } diff --git a/fuzz/test_fuzz.sh b/fuzz/test_fuzz.sh index e536e113..60d75a23 100755 --- a/fuzz/test_fuzz.sh +++ b/fuzz/test_fuzz.sh @@ -3,7 +3,7 @@ #set -x set -e -# Glib is build without -fno-omit-frame-pointer. We need +# Glib is built without -fno-omit-frame-pointer. We need # to disable the fast unwinder to get full stacktraces. export ASAN_OPTIONS="fast_unwind_on_malloc=0:allocator_may_return_null=1" export UBSAN_OPTIONS="print_stacktrace=1" @@ -14,8 +14,12 @@ export VIPS_WARNING=0 ret=0 for fuzzer in *_fuzzer; do - find "common_fuzzer_corpus" -type f -not -empty -print0 \ - | xargs -0 -n1 "./$fuzzer" || ret=1 + for file in common_fuzzer_corpus/*; do + if ! ./$fuzzer $file; then + echo FAIL $fuzzer $file + ret=1 + fi + done done exit $ret diff --git a/fuzz/thumbnail_fuzzer.cc b/fuzz/thumbnail_fuzzer.cc index e96a4e52..8a21edac 100644 --- a/fuzz/thumbnail_fuzzer.cc +++ b/fuzz/thumbnail_fuzzer.cc @@ -16,11 +16,9 @@ LLVMFuzzerTestOneInput( const guint8 *data, size_t size ) if( !(image = vips_image_new_from_buffer( data, size, "", NULL )) ) return( 0 ); - /* Skip big images. They are likely to timeout. - */ - if( image->Xsize > 1024 || - image->Ysize > 1024 || - image->Bands > 10 ) { + if( image->Xsize > 100 || + image->Ysize > 100 || + image->Bands > 4 ) { g_object_unref( image ); return( 0 ); } diff --git a/fuzz/webpsave_buffer_fuzzer.cc b/fuzz/webpsave_buffer_fuzzer.cc index 4a53135b..931645c2 100644 --- a/fuzz/webpsave_buffer_fuzzer.cc +++ b/fuzz/webpsave_buffer_fuzzer.cc @@ -17,11 +17,9 @@ LLVMFuzzerTestOneInput( const guint8 *data, size_t size ) if( !(image = vips_image_new_from_buffer( data, size, "", NULL )) ) return( 0 ); - /* Skip big images. They are likely to timeout. - */ - if( image->Xsize > 1024 || - image->Ysize > 1024 || - image->Bands > 10 ) { + if( image->Xsize > 100 || + image->Ysize > 100 || + image->Bands > 4 ) { g_object_unref( image ); return( 0 ); } diff --git a/libvips/Makefile.am b/libvips/Makefile.am index eab47939..6288cf33 100644 --- a/libvips/Makefile.am +++ b/libvips/Makefile.am @@ -1,6 +1,6 @@ -OPTIONAL_COMPILE_DIR = +OPTIONAL_COMPILE_DIR = OPTIONAL_DIST_DIR = -OPTIONAL_LIB = +OPTIONAL_LIB = if ENABLE_DEPRECATED OPTIONAL_COMPILE_DIR += deprecated @@ -24,8 +24,7 @@ SUBDIRS = \ iofuncs \ morphology \ mosaicing \ - create - . + create lib_LTLIBRARIES = libvips.la @@ -87,7 +86,7 @@ AM_LDFLAGS = \ LDADD = @INTROSPECTION_LIBS@ @VIPS_CFLAGS@ libvips.la @VIPS_LIBS@ noinst_PROGRAMS = \ - introspect + introspect$(EXEEXT) introspect_SOURCES = \ introspect.c @@ -96,7 +95,7 @@ introspect_SOURCES = \ introspection_sources = @vips_introspection_sources@ # we make the vips8 API -Vips-8.0.gir: introspect +Vips-8.0.gir: introspect$(EXEEXT) Vips_8_0_gir_INCLUDES = GObject-2.0 Vips_8_0_gir_CFLAGS = $(INCLUDES) -I${top_srcdir}/libvips/include Vips_8_0_gir_LIBS = libvips.la diff --git a/libvips/arithmetic/add.c b/libvips/arithmetic/add.c index a077e3af..8a54ce97 100644 --- a/libvips/arithmetic/add.c +++ b/libvips/arithmetic/add.c @@ -261,7 +261,7 @@ vips_add_init( VipsAdd *add ) * range of possible values. * * Operations on integer images are performed using the processor's vector unit, - * if possible. Disable this with --vips-novector or IM_NOVECTOR. + * if possible. Disable this with --vips-novector or VIPS_NOVECTOR. * * See also: vips_subtract(), vips_linear(). * diff --git a/libvips/arithmetic/boolean.c b/libvips/arithmetic/boolean.c index c4fcf18d..ad978fe1 100644 --- a/libvips/arithmetic/boolean.c +++ b/libvips/arithmetic/boolean.c @@ -562,7 +562,7 @@ vips_boolean_const_init( VipsBooleanConst *boolean_const ) static int vips_boolean_constv( VipsImage *in, VipsImage **out, - VipsOperationBoolean operation, double *c, int n, va_list ap ) + VipsOperationBoolean operation, const double *c, int n, va_list ap ) { VipsArea *area_c; double *array; @@ -609,7 +609,7 @@ vips_boolean_constv( VipsImage *in, VipsImage **out, */ int vips_boolean_const( VipsImage *in, VipsImage **out, - VipsOperationBoolean boolean, double *c, int n, ... ) + VipsOperationBoolean boolean, const double *c, int n, ... ) { va_list ap; int result; @@ -637,7 +637,8 @@ vips_boolean_const( VipsImage *in, VipsImage **out, * Returns: 0 on success, -1 on error */ int -vips_andimage_const( VipsImage *in, VipsImage **out, double *c, int n, ... ) +vips_andimage_const( VipsImage *in, VipsImage **out, + const double *c, int n, ... ) { va_list ap; int result; @@ -666,7 +667,8 @@ vips_andimage_const( VipsImage *in, VipsImage **out, double *c, int n, ... ) * Returns: 0 on success, -1 on error */ int -vips_orimage_const( VipsImage *in, VipsImage **out, double *c, int n, ... ) +vips_orimage_const( VipsImage *in, VipsImage **out, + const double *c, int n, ... ) { va_list ap; int result; @@ -695,7 +697,8 @@ vips_orimage_const( VipsImage *in, VipsImage **out, double *c, int n, ... ) * Returns: 0 on success, -1 on error */ int -vips_eorimage_const( VipsImage *in, VipsImage **out, double *c, int n, ... ) +vips_eorimage_const( VipsImage *in, VipsImage **out, + const double *c, int n, ... ) { va_list ap; int result; @@ -724,7 +727,7 @@ vips_eorimage_const( VipsImage *in, VipsImage **out, double *c, int n, ... ) * Returns: 0 on success, -1 on error */ int -vips_lshift_const( VipsImage *in, VipsImage **out, double *c, int n, ... ) +vips_lshift_const( VipsImage *in, VipsImage **out, const double *c, int n, ... ) { va_list ap; int result; @@ -753,7 +756,7 @@ vips_lshift_const( VipsImage *in, VipsImage **out, double *c, int n, ... ) * Returns: 0 on success, -1 on error */ int -vips_rshift_const( VipsImage *in, VipsImage **out, double *c, int n, ... ) +vips_rshift_const( VipsImage *in, VipsImage **out, const double *c, int n, ... ) { va_list ap; int result; diff --git a/libvips/arithmetic/hough_circle.c b/libvips/arithmetic/hough_circle.c index 86c76a50..36b12040 100644 --- a/libvips/arithmetic/hough_circle.c +++ b/libvips/arithmetic/hough_circle.c @@ -150,11 +150,12 @@ vips_hough_circle_vote_endpoints_clip( VipsImage *image, int y, int x1, int x2, int quadrant, void *client ) { int r = *((int *) client); - guint *line = (guint *) VIPS_IMAGE_ADDR( image, 0, y ) + r; int b = image->Bands; if( y >= 0 && y < image->Ysize ) { + guint *line = (guint *) VIPS_IMAGE_ADDR( image, 0, y ) + r; + if( x1 >=0 && x1 < image->Xsize ) line[x1 * b] += 1; @@ -278,6 +279,9 @@ vips_hough_circle_init( VipsHoughCircle *hough_circle ) * votes by circle circumference so circles of differing size are given equal * weight. * + * The output pixel at (x, y, band) is the strength of the circle centred on + * (x, y) and with radius (band). + * * Use @max_radius and @min_radius to set the range of radii to search for. * * Use @scale to set how @in coordinates are scaled to @out coordinates. A diff --git a/libvips/arithmetic/linear.c b/libvips/arithmetic/linear.c index 4a376a55..ebf4fb33 100644 --- a/libvips/arithmetic/linear.c +++ b/libvips/arithmetic/linear.c @@ -453,7 +453,7 @@ vips_linear_init( VipsLinear *linear ) static int vips_linearv( VipsImage *in, VipsImage **out, - double *a, double *b, int n, va_list ap ) + const double *a, const double *b, int n, va_list ap ) { VipsArea *area_a; VipsArea *area_b; @@ -500,7 +500,8 @@ vips_linearv( VipsImage *in, VipsImage **out, * Returns: 0 on success, -1 on error */ int -vips_linear( VipsImage *in, VipsImage **out, double *a, double *b, int n, ... ) +vips_linear( VipsImage *in, VipsImage **out, + const double *a, const double *b, int n, ... ) { va_list ap; int result; diff --git a/libvips/arithmetic/math2.c b/libvips/arithmetic/math2.c index cbcd2b99..dd3e5323 100644 --- a/libvips/arithmetic/math2.c +++ b/libvips/arithmetic/math2.c @@ -420,7 +420,7 @@ vips_math2_const_init( VipsMath2Const *math2_const ) static int vips_math2_constv( VipsImage *in, VipsImage **out, - VipsOperationMath2 math2, double *c, int n, va_list ap ) + VipsOperationMath2 math2, const double *c, int n, va_list ap ) { VipsArea *area_c; double *array; @@ -470,7 +470,7 @@ vips_math2_constv( VipsImage *in, VipsImage **out, */ int vips_math2_const( VipsImage *in, VipsImage **out, - VipsOperationMath2 math2, double *c, int n, ... ) + VipsOperationMath2 math2, const double *c, int n, ... ) { va_list ap; int result; @@ -496,7 +496,7 @@ vips_math2_const( VipsImage *in, VipsImage **out, * Returns: 0 on success, -1 on error */ int -vips_pow_const( VipsImage *in, VipsImage **out, double *c, int n, ... ) +vips_pow_const( VipsImage *in, VipsImage **out, const double *c, int n, ... ) { va_list ap; int result; @@ -523,7 +523,7 @@ vips_pow_const( VipsImage *in, VipsImage **out, double *c, int n, ... ) * Returns: 0 on success, -1 on error */ int -vips_wop_const( VipsImage *in, VipsImage **out, double *c, int n, ... ) +vips_wop_const( VipsImage *in, VipsImage **out, const double *c, int n, ... ) { va_list ap; int result; diff --git a/libvips/arithmetic/relational.c b/libvips/arithmetic/relational.c index f2f8f817..d740ea5c 100644 --- a/libvips/arithmetic/relational.c +++ b/libvips/arithmetic/relational.c @@ -598,7 +598,7 @@ vips_relational_const_init( VipsRelationalConst *relational_const ) static int vips_relational_constv( VipsImage *in, VipsImage **out, - VipsOperationRelational relational, double *c, int n, va_list ap ) + VipsOperationRelational relational, const double *c, int n, va_list ap ) { VipsArea *area_c; double *array; @@ -645,7 +645,7 @@ vips_relational_constv( VipsImage *in, VipsImage **out, */ int vips_relational_const( VipsImage *in, VipsImage **out, - VipsOperationRelational relational, double *c, int n, ... ) + VipsOperationRelational relational, const double *c, int n, ... ) { va_list ap; int result; @@ -671,7 +671,7 @@ vips_relational_const( VipsImage *in, VipsImage **out, * Returns: 0 on success, -1 on error */ int -vips_equal_const( VipsImage *in, VipsImage **out, double *c, int n, ... ) +vips_equal_const( VipsImage *in, VipsImage **out, const double *c, int n, ... ) { va_list ap; int result; @@ -698,7 +698,8 @@ vips_equal_const( VipsImage *in, VipsImage **out, double *c, int n, ... ) * Returns: 0 on success, -1 on error */ int -vips_notequal_const( VipsImage *in, VipsImage **out, double *c, int n, ... ) +vips_notequal_const( VipsImage *in, VipsImage **out, + const double *c, int n, ... ) { va_list ap; int result; @@ -725,7 +726,7 @@ vips_notequal_const( VipsImage *in, VipsImage **out, double *c, int n, ... ) * Returns: 0 on success, -1 on error */ int -vips_less_const( VipsImage *in, VipsImage **out, double *c, int n, ... ) +vips_less_const( VipsImage *in, VipsImage **out, const double *c, int n, ... ) { va_list ap; int result; @@ -752,7 +753,7 @@ vips_less_const( VipsImage *in, VipsImage **out, double *c, int n, ... ) * Returns: 0 on success, -1 on error */ int -vips_lesseq_const( VipsImage *in, VipsImage **out, double *c, int n, ... ) +vips_lesseq_const( VipsImage *in, VipsImage **out, const double *c, int n, ... ) { va_list ap; int result; @@ -779,7 +780,7 @@ vips_lesseq_const( VipsImage *in, VipsImage **out, double *c, int n, ... ) * Returns: 0 on success, -1 on error */ int -vips_more_const( VipsImage *in, VipsImage **out, double *c, int n, ... ) +vips_more_const( VipsImage *in, VipsImage **out, const double *c, int n, ... ) { va_list ap; int result; @@ -806,7 +807,7 @@ vips_more_const( VipsImage *in, VipsImage **out, double *c, int n, ... ) * Returns: 0 on success, -1 on error */ int -vips_moreeq_const( VipsImage *in, VipsImage **out, double *c, int n, ... ) +vips_moreeq_const( VipsImage *in, VipsImage **out, const double *c, int n, ... ) { va_list ap; int result; diff --git a/libvips/arithmetic/remainder.c b/libvips/arithmetic/remainder.c index 75df8f4d..3c994345 100644 --- a/libvips/arithmetic/remainder.c +++ b/libvips/arithmetic/remainder.c @@ -333,7 +333,7 @@ vips_remainder_const_init( VipsRemainderConst *remainder_const ) static int vips_remainder_constv( VipsImage *in, VipsImage **out, - double *c, int n, va_list ap ) + const double *c, int n, va_list ap ) { VipsArea *area_c; double *array; @@ -379,7 +379,8 @@ vips_remainder_constv( VipsImage *in, VipsImage **out, * Returns: 0 on success, -1 on error */ int -vips_remainder_const( VipsImage *in, VipsImage **out, double *c, int n, ... ) +vips_remainder_const( VipsImage *in, VipsImage **out, + const double *c, int n, ... ) { va_list ap; int result; diff --git a/libvips/arithmetic/unaryconst.c b/libvips/arithmetic/unaryconst.c index f098f169..f3aa4732 100644 --- a/libvips/arithmetic/unaryconst.c +++ b/libvips/arithmetic/unaryconst.c @@ -44,6 +44,7 @@ #include #include +#include #include diff --git a/libvips/colour/Makefile.am b/libvips/colour/Makefile.am index 52a4f594..b8ced977 100644 --- a/libvips/colour/Makefile.am +++ b/libvips/colour/Makefile.am @@ -1,5 +1,7 @@ noinst_LTLIBRARIES = libcolour.la +SUBDIRS = profiles + libcolour_la_SOURCES = \ profiles.c \ profiles.h \ @@ -42,7 +44,6 @@ profiles.c: ./wrap-profiles.sh profiles profiles.c EXTRA_DIST = \ - profiles \ wrap-profiles.sh AM_CPPFLAGS = -I${top_srcdir}/libvips/include @VIPS_CFLAGS@ @VIPS_INCLUDES@ diff --git a/libvips/colour/colourspace.c b/libvips/colour/colourspace.c index 3779ce6f..1b05545d 100644 --- a/libvips/colour/colourspace.c +++ b/libvips/colour/colourspace.c @@ -493,9 +493,7 @@ static VipsColourRoute vips_colour_routes[] = { * vips_colourspace_issupported: * @image: input image * - * Test if @image is in a colourspace that vips_colourspace() can process. For - * example, #VIPS_INTERPRETATION_RGB images are not in a well-defined - * colourspace, but #VIPS_INTERPRETATION_sRGB ones are. + * Test if @image is in a colourspace that vips_colourspace() can process. * * Returns: %TRUE if @image is in a supported colourspace. */ diff --git a/libvips/colour/icc_transform.c b/libvips/colour/icc_transform.c index 9dab23c5..1caa8a58 100644 --- a/libvips/colour/icc_transform.c +++ b/libvips/colour/icc_transform.c @@ -282,7 +282,7 @@ vips_icc_build( VipsObject *object ) case cmsSigRgbData: colour->interpretation = icc->depth == 8 ? - VIPS_INTERPRETATION_RGB : + VIPS_INTERPRETATION_sRGB : VIPS_INTERPRETATION_RGB16; colour->format = icc->depth == 8 ? diff --git a/libvips/colour/profile_load.c b/libvips/colour/profile_load.c index 083e56d5..c7fdb4a5 100644 --- a/libvips/colour/profile_load.c +++ b/libvips/colour/profile_load.c @@ -89,17 +89,14 @@ vips_profile_load_build( VipsObject *object ) build( object ) ) return( -1 ); - if( g_ascii_strcasecmp( load->name, "none" ) == 0 ) { + if( g_ascii_strcasecmp( load->name, "none" ) == 0 ) profile = NULL; - } - else if( (data = vips_profile_fallback_get( load->name, &length )) ) { + else if( (data = vips_profile_fallback_get( load->name, &length )) ) profile = vips_blob_new( NULL, data, length ); - } else if( (data = vips__file_read_name( load->name, - vips__icc_dir(), &length )) ) { + vips__icc_dir(), &length )) ) profile = vips_blob_new( (VipsCallbackFn) g_free, data, length ); - } else { vips_error( class->nickname, _( "unable to load profile \"%s\"" ), load->name ); diff --git a/libvips/colour/profiles/Makefile.am b/libvips/colour/profiles/Makefile.am new file mode 100644 index 00000000..fc8f9734 --- /dev/null +++ b/libvips/colour/profiles/Makefile.am @@ -0,0 +1,3 @@ +EXTRA_DIST = \ + cmyk.icm \ + sRGB.icm diff --git a/libvips/conversion/autorot.c b/libvips/conversion/autorot.c index ceb5de83..b297dc95 100644 --- a/libvips/conversion/autorot.c +++ b/libvips/conversion/autorot.c @@ -6,6 +6,9 @@ * - test and remove orientation from every ifd * 6/10/18 * - don't remove orientation if it's one of the cases we don't handle + * 10/5/20 + * - handle mirrored images + * - deprecate vips_autorot_get_angle() */ /* @@ -54,6 +57,7 @@ typedef struct _VipsAutorot { VipsImage *in; VipsAngle angle; + gboolean flip; } VipsAutorot; @@ -61,83 +65,6 @@ typedef VipsConversionClass VipsAutorotClass; G_DEFINE_TYPE( VipsAutorot, vips_autorot, VIPS_TYPE_CONVERSION ); -/** - * vips_autorot_get_angle: - * @image: image to fetch orientation from - * - * Examine the metadata on @im and return the #VipsAngle to rotate by to turn - * the image upright. - * - * See also: vips_autorot(). - * - * Returns: the #VipsAngle to rotate by to make the image upright. - */ -VipsAngle -vips_autorot_get_angle( VipsImage *im ) -{ - int orientation; - VipsAngle angle; - - if( !vips_image_get_typeof( im, VIPS_META_ORIENTATION ) || - vips_image_get_int( im, VIPS_META_ORIENTATION, &orientation ) ) - orientation = 1; - - switch( orientation ) { - case 6: - angle = VIPS_ANGLE_D90; - break; - - case 8: - angle = VIPS_ANGLE_D270; - break; - - case 3: - angle = VIPS_ANGLE_D180; - break; - - default: - /* Other values do rotate + mirror, don't bother handling them - * though, how common can mirroring be. - * - * See: - * - * http://www.80sidea.com/archives/2316 - */ - angle = VIPS_ANGLE_D0; - break; - } - - return( angle ); -} - -/* TRUE if this is one of the cases we handle. - */ -static gboolean -vips_autorot_handled( VipsImage *im ) -{ - int orientation; - gboolean handled; - - if( !vips_image_get_typeof( im, VIPS_META_ORIENTATION ) || - vips_image_get_int( im, VIPS_META_ORIENTATION, &orientation ) ) - orientation = 1; - - switch( orientation ) { - case 1: - case 3: - case 6: - case 8: - handled = TRUE; - break; - - default: - handled = FALSE; - break; - } - - return( handled ); -} - static void * vips_autorot_remove_angle_sub( VipsImage *image, const char *field, GValue *value, void *my_data ) @@ -158,14 +85,14 @@ vips_autorot_remove_angle_sub( VipsImage *image, * vips_autorot_remove_angle: (method) * @image: image to remove orientation from * - * Remove the orientation tag on @image. Also remove any exif orientation tags. - * - * See also: vips_autorot_get_angle(). + * Remove the orientation tag on @image. Also remove any exif orientation tags. + * You must vips_copy() the image before calling this function since it + * modifies metadata. */ void vips_autorot_remove_angle( VipsImage *image ) { - (void) vips_image_remove( image, VIPS_META_ORIENTATION ); + (void) vips_image_remove( image, VIPS_META_ORIENTATION ); (void) vips_image_map( image, vips_autorot_remove_angle_sub, NULL ); } @@ -174,26 +101,87 @@ vips_autorot_build( VipsObject *object ) { VipsConversion *conversion = VIPS_CONVERSION( object ); VipsAutorot *autorot = (VipsAutorot *) object; - VipsImage **t = (VipsImage **) vips_object_local_array( object, 1 ); + VipsImage **t = (VipsImage **) vips_object_local_array( object, 3 ); if( VIPS_OBJECT_CLASS( vips_autorot_parent_class )->build( object ) ) return( -1 ); + + VipsAngle angle; + gboolean flip; + VipsImage *in; - g_object_set( object, - "angle", vips_autorot_get_angle( autorot->in ), - NULL ); + in = autorot->in; + + switch( vips_image_get_orientation( in ) ) { + case 2: + angle = VIPS_ANGLE_D0; + flip = TRUE; + break; + + case 3: + angle = VIPS_ANGLE_D180; + flip = FALSE; + break; + + case 4: + angle = VIPS_ANGLE_D180; + flip = TRUE; + break; + + case 5: + angle = VIPS_ANGLE_D90; + flip = TRUE; + break; + + case 6: + angle = VIPS_ANGLE_D90; + flip = FALSE; + break; + + case 7: + angle = VIPS_ANGLE_D270; + flip = TRUE; + break; + + case 8: + angle = VIPS_ANGLE_D270; + flip = FALSE; + break; + + case 1: + default: + angle = VIPS_ANGLE_D0; + flip = FALSE; + break; - if( vips_autorot_handled( autorot->in ) ) { - if( vips_rot( autorot->in, &t[0], autorot->angle, NULL ) ) - return( -1 ); - vips_autorot_remove_angle( t[0] ); - } - else { - if( vips_copy( autorot->in, &t[0], NULL ) ) - return( -1 ); } - if( vips_image_write( t[0], conversion->out ) ) + g_object_set( object, + "angle", angle, + "flip", flip, + NULL ); + + if( angle != VIPS_ANGLE_D0 ) { + if( vips_rot( in, &t[0], angle, NULL ) ) + return( -1 ); + in = t[0]; + } + + if( flip ) { + if( vips_flip( in, &t[1], VIPS_DIRECTION_HORIZONTAL, NULL ) ) + return( -1 ); + in = t[1]; + } + + /* We must copy before modifying metadata. + */ + if( vips_copy( in, &t[2], NULL ) ) + return( -1 ); + in = t[2]; + + vips_autorot_remove_angle( in ); + + if( vips_image_write( in, conversion->out ) ) return( -1 ); return( 0 ); @@ -223,13 +211,21 @@ vips_autorot_class_init( VipsAutorotClass *class ) _( "Angle image was rotated by" ), VIPS_ARGUMENT_OPTIONAL_OUTPUT, G_STRUCT_OFFSET( VipsAutorot, angle ), - VIPS_TYPE_ANGLE, VIPS_ANGLE_D0 ); + VIPS_TYPE_ANGLE, VIPS_ANGLE_D0 ); + + VIPS_ARG_BOOL( class, "flip", 7, + _( "Flip" ), + _( "Whether the image was flipped or not" ), + VIPS_ARGUMENT_OPTIONAL_OUTPUT, + G_STRUCT_OFFSET( VipsAutorot, flip ), + FALSE ); } static void vips_autorot_init( VipsAutorot *autorot ) { autorot->angle = VIPS_ANGLE_D0; + autorot->flip = FALSE; } /** @@ -241,18 +237,14 @@ vips_autorot_init( VipsAutorot *autorot ) * Optional arguments: * * * @angle: output #VipsAngle the image was rotated by + * * @flip: output %gboolean whether the image was flipped * - * Look at the image metadata and rotate the image to make it upright. The - * #VIPS_META_ORIENTATION tag is removed from @out to prevent accidental - * double rotation. + * Look at the image metadata and rotate and flip the image to make it + * upright. The #VIPS_META_ORIENTATION tag is removed from @out to prevent + * accidental double rotation. * - * Read @angle to find the amount the image was rotated by. - * - * vips only supports the four simple rotations, it does not support the - * various mirror modes. If the image is using one of these mirror modes, the - * image is not rotated and the #VIPS_META_ORIENTATION tag is not removed. - * - * See also: vips_autorot_get_angle(), vips_autorot_remove_angle(), vips_rot(). + * Read @angle to find the amount the image was rotated by. Read @flip to + * see if the image was also flipped. * * Returns: 0 on success, -1 on error */ @@ -268,4 +260,3 @@ vips_autorot( VipsImage *in, VipsImage **out, ... ) return( result ); } - diff --git a/libvips/conversion/cast.c b/libvips/conversion/cast.c index 1a7a8848..3efb0918 100644 --- a/libvips/conversion/cast.c +++ b/libvips/conversion/cast.c @@ -126,10 +126,11 @@ G_DEFINE_TYPE( VipsCast, vips_cast, VIPS_TYPE_CONVERSION ); #define CAST_SHORT( X ) VIPS_CLIP( SHRT_MIN, (X), SHRT_MAX ) /* We know the source cannot be the same as the dest, so we will only use - * CAST_UINT() for an INT source, and vice versa. + * CAST_UINT() for an INT source, and vice versa. We don't need to clip to + * INT_MAX, since float->int does that for us. */ #define CAST_UINT( X ) VIPS_MAX( 0, (X) ) -#define CAST_INT( X ) VIPS_MIN( (X), INT_MAX ) +#define CAST_INT( X ) (X) /* Rightshift an integer type, ie. sizeof(ITYPE) > sizeof(OTYPE). */ diff --git a/libvips/conversion/composite.cpp b/libvips/conversion/composite.cpp index 84ad9cfb..946975d5 100644 --- a/libvips/conversion/composite.cpp +++ b/libvips/conversion/composite.cpp @@ -343,6 +343,13 @@ vips_composite_base_max_band( VipsCompositeBase *composite, double *max_band ) max_band[2] = 255; break; + case VIPS_INTERPRETATION_CMYK: + max_band[0] = 255; + max_band[1] = 255; + max_band[2] = 255; + max_band[3] = 255; + break; + case VIPS_INTERPRETATION_RGB16: max_band[0] = 65535; max_band[1] = 65535; @@ -621,7 +628,7 @@ vips_composite_base_blend( VipsCompositeBase *composite, case VIPS_BLEND_MODE_DIFFERENCE: for( int b = 0; b < bands; b++ ) - f[b] = abs( B[b] - A[b] ); + f[b] = fabs( B[b] - A[b] ); break; case VIPS_BLEND_MODE_EXCLUSION: diff --git a/libvips/conversion/conversion.c b/libvips/conversion/conversion.c index 51dcfb97..66a2f87f 100644 --- a/libvips/conversion/conversion.c +++ b/libvips/conversion/conversion.c @@ -214,6 +214,7 @@ * @VIPS_INTERESTING_ATTENTION: look for features likely to draw human attention * @VIPS_INTERESTING_LOW: position the crop towards the low coordinate * @VIPS_INTERESTING_HIGH: position the crop towards the high coordinate + * @VIPS_INTERESTING_ALL: everything is interesting * * Pick the algorithm vips uses to decide image "interestingness". This is used * by vips_smartcrop(), for example, to decide what parts of the image to diff --git a/libvips/conversion/sequential.c b/libvips/conversion/sequential.c index 9da91786..1823ba48 100644 --- a/libvips/conversion/sequential.c +++ b/libvips/conversion/sequential.c @@ -195,6 +195,11 @@ vips_sequential_build( VipsObject *object ) if( vips_linecache( sequential->in, &t, "tile_height", sequential->tile_height, "access", VIPS_ACCESS_SEQUENTIAL, + /* We need seq caches to persist across minimise in case + * someone is trying to read an image with a series of crop + * operations. + */ + "persistent", TRUE, NULL ) ) return( -1 ); diff --git a/libvips/conversion/smartcrop.c b/libvips/conversion/smartcrop.c index 2d83954c..c156fa73 100644 --- a/libvips/conversion/smartcrop.c +++ b/libvips/conversion/smartcrop.c @@ -12,6 +12,8 @@ * - move shrink to start of processing * 22/9/18 jcupitt * - add low and high + * 19/3/20 jcupitt + * - add all */ /* @@ -345,8 +347,8 @@ vips_smartcrop_build( VipsObject *object ) break; case VIPS_INTERESTING_CENTRE: - left = (smartcrop->in->Xsize - smartcrop->width) / 2; - top = (smartcrop->in->Ysize - smartcrop->height) / 2; + left = (in->Xsize - smartcrop->width) / 2; + top = (in->Ysize - smartcrop->height) / 2; break; case VIPS_INTERESTING_ENTROPY: @@ -360,8 +362,15 @@ vips_smartcrop_build( VipsObject *object ) break; case VIPS_INTERESTING_HIGH: - left = smartcrop->in->Xsize - smartcrop->width; - top = smartcrop->in->Ysize - smartcrop->height; + left = in->Xsize - smartcrop->width; + top = in->Ysize - smartcrop->height; + break; + + case VIPS_INTERESTING_ALL: + left = 0; + top = 0; + smartcrop->width = in->Xsize; + smartcrop->height = in->Ysize; break; default: @@ -448,10 +457,10 @@ vips_smartcrop_init( VipsSmartcrop *smartcrop ) * Crop an image down to a specified width and height by removing boring parts. * * Use @interesting to pick the method vips uses to decide which bits of the - * image should be kept. + * image should be kept. * * You can test xoffset / yoffset on @out to find the location of the crop - * within the input image. + * within the input image. * * See also: vips_extract_area(). * diff --git a/libvips/conversion/switch.c b/libvips/conversion/switch.c index 055b372b..0669039a 100644 --- a/libvips/conversion/switch.c +++ b/libvips/conversion/switch.c @@ -74,8 +74,8 @@ vips_switch_gen( VipsRegion *or, void *seq, void *a, void *b, if( vips_reorder_prepare_many( or->im, ar, r ) ) return( -1 ); - g_assert( ar->im->BandFmt == VIPS_FORMAT_UCHAR ); - g_assert( ar->im->Bands == 1 ); + g_assert( ar[0]->im->BandFmt == VIPS_FORMAT_UCHAR ); + g_assert( ar[0]->im->Bands == 1 ); for( i = 0; i < swit->n; i++ ) { p[i] = VIPS_REGION_ADDR( ar[i], r->left, r->top ); diff --git a/libvips/conversion/tilecache.c b/libvips/conversion/tilecache.c index 3ab20d1a..c458254f 100644 --- a/libvips/conversion/tilecache.c +++ b/libvips/conversion/tilecache.c @@ -35,8 +35,6 @@ * - terminate on tile calc error * 7/3/17 * - remove "access" on linecache, use the base class instead - * 15/2/19 - * - remove the search for LRU, have a gqueue instead, much faster */ /* @@ -127,10 +125,6 @@ typedef struct _VipsBlockCache { int tile_height; int max_tiles; - /* access doesn't actually have any effect now. It used to set - * the order for the recycle queue, but there's no efficient way to do - * top-most, so we're just always LRU. - */ VipsAccess access; gboolean threaded; gboolean persistent; @@ -254,6 +248,30 @@ vips_tile_search( VipsBlockCache *cache, int x, int y ) return( tile ); } +static void +vips_tile_find_is_topper( gpointer element, gpointer user_data ) +{ + VipsTile *this = (VipsTile *) element; + VipsTile **best = (VipsTile **) user_data; + + if( !*best || + this->pos.top < (*best)->pos.top ) + *best = this; +} + +/* Search the recycle list for the topmost tile. + */ +static VipsTile * +vips_tile_find_topmost( GQueue *recycle ) +{ + VipsTile *tile; + + tile = NULL; + g_queue_foreach( recycle, vips_tile_find_is_topper, &tile ); + + return( tile ); +} + /* Find existing tile, make a new tile, or if we have a full set of tiles, * reuse one. */ @@ -282,12 +300,18 @@ vips_tile_find( VipsBlockCache *cache, int x, int y ) return( tile ); } - /* Reuse an old one, if there are any. + /* Reuse an old one, if there are any. We just peek the tile pointer, + * it is removed from the recycle list later on _ref. */ - if( cache->recycle ) - /* Gets removed from the recycle list on _ref. - */ - tile = g_queue_peek_head( cache->recycle ); + if( cache->recycle ) { + if( cache->access == VIPS_ACCESS_RANDOM ) + tile = g_queue_peek_head( cache->recycle ); + else + /* This is slower :( We have to search the recycle + * queue. + */ + tile = vips_tile_find_topmost( cache->recycle ); + } if( !tile ) { /* There are no tiles we can reuse -- we have to make another @@ -913,8 +937,7 @@ vips_line_cache_build( VipsObject *object ) block_cache->tile_width = block_cache->in->Xsize; /* Output has two buffers n_lines height, so 2 * n_lines is the maximum - * non-locality from threading. Add another n_lines for conv / reducev - * etc. + * non-locality from threading. Double again for conv, rounding, etc. * * tile_height can be huge for things like tiff read, where we can * have a whole strip in a single tile ... we still need to have a @@ -922,7 +945,7 @@ vips_line_cache_build( VipsObject *object ) * tile boundary. */ block_cache->max_tiles = VIPS_MAX( 2, - 3 * n_lines / block_cache->tile_height ); + 4 * n_lines / block_cache->tile_height ); VIPS_DEBUG_MSG( "vips_line_cache_build: n_lines = %d\n", n_lines ); diff --git a/libvips/conversion/unpremultiply.c b/libvips/conversion/unpremultiply.c index c9f9d65f..219fac28 100644 --- a/libvips/conversion/unpremultiply.c +++ b/libvips/conversion/unpremultiply.c @@ -62,6 +62,7 @@ typedef struct _VipsUnpremultiply { VipsImage *in; double max_alpha; + int alpha_band; } VipsUnpremultiply; @@ -76,17 +77,21 @@ G_DEFINE_TYPE( VipsUnpremultiply, vips_unpremultiply, VIPS_TYPE_CONVERSION ); OUT * restrict q = (OUT *) out; \ \ for( x = 0; x < width; x++ ) { \ - IN alpha = p[bands - 1]; \ - IN clip_alpha = VIPS_CLIP( 0, alpha, max_alpha ); \ - OUT nalpha = (OUT) clip_alpha / max_alpha; \ + IN alpha = p[alpha_band]; \ \ - if( nalpha == 0 ) \ - for( i = 0; i < bands - 1; i++ ) \ - q[i] = 0; \ + if( alpha != 0 ) { \ + OUT factor = max_alpha / alpha; \ + \ + for( i = 0; i < alpha_band; i++ ) \ + q[i] = factor * p[i]; \ + q[alpha_band] = alpha; \ + } \ else \ - for( i = 0; i < bands - 1; i++ ) \ - q[i] = p[i] / nalpha; \ - q[i] = clip_alpha; \ + for( i = 0; i < alpha_band + 1; i++ ) \ + q[i] = 0; \ + \ + for( i = alpha_band + 1; i < bands; i++ ) \ + q[i] = p[i]; \ \ p += bands; \ q += bands; \ @@ -101,20 +106,21 @@ G_DEFINE_TYPE( VipsUnpremultiply, vips_unpremultiply, VIPS_TYPE_CONVERSION ); \ for( x = 0; x < width; x++ ) { \ IN alpha = p[3]; \ - IN clip_alpha = VIPS_CLIP( 0, alpha, max_alpha ); \ - OUT nalpha = (OUT) clip_alpha / max_alpha; \ \ - if( nalpha == 0 ) { \ + if( alpha != 0 ) { \ + OUT factor = max_alpha / alpha; \ + \ + q[0] = factor * p[0]; \ + q[1] = factor * p[1]; \ + q[2] = factor * p[2]; \ + q[3] = alpha; \ + } \ + else { \ q[0] = 0; \ q[1] = 0; \ q[2] = 0; \ + q[3] = 0; \ } \ - else { \ - q[0] = p[0] / nalpha; \ - q[1] = p[1] / nalpha; \ - q[2] = p[2] / nalpha; \ - } \ - q[3] = clip_alpha; \ \ p += 4; \ q += 4; \ @@ -141,6 +147,7 @@ vips_unpremultiply_gen( VipsRegion *or, void *vseq, void *a, void *b, int width = r->width; int bands = im->Bands; double max_alpha = unpremultiply->max_alpha; + int alpha_band = unpremultiply->alpha_band; int x, y, i; @@ -235,6 +242,11 @@ vips_unpremultiply_build( VipsObject *object ) in->Type == VIPS_INTERPRETATION_RGB16 ) unpremultiply->max_alpha = 65535; + /* Is alpha-band unset? Default to the final band for this image. + */ + if( !vips_object_argument_isset( object, "alpha_band" ) ) + unpremultiply->alpha_band = in->Bands - 1; + if( in->BandFmt == VIPS_FORMAT_DOUBLE ) conversion->out->BandFmt = VIPS_FORMAT_DOUBLE; else @@ -279,6 +291,13 @@ vips_unpremultiply_class_init( VipsUnpremultiplyClass *class ) G_STRUCT_OFFSET( VipsUnpremultiply, max_alpha ), 0, 100000000, 255 ); + VIPS_ARG_INT( class, "alpha_band", 116, + _( "Alpha band" ), + _( "Unpremultiply with this alpha" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsUnpremultiply, alpha_band ), + 0, 100000000, 3 ); + } static void @@ -296,10 +315,11 @@ vips_unpremultiply_init( VipsUnpremultiply *unpremultiply ) * Optional arguments: * * * @max_alpha: %gdouble, maximum value for alpha + * * @alpha_band: %gint, band containing alpha data * * Unpremultiplies any alpha channel. - * The final band is taken to be the alpha - * and the bands are transformed as: + * Band @alpha_band (by default the final band) contains the alpha and all + * other bands are transformed as: * * |[ * alpha = (int) clip( 0, in[in.bands - 1], @max_alpha ); @@ -312,8 +332,7 @@ vips_unpremultiply_init( VipsUnpremultiply *unpremultiply ) * * So for an N-band image, the first N - 1 bands are divided by the clipped * and normalised final band, the final band is clipped. - * If there is only a single band, - * the image is passed through unaltered. + * If there is only a single band, the image is passed through unaltered. * * The result is * #VIPS_FORMAT_FLOAT unless the input format is #VIPS_FORMAT_DOUBLE, in which diff --git a/libvips/convolution/conv.c b/libvips/convolution/conv.c index 067ae9f3..6e47b7ea 100644 --- a/libvips/convolution/conv.c +++ b/libvips/convolution/conv.c @@ -34,11 +34,6 @@ */ -/* This is a simple wrapper over the old vips7 functions. At some point we - * should rewrite this as a pure vips8 class and redo the vips7 functions as - * wrappers over this. - */ - #ifdef HAVE_CONFIG_H #include #endif /*HAVE_CONFIG_H*/ diff --git a/libvips/convolution/convi.c b/libvips/convolution/convi.c index 20aff567..e92b8b9a 100644 --- a/libvips/convolution/convi.c +++ b/libvips/convolution/convi.c @@ -147,9 +147,6 @@ typedef struct { typedef struct { VipsConvolution parent_instance; - /* An int version of M. - */ - VipsImage *iM; int n_point; /* w * h for our matrix */ /* We make a smaller version of the mask with the zeros squeezed out. @@ -853,7 +850,6 @@ vips__image_intize( VipsImage *in, VipsImage **out ) static int vips_convi_intize( VipsConvi *convi, VipsImage *M ) { - int n_point; VipsImage *t; double scale; double *scaled; @@ -862,21 +858,17 @@ vips_convi_intize( VipsConvi *convi, VipsImage *M ) int shift; int i; - n_point = M->Xsize * M->Ysize; - - g_assert( convi->n_point == n_point ); - if( vips_check_matrix( "vips2imask", M, &t ) ) return( -1 ); /* Bake the scale into the mask to make a double version. */ scale = vips_image_get_scale( t ); - if( !(scaled = VIPS_ARRAY( convi, n_point, double )) ) { + if( !(scaled = VIPS_ARRAY( convi, convi->n_point, double )) ) { g_object_unref( t ); return( -1 ); } - for( i = 0; i < n_point; i++ ) + for( i = 0; i < convi->n_point; i++ ) scaled[i] = VIPS_MATRIX( t, 0, 0 )[i] / scale; g_object_unref( t ); @@ -896,7 +888,7 @@ vips_convi_intize( VipsConvi *convi, VipsImage *M ) mx = scaled[0]; mn = scaled[0]; - for( i = 1; i < n_point; i++ ) { + for( i = 1; i < convi->n_point; i++ ) { if( scaled[i] > mx ) mx = scaled[i]; if( scaled[i] < mn ) @@ -915,7 +907,7 @@ vips_convi_intize( VipsConvi *convi, VipsImage *M ) /* We need to sum n_points, so we have to shift right before adding a * new value to make sure we have enough range. */ - convi->sexp = ceil( log2( n_point ) ); + convi->sexp = ceil( log2( convi->n_point ) ); if( convi->sexp > 10 ) { g_info( "vips_convi_intize: mask too large" ); return( -1 ); @@ -925,9 +917,9 @@ vips_convi_intize( VipsConvi *convi, VipsImage *M ) */ convi->exp = 7 - shift - convi->sexp; - if( !(convi->mant = VIPS_ARRAY( convi, n_point, int )) ) + if( !(convi->mant = VIPS_ARRAY( convi, convi->n_point, int )) ) return( -1 ); - for( i = 0; i < n_point; i++ ) { + for( i = 0; i < convi->n_point; i++ ) { /* 128 since this is signed. */ convi->mant[i] = VIPS_RINT( 128 * scaled[i] * pow(2, -shift) ); @@ -965,7 +957,7 @@ vips_convi_intize( VipsConvi *convi, VipsImage *M ) true_sum = 0.0; int_sum = 0; - for( i = 0; i < n_point; i++ ) { + for( i = 0; i < convi->n_point; i++ ) { int value; true_sum += 128 * scaled[i]; @@ -1001,7 +993,6 @@ vips_convi_build( VipsObject *object ) VipsImage *in; VipsImage *M; - int n_point; VipsGenerateFn generate; double *coeff; int i; @@ -1047,19 +1038,18 @@ vips_convi_build( VipsObject *object ) */ if( vips__image_intize( M, &t[1] ) ) return( -1 ); - convi->iM = M = t[1]; + M = t[1]; coeff = VIPS_MATRIX( M, 0, 0 ); - n_point = M->Xsize * M->Ysize; - if( !(convi->coeff = VIPS_ARRAY( object, n_point, int )) || + if( !(convi->coeff = VIPS_ARRAY( object, convi->n_point, int )) || !(convi->coeff_pos = - VIPS_ARRAY( object, n_point, int )) ) + VIPS_ARRAY( object, convi->n_point, int )) ) return( -1 ); /* Squeeze out zero mask elements. */ convi->nnz = 0; - for( i = 0; i < n_point; i++ ) + for( i = 0; i < convi->n_point; i++ ) if( coeff[i] ) { convi->coeff[convi->nnz] = coeff[i]; convi->coeff_pos[convi->nnz] = i; diff --git a/libvips/convolution/convolution.c b/libvips/convolution/convolution.c index bb195984..3d762e81 100644 --- a/libvips/convolution/convolution.c +++ b/libvips/convolution/convolution.c @@ -157,19 +157,19 @@ vips_convolution_init( VipsConvolution *convolution ) void vips_convolution_operation_init( void ) { - extern int vips_conv_get_type( void ); - extern int vips_conva_get_type( void ); - extern int vips_convf_get_type( void ); - extern int vips_convi_get_type( void ); - extern int vips_convsep_get_type( void ); - extern int vips_convasep_get_type( void ); - extern int vips_compass_get_type( void ); - extern int vips_fastcor_get_type( void ); - extern int vips_spcor_get_type( void ); - extern int vips_sharpen_get_type( void ); - extern int vips_gaussblur_get_type( void ); - extern int vips_sobel_get_type( void ); - extern int vips_canny_get_type( void ); + extern GType vips_conv_get_type( void ); + extern GType vips_conva_get_type( void ); + extern GType vips_convf_get_type( void ); + extern GType vips_convi_get_type( void ); + extern GType vips_convsep_get_type( void ); + extern GType vips_convasep_get_type( void ); + extern GType vips_compass_get_type( void ); + extern GType vips_fastcor_get_type( void ); + extern GType vips_spcor_get_type( void ); + extern GType vips_sharpen_get_type( void ); + extern GType vips_gaussblur_get_type( void ); + extern GType vips_sobel_get_type( void ); + extern GType vips_canny_get_type( void ); vips_conv_get_type(); vips_conva_get_type(); diff --git a/libvips/convolution/convsep.c b/libvips/convolution/convsep.c index cb1cc178..9e85d7ba 100644 --- a/libvips/convolution/convsep.c +++ b/libvips/convolution/convsep.c @@ -64,7 +64,7 @@ vips_convsep_build( VipsObject *object ) VipsConvolution *convolution = (VipsConvolution *) object; VipsConvsep *convsep = (VipsConvsep *) object; VipsImage **t = (VipsImage **) - vips_object_local_array( object, 3 ); + vips_object_local_array( object, 4 ); VipsImage *in; @@ -86,19 +86,19 @@ vips_convsep_build( VipsObject *object ) in = t[0]; } else { - if( vips_rot( convolution->M, &t[0], VIPS_ANGLE_D90, NULL ) ) - return( -1 ); - - /* We must only add the offset once. + /* Take a copy, since we must set the offset. */ - vips_image_set_double( t[0], "offset", 0 ); + if( vips_rot( convolution->M, &t[0], VIPS_ANGLE_D90, NULL ) || + vips_copy( t[0], &t[3], NULL ) ) + return( -1 ); + vips_image_set_double( t[3], "offset", 0 ); if( vips_conv( in, &t[1], convolution->M, "precision", convsep->precision, "layers", convsep->layers, "cluster", convsep->cluster, NULL ) || - vips_conv( t[1], &t[2], t[0], + vips_conv( t[1], &t[2], t[3], "precision", convsep->precision, "layers", convsep->layers, "cluster", convsep->cluster, diff --git a/libvips/create/mask_butterworth.c b/libvips/create/mask_butterworth.c index 4e9d9cac..c5f4a4a6 100644 --- a/libvips/create/mask_butterworth.c +++ b/libvips/create/mask_butterworth.c @@ -64,9 +64,12 @@ vips_mask_butterworth_point( VipsMask *mask, double dx, double dy ) double cnst = (1.0 / ac) - 1.0; double fc2 = fc * fc; - double dist2 = fc2 / (dx * dx + dy * dy); + double d = dx * dx + dy * dy; - return( 1.0 / (1.0 + cnst * pow( dist2, order )) ); + if( d == 0 ) + return( 0 ); + else + return( 1.0 / (1.0 + cnst * pow( fc2 / d, order )) ); } static void diff --git a/libvips/create/text.c b/libvips/create/text.c index a2d22a30..a750c8ec 100644 --- a/libvips/create/text.c +++ b/libvips/create/text.c @@ -30,6 +30,8 @@ * - set Xoffset/Yoffset to ink left/top * 27/6/19 * - fitting could occasionally terminate early [levmorozov] + * 16/5/20 [keiviv] + * - don't add fontfiles repeatedly */ /* @@ -103,6 +105,10 @@ typedef VipsCreateClass VipsTextClass; G_DEFINE_TYPE( VipsText, vips_text, VIPS_TYPE_CREATE ); +/* ... single-thread the body of vips_text() with this. + */ +static GMutex *vips_text_lock = NULL; + /* Just have one of these and reuse it. * * This does not unref cleanly on many platforms, so we will leak horribly @@ -111,9 +117,10 @@ G_DEFINE_TYPE( VipsText, vips_text, VIPS_TYPE_CREATE ); */ static PangoFontMap *vips_text_fontmap = NULL; -/* ... single-thread the body of vips_text() with this. +/* All the fontfiles we've loaded. fontconfig lets you add a fontfile + * repeatedly, and we obviously don't want that. */ -static GMutex *vips_text_lock = NULL; +static GHashTable *vips_text_fontfiles = NULL; static void vips_text_dispose( GObject *gobject ) @@ -182,6 +189,7 @@ vips_text_get_extents( VipsText *text, VipsRect *extents ) pango_ft2_font_map_set_resolution( PANGO_FT2_FONT_MAP( vips_text_fontmap ), text->dpi, text->dpi ); + VIPS_UNREF( text->layout ); if( !(text->layout = text_layout_new( text->context, text->text, text->font, text->width, text->spacing, text->align, text->justify )) ) @@ -239,6 +247,7 @@ vips_text_autofit( VipsText *text ) target.width = text->width; target.height = text->height; previous_dpi = -1; + previous_difference = 0; #ifdef DEBUG printf( "vips_text_autofit: " @@ -348,17 +357,26 @@ vips_text_build( VipsObject *object ) if( !vips_text_fontmap ) vips_text_fontmap = pango_ft2_font_map_new(); + if( !vips_text_fontfiles ) + vips_text_fontfiles = + g_hash_table_new( g_str_hash, g_str_equal ); text->context = pango_font_map_create_context( PANGO_FONT_MAP( vips_text_fontmap ) ); if( text->fontfile && - !FcConfigAppFontAddFile( NULL, + !g_hash_table_lookup( vips_text_fontfiles, text->fontfile ) ) { + if( !FcConfigAppFontAddFile( NULL, (const FcChar8 *) text->fontfile ) ) { - vips_error( class->nickname, - _( "unable to load font \"%s\"" ), text->fontfile ); - g_mutex_unlock( vips_text_lock ); - return( -1 ); + vips_error( class->nickname, + _( "unable to load font \"%s\"" ), + text->fontfile ); + g_mutex_unlock( vips_text_lock ); + return( -1 ); + } + g_hash_table_insert( vips_text_fontfiles, + text->fontfile, + g_strdup( text->fontfile ) ); } /* If our caller set height and not dpi, we adjust dpi until @@ -541,7 +559,7 @@ vips_text_init( VipsText *text ) * * @fontfile: %gchararray, load this font file * * @width: %gint, image should be no wider than this many pixels * * @height: %gint, image should be no higher than this many pixels - * * @align: #VipsAlign, left/centre/right alignment + * * @align: #VipsAlign, set justification alignment * * @justify: %gboolean, justify lines * * @dpi: %gint, render at this resolution * * @autofit_dpi: %gint, read out auto-fitted DPI @@ -562,11 +580,12 @@ vips_text_init( VipsText *text ) * * @width is the number of pixels to word-wrap at. Lines of text wider than * this will be broken at word boundaries. - * @align can be used to set the alignment style for multi-line - * text. Note that the output image can be wider than @width if there are no - * word breaks, or narrower if the lines don't break exactly at @width. * * Set @justify to turn on line justification. + * @align can be used to set the alignment style for multi-line + * text to the low (left) edge centre, or high (right) edge. Note that the + * output image can be wider than @width if there are no + * word breaks, or narrower if the lines don't break exactly at @width. * * @height is the maximum number of pixels high the generated text can be. This * only takes effect when @dpi is not set, and @width is set, making a box. diff --git a/libvips/deprecated/format.c b/libvips/deprecated/format.c index 03ea8fd1..fd94a2f0 100644 --- a/libvips/deprecated/format.c +++ b/libvips/deprecated/format.c @@ -176,7 +176,7 @@ im_isvips( const char *filename ) { unsigned char buf[4]; - if( im__get_bytes( filename, buf, 4 ) ) { + if( im__get_bytes( filename, buf, 4 ) == 4 ) { if( buf[0] == 0x08 && buf[1] == 0xf2 && buf[2] == 0xa6 && buf[3] == 0xb6 ) /* SPARC-order VIPS image. @@ -212,7 +212,7 @@ vips_flags( const char *filename ) flags = VIPS_FORMAT_PARTIAL; - if( im__get_bytes( filename, buf, 4 ) && + if( im__get_bytes( filename, buf, 4 ) == 4 && buf[0] == 0x08 && buf[1] == 0xf2 && buf[2] == 0xa6 && diff --git a/libvips/deprecated/im_csv2vips.c b/libvips/deprecated/im_csv2vips.c index e4e5a860..38d0f928 100644 --- a/libvips/deprecated/im_csv2vips.c +++ b/libvips/deprecated/im_csv2vips.c @@ -61,6 +61,8 @@ im_csv2vips( const char *filename, IMAGE *out ) char mode[FILENAME_MAX]; char *p, *q, *r; + VipsImage *x; + /* Parse mode string. */ im_filename_split( filename, name, mode ); @@ -76,9 +78,18 @@ im_csv2vips( const char *filename, IMAGE *out ) lines = atoi( r ); } - if( vips__csv_read( name, out, - start_skip, lines, whitespace, separator, FALSE ) ) + if( vips_csvload( name, &x, + "skip", start_skip, + "lines", lines, + "whitespace", whitespace, + "separator", separator, + NULL ) ) return( -1 ); + if( vips_image_write( x, out ) ) { + g_object_unref( x ); + return( -1 ); + } + g_object_unref( x ); return( 0 ); } diff --git a/libvips/deprecated/im_jpeg2vips.c b/libvips/deprecated/im_jpeg2vips.c index 8b00fd79..98f5fcb4 100644 --- a/libvips/deprecated/im_jpeg2vips.c +++ b/libvips/deprecated/im_jpeg2vips.c @@ -111,9 +111,18 @@ jpeg2vips( const char *name, IMAGE *out, gboolean header_only ) } #ifdef HAVE_JPEG - if( vips__jpeg_read_file( filename, out, - header_only, shrink, fail_on_warn, FALSE ) ) +{ + VipsSource *source; + + if( !(source = vips_source_new_from_file( filename )) ) return( -1 ); + if( vips__jpeg_read_source( source, out, + header_only, shrink, fail_on_warn, FALSE ) ) { + VIPS_UNREF( source ); + return( -1 ); + } + VIPS_UNREF( source ); +} #else vips_error( "im_jpeg2vips", "%s", _( "no JPEG support in your libvips" ) ); diff --git a/libvips/deprecated/im_png2vips.c b/libvips/deprecated/im_png2vips.c index 6f7d87b8..fab7f3dc 100644 --- a/libvips/deprecated/im_png2vips.c +++ b/libvips/deprecated/im_png2vips.c @@ -83,14 +83,21 @@ png2vips( const char *name, IMAGE *out, gboolean header_only ) } #ifdef HAVE_PNG - if( header_only ) { - if( vips__png_header( filename, out ) ) - return( -1 ); - } - else { - if( vips__png_read( filename, out, TRUE ) ) - return( -1 ); - } +{ + VipsSource *source; + int result; + + if( !(source = vips_source_new_from_file( filename )) ) + return( -1 ); + if( header_only ) + result = vips__png_header_source( source, out ); + else + result = vips__png_read_source( source, out, TRUE ); + VIPS_UNREF( source ); + + if( result ) + return( result ); +} #else vips_error( "im_png2vips", "%s", _( "no PNG support in your libvips" ) ); diff --git a/libvips/deprecated/im_tiff2vips.c b/libvips/deprecated/im_tiff2vips.c index b082a443..785faf8d 100644 --- a/libvips/deprecated/im_tiff2vips.c +++ b/libvips/deprecated/im_tiff2vips.c @@ -52,6 +52,57 @@ #include "../foreign/pforeign.h" +#ifdef HAVE_TIFF +static gboolean +im_istifftiled( const char *filename ) +{ + VipsSource *source; + gboolean result; + + if( !(source = vips_source_new_from_file( filename )) ) + return( FALSE ); + result = vips__istiff_source( source ); + VIPS_UNREF( source ); + + return( result ); +} + +static int +im_tiff_read_header( const char *filename, VipsImage *out, + int page, int n, gboolean autorotate ) +{ + VipsSource *source; + + if( !(source = vips_source_new_from_file( filename )) ) + return( -1 ); + if( vips__tiff_read_header_source( source, + out, page, n, autorotate, -1 ) ) { + VIPS_UNREF( source ); + return( -1 ); + } + VIPS_UNREF( source ); + + return( 0 ); +} + +static int +im_tiff_read( const char *filename, VipsImage *out, + int page, int n, gboolean autorotate ) +{ + VipsSource *source; + + if( !(source = vips_source_new_from_file( filename )) ) + return( -1 ); + if( vips__tiff_read_source( source, out, page, n, autorotate, -1 ) ) { + VIPS_UNREF( source ); + return( -1 ); + } + VIPS_UNREF( source ); + + return( 0 ); +} +#endif /*HAVE_TIFF*/ + static int tiff2vips( const char *name, IMAGE *out, gboolean header_only ) { @@ -88,18 +139,18 @@ tiff2vips( const char *name, IMAGE *out, gboolean header_only ) if( !header_only && !seq && - !vips__istifftiled( filename ) && + !im_istifftiled( filename ) && out->dtype == VIPS_IMAGE_PARTIAL ) { if( vips__image_wio_output( out ) ) return( -1 ); } if( header_only ) { - if( vips__tiff_read_header( filename, out, page, 1, FALSE ) ) + if( im_tiff_read_header( filename, out, page, 1, FALSE ) ) return( -1 ); } else { - if( vips__tiff_read( filename, out, page, 1, FALSE ) ) + if( im_tiff_read( filename, out, page, 1, FALSE ) ) return( -1 ); } #else diff --git a/libvips/deprecated/im_webp2vips.c b/libvips/deprecated/im_webp2vips.c index 9ad166ed..f7a235dd 100644 --- a/libvips/deprecated/im_webp2vips.c +++ b/libvips/deprecated/im_webp2vips.c @@ -51,14 +51,21 @@ webp2vips( const char *name, IMAGE *out, gboolean header_only ) im_filename_split( name, filename, mode ); #ifdef HAVE_LIBWEBP - if( header_only ) { - if( vips__webp_read_file_header( filename, out, 0, 1, 1 ) ) - return( -1 ); - } - else { - if( vips__webp_read_file( filename, out, 0, 1, 1 ) ) - return( -1 ); - } +{ + VipsSource *source; + int result; + + if( !(source = vips_source_new_from_file( filename )) ) + return( -1 ); + if( header_only ) + result = vips__webp_read_header_source( source, out, 0, 1, 1 ); + else + result = vips__webp_read_source( source, out, 0, 1, 1 ); + VIPS_UNREF( source ); + + if( result ) + return( result ); +} #else vips_error( "im_webp2vips", "%s", _( "no webp support in your libvips" ) ); @@ -69,6 +76,25 @@ webp2vips( const char *name, IMAGE *out, gboolean header_only ) return( 0 ); } +static gboolean +vips__iswebp( const char *filename ) +{ + gboolean result; + +#ifdef HAVE_LIBWEBP + VipsSource *source; + + if( !(source = vips_source_new_from_file( filename )) ) + return( FALSE ); + result = vips__iswebp_source( source ); + VIPS_UNREF( source ); +#else /*!HAVE_LIBWEBP*/ + result = -1; +#endif /*HAVE_LIBWEBP*/ + + return( result ); +} + int im_webp2vips( const char *name, IMAGE *out ) { diff --git a/libvips/deprecated/mosaicing_dispatch.c b/libvips/deprecated/mosaicing_dispatch.c index e4749585..ab70f938 100644 --- a/libvips/deprecated/mosaicing_dispatch.c +++ b/libvips/deprecated/mosaicing_dispatch.c @@ -123,7 +123,7 @@ lrmosaic_vec( im_object *argv ) int balancetype = *((int *) argv[10]); int mwidth = *((int *) argv[11]); - return( im_lrmosaic( argv[0], argv[1], argv[2], + return( vips_lrmosaic( argv[0], argv[1], argv[2], bandno, xr, yr, xs, ys, halfcorrelation, halfarea, @@ -210,7 +210,7 @@ find_lroverlap_vec( im_object *argv ) if( !(t = im_open( "find_lroverlap_vec", "p" )) ) return( -1 ); - result = im__find_lroverlap( argv[0], argv[1], t, + result = vips__find_lroverlap( argv[0], argv[1], t, bandno, xr, yr, xs, ys, halfcorrelation, halfarea, @@ -257,7 +257,7 @@ tbmosaic_vec( im_object *argv ) int balancetype = *((int *) argv[10]); int mwidth = *((int *) argv[11]); - return( im_tbmosaic( argv[0], argv[1], argv[2], + return( vips_tbmosaic( argv[0], argv[1], argv[2], bandno, x1, y1, x2, y2, halfcorrelation, halfarea, @@ -315,7 +315,7 @@ find_tboverlap_vec( im_object *argv ) if( !(t = im_open( "find_tboverlap_vec", "p" )) ) return( -1 ); - result = im__find_tboverlap( argv[0], argv[1], t, + result = vips__find_tboverlap( argv[0], argv[1], t, bandno, xr, yr, xs, ys, halfcorrelation, halfarea, @@ -367,7 +367,7 @@ lrmerge_vec( im_object *argv ) int dy = *((int *) argv[4]); int mwidth = *((int *) argv[5]); - return( im_lrmerge( argv[0], argv[1], argv[2], dx, dy, mwidth ) ); + return( vips_lrmerge( argv[0], argv[1], argv[2], dx, dy, mwidth ) ); } /* Call im_lrmerge1 via arg vector. @@ -421,7 +421,7 @@ tbmerge_vec( im_object *argv ) int dy = *((int *) argv[4]); int mwidth = *((int *) argv[5]); - return( im_tbmerge( argv[0], argv[1], argv[2], dx, dy, mwidth ) ); + return( vips_tbmerge( argv[0], argv[1], argv[2], dx, dy, mwidth ) ); } /* Call im_tbmerge1 via arg vector. @@ -594,7 +594,7 @@ correl_vec( im_object *argv ) int *y = (int *) argv[9]; double *correlation = (double *) argv[10]; - return( im_correl( argv[0], argv[1], + return( vips_correl( argv[0], argv[1], xref, yref, xsec, ysec, cor, area, correlation, x, y ) ); } diff --git a/libvips/deprecated/rename.c b/libvips/deprecated/rename.c index 28146b94..ad3eaa22 100644 --- a/libvips/deprecated/rename.c +++ b/libvips/deprecated/rename.c @@ -801,3 +801,15 @@ vips_warn( const char *domain, const char *fmt, ... ) va_end( ap ); } +/** + * vips_autorot_get_angle: + * @image: image to fetch orientation from + * + * This function is deprecated. Use vips_autorot() instead. + */ +VipsAngle +vips_autorot_get_angle( VipsImage *im ) +{ + return( VIPS_ANGLE_D0 ); +} + diff --git a/libvips/deprecated/vips7compat.c b/libvips/deprecated/vips7compat.c index 308220b7..dd01f70c 100644 --- a/libvips/deprecated/vips7compat.c +++ b/libvips/deprecated/vips7compat.c @@ -4370,6 +4370,68 @@ im_profile( IMAGE *in, IMAGE *out, int dir ) return( 0 ); } +int +im_erode( IMAGE *in, IMAGE *out, INTMASK *mask ) +{ + VipsImage *t1, *t2; + + if( !(t1 = vips_image_new()) || + im_imask2vips( mask, t1 ) ) + return( -1 ); + + if( vips_morph( in, &t2, t1, VIPS_OPERATION_MORPHOLOGY_ERODE, + NULL ) ) { + g_object_unref( t1 ); + return( -1 ); + } + g_object_unref( t1 ); + + if( vips_image_write( t2, out ) ) { + g_object_unref( t2 ); + return( -1 ); + } + g_object_unref( t2 ); + + return( 0 ); +} + +int +im_erode_raw( IMAGE *in, IMAGE *out, INTMASK *m ) +{ + return( im_erode( in, out, m ) ); +} + +int +im_dilate( IMAGE *in, IMAGE *out, INTMASK *mask ) +{ + VipsImage *t1, *t2; + + if( !(t1 = vips_image_new()) || + im_imask2vips( mask, t1 ) ) + return( -1 ); + + if( vips_morph( in, &t2, t1, VIPS_OPERATION_MORPHOLOGY_DILATE, + NULL ) ) { + g_object_unref( t1 ); + return( -1 ); + } + g_object_unref( t1 ); + + if( vips_image_write( t2, out ) ) { + g_object_unref( t2 ); + return( -1 ); + } + g_object_unref( t2 ); + + return( 0 ); +} + +int +im_dilate_raw( IMAGE *in, IMAGE *out, INTMASK *m ) +{ + return( im_dilate( in, out, m ) ); +} + int im_mpercent( IMAGE *in, double percent, int *out ) { diff --git a/libvips/draw/draw.c b/libvips/draw/draw.c index d8f423da..bfa6c96b 100644 --- a/libvips/draw/draw.c +++ b/libvips/draw/draw.c @@ -69,7 +69,7 @@ * to be a slow way to do it. This is where the draw operations come in. * * To use these operations, use vips_copy() to make a copy of the image you - * want to modify, to ensure that no one else is using it, then call a + * want to modify to ensure that no one else is using it, then call a * series of draw operations. * Once you are done drawing, return to normal use of vips operations. Any time * you want to start drawing again, you'll need to copy again. diff --git a/libvips/draw/draw_flood.c b/libvips/draw/draw_flood.c index 2d2367a1..37b8b0e6 100644 --- a/libvips/draw/draw_flood.c +++ b/libvips/draw/draw_flood.c @@ -30,6 +30,8 @@ * - use Draw base class * 21/1/14 * - redo as a class + * 5/4/20 + * - could fail to complete for some very complex shapes */ /* @@ -184,7 +186,7 @@ buffer_free( Buffer *buf ) /* Add a scanline to a buffer, prepending a new buffer if necessary. Return * the new head buffer. */ -static Buffer * +static Buffer * buffer_add( Buffer *buf, Flood *flood, int x1, int x2, int y, int dir ) { /* Clip against image size. @@ -197,19 +199,19 @@ buffer_add( Buffer *buf, Flood *flood, int x1, int x2, int y, int dir ) if( x2 - x1 < 0 ) return( buf ); - buf->scan[buf->n].x1 = x1; - buf->scan[buf->n].x2 = x2; - buf->scan[buf->n].y = y; - buf->scan[buf->n].dir = dir; - buf->n += 1; - if( buf->n == PBUFSIZE ) { Buffer *new; new = buffer_build(); new->next = buf; buf = new; - } + } + + buf->scan[buf->n].x1 = x1; + buf->scan[buf->n].x2 = x2; + buf->scan[buf->n].y = y; + buf->scan[buf->n].dir = dir; + buf->n += 1; return( buf ); } @@ -297,7 +299,6 @@ flood_scanline( Flood *flood, int x, int y, int *x1, int *x2 ) { const int width = flood->test->Xsize; - VipsPel *p; int i; g_assert( flood_connected( flood, @@ -309,23 +310,33 @@ flood_scanline( Flood *flood, int x, int y, int *x1, int *x2 ) * pixel is unpainted, we know all the intervening pixels must be * unpainted too. */ - p = VIPS_IMAGE_ADDR( flood->test, x + 1, y ); - for( i = x + 1; i < width; i++ ) { - if( !flood_connected( flood, p ) ) - break; - p += flood->tsize; + if( x < width ) { + VipsPel *p = VIPS_IMAGE_ADDR( flood->test, x + 1, y ); + + for( i = x + 1; i < width; i++ ) { + if( !flood_connected( flood, p ) ) + break; + p += flood->tsize; + } + *x2 = i - 1; } - *x2 = i - 1; + else + *x2 = width; /* Search left. */ - p = VIPS_IMAGE_ADDR( flood->test, x - 1, y ); - for( i = x - 1; i >= 0; i-- ) { - if( !flood_connected( flood, p ) ) - break; - p -= flood->tsize; + if( x > 0 ) { + VipsPel *p = VIPS_IMAGE_ADDR( flood->test, x - 1, y ); + + for( i = x - 1; i >= 0; i-- ) { + if( !flood_connected( flood, p ) ) + break; + p -= flood->tsize; + } + *x1 = i + 1; } - *x1 = i + 1; + else + *x1 = 0; /* Paint the range we discovered. */ diff --git a/libvips/foreign/Makefile.am b/libvips/foreign/Makefile.am index fb4c353a..0fef2c4a 100644 --- a/libvips/foreign/Makefile.am +++ b/libvips/foreign/Makefile.am @@ -17,10 +17,8 @@ libforeign_la_SOURCES = \ radiance.c \ radload.c \ radsave.c \ - ppm.c \ ppmload.c \ ppmsave.c \ - csv.c \ csvload.c \ csvsave.c \ matrixload.c \ @@ -42,6 +40,7 @@ libforeign_la_SOURCES = \ magickload.c \ magick7load.c \ magicksave.c \ + spngload.c \ pngload.c \ pngsave.c \ vipspng.c \ diff --git a/libvips/foreign/csv.c b/libvips/foreign/csv.c deleted file mode 100644 index 70aae08c..00000000 --- a/libvips/foreign/csv.c +++ /dev/null @@ -1,777 +0,0 @@ -/* Read/write csv files. - * - * 19/12/05 JC - * - hacked from ppm reader - * 9/6/06 - * - hacked from im_debugim - * 11/9/06 - * - now distingushes whitespace and separators, so we can have blank - * fields - * 20/9/06 - * - oop, unquoted trailing columns could get missed - * 23/10/06 - * - allow separator to be specified (default "\t", ) - * 17/11/06 - * - oops, was broken - * 17/5/07 - * - added im_csv2vips_header() - * 4/2/10 - * - gtkdoc - * 1/3/10 - * - allow lines that end with EOF - * 23/9/11 - * - allow quoted strings, including escaped quotes - * 16/12/11 - * - rework as a set of fns ready for wrapping as a class - * 23/2/12 - * - report positions for EOF/EOL errors - * 2/7/13 - * - add matrix read/write - * 4/6/15 - * - try to support DOS files under linux ... we have to look for \r\n - * linebreaks - * 12/8/16 - * - allow missing offset and scale in matrix header - */ - -/* - - 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 - - */ - -#ifdef HAVE_CONFIG_H -#include -#endif /*HAVE_CONFIG_H*/ -#include - -#include -#include -#include -#include -#include -#include - -#include - -#include "pforeign.h" - -/* Skip to the start of the next line (ie. read until we see a '\n'), return - * zero if we are at EOF. - * - * Files can end with EOF or with \nEOF. Tricky! - */ -static int -skip_line( FILE *fp ) -{ - int ch; - - /* Are we at a delayed EOF? See below. - */ - if( (ch = vips__fgetc( fp )) == EOF ) - return( 0 ); - ungetc( ch, fp ); - - /* If we hit EOF and no \n, wait until the next call to report EOF. - */ - while( (ch = vips__fgetc( fp )) != '\n' && - ch != EOF ) - ; - - return( -1 ); -} - -static int -skip_white( FILE *fp, const char whitemap[256] ) -{ - int ch; - - do { - ch = vips__fgetc( fp ); - } while( ch != EOF && - ch != '\n' && - whitemap[ch] ); - - ungetc( ch, fp ); - - return( ch ); -} - -static int -skip_to_quote( FILE *fp ) -{ - int ch; - - do { - ch = vips__fgetc( fp ); - - /* Ignore \" in strings. - */ - if( ch == '\\' ) - ch = vips__fgetc( fp ); - else if( ch == '"' ) - break; - } while( ch != EOF && - ch != '\n' ); - - ungetc( ch, fp ); - - return( ch ); -} - -static int -skip_to_sep( FILE *fp, const char sepmap[256] ) -{ - int ch; - - do { - ch = vips__fgetc( fp ); - } while( ch != EOF && - ch != '\n' && - !sepmap[ch] ); - - ungetc( ch, fp ); - - return( ch ); -} - -/* Read a single item. Syntax is: - * - * element : - * whitespace* item whitespace* [EOF|EOL|separator] - * - * item : - * double | - * "anything" | - * empty - * - * the anything in quotes can contain " escaped with \ - * - * Return the char that caused failure on fail (EOF or \n). - */ -static int -read_double( FILE *fp, const char whitemap[256], const char sepmap[256], - int lineno, int colno, double *out, gboolean fail ) -{ - int ch; - - /* The fscanf() may change this ... but all other cases need a zero. - */ - *out = 0; - - ch = skip_white( fp, whitemap ); - if( ch == EOF || - ch == '\n' ) - return( ch ); - - if( ch == '"' ) { - (void) vips__fgetc( fp ); - (void) skip_to_quote( fp ); - (void) vips__fgetc( fp ); - } - else if( !sepmap[ch] && - fscanf( fp, "%lf", out ) != 1 ) { - /* Only a warning, since (for example) exported spreadsheets - * will often have text or date fields. - */ - g_warning( _( "error parsing number, line %d, column %d" ), - lineno, colno ); - if( fail ) - return( EOF ); - - /* Step over the bad data to the next separator. - */ - (void) skip_to_sep( fp, sepmap ); - } - - /* Don't need to check result, we have read a field successfully. - */ - ch = skip_white( fp, whitemap ); - - /* If it's a separator, we have to step over it. - */ - if( ch != EOF && - sepmap[ch] ) - (void) vips__fgetc( fp ); - - return( 0 ); -} - -static int -read_csv( FILE *fp, VipsImage *out, - int skip, - int lines, - const char *whitespace, const char *separator, - gboolean read_image, - gboolean fail ) -{ - int i; - char whitemap[256]; - char sepmap[256]; - const char *p; - fpos_t pos; - int columns; - int ch; - double d; - double *buf; - int y; - - /* Make our char maps. - */ - for( i = 0; i < 256; i++ ) { - whitemap[i] = 0; - sepmap[i] = 0; - } - for( p = whitespace; *p; p++ ) - whitemap[(int) *p] = 1; - for( p = separator; *p; p++ ) - sepmap[(int) *p] = 1; - - /* Skip first few lines. - */ - for( i = 0; i < skip; i++ ) - if( !skip_line( fp ) ) { - vips_error( "csv2vips", - "%s", _( "end of file while skipping start" ) ); - return( -1 ); - } - - /* Parse the first line to get number of columns. Only bother checking - * fgetpos() the first time we use it: assume it's working after this. - */ - if( fgetpos( fp, &pos ) ) { - vips_error_system( errno, "csv2vips", - "%s", _( "unable to seek" ) ); - return( -1 ); - } - for( columns = 0; - (ch = read_double( fp, whitemap, sepmap, - skip + 1, columns + 1, &d, fail )) == 0; - columns++ ) - ; - (void) fsetpos( fp, &pos ); - - if( columns == 0 ) { - vips_error( "csv2vips", "%s", _( "empty line" ) ); - return( -1 ); - } - - /* If lines is -1, we have to scan the whole file to get the - * number of lines out. - */ - if( lines == -1 ) { - (void) fgetpos( fp, &pos ); - for( lines = 0; skip_line( fp ); lines++ ) - ; - (void) fsetpos( fp, &pos ); - } - - vips_image_pipelinev( out, VIPS_DEMAND_STYLE_THINSTRIP, NULL ); - vips_image_init_fields( out, - columns, lines, 1, - VIPS_FORMAT_DOUBLE, - VIPS_CODING_NONE, VIPS_INTERPRETATION_B_W, 1.0, 1.0 ); - - /* Just reading the header? We are done. - */ - if( !read_image ) - return( 0 ); - - if( !(buf = VIPS_ARRAY( out, - VIPS_IMAGE_N_ELEMENTS( out ), double )) ) - return( -1 ); - - for( y = 0; y < lines; y++ ) { - int x; - - for( x = 0; x < columns; x++ ) { - int lineno = y + skip + 1; - int colno = x + 1; - - ch = read_double( fp, whitemap, sepmap, - lineno, colno, &d, fail ); - if( ch == EOF ) { - vips_error( "csv2vips", - _( "unexpected EOF, line %d col %d" ), - lineno, colno ); - return( -1 ); - } - else if( ch == '\n' ) { - vips_error( "csv2vips", - _( "unexpected EOL, line %d col %d" ), - lineno, colno ); - return( -1 ); - } - else if( ch ) - /* Parse error. - */ - return( -1 ); - - buf[x] = d; - } - - if( vips_image_write_line( out, y, (VipsPel *) buf ) ) - return( -1 ); - - /* Skip over the '\n' to the next line. - */ - skip_line( fp ); - } - - return( 0 ); -} - -int -vips__csv_read( const char *filename, VipsImage *out, - int skip, int lines, const char *whitespace, const char *separator, - gboolean fail ) -{ - FILE *fp; - - if( !(fp = vips__file_open_read( filename, NULL, TRUE )) ) - return( -1 ); - if( read_csv( fp, out, - skip, lines, whitespace, separator, TRUE, fail ) ) { - fclose( fp ); - return( -1 ); - } - fclose( fp ); - - return( 0 ); -} - -int -vips__csv_read_header( const char *filename, VipsImage *out, - int skip, int lines, const char *whitespace, const char *separator, - gboolean fail ) -{ - FILE *fp; - - if( !(fp = vips__file_open_read( filename, NULL, TRUE )) ) - return( -1 ); - if( read_csv( fp, out, - skip, lines, whitespace, separator, FALSE, fail ) ) { - fclose( fp ); - return( -1 ); - } - fclose( fp ); - - return( 0 ); -} - -const char *vips__foreign_csv_suffs[] = { ".csv", NULL }; - -#define PRINT_INT( TYPE ) fprintf( fp, "%d", *((TYPE*)p) ); -#define PRINT_FLOAT( TYPE ) fprintf( fp, "%g", *((TYPE*)p) ); -#define PRINT_COMPLEX( TYPE ) fprintf( fp, "(%g, %g)", \ - ((TYPE*)p)[0], ((TYPE*)p)[1] ); - -static int -vips2csv( VipsImage *in, FILE *fp, const char *sep ) -{ - int w = VIPS_IMAGE_N_ELEMENTS( in ); - int es = VIPS_IMAGE_SIZEOF_ELEMENT( in ); - - int x, y; - VipsPel *p; - - p = in->data; - for( y = 0; y < in->Ysize; y++ ) { - for( x = 0; x < w; x++ ) { - if( x > 0 ) - fprintf( fp, "%s", sep ); - - switch( in->BandFmt ) { - case VIPS_FORMAT_UCHAR: - PRINT_INT( unsigned char ); break; - case VIPS_FORMAT_CHAR: - PRINT_INT( char ); break; - case VIPS_FORMAT_USHORT: - PRINT_INT( unsigned short ); break; - case VIPS_FORMAT_SHORT: - PRINT_INT( short ); break; - case VIPS_FORMAT_UINT: - PRINT_INT( unsigned int ); break; - case VIPS_FORMAT_INT: - PRINT_INT( int ); break; - case VIPS_FORMAT_FLOAT: - PRINT_FLOAT( float ); break; - case VIPS_FORMAT_DOUBLE: - PRINT_FLOAT( double ); break; - case VIPS_FORMAT_COMPLEX: - PRINT_COMPLEX( float ); break; - case VIPS_FORMAT_DPCOMPLEX: - PRINT_COMPLEX( double ); break; - - default: - g_assert_not_reached(); - } - - p += es; - } - - fprintf( fp, "\n" ); - } - - return( 0 ); -} - -int -vips__csv_write( VipsImage *in, const char *filename, const char *separator ) -{ - FILE *fp; - VipsImage *memory; - - if( vips_check_mono( "vips2csv", in ) || - vips_check_uncoded( "vips2csv", in ) || - !(memory = vips_image_copy_memory( in )) ) - return( -1 ); - - if( !(fp = vips__file_open_write( filename, TRUE )) ) { - VIPS_UNREF( memory ); - return( -1 ); - } - - if( vips2csv( memory, fp, separator ) ) { - fclose( fp ); - VIPS_UNREF( memory ); - return( -1 ); - } - fclose( fp ); - VIPS_UNREF( memory ); - - return( 0 ); -} - -/* Read to non-whitespace, or buffer overflow. - */ -static int -fetch_nonwhite( FILE *fp, const char whitemap[256], char *buf, int max ) -{ - int ch; - int i; - - for( i = 0; i < max - 1; i++ ) { - ch = vips__fgetc( fp ); - - if( ch == EOF || - ch == '\n' || - whitemap[ch] ) - break; - - buf[i] = ch; - } - - buf[i] = '\0'; - - /* We mustn't skip the terminator. - */ - ungetc( ch, fp ); - - return( ch ); -} - -/* Read a single double in ascii (not locale) encoding. - * - * Return the char that caused failure on fail (EOF or \n). - */ -static int -read_ascii_double( FILE *fp, const char whitemap[256], double *out ) -{ - int ch; - char buf[256]; - char *p; - - *out = 0.0; - - ch = skip_white( fp, whitemap ); - - if( ch == EOF || - ch == '\n' ) - return( ch ); - - fetch_nonwhite( fp, whitemap, buf, 256 ); - - /* The str we fetched must contain at least 1 digit. This helps stop - * us trying to convert "MATLAB" (for example) to a number and - * getting zero. - */ - for( p = buf; *p; p++ ) - if( isdigit( *p ) ) - break; - if( !*p ) - return( *buf ); - - *out = g_ascii_strtod( buf, NULL ); - - return( 0 ); -} - -/* Read the header. Two numbers for width and height, and two optional - * numbers for scale and offset. - * - * We can have scale and no offset, in which case we assume offset = 0. - */ -static int -vips__matrix_header( char *whitemap, FILE *fp, - int *width, int *height, double *scale, double *offset ) -{ - double header[4]; - double d; - int i; - int ch; - - for( i = 0; i < 4 && - (ch = read_ascii_double( fp, whitemap, &header[i] )) == 0; - i++ ) - ; - if( i < 4 ) - header[3] = 0.0; - if( i < 3 ) - header[2] = 1.0; - if( i < 2 ) { - vips_error( "mask2vips", "%s", _( "no width / height" ) ); - return( -1 ); - } - - if( VIPS_FLOOR( header[0] ) != header[0] || - VIPS_FLOOR( header[1] ) != header[1] ) { - vips_error( "mask2vips", "%s", _( "width / height not int" ) ); - return( -1 ); - } - *width = header[0]; - *height = header[1]; - if( *width <= 0 || - *width > 100000 || - *height <= 0 || - *height > 100000 ) { - vips_error( "mask2vips", - "%s", _( "width / height out of range" ) ); - return( -1 ); - } - if( (ch = read_ascii_double( fp, whitemap, &d )) != '\n' ) { - vips_error( "mask2vips", "%s", _( "extra chars in header" ) ); - return( -1 ); - } - if( header[2] == 0.0 ) { - vips_error( "mask2vips", "%s", _( "zero scale" ) ); - return( -1 ); - } - - *scale = header[2]; - *offset = header[3]; - - skip_line( fp ); - - return( 0 ); -} - -#define WHITESPACE " \"\t\n;," - -/* Get the header from an matrix file. - * - * Also read the first line and make sure there are the right number of - * entries. - */ -int -vips__matrix_read_header( const char *filename, - int *width, int *height, double *scale, double *offset ) -{ - char whitemap[256]; - int i; - char *p; - FILE *fp; - int ch; - double d; - - for( i = 0; i < 256; i++ ) - whitemap[i] = 0; - for( p = WHITESPACE; *p; p++ ) - whitemap[(int) *p] = 1; - - if( !(fp = vips__file_open_read( filename, NULL, TRUE )) ) - return( -1 ); - if( vips__matrix_header( whitemap, fp, - width, height, scale, offset ) ) { - fclose( fp ); - return( -1 ); - } - - for( i = 0; i < *width; i++ ) { - ch = read_ascii_double( fp, whitemap, &d ); - - if( ch ) { - fclose( fp ); - vips_error( "mask2vips", "%s", _( "line too short" ) ); - return( -1 ); - } - } - - /* Deliberately don't check for line too long. - */ - - fclose( fp ); - - return( 0 ); -} - -int -vips__matrix_ismatrix( const char *filename ) -{ - int width; - int height; - double scale; - double offset; - int result; - - vips_error_freeze(); - result = vips__matrix_read_header( filename, - &width, &height, &scale, &offset ); - vips_error_thaw(); - - return( result == 0 ); -} - -static int -vips__matrix_body( char *whitemap, VipsImage *out, FILE *fp ) -{ - int x, y; - - for( y = 0; y < out->Ysize; y++ ) { - for( x = 0; x < out->Xsize; x++ ) { - int ch; - double d; - - ch = read_ascii_double( fp, whitemap, &d ); - if( ch == EOF || - ch == '\n' ) { - vips_error( "mask2vips", - _( "line %d too short" ), y + 1 ); - return( -1 ); - } - *VIPS_MATRIX( out, x, y ) = d; - - /* Deliberately don't check for line too long. - */ - } - - skip_line( fp ); - } - - return( 0 ); -} - -VipsImage * -vips__matrix_read_file( FILE *fp ) -{ - char whitemap[256]; - int i; - char *p; - int width; - int height; - double scale; - double offset; - VipsImage *out; - - for( i = 0; i < 256; i++ ) - whitemap[i] = 0; - for( p = WHITESPACE; *p; p++ ) - whitemap[(int) *p] = 1; - - if( vips__matrix_header( whitemap, fp, - &width, &height, &scale, &offset ) ) - return( NULL ); - - if( !(out = vips_image_new_matrix( width, height )) ) - return( NULL ); - vips_image_set_double( out, "scale", scale ); - vips_image_set_double( out, "offset", offset ); - - if( vips__matrix_body( whitemap, out, fp ) ) { - g_object_unref( out ); - return( NULL ); - } - - return( out ); -} - -VipsImage * -vips__matrix_read( const char *filename ) -{ - FILE *fp; - VipsImage *out; - - if( !(fp = vips__file_open_read( filename, NULL, TRUE )) ) - return( NULL ); - out = vips__matrix_read_file( fp ); - fclose( fp ); - - return( out ); -} - -int -vips__matrix_write_file( VipsImage *in, FILE *fp ) -{ - VipsImage *mask; - int x, y; - - if( vips_check_matrix( "vips2mask", in, &mask ) ) - return( -1 ); - - fprintf( fp, "%d %d ", mask->Xsize, mask->Ysize ); - if( vips_image_get_typeof( mask, "scale" ) && - vips_image_get_typeof( mask, "offset" ) ) - fprintf( fp, "%g %g ", - vips_image_get_scale( mask ), - vips_image_get_offset( mask ) ); - fprintf( fp, "\n" ); - - for( y = 0; y < mask->Ysize; y++ ) { - for( x = 0; x < mask->Xsize; x++ ) - fprintf( fp, "%g ", *VIPS_MATRIX( mask, x, y ) ); - - fprintf( fp, "\n" ); - } - - g_object_unref( mask ); - - return( 0 ); -} - -int -vips__matrix_write( VipsImage *in, const char *filename ) -{ - FILE *fp; - int result; - - if( !(fp = vips__file_open_write( filename, TRUE )) ) - return( -1 ); - result = vips__matrix_write_file( in, fp ); - fclose( fp ); - - return( result ); -} - -const char *vips__foreign_matrix_suffs[] = { ".mat", NULL }; - diff --git a/libvips/foreign/csvload.c b/libvips/foreign/csvload.c index 41825706..db223abe 100644 --- a/libvips/foreign/csvload.c +++ b/libvips/foreign/csvload.c @@ -2,6 +2,8 @@ * * 5/12/11 * - from csvload.c + * 21/2/20 + * - rewrite for new source API */ /* @@ -43,6 +45,7 @@ #include #include #include +#include #include #include @@ -50,50 +53,327 @@ #include "pforeign.h" +/* The largest item we can read. It only needs to be big enough for a double. + */ +#define MAX_ITEM_SIZE (256) + typedef struct _VipsForeignLoadCsv { VipsForeignLoad parent_object; - /* Filename for load. + /* Set by subclasses. */ - char *filename; + VipsSource *source; + /* Buffered source. + */ + VipsSbuf *sbuf; + + /* Load options. + */ int skip; int lines; const char *whitespace; const char *separator; + /* Current position in file for error messages. + */ + int lineno; + int colno; + + /* Our whitespace and separator strings turned into LUTs. + */ + char whitemap[256]; + char sepmap[256]; + + /* Fetch items into this buffer. It just needs to be large enough for + * a double. + */ + char item[MAX_ITEM_SIZE]; + + /* A line of pixels. + */ + double *linebuf; + } VipsForeignLoadCsv; typedef VipsForeignLoadClass VipsForeignLoadCsvClass; -G_DEFINE_TYPE( VipsForeignLoadCsv, vips_foreign_load_csv, +G_DEFINE_ABSTRACT_TYPE( VipsForeignLoadCsv, vips_foreign_load_csv, VIPS_TYPE_FOREIGN_LOAD ); -static VipsForeignFlags -vips_foreign_load_csv_get_flags_filename( const char *filename ) +static void +vips_foreign_load_csv_dispose( GObject *gobject ) { + VipsForeignLoadCsv *csv = (VipsForeignLoadCsv *) gobject; + + VIPS_UNREF( csv->source ); + VIPS_UNREF( csv->sbuf ); + VIPS_FREE( csv->linebuf ); + + G_OBJECT_CLASS( vips_foreign_load_csv_parent_class )-> + dispose( gobject ); +} + +static int +vips_foreign_load_csv_build( VipsObject *object ) +{ + VipsForeignLoadCsv *csv = (VipsForeignLoadCsv *) object; + + int i; + const char *p; + + if( !(csv->sbuf = vips_sbuf_new_from_source( csv->source )) ) + return( -1 ); + + /* Make our char maps. + */ + for( i = 0; i < 256; i++ ) { + csv->whitemap[i] = 0; + csv->sepmap[i] = 0; + } + for( p = csv->whitespace; *p; p++ ) + csv->whitemap[(int) *p] = 1; + for( p = csv->separator; *p; p++ ) + csv->sepmap[(int) *p] = 1; + + /* \n must not be in the maps or we'll get very confused. + */ + csv->sepmap[(int) '\n'] = 0; + csv->whitemap[(int) '\n'] = 0; + + if( VIPS_OBJECT_CLASS( vips_foreign_load_csv_parent_class )-> + build( object ) ) + return( -1 ); + return( 0 ); } static VipsForeignFlags vips_foreign_load_csv_get_flags( VipsForeignLoad *load ) { - VipsForeignLoadCsv *csv = (VipsForeignLoadCsv *) load; + return( 0 ); +} - return( vips_foreign_load_csv_get_flags_filename( csv->filename ) ); +/* Skip to the start of the next block of non-whitespace. + * + * Result: !white, \n, EOF + */ +static int +vips_foreign_load_csv_skip_white( VipsForeignLoadCsv *csv ) +{ + int ch; + + do { + ch = VIPS_SBUF_GETC( csv->sbuf ); + } while( ch != EOF && + ch != '\n' && + csv->whitemap[ch] ); + + VIPS_SBUF_UNGETC( csv->sbuf ); + + return( ch ); +} + +/* We have just seen " (open quotes). Skip to just after the matching close + * quotes. + * + * If there is no matching close quotes before the end of the line, don't + * skip to the next line. + * + * Result: ", \n, EOF + */ +static int +vips_foreign_load_csv_skip_quoted( VipsForeignLoadCsv *csv ) +{ + int ch; + + do { + ch = VIPS_SBUF_GETC( csv->sbuf ); + + /* Ignore \" (actually \anything) in strings. + */ + if( ch == '\\' ) + ch = VIPS_SBUF_GETC( csv->sbuf ); + else if( ch == '"' ) + break; + } while( ch != EOF && + ch != '\n' ); + + if( ch == '\n' ) + VIPS_SBUF_UNGETC( csv->sbuf ); + + return( ch ); +} + +/* Fetch the next item (not whitespace, separator or \n), as a string. The + * returned string is valid until the next call to fetch item. NULL for EOF. + */ +static const char * +vips_foreign_load_csv_fetch_item( VipsForeignLoadCsv *csv ) +{ + int write_point; + int space_remaining; + int ch; + + /* -1 so there's space for the \0 terminator. + */ + space_remaining = MAX_ITEM_SIZE - 1; + write_point = 0; + + while( (ch = VIPS_SBUF_GETC( csv->sbuf )) != -1 && + ch != '\n' && + !csv->whitemap[ch] && + !csv->sepmap[ch] && + space_remaining > 0 ) { + csv->item[write_point] = ch; + write_point += 1; + space_remaining -= 1; + } + csv->item[write_point] = '\0'; + + /* If we hit EOF immediately, return EOF. + */ + if( ch == -1 && + write_point == 0 ) + return( NULL ); + + /* If we filled the item buffer without seeing the end of the item, + * read up to the item end. + */ + while( ch != -1 && + ch != '\n' && + !csv->whitemap[ch] && + !csv->sepmap[ch] ) + ch = VIPS_SBUF_GETC( csv->sbuf ); + + /* We've (probably) read the end of item character. Push it bakc. + */ + if( ch == '\n' || + csv->whitemap[ch] || + csv->sepmap[ch] ) + VIPS_SBUF_UNGETC( csv->sbuf ); + + return( csv->item ); +} + +/* Read a single item. The syntax is: + * + * element : + * whitespace* item whitespace* [EOF|EOL|separator] + * + * item : + * double | + * "anything" | + * empty + * + * the anything in quotes can contain " escaped with \, and can contain + * separator and whitespace characters. + * + * Result: sep, \n, EOF + */ +static int +vips_foreign_load_csv_read_double( VipsForeignLoadCsv *csv, double *out ) +{ + int ch; + + /* The strtod() may change this ... but all other cases need a zero. + */ + *out = 0; + + ch = vips_foreign_load_csv_skip_white( csv ); + if( ch == EOF || + ch == '\n' ) + return( ch ); + + if( ch == '"' ) { + (void) VIPS_SBUF_GETC( csv->sbuf ); + ch = vips_foreign_load_csv_skip_quoted( csv ); + } + else if( !csv->sepmap[ch] ) { + const char *item; + + item = vips_foreign_load_csv_fetch_item( csv ); + if( !item ) + return( EOF ); + + if( vips_strtod( item, out ) ) + /* Only a warning, since (for example) exported + * spreadsheets will often have text or date fields. + */ + g_warning( _( "bad number, line %d, column %d" ), + csv->lineno, csv->colno ); + } + + ch = vips_foreign_load_csv_skip_white( csv ); + if( ch == EOF || + ch == '\n' ) + return( ch ); + + /* If it's a separator, we have to step over it. + */ + if( csv->sepmap[ch] ) + (void) VIPS_SBUF_GETC( csv->sbuf ); + + return( ch ); } static int vips_foreign_load_csv_header( VipsForeignLoad *load ) { + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( load ); VipsForeignLoadCsv *csv = (VipsForeignLoadCsv *) load; - if( vips__csv_read_header( csv->filename, load->out, - csv->skip, csv->lines, csv->whitespace, csv->separator, - load->fail ) ) + int i; + double value; + int ch; + int width; + int height; + + /* Rewind. + */ + vips_sbuf_unbuffer( csv->sbuf ); + if( vips_source_rewind( csv->source ) ) return( -1 ); - VIPS_SETSTR( load->out->filename, csv->filename ); + /* Skip the first few lines. + */ + for( i = 0; i < csv->skip; i++ ) + if( !vips_sbuf_get_line( csv->sbuf ) ) { + vips_error( class->nickname, + "%s", _( "unexpected end of file" ) ); + return( -1 ); + } + + /* Parse the first line to get the number of columns. + */ + csv->lineno = csv->skip + 1; + csv->colno = 0; + do { + csv->colno += 1; + ch = vips_foreign_load_csv_read_double( csv, &value ); + } while( ch != '\n' && + ch != EOF ); + width = csv->colno; + + if( !(csv->linebuf = VIPS_ARRAY( NULL, width, double )) ) + return( -1 ); + + /* If @lines is -1, we must scan the whole file to get the height. + */ + if( csv->lines == -1 ) + for( height = 0; vips_sbuf_get_line( csv->sbuf ); height++ ) + ; + else + height = csv->lines; + + vips_image_pipelinev( load->out, VIPS_DEMAND_STYLE_THINSTRIP, NULL ); + vips_image_init_fields( load->out, + width, height, 1, + VIPS_FORMAT_DOUBLE, + VIPS_CODING_NONE, VIPS_INTERPRETATION_B_W, 1.0, 1.0 ); + + VIPS_SETSTR( load->out->filename, + vips_connection_filename( VIPS_CONNECTION( csv->source ) ) ); return( 0 ); } @@ -101,13 +381,76 @@ vips_foreign_load_csv_header( VipsForeignLoad *load ) static int vips_foreign_load_csv_load( VipsForeignLoad *load ) { + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( load ); VipsForeignLoadCsv *csv = (VipsForeignLoadCsv *) load; - if( vips__csv_read( csv->filename, load->real, - csv->skip, csv->lines, csv->whitespace, csv->separator, - load->fail ) ) + int i; + int x, y; + int ch; + + /* Rewind. + */ + vips_sbuf_unbuffer( csv->sbuf ); + if( vips_source_rewind( csv->source ) ) return( -1 ); + /* Skip the first few lines. + */ + for( i = 0; i < csv->skip; i++ ) + if( !vips_sbuf_get_line( csv->sbuf ) ) { + vips_error( class->nickname, + "%s", _( "unexpected end of file" ) ); + return( -1 ); + } + + vips_image_pipelinev( load->real, VIPS_DEMAND_STYLE_THINSTRIP, NULL ); + vips_image_init_fields( load->real, + load->out->Xsize, load->out->Ysize, 1, + VIPS_FORMAT_DOUBLE, + VIPS_CODING_NONE, VIPS_INTERPRETATION_B_W, 1.0, 1.0 ); + + csv->lineno = csv->skip + 1; + for( y = 0; y < load->real->Ysize; y++ ) { + csv->colno = 0; + + /* Not needed, but stops a used-before-set compiler warning. + */ + ch = EOF; + + for( x = 0; x < load->real->Xsize; x++ ) { + double value; + + csv->colno += 1; + ch = vips_foreign_load_csv_read_double( csv, &value ); + if( ch == EOF ) { + vips_error( class->nickname, + "%s", _( "unexpected end of file" ) ); + return( -1 ); + } + if( ch == '\n' && + x != load->real->Xsize - 1 ) { + vips_error( class->nickname, + _( "line %d has only %d columns" ), + csv->lineno, csv->colno ); + if( load->fail ) + return( -1 ); + } + + csv->linebuf[x] = value; + } + + /* Step over the line separator. + */ + if( ch == '\n' ) { + (void) VIPS_SBUF_GETC( csv->sbuf ); + csv->lineno += 1; + } + + if( vips_image_write_line( load->real, y, + (VipsPel *) csv->linebuf ) ) + return( -1 ); + } + return( 0 ); } @@ -116,30 +459,20 @@ vips_foreign_load_csv_class_init( VipsForeignLoadCsvClass *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->dispose = vips_foreign_load_csv_dispose; gobject_class->set_property = vips_object_set_property; gobject_class->get_property = vips_object_get_property; - object_class->nickname = "csvload"; - object_class->description = _( "load csv from file" ); + object_class->nickname = "csvload_base"; + object_class->description = _( "load csv" ); + object_class->build = vips_foreign_load_csv_build; - foreign_class->suffs = vips__foreign_csv_suffs; - - load_class->get_flags_filename = - vips_foreign_load_csv_get_flags_filename; load_class->get_flags = vips_foreign_load_csv_get_flags; load_class->header = vips_foreign_load_csv_header; load_class->load = vips_foreign_load_csv_load; - VIPS_ARG_STRING( class, "filename", 1, - _( "Filename" ), - _( "Filename to load from" ), - VIPS_ARGUMENT_REQUIRED_INPUT, - G_STRUCT_OFFSET( VipsForeignLoadCsv, filename ), - NULL ); - VIPS_ARG_INT( class, "skip", 20, _( "Skip" ), _( "Skip this many lines at the start of the file" ), @@ -177,6 +510,138 @@ vips_foreign_load_csv_init( VipsForeignLoadCsv *csv ) csv->separator = g_strdup( ";,\t" ); } +typedef struct _VipsForeignLoadCsvFile { + VipsForeignLoadCsv parent_object; + + /* Filename for load. + */ + char *filename; + +} VipsForeignLoadCsvFile; + +typedef VipsForeignLoadCsvClass VipsForeignLoadCsvFileClass; + +G_DEFINE_TYPE( VipsForeignLoadCsvFile, vips_foreign_load_csv_file, + vips_foreign_load_csv_get_type() ); + +static VipsForeignFlags +vips_foreign_load_csv_file_get_flags_filename( const char *filename ) +{ + return( 0 ); +} + +static int +vips_foreign_load_csv_file_build( VipsObject *object ) +{ + VipsForeignLoadCsv *csv = (VipsForeignLoadCsv *) object; + VipsForeignLoadCsvFile *file = (VipsForeignLoadCsvFile *) object; + + if( file->filename ) + if( !(csv->source = + vips_source_new_from_file( file->filename )) ) + return( -1 ); + + if( VIPS_OBJECT_CLASS( vips_foreign_load_csv_file_parent_class )-> + build( object ) ) + return( -1 ); + + return( 0 ); +} + +static const char *vips_foreign_load_csv_suffs[] = { + ".csv", + NULL +}; + +static void +vips_foreign_load_csv_file_class_init( VipsForeignLoadCsvFileClass *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 = "csvload"; + object_class->build = vips_foreign_load_csv_file_build; + + foreign_class->suffs = vips_foreign_load_csv_suffs; + + load_class->get_flags_filename = + vips_foreign_load_csv_file_get_flags_filename; + + VIPS_ARG_STRING( class, "filename", 1, + _( "Filename" ), + _( "Filename to load from" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadCsvFile, filename ), + NULL ); + +} + +static void +vips_foreign_load_csv_file_init( VipsForeignLoadCsvFile *file ) +{ +} + +typedef struct _VipsForeignLoadCsvSource { + VipsForeignLoadCsv parent_object; + + VipsSource *source; + +} VipsForeignLoadCsvSource; + +typedef VipsForeignLoadCsvClass VipsForeignLoadCsvSourceClass; + +G_DEFINE_TYPE( VipsForeignLoadCsvSource, vips_foreign_load_csv_source, + vips_foreign_load_csv_get_type() ); + +static int +vips_foreign_load_csv_source_build( VipsObject *object ) +{ + VipsForeignLoadCsv *csv = (VipsForeignLoadCsv *) object; + VipsForeignLoadCsvSource *source = (VipsForeignLoadCsvSource *) object; + + if( source->source ) { + csv->source = source->source; + g_object_ref( csv->source ); + } + + if( VIPS_OBJECT_CLASS( vips_foreign_load_csv_source_parent_class )-> + build( object ) ) + return( -1 ); + + return( 0 ); +} + +static void +vips_foreign_load_csv_source_class_init( VipsForeignLoadCsvFileClass *class ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( class ); + VipsObjectClass *object_class = (VipsObjectClass *) class; + + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + object_class->nickname = "csvload_source"; + object_class->build = vips_foreign_load_csv_source_build; + + VIPS_ARG_OBJECT( class, "source", 1, + _( "Source" ), + _( "Source to load from" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadCsvSource, source ), + VIPS_TYPE_SOURCE ); + +} + +static void +vips_foreign_load_csv_source_init( VipsForeignLoadCsvSource *source ) +{ +} + /** * vips_csvload: * @filename: file to load @@ -200,10 +665,6 @@ vips_foreign_load_csv_init( VipsForeignLoadCsv *csv ) * You can use a backslash (\) within the quotes to escape special characters, * such as quote marks. * - * The reader is deliberately rather fussy: it will fail if there are any - * short lines, or if the file is too short. It will ignore lines that are - * too long. - * * @skip sets the number of lines to skip at the start of the file. * Default zero. * @@ -236,4 +697,39 @@ vips_csvload( const char *filename, VipsImage **out, ... ) return( result ); } +/** + * vips_csvload_source: + * @source: source to load + * @out: (out): output image + * @...: %NULL-terminated list of optional named arguments + * + * Optional arguments: + * + * * @skip: skip this many lines at start of file + * * @lines: read this many lines from file + * * @whitespace: set of whitespace characters + * * @separator: set of separator characters + * * @fail: %gboolean, fail on errors + * + * Exactly as vips_csvload(), but read from a source. + * + * See also: vips_csvload(). + * + * Returns: 0 on success, -1 on error. + */ +int +vips_csvload_source( VipsSource *source, VipsImage **out, ... ) +{ + va_list ap; + int result; + + va_start( ap, out ); + result = vips_call_split( "csvload_source", ap, source, out ); + va_end( ap ); + + return( result ); +} + + + diff --git a/libvips/foreign/csvsave.c b/libvips/foreign/csvsave.c index 633f9ac9..a4178d56 100644 --- a/libvips/foreign/csvsave.c +++ b/libvips/foreign/csvsave.c @@ -2,6 +2,8 @@ * * 2/12/11 * - wrap a class around the csv writer + * 21/2/20 + * - rewrite for the VipsTarget API */ /* @@ -52,34 +54,139 @@ typedef struct _VipsForeignSaveCsv { VipsForeignSave parent_object; - /* Filename for save. - */ - char *filename; + VipsTarget *target; const char *separator; } VipsForeignSaveCsv; typedef VipsForeignSaveClass VipsForeignSaveCsvClass; -G_DEFINE_TYPE( VipsForeignSaveCsv, vips_foreign_save_csv, +G_DEFINE_ABSTRACT_TYPE( VipsForeignSaveCsv, vips_foreign_save_csv, VIPS_TYPE_FOREIGN_SAVE ); +static void +vips_foreign_save_csv_dispose( GObject *gobject ) +{ + VipsForeignSaveCsv *csv = (VipsForeignSaveCsv *) gobject; + + if( csv->target ) + vips_target_finish( csv->target ); + VIPS_UNREF( csv->target ); + + G_OBJECT_CLASS( vips_foreign_save_csv_parent_class )-> + dispose( gobject ); +} + +#define PRINT_INT( TYPE ) { \ + TYPE *pt = (TYPE *) p; \ + \ + for( x = 0; x < image->Xsize; x++ ) { \ + if( x > 0 ) \ + vips_target_writes( csv->target, csv->separator ); \ + vips_target_writef( csv->target, "%d", pt[x] ); \ + } \ +} + +#define PRINT_FLOAT( TYPE ) { \ + TYPE *pt = (TYPE *) p; \ + char buf[G_ASCII_DTOSTR_BUF_SIZE]; \ + \ + for( x = 0; x < image->Xsize; x++ ) { \ + if( x > 0 ) \ + vips_target_writes( csv->target, csv->separator ); \ + g_ascii_dtostr( buf, G_ASCII_DTOSTR_BUF_SIZE, pt[x] ); \ + vips_target_writes( csv->target, buf ); \ + } \ +} + +#define PRINT_COMPLEX( TYPE ) { \ + TYPE *pt = (TYPE *) p; \ + char buf[G_ASCII_DTOSTR_BUF_SIZE]; \ + \ + for( x = 0; x < image->Xsize; x++ ) { \ + if( x > 0 ) \ + vips_target_writes( csv->target, csv->separator ); \ + VIPS_TARGET_PUTC( csv->target, '(' ); \ + g_ascii_dtostr( buf, G_ASCII_DTOSTR_BUF_SIZE, pt[0] ); \ + vips_target_writes( csv->target, buf ); \ + VIPS_TARGET_PUTC( csv->target, ',' ); \ + g_ascii_dtostr( buf, G_ASCII_DTOSTR_BUF_SIZE, pt[1] ); \ + vips_target_writes( csv->target, buf ); \ + VIPS_TARGET_PUTC( csv->target, ')' ); \ + pt += 2; \ + } \ +} + +static int +vips_foreign_save_csv_block( VipsRegion *region, VipsRect *area, void *a ) +{ + VipsForeignSaveCsv *csv = (VipsForeignSaveCsv *) a; + VipsImage *image = region->im; + + int x, y; + + for( y = 0; y < area->height; y++ ) { + VipsPel *p = VIPS_REGION_ADDR( region, 0, area->top + y ); + + switch( image->BandFmt ) { + case VIPS_FORMAT_UCHAR: + PRINT_INT( unsigned char ); break; + case VIPS_FORMAT_CHAR: + PRINT_INT( char ); break; + case VIPS_FORMAT_USHORT: + PRINT_INT( unsigned short ); break; + case VIPS_FORMAT_SHORT: + PRINT_INT( short ); break; + case VIPS_FORMAT_UINT: + PRINT_INT( unsigned int ); break; + case VIPS_FORMAT_INT: + PRINT_INT( int ); break; + case VIPS_FORMAT_FLOAT: + PRINT_FLOAT( float ); break; + case VIPS_FORMAT_DOUBLE: + PRINT_FLOAT( double ); break; + case VIPS_FORMAT_COMPLEX: + PRINT_COMPLEX( float ); break; + case VIPS_FORMAT_DPCOMPLEX: + PRINT_COMPLEX( double ); break; + + default: + g_assert_not_reached(); + } + + if( vips_target_writes( csv->target, "\n" ) ) + return( -1 ); + } + + return( 0 ); +} + static int vips_foreign_save_csv_build( VipsObject *object ) { VipsForeignSave *save = (VipsForeignSave *) object; VipsForeignSaveCsv *csv = (VipsForeignSaveCsv *) object; + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object ); if( VIPS_OBJECT_CLASS( vips_foreign_save_csv_parent_class )-> build( object ) ) return( -1 ); - if( vips__csv_write( save->ready, csv->filename, csv->separator ) ) + if( vips_check_mono( class->nickname, save->ready ) || + vips_check_uncoded( class->nickname, save->ready ) ) + return( -1 ); + + if( vips_sink_disc( save->ready, vips_foreign_save_csv_block, csv ) ) return( -1 ); return( 0 ); } +static const char *vips_foreign_save_csv_suffs[] = { + ".csv", + NULL +}; + static void vips_foreign_save_csv_class_init( VipsForeignSaveCsvClass *class ) { @@ -88,24 +195,18 @@ vips_foreign_save_csv_class_init( VipsForeignSaveCsvClass *class ) VipsForeignClass *foreign_class = (VipsForeignClass *) class; VipsForeignSaveClass *save_class = (VipsForeignSaveClass *) class; + gobject_class->dispose = vips_foreign_save_csv_dispose; gobject_class->set_property = vips_object_set_property; gobject_class->get_property = vips_object_get_property; - object_class->nickname = "csvsave"; - object_class->description = _( "save image to csv file" ); + object_class->nickname = "csvsave_base"; + object_class->description = _( "save image to csv" ); object_class->build = vips_foreign_save_csv_build; - foreign_class->suffs = vips__foreign_csv_suffs; + foreign_class->suffs = vips_foreign_save_csv_suffs; save_class->saveable = VIPS_SAVEABLE_MONO; - VIPS_ARG_STRING( class, "filename", 1, - _( "Filename" ), - _( "Filename to save to" ), - VIPS_ARGUMENT_REQUIRED_INPUT, - G_STRUCT_OFFSET( VipsForeignSaveCsv, filename ), - NULL ); - VIPS_ARG_STRING( class, "separator", 13, _( "Separator" ), _( "Separator characters" ), @@ -120,6 +221,109 @@ vips_foreign_save_csv_init( VipsForeignSaveCsv *csv ) csv->separator = g_strdup( "\t" ); } +typedef struct _VipsForeignSaveCsvFile { + VipsForeignSaveCsv parent_object; + + char *filename; +} VipsForeignSaveCsvFile; + +typedef VipsForeignSaveCsvClass VipsForeignSaveCsvFileClass; + +G_DEFINE_TYPE( VipsForeignSaveCsvFile, vips_foreign_save_csv_file, + vips_foreign_save_csv_get_type() ); + +static int +vips_foreign_save_csv_file_build( VipsObject *object ) +{ + VipsForeignSaveCsv *csv = (VipsForeignSaveCsv *) object; + VipsForeignSaveCsvFile *file = (VipsForeignSaveCsvFile *) object; + + if( file->filename && + !(csv->target = vips_target_new_to_file( file->filename )) ) + return( -1 ); + + return( VIPS_OBJECT_CLASS( vips_foreign_save_csv_file_parent_class )-> + build( object ) ); +} + +static void +vips_foreign_save_csv_file_class_init( VipsForeignSaveCsvFileClass *class ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( class ); + VipsObjectClass *object_class = (VipsObjectClass *) class; + + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + object_class->nickname = "csvsave"; + object_class->build = vips_foreign_save_csv_file_build; + + VIPS_ARG_STRING( class, "filename", 1, + _( "Filename" ), + _( "Filename to save to" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsForeignSaveCsvFile, filename ), + NULL ); + +} + +static void +vips_foreign_save_csv_file_init( VipsForeignSaveCsvFile *file ) +{ +} + +typedef struct _VipsForeignSaveCsvTarget { + VipsForeignSaveCsv parent_object; + + VipsTarget *target; +} VipsForeignSaveCsvTarget; + +typedef VipsForeignSaveCsvClass VipsForeignSaveCsvTargetClass; + +G_DEFINE_TYPE( VipsForeignSaveCsvTarget, vips_foreign_save_csv_target, + vips_foreign_save_csv_get_type() ); + +static int +vips_foreign_save_csv_target_build( VipsObject *object ) +{ + VipsForeignSaveCsv *csv = (VipsForeignSaveCsv *) object; + VipsForeignSaveCsvTarget *target = (VipsForeignSaveCsvTarget *) object; + + if( target->target ) { + csv->target = target->target; + g_object_ref( csv->target ); + } + + return( VIPS_OBJECT_CLASS( vips_foreign_save_csv_target_parent_class )-> + build( object ) ); +} + +static void +vips_foreign_save_csv_target_class_init( VipsForeignSaveCsvTargetClass *class ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( class ); + VipsObjectClass *object_class = (VipsObjectClass *) class; + + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + object_class->nickname = "csvsave_target"; + object_class->build = vips_foreign_save_csv_target_build; + + VIPS_ARG_OBJECT( class, "target", 1, + _( "Target" ), + _( "Target to save to" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsForeignSaveCsvTarget, target ), + VIPS_TYPE_TARGET ); + +} + +static void +vips_foreign_save_csv_target_init( VipsForeignSaveCsvTarget *target ) +{ +} + /** * vips_csvsave: (method) * @in: image to save @@ -155,3 +359,32 @@ vips_csvsave( VipsImage *in, const char *filename, ... ) return( result ); } + +/** + * vips_csvsave_target: (method) + * @in: image to save + * @target: save image to this target + * @...: %NULL-terminated list of optional named arguments + * + * Optional arguments: + * + * * @separator: separator string + * + * As vips_csvsave(), but save to a target. + * + * See also: vips_csvsave(). + * + * Returns: 0 on success, -1 on error. + */ +int +vips_csvsave_target( VipsImage *in, VipsTarget *target, ... ) +{ + va_list ap; + int result; + + va_start( ap, target ); + result = vips_call_split( "csvsave_target", ap, in, target ); + va_end( ap ); + + return( result ); +} diff --git a/libvips/foreign/dzsave.c b/libvips/foreign/dzsave.c index a1ab7067..d0e2a98a 100644 --- a/libvips/foreign/dzsave.c +++ b/libvips/foreign/dzsave.c @@ -81,6 +81,12 @@ * - write all associated images * 19/12/18 * - add @skip_blanks + * 21/10/19 + * - add @no_strip + * 9/11/19 + * - add IIIF layout + * 24/4/20 [IllyaMoskvin] + * - better IIIF tile naming */ /* @@ -454,6 +460,8 @@ struct _VipsForeignSaveDz { int compression; VipsRegionShrink region_shrink; int skip_blanks; + gboolean no_strip; + char *id; /* Tile and overlap geometry. The members above are the parameters we * accept, this next set are the derived values which are actually @@ -585,18 +593,29 @@ write_image( VipsForeignSaveDz *dz, { VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( dz ); + VipsImage *t; void *buf; size_t len; - /* Hopefully, no one will want the same metadata on all the images. - * Strip them. + /* We need to block progress signalling on individual image write, so + * we need a copy of the tile in case it's shared (eg. associated + * images). */ - vips_image_set_int( image, "hide-progress", 1 ); - if( vips_image_write_to_buffer( image, format, &buf, &len, - "strip", TRUE, - NULL ) ) + if( vips_copy( image, &t, NULL ) ) return( -1 ); + /* We default to stripping all metadata. Only "no_strip" turns this + * off. Very few people really want metadata on every tile. + */ + vips_image_set_int( t, "hide-progress", 1 ); + if( vips_image_write_to_buffer( t, format, &buf, &len, + "strip", !dz->no_strip, + NULL ) ) { + VIPS_UNREF( t ); + return( -1 ); + } + VIPS_UNREF( t ); + /* gsf doesn't like more than one write active at once. */ g_mutex_lock( vips__global_lock ); @@ -821,7 +840,7 @@ pyramid_build( VipsForeignSaveDz *dz, Layer *above, real_pixels->left, real_pixels->top ); printf( "\treal_pixels.width = %d, real_pixels.height = %d\n", real_pixels->width, real_pixels->height ); -#endif +#endif /*DEBUG*/ return( layer ); } @@ -934,6 +953,105 @@ write_blank( VipsForeignSaveDz *dz ) return( 0 ); } +/* Write IIIF JSON metadata. + */ +static int +write_json( VipsForeignSaveDz *dz ) +{ + /* Can be NULL for memory output. + */ + const char *name = dz->basename ? dz->basename : "untitled"; + + /* dz->file_suffix has a leading "." character. + */ + const char *suffix = dz->file_suffix[0] == '.' ? + dz->file_suffix + 1 : dz->file_suffix; + + GsfOutput *out; + int i; + + out = vips_gsf_path( dz->tree, "info.json", NULL ); + + gsf_output_printf( out, + "{\n" + " \"@context\": \"http://iiif.io/api/image/2/context.json\",\n" + " \"@id\": \"%s/%s\",\n" + " \"profile\": [\n" + " \"http://iiif.io/api/image/2/level0.json\",\n" + " {\n" + " \"formats\": [\n" + " \"%s\"\n" + " ],\n" + " \"qualities\": [\n" + " \"default\"\n" + " ]\n" + " }\n" + " ],\n" + " \"protocol\": \"http://iiif.io/api/image\",\n", + dz->id ? dz->id : "https://example.com/iiif", + name, + suffix ); + + /* "sizes" is needed for the full/ set of untiled images, which we + * don't yet support. Leave this commented out for now. + + gsf_output_printf( out, + " \"sizes\": [\n" ); + + for( i = 0; i < dz->layer->n + 5; i++ ) { + gsf_output_printf( out, + " {\n" + " \"width\": %d,\n" + " \"height\": \"full\"\n" + " }", + 1 << (i + 4) ); + if( i != dz->layer->n - 4 ) + gsf_output_printf( out, "," ); + gsf_output_printf( out, "\n" ); + } + + gsf_output_printf( out, + " ],\n" ); + + */ + + /* The set of pyramid layers we have written. + */ + gsf_output_printf( out, + " \"tiles\": [\n" + " {\n" + " \"scaleFactors\": [\n" ); + + for( i = 0; i < dz->layer->n; i++ ) { + gsf_output_printf( out, + " %d", + 1 << i ); + if( i != dz->layer->n - 1 ) + gsf_output_printf( out, "," ); + gsf_output_printf( out, "\n" ); + } + + gsf_output_printf( out, + " ],\n" + " \"width\": %d\n" + " }\n" + " ],\n", dz->tile_size ); + + gsf_output_printf( out, + " \"width\": %d,\n" + " \"height\": %d\n", + dz->layer->image->Xsize, + dz->layer->image->Ysize ); + + gsf_output_printf( out, + "}\n" ); + + (void) gsf_output_close( out ); + g_object_unref( out ); + + return( 0 ); +} + static int write_vips_meta( VipsForeignSaveDz *dz ) { @@ -1030,20 +1148,19 @@ char * build_scan_properties( VipsImage *image ) { VipsDbuf dbuf; - GTimeVal now; char *date; int i; - vips_dbuf_init( &dbuf ); + date = vips__get_iso8601(); - g_get_current_time( &now ); - date = g_time_val_to_iso8601( &now ); + vips_dbuf_init( &dbuf ); vips_dbuf_writef( &dbuf, "\n" ); vips_dbuf_writef( &dbuf, "\n", date ); - g_free( date ); vips_dbuf_writef( &dbuf, " \n" ); + g_free( date ); + for( i = 0; i < VIPS_NUMBER( scan_property_names ); i++ ) build_scan_property( &dbuf, image, scan_property_names[i][0], @@ -1245,6 +1362,7 @@ static GsfOutput * tile_name( Layer *layer, int x, int y ) { VipsForeignSaveDz *dz = layer->dz; + VipsForeignSave *save = (VipsForeignSave *) dz; GsfOutput *out; char name[VIPS_PATH_MAX]; @@ -1303,6 +1421,43 @@ tile_name( Layer *layer, int x, int y ) break; + case VIPS_FOREIGN_DZ_LAYOUT_IIIF: +{ + /* Tiles are addressed in full resolution coordinates, so + * scale up by layer->sub and dz->tile_size + * + * We always clip against the full-sized image, not the scaled + * up layer. + */ + int left = x * dz->tile_size * layer->sub; + int top = y * dz->tile_size * layer->sub; + int width = VIPS_MIN( dz->tile_size * layer->sub, + save->ready->Xsize - left ); + int height = VIPS_MIN( dz->tile_size * layer->sub, + save->ready->Ysize - top ); + + /* IIIF "size" is just real tile width, I think. + * + * TODO .. .is this right? shouldn't it be the smaller of + * width and height? + */ + int size = VIPS_MIN( dz->tile_size, + layer->width - x * dz->tile_size ); + + vips_snprintf( dirname, VIPS_PATH_MAX, "%d,%d,%d,%d", + left, top, width, height ); + vips_snprintf( dirname2, VIPS_PATH_MAX, "%d,", size ); + vips_snprintf( name, VIPS_PATH_MAX, "default%s", + dz->file_suffix ); + + /* "0" is rotation and is always 0. + */ + out = vips_gsf_path( dz->tree, + name, dirname, dirname2, "0", NULL ); +} + + break; + default: g_assert_not_reached(); @@ -1448,12 +1603,14 @@ strip_work( VipsThreadState *state, void *a ) g_mutex_unlock( vips__global_lock ); if( write_image( dz, out, x, dz->suffix ) ) { + g_object_unref( out ); g_object_unref( x ); return( -1 ); } g_object_unref( out ); + g_object_unref( x ); #ifdef DEBUG_VERBOSE printf( "strip_work: success\n" ); @@ -1562,7 +1719,7 @@ strip_shrink( Layer *layer ) #ifdef DEBUG printf( "strip_shrink: %d lines in layer %d to layer %d\n", from->valid.height, layer->n, below->n ); -#endif/*DEBUG*/ +#endif /*DEBUG*/ /* We may have an extra column of pixels on the right or * bottom that need filling: generate them. @@ -1644,7 +1801,7 @@ strip_arrived( Layer *layer ) #ifdef DEBUG printf( "strip_arrived: layer %d, strip at %d, height %d\n", layer->n, layer->y, layer->strip->valid.height ); -#endif/*DEBUG*/ +#endif /*DEBUG*/ if( strip_save( layer ) ) return( -1 ); @@ -1738,7 +1895,7 @@ pyramid_strip( VipsRegion *region, VipsRect *area, void *a ) #ifdef DEBUG printf( "pyramid_strip: strip at %d, height %d\n", area->top, area->height ); -#endif/*DEBUG*/ +#endif /*DEBUG*/ for(;;) { VipsRect *to = &layer->strip->valid; @@ -1794,7 +1951,7 @@ pyramid_strip( VipsRegion *region, VipsRect *area, void *a ) if( layer->write_y == layer->height ) { #ifdef DEBUG printf( "pyramid_strip: flushing ..\n" ); -#endif/*DEBUG*/ +#endif /*DEBUG*/ if( strip_flush( layer ) ) return( -1 ); @@ -1811,10 +1968,11 @@ vips_foreign_save_dz_build( VipsObject *object ) VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( dz ); VipsRect real_pixels; - /* Google and zoomify default to zero overlap, ".jpg". + /* Google, zoomify and iiif default to zero overlap, ".jpg". */ if( dz->layout == VIPS_FOREIGN_DZ_LAYOUT_ZOOMIFY || - dz->layout == VIPS_FOREIGN_DZ_LAYOUT_GOOGLE ) { + dz->layout == VIPS_FOREIGN_DZ_LAYOUT_GOOGLE || + dz->layout == VIPS_FOREIGN_DZ_LAYOUT_IIIF ) { if( !vips_object_argument_isset( object, "overlap" ) ) dz->overlap = 0; if( !vips_object_argument_isset( object, "suffix" ) ) @@ -1829,6 +1987,13 @@ vips_foreign_save_dz_build( VipsObject *object ) dz->tile_size = 256; } + /* Some iif writers default to 256, some to 512. We pick 512. + */ + if( dz->layout == VIPS_FOREIGN_DZ_LAYOUT_IIIF ) { + if( !vips_object_argument_isset( object, "tile_size" ) ) + dz->tile_size = 512; + } + /* skip_blanks defaults to 5 in google mode. */ if( dz->layout == VIPS_FOREIGN_DZ_LAYOUT_GOOGLE && @@ -1948,7 +2113,7 @@ vips_foreign_save_dz_build( VipsObject *object ) #ifdef DEBUG printf( "centre: centring within a %d x %d image\n", size, size ); -#endif +#endif /*DEBUG*/ } @@ -1961,7 +2126,7 @@ vips_foreign_save_dz_build( VipsObject *object ) dz->tile_margin ); printf( "vips_foreign_save_dz_build: tile_step == %d\n", dz->tile_step ); -#endif +#endif /*DEBUG*/ /* Build the skeleton of the image pyramid. */ @@ -2100,7 +2265,7 @@ vips_foreign_save_dz_build( VipsObject *object ) "using default compression" ) ); dz->compression = -1; } -#endif +#endif /*HAVE_GSF_DEFLATE_LEVEL*/ dz->tree = vips_gsf_tree_new( out2, dz->compression ); @@ -2133,6 +2298,11 @@ vips_foreign_save_dz_build( VipsObject *object ) return( -1 ); break; + case VIPS_FOREIGN_DZ_LAYOUT_IIIF: + if( write_json( dz ) ) + return( -1 ); + break; + default: g_assert_not_reached(); } @@ -2329,6 +2499,20 @@ vips_foreign_save_dz_class_init( VipsForeignSaveDzClass *class ) G_STRUCT_OFFSET( VipsForeignSaveDz, skip_blanks ), -1, 65535, -1 ); + VIPS_ARG_BOOL( class, "no_strip", 20, + _( "No strip" ), + _( "Don't strip tile metadata" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsForeignSaveDz, no_strip ), + FALSE ); + + VIPS_ARG_STRING( class, "id", 21, + _( "id" ), + _( "Resource ID" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsForeignSaveDz, id ), + "https://example.com/iiif" ); + /* How annoying. We stupidly had these in earlier versions. */ @@ -2556,9 +2740,9 @@ vips_foreign_save_dz_buffer_init( VipsForeignSaveDzBuffer *buffer ) * * Optional arguments: * - * * @basename: %gchar, base part of name + * * @basename: %gchar base part of name * * @layout: #VipsForeignDzLayout directory layout convention - * * @suffix: suffix for tile tiles + * * @suffix: %gchar suffix for tiles * * @overlap: %gint set tile overlap * * @tile_size: %gint set tile size * * @background: #VipsArrayDouble background colour @@ -2570,6 +2754,8 @@ vips_foreign_save_dz_buffer_init( VipsForeignSaveDzBuffer *buffer ) * * @compression: %gint zip deflate compression level * * @region_shrink: #VipsRegionShrink how to shrink each 2x2 region * * @skip_blanks: %gint skip tiles which are nearly equal to the background + * * @no_strip: %gboolean don't strip tiles + * * @id: %gchar id for IIIF properties * * Save an image as a set of tiles at various resolutions. By default dzsave * uses DeepZoom layout -- use @layout to pick other conventions. @@ -2609,6 +2795,10 @@ vips_foreign_save_dz_buffer_init( VipsForeignSaveDzBuffer *buffer ) * programs which wish to use fields from source files loaded via * vips_openslideload(). * + * By default, all tiles are stripped, since very few people want a copy of + * the metadata on every tile. Set @no_strip if you really want to keep + * metadata. + * * If @container is set to `zip`, you can set a compression level from -1 * (use zlib default), 0 (store, compression disabled) to 9 (max compression). * If no value is given, the default is to store files without compression. @@ -2621,6 +2811,9 @@ vips_foreign_save_dz_buffer_init( VipsForeignSaveDzBuffer *buffer ) * which are all within that many pixel values to the background are skipped. * This can save a lot of space for some image types. This option defaults to * 5 in Google layout mode, -1 otherwise. + * + * In IIIF layout, you can set the base of the `id` property in `info.json` + * with @id. The default is `https://example.com/iiif`. * * See also: vips_tiffsave(). * @@ -2648,9 +2841,9 @@ vips_dzsave( VipsImage *in, const char *name, ... ) * * Optional arguments: * - * * @basename: %gchar, base part of name + * * @basename: %gchar base part of name * * @layout: #VipsForeignDzLayout directory layout convention - * * @suffix: suffix for tile tiles + * * @suffix: %gchar suffix for tiles * * @overlap: %gint set tile overlap * * @tile_size: %gint set tile size * * @background: #VipsArrayDouble background colour @@ -2662,6 +2855,8 @@ vips_dzsave( VipsImage *in, const char *name, ... ) * * @compression: %gint zip deflate compression level * * @region_shrink: #VipsRegionShrink how to shrink each 2x2 region. * * @skip_blanks: %gint skip tiles which are nearly equal to the background + * * @no_strip: %gboolean don't strip tiles + * * @id: %gchar id for IIIF properties * * As vips_dzsave(), but save to a memory buffer. * diff --git a/libvips/foreign/exif.c b/libvips/foreign/exif.c index 12b2d448..6e6c442d 100644 --- a/libvips/foreign/exif.c +++ b/libvips/foreign/exif.c @@ -418,10 +418,8 @@ vips_image_resolution_from_exif( VipsImage *image, ExifData *ed ) switch( unit ) { case 1: - /* No unit ... just pass the fields straight to vips. + /* No units, instead xres / yres gives the pixel aspect ratio. */ - vips_image_set_string( image, - VIPS_META_RESOLUTION_UNIT, "none" ); break; case 2: @@ -525,7 +523,9 @@ vips__exif_parse( VipsImage *image ) int orientation; orientation = atoi( str ); - orientation = VIPS_CLIP( 1, orientation, 8 ); + if( orientation < 1 || + orientation > 8 ) + orientation = 1; vips_image_set_int( image, VIPS_META_ORIENTATION, orientation ); } diff --git a/libvips/foreign/fits.c b/libvips/foreign/fits.c index b3a74df7..73c9a584 100644 --- a/libvips/foreign/fits.c +++ b/libvips/foreign/fits.c @@ -326,7 +326,7 @@ vips_fits_get_header( VipsFits *fits, VipsImage *out ) if( format == VIPS_FORMAT_USHORT ) type = VIPS_INTERPRETATION_RGB16; else - type = VIPS_INTERPRETATION_RGB; + type = VIPS_INTERPRETATION_sRGB; } else type = VIPS_INTERPRETATION_MULTIBAND; diff --git a/libvips/foreign/foreign.c b/libvips/foreign/foreign.c index bb973383..ab8b9427 100644 --- a/libvips/foreign/foreign.c +++ b/libvips/foreign/foreign.c @@ -77,6 +77,7 @@ * @stability: Stable * @see_also: image * @include: vips/vips.h + * @title: VipsForeign * * This set of operations load and save images in a variety of formats. * @@ -457,6 +458,8 @@ vips_foreign_load_summary_class( VipsObjectClass *object_class, VipsBuf *buf ) vips_buf_appends( buf, ", is_a" ); if( class->is_a_buffer ) vips_buf_appends( buf, ", is_a_buffer" ); + if( class->is_a_source ) + vips_buf_appends( buf, ", is_a_source" ); if( class->get_flags ) vips_buf_appends( buf, ", get_flags" ); if( class->get_flags_filename ) @@ -478,6 +481,7 @@ static void * vips_foreign_find_load_sub( VipsForeignLoadClass *load_class, const char *filename ) { + VipsObjectClass *object_class = VIPS_OBJECT_CLASS( load_class ); VipsForeignClass *class = VIPS_FOREIGN_CLASS( load_class ); #ifdef DEBUG @@ -485,7 +489,9 @@ vips_foreign_find_load_sub( VipsForeignLoadClass *load_class, VIPS_OBJECT_CLASS( class )->nickname ); #endif /*DEBUG*/ - if( load_class->is_a ) { + if( load_class->is_a && + !vips_ispostfix( object_class->nickname, "_buffer" ) && + !vips_ispostfix( object_class->nickname, "_source" ) ) { if( load_class->is_a( filename ) ) return( load_class ); @@ -525,9 +531,16 @@ vips_foreign_find_load( const char *name ) vips__filename_split8( name, filename, option_string ); + /* Very common, so make a better error message for this case. + */ if( !vips_existsf( "%s", filename ) ) { vips_error( "VipsForeignLoad", - _( "file \"%s\" not found" ), name ); + _( "file \"%s\" does not exist" ), name ); + return( NULL ); + } + if( vips_isdirf( "%s", filename ) ) { + vips_error( "VipsForeignLoad", + _( "\"%s\" is a directory" ), name ); return( NULL ); } @@ -579,7 +592,10 @@ static void * vips_foreign_find_load_buffer_sub( VipsForeignLoadClass *load_class, const void **buf, size_t *len ) { + VipsObjectClass *object_class = VIPS_OBJECT_CLASS( load_class ); + if( load_class->is_a_buffer && + vips_ispostfix( object_class->nickname, "_buffer" ) && load_class->is_a_buffer( *buf, *len ) ) return( load_class ); @@ -619,6 +635,60 @@ vips_foreign_find_load_buffer( const void *data, size_t size ) return( G_OBJECT_CLASS_NAME( load_class ) ); } +/* Can this VipsForeign open this source? + */ +static void * +vips_foreign_find_load_source_sub( void *item, void *a, void *b ) +{ + VipsObjectClass *object_class = VIPS_OBJECT_CLASS( item ); + VipsForeignLoadClass *load_class = VIPS_FOREIGN_LOAD_CLASS( item ); + VipsSource *source = VIPS_SOURCE( a ); + + if( load_class->is_a_source && + vips_ispostfix( object_class->nickname, "_source" ) ) { + /* We may have done a read() rather than a sniff() in one of + * the is_a testers. Always rewind. + */ + (void) vips_source_rewind( source ); + + if( load_class->is_a_source( source ) ) + return( load_class ); + } + + return( NULL ); +} + +/** + * vips_foreign_find_load_source: + * @source: source to load from + * + * Searches for an operation you could use to load a source. To see the + * range of source loaders supported by your vips, try something like: + * + * vips -l | grep load_source + * + * See also: vips_image_new_from_source(). + * + * Returns: (transfer none): the name of an operation on success, %NULL on + * error. + */ +const char * +vips_foreign_find_load_source( VipsSource *source ) +{ + VipsForeignLoadClass *load_class; + + if( !(load_class = (VipsForeignLoadClass *) vips_foreign_map( + "VipsForeignLoad", + vips_foreign_find_load_source_sub, + source, NULL )) ) { + vips_error( "VipsForeignLoad", + "%s", _( "source is not in a known format" ) ); + return( NULL ); + } + + return( G_OBJECT_CLASS_NAME( load_class ) ); +} + /** * vips_foreign_is_a: * @loader: name of loader to use for test @@ -672,6 +742,32 @@ vips_foreign_is_a_buffer( const char *loader, const void *data, size_t size ) return( FALSE ); } +/** + * vips_foreign_is_a_source: + * @loader: name of loader to use for test + * @source: source to test + * + * Return %TRUE if @source can be loaded by @loader. @loader is something + * like "tiffload_source" or "VipsForeignLoadTiffSource". + * + * Returns: %TRUE if @data can be loaded by @source. + */ +gboolean +vips_foreign_is_a_source( const char *loader, VipsSource *source ) +{ + const VipsObjectClass *class; + VipsForeignLoadClass *load_class; + + if( !(class = vips_class_find( "VipsForeignLoad", loader )) ) + return( FALSE ); + load_class = VIPS_FOREIGN_LOAD_CLASS( class ); + if( load_class->is_a_source && + load_class->is_a_source( source ) ) + return( TRUE ); + + return( FALSE ); +} + /** * vips_foreign_flags: * @loader: name of loader to use for test @@ -1514,8 +1610,19 @@ vips__foreign_convert_saveable( VipsImage *in, VipsImage **ready, if( !vips_image_get_blob( in, VIPS_META_ICC_NAME, &data, &length ) && - !vips_icc_is_compatible_profile( in, data, length ) ) + !vips_icc_is_compatible_profile( in, data, length ) ) { + VipsImage *out; + + if( vips_copy( in, &out, NULL ) ) { + g_object_unref( in ); + return( -1 ); + } + g_object_unref( in ); + + in = out; + vips_image_remove( in, VIPS_META_ICC_NAME ); + } } *ready = in; @@ -1538,9 +1645,19 @@ vips_foreign_save_build( VipsObject *object ) save->background ) ) return( -1 ); - if( save->page_height ) + if( save->page_height ) { + VipsImage *x; + + if( vips_copy( ready, &x, NULL ) ) { + VIPS_UNREF( ready ); + return( -1 ); + } + VIPS_UNREF( ready ); + ready = x; + vips_image_set_int( ready, VIPS_META_PAGE_HEIGHT, save->page_height ); + } VIPS_UNREF( save->ready ); save->ready = ready; @@ -1628,7 +1745,7 @@ vips_foreign_save_class_init( VipsForeignSaveClass *class ) G_STRUCT_OFFSET( VipsForeignSave, background ), VIPS_TYPE_ARRAY_DOUBLE ); - VIPS_ARG_INT( class, "page_height", 8, + VIPS_ARG_INT( class, "page_height", 102, _( "Page height" ), _( "Set page height for multipage save" ), VIPS_ARGUMENT_OPTIONAL_INPUT, @@ -1642,19 +1759,26 @@ vips_foreign_save_init( VipsForeignSave *save ) save->background = vips_array_double_newv( 1, 0.0 ); } -/* Can we write this filename with this file? +/* Can we write this filename with this class? */ static void * vips_foreign_find_save_sub( VipsForeignSaveClass *save_class, const char *filename ) { + VipsObjectClass *object_class = VIPS_OBJECT_CLASS( save_class ); VipsForeignClass *class = VIPS_FOREIGN_CLASS( save_class ); - /* The suffs might be defined on an abstract base class, make sure we - * don't pick that. + /* All concrete savers needs suffs, since we use the suff to pick the + * saver. */ + if( !G_TYPE_IS_ABSTRACT( G_TYPE_FROM_CLASS( class ) ) && + !class->suffs ) + g_warning( "no suffix defined for %s", object_class->nickname ); + if( !G_TYPE_IS_ABSTRACT( G_TYPE_FROM_CLASS( class ) ) && class->suffs && + !vips_ispostfix( object_class->nickname, "_buffer" ) && + !vips_ispostfix( object_class->nickname, "_target" ) && vips_filename_suffix_match( filename, class->suffs ) ) return( save_class ); @@ -1791,6 +1915,64 @@ vips_foreign_save( VipsImage *in, const char *name, ... ) return( result ); } +/* Can this class write this filetype to a target? + */ +static void * +vips_foreign_find_save_target_sub( VipsForeignSaveClass *save_class, + const char *suffix ) +{ + VipsObjectClass *object_class = VIPS_OBJECT_CLASS( save_class ); + VipsForeignClass *class = VIPS_FOREIGN_CLASS( save_class ); + + /* All concrete savers needs suffs, since we use the suff to pick the + * saver. + */ + if( !G_TYPE_IS_ABSTRACT( G_TYPE_FROM_CLASS( class ) ) && + !class->suffs ) + g_warning( "no suffix defined for %s", object_class->nickname ); + + if( !G_TYPE_IS_ABSTRACT( G_TYPE_FROM_CLASS( class ) ) && + class->suffs && + vips_ispostfix( object_class->nickname, "_target" ) && + vips_filename_suffix_match( suffix, class->suffs ) ) + return( save_class ); + + return( NULL ); +} + +/** + * vips_foreign_find_save_target: + * @suffix: format to find a saver for + * + * Searches for an operation you could use to write to a target in @suffix + * format. + * + * See also: vips_image_write_to_buffer(). + * + * Returns: the name of an operation on success, %NULL on error + */ +const char * +vips_foreign_find_save_target( const char *name ) +{ + char suffix[VIPS_PATH_MAX]; + char option_string[VIPS_PATH_MAX]; + VipsForeignSaveClass *save_class; + + vips__filename_split8( name, suffix, option_string ); + + if( !(save_class = (VipsForeignSaveClass *) vips_foreign_map( + "VipsForeignSave", + (VipsSListMap2Fn) vips_foreign_find_save_target_sub, + (void *) suffix, NULL )) ) { + vips_error( "VipsForeignSave", + _( "\"%s\" is not a known target format" ), name ); + + return( NULL ); + } + + return( G_OBJECT_CLASS_NAME( save_class ) ); +} + /* Can we write this buffer with this file type? */ static void * @@ -1800,7 +1982,15 @@ vips_foreign_find_save_buffer_sub( VipsForeignSaveClass *save_class, VipsObjectClass *object_class = VIPS_OBJECT_CLASS( save_class ); VipsForeignClass *class = VIPS_FOREIGN_CLASS( save_class ); - if( class->suffs && + /* All concrete savers needs suffs, since we use the suff to pick the + * saver. + */ + if( !G_TYPE_IS_ABSTRACT( G_TYPE_FROM_CLASS( class ) ) && + !class->suffs ) + g_warning( "no suffix defined for %s", object_class->nickname ); + + if( !G_TYPE_IS_ABSTRACT( G_TYPE_FROM_CLASS( class ) ) && + class->suffs && vips_ispostfix( object_class->nickname, "_buffer" ) && vips_filename_suffix_match( suffix, class->suffs ) ) return( save_class ); @@ -1847,81 +2037,124 @@ vips_foreign_find_save_buffer( const char *name ) void vips_foreign_operation_init( void ) { - extern GType vips_foreign_load_rad_get_type( void ); + extern GType vips_foreign_load_rad_file_get_type( void ); + extern GType vips_foreign_load_rad_buffer_get_type( void ); + extern GType vips_foreign_load_rad_source_get_type( void ); extern GType vips_foreign_save_rad_file_get_type( void ); extern GType vips_foreign_save_rad_buffer_get_type( void ); + extern GType vips_foreign_save_rad_target_get_type( void ); + extern GType vips_foreign_load_mat_get_type( void ); - extern GType vips_foreign_load_ppm_get_type( void ); - extern GType vips_foreign_save_ppm_get_type( void ); - extern GType vips_foreign_load_png_get_type( void ); + + extern GType vips_foreign_load_ppm_file_get_type( void ); + extern GType vips_foreign_load_ppm_source_get_type( void ); + extern GType vips_foreign_save_ppm_file_get_type( void ); + extern GType vips_foreign_save_ppm_target_get_type( void ); + + extern GType vips_foreign_load_png_file_get_type( void ); extern GType vips_foreign_load_png_buffer_get_type( void ); + extern GType vips_foreign_load_png_source_get_type( void ); extern GType vips_foreign_save_png_file_get_type( void ); extern GType vips_foreign_save_png_buffer_get_type( void ); - extern GType vips_foreign_load_csv_get_type( void ); - extern GType vips_foreign_save_csv_get_type( void ); - extern GType vips_foreign_load_matrix_get_type( void ); - extern GType vips_foreign_save_matrix_get_type( void ); + extern GType vips_foreign_save_png_target_get_type( void ); + + extern GType vips_foreign_load_csv_file_get_type( void ); + extern GType vips_foreign_load_csv_source_get_type( void ); + extern GType vips_foreign_save_csv_file_get_type( void ); + extern GType vips_foreign_save_csv_target_get_type( void ); + + extern GType vips_foreign_load_matrix_file_get_type( void ); + extern GType vips_foreign_load_matrix_source_get_type( void ); + extern GType vips_foreign_save_matrix_file_get_type( void ); + extern GType vips_foreign_save_matrix_target_get_type( void ); extern GType vips_foreign_print_matrix_get_type( void ); + extern GType vips_foreign_load_fits_get_type( void ); extern GType vips_foreign_save_fits_get_type( void ); + extern GType vips_foreign_load_analyze_get_type( void ); + extern GType vips_foreign_load_openexr_get_type( void ); + extern GType vips_foreign_load_openslide_get_type( void ); + extern GType vips_foreign_load_jpeg_file_get_type( void ); extern GType vips_foreign_load_jpeg_buffer_get_type( void ); + extern GType vips_foreign_load_jpeg_source_get_type( void ); extern GType vips_foreign_save_jpeg_file_get_type( void ); extern GType vips_foreign_save_jpeg_buffer_get_type( void ); + extern GType vips_foreign_save_jpeg_target_get_type( void ); extern GType vips_foreign_save_jpeg_mime_get_type( void ); + extern GType vips_foreign_load_tiff_file_get_type( void ); extern GType vips_foreign_load_tiff_buffer_get_type( void ); + extern GType vips_foreign_load_tiff_source_get_type( void ); extern GType vips_foreign_save_tiff_file_get_type( void ); extern GType vips_foreign_save_tiff_buffer_get_type( void ); + extern GType vips_foreign_load_vips_get_type( void ); extern GType vips_foreign_save_vips_get_type( void ); + extern GType vips_foreign_load_raw_get_type( void ); extern GType vips_foreign_save_raw_get_type( void ); extern GType vips_foreign_save_raw_fd_get_type( 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_magick7_file_get_type( void ); extern GType vips_foreign_load_magick7_buffer_get_type( void ); extern GType vips_foreign_save_magick_file_get_type( void ); extern GType vips_foreign_save_magick_buffer_get_type( void ); + extern GType vips_foreign_save_dz_file_get_type( void ); extern GType vips_foreign_save_dz_buffer_get_type( void ); + extern GType vips_foreign_load_webp_file_get_type( void ); extern GType vips_foreign_load_webp_buffer_get_type( void ); + extern GType vips_foreign_load_webp_source_get_type( void ); extern GType vips_foreign_save_webp_file_get_type( void ); extern GType vips_foreign_save_webp_buffer_get_type( void ); - extern GType vips_foreign_load_pdf_get_type( void ); + extern GType vips_foreign_save_webp_target_get_type( void ); + extern GType vips_foreign_load_pdf_file_get_type( void ); extern GType vips_foreign_load_pdf_buffer_get_type( void ); - extern GType vips_foreign_load_svg_get_type( void ); + extern GType vips_foreign_load_pdf_source_get_type( void ); + extern GType vips_foreign_load_svg_file_get_type( void ); extern GType vips_foreign_load_svg_buffer_get_type( void ); - extern GType vips_foreign_load_heif_get_type( void ); + extern GType vips_foreign_load_svg_source_get_type( void ); + extern GType vips_foreign_load_heif_file_get_type( void ); extern GType vips_foreign_load_heif_buffer_get_type( void ); - extern GType vips_foreign_save_heif_get_type( void ); + extern GType vips_foreign_load_heif_source_get_type( void ); extern GType vips_foreign_save_heif_file_get_type( void ); extern GType vips_foreign_save_heif_buffer_get_type( void ); + extern GType vips_foreign_save_heif_target_get_type( void ); + extern GType vips_foreign_load_nifti_get_type( void ); extern GType vips_foreign_save_nifti_get_type( void ); - extern GType vips_foreign_load_gif_get_type( void ); + extern GType vips_foreign_load_gif_file_get_type( void ); extern GType vips_foreign_load_gif_buffer_get_type( void ); - extern GType vips_foreign_load_gifns_get_type( void ); + extern GType vips_foreign_load_gif_source_get_type( void ); extern GType vips_foreign_load_gifns_file_get_type( void ); extern GType vips_foreign_load_gifns_buffer_get_type( void ); - vips_foreign_load_csv_get_type(); - vips_foreign_save_csv_get_type(); - vips_foreign_load_matrix_get_type(); - vips_foreign_save_matrix_get_type(); + vips_foreign_load_csv_file_get_type(); + vips_foreign_load_csv_source_get_type(); + vips_foreign_save_csv_file_get_type(); + vips_foreign_save_csv_target_get_type(); + + vips_foreign_load_matrix_file_get_type(); + vips_foreign_load_matrix_source_get_type(); + vips_foreign_save_matrix_file_get_type(); + vips_foreign_save_matrix_target_get_type(); vips_foreign_print_matrix_get_type(); + vips_foreign_load_raw_get_type(); vips_foreign_save_raw_get_type(); vips_foreign_save_raw_fd_get_type(); + vips_foreign_load_vips_get_type(); vips_foreign_save_vips_get_type(); @@ -1930,42 +2163,48 @@ vips_foreign_operation_init( void ) #endif /*HAVE_ANALYZE*/ #ifdef HAVE_PPM - vips_foreign_load_ppm_get_type(); - vips_foreign_save_ppm_get_type(); + vips_foreign_load_ppm_file_get_type(); + vips_foreign_load_ppm_source_get_type(); + vips_foreign_save_ppm_file_get_type(); + vips_foreign_save_ppm_target_get_type(); #endif /*HAVE_PPM*/ #ifdef HAVE_RADIANCE - vips_foreign_load_rad_get_type(); + vips_foreign_load_rad_file_get_type(); + vips_foreign_load_rad_buffer_get_type(); + vips_foreign_load_rad_source_get_type(); vips_foreign_save_rad_file_get_type(); vips_foreign_save_rad_buffer_get_type(); + vips_foreign_save_rad_target_get_type(); #endif /*HAVE_RADIANCE*/ -#ifdef HAVE_POPPLER - vips_foreign_load_pdf_get_type(); +#if defined(HAVE_POPPLER) vips_foreign_load_pdf_file_get_type(); vips_foreign_load_pdf_buffer_get_type(); + vips_foreign_load_pdf_source_get_type(); #endif /*HAVE_POPPLER*/ #ifdef HAVE_PDFIUM - vips_foreign_load_pdf_get_type(); vips_foreign_load_pdf_file_get_type(); vips_foreign_load_pdf_buffer_get_type(); #endif /*HAVE_PDFIUM*/ #ifdef HAVE_RSVG - vips_foreign_load_svg_get_type(); vips_foreign_load_svg_file_get_type(); vips_foreign_load_svg_buffer_get_type(); + vips_foreign_load_svg_source_get_type(); #endif /*HAVE_RSVG*/ -#if defined(HAVE_GIFLIB) || defined(HAVE_LIBNSGIF) - vips_foreign_load_gif_get_type(); +#ifdef HAVE_GIFLIB vips_foreign_load_gif_file_get_type(); vips_foreign_load_gif_buffer_get_type(); - vips_foreign_load_gifns_get_type(); + vips_foreign_load_gif_source_get_type(); +#endif /*HAVE_GIFLIB*/ + +#ifdef HAVE_LIBNSGIF vips_foreign_load_gifns_file_get_type(); vips_foreign_load_gifns_buffer_get_type(); -#endif /*gif*/ +#endif /*HAVE_LIBNSGIF*/ #ifdef HAVE_GSF vips_foreign_save_dz_file_get_type(); @@ -1973,12 +2212,20 @@ vips_foreign_operation_init( void ) #endif /*HAVE_GSF*/ #ifdef HAVE_PNG - vips_foreign_load_png_get_type(); + vips_foreign_load_png_file_get_type(); vips_foreign_load_png_buffer_get_type(); + vips_foreign_load_png_source_get_type(); vips_foreign_save_png_file_get_type(); vips_foreign_save_png_buffer_get_type(); + vips_foreign_save_png_target_get_type(); #endif /*HAVE_PNG*/ +#ifdef HAVE_SPNG + vips_foreign_load_png_file_get_type(); + vips_foreign_load_png_buffer_get_type(); + vips_foreign_load_png_source_get_type(); +#endif /*HAVE_SPNG*/ + #ifdef HAVE_MATIO vips_foreign_load_mat_get_type(); #endif /*HAVE_MATIO*/ @@ -1986,21 +2233,26 @@ vips_foreign_operation_init( void ) #ifdef HAVE_JPEG vips_foreign_load_jpeg_file_get_type(); vips_foreign_load_jpeg_buffer_get_type(); + vips_foreign_load_jpeg_source_get_type(); vips_foreign_save_jpeg_file_get_type(); vips_foreign_save_jpeg_buffer_get_type(); + vips_foreign_save_jpeg_target_get_type(); vips_foreign_save_jpeg_mime_get_type(); #endif /*HAVE_JPEG*/ #ifdef HAVE_LIBWEBP vips_foreign_load_webp_file_get_type(); vips_foreign_load_webp_buffer_get_type(); + vips_foreign_load_webp_source_get_type(); vips_foreign_save_webp_file_get_type(); vips_foreign_save_webp_buffer_get_type(); + vips_foreign_save_webp_target_get_type(); #endif /*HAVE_LIBWEBP*/ #ifdef HAVE_TIFF vips_foreign_load_tiff_file_get_type(); vips_foreign_load_tiff_buffer_get_type(); + vips_foreign_load_tiff_source_get_type(); vips_foreign_save_tiff_file_get_type(); vips_foreign_save_tiff_buffer_get_type(); #endif /*HAVE_TIFF*/ @@ -2041,15 +2293,15 @@ vips_foreign_operation_init( void ) #endif /*HAVE_NIFTI*/ #ifdef HAVE_HEIF_DECODER - vips_foreign_load_heif_get_type(); vips_foreign_load_heif_file_get_type(); vips_foreign_load_heif_buffer_get_type(); + vips_foreign_load_heif_source_get_type(); #endif /*HAVE_HEIF_DECODER*/ #ifdef HAVE_HEIF_ENCODER - vips_foreign_save_heif_get_type(); vips_foreign_save_heif_file_get_type(); vips_foreign_save_heif_buffer_get_type(); + vips_foreign_save_heif_target_get_type(); #endif /*HAVE_HEIF_ENCODER*/ vips__foreign_load_operation = diff --git a/libvips/foreign/gifload.c b/libvips/foreign/gifload.c index 03115a80..6930a682 100644 --- a/libvips/foreign/gifload.c +++ b/libvips/foreign/gifload.c @@ -34,6 +34,11 @@ * - check image and frame bounds, since giflib does not * 1/9/19 * - improve early close again + * 30/1/19 + * - rework on top of VipsSource + * - add gifload_source + * 5/2/20 alon-ne + * - fix DISPOSE_BACKGROUND and DISPOSE_PREVIOUS */ /* @@ -108,6 +113,12 @@ #define DISPOSE_PREVIOUS 3 #endif + +#define NO_TRANSPARENT_INDEX -1 +#define TRANSPARENT_MASK 0x01 +#define DISPOSE_MASK 0x07 +#define DISPOSE_SHIFT 2 + #define VIPS_TYPE_FOREIGN_LOAD_GIF (vips_foreign_load_gif_get_type()) #define VIPS_FOREIGN_LOAD_GIF( obj ) \ (G_TYPE_CHECK_INSTANCE_CAST( (obj), \ @@ -115,10 +126,6 @@ #define VIPS_FOREIGN_LOAD_GIF_CLASS( klass ) \ (G_TYPE_CHECK_CLASS_CAST( (klass), \ VIPS_TYPE_FOREIGN_LOAD_GIF, VipsForeignLoadGifClass)) -#define VIPS_IS_FOREIGN_LOAD_GIF( obj ) \ - (G_TYPE_CHECK_INSTANCE_TYPE( (obj), VIPS_TYPE_FOREIGN_LOAD_GIF )) -#define VIPS_IS_FOREIGN_LOAD_GIF_CLASS( klass ) \ - (G_TYPE_CHECK_CLASS_TYPE( (klass), VIPS_TYPE_FOREIGN_LOAD_GIF )) #define VIPS_FOREIGN_LOAD_GIF_GET_CLASS( obj ) \ (G_TYPE_INSTANCE_GET_CLASS( (obj), \ VIPS_TYPE_FOREIGN_LOAD_GIF, VipsForeignLoadGifClass )) @@ -134,6 +141,10 @@ typedef struct _VipsForeignLoadGif { */ int n; + /* Load from this source (set by subclasses). + */ + VipsSource *source; + GifFileType *file; /* We decompress the whole thing to a huge RGBA memory image, and @@ -161,11 +172,15 @@ typedef struct _VipsForeignLoadGif { */ int n_pages; - /* A memory image the sized of one frame ... we accumulate to this as + /* A memory image the size of one frame ... we accumulate to this as * we scan the image, and copy lines to the output on generate. */ VipsImage *frame; + /* A scratch buffer the size of frame, used for rendering. + */ + VipsImage *scratch; + /* A copy of the previous frame, in case we need a DISPOSE_PREVIOUS. */ VipsImage *previous; @@ -194,7 +209,7 @@ typedef struct _VipsForeignLoadGif { /* As we scan the file, the index of the transparent pixel for this * frame. */ - int transparency; + int transparent_index; /* Params for DGifOpen(). Set by subclasses, called by base class in * _open(). @@ -203,17 +218,7 @@ typedef struct _VipsForeignLoadGif { } VipsForeignLoadGif; -typedef struct _VipsForeignLoadGifClass { - VipsForeignLoadClass parent_class; - - /* Close and reopen gif->file. - */ - int (*open)( VipsForeignLoadGif *gif ); - - /* Close any underlying file resource. - */ - void (*close)( VipsForeignLoadGif *gif ); -} VipsForeignLoadGifClass; +typedef VipsForeignLoadClass VipsForeignLoadGifClass; G_DEFINE_ABSTRACT_TYPE( VipsForeignLoadGif, vips_foreign_load_gif, VIPS_TYPE_FOREIGN_LOAD ); @@ -307,16 +312,110 @@ vips_foreign_load_gif_error( VipsForeignLoadGif *gif ) vips_foreign_load_gif_error_vips( gif, error ); } +/* Shut down giflib plus any underlying file resource. + */ +static int +vips_foreign_load_gif_close_giflib( VipsForeignLoadGif *gif ) +{ + VIPS_DEBUG_MSG( "vips_foreign_load_gif_close_giflib:\n" ); + +#ifdef HAVE_GIFLIB_5 + if( gif->file ) { + int error; + + if( DGifCloseFile( gif->file, &error ) == GIF_ERROR ) { + vips_foreign_load_gif_error_vips( gif, error ); + gif->file = NULL; + + return( -1 ); + } + gif->file = NULL; + } +#else + if( gif->file ) { + if( DGifCloseFile( gif->file ) == GIF_ERROR ) { + vips_foreign_load_gif_error_vips( gif, GifLastError() ); + gif->file = NULL; + + return( -1 ); + } + gif->file = NULL; + } +#endif + + if( gif->source ) + vips_source_minimise( gif->source ); + + return( 0 ); +} + +/* Callback from the gif loader. + * + * Read up to len bytes into buffer, return number of bytes read, 0 for EOF. + */ +static int +vips_giflib_read( GifFileType *file, GifByteType *buf, int n ) +{ + VipsForeignLoadGif *gif = (VipsForeignLoadGif *) file->UserData; + + gint64 read; + + read = vips_source_read( gif->source, buf, n ); + if( read == 0 ) + gif->eof = TRUE; + + return( (int) read ); +} + +/* Open any underlying file resource, then giflib. + */ +static int +vips_foreign_load_gif_open_giflib( VipsForeignLoadGif *gif ) +{ + VIPS_DEBUG_MSG( "vips_foreign_load_gif_open_giflib:\n" ); + + g_assert( !gif->file ); + + /* Must always rewind before opening giflib again. + */ + vips_source_rewind( gif->source ); + +#ifdef HAVE_GIFLIB_5 +{ + int error; + + if( !(gif->file = DGifOpen( gif, vips_giflib_read, &error )) ) { + vips_foreign_load_gif_error_vips( gif, error ); + (void) vips_foreign_load_gif_close_giflib( gif ); + return( -1 ); + } +} +#else + if( !(gif->file = DGifOpen( gif, vips_giflib_read )) ) { + vips_foreign_load_gif_error_vips( gif, GifLastError() ); + (void) vips_foreign_load_gif_close_giflib( gif ); + return( -1 ); + } +#endif + + gif->eof = FALSE; + gif->current_page = 0; + + return( 0 ); +} + static void vips_foreign_load_gif_dispose( GObject *gobject ) { VipsForeignLoadGif *gif = (VipsForeignLoadGif *) gobject; - VipsForeignLoadGifClass *class = - (VipsForeignLoadGifClass *) VIPS_OBJECT_GET_CLASS( gif ); - class->close( gif ); + VIPS_DEBUG_MSG( "vips_foreign_load_gif_dispose:\n" ); + vips_foreign_load_gif_close_giflib( gif ); + + VIPS_UNREF( gif->source ); VIPS_UNREF( gif->frame ); + VIPS_UNREF( gif->scratch ); VIPS_UNREF( gif->previous ); VIPS_FREE( gif->comment ); VIPS_FREE( gif->line ); @@ -339,30 +438,18 @@ vips_foreign_load_gif_get_flags( VipsForeignLoad *load ) } static gboolean -vips_foreign_load_gif_is_a_buffer( const void *buf, size_t len ) +vips_foreign_load_gif_is_a_source( VipsSource *source ) { - const guchar *str = (const guchar *) buf; + const unsigned char *data; - if( len >= 4 && - str[0] == 'G' && - str[1] == 'I' && - str[2] == 'F' && - str[3] == '8' ) - return( 1 ); + if( (data = vips_source_sniff( source, 4 )) && + data[0] == 'G' && + data[1] == 'I' && + data[2] == 'F' && + data[3] == '8' ) + return( TRUE ); - return( 0 ); -} - -static gboolean -vips_foreign_load_gif_is_a( const char *filename ) -{ - unsigned char buf[4]; - - if( vips__get_bytes( filename, buf, 4 ) == 4 && - vips_foreign_load_gif_is_a_buffer( buf, 4 ) ) - return( 1 ); - - return( 0 ); + return( FALSE ); } /* Make sure delays is allocated and large enough. @@ -375,7 +462,7 @@ vips_foreign_load_gif_allocate_delays( VipsForeignLoadGif *gif ) int i; gif->delays_length = gif->delays_length + gif->n_pages + 64; - gif->delays = (int *) g_realloc( gif->delays, + gif->delays = (int *) g_realloc( gif->delays, gif->delays_length * sizeof( int ) ); for( i = old; i < gif->delays_length; i++ ) gif->delays[i] = 40; @@ -415,7 +502,7 @@ vips_foreign_load_gif_code_next( VipsForeignLoadGif *gif, /* Quickly scan an image record. */ static int -vips_foreign_load_gif_scan_image( VipsForeignLoadGif *gif ) +vips_foreign_load_gif_scan_image( VipsForeignLoadGif *gif ) { VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( gif ); GifFileType *file = gif->file; @@ -424,7 +511,7 @@ vips_foreign_load_gif_scan_image( VipsForeignLoadGif *gif ) GifByteType *extension; if( DGifGetImageDesc( gif->file ) == GIF_ERROR ) { - vips_foreign_load_gif_error( gif ); + vips_foreign_load_gif_error( gif ); return( -1 ); } @@ -478,7 +565,10 @@ vips_foreign_load_gif_scan_application_ext( VipsForeignLoadGif *gif, */ have_netscape = FALSE; if( extension[0] == 11 && - vips_isprefix( "NETSCAPE2.0", (const char*) (extension + 1) ) ) + (vips_isprefix( "NETSCAPE2.0", + (const char*) (extension + 1) ) || + vips_isprefix( "ANIMEXTS1.0", + (const char*) (extension + 1) )) ) have_netscape = TRUE; while( extension != NULL ) { @@ -488,8 +578,11 @@ vips_foreign_load_gif_scan_application_ext( VipsForeignLoadGif *gif, if( have_netscape && extension && extension[0] == 3 && - extension[1] == 1 ) - gif->loop = extension[2] | (extension[3] << 8); + extension[1] == 1 ) { + gif->loop = extension[2] | (extension[3] << 8); + if( gif->loop != 0 ) + gif->loop += 1; + } } return( 0 ); @@ -534,14 +627,14 @@ vips_foreign_load_gif_scan_extension( VipsForeignLoadGif *gif ) switch( ext_code ) { case GRAPHICS_EXT_FUNC_CODE: if( extension[0] == 4 && - extension[1] & 0x1 ) { + extension[1] & TRANSPARENT_MASK ) { VIPS_DEBUG_MSG( "gifload: has transp.\n" ); gif->has_transparency = TRUE; } /* giflib uses centiseconds, we use ms. */ - gif->delays[gif->n_pages] = + gif->delays[gif->n_pages] = (extension[2] | (extension[3] << 8)) * 10; while( extension != NULL ) @@ -592,16 +685,24 @@ vips_foreign_load_gif_set_header( VipsForeignLoadGif *gif, VipsImage *image ) vips_image_set_int( image, VIPS_META_PAGE_HEIGHT, gif->file->SHeight ); vips_image_set_int( image, VIPS_META_N_PAGES, gif->n_pages ); - vips_image_set_int( image, "gif-loop", gif->loop ); + vips_image_set_int( image, "loop", gif->loop ); + + /* DEPRECATED "gif-loop" + * + * Not the correct behavior as loop=1 became gif-loop=0 + * but we want to keep the old behavior untouched! + */ + vips_image_set_int( image, + "gif-loop", gif->loop == 0 ? 0 : gif->loop - 1 ); if( gif->delays ) { /* The deprecated gif-delay field is in centiseconds. */ vips_image_set_int( image, "gif-delay", VIPS_RINT( gif->delays[0] / 10.0 ) ); - vips_image_set_array_int( image, + vips_image_set_array_int( image, "delay", gif->delays, gif->n_pages ); - } + } else vips_image_set_int( image, "gif-delay", 4 ); @@ -623,10 +724,12 @@ vips_foreign_load_gif_scan( VipsForeignLoadGif *gif ) GifRecordType record; + VIPS_DEBUG_MSG( "vips_foreign_load_gif_scan:\n" ); + gif->n_pages = 0; do { - if( DGifGetRecordType( gif->file, &record ) == GIF_ERROR ) + if( DGifGetRecordType( gif->file, &record ) == GIF_ERROR ) continue; switch( record ) { @@ -675,24 +778,39 @@ vips_foreign_load_gif_scan( VipsForeignLoadGif *gif ) static int vips_foreign_load_gif_header( VipsForeignLoad *load ) { - VipsForeignLoadGifClass *class = - (VipsForeignLoadGifClass *) VIPS_OBJECT_GET_CLASS( load ); - VipsForeignLoadGif *gif = (VipsForeignLoadGif *) load; + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( load ); + VipsForeignLoadGif *gif = VIPS_FOREIGN_LOAD_GIF( load ); - if( class->open( gif ) ) + VIPS_DEBUG_MSG( "vips_foreign_load_gif_header: %p\n", gif ); + + if( vips_foreign_load_gif_open_giflib( gif ) ) return( -1 ); - if( vips_foreign_load_gif_scan( gif ) ) { - class->close( gif ); + /* giflib does no checking of image dimensions, not even for 0. + */ + if( gif->file->SWidth <= 0 || + gif->file->SWidth > VIPS_MAX_COORD || + gif->file->SHeight <= 0 || + gif->file->SHeight > VIPS_MAX_COORD ) { + vips_error( class->nickname, + "%s", _( "image size out of bounds" ) ); + (void) vips_foreign_load_gif_close_giflib( gif ); + return( -1 ); } - if( vips_foreign_load_gif_set_header( gif, load->out ) ) { - class->close( gif ); + /* Allocate a line buffer now that we have the GIF width. + */ + if( !(gif->line = + VIPS_ARRAY( NULL, gif->file->SWidth, GifPixelType )) || + vips_foreign_load_gif_scan( gif ) || + vips_foreign_load_gif_set_header( gif, load->out ) ) { + (void) vips_foreign_load_gif_close_giflib( gif ); + return( -1 ); } - class->close( gif ); + (void) vips_foreign_load_gif_close_giflib( gif ); return( 0 ); } @@ -727,28 +845,18 @@ vips_foreign_load_gif_build_cmap( VipsForeignLoadGif *gif ) } static void -vips_foreign_load_gif_render_line( VipsForeignLoadGif *gif, - int width, VipsPel * restrict q, VipsPel * restrict p ) +vips_foreign_load_gif_render_line( VipsForeignLoadGif *gif, + int width, VipsPel * restrict dst, VipsPel * restrict src ) { - guint32 *iq; + guint32 * restrict idst = (guint32 *) dst; + int x; - iq = (guint32 *) q; for( x = 0; x < width; x++ ) { - VipsPel v = p[x]; + VipsPel v = src[x]; - if( v == gif->transparency ) { - /* In DISPOSE_DO_NOT mode, the previous frame shows - * through (ie. we do nothing). In all other modes, - * it's just transparent. - */ - if( gif->dispose != DISPOSE_DO_NOT ) - iq[x] = 0; - } - else - /* Blast in the RGBA for this value. - */ - iq[x] = gif->cmap[v]; + if( v != gif->transparent_index ) + idst[x] = gif->cmap[v]; } } @@ -765,31 +873,6 @@ vips_foreign_load_gif_render( VipsForeignLoadGif *gif ) return( -1 ); } - /* Update the colour map for this frame. - */ - vips_foreign_load_gif_build_cmap( gif ); - - /* BACKGROUND means we reset the frame to 0 (transparent) before we - * render the next set of pixels. - */ - if( gif->dispose == DISPOSE_BACKGROUND ) - memset( VIPS_IMAGE_ADDR( gif->frame, 0, 0 ), 0, - VIPS_IMAGE_SIZEOF_IMAGE( gif->frame ) ); - - /* PREVIOUS means we init the frame with the frame before last, ie. we - * undo the last render. - * - * Anything other than PREVIOUS, we must update the previous buffer, - */ - if( gif->dispose == DISPOSE_PREVIOUS ) - memcpy( VIPS_IMAGE_ADDR( gif->frame, 0, 0 ), - VIPS_IMAGE_ADDR( gif->previous, 0, 0 ), - VIPS_IMAGE_SIZEOF_IMAGE( gif->frame ) ); - else - memcpy( VIPS_IMAGE_ADDR( gif->previous, 0, 0 ), - VIPS_IMAGE_ADDR( gif->frame, 0, 0 ), - VIPS_IMAGE_SIZEOF_IMAGE( gif->frame ) ); - /* giflib does not check that the Left / Top / Width / Height for this * Image is inside the canvas. * @@ -810,8 +893,26 @@ vips_foreign_load_gif_render( VipsForeignLoadGif *gif ) "out of bounds frame of %d x %d pixels at %d x %d\n", file->Image.Width, file->Image.Height, file->Image.Left, file->Image.Top ); + + /* Don't flag an error -- many GIFs have this problem. + */ + return( 0 ); } - else if( file->Image.Interlace ) { + + /* Update the colour map for this frame. + */ + vips_foreign_load_gif_build_cmap( gif ); + + /* PREVIOUS means we init the frame with the last un-disposed frame. + * So the last un-disposed frame is used as a backdrop for the new + * frame. + */ + if( gif->dispose == DISPOSE_PREVIOUS ) + memcpy( VIPS_IMAGE_ADDR( gif->scratch, 0, 0 ), + VIPS_IMAGE_ADDR( gif->previous, 0, 0 ), + VIPS_IMAGE_SIZEOF_IMAGE( gif->scratch ) ); + + if( file->Image.Interlace ) { int i; VIPS_DEBUG_MSG( "vips_foreign_load_gif_render: " @@ -822,20 +923,21 @@ vips_foreign_load_gif_render( VipsForeignLoadGif *gif ) for( i = 0; i < 4; i++ ) { int y; - for( y = InterlacedOffset[i]; - y < file->Image.Height; - y += InterlacedJumps[i] ) - if( DGifGetLine( gif->file, gif->line, - file->Image.Width ) == GIF_ERROR ) { - VipsPel *q = VIPS_IMAGE_ADDR( - gif->frame, - file->Image.Left, - file->Image.Top + y ); + for( y = InterlacedOffset[i]; y < file->Image.Height; + y += InterlacedJumps[i] ) { + VipsPel *dst = VIPS_IMAGE_ADDR( gif->scratch, + file->Image.Left, file->Image.Top + y ); - vips_foreign_load_gif_render_line( gif, - file->Image.Width, q, - gif->line ); + if( DGifGetLine( gif->file, + gif->line, file->Image.Width ) == + GIF_ERROR ) { + vips_foreign_load_gif_error( gif ); + return( -1 ); } + + vips_foreign_load_gif_render_line( gif, + file->Image.Width, dst, gif->line ); + } } } else { @@ -846,20 +948,84 @@ vips_foreign_load_gif_render( VipsForeignLoadGif *gif ) file->Image.Width, file->Image.Height, file->Image.Left, file->Image.Top ); - for( y = 0; y < file->Image.Height; y++ ) - if( DGifGetLine( gif->file, gif->line, - file->Image.Width ) != GIF_ERROR ) { - VipsPel *q = VIPS_IMAGE_ADDR( gif->frame, - file->Image.Left, file->Image.Top + y ); + for( y = 0; y < file->Image.Height; y++ ) { + VipsPel *dst = VIPS_IMAGE_ADDR( gif->scratch, + file->Image.Left, file->Image.Top + y ); - vips_foreign_load_gif_render_line( gif, - file->Image.Width, q, gif->line ); + if( DGifGetLine( gif->file, + gif->line, file->Image.Width ) == GIF_ERROR ) { + vips_foreign_load_gif_error( gif ); + return( -1 ); } + + vips_foreign_load_gif_render_line( gif, + file->Image.Width, dst, gif->line ); + } } + /* Copy the result to frame, which then is picked up from outside + */ + memcpy( VIPS_IMAGE_ADDR( gif->frame, 0, 0 ), + VIPS_IMAGE_ADDR( gif->scratch, 0, 0 ), + VIPS_IMAGE_SIZEOF_IMAGE( gif->frame ) ); + + if( gif->dispose == DISPOSE_BACKGROUND ) { + /* BACKGROUND means we reset the frame to transparent before we + * render the next set of pixels. + */ + guint32 *q = (guint32 *) VIPS_IMAGE_ADDR( gif->scratch, + file->Image.Left, file->Image.Top ); + + /* What we write for transparent pixels. We want RGB to be + * 255, and A to be 0. + */ + guint32 ink = GUINT32_TO_BE( 0xffffff00 ); + + int x, y; + + /* Generate the first line a pixel at a time, memcpy() for + * subsequent lines. + */ + if( file->Image.Height > 0 ) + for( x = 0; x < file->Image.Width; x++ ) + q[x] = ink; + + for( y = 1; y < file->Image.Height; y++ ) + memcpy( q + gif->scratch->Xsize * y, + q, + file->Image.Width * sizeof( guint32 ) ); + } + else if( gif->dispose == DISPOSAL_UNSPECIFIED || + gif->dispose == DISPOSE_DO_NOT ) + /* Copy the frame to previous, so it can be restored if + * DISPOSE_PREVIOUS is specified in a later frame. + */ + memcpy( VIPS_IMAGE_ADDR( gif->previous, 0, 0 ), + VIPS_IMAGE_ADDR( gif->frame, 0, 0 ), + VIPS_IMAGE_SIZEOF_IMAGE( gif->previous ) ); + + /* Reset values, as Graphic Control Extension is optional + */ + gif->dispose = DISPOSAL_UNSPECIFIED; + gif->transparent_index = NO_TRANSPARENT_INDEX; + return( 0 ); } +#ifdef VIPS_DEBUG +static const char * +dispose2str( int dispose ) +{ + switch( dispose ) { + case DISPOSAL_UNSPECIFIED: return( "DISPOSAL_UNSPECIFIED" ); + case DISPOSE_DO_NOT: return( "DISPOSE_DO_NOT" ); + case DISPOSE_BACKGROUND: return( "DISPOSE_BACKGROUND" ); + case DISPOSE_PREVIOUS: return( "DISPOSE_PREVIOUS" ); + default: return( "" ); + } +} +#endif /*VIPS_DEBUG*/ + static int vips_foreign_load_gif_extension( VipsForeignLoadGif *gif ) { @@ -877,20 +1043,23 @@ vips_foreign_load_gif_extension( VipsForeignLoadGif *gif ) if( extension && ext_code == GRAPHICS_EXT_FUNC_CODE && extension[0] == 4 ) { - /* Bytes are flags, delay low, delay high, - * transparency. Flag bit 1 means transparency - * is being set. + int flags = extension[1]; + + /* Bytes are flags, delay low, delay high, transparency. + * Flag bit 1 means transparency is being set. */ - gif->transparency = -1; - if( extension[1] & 0x1 ) - gif->transparency = extension[4]; + gif->transparent_index = (flags & TRANSPARENT_MASK) ? + extension[4] : NO_TRANSPARENT_INDEX; + VIPS_DEBUG_MSG( "vips_foreign_load_gif_extension: " + "transparency = %d\n", gif->transparent_index ); /* Set the current dispose mode. This is read during frame load * to set the meaning of background and transparent pixels. */ - gif->dispose = (extension[1] >> 2) & 0x7; + gif->dispose = (flags >> DISPOSE_SHIFT) & DISPOSE_MASK; + VIPS_DEBUG_MSG( "vips_foreign_load_gif_extension: " - "dispose = %d\n", gif->dispose ); + "dispose = %s\n", dispose2str( gif->dispose ) ); } while( extension != NULL ) @@ -910,8 +1079,10 @@ vips_foreign_load_gif_next_page( VipsForeignLoadGif *gif ) have_read_frame = FALSE; do { - if( DGifGetRecordType( gif->file, &record ) == GIF_ERROR ) - continue; + if( DGifGetRecordType( gif->file, &record ) == GIF_ERROR ) { + vips_foreign_load_gif_error( gif ); + return( -1 ); + } switch( record ) { case IMAGE_DESC_RECORD_TYPE: @@ -959,11 +1130,18 @@ static int vips_foreign_load_gif_generate( VipsRegion *or, void *seq, void *a, void *b, gboolean *stop ) { - VipsRect *r = &or->valid; + VipsRect *r = &or->valid; VipsForeignLoadGif *gif = (VipsForeignLoadGif *) a; int y; +#ifdef DEBUG_VERBOSE + printf( "vips_foreign_load_gif_generate: %p " + "left = %d, top = %d, width = %d, height = %d\n", + gif, + r->left, r->top, r->width, r->height ); +#endif /*DEBUG_VERBOSE*/ + for( y = 0; y < r->height; y++ ) { /* The page for this output line, and the line number in page. */ @@ -1038,45 +1216,43 @@ vips_foreign_load_gif_generate( VipsRegion *or, static void vips_foreign_load_gif_minimise( VipsObject *object, VipsForeignLoadGif *gif ) { - VipsForeignLoadGifClass *class = - (VipsForeignLoadGifClass *) VIPS_OBJECT_GET_CLASS( gif ); + vips_source_minimise( gif->source ); +} - class->close( gif ); +static VipsImage * +vips_foreign_load_gif_temp( VipsForeignLoadGif *gif ) +{ + VipsImage *temp; + + temp = vips_image_new_memory(); + vips_image_init_fields( temp, + gif->file->SWidth, gif->file->SHeight, 4, VIPS_FORMAT_UCHAR, + VIPS_CODING_NONE, VIPS_INTERPRETATION_sRGB, 1.0, 1.0 ); + if( vips_image_write_prepare( temp ) ) { + VIPS_UNREF( temp ); + return( NULL ); + } + + return( temp ); } static int vips_foreign_load_gif_load( VipsForeignLoad *load ) { - VipsForeignLoadGifClass *class = - (VipsForeignLoadGifClass *) VIPS_OBJECT_GET_CLASS( load ); - VipsForeignLoadGif *gif = (VipsForeignLoadGif *) load; + VipsForeignLoadGif *gif = VIPS_FOREIGN_LOAD_GIF( load ); VipsImage **t = (VipsImage **) vips_object_local_array( VIPS_OBJECT( load ), 4 ); - VIPS_DEBUG_MSG( "vips_foreign_load_gif_load:\n" ); + VIPS_DEBUG_MSG( "vips_foreign_load_gif_load: %p\n", gif ); - if( class->open( gif ) ) + if( vips_foreign_load_gif_open_giflib( gif ) ) return( -1 ); - /* Make the memory image we accumulate pixels in. We always accumulate - * to RGBA, then trim down to whatever the output image needs on - * _generate. + /* Set of temp images we use during rendering. */ - gif->frame = vips_image_new_memory(); - vips_image_init_fields( gif->frame, - gif->file->SWidth, gif->file->SHeight, 4, VIPS_FORMAT_UCHAR, - VIPS_CODING_NONE, VIPS_INTERPRETATION_sRGB, 1.0, 1.0 ); - if( vips_image_write_prepare( gif->frame ) ) - return( -1 ); - - /* A copy of the previous state of the frame, in case we have to - * process a DISPOSE_PREVIOUS. - */ - gif->previous = vips_image_new_memory(); - vips_image_init_fields( gif->previous, - gif->file->SWidth, gif->file->SHeight, 4, VIPS_FORMAT_UCHAR, - VIPS_CODING_NONE, VIPS_INTERPRETATION_sRGB, 1.0, 1.0 ); - if( vips_image_write_prepare( gif->previous ) ) + if( !(gif->frame = vips_foreign_load_gif_temp( gif )) || + !(gif->scratch = vips_foreign_load_gif_temp( gif )) || + !(gif->previous = vips_foreign_load_gif_temp( gif )) ) return( -1 ); /* Make the output pipeline. @@ -1087,8 +1263,8 @@ vips_foreign_load_gif_load( VipsForeignLoad *load ) /* Close input immediately at end of read. */ - g_signal_connect( t[0], "minimise", - G_CALLBACK( vips_foreign_load_gif_minimise ), gif ); + g_signal_connect( t[0], "minimise", + G_CALLBACK( vips_foreign_load_gif_minimise ), gif ); /* Strips 8 pixels high to avoid too many tiny regions. */ @@ -1103,90 +1279,22 @@ vips_foreign_load_gif_load( VipsForeignLoad *load ) return( 0 ); } -static int -vips_foreign_load_gif_open( VipsForeignLoadGif *gif ) -{ - VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( gif ); - -#ifdef HAVE_GIFLIB_5 -{ - int error; - - if( !(gif->file = DGifOpen( gif, gif->read_func, &error )) ) { - vips_foreign_load_gif_error_vips( gif, error ); - return( -1 ); - } -} -#else - if( !(gif->file = DGifOpen( gif, gif->read_func )) ) { - vips_foreign_load_gif_error_vips( gif, GifLastError() ); - return( -1 ); - } -#endif - - gif->eof = FALSE; - gif->current_page = 0; - - /* giflib does no checking of image dimensions, not even for 0. - */ - if( gif->file->SWidth <= 0 || - gif->file->SWidth > VIPS_MAX_COORD || - gif->file->SHeight <= 0 || - gif->file->SHeight > VIPS_MAX_COORD ) { - vips_error( class->nickname, - "%s", _( "image size out of bounds" ) ); - return( -1 ); - } - - /* Allocate a line buffer now that we have the GIF width. - */ - VIPS_FREE( gif->line ) - if( !(gif->line = VIPS_ARRAY( NULL, gif->file->SWidth, GifPixelType )) ) - return( -1 ); - - return( 0 ); -} - -static void -vips_foreign_load_gif_close( VipsForeignLoadGif *gif ) -{ -#ifdef HAVE_GIFLIB_5 - if( gif->file ) { - int error; - - if( DGifCloseFile( gif->file, &error ) == GIF_ERROR ) - vips_foreign_load_gif_error_vips( gif, error ); - gif->file = NULL; - } -#else - if( gif->file ) { - if( DGifCloseFile( gif->file ) == GIF_ERROR ) - vips_foreign_load_gif_error_vips( gif, GifLastError() ); - gif->file = NULL; - } -#endif -} - static void vips_foreign_load_gif_class_init( VipsForeignLoadGifClass *class ) { GObjectClass *gobject_class = G_OBJECT_CLASS( class ); VipsObjectClass *object_class = (VipsObjectClass *) class; VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class; - VipsForeignLoadGifClass *gif_class = (VipsForeignLoadGifClass *) class; gobject_class->dispose = vips_foreign_load_gif_dispose; gobject_class->set_property = vips_object_set_property; gobject_class->get_property = vips_object_get_property; - gif_class->open = vips_foreign_load_gif_open; - gif_class->close = vips_foreign_load_gif_close; - load_class->header = vips_foreign_load_gif_header; - load_class->load = vips_foreign_load_gif_load; - object_class->nickname = "gifload_base"; object_class->description = _( "load GIF with giflib" ); + load_class->header = vips_foreign_load_gif_header; + load_class->load = vips_foreign_load_gif_load; load_class->get_flags_filename = vips_foreign_load_gif_get_flags_filename; load_class->get_flags = vips_foreign_load_gif_get_flags; @@ -1211,12 +1319,12 @@ static void vips_foreign_load_gif_init( VipsForeignLoadGif *gif ) { gif->n = 1; - gif->transparency = -1; + gif->transparent_index = NO_TRANSPARENT_INDEX; gif->delays = NULL; gif->delays_length = 0; - gif->loop = 0; + gif->loop = 1; gif->comment = NULL; - gif->dispose = 0; + gif->dispose = DISPOSAL_UNSPECIFIED; vips_foreign_load_gif_allocate_delays( gif ); } @@ -1228,10 +1336,6 @@ typedef struct _VipsForeignLoadGifFile { */ char *filename; - /* The FILE* we read from. - */ - FILE *fp; - } VipsForeignLoadGifFile; typedef VipsForeignLoadGifClass VipsForeignLoadGifFileClass; @@ -1239,83 +1343,43 @@ typedef VipsForeignLoadGifClass VipsForeignLoadGifFileClass; G_DEFINE_TYPE( VipsForeignLoadGifFile, vips_foreign_load_gif_file, vips_foreign_load_gif_get_type() ); -/* Our input function for file open. We can't use DGifOpenFileName(), since - * that just calls open() and won't work with unicode on win32. We can't use - * DGifOpenFileHandle() since that's an fd from open() and you can't pass those - * across DLL boundaries on Windows. - */ -static int -vips_giflib_file_read( GifFileType *gfile, GifByteType *buffer, int n ) -{ - VipsForeignLoadGif *gif = (VipsForeignLoadGif *) gfile->UserData; - VipsForeignLoadGifFile *file = (VipsForeignLoadGifFile *) gif; - - if( feof( file->fp ) ) - gif->eof = TRUE; - - return( (int) fread( (void *) buffer, 1, n, file->fp ) ); -} - static int -vips_foreign_load_gif_file_header( VipsForeignLoad *load ) +vips_foreign_load_gif_file_build( VipsObject *object ) { - VipsForeignLoadGif *gif = (VipsForeignLoadGif *) load; - VipsForeignLoadGifClass *class = - (VipsForeignLoadGifClass *) VIPS_OBJECT_GET_CLASS( load ); + VipsForeignLoadGif *gif = (VipsForeignLoadGif *) object; + VipsForeignLoadGifFile *file = (VipsForeignLoadGifFile *) object; - if( VIPS_FOREIGN_LOAD_CLASS( - vips_foreign_load_gif_file_parent_class )->header( load ) ) { - /* Close early if header read fails in our base class. - */ - class->close( gif ); + if( file->filename ) + if( !(gif->source = + vips_source_new_from_file( file->filename )) ) + return( -1 ); + + if( VIPS_OBJECT_CLASS( vips_foreign_load_gif_file_parent_class )-> + build( object ) ) return( -1 ); - } return( 0 ); } -/* We have to have _open() as a vfunc since our base class needs to be able to - * make two scans of the gif (scan for header, then scan for pixels), so we - * must be able to close and reopen. - */ -static int -vips_foreign_load_gif_file_open( VipsForeignLoadGif *gif ) -{ - VipsForeignLoad *load = (VipsForeignLoad *) gif; - VipsForeignLoadGifFile *file = (VipsForeignLoadGifFile *) gif; - VipsForeignLoadGifClass *class = - (VipsForeignLoadGifClass *) VIPS_OBJECT_GET_CLASS( load ); - - class->close( gif ); - - if( !(file->fp = - vips__file_open_read( file->filename, NULL, FALSE )) ) - return( -1 ); - - VIPS_SETSTR( load->out->filename, file->filename ); - - gif->read_func = vips_giflib_file_read; - - return( VIPS_FOREIGN_LOAD_GIF_CLASS( - vips_foreign_load_gif_file_parent_class )->open( gif ) ); -} - -static void -vips_foreign_load_gif_file_close( VipsForeignLoadGif *gif ) -{ - VipsForeignLoadGifFile *file = (VipsForeignLoadGifFile *) gif; - - VIPS_FOREIGN_LOAD_GIF_CLASS( - vips_foreign_load_gif_file_parent_class )->close( gif ); - - VIPS_FREEF( fclose, file->fp ); -} - static const char *vips_foreign_gif_suffs[] = { ".gif", NULL }; +static gboolean +vips_foreign_load_gif_file_is_a( const char *filename ) +{ + VipsSource *source; + gboolean result; + + if( !(source = vips_source_new_from_file( filename )) ) + return( FALSE ); + result = vips_foreign_load_gif_is_a_source( source ); + VIPS_UNREF( source ); + + return( result ); +} + static void vips_foreign_load_gif_file_class_init( VipsForeignLoadGifFileClass *class ) @@ -1324,21 +1388,17 @@ vips_foreign_load_gif_file_class_init( VipsObjectClass *object_class = (VipsObjectClass *) class; VipsForeignClass *foreign_class = (VipsForeignClass *) class; VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class; - VipsForeignLoadGifClass *gif_class = (VipsForeignLoadGifClass *) class; gobject_class->set_property = vips_object_set_property; gobject_class->get_property = vips_object_get_property; object_class->nickname = "gifload"; object_class->description = _( "load GIF with giflib" ); + object_class->build = vips_foreign_load_gif_file_build; foreign_class->suffs = vips_foreign_gif_suffs; - load_class->is_a = vips_foreign_load_gif_is_a; - load_class->header = vips_foreign_load_gif_file_header; - - gif_class->open = vips_foreign_load_gif_file_open; - gif_class->close = vips_foreign_load_gif_file_close; + load_class->is_a = vips_foreign_load_gif_file_is_a; VIPS_ARG_STRING( class, "filename", 1, _( "Filename" ), @@ -1359,12 +1419,7 @@ typedef struct _VipsForeignLoadGifBuffer { /* Load from a buffer. */ - VipsArea *buf; - - /* Current read point, bytes left in buffer. - */ - VipsPel *p; - size_t bytes_to_go; + VipsArea *blob; } VipsForeignLoadGifBuffer; @@ -1373,42 +1428,38 @@ typedef VipsForeignLoadGifClass VipsForeignLoadGifBufferClass; G_DEFINE_TYPE( VipsForeignLoadGifBuffer, vips_foreign_load_gif_buffer, vips_foreign_load_gif_get_type() ); -/* Callback from the gif loader. - * - * Read up to len bytes into buffer, return number of bytes read, 0 for EOF. - */ static int -vips_giflib_buffer_read( GifFileType *file, GifByteType *buf, int n ) +vips_foreign_load_gif_buffer_build( VipsObject *object ) { - VipsForeignLoadGif *gif = (VipsForeignLoadGif *) file->UserData; - VipsForeignLoadGifBuffer *buffer = (VipsForeignLoadGifBuffer *) gif; - size_t will_read = VIPS_MIN( n, buffer->bytes_to_go ); + VipsForeignLoadGif *gif = (VipsForeignLoadGif *) object; + VipsForeignLoadGifBuffer *buffer = + (VipsForeignLoadGifBuffer *) object; - memcpy( buf, buffer->p, will_read ); - buffer->p += will_read; - buffer->bytes_to_go -= will_read; + if( buffer->blob && + !(gif->source = vips_source_new_from_memory( + VIPS_AREA( buffer->blob )->data, + VIPS_AREA( buffer->blob )->length )) ) + return( -1 ); - if( will_read == 0 ) - gif->eof = TRUE; + if( VIPS_OBJECT_CLASS( vips_foreign_load_gif_buffer_parent_class )-> + build( object ) ) + return( -1 ); - return( will_read ); + return( 0 ); } -static int -vips_foreign_load_gif_buffer_open( VipsForeignLoadGif *gif ) +static gboolean +vips_foreign_load_gif_buffer_is_a_buffer( const void *buf, size_t len ) { - VipsForeignLoadGifBuffer *buffer = (VipsForeignLoadGifBuffer *) gif; - VipsForeignLoadGifClass *class = - (VipsForeignLoadGifClass *) VIPS_OBJECT_GET_CLASS( gif ); + VipsSource *source; + gboolean result; - class->close( gif ); + if( !(source = vips_source_new_from_memory( buf, len )) ) + return( FALSE ); + result = vips_foreign_load_gif_is_a_source( source ); + VIPS_UNREF( source ); - buffer->p = buffer->buf->data; - buffer->bytes_to_go = buffer->buf->length; - gif->read_func = vips_giflib_buffer_read;; - - return( VIPS_FOREIGN_LOAD_GIF_CLASS( - vips_foreign_load_gif_file_parent_class )->open( gif ) ); + return( result ); } static void @@ -1418,23 +1469,21 @@ vips_foreign_load_gif_buffer_class_init( GObjectClass *gobject_class = G_OBJECT_CLASS( class ); VipsObjectClass *object_class = (VipsObjectClass *) class; VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class; - VipsForeignLoadGifClass *gif_class = (VipsForeignLoadGifClass *) class; gobject_class->set_property = vips_object_set_property; gobject_class->get_property = vips_object_get_property; object_class->nickname = "gifload_buffer"; object_class->description = _( "load GIF with giflib" ); + object_class->build = vips_foreign_load_gif_buffer_build; - load_class->is_a_buffer = vips_foreign_load_gif_is_a_buffer; - - gif_class->open = vips_foreign_load_gif_buffer_open; + load_class->is_a_buffer = vips_foreign_load_gif_buffer_is_a_buffer; VIPS_ARG_BOXED( class, "buffer", 1, _( "Buffer" ), _( "Buffer to load from" ), VIPS_ARGUMENT_REQUIRED_INPUT, - G_STRUCT_OFFSET( VipsForeignLoadGifBuffer, buf ), + G_STRUCT_OFFSET( VipsForeignLoadGifBuffer, blob ), VIPS_TYPE_BLOB ); } @@ -1444,6 +1493,70 @@ vips_foreign_load_gif_buffer_init( VipsForeignLoadGifBuffer *buffer ) { } +typedef struct _VipsForeignLoadGifSource { + VipsForeignLoadGif parent_object; + + /* Load from a source. + */ + VipsSource *source; + +} VipsForeignLoadGifSource; + +typedef VipsForeignLoadGifClass VipsForeignLoadGifSourceClass; + +G_DEFINE_TYPE( VipsForeignLoadGifSource, vips_foreign_load_gif_source, + vips_foreign_load_gif_get_type() ); + +static int +vips_foreign_load_gif_source_build( VipsObject *object ) +{ + VipsForeignLoadGif *gif = (VipsForeignLoadGif *) object; + VipsForeignLoadGifSource *source = + (VipsForeignLoadGifSource *) object; + + if( source->source ) { + gif->source = source->source; + g_object_ref( gif->source ); + } + + if( VIPS_OBJECT_CLASS( vips_foreign_load_gif_source_parent_class )-> + build( object ) ) + return( -1 ); + + return( 0 ); +} + +static void +vips_foreign_load_gif_source_class_init( + VipsForeignLoadGifSourceClass *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 = "gifload_source"; + object_class->description = _( "load GIF with giflib" ); + object_class->build = vips_foreign_load_gif_source_build; + + load_class->is_a_source = vips_foreign_load_gif_is_a_source; + + VIPS_ARG_OBJECT( class, "source", 1, + _( "Source" ), + _( "Source to load from" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadGifSource, source ), + VIPS_TYPE_SOURCE ); + +} + +static void +vips_foreign_load_gif_source_init( VipsForeignLoadGifSource *source ) +{ +} + #endif /*HAVE_GIFLIB*/ /** @@ -1528,3 +1641,33 @@ vips_gifload_buffer( void *buf, size_t len, VipsImage **out, ... ) return( result ); } +/** + * vips_gifload_source: + * @source: source to load + * @out: (out): image to write + * @...: %NULL-terminated list of optional named arguments + * + * Optional arguments: + * + * * @page: %gint, page (frame) to read + * * @n: %gint, load this many pages + * + * Exactly as vips_gifload(), but read from a source. + * + * See also: vips_gifload(). + * + * Returns: 0 on success, -1 on error. + */ +int +vips_gifload_source( VipsSource *source, VipsImage **out, ... ) +{ + va_list ap; + int result; + + va_start( ap, out ); + result = vips_call_split( "gifload_source", ap, source, out ); + va_end( ap ); + + return( result ); +} + diff --git a/libvips/foreign/heifload.c b/libvips/foreign/heifload.c index 2b8ed2f3..772420ab 100644 --- a/libvips/foreign/heifload.c +++ b/libvips/foreign/heifload.c @@ -9,6 +9,15 @@ * - close early on error * 1/9/19 [meyermarcel] * - handle alpha + * 30/9/19 + * - much faster handling of thumbnail=TRUE and missing thumbnail ... we + * were reselecting the image for each scanline + * 3/10/19 + * - restart after minimise + * 15/3/20 + * - revise for new VipsSource API + * 10/5/20 + * - deprecate autorotate -- it's too difficult to support properly */ /* @@ -63,6 +72,21 @@ #include "pforeign.h" +#define VIPS_TYPE_FOREIGN_LOAD_HEIF (vips_foreign_load_heif_get_type()) +#define VIPS_FOREIGN_LOAD_HEIF( obj ) \ + (G_TYPE_CHECK_INSTANCE_CAST( (obj), \ + VIPS_TYPE_FOREIGN_LOAD_HEIF, VipsForeignLoadHeif )) +#define VIPS_FOREIGN_LOAD_HEIF_CLASS( klass ) \ + (G_TYPE_CHECK_CLASS_CAST( (klass), \ + VIPS_TYPE_FOREIGN_LOAD_HEIF, VipsForeignLoadHeifClass)) +#define VIPS_IS_FOREIGN_LOAD_HEIF( obj ) \ + (G_TYPE_CHECK_INSTANCE_TYPE( (obj), VIPS_TYPE_FOREIGN_LOAD_HEIF )) +#define VIPS_IS_FOREIGN_LOAD_HEIF_CLASS( klass ) \ + (G_TYPE_CHECK_CLASS_TYPE( (klass), VIPS_TYPE_FOREIGN_LOAD_HEIF )) +#define VIPS_FOREIGN_LOAD_HEIF_GET_CLASS( obj ) \ + (G_TYPE_INSTANCE_GET_CLASS( (obj), \ + VIPS_TYPE_FOREIGN_LOAD_HEIF, VipsForeignLoadHeifClass )) + typedef struct _VipsForeignLoadHeif { VipsForeignLoad parent_object; @@ -77,6 +101,11 @@ typedef struct _VipsForeignLoadHeif { gboolean thumbnail; /* Apply any orientation tags in the header. + * + * This is deprecated and does nothing. Non-autorotated reads from + * libheif are surprisingly hard to support well, since orientation can + * be represented in several different ways in HEIC files and devices + * vary in how they do this. */ gboolean autorotate; @@ -132,29 +161,42 @@ typedef struct _VipsForeignLoadHeif { int stride; const uint8_t *data; + /* Set from subclasses. + */ + VipsSource *source; + + /* The reader struct. We use this to attach to our VipsSource. This + * has to be alloced rather than in our struct, since it may change + * size in libheif API versions. + */ + struct heif_reader *reader; + + /* When we see EOF from read(), record the source length here. + */ + gint64 length; + } VipsForeignLoadHeif; -typedef VipsForeignLoadClass VipsForeignLoadHeifClass; +typedef struct _VipsForeignLoadHeifClass { + VipsForeignLoadClass parent_class; + +} VipsForeignLoadHeifClass; G_DEFINE_ABSTRACT_TYPE( VipsForeignLoadHeif, vips_foreign_load_heif, VIPS_TYPE_FOREIGN_LOAD ); -static void -vips_foreign_load_heif_close( VipsForeignLoadHeif *heif ) -{ - VIPS_FREEF( heif_image_release, heif->img ); - heif->data = NULL; - VIPS_FREEF( heif_image_handle_release, heif->handle ); - VIPS_FREEF( heif_context_free, heif->ctx ); -} - static void vips_foreign_load_heif_dispose( GObject *gobject ) { VipsForeignLoadHeif *heif = (VipsForeignLoadHeif *) gobject; - vips_foreign_load_heif_close( heif ); + heif->data = NULL; + VIPS_FREEF( heif_image_release, heif->img ); + VIPS_FREEF( heif_image_handle_release, heif->handle ); + VIPS_FREEF( heif_context_free, heif->ctx ); VIPS_FREE( heif->id ); + VIPS_FREE( heif->reader ); + VIPS_UNREF( heif->source ); G_OBJECT_CLASS( vips_foreign_load_heif_parent_class )-> dispose( gobject ); @@ -168,6 +210,31 @@ vips__heif_error( struct heif_error *error ) error->subcode ); } +static int +vips_foreign_load_heif_build( VipsObject *object ) +{ + VipsForeignLoadHeif *heif = (VipsForeignLoadHeif *) object; + + if( !heif->ctx ) { + struct heif_error error; + + heif->ctx = heif_context_alloc(); + error = heif_context_read_from_reader( heif->ctx, + heif->reader, heif, NULL ); + if( error.code ) { + vips__heif_error( &error ); + return( -1 ); + } + } + + if( VIPS_OBJECT_CLASS( vips_foreign_load_heif_parent_class )-> + build( object ) ) + return( -1 ); + + return( 0 ); +} + + static const char *heif_magic[] = { "ftypheic", /* A regular heif image */ "ftypheix", /* Extended range (>8 bit) image */ @@ -177,7 +244,8 @@ static const char *heif_magic[] = { "ftyphevm", /* Multiview sequence */ "ftyphevs", /* Scaleable sequence */ "ftypmif1", /* Nokia alpha_ image */ - "ftypmsf1" /* Nokia animation image */ + "ftypmsf1", /* Nokia animation image */ + "ftypavif" /* AV1 image format */ }; /* THe API has: @@ -216,6 +284,11 @@ static int vips_foreign_load_heif_set_page( VipsForeignLoadHeif *heif, int page_no, gboolean thumbnail ) { +#ifdef DEBUG + printf( "vips_foreign_load_heif_set_page: %d, thumbnail = %d\n", + page_no, thumbnail ); +#endif /*DEBUG*/ + if( !heif->handle || page_no != heif->page_no || thumbnail != heif->thumbnail_set ) { @@ -253,8 +326,17 @@ vips_foreign_load_heif_set_page( VipsForeignLoadHeif *heif, VIPS_FREEF( heif_image_handle_release, heif->handle ); heif->handle = thumb_handle; - heif->thumbnail_set = TRUE; } + + /* If we were asked to select the thumbnail, say we + * did, even if there are no thumbnails and we just + * selected the main image. + * + * If we don't do this, next time around in _generate + * we'll try to select the thumbnail again, which will + * be horribly slow. + */ + heif->thumbnail_set = TRUE; } heif->page_no = page_no; @@ -266,6 +348,8 @@ vips_foreign_load_heif_set_page( VipsForeignLoadHeif *heif, static int vips_foreign_load_heif_set_header( VipsForeignLoadHeif *heif, VipsImage *out ) { + VipsForeignLoad *load = (VipsForeignLoad *) heif; + int bands; int i; /* Surely, 16 metadata items will be enough for anyone. @@ -287,7 +371,7 @@ vips_foreign_load_heif_set_header( VipsForeignLoadHeif *heif, VipsImage *out ) #endif /*DEBUG*/ bands = heif->has_alpha ? 4 : 3; - /* FIXME .. need to test XMP and IPCT. + /* FIXME .. IPTC as well? */ n_metadata = heif_image_handle_get_list_of_metadata_block_IDs( heif->handle, NULL, id, VIPS_NUMBER( id ) ); @@ -330,23 +414,33 @@ vips_foreign_load_heif_set_header( VipsForeignLoadHeif *heif, VipsImage *out ) vips_snprintf( name, 256, VIPS_META_EXIF_NAME ); else if( g_ascii_strcasecmp( type, "mime" ) == 0 && vips_isprefix( "handle ); +#ifdef DEBUG +{ printf( "profile type = " ); switch( profile_type ) { case heif_color_profile_type_not_present: @@ -373,10 +467,10 @@ vips_foreign_load_heif_set_header( VipsForeignLoadHeif *heif, VipsImage *out ) } #endif /*DEBUG*/ - /* FIXME should probably check the profile type ... lcms seems to be - * able to load at least rICC and prof. + /* lcms can load standard (prof) and reduced (rICC) profiles */ - if( heif_image_handle_get_color_profile_type( heif->handle ) ) { + if( profile_type == heif_color_profile_type_prof || + profile_type == heif_color_profile_type_rICC ) { size_t length = heif_image_handle_get_raw_color_profile_size( heif->handle ); @@ -398,15 +492,11 @@ vips_foreign_load_heif_set_header( VipsForeignLoadHeif *heif, VipsImage *out ) vips_image_set_blob( out, VIPS_META_ICC_NAME, (VipsCallbackFn) NULL, data, length ); } + else if( profile_type == heif_color_profile_type_nclx ) { + g_warning( "heifload: ignoring nclx profile" ); + } #endif /*HAVE_HEIF_COLOR_PROFILE*/ - /* If we are using libheif's autorotate, remove the exif one. - */ -#ifdef HAVE_HEIF_IMAGE_HANDLE_GET_ISPE_WIDTH - if( heif->autorotate ) - vips_autorot_remove_angle( out ); -#endif /*HAVE_HEIF_IMAGE_HANDLE_GET_ISPE_WIDTH*/ - vips_image_set_int( out, "heif-primary", heif->primary_page ); vips_image_set_int( out, "n-pages", heif->n_top ); if( vips_object_argument_isset( VIPS_OBJECT( heif ), "n" ) ) @@ -422,43 +512,12 @@ vips_foreign_load_heif_set_header( VipsForeignLoadHeif *heif, VipsImage *out ) VIPS_FORMAT_UCHAR, VIPS_CODING_NONE, VIPS_INTERPRETATION_sRGB, 1.0, 1.0 ); + VIPS_SETSTR( load->out->filename, + vips_connection_filename( VIPS_CONNECTION( heif->source ) ) ); + return( 0 ); } -static int -vips_foreign_load_heif_get_width( VipsForeignLoadHeif *heif, - struct heif_image_handle *handle ) -{ - int width; - - /* _get_ipse_width() fetches the untransformed dimension, but was only - * added in 1.3.4. Without it, we just use the transformed dimension - * and have to autorotate. - */ - width = heif_image_handle_get_width( handle ); -#ifdef HAVE_HEIF_IMAGE_HANDLE_GET_ISPE_WIDTH - if( !heif->autorotate ) - width = heif_image_handle_get_ispe_width( handle ); -#endif /*HAVE_HEIF_IMAGE_HANDLE_GET_ISPE_WIDTH*/ - - return( width ); -} - -static int -vips_foreign_load_heif_get_height( VipsForeignLoadHeif *heif, - struct heif_image_handle *handle ) -{ - int height; - - height = heif_image_handle_get_height( handle ); -#ifdef HAVE_HEIF_IMAGE_HANDLE_GET_ISPE_WIDTH - if( !heif->autorotate ) - height = heif_image_handle_get_ispe_height( handle ); -#endif /*HAVE_HEIF_IMAGE_HANDLE_GET_ISPE_WIDTH*/ - - return( height ); -} - static int vips_foreign_load_heif_header( VipsForeignLoad *load ) { @@ -502,10 +561,6 @@ vips_foreign_load_heif_header( VipsForeignLoad *load ) } #ifdef DEBUG -#ifdef HAVE_HEIF_IMAGE_HANDLE_GET_ISPE_WIDTH - if( !heif->autorotate ) - printf( "using _get_ispe_width() / _height()\n" ); -#endif /*HAVE_HEIF_IMAGE_HANDLE_GET_ISPE_WIDTH*/ for( i = heif->page; i < heif->page + heif->n; i++ ) { heif_item_id thumb_ids[1]; int n_items; @@ -536,11 +591,9 @@ vips_foreign_load_heif_header( VipsForeignLoad *load ) printf( " thumb %d\n", j ); printf( " width = %d\n", - vips_foreign_load_heif_get_width( heif, - thumb_handle ) ); + heif_image_handle_get_width( thumb_handle ) ); printf( " height = %d\n", - vips_foreign_load_heif_get_height( heif, - thumb_handle ) ); + heif_image_handle_get_height( thumb_handle ) ); } } #endif /*DEBUG*/ @@ -550,18 +603,16 @@ vips_foreign_load_heif_header( VipsForeignLoad *load ) if( vips_foreign_load_heif_set_page( heif, heif->page, heif->thumbnail ) ) return( -1 ); - heif->page_width = vips_foreign_load_heif_get_width( heif, - heif->handle ); - heif->page_height = vips_foreign_load_heif_get_height( heif, - heif->handle ); + heif->page_width = heif_image_handle_get_width( heif->handle ); + heif->page_height = heif_image_handle_get_height( heif->handle ); for( i = heif->page + 1; i < heif->page + heif->n; i++ ) { if( vips_foreign_load_heif_set_page( heif, i, heif->thumbnail ) ) return( -1 ); - if( vips_foreign_load_heif_get_width( heif, - heif->handle ) != heif->page_width || - vips_foreign_load_heif_get_height( heif, - heif->handle ) != heif->page_height ) { + if( heif_image_handle_get_width( heif->handle ) + != heif->page_width || + heif_image_handle_get_height( heif->handle ) + != heif->page_height ) { vips_error( class->nickname, "%s", _( "not all pages are the same size" ) ); return( -1 ); @@ -575,11 +626,9 @@ vips_foreign_load_heif_header( VipsForeignLoad *load ) if( vips_foreign_load_heif_set_page( heif, i, FALSE ) ) return( -1 ); printf( " width = %d\n", - vips_foreign_load_heif_get_width( heif, - heif->handle ) ); + heif_image_handle_get_width( heif->handle ) ); printf( " height = %d\n", - vips_foreign_load_heif_get_height( heif, - heif->handle ) ); + heif_image_handle_get_height( heif->handle ) ); printf( " has_depth = %d\n", heif_image_handle_has_depth_image( heif->handle ) ); printf( " has_alpha = %d\n", @@ -598,6 +647,8 @@ vips_foreign_load_heif_header( VipsForeignLoad *load ) if( vips_foreign_load_heif_set_header( heif, load->out ) ) return( -1 ); + vips_source_minimise( heif->source ); + return( 0 ); } @@ -628,13 +679,12 @@ vips_foreign_load_heif_generate( VipsRegion *or, heif_chroma_interleaved_RGBA : heif_chroma_interleaved_RGB; - /* Only disable transforms if we have been able to fetch the - * untransformed dimensions. - */ options = heif_decoding_options_alloc(); -#ifdef HAVE_HEIF_IMAGE_HANDLE_GET_ISPE_WIDTH - options->ignore_transformations = !heif->autorotate; -#endif /*HAVE_HEIF_IMAGE_HANDLE_GET_ISPE_WIDTH*/ +#ifdef HAVE_HEIF_DECODING_OPTIONS_CONVERT_HDR_TO_8BIT + /* VIPS_FORMAT_UCHAR is assumed so downsample HDR to 8bpc + */ + options->convert_hdr_to_8bit = TRUE; +#endif /*HAVE_HEIF_DECODING_OPTIONS_CONVERT_HDR_TO_8BIT*/ error = heif_decode_image( heif->handle, &heif->img, heif_colorspace_RGB, chroma, options ); @@ -725,7 +775,7 @@ vips_foreign_load_heif_generate( VipsRegion *or, static void vips_foreign_load_heif_minimise( VipsObject *object, VipsForeignLoadHeif *heif ) { - vips_foreign_load_heif_close( heif ); + vips_source_minimise( heif->source ); } static int @@ -771,6 +821,7 @@ vips_foreign_load_heif_class_init( VipsForeignLoadHeifClass *class ) object_class->nickname = "heifload_base"; object_class->description = _( "load a HEIF image" ); + object_class->build = vips_foreign_load_heif_build; load_class->get_flags = vips_foreign_load_heif_get_flags; load_class->header = vips_foreign_load_heif_header; @@ -800,17 +851,94 @@ vips_foreign_load_heif_class_init( VipsForeignLoadHeifClass *class ) VIPS_ARG_BOOL( class, "autorotate", 21, _( "Autorotate" ), _( "Rotate image using exif orientation" ), - VIPS_ARGUMENT_OPTIONAL_INPUT, + VIPS_ARGUMENT_OPTIONAL_INPUT | VIPS_ARGUMENT_DEPRECATED, G_STRUCT_OFFSET( VipsForeignLoadHeif, autorotate ), FALSE ); } +static gint64 +vips_foreign_load_heif_get_position( void *userdata ) +{ + VipsForeignLoadHeif *heif = (VipsForeignLoadHeif *) userdata; + + return( vips_source_seek( heif->source, 0L, SEEK_CUR ) ); +} + +static int +vips_foreign_load_heif_read( void *data, size_t size, void *userdata ) +{ + VipsForeignLoadHeif *heif = (VipsForeignLoadHeif *) userdata; + + gint64 result; + + result = vips_source_read( heif->source, data, size ); + /* On EOF, make a note of the file length. + * + * libheif can sometimes ask for zero bytes, be careful not to + * interpret that as EOF. + */ + if( size > 0 && + result == 0 && + heif->length == -1 ) + result = heif->length = + vips_source_seek( heif->source, 0L, SEEK_CUR ); + if( result < 0 ) + return( -1 ); + + return( 0 ); +} + +static int +vips_foreign_load_heif_seek( gint64 position, void *userdata ) +{ + VipsForeignLoadHeif *heif = (VipsForeignLoadHeif *) userdata; + + vips_source_seek( heif->source, position, SEEK_SET ); + + return( 0 ); +} + +static enum heif_reader_grow_status +vips_foreign_load_heif_wait_for_file_size( gint64 target_size, void *userdata ) +{ + VipsForeignLoadHeif *heif = (VipsForeignLoadHeif *) userdata; + + enum heif_reader_grow_status status; + + if( heif->length == -1 ) + /* We've not seen EOF yet, so seeking to any point is fine (as + * far as we know). + */ + status = heif_reader_grow_status_size_reached; + else if( target_size <= heif->length ) + /* We've seen EOF, and this target is less than that. + */ + status = heif_reader_grow_status_size_reached; + else + /* We've seen EOF, we know the length, and this is too far. + */ + status = heif_reader_grow_status_size_beyond_eof; + + return( status ); +} + static void vips_foreign_load_heif_init( VipsForeignLoadHeif *heif ) { - heif->ctx = heif_context_alloc(); heif->n = 1; + heif->length = -1; + + heif->reader = VIPS_ARRAY( NULL, 1, struct heif_reader ); + + /* The first version to support heif_reader. + */ + heif->reader->reader_api_version = 1; + heif->reader->get_position = vips_foreign_load_heif_get_position; + heif->reader->read = vips_foreign_load_heif_read; + heif->reader->seek = vips_foreign_load_heif_seek; + heif->reader->wait_for_file_size = + vips_foreign_load_heif_wait_for_file_size; } typedef struct _VipsForeignLoadHeifFile { @@ -827,6 +955,24 @@ typedef VipsForeignLoadHeifClass VipsForeignLoadHeifFileClass; G_DEFINE_TYPE( VipsForeignLoadHeifFile, vips_foreign_load_heif_file, vips_foreign_load_heif_get_type() ); +static int +vips_foreign_load_heif_file_build( VipsObject *object ) +{ + VipsForeignLoadHeif *heif = (VipsForeignLoadHeif *) object; + VipsForeignLoadHeifFile *file = (VipsForeignLoadHeifFile *) object; + + if( file->filename ) + if( !(heif->source = + vips_source_new_from_file( file->filename )) ) + return( -1 ); + + if( VIPS_OBJECT_CLASS( vips_foreign_load_heif_file_parent_class )-> + build( object ) ) + return( -1 ); + + return( 0 ); +} + static int vips_foreign_load_heif_file_is_a( const char *filename ) { @@ -838,36 +984,6 @@ vips_foreign_load_heif_file_is_a( const char *filename ) return( vips_foreign_load_heif_is_a( buf, 12 ) ); } -static int -vips_foreign_load_heif_file_header( VipsForeignLoad *load ) -{ - VipsForeignLoadHeif *heif = (VipsForeignLoadHeif *) load; - VipsForeignLoadHeifFile *file = (VipsForeignLoadHeifFile *) load; - - struct heif_error error; - - error = heif_context_read_from_file( heif->ctx, file->filename, NULL ); - if( error.code ) { - /* Make we close the fd as soon as we can on error. - */ - vips_foreign_load_heif_close( heif ); - vips__heif_error( &error ); - return( -1 ); - } - - if( VIPS_FOREIGN_LOAD_CLASS( - vips_foreign_load_heif_file_parent_class )->header( load ) ) { - /* Close early if our base class fails to read. - */ - vips_foreign_load_heif_close( heif ); - return( -1 ); - } - - VIPS_SETSTR( load->out->filename, file->filename ); - - return( 0 ); -} - const char *vips__heif_suffs[] = { ".heic", ".heif", @@ -887,11 +1003,11 @@ vips_foreign_load_heif_file_class_init( VipsForeignLoadHeifFileClass *class ) gobject_class->get_property = vips_object_get_property; object_class->nickname = "heifload"; + object_class->build = vips_foreign_load_heif_file_build; foreign_class->suffs = vips__heif_suffs; load_class->is_a = vips_foreign_load_heif_file_is_a; - load_class->header = vips_foreign_load_heif_file_header; VIPS_ARG_STRING( class, "filename", 1, _( "Filename" ), @@ -921,34 +1037,32 @@ typedef VipsForeignLoadHeifClass VipsForeignLoadHeifBufferClass; G_DEFINE_TYPE( VipsForeignLoadHeifBuffer, vips_foreign_load_heif_buffer, vips_foreign_load_heif_get_type() ); +static int +vips_foreign_load_heif_buffer_build( VipsObject *object ) +{ + VipsForeignLoadHeif *heif = (VipsForeignLoadHeif *) object; + VipsForeignLoadHeifBuffer *buffer = + (VipsForeignLoadHeifBuffer *) object; + + if( buffer->buf ) + if( !(heif->source = vips_source_new_from_memory( + VIPS_AREA( buffer->buf )->data, + VIPS_AREA( buffer->buf )->length )) ) + return( -1 ); + + if( VIPS_OBJECT_CLASS( vips_foreign_load_heif_file_parent_class )-> + build( object ) ) + return( -1 ); + + return( 0 ); +} + static gboolean vips_foreign_load_heif_buffer_is_a( const void *buf, size_t len ) { return( vips_foreign_load_heif_is_a( buf, len ) ); } -static int -vips_foreign_load_heif_buffer_header( VipsForeignLoad *load ) -{ - VipsForeignLoadHeif *heif = (VipsForeignLoadHeif *) load; - VipsForeignLoadHeifBuffer *buffer = (VipsForeignLoadHeifBuffer *) load; - - struct heif_error error; - - error = heif_context_read_from_memory( heif->ctx, - buffer->buf->data, buffer->buf->length, NULL ); - if( error.code ) { - vips__heif_error( &error ); - return( -1 ); - } - - if( VIPS_FOREIGN_LOAD_CLASS( - vips_foreign_load_heif_buffer_parent_class )->header( load ) ) - return( -1 ); - - return( 0 ); -} - static void vips_foreign_load_heif_buffer_class_init( VipsForeignLoadHeifBufferClass *class ) @@ -961,9 +1075,9 @@ vips_foreign_load_heif_buffer_class_init( gobject_class->get_property = vips_object_get_property; object_class->nickname = "heifload_buffer"; + object_class->build = vips_foreign_load_heif_buffer_build; load_class->is_a_buffer = vips_foreign_load_heif_buffer_is_a; - load_class->header = vips_foreign_load_heif_buffer_header; VIPS_ARG_BOXED( class, "buffer", 1, _( "Buffer" ), @@ -979,6 +1093,78 @@ vips_foreign_load_heif_buffer_init( VipsForeignLoadHeifBuffer *buffer ) { } +typedef struct _VipsForeignLoadHeifSource { + VipsForeignLoadHeif parent_object; + + /* Load from a source. + */ + VipsSource *source; + +} VipsForeignLoadHeifSource; + +typedef VipsForeignLoadHeifClass VipsForeignLoadHeifSourceClass; + +G_DEFINE_TYPE( VipsForeignLoadHeifSource, vips_foreign_load_heif_source, + vips_foreign_load_heif_get_type() ); + +static int +vips_foreign_load_heif_source_build( VipsObject *object ) +{ + VipsForeignLoadHeif *heif = (VipsForeignLoadHeif *) object; + VipsForeignLoadHeifSource *source = + (VipsForeignLoadHeifSource *) object; + + if( source->source ) { + heif->source = source->source; + g_object_ref( heif->source ); + } + + if( VIPS_OBJECT_CLASS( vips_foreign_load_heif_file_parent_class )-> + build( object ) ) + return( -1 ); + + return( 0 ); +} + +static gboolean +vips_foreign_load_heif_source_is_a_source( VipsSource *source ) +{ + const char *p; + + return( (p = (const char *) vips_source_sniff( source, 12 )) && + vips_foreign_load_heif_is_a( p, 12 ) ); +} + +static void +vips_foreign_load_heif_source_class_init( + VipsForeignLoadHeifSourceClass *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 = "heifload_source"; + object_class->build = vips_foreign_load_heif_source_build; + + load_class->is_a_source = vips_foreign_load_heif_source_is_a_source; + + VIPS_ARG_OBJECT( class, "source", 1, + _( "Source" ), + _( "Source to load from" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadHeifSource, source ), + VIPS_TYPE_SOURCE ); + +} + +static void +vips_foreign_load_heif_source_init( VipsForeignLoadHeifSource *source ) +{ +} + #endif /*HAVE_HEIF_DECODER*/ /** @@ -992,7 +1178,6 @@ vips_foreign_load_heif_buffer_init( VipsForeignLoadHeifBuffer *buffer ) * * @page: %gint, page (top-level image number) to read * * @n: %gint, load this many pages * * @thumbnail: %gboolean, fetch thumbnail instead of image - * * @autorotate: %gboolean, rotate image upright during load * * Read a HEIF image file into a VIPS image. * @@ -1009,17 +1194,6 @@ vips_foreign_load_heif_buffer_init( VipsForeignLoadHeifBuffer *buffer ) * If @thumbnail is %TRUE, then fetch a stored thumbnail rather than the * image. * - * Setting @autorotate to %TRUE will make the loader interpret the - * orientation tag and automatically rotate the image appropriately during - * load. - * - * If @autorotate is %FALSE, the metadata field #VIPS_META_ORIENTATION is set - * to the value of the orientation tag. Applications may read and interpret - * this field - * as they wish later in processing. See vips_autorot(). Save - * operations will use #VIPS_META_ORIENTATION, if present, to set the - * orientation of output images. - * * See also: vips_image_new_from_file(). * * Returns: 0 on success, -1 on error. @@ -1049,7 +1223,6 @@ vips_heifload( const char *filename, VipsImage **out, ... ) * * @page: %gint, page (top-level image number) to read * * @n: %gint, load this many pages * * @thumbnail: %gboolean, fetch thumbnail instead of image - * * @autorotate: %gboolean, rotate image upright during load * * Read a HEIF image file into a VIPS image. * Exactly as vips_heifload(), but read from a memory buffer. @@ -1080,3 +1253,34 @@ vips_heifload_buffer( void *buf, size_t len, VipsImage **out, ... ) return( result ); } + +/** + * vips_heifload_source: + * @source: source to load from + * @out: (out): image to write + * @...: %NULL-terminated list of optional named arguments + * + * Optional arguments: + * + * * @page: %gint, page (top-level image number) to read + * * @n: %gint, load this many pages + * * @thumbnail: %gboolean, fetch thumbnail instead of image + * + * Exactly as vips_heifload(), but read from a source. + * + * See also: vips_heifload(). + * + * Returns: 0 on success, -1 on error. + */ +int +vips_heifload_source( VipsSource *source, VipsImage **out, ... ) +{ + va_list ap; + int result; + + va_start( ap, out ); + result = vips_call_split( "heifload_source", ap, source, out ); + va_end( ap ); + + return( result ); +} diff --git a/libvips/foreign/heifsave.c b/libvips/foreign/heifsave.c index be425d72..459d8f1a 100644 --- a/libvips/foreign/heifsave.c +++ b/libvips/foreign/heifsave.c @@ -6,6 +6,8 @@ * - add "compression" option * 1/9/19 [meyermarcel] * - save alpha when necessary + * 15/3/20 + * - revise for new VipsTarget API */ /* @@ -61,6 +63,10 @@ typedef struct _VipsForeignSaveHeif { VipsForeignSave parent_object; + /* Where to write (set by subclasses). + */ + VipsTarget *target; + /* Coding quality factor (1-100). */ int Q; @@ -73,6 +79,11 @@ typedef struct _VipsForeignSaveHeif { */ VipsForeignHeifCompression compression; + /* The image we save. This is a copy of save->ready since we need to + * be able to update the metadata. + */ + VipsImage *image; + int page_width; int page_height; int n_pages; @@ -107,6 +118,8 @@ vips_foreign_save_heif_dispose( GObject *gobject ) { VipsForeignSaveHeif *heif = (VipsForeignSaveHeif *) gobject; + VIPS_UNREF( heif->target ); + VIPS_UNREF( heif->image ); VIPS_FREEF( heif_image_release, heif->img ); VIPS_FREEF( heif_image_handle_release, heif->handle ); VIPS_FREEF( heif_encoder_release, heif->encoder ); @@ -134,13 +147,12 @@ static int vips_foreign_save_heif_write_metadata( VipsForeignSaveHeif *heif ) { #ifdef HAVE_HEIF_CONTEXT_ADD_EXIF_METADATA - VipsForeignSave *save = (VipsForeignSave *) heif; int i; struct heif_error error; for( i = 0; i < VIPS_NUMBER( libheif_metadata ); i++ ) - if( vips_image_get_typeof( save->ready, + if( vips_image_get_typeof( heif->image, libheif_metadata[i].name ) ) { const void *data; size_t length; @@ -150,7 +162,7 @@ vips_foreign_save_heif_write_metadata( VipsForeignSaveHeif *heif ) libheif_metadata[i].name ); #endif /*DEBUG*/ - if( vips_image_get_blob( save->ready, + if( vips_image_get_blob( heif->image, VIPS_META_EXIF_NAME, &data, &length ) ) return( -1 ); @@ -176,7 +188,7 @@ vips_foreign_save_heif_write_page( VipsForeignSaveHeif *heif, int page ) #ifdef HAVE_HEIF_COLOR_PROFILE if( !save->strip && - vips_image_get_typeof( save->ready, VIPS_META_ICC_NAME ) ) { + vips_image_get_typeof( heif->image, VIPS_META_ICC_NAME ) ) { const void *data; size_t length; @@ -184,7 +196,7 @@ vips_foreign_save_heif_write_page( VipsForeignSaveHeif *heif, int page ) printf( "attaching profile ..\n" ); #endif /*DEBUG*/ - if( vips_image_get_blob( save->ready, + if( vips_image_get_blob( heif->image, VIPS_META_ICC_NAME, &data, &length ) ) return( -1 ); @@ -201,7 +213,7 @@ vips_foreign_save_heif_write_page( VipsForeignSaveHeif *heif, int page ) #ifdef HAVE_HEIF_ENCODING_OPTIONS_ALLOC options = heif_encoding_options_alloc(); - if( vips_image_hasalpha( save->ready ) ) + if( vips_image_hasalpha( heif->image ) ) options->save_alpha_channel = 1; #else /*!HAVE_HEIF_ENCODING_OPTIONS_ALLOC*/ options = NULL; @@ -223,10 +235,10 @@ vips_foreign_save_heif_write_page( VipsForeignSaveHeif *heif, int page ) } #ifdef HAVE_HEIF_CONTEXT_SET_PRIMARY_IMAGE - if( vips_image_get_typeof( save->ready, "heif-primary" ) ) { + if( vips_image_get_typeof( heif->image, "heif-primary" ) ) { int primary; - if( vips_image_get_int( save->ready, + if( vips_image_get_int( heif->image, "heif-primary", &primary ) ) return( -1 ); @@ -287,6 +299,21 @@ vips_foreign_save_heif_write_block( VipsRegion *region, VipsRect *area, return( 0 ); } +struct heif_error +vips_foreign_save_heif_write( struct heif_context *ctx, + const void *data, size_t length, void *userdata ) +{ + VipsForeignSaveHeif *heif = (VipsForeignSaveHeif *) userdata; + + struct heif_error error; + + error.code = 0; + if( vips_target_write( heif->target, data, length ) ) + error.code = -1; + + return( error ); +} + static int vips_foreign_save_heif_build( VipsObject *object ) { @@ -294,11 +321,21 @@ vips_foreign_save_heif_build( VipsObject *object ) VipsForeignSaveHeif *heif = (VipsForeignSaveHeif *) object; struct heif_error error; + struct heif_writer writer; if( VIPS_OBJECT_CLASS( vips_foreign_save_heif_parent_class )-> build( object ) ) return( -1 ); + /* Only rebuild exif if there's an EXIF block or we'll make a + * default set of tags. EXIF is not required for heif. + */ + if( vips_copy( save->ready, &heif->image, NULL ) ) + return( -1 ); + if( vips_image_get_typeof( heif->image, VIPS_META_EXIF_NAME ) ) + if( vips__exif_update( heif->image ) ) + return( -1 ); + error = heif_context_get_encoder_for_format( heif->ctx, (enum heif_compression_format) heif->compression, &heif->encoder ); @@ -328,16 +365,16 @@ vips_foreign_save_heif_build( VipsObject *object ) * heif_encoder_list_parameters(). */ - heif->page_width = save->ready->Xsize; - heif->page_height = vips_image_get_page_height( save->ready ); - heif->n_pages = save->ready->Ysize / heif->page_height; + heif->page_width = heif->image->Xsize; + heif->page_height = vips_image_get_page_height( heif->image ); + heif->n_pages = heif->image->Ysize / heif->page_height; /* Make a heif image the size of a page. We send sink_disc() output * here and write a frame each time it fills. */ error = heif_image_create( heif->page_width, heif->page_height, heif_colorspace_RGB, - vips_image_hasalpha( save->ready ) ? + vips_image_hasalpha( heif->image ) ? heif_chroma_interleaved_RGBA : heif_chroma_interleaved_RGB, &heif->img ); @@ -348,7 +385,7 @@ vips_foreign_save_heif_build( VipsObject *object ) error = heif_image_add_plane( heif->img, heif_channel_interleaved, heif->page_width, heif->page_height, - vips_image_hasalpha( save->ready ) ? 32 : 24 ); + vips_image_hasalpha( heif->image ) ? 32 : 24 ); if( error.code ) { vips__heif_error( &error ); return( -1 ); @@ -357,18 +394,25 @@ vips_foreign_save_heif_build( VipsObject *object ) heif->data = heif_image_get_plane( heif->img, heif_channel_interleaved, &heif->stride ); - /* Just do this once, so we don't rebuild exif on every page. - */ - if( vips_image_get_typeof( save->ready, VIPS_META_EXIF_NAME ) ) - if( vips__exif_update( save->ready ) ) - return( -1 ); - /* Write data. */ - if( vips_sink_disc( save->ready, + if( vips_sink_disc( heif->image, vips_foreign_save_heif_write_block, heif ) ) return( -1 ); + /* This has to come right at the end :-( so there's no support for + * incremental writes. + */ + writer.writer_api_version = 1; + writer.write = vips_foreign_save_heif_write; + error = heif_context_write( heif->ctx, &writer, heif ); + if( error.code ) { + vips__heif_error( &error ); + return( -1 ); + } + + vips_target_finish( heif->target ); + return( 0 ); } @@ -454,21 +498,13 @@ vips_foreign_save_heif_file_build( VipsObject *object ) VipsForeignSaveHeif *heif = (VipsForeignSaveHeif *) object; VipsForeignSaveHeifFile *file = (VipsForeignSaveHeifFile *) object; - struct heif_error error; + if( !(heif->target = vips_target_new_to_file( file->filename )) ) + return( -1 ); if( VIPS_OBJECT_CLASS( vips_foreign_save_heif_file_parent_class )-> build( object ) ) return( -1 ); - /* This has to come right at the end :-( so there's no support for - * incremental writes. - */ - error = heif_context_write_to_file( heif->ctx, file->filename ); - if( error.code ) { - vips__heif_error( &error ); - return( -1 ); - } - return( 0 ); } @@ -512,55 +548,25 @@ typedef VipsForeignSaveHeifClass VipsForeignSaveHeifBufferClass; G_DEFINE_TYPE( VipsForeignSaveHeifBuffer, vips_foreign_save_heif_buffer, vips_foreign_save_heif_get_type() ); -struct heif_error -vips_foreign_save_heif_buffer_write( struct heif_context *ctx, - const void *data, size_t length, void *userdata ) -{ - VipsForeignSaveHeif *heif = (VipsForeignSaveHeif *) userdata; - - VipsBlob *blob; - struct heif_error error; - void *data_copy; - - /* FIXME .. we have to memcpy()! - */ - data_copy = vips_malloc( NULL, length ); - memcpy( data_copy, data, length ); - - blob = vips_blob_new( (VipsCallbackFn) vips_free, data_copy, length ); - g_object_set( heif, "buffer", blob, NULL ); - vips_area_unref( VIPS_AREA( blob ) ); - - error.code = 0; - - return( error ); -} - static int vips_foreign_save_heif_buffer_build( VipsObject *object ) { VipsForeignSaveHeif *heif = (VipsForeignSaveHeif *) object; + VipsForeignSaveHeifBuffer *buffer = + (VipsForeignSaveHeifBuffer *) object; - /* FIXME ... argh, allocating on the stack! But the example code does - * this too. - */ - struct heif_writer writer; - struct heif_error error; + VipsBlob *blob; + + if( !(heif->target = vips_target_new_to_memory()) ) + return( -1 ); if( VIPS_OBJECT_CLASS( vips_foreign_save_heif_buffer_parent_class )-> build( object ) ) return( -1 ); - /* This has to come right at the end :-( so there's no support for - * incremental writes. - */ - writer.writer_api_version = 1; - writer.write = vips_foreign_save_heif_buffer_write; - error = heif_context_write( heif->ctx, &writer, heif ); - if( error.code ) { - vips__heif_error( &error ); - return( -1 ); - } + g_object_get( heif->target, "blob", &blob, NULL ); + g_object_set( buffer, "buffer", blob, NULL ); + vips_area_unref( VIPS_AREA( blob ) ); return( 0 ); } @@ -592,6 +598,63 @@ vips_foreign_save_heif_buffer_init( VipsForeignSaveHeifBuffer *buffer ) { } +typedef struct _VipsForeignSaveHeifTarget { + VipsForeignSaveHeif parent_object; + + VipsTarget *target; +} VipsForeignSaveHeifTarget; + +typedef VipsForeignSaveHeifClass VipsForeignSaveHeifTargetClass; + +G_DEFINE_TYPE( VipsForeignSaveHeifTarget, vips_foreign_save_heif_target, + vips_foreign_save_heif_get_type() ); + +static int +vips_foreign_save_heif_target_build( VipsObject *object ) +{ + VipsForeignSaveHeif *heif = (VipsForeignSaveHeif *) object; + VipsForeignSaveHeifTarget *target = + (VipsForeignSaveHeifTarget *) object; + + if( target->target ) { + heif->target = target->target; + g_object_ref( heif->target ); + } + + if( VIPS_OBJECT_CLASS( vips_foreign_save_heif_target_parent_class )-> + build( object ) ) + return( -1 ); + + return( 0 ); +} + +static void +vips_foreign_save_heif_target_class_init( + VipsForeignSaveHeifTargetClass *class ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( class ); + VipsObjectClass *object_class = (VipsObjectClass *) class; + + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + object_class->nickname = "heifsave_target"; + object_class->build = vips_foreign_save_heif_target_build; + + VIPS_ARG_OBJECT( class, "target", 1, + _( "Target" ), + _( "Target to save to" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsForeignSaveHeifTarget, target ), + VIPS_TYPE_TARGET ); + +} + +static void +vips_foreign_save_heif_target_init( VipsForeignSaveHeifTarget *target ) +{ +} + #endif /*HAVE_HEIF_ENCODER*/ /** @@ -682,3 +745,34 @@ vips_heifsave_buffer( VipsImage *in, void **buf, size_t *len, ... ) return( result ); } + +/** + * vips_heifsave_target: (method) + * @in: image to save + * @target: save image to this target + * @...: %NULL-terminated list of optional named arguments + * + * Optional arguments: + * + * * @Q: %gint, quality factor + * * @lossless: %gboolean, enable lossless encoding + * * @compression: #VipsForeignHeifCompression, write with this compression + * + * As vips_heifsave(), but save to a target. + * + * See also: vips_heifsave(), vips_image_write_to_target(). + * + * Returns: 0 on success, -1 on error. + */ +int +vips_heifsave_target( VipsImage *in, VipsTarget *target, ... ) +{ + va_list ap; + int result; + + va_start( ap, target ); + result = vips_call_split( "heifsave_target", ap, in, target ); + va_end( ap ); + + return( result ); +} diff --git a/libvips/foreign/jpeg2vips.c b/libvips/foreign/jpeg2vips.c index 5cd5c295..26accc40 100644 --- a/libvips/foreign/jpeg2vips.c +++ b/libvips/foreign/jpeg2vips.c @@ -102,6 +102,12 @@ * - shut down the input file as soon as we can [kleisauke] * 20/7/19 * - close input on minimise rather than Y read position + * 3/10/19 + * - restart after minimise + * 14/10/19 + * - revise for source IO + * 5/5/20 angelmixu + * - better handling of JFIF res unit 0 */ /* @@ -167,10 +173,6 @@ typedef struct _ReadJpeg { */ gboolean fail; - /* Used for file input only. - */ - char *filename; - struct jpeg_decompress_struct cinfo; ErrorManager eman; gboolean invert_pels; @@ -190,24 +192,116 @@ typedef struct _ReadJpeg { */ int output_width; int output_height; + + /* The source we read from. + */ + VipsSource *source; + } ReadJpeg; -/* This can be called many times. +#define SOURCE_BUFFER_SIZE (4096) + +/* Private struct for source input. */ +typedef struct { + /* Public jpeg fields. + */ + struct jpeg_source_mgr pub; + + /* Private stuff during read. + */ + VipsSource *source; + unsigned char buf[SOURCE_BUFFER_SIZE]; + +} Source; + static void -readjpeg_close_input( ReadJpeg *jpeg ) +source_init_source( j_decompress_ptr cinfo ) { - VIPS_FREEF( fclose, jpeg->eman.fp ); + Source *src = (Source *) cinfo->src; - /* Don't call jpeg_finish_decompress(). It just checks the tail of the - * file and who cares about that. All mem is freed in - * jpeg_destroy_decompress(). + /* Start off empty ... libjpeg will call fill_input_buffer to get the + * first bytes. */ + src->pub.next_input_byte = src->buf; + src->pub.bytes_in_buffer = 0; +} - /* I don't think this can fail. It's harmless to call many times. - */ - jpeg_destroy_decompress( &jpeg->cinfo ); +/* Fill the input buffer --- called whenever buffer is emptied. + */ +static boolean +source_fill_input_buffer( j_decompress_ptr cinfo ) +{ + static const JOCTET eoi_buffer[4] = { + (JOCTET) 0xFF, (JOCTET) JPEG_EOI, 0, 0 + }; + Source *src = (Source *) cinfo->src; + + size_t read; + + if( (read = vips_source_read( src->source, + src->buf, SOURCE_BUFFER_SIZE )) > 0 ) { + src->pub.next_input_byte = src->buf; + src->pub.bytes_in_buffer = read; + } + else { + WARNMS( cinfo, JWRN_JPEG_EOF ); + src->pub.next_input_byte = eoi_buffer; + src->pub.bytes_in_buffer = 2; + } + + return( TRUE ); +} + +static void +skip_input_data( j_decompress_ptr cinfo, long num_bytes ) +{ + Source *src = (Source *) cinfo->src; + + if( num_bytes > 0 ) { + while (num_bytes > (long) src->pub.bytes_in_buffer) { + num_bytes -= (long) src->pub.bytes_in_buffer; + (void) (*src->pub.fill_input_buffer) (cinfo); + + /* note we assume that fill_input_buffer will never + * return FALSE, so suspension need not be handled. + */ + } + + src->pub.next_input_byte += (size_t) num_bytes; + src->pub.bytes_in_buffer -= (size_t) num_bytes; + } +} + +static int +readjpeg_open_input( ReadJpeg *jpeg ) +{ + j_decompress_ptr cinfo = &jpeg->cinfo; + + if( jpeg->source && + !cinfo->src ) { + Source *src; + + if( vips_source_rewind( jpeg->source ) ) + return( -1 ); + + cinfo->src = (struct jpeg_source_mgr *) + (*cinfo->mem->alloc_small)( + (j_common_ptr) cinfo, JPOOL_PERMANENT, + sizeof( Source ) ); + + src = (Source *) cinfo->src; + src->source = jpeg->source; + src->pub.init_source = source_init_source; + src->pub.fill_input_buffer = source_fill_input_buffer; + src->pub.resync_to_restart = jpeg_resync_to_restart; + src->pub.skip_input_data = skip_input_data; + src->pub.bytes_in_buffer = 0; + src->pub.next_input_byte = src->buf; + } + + return( 0 ); } /* This can be called many times. @@ -225,9 +319,16 @@ readjpeg_free( ReadJpeg *jpeg ) jpeg->eman.pub.num_warnings = 0; } - readjpeg_close_input( jpeg ); + /* Don't call jpeg_finish_decompress(). It just checks the tail of the + * file and who cares about that. All mem is freed in + * jpeg_destroy_decompress(). + */ - VIPS_FREE( jpeg->filename ); + /* I don't think this can fail. It's harmless to call many times. + */ + jpeg_destroy_decompress( &jpeg->cinfo ); + + VIPS_UNREF( jpeg->source ); return( 0 ); } @@ -239,22 +340,24 @@ readjpeg_close_cb( VipsObject *object, ReadJpeg *jpeg ) } static void -readjpeg_minimise_cb( VipsObject *object, ReadJpeg *jpeg ) +readjpeg_minimise_cb( VipsImage *image, ReadJpeg *jpeg ) { - readjpeg_close_input( jpeg ); + vips_source_minimise( jpeg->source ); } static ReadJpeg * -readjpeg_new( VipsImage *out, int shrink, gboolean fail, gboolean autorotate ) +readjpeg_new( VipsSource *source, VipsImage *out, + int shrink, gboolean fail, gboolean autorotate ) { ReadJpeg *jpeg; if( !(jpeg = VIPS_NEW( out, ReadJpeg )) ) return( NULL ); + jpeg->source = source; + g_object_ref( source ); jpeg->shrink = shrink; jpeg->fail = fail; - jpeg->filename = NULL; jpeg->cinfo.err = jpeg_std_error( &jpeg->eman.pub ); jpeg->eman.pub.error_exit = vips__new_error_exit; jpeg->eman.pub.output_message = vips__new_output_message; @@ -283,19 +386,6 @@ readjpeg_new( VipsImage *out, int shrink, gboolean fail, gboolean autorotate ) return( jpeg ); } -/* Set input to a file. - */ -static int -readjpeg_file( ReadJpeg *jpeg, const char *filename ) -{ - jpeg->filename = g_strdup( filename ); - if( !(jpeg->eman.fp = vips__file_open_read( filename, NULL, FALSE )) ) - return( -1 ); - jpeg_stdio_src( &jpeg->cinfo, jpeg->eman.fp ); - - return( 0 ); -} - static const char * find_chroma_subsample( struct jpeg_decompress_struct *cinfo ) { @@ -326,7 +416,8 @@ attach_blob( VipsImage *im, const char *field, void *data, size_t data_length ) } #ifdef DEBUG - printf( "attach_blob: attaching %d bytes of %s\n", data_length, field ); + printf( "attach_blob: attaching %zd bytes of %s\n", + data_length, field ); #endif /*DEBUG*/ vips_image_set_blob_copy( im, field, data, data_length ); @@ -415,24 +506,28 @@ read_jpeg_header( ReadJpeg *jpeg, VipsImage *out ) break; } - /* Get the jfif resolution. exif may overwrite this later. + /* Get the jfif resolution. exif may overwrite this later. Default to + * 72dpi (as EXIF does). */ - xres = 1.0; - yres = 1.0; + xres = 72.0 / 25.4; + yres = 72.0 / 25.4; if( cinfo->saw_JFIF_marker && cinfo->X_density != 1U && cinfo->Y_density != 1U ) { #ifdef DEBUG - printf( "read_jpeg_header: seen jfif _density %d, %d\n", - cinfo->X_density, cinfo->Y_density ); + printf( "read_jpeg_header: jfif _density %d, %d, unit %d\n", + cinfo->X_density, cinfo->Y_density, + cinfo->density_unit ); #endif /*DEBUG*/ switch( cinfo->density_unit ) { case 0: - /* None. Just set. + /* X_density / Y_density gives the pixel aspect ratio. + * Leave xres, but adjust yres. */ - xres = cinfo->X_density; - yres = cinfo->Y_density; + if( cinfo->Y_density > 0 ) + yres = xres * cinfo->X_density / + cinfo->Y_density; break; case 1: @@ -469,6 +564,9 @@ read_jpeg_header( ReadJpeg *jpeg, VipsImage *out ) interpretation, xres, yres ); + VIPS_SETSTR( out->filename, + vips_connection_filename( VIPS_CONNECTION( jpeg->source ) ) ); + vips_image_pipelinev( out, VIPS_DEMAND_STYLE_FATSTRIP, NULL ); /* cinfo->output_width and cinfo->output_height round up with @@ -687,7 +785,7 @@ read_jpeg_generate( VipsRegion *or, VIPS_GATE_STOP( "read_jpeg_generate: work" ); #ifdef DEBUG - printf( "read_jpeg_generate: lonjmp() exit\n" ); + printf( "read_jpeg_generate: longjmp() exit\n" ); #endif /*DEBUG*/ return( -1 ); @@ -727,46 +825,11 @@ read_jpeg_generate( VipsRegion *or, jpeg->y_pos += 1; } - /* Shut down the input early if we can. - */ - if( jpeg->y_pos >= or->im->Ysize ) - readjpeg_close_input( jpeg ); - VIPS_GATE_STOP( "read_jpeg_generate: work" ); return( 0 ); } -/* Auto-rotate, if rotate_image is set. - */ -static VipsImage * -read_jpeg_rotate( VipsObject *process, VipsImage *im ) -{ - VipsImage **t = (VipsImage **) vips_object_local_array( process, 2 ); - VipsAngle angle = vips_autorot_get_angle( im ); - - if( angle != VIPS_ANGLE_D0 ) { - /* Need to copy to memory or disc, we have to stay seq. - */ - const guint64 image_size = VIPS_IMAGE_SIZEOF_IMAGE( im ); - const guint64 disc_threshold = vips_get_disc_threshold(); - - if( image_size > disc_threshold ) - t[0] = vips_image_new_temp_file( "%s.v" ); - else - t[0] = vips_image_new_memory(); - - if( vips_image_write( im, t[0] ) || - vips_rot( t[0], &t[1], angle, NULL ) ) - return( NULL ); - im = t[1]; - - vips_autorot_remove_angle( im ); - } - - return( im ); -} - /* Read a cinfo to a VIPS image. */ static int @@ -774,7 +837,7 @@ read_jpeg_image( ReadJpeg *jpeg, VipsImage *out ) { struct jpeg_decompress_struct *cinfo = &jpeg->cinfo; VipsImage **t = (VipsImage **) - vips_object_local_array( VIPS_OBJECT( out ), 3 ); + vips_object_local_array( VIPS_OBJECT( out ), 4 ); VipsImage *im; @@ -805,10 +868,16 @@ read_jpeg_image( ReadJpeg *jpeg, VipsImage *out ) vips_extract_area( t[1], &t[2], 0, 0, jpeg->output_width, jpeg->output_height, NULL ) ) return( -1 ); - im = t[2]; - if( jpeg->autorotate ) - im = read_jpeg_rotate( VIPS_OBJECT( out ), im ); + + if( jpeg->autorotate && + vips_image_get_orientation( im ) != 1 ) { + /* This will go via a huge memory buffer :-( + */ + if( vips_autorot( im, &t[3], NULL ) ) + return( -1 ); + im = t[3]; + } if( vips_image_write( im, out ) ) return( -1 ); @@ -816,8 +885,6 @@ read_jpeg_image( ReadJpeg *jpeg, VipsImage *out ) return( 0 ); } -/* Read the jpeg from file or buffer. - */ static int vips__jpeg_read( ReadJpeg *jpeg, VipsImage *out, gboolean header_only ) { @@ -853,15 +920,9 @@ vips__jpeg_read( ReadJpeg *jpeg, VipsImage *out, gboolean header_only ) /* Swap width and height if we're going to rotate this image. */ - if( jpeg->autorotate ) { - VipsAngle angle = vips_autorot_get_angle( out ); - - if( angle == VIPS_ANGLE_D90 || - angle == VIPS_ANGLE_D270 ) - VIPS_SWAP( int, out->Xsize, out->Ysize ); - - /* We won't be returning an orientation tag. - */ + if( jpeg->autorotate && + vips_image_get_orientation_swap( out ) ) { + VIPS_SWAP( int, out->Xsize, out->Ysize ); vips_autorot_remove_angle( out ); } } @@ -873,223 +934,40 @@ vips__jpeg_read( ReadJpeg *jpeg, VipsImage *out, gboolean header_only ) return( 0 ); } -/* Read a JPEG file into a VIPS image. - */ int -vips__jpeg_read_file( const char *filename, VipsImage *out, - gboolean header_only, int shrink, gboolean fail, - gboolean autorotate ) -{ - ReadJpeg *jpeg; - - if( !(jpeg = readjpeg_new( out, shrink, fail, autorotate )) ) - return( -1 ); - - /* Here for longjmp() from vips__new_error_exit() during startup. - */ - if( setjmp( jpeg->eman.jmp ) ) - return( -1 ); - - /* Set input to file. - */ - if( readjpeg_file( jpeg, filename ) ) - return( -1 ); - - if( vips__jpeg_read( jpeg, out, header_only ) ) - return( -1 ); - - VIPS_SETSTR( out->filename, filename ); - - /* We can kill off the decompress early if this is just a header read. - * This saves an fd during read. - */ - if( header_only ) - readjpeg_free( jpeg ); - - return( 0 ); -} - -/* Just like the above, but we read from a memory buffer. - */ -typedef struct { - /* Public jpeg fields. - */ - struct jpeg_source_mgr pub; - - /* Private stuff during read. - */ - const JOCTET *buf; - size_t len; -} InputBuffer; - -static void -init_source (j_decompress_ptr cinfo) -{ - /* no work necessary here */ -} - -/* - * Fill the input buffer --- called whenever buffer is emptied. - * - * We fill the buffer on readjpeg_buffer(), so this will only be called if - * libjpeg tries to read beyond the buffer. - */ - -static boolean -fill_input_buffer (j_decompress_ptr cinfo) -{ - static const JOCTET eoi_buffer[4] = { - (JOCTET) 0xFF, (JOCTET) JPEG_EOI, 0, 0 - }; - - InputBuffer *src = (InputBuffer *) cinfo->src; - - WARNMS(cinfo, JWRN_JPEG_EOF); - src->pub.next_input_byte = eoi_buffer; - src->pub.bytes_in_buffer = 2; - - return TRUE; -} - -/* - * Skip data --- used to skip over a potentially large amount of - * uninteresting data (such as an APPn marker). - * - * Writers of suspendable-input applications must note that skip_input_data - * is not granted the right to give a suspension return. If the skip extends - * beyond the data currently in the buffer, the buffer can be marked empty so - * that the next read will cause a fill_input_buffer call that can suspend. - * Arranging for additional bytes to be discarded before reloading the input - * buffer is the application writer's problem. - */ - -static void -skip_input_data (j_decompress_ptr cinfo, long num_bytes) -{ - InputBuffer *src = (InputBuffer *) cinfo->src; - - if (num_bytes > 0) { - while (num_bytes > (long) src->pub.bytes_in_buffer) { - num_bytes -= (long) src->pub.bytes_in_buffer; - (void) (*src->pub.fill_input_buffer) (cinfo); - - /* note we assume that fill_input_buffer will never return FALSE, - * so suspension need not be handled. - */ - } - - src->pub.next_input_byte += (size_t) num_bytes; - src->pub.bytes_in_buffer -= (size_t) num_bytes; - } -} - -/* - * An additional method that can be provided by data source modules is the - * resync_to_restart method for error recovery in the presence of RST markers. - * For the moment, this source module just uses the default resync method - * provided by the JPEG library. That method assumes that no backtracking - * is possible. - */ - -/* - * Terminate source --- called by jpeg_finish_decompress - * after all data has been read. Often a no-op. - * - * NB: *not* called by jpeg_abort or jpeg_destroy; surrounding - * application must deal with any cleanup that should happen even - * for error exit. - */ - -static void -term_source (j_decompress_ptr cinfo) -{ - /* no work necessary here */ -} - -/* - * Prepare for input from a memory buffer. The caller needs to free the - * buffer after decompress is done, we don't take ownership. - */ - -static void -readjpeg_buffer (ReadJpeg *jpeg, const void *buf, size_t len) -{ - j_decompress_ptr cinfo = &jpeg->cinfo; - InputBuffer *src; - - /* Empty buffer is a fatal error. - */ - if (len == 0 || buf == NULL) - ERREXIT(cinfo, JERR_INPUT_EMPTY); - - /* The source object and input buffer are made permanent so that a series - * of JPEG images can be read from the same file by calling jpeg_stdio_src - * only before the first one. (If we discarded the buffer at the end of - * one image, we'd likely lose the start of the next one.) - * This makes it unsafe to use this manager and a different source - * manager serially with the same JPEG object. Caveat programmer. - */ - if (cinfo->src == NULL) { /* first time for this JPEG object? */ - cinfo->src = (struct jpeg_source_mgr *) - (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, - sizeof(InputBuffer)); - src = (InputBuffer *) cinfo->src; - src->buf = buf; - src->len = len; - } - - src = (InputBuffer *) cinfo->src; - src->pub.init_source = init_source; - src->pub.fill_input_buffer = fill_input_buffer; - src->pub.skip_input_data = skip_input_data; - src->pub.resync_to_restart = jpeg_resync_to_restart; /* use default method */ - src->pub.term_source = term_source; - src->pub.bytes_in_buffer = len; - src->pub.next_input_byte = buf; -} - -int -vips__jpeg_read_buffer( const void *buf, size_t len, VipsImage *out, +vips__jpeg_read_source( VipsSource *source, VipsImage *out, gboolean header_only, int shrink, int fail, gboolean autorotate ) { ReadJpeg *jpeg; - if( !(jpeg = readjpeg_new( out, shrink, fail, autorotate )) ) + if( !(jpeg = readjpeg_new( source, out, shrink, fail, autorotate )) ) return( -1 ); if( setjmp( jpeg->eman.jmp ) ) return( -1 ); - /* Set input to buffer. - */ - readjpeg_buffer( jpeg, buf, len ); - - if( vips__jpeg_read( jpeg, out, header_only ) ) + if( readjpeg_open_input( jpeg ) || + vips__jpeg_read( jpeg, out, header_only ) ) return( -1 ); - return( 0 ); -} - -int -vips__isjpeg_buffer( const void *buf, size_t len ) -{ - const guchar *str = (const guchar *) buf; - - if( len >= 2 && - str[0] == 0xff && - str[1] == 0xd8 ) - return( 1 ); + if( header_only ) + vips_source_minimise( source ); + else { + if( vips_source_decode( source ) ) + return( -1 ); + } return( 0 ); } int -vips__isjpeg( const char *filename ) +vips__isjpeg_source( VipsSource *source ) { - unsigned char buf[2]; + const unsigned char *p; - if( vips__get_bytes( filename, buf, 2 ) == 2 && - vips__isjpeg_buffer( buf, 2 ) ) + if( (p = vips_source_sniff( source, 2 )) && + p[0] == 0xff && + p[1] == 0xd8 ) return( 1 ); return( 0 ); diff --git a/libvips/foreign/jpegload.c b/libvips/foreign/jpegload.c index 742ee435..5eed90c6 100644 --- a/libvips/foreign/jpegload.c +++ b/libvips/foreign/jpegload.c @@ -33,11 +33,6 @@ */ -/* -#define DEBUG_VERBOSE -#define DEBUG - */ - #ifdef HAVE_CONFIG_H #include #endif /*HAVE_CONFIG_H*/ @@ -73,6 +68,10 @@ typedef struct _VipsForeignLoadJpeg { VipsForeignLoad parent_object; + /* Set by subclasses. + */ + VipsSource *source; + /* Shrink by this much during load. */ int shrink; @@ -88,12 +87,15 @@ typedef VipsForeignLoadClass VipsForeignLoadJpegClass; G_DEFINE_ABSTRACT_TYPE( VipsForeignLoadJpeg, vips_foreign_load_jpeg, VIPS_TYPE_FOREIGN_LOAD ); -static VipsForeignFlags -vips_foreign_load_jpeg_get_flags( VipsForeignLoad *load ) +static void +vips_foreign_load_jpeg_dispose( GObject *gobject ) { - /* The jpeg reader supports sequential read. - */ - return( VIPS_FOREIGN_SEQUENTIAL ); + VipsForeignLoadJpeg *jpeg = (VipsForeignLoadJpeg *) gobject; + + VIPS_UNREF( jpeg->source ); + + G_OBJECT_CLASS( vips_foreign_load_jpeg_parent_class )-> + dispose( gobject ); } static int @@ -117,13 +119,52 @@ vips_foreign_load_jpeg_build( VipsObject *object ) return( 0 ); } +static VipsForeignFlags +vips_foreign_load_jpeg_get_flags( VipsForeignLoad *load ) +{ + return( VIPS_FOREIGN_SEQUENTIAL ); +} + +static VipsForeignFlags +vips_foreign_load_jpeg_get_flags_filename( const char *filename ) +{ + return( VIPS_FOREIGN_SEQUENTIAL ); +} + +static int +vips_foreign_load_jpeg_header( VipsForeignLoad *load ) +{ + VipsForeignLoadJpeg *jpeg = (VipsForeignLoadJpeg *) load; + + if( vips__jpeg_read_source( jpeg->source, + load->out, TRUE, jpeg->shrink, load->fail, jpeg->autorotate ) ) + return( -1 ); + + return( 0 ); +} + +static int +vips_foreign_load_jpeg_load( VipsForeignLoad *load ) +{ + VipsForeignLoadJpeg *jpeg = (VipsForeignLoadJpeg *) load; + + if( vips__jpeg_read_source( jpeg->source, + load->real, FALSE, jpeg->shrink, load->fail, + jpeg->autorotate ) ) + return( -1 ); + + return( 0 ); +} + static void vips_foreign_load_jpeg_class_init( VipsForeignLoadJpegClass *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->dispose = vips_foreign_load_jpeg_dispose; gobject_class->set_property = vips_object_set_property; gobject_class->get_property = vips_object_get_property; @@ -131,7 +172,15 @@ vips_foreign_load_jpeg_class_init( VipsForeignLoadJpegClass *class ) object_class->description = _( "load jpeg" ); object_class->build = vips_foreign_load_jpeg_build; + /* We are fast at is_a(), so high priority. + */ + foreign_class->priority = 50; + + load_class->get_flags_filename = + vips_foreign_load_jpeg_get_flags_filename; load_class->get_flags = vips_foreign_load_jpeg_get_flags; + load_class->header = vips_foreign_load_jpeg_header; + load_class->load = vips_foreign_load_jpeg_load; VIPS_ARG_INT( class, "shrink", 20, _( "Shrink" ), @@ -155,11 +204,77 @@ vips_foreign_load_jpeg_init( VipsForeignLoadJpeg *jpeg ) jpeg->shrink = 1; } +typedef struct _VipsForeignLoadJpegSource { + VipsForeignLoadJpeg parent_object; + + VipsSource *source; + +} VipsForeignLoadJpegSource; + +typedef VipsForeignLoadJpegClass VipsForeignLoadJpegSourceClass; + +G_DEFINE_TYPE( VipsForeignLoadJpegSource, vips_foreign_load_jpeg_source, + vips_foreign_load_jpeg_get_type() ); + +static int +vips_foreign_load_jpeg_source_build( VipsObject *object ) +{ + VipsForeignLoadJpeg *jpeg = (VipsForeignLoadJpeg *) object; + VipsForeignLoadJpegSource *source = + (VipsForeignLoadJpegSource *) object; + + if( source->source ) { + jpeg->source = source->source; + g_object_ref( jpeg->source ); + } + + if( VIPS_OBJECT_CLASS( vips_foreign_load_jpeg_source_parent_class )-> + build( object ) ) + return( -1 ); + + return( 0 ); +} + +static gboolean +vips_foreign_load_jpeg_source_is_a_source( VipsSource *source ) +{ + return( vips__isjpeg_source( source ) ); +} + +static void +vips_foreign_load_jpeg_source_class_init( + VipsForeignLoadJpegSourceClass *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 = "jpegload_source"; + object_class->description = _( "load image from jpeg source" ); + object_class->build = vips_foreign_load_jpeg_source_build; + + load_class->is_a_source = vips_foreign_load_jpeg_source_is_a_source; + + VIPS_ARG_OBJECT( class, "source", 1, + _( "Source" ), + _( "Source to load from" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadJpegSource, source ), + VIPS_TYPE_SOURCE ); + +} + +static void +vips_foreign_load_jpeg_source_init( VipsForeignLoadJpegSource *source ) +{ +} + typedef struct _VipsForeignLoadJpegFile { VipsForeignLoadJpeg parent_object; - /* Filename for load. - */ char *filename; } VipsForeignLoadJpegFile; @@ -169,36 +284,36 @@ typedef VipsForeignLoadJpegClass VipsForeignLoadJpegFileClass; G_DEFINE_TYPE( VipsForeignLoadJpegFile, vips_foreign_load_jpeg_file, vips_foreign_load_jpeg_get_type() ); +static int +vips_foreign_load_jpeg_file_build( VipsObject *object ) +{ + VipsForeignLoadJpeg *jpeg = (VipsForeignLoadJpeg *) object; + VipsForeignLoadJpegFile *file = (VipsForeignLoadJpegFile *) object; + + if( file->filename && + !(jpeg->source = + vips_source_new_from_file( file->filename )) ) + return( -1 ); + + if( VIPS_OBJECT_CLASS( vips_foreign_load_jpeg_file_parent_class )-> + build( object ) ) + return( -1 ); + + return( 0 ); +} + static gboolean vips_foreign_load_jpeg_file_is_a( const char *filename ) { - return( vips__isjpeg( filename ) ); -} + VipsSource *source; + gboolean result; -static int -vips_foreign_load_jpeg_file_header( VipsForeignLoad *load ) -{ - VipsForeignLoadJpeg *jpeg = (VipsForeignLoadJpeg *) load; - VipsForeignLoadJpegFile *file = (VipsForeignLoadJpegFile *) load; + if( !(source = vips_source_new_from_file( filename )) ) + return( FALSE ); + result = vips_foreign_load_jpeg_source_is_a_source( source ); + VIPS_UNREF( source ); - if( vips__jpeg_read_file( file->filename, load->out, - TRUE, jpeg->shrink, load->fail, jpeg->autorotate ) ) - return( -1 ); - - return( 0 ); -} - -static int -vips_foreign_load_jpeg_file_load( VipsForeignLoad *load ) -{ - VipsForeignLoadJpeg *jpeg = (VipsForeignLoadJpeg *) load; - VipsForeignLoadJpegFile *file = (VipsForeignLoadJpegFile *) load; - - if( vips__jpeg_read_file( file->filename, load->real, - FALSE, jpeg->shrink, load->fail, jpeg->autorotate ) ) - return( -1 ); - - return( 0 ); + return( result ); } static void @@ -214,16 +329,11 @@ vips_foreign_load_jpeg_file_class_init( VipsForeignLoadJpegFileClass *class ) object_class->nickname = "jpegload"; object_class->description = _( "load jpeg from file" ); + object_class->build = vips_foreign_load_jpeg_file_build; foreign_class->suffs = vips__jpeg_suffs; - /* We are fast at is_a(), so high priority. - */ - foreign_class->priority = 50; - load_class->is_a = vips_foreign_load_jpeg_file_is_a; - load_class->header = vips_foreign_load_jpeg_file_header; - load_class->load = vips_foreign_load_jpeg_file_load; VIPS_ARG_STRING( class, "filename", 1, _( "Filename" ), @@ -241,9 +351,7 @@ vips_foreign_load_jpeg_file_init( VipsForeignLoadJpegFile *file ) typedef struct _VipsForeignLoadJpegBuffer { VipsForeignLoadJpeg parent_object; - /* Load from a buffer. - */ - VipsArea *buf; + VipsBlob *blob; } VipsForeignLoadJpegBuffer; @@ -253,35 +361,37 @@ G_DEFINE_TYPE( VipsForeignLoadJpegBuffer, vips_foreign_load_jpeg_buffer, vips_foreign_load_jpeg_get_type() ); static int -vips_foreign_load_jpeg_buffer_header( VipsForeignLoad *load ) +vips_foreign_load_jpeg_buffer_build( VipsObject *object ) { - VipsForeignLoadJpeg *jpeg = (VipsForeignLoadJpeg *) load; - VipsForeignLoadJpegBuffer *buffer = (VipsForeignLoadJpegBuffer *) load; + VipsForeignLoadJpeg *jpeg = (VipsForeignLoadJpeg *) object; + VipsForeignLoadJpegBuffer *buffer = + (VipsForeignLoadJpegBuffer *) object; - if( vips__jpeg_read_buffer( buffer->buf->data, buffer->buf->length, - load->out, TRUE, jpeg->shrink, load->fail, jpeg->autorotate ) ) + if( buffer->blob && + !(jpeg->source = vips_source_new_from_memory( + VIPS_AREA( buffer->blob )->data, + VIPS_AREA( buffer->blob )->length )) ) return( -1 ); - return( 0 ); -} - -static int -vips_foreign_load_jpeg_buffer_load( VipsForeignLoad *load ) -{ - VipsForeignLoadJpeg *jpeg = (VipsForeignLoadJpeg *) load; - VipsForeignLoadJpegBuffer *buffer = (VipsForeignLoadJpegBuffer *) load; - - if( vips__jpeg_read_buffer( buffer->buf->data, buffer->buf->length, - load->real, FALSE, jpeg->shrink, load->fail, jpeg->autorotate ) ) + if( VIPS_OBJECT_CLASS( vips_foreign_load_jpeg_buffer_parent_class )-> + build( object ) ) return( -1 ); return( 0 ); } static gboolean -vips_foreign_load_jpeg_buffer_is_a( const void *buf, size_t len ) +vips_foreign_load_jpeg_buffer_is_a_buffer( const void *buf, size_t len ) { - return( vips__isjpeg_buffer( buf, len ) ); + VipsSource *source; + gboolean result; + + if( !(source = vips_source_new_from_memory( buf, len )) ) + return( FALSE ); + result = vips_foreign_load_jpeg_source_is_a_source( source ); + VIPS_UNREF( source ); + + return( result ); } static void @@ -297,16 +407,15 @@ vips_foreign_load_jpeg_buffer_class_init( object_class->nickname = "jpegload_buffer"; object_class->description = _( "load jpeg from buffer" ); + object_class->build = vips_foreign_load_jpeg_buffer_build; - load_class->is_a_buffer = vips_foreign_load_jpeg_buffer_is_a; - load_class->header = vips_foreign_load_jpeg_buffer_header; - load_class->load = vips_foreign_load_jpeg_buffer_load; + load_class->is_a_buffer = vips_foreign_load_jpeg_buffer_is_a_buffer; VIPS_ARG_BOXED( class, "buffer", 1, _( "Buffer" ), _( "Buffer to load from" ), VIPS_ARGUMENT_REQUIRED_INPUT, - G_STRUCT_OFFSET( VipsForeignLoadJpegBuffer, buf ), + G_STRUCT_OFFSET( VipsForeignLoadJpegBuffer, blob ), VIPS_TYPE_BLOB ); } diff --git a/libvips/foreign/jpegsave.c b/libvips/foreign/jpegsave.c index 3160bde9..e1cca6c1 100644 --- a/libvips/foreign/jpegsave.c +++ b/libvips/foreign/jpegsave.c @@ -2,6 +2,8 @@ * * 24/11/11 * - wrap a class around the jpeg writer + * 18/2/20 Elad-Laufer + * - add subsample_mode, deprecate no_subsample */ /* @@ -54,20 +56,6 @@ #ifdef HAVE_JPEG -#ifdef HAVE_EXIF -#ifdef UNTAGGED_EXIF -#include -#include -#include -#include -#else /*!UNTAGGED_EXIF*/ -#include -#include -#include -#include -#endif /*UNTAGGED_EXIF*/ -#endif /*HAVE_EXIF*/ - typedef struct _VipsForeignSaveJpeg { VipsForeignSave parent_object; @@ -87,10 +75,17 @@ typedef struct _VipsForeignSaveJpeg { */ gboolean interlace; - /* Disable chroma subsampling. + /* Deprecated: Disable chroma subsampling. Use subsample_mode instead. */ gboolean no_subsample; + /* Select chroma subsampling mode: + * auto will disable subsampling for Q >= 90 + * on will always enable subsampling + * off will always disable subsampling + */ + VipsForeignJpegSubsample subsample_mode; + /* Apply trellis quantisation to each 8x8 block. */ gboolean trellis_quant; @@ -120,10 +115,30 @@ G_DEFINE_ABSTRACT_TYPE( VipsForeignSaveJpeg, vips_foreign_save_jpeg, /* Type promotion for save ... just always go to uchar. */ static int bandfmt_jpeg[10] = { -/* UC C US S UI I F X D DX */ - UC, UC, UC, UC, UC, UC, UC, UC, UC, UC + /* UC C US S UI I F X D DX */ + UC, UC, UC, UC, UC, UC, UC, UC, UC, UC }; +static int +vips_foreign_save_jpeg_build( VipsObject *object ) +{ + VipsForeignSaveJpeg *jpeg = (VipsForeignSaveJpeg *) object; + + if( VIPS_OBJECT_CLASS( vips_foreign_save_jpeg_parent_class )-> + build( object ) ) + return( -1 ); + + /* no_subsample is deprecated, but we retain backwards compatibility + * new code should use subsample_mode + */ + if( vips_object_argument_isset( object, "no_subsample" ) ) + jpeg->subsample_mode = jpeg->no_subsample ? + VIPS_FOREIGN_JPEG_SUBSAMPLE_OFF : + VIPS_FOREIGN_JPEG_SUBSAMPLE_AUTO; + + return( 0 ); +} + static void vips_foreign_save_jpeg_class_init( VipsForeignSaveJpegClass *class ) { @@ -137,6 +152,7 @@ vips_foreign_save_jpeg_class_init( VipsForeignSaveJpegClass *class ) object_class->nickname = "jpegsave_base"; object_class->description = _( "save jpeg" ); + object_class->build = vips_foreign_save_jpeg_build; foreign_class->suffs = vips__jpeg_suffs; @@ -176,7 +192,7 @@ vips_foreign_save_jpeg_class_init( VipsForeignSaveJpegClass *class ) VIPS_ARG_BOOL( class, "no_subsample", 14, _( "No subsample" ), _( "Disable chroma subsample" ), - VIPS_ARGUMENT_OPTIONAL_INPUT, + VIPS_ARGUMENT_OPTIONAL_INPUT | VIPS_ARGUMENT_DEPRECATED, G_STRUCT_OFFSET( VipsForeignSaveJpeg, no_subsample ), FALSE ); @@ -196,7 +212,7 @@ vips_foreign_save_jpeg_class_init( VipsForeignSaveJpegClass *class ) VIPS_ARG_BOOL( class, "optimize_scans", 17, _( "Optimize scans" ), - _( "Split the spectrum of DCT coefficients into separate scans" ), + _( "Split spectrum of DCT coefficients into separate scans" ), VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET( VipsForeignSaveJpeg, optimize_scans ), FALSE ); @@ -208,12 +224,81 @@ vips_foreign_save_jpeg_class_init( VipsForeignSaveJpegClass *class ) G_STRUCT_OFFSET( VipsForeignSaveJpeg, quant_table ), 0, 8, 0 ); + VIPS_ARG_ENUM( class, "subsample_mode", 19, + _( "Subsample mode" ), + _( "Select chroma subsample operation mode" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsForeignSaveJpeg, subsample_mode ), + VIPS_TYPE_FOREIGN_JPEG_SUBSAMPLE, + VIPS_FOREIGN_JPEG_SUBSAMPLE_AUTO ); } static void vips_foreign_save_jpeg_init( VipsForeignSaveJpeg *jpeg ) { jpeg->Q = 75; + jpeg->subsample_mode = VIPS_FOREIGN_JPEG_SUBSAMPLE_AUTO; +} + +typedef struct _VipsForeignSaveJpegTarget { + VipsForeignSaveJpeg parent_object; + + VipsTarget *target; + +} VipsForeignSaveJpegTarget; + +typedef VipsForeignSaveJpegClass VipsForeignSaveJpegTargetClass; + +G_DEFINE_TYPE( VipsForeignSaveJpegTarget, vips_foreign_save_jpeg_target, + vips_foreign_save_jpeg_get_type() ); + +static int +vips_foreign_save_jpeg_target_build( VipsObject *object ) +{ + VipsForeignSave *save = (VipsForeignSave *) object; + VipsForeignSaveJpeg *jpeg = (VipsForeignSaveJpeg *) object; + VipsForeignSaveJpegTarget *target = + (VipsForeignSaveJpegTarget *) object; + + if( VIPS_OBJECT_CLASS( vips_foreign_save_jpeg_target_parent_class )-> + build( object ) ) + return( -1 ); + + if( vips__jpeg_write_target( save->ready, target->target, + jpeg->Q, jpeg->profile, jpeg->optimize_coding, + jpeg->interlace, save->strip, jpeg->trellis_quant, + jpeg->overshoot_deringing, jpeg->optimize_scans, + jpeg->quant_table, jpeg->subsample_mode ) ) + return( -1 ); + + return( 0 ); +} + +static void +vips_foreign_save_jpeg_target_class_init( + VipsForeignSaveJpegTargetClass *class ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( class ); + VipsObjectClass *object_class = (VipsObjectClass *) class; + + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + object_class->nickname = "jpegsave_target"; + object_class->description = _( "save image to jpeg target" ); + object_class->build = vips_foreign_save_jpeg_target_build; + + VIPS_ARG_OBJECT( class, "target", 1, + _( "Target" ), + _( "Target to save to" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsForeignSaveJpegTarget, target ), + VIPS_TYPE_TARGET ); +} + +static void +vips_foreign_save_jpeg_target_init( VipsForeignSaveJpegTarget *target ) +{ } typedef struct _VipsForeignSaveJpegFile { @@ -237,16 +322,23 @@ vips_foreign_save_jpeg_file_build( VipsObject *object ) VipsForeignSaveJpeg *jpeg = (VipsForeignSaveJpeg *) object; VipsForeignSaveJpegFile *file = (VipsForeignSaveJpegFile *) object; + VipsTarget *target; + if( VIPS_OBJECT_CLASS( vips_foreign_save_jpeg_file_parent_class )-> build( object ) ) return( -1 ); - if( vips__jpeg_write_file( save->ready, file->filename, - jpeg->Q, jpeg->profile, jpeg->optimize_coding, - jpeg->interlace, save->strip, jpeg->no_subsample, - jpeg->trellis_quant, jpeg->overshoot_deringing, - jpeg->optimize_scans, jpeg->quant_table ) ) + if( !(target = vips_target_new_to_file( file->filename )) ) return( -1 ); + if( vips__jpeg_write_target( save->ready, target, + jpeg->Q, jpeg->profile, jpeg->optimize_coding, + jpeg->interlace, save->strip, jpeg->trellis_quant, + jpeg->overshoot_deringing, jpeg->optimize_scans, + jpeg->quant_table, jpeg->subsample_mode ) ) { + VIPS_UNREF( target ); + return( -1 ); + } + VIPS_UNREF( target ); return( 0 ); } @@ -298,27 +390,31 @@ vips_foreign_save_jpeg_buffer_build( VipsObject *object ) VipsForeignSaveJpeg *jpeg = (VipsForeignSaveJpeg *) object; VipsForeignSaveJpegBuffer *file = (VipsForeignSaveJpegBuffer *) object; - void *obuf; - size_t olen; + VipsTarget *target; VipsBlob *blob; if( VIPS_OBJECT_CLASS( vips_foreign_save_jpeg_buffer_parent_class )-> build( object ) ) return( -1 ); - if( vips__jpeg_write_buffer( save->ready, - &obuf, &olen, jpeg->Q, jpeg->profile, jpeg->optimize_coding, - jpeg->interlace, save->strip, jpeg->no_subsample, - jpeg->trellis_quant, jpeg->overshoot_deringing, - jpeg->optimize_scans, jpeg->quant_table ) ) + if( !(target = vips_target_new_to_memory()) ) return( -1 ); - /* obuf is a g_free() buffer, not vips_free(). - */ - blob = vips_blob_new( (VipsCallbackFn) g_free, obuf, olen ); + if( vips__jpeg_write_target( save->ready, target, + jpeg->Q, jpeg->profile, jpeg->optimize_coding, + jpeg->interlace, save->strip, jpeg->trellis_quant, + jpeg->overshoot_deringing, jpeg->optimize_scans, + jpeg->quant_table, jpeg->subsample_mode ) ) { + VIPS_UNREF( target ); + return( -1 ); + } + + g_object_get( target, "blob", &blob, NULL ); g_object_set( file, "buffer", blob, NULL ); vips_area_unref( VIPS_AREA( blob ) ); + VIPS_UNREF( target ); + return( 0 ); } @@ -365,30 +461,39 @@ vips_foreign_save_jpeg_mime_build( VipsObject *object ) VipsForeignSave *save = (VipsForeignSave *) object; VipsForeignSaveJpeg *jpeg = (VipsForeignSaveJpeg *) object; - void *obuf; + VipsTarget *target; + VipsBlob *blob; + const unsigned char *obuf; size_t olen; if( VIPS_OBJECT_CLASS( vips_foreign_save_jpeg_mime_parent_class )-> build( object ) ) return( -1 ); - if( vips__jpeg_write_buffer( save->ready, - &obuf, &olen, jpeg->Q, jpeg->profile, jpeg->optimize_coding, - jpeg->interlace, save->strip, jpeg->no_subsample, - jpeg->trellis_quant, jpeg->overshoot_deringing, - jpeg->optimize_scans, jpeg->quant_table ) ) + if( !(target = vips_target_new_to_memory()) ) return( -1 ); + if( vips__jpeg_write_target( save->ready, target, + jpeg->Q, jpeg->profile, jpeg->optimize_coding, + jpeg->interlace, save->strip, jpeg->trellis_quant, + jpeg->overshoot_deringing, jpeg->optimize_scans, + jpeg->quant_table, jpeg->subsample_mode ) ) { + VIPS_UNREF( target ); + return( -1 ); + } + + g_object_get( target, "blob", &blob, NULL ); + + obuf = vips_blob_get( blob, &olen ); printf( "Content-length: %zu\r\n", olen ); printf( "Content-type: image/jpeg\r\n" ); printf( "\r\n" ); - if( fwrite( obuf, sizeof( char ), olen, stdout ) != olen ) { - vips_error( "VipsJpeg", "%s", _( "error writing output" ) ); - return( -1 ); - } + (void) fwrite( obuf, sizeof( char ), olen, stdout ); fflush( stdout ); - g_free( obuf ); + vips_area_unref( VIPS_AREA( blob ) ); + + VIPS_UNREF( target ); return( 0 ); } @@ -424,7 +529,7 @@ vips_foreign_save_jpeg_mime_init( VipsForeignSaveJpegMime *mime ) * * @optimize_coding: %gboolean, compute optimal Huffman coding tables * * @interlace: %gboolean, write an interlaced (progressive) jpeg * * @strip: %gboolean, remove all metadata from image - * * @no_subsample: %gboolean, disable chroma subsampling + * * @subsample_mode: #VipsForeignJpegSubsample, chroma subsampling mode * * @trellis_quant: %gboolean, apply trellis quantisation to each 8x8 block * * @overshoot_deringing: %gboolean, overshoot samples with extreme values * * @optimize_scans: %gboolean, split DCT coefficients into separate scans @@ -450,10 +555,10 @@ vips_foreign_save_jpeg_mime_init( VipsForeignSaveJpegMime *mime ) * conection, but need much more memory to encode and decode. * * If @strip is set, no EXIF data, IPTC data, ICC profile or XMP metadata is - * written into the output file. + * written into the output file. * - * If @no_subsample is set, chrominance subsampling is disabled. This will - * improve quality at the cost of larger file size. Useful for high Q factors. + * Chroma subsampling is normally automatically disabled for Q > 90. You can + * force the subsampling mode with @@subsample_mode. * * If @trellis_quant is set and the version of libjpeg supports it * (e.g. mozjpeg >= 3.0), apply trellis quantisation to each 8x8 block. @@ -527,6 +632,44 @@ vips_jpegsave( VipsImage *in, const char *filename, ... ) return( result ); } +/** + * vips_jpegsave_target: (method) + * @in: image to save + * @target: save image to this target + * @...: %NULL-terminated list of optional named arguments + * + * Optional arguments: + * + * * @Q: %gint, quality factor + * * @profile: filename of ICC profile to attach + * * @optimize_coding: %gboolean, compute optimal Huffman coding tables + * * @interlace: %gboolean, write an interlaced (progressive) jpeg + * * @strip: %gboolean, remove all metadata from image + * * @subsample_mode: #VipsForeignJpegSubsample, chroma subsampling mode + * * @trellis_quant: %gboolean, apply trellis quantisation to each 8x8 block + * * @overshoot_deringing: %gboolean, overshoot samples with extreme values + * * @optimize_scans: %gboolean, split DCT coefficients into separate scans + * * @quant_table: %gint, quantization table index + * + * As vips_jpegsave(), but save to a target. + * + * See also: vips_jpegsave(), vips_image_write_to_target(). + * + * Returns: 0 on success, -1 on error. + */ +int +vips_jpegsave_target( VipsImage *in, VipsTarget *target, ... ) +{ + va_list ap; + int result; + + va_start( ap, target ); + result = vips_call_split( "jpegsave_target", ap, in, target ); + va_end( ap ); + + return( result ); +} + /** * vips_jpegsave_buffer: (method) * @in: image to save @@ -541,7 +684,7 @@ vips_jpegsave( VipsImage *in, const char *filename, ... ) * * @optimize_coding: %gboolean, compute optimal Huffman coding tables * * @interlace: %gboolean, write an interlaced (progressive) jpeg * * @strip: %gboolean, remove all metadata from image - * * @no_subsample: %gboolean, disable chroma subsampling + * * @subsample_mode: #VipsForeignJpegSubsample, chroma subsampling mode * * @trellis_quant: %gboolean, apply trellis quantisation to each 8x8 block * * @overshoot_deringing: %gboolean, overshoot samples with extreme values * * @optimize_scans: %gboolean, split DCT coefficients into separate scans @@ -597,7 +740,7 @@ vips_jpegsave_buffer( VipsImage *in, void **buf, size_t *len, ... ) * * @optimize_coding: %gboolean, compute optimal Huffman coding tables * * @interlace: %gboolean, write an interlaced (progressive) jpeg * * @strip: %gboolean, remove all metadata from image - * * @no_subsample: %gboolean, disable chroma subsampling + * * @subsample_mode: #VipsForeignJpegSubsample, chroma subsampling mode * * @trellis_quant: %gboolean, apply trellis quantisation to each 8x8 block * * @overshoot_deringing: %gboolean, overshoot samples with extreme values * * @optimize_scans: %gboolean, split DCT coefficients into separate scans diff --git a/libvips/foreign/magick.c b/libvips/foreign/magick.c index 9c4ab5d5..642e8b68 100644 --- a/libvips/foreign/magick.c +++ b/libvips/foreign/magick.c @@ -214,13 +214,14 @@ magick_optimize_image_layers( Image **images, ExceptionInfo *exception ) { Image *tmp; - tmp = OptimizePlusImageLayers(*images, exception ); + tmp = OptimizePlusImageLayers( *images, exception ); - if ( exception->severity != UndefinedException ) + if( exception->severity != UndefinedException ) { + VIPS_FREEF( DestroyImageList, tmp ); return MagickFalse; + } VIPS_FREEF( DestroyImageList, *images ); - *images = tmp; return MagickTrue; @@ -230,8 +231,9 @@ int magick_optimize_image_transparency( const Image *images, ExceptionInfo *exception ) { - OptimizeImageTransparency(images, exception); - return ( exception->severity == UndefinedException ); + OptimizeImageTransparency( images, exception ); + + return( exception->severity == UndefinedException ); } /* Does a few bytes look like a file IM can handle? @@ -285,10 +287,17 @@ int magick_set_image_size( Image *image, const size_t width, const size_t height, ExceptionInfo *exception ) { - (void) exception; #ifdef HAVE_SETIMAGEEXTENT - return( SetImageExtent( image, width, height ) ); + int result = SetImageExtent( image, width, height ); + + /* IM6 sets the exception on the image. + */ + if( !result ) + magick_inherit_exception( exception, image ); + + return( result ); #else /*!HAVE_SETIMAGEEXTENT*/ + (void) exception; image->columns = width; image->rows = height; @@ -577,21 +586,24 @@ static MagickColorspaceTypeNames magick_colorspace_names[] = { #ifdef HAVE_CMYCOLORSPACE { CMYColorspace, "CMYColorspace" }, { HCLColorspace, "HCLColorspace" }, - { HCLpColorspace, "HCLpColorspace" }, { HSBColorspace, "HSBColorspace" }, + { LabColorspace, "LabColorspace" }, + { LogColorspace, "LogColorspace" }, + { LuvColorspace, "LuvColorspace" }, +#endif /*HAVE_CMYCOLORSPACE*/ + +#ifdef HAVE_HCLPCOLORSPACE + { HCLpColorspace, "HCLpColorspace" }, { HSIColorspace, "HSIColorspace" }, { HSVColorspace, "HSVColorspace" }, - { LabColorspace, "LabColorspace" }, { LCHColorspace, "LCHColorspace" }, { LCHabColorspace, "LCHabColorspace" }, { LCHuvColorspace, "LCHuvColorspace" }, - { LogColorspace, "LogColorspace" }, { LMSColorspace, "LMSColorspace" }, - { LuvColorspace, "LuvColorspace" }, { scRGBColorspace, "scRGBColorspace" }, { xyYColorspace, "xyYColorspace" }, { YDbDrColorspace, "YDbDrColorspace" }, -#endif /*HAVE_CMYCOLORSPACE*/ +#endif /*HAVE_HCLPCOLORSPACE*/ /* im7 has this, I think * @@ -631,6 +643,8 @@ magick_vips_error( const char *domain, ExceptionInfo *exception ) static void * magick_genesis_cb( void *client ) { + ExceptionInfo *exception; + #ifdef DEBUG printf( "magick_genesis_cb:\n" ); #endif /*DEBUG*/ @@ -638,9 +652,18 @@ magick_genesis_cb( void *client ) #if defined(HAVE_MAGICKCOREGENESIS) || defined(HAVE_MAGICK7) MagickCoreGenesis( vips_get_argv0(), MagickFalse ); #else /*!HAVE_MAGICKCOREGENESIS*/ - InitializeMagick( "" ); + InitializeMagick( vips_get_argv0() ); #endif /*HAVE_MAGICKCOREGENESIS*/ + /* This forces *magick to init all loaders. We have to do this so we + * can sniff files with GetImageMagick(). + * + * We don't care about errors from magickinit. + */ + exception = magick_acquire_exception(); + (void) GetMagickInfo( "*", exception ); + magick_destroy_exception(exception); + return( NULL ); } diff --git a/libvips/foreign/magick2vips.c b/libvips/foreign/magick2vips.c index 3338dc26..d97b1c1f 100644 --- a/libvips/foreign/magick2vips.c +++ b/libvips/foreign/magick2vips.c @@ -377,14 +377,8 @@ parse_header( Read *read ) im->Type = VIPS_INTERPRETATION_B_W; break; - case RGBColorspace: - if( im->BandFmt == VIPS_FORMAT_USHORT ) - im->Type = VIPS_INTERPRETATION_RGB16; - else - im->Type = VIPS_INTERPRETATION_RGB; - break; - case sRGBColorspace: + case RGBColorspace: if( im->BandFmt == VIPS_FORMAT_USHORT ) im->Type = VIPS_INTERPRETATION_RGB16; else diff --git a/libvips/foreign/magick7load.c b/libvips/foreign/magick7load.c index 5c0ceb76..f7292888 100644 --- a/libvips/foreign/magick7load.c +++ b/libvips/foreign/magick7load.c @@ -477,14 +477,8 @@ vips_foreign_load_magick7_parse( VipsForeignLoadMagick7 *magick7, out->Type = VIPS_INTERPRETATION_B_W; break; - case RGBColorspace: - if( out->BandFmt == VIPS_FORMAT_USHORT ) - out->Type = VIPS_INTERPRETATION_RGB16; - else - out->Type = VIPS_INTERPRETATION_RGB; - break; - case sRGBColorspace: + case RGBColorspace: if( out->BandFmt == VIPS_FORMAT_USHORT ) out->Type = VIPS_INTERPRETATION_RGB16; else @@ -757,15 +751,13 @@ G_DEFINE_TYPE( VipsForeignLoadMagick7File, vips_foreign_load_magick7_file, static gboolean ismagick7( const char *filename ) { - /* Fetch the first 100 bytes. Hopefully that'll be enough. + /* Fetch up to the first 100 bytes. Hopefully that'll be enough. */ unsigned char buf[100]; + int len; - /* Files shorter than 100 bytes will leave nonsense at the end of buf, - * but it shouldn't matter. - */ - return( vips__get_bytes( filename, buf, 100 ) && - magick_ismagick( buf, 100 ) ); + return( (len = vips__get_bytes( filename, buf, 100 )) > 10 && + magick_ismagick( buf, len ) ); } static int @@ -849,7 +841,7 @@ G_DEFINE_TYPE( VipsForeignLoadMagick7Buffer, vips_foreign_load_magick7_buffer, static gboolean vips_foreign_load_magick7_buffer_is_a_buffer( const void *buf, size_t len ) { - return( magick_ismagick( (const unsigned char *) buf, len ) ); + return( len > 10 && magick_ismagick( (const unsigned char *) buf, len ) ); } static int diff --git a/libvips/foreign/magickload.c b/libvips/foreign/magickload.c index a1215a30..bfb26566 100644 --- a/libvips/foreign/magickload.c +++ b/libvips/foreign/magickload.c @@ -173,15 +173,13 @@ G_DEFINE_TYPE( VipsForeignLoadMagickFile, vips_foreign_load_magick_file, static gboolean ismagick( const char *filename ) { - /* Fetch the first 100 bytes. Hopefully that'll be enough. + /* Fetch up to the first 100 bytes. Hopefully that'll be enough. */ unsigned char buf[100]; + int len; - /* Files shorter than 100 bytes will leave nonsense at the end of buf, - * but it shouldn't matter. - */ - return( vips__get_bytes( filename, buf, 100 ) && - magick_ismagick( buf, 100 ) ); + return( (len = vips__get_bytes( filename, buf, 100 )) > 10 && + magick_ismagick( buf, len ) ); } /* Unfortunately, libMagick does not support header-only reads very well. See diff --git a/libvips/foreign/magicksave.c b/libvips/foreign/magicksave.c index 78142784..102252d9 100644 --- a/libvips/foreign/magicksave.c +++ b/libvips/foreign/magicksave.c @@ -149,8 +149,10 @@ vips_foreign_save_magick_next_image( VipsForeignSaveMagick *magick ) } if( !magick_set_image_size( image, - im->Xsize, magick->page_height, magick->exception ) ) + im->Xsize, magick->page_height, magick->exception ) ) { + magick_vips_error( class->nickname, magick->exception ); return( -1 ); + } /* Delay must be converted from milliseconds into centiseconds * as GIF image requires centiseconds. @@ -167,13 +169,21 @@ vips_foreign_save_magick_next_image( VipsForeignSaveMagick *magick ) * 1 - don't write the netscape extension block * 2 - loop once * 3 - loop twice etc. - * - * We have the simple gif meaning, so we must add one unless it's - * zero. */ - if( vips_image_get_typeof( im, "gif-loop" ) && - !vips_image_get_int( im, "gif-loop", &number ) ) - image->iterations = (size_t) (number ? number + 1 : 0); + if( vips_image_get_typeof( im, "loop" ) && + !vips_image_get_int( im, "loop", &number ) ) { + image->iterations = (size_t) number; + } + else { + /* DEPRECATED "gif-loop" + * + * We have the simple gif meaning, so we must add one unless + * it's zero. + */ + if( vips_image_get_typeof( im, "gif-loop" ) && + !vips_image_get_int( im, "gif-loop", &number ) ) + image->iterations = (size_t) (number ? number + 1 : 0); + } if( vips_image_get_typeof( im, "gif-comment" ) && !vips_image_get_string( im, "gif-comment", &str ) ) @@ -359,6 +369,7 @@ vips_foreign_save_magick_build( VipsObject *object ) * need it. */ if( vips_image_get_typeof( im, "delay" ) ) { + g_value_unset( &magick->delay_gvalue ); if( vips_image_get( im, "delay", &magick->delay_gvalue ) ) return( -1 ); magick->delays = vips_value_get_array_int( @@ -370,8 +381,10 @@ vips_foreign_save_magick_build( VipsObject *object ) return( -1 ); if( magick->optimize_gif_frames ) { - if( !magick_optimize_image_layers(&magick->images, magick->exception ) ) { - magick_inherit_exception( magick->exception, magick->images ); + if( !magick_optimize_image_layers( &magick->images, + magick->exception ) ) { + magick_inherit_exception( magick->exception, + magick->images ); magick_vips_error( class->nickname, magick->exception ); return( -1 ); @@ -379,8 +392,10 @@ vips_foreign_save_magick_build( VipsObject *object ) } if( magick->optimize_gif_transparency ) { - if( !magick_optimize_image_transparency(magick->images, magick->exception) ) { - magick_inherit_exception( magick->exception, magick->images ); + if( !magick_optimize_image_transparency( magick->images, + magick->exception ) ) { + magick_inherit_exception( magick->exception, + magick->images ); magick_vips_error( class->nickname, magick->exception ); return( -1 ); @@ -462,13 +477,18 @@ vips_foreign_save_magick_class_init( VipsForeignSaveMagickClass *class ) _( "Optimize_gif_transparency" ), _( "Apply GIF transparency optimization" ), VIPS_ARGUMENT_OPTIONAL_INPUT, - G_STRUCT_OFFSET( VipsForeignSaveMagick, optimize_gif_transparency ), + G_STRUCT_OFFSET( VipsForeignSaveMagick, + optimize_gif_transparency ), FALSE ); } static void vips_foreign_save_magick_init( VipsForeignSaveMagick *magick ) { + /* Init to an int just to have something there. It is swapped for an + * int array later. + */ + g_value_init( &magick->delay_gvalue, G_TYPE_INT ); } typedef struct _VipsForeignSaveMagickFile { diff --git a/libvips/foreign/matrixload.c b/libvips/foreign/matrixload.c index 4f7c0b4b..f382286a 100644 --- a/libvips/foreign/matrixload.c +++ b/libvips/foreign/matrixload.c @@ -2,6 +2,8 @@ * * 5/12/11 * - from csvload.c + * 22/2/20 + * - rewrite for source API */ /* @@ -53,29 +55,118 @@ typedef struct _VipsForeignLoadMatrix { VipsForeignLoad parent_object; - /* Filename for load. + /* Set by subclasses. */ - char *filename; + VipsSource *source; + + /* Buffered source. + */ + VipsSbuf *sbuf; + + /* A line of pixels. + */ + double *linebuf; } VipsForeignLoadMatrix; typedef VipsForeignLoadClass VipsForeignLoadMatrixClass; -G_DEFINE_TYPE( VipsForeignLoadMatrix, vips_foreign_load_matrix, +G_DEFINE_ABSTRACT_TYPE( VipsForeignLoadMatrix, vips_foreign_load_matrix, VIPS_TYPE_FOREIGN_LOAD ); -static VipsForeignFlags -vips_foreign_load_matrix_get_flags_filename( const char *filename ) +static void +vips_foreign_load_matrix_dispose( GObject *gobject ) { + VipsForeignLoadMatrix *matrix = (VipsForeignLoadMatrix *) gobject; + + VIPS_UNREF( matrix->source ); + VIPS_UNREF( matrix->sbuf ); + VIPS_FREE( matrix->linebuf ); + + G_OBJECT_CLASS( vips_foreign_load_matrix_parent_class )-> + dispose( gobject ); +} + +static int +vips_foreign_load_matrix_build( VipsObject *object ) +{ + VipsForeignLoadMatrix *matrix = (VipsForeignLoadMatrix *) object; + + if( !(matrix->sbuf = vips_sbuf_new_from_source( matrix->source )) ) + return( -1 ); + + if( VIPS_OBJECT_CLASS( vips_foreign_load_matrix_parent_class )-> + build( object ) ) + return( -1 ); + return( 0 ); } static VipsForeignFlags vips_foreign_load_matrix_get_flags( VipsForeignLoad *load ) { - VipsForeignLoadMatrix *matrix = (VipsForeignLoadMatrix *) load; + return( 0 ); +} - return( vips_foreign_load_matrix_get_flags_filename( matrix->filename ) ); +/* Parse a header line. Two numbers for width and height, and two optional + * numbers for scale and offset. + * + * We can have scale and no offset, in which case we assume offset = 0. + */ +static int +parse_matrix_header( char *line, + int *width, int *height, double *scale, double *offset ) +{ + double header[4]; + char *p, *q; + int i; + + for( i = 0, p = line; + (q = vips_break_token( p, " \t" )) && + i < 4; + i++, p = q ) + if( vips_strtod( p, &header[i] ) ) { + vips_error( "matload", + _( "bad number \"%s\"" ), p ); + return( -1 ); + } + + if( i < 4 ) + header[3] = 0.0; + if( i < 3 ) + header[2] = 1.0; + if( i < 2 ) { + vips_error( "matload", "%s", _( "no width / height" ) ); + return( -1 ); + } + + if( VIPS_FLOOR( header[0] ) != header[0] || + VIPS_FLOOR( header[1] ) != header[1] ) { + vips_error( "mask2vips", "%s", _( "width / height not int" ) ); + return( -1 ); + } + + /* Width / height can be 65536 for a 16-bit LUT, for example. + */ + *width = header[0]; + *height = header[1]; + if( *width <= 0 || + *width > 100000 || + *height <= 0 || + *height > 100000 ) { + vips_error( "mask2vips", + "%s", _( "width / height out of range" ) ); + return( -1 ); + } + if( header[2] == 0.0 ) { + vips_error( "mask2vips", "%s", _( "zero scale" ) ); + return( -1 ); + } + + *scale = header[2]; + *offset = header[3]; + + return( 0 ); } static int @@ -83,23 +174,39 @@ vips_foreign_load_matrix_header( VipsForeignLoad *load ) { VipsForeignLoadMatrix *matrix = (VipsForeignLoadMatrix *) load; + char *line; int width; int height; double scale; double offset; + int result; - if( vips__matrix_read_header( matrix->filename, - &width, &height, &scale, &offset ) ) + /* Rewind. + */ + vips_sbuf_unbuffer( matrix->sbuf ); + if( vips_source_rewind( matrix->source ) ) return( -1 ); + line = vips_sbuf_get_line_copy( matrix->sbuf ); + result = parse_matrix_header( line, &width, &height, &scale, &offset ); + g_free( line ); + if( result ) + return( -1 ); + + vips_image_pipelinev( load->out, VIPS_DEMAND_STYLE_THINSTRIP, NULL ); vips_image_init_fields( load->out, width, height, 1, VIPS_FORMAT_DOUBLE, VIPS_CODING_NONE, VIPS_INTERPRETATION_B_W, 1.0, 1.0 ); + vips_image_set_double( load->out, "scale", scale ); vips_image_set_double( load->out, "offset", offset ); - VIPS_SETSTR( load->out->filename, matrix->filename ); + VIPS_SETSTR( load->out->filename, + vips_connection_filename( VIPS_CONNECTION( matrix->source ) ) ); + + if( !(matrix->linebuf = VIPS_ARRAY( NULL, width, double )) ) + return( -1 ); return( 0 ); } @@ -108,22 +215,142 @@ static int vips_foreign_load_matrix_load( VipsForeignLoad *load ) { VipsForeignLoadMatrix *matrix = (VipsForeignLoadMatrix *) load; + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( load ); - VipsImage *out; + int x, y; - if( !(out = vips__matrix_read( matrix->filename )) ) - return( -1 ); - if( vips_image_write( out, load->real ) ) { - g_object_unref( out ); - return( -1 ); + vips_image_pipelinev( load->real, VIPS_DEMAND_STYLE_THINSTRIP, NULL ); + vips_image_init_fields( load->real, + load->out->Xsize, load->out->Ysize, 1, + VIPS_FORMAT_DOUBLE, + VIPS_CODING_NONE, VIPS_INTERPRETATION_B_W, 1.0, 1.0 ); + + for( y = 0; y < load->real->Ysize; y++ ) { + char *line; + char *p, *q; + + line = vips_sbuf_get_line_copy( matrix->sbuf ); + + for( x = 0, p = line; + (q = vips_break_token( p, " \t" )) && + x < load->out->Xsize; + x++, p = q ) + if( vips_strtod( p, &matrix->linebuf[x] ) ) { + vips_error( class->nickname, + _( "bad number \"%s\"" ), p ); + g_free( line ); + return( -1 ); + } + + g_free( line ); + + if( x != load->out->Xsize ) { + vips_error( class->nickname, + _( "line %d too short" ), y ); + return( -1 ); + } + + if( vips_image_write_line( load->real, y, + (VipsPel *) matrix->linebuf ) ) + return( -1 ); } - g_object_unref( out ); return( 0 ); } static void vips_foreign_load_matrix_class_init( VipsForeignLoadMatrixClass *class ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( class ); + VipsObjectClass *object_class = (VipsObjectClass *) class; + VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class; + + gobject_class->dispose = vips_foreign_load_matrix_dispose; + + object_class->nickname = "matrixload_base"; + object_class->description = _( "load matrix" ); + object_class->build = vips_foreign_load_matrix_build; + + load_class->get_flags = vips_foreign_load_matrix_get_flags; + load_class->header = vips_foreign_load_matrix_header; + load_class->load = vips_foreign_load_matrix_load; + +} + +static void +vips_foreign_load_matrix_init( VipsForeignLoadMatrix *matrix ) +{ +} + +typedef struct _VipsForeignLoadMatrixFile { + VipsForeignLoadMatrix parent_object; + + /* Filename for load. + */ + char *filename; + +} VipsForeignLoadMatrixFile; + +typedef VipsForeignLoadMatrixClass VipsForeignLoadMatrixFileClass; + +G_DEFINE_TYPE( VipsForeignLoadMatrixFile, vips_foreign_load_matrix_file, + vips_foreign_load_matrix_get_type() ); + +static VipsForeignFlags +vips_foreign_load_matrix_file_get_flags_filename( const char *filename ) +{ + return( 0 ); +} + +static int +vips_foreign_load_matrix_file_build( VipsObject *object ) +{ + VipsForeignLoadMatrix *matrix = (VipsForeignLoadMatrix *) object; + VipsForeignLoadMatrixFile *file = (VipsForeignLoadMatrixFile *) object; + + if( file->filename ) + if( !(matrix->source = + vips_source_new_from_file( file->filename )) ) + return( -1 ); + + if( VIPS_OBJECT_CLASS( vips_foreign_load_matrix_file_parent_class )-> + build( object ) ) + return( -1 ); + + return( 0 ); +} + +static const char *vips_foreign_load_matrix_suffs[] = { + ".mat", + NULL +}; + +static gboolean +vips_foreign_load_matrix_file_is_a( const char *filename ) +{ + unsigned char line[80]; + guint64 bytes; + int width; + int height; + double scale; + double offset; + int result; + + if( (bytes = vips__get_bytes( filename, line, 79 )) <= 0 ) + return( FALSE ); + line[bytes] = '\0'; + + vips_error_freeze(); + result = parse_matrix_header( (char *) line, + &width, &height, &scale, &offset ); + vips_error_thaw(); + + return( result == 0 ); +} + +static void +vips_foreign_load_matrix_file_class_init( + VipsForeignLoadMatrixFileClass *class ) { GObjectClass *gobject_class = G_OBJECT_CLASS( class ); VipsObjectClass *object_class = (VipsObjectClass *) class; @@ -134,31 +361,111 @@ vips_foreign_load_matrix_class_init( VipsForeignLoadMatrixClass *class ) gobject_class->get_property = vips_object_get_property; object_class->nickname = "matrixload"; - object_class->description = _( "load matrix from file" ); + object_class->build = vips_foreign_load_matrix_file_build; - foreign_class->suffs = vips__foreign_matrix_suffs; + foreign_class->suffs = vips_foreign_load_matrix_suffs; - /* is_a() is not that quick ... lower the priority. - */ - foreign_class->priority = -50; - - load_class->is_a = vips__matrix_ismatrix; + load_class->is_a = vips_foreign_load_matrix_file_is_a; load_class->get_flags_filename = - vips_foreign_load_matrix_get_flags_filename; - load_class->get_flags = vips_foreign_load_matrix_get_flags; - load_class->header = vips_foreign_load_matrix_header; - load_class->load = vips_foreign_load_matrix_load; + vips_foreign_load_matrix_file_get_flags_filename; - VIPS_ARG_STRING( class, "filename", 1, + VIPS_ARG_STRING( class, "filename", 1, _( "Filename" ), _( "Filename to load from" ), - VIPS_ARGUMENT_REQUIRED_INPUT, - G_STRUCT_OFFSET( VipsForeignLoadMatrix, filename ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadMatrixFile, filename ), NULL ); + } static void -vips_foreign_load_matrix_init( VipsForeignLoadMatrix *matrix ) +vips_foreign_load_matrix_file_init( VipsForeignLoadMatrixFile *file ) +{ +} + +typedef struct _VipsForeignLoadMatrixSource { + VipsForeignLoadMatrix parent_object; + + VipsSource *source; + +} VipsForeignLoadMatrixSource; + +typedef VipsForeignLoadMatrixClass VipsForeignLoadMatrixSourceClass; + +G_DEFINE_TYPE( VipsForeignLoadMatrixSource, vips_foreign_load_matrix_source, + vips_foreign_load_matrix_get_type() ); + +static int +vips_foreign_load_matrix_source_build( VipsObject *object ) +{ + VipsForeignLoadMatrix *matrix = (VipsForeignLoadMatrix *) object; + VipsForeignLoadMatrixSource *source = + (VipsForeignLoadMatrixSource *) object; + + if( source->source ) { + matrix->source = source->source; + g_object_ref( matrix->source ); + } + + if( VIPS_OBJECT_CLASS( vips_foreign_load_matrix_source_parent_class )-> + build( object ) ) + return( -1 ); + + return( 0 ); +} + +static int +vips_foreign_load_matrix_source_is_a_source( VipsSource *source ) +{ + unsigned char *data; + size_t bytes_read; + char line[80]; + int width; + int height; + double scale; + double offset; + int result; + + if( (bytes_read = vips_source_sniff_at_most( source, + &data, 79 )) <= 0 ) + return( FALSE ); + vips_strncpy( line, (const char *) data, 80 ); + + vips_error_freeze(); + result = parse_matrix_header( line, + &width, &height, &scale, &offset ); + vips_error_thaw(); + + return( result == 0 ); +} + +static void +vips_foreign_load_matrix_source_class_init( + VipsForeignLoadMatrixFileClass *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 = "matrixload_source"; + object_class->build = vips_foreign_load_matrix_source_build; + + load_class->is_a_source = vips_foreign_load_matrix_source_is_a_source; + + VIPS_ARG_OBJECT( class, "source", 1, + _( "Source" ), + _( "Source to load from" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadMatrixSource, source ), + VIPS_TYPE_SOURCE ); + +} + +static void +vips_foreign_load_matrix_source_init( VipsForeignLoadMatrixSource *source ) { } @@ -190,7 +497,7 @@ vips_foreign_load_matrix_init( VipsForeignLoadMatrix *matrix ) * Extra characters at the ends of lines or at the end of the file are * ignored. * - * See also: vips_csvload(). + * See also: vips_matrixload(). * * Returns: 0 on success, -1 on error. */ @@ -207,3 +514,28 @@ vips_matrixload( const char *filename, VipsImage **out, ... ) return( result ); } +/** + * vips_matrixload_source: + * @source: source to load + * @out: (out): output image + * @...: %NULL-terminated list of optional named arguments + * + * Exactly as vips_matrixload(), but read from a source. + * + * See also: vips_matrixload(). + * + * Returns: 0 on success, -1 on error. + */ +int +vips_matrixload_source( VipsSource *source, VipsImage **out, ... ) +{ + va_list ap; + int result; + + va_start( ap, out ); + result = vips_call_split( "matrixload_source", ap, source, out ); + va_end( ap ); + + return( result ); +} + diff --git a/libvips/foreign/matrixsave.c b/libvips/foreign/matrixsave.c index 55289b9c..fd93efba 100644 --- a/libvips/foreign/matrixsave.c +++ b/libvips/foreign/matrixsave.c @@ -1,7 +1,9 @@ /* save to matrix * - * 2/7/13 + * 2/12/11 * - wrap a class around the matrix writer + * 21/2/20 + * - rewrite for the VipsTarget API */ /* @@ -52,28 +54,86 @@ typedef struct _VipsForeignSaveMatrix { VipsForeignSave parent_object; - /* Filename for save. - */ - char *filename; + VipsTarget *target; + const char *separator; } VipsForeignSaveMatrix; typedef VipsForeignSaveClass VipsForeignSaveMatrixClass; -G_DEFINE_TYPE( VipsForeignSaveMatrix, vips_foreign_save_matrix, +G_DEFINE_ABSTRACT_TYPE( VipsForeignSaveMatrix, vips_foreign_save_matrix, VIPS_TYPE_FOREIGN_SAVE ); +static void +vips_foreign_save_matrix_dispose( GObject *gobject ) +{ + VipsForeignSaveMatrix *matrix = (VipsForeignSaveMatrix *) gobject; + + if( matrix->target ) + vips_target_finish( matrix->target ); + VIPS_UNREF( matrix->target ); + + G_OBJECT_CLASS( vips_foreign_save_matrix_parent_class )-> + dispose( gobject ); +} + +static int +vips_foreign_save_matrix_block( VipsRegion *region, VipsRect *area, void *a ) +{ + VipsForeignSaveMatrix *matrix = (VipsForeignSaveMatrix *) a; + + int x, y; + + for( y = 0; y < area->height; y++ ) { + double *p = (double *) + VIPS_REGION_ADDR( region, 0, area->top + y ); + + char buf[G_ASCII_DTOSTR_BUF_SIZE]; + + for( x = 0; x < area->width; x++ ) { + if( x > 0 ) + vips_target_writes( matrix->target, " " ); + + g_ascii_dtostr( buf, G_ASCII_DTOSTR_BUF_SIZE, p[x] ); + vips_target_writes( matrix->target, buf ); + } + + if( vips_target_writes( matrix->target, "\n" ) ) + return( -1 ); + } + + return( 0 ); +} + static int vips_foreign_save_matrix_build( VipsObject *object ) { VipsForeignSave *save = (VipsForeignSave *) object; VipsForeignSaveMatrix *matrix = (VipsForeignSaveMatrix *) object; + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object ); + + double scale; + double offset; if( VIPS_OBJECT_CLASS( vips_foreign_save_matrix_parent_class )-> build( object ) ) return( -1 ); - if( vips__matrix_write( save->ready, matrix->filename ) ) + if( vips_check_mono( class->nickname, save->ready ) || + vips_check_uncoded( class->nickname, save->ready ) ) + return( -1 ); + + vips_target_writef( matrix->target, "%d %d", + save->ready->Xsize, save->ready->Ysize ); + scale = vips_image_get_scale( save->ready ); + offset = vips_image_get_offset( save->ready ); + if( scale != 1.0 || offset != 0.0 ) + vips_target_writef( matrix->target, " %g %g", scale, offset ); + if( vips_target_writes( matrix->target, "\n" ) ) + return( -1 ); + + if( vips_sink_disc( save->ready, + vips_foreign_save_matrix_block, matrix ) ) return( -1 ); return( 0 ); @@ -97,6 +157,11 @@ static int bandfmt_matrix[10] = { D, D, D, D, D, D, D, D, D, D }; +static const char *vips_foreign_save_matrix_suffs[] = { + ".mat", + NULL +}; + static void vips_foreign_save_matrix_class_init( VipsForeignSaveMatrixClass *class ) { @@ -105,28 +170,168 @@ vips_foreign_save_matrix_class_init( VipsForeignSaveMatrixClass *class ) VipsForeignClass *foreign_class = (VipsForeignClass *) class; VipsForeignSaveClass *save_class = (VipsForeignSaveClass *) class; + gobject_class->dispose = vips_foreign_save_matrix_dispose; + + object_class->nickname = "matrixsave_base"; + object_class->description = _( "save image to matrix" ); + object_class->build = vips_foreign_save_matrix_build; + + foreign_class->suffs = vips_foreign_save_matrix_suffs; + + save_class->saveable = VIPS_SAVEABLE_MONO; + save_class->format_table = bandfmt_matrix; + +} + +static void +vips_foreign_save_matrix_init( VipsForeignSaveMatrix *matrix ) +{ +} + +typedef struct _VipsForeignSaveMatrixFile { + VipsForeignSaveMatrix parent_object; + + char *filename; +} VipsForeignSaveMatrixFile; + +typedef VipsForeignSaveMatrixClass VipsForeignSaveMatrixFileClass; + +G_DEFINE_TYPE( VipsForeignSaveMatrixFile, vips_foreign_save_matrix_file, + vips_foreign_save_matrix_get_type() ); + +static int +vips_foreign_save_matrix_file_build( VipsObject *object ) +{ + VipsForeignSaveMatrix *matrix = (VipsForeignSaveMatrix *) object; + VipsForeignSaveMatrixFile *file = (VipsForeignSaveMatrixFile *) object; + + if( file->filename && + !(matrix->target = vips_target_new_to_file( file->filename )) ) + return( -1 ); + + return( VIPS_OBJECT_CLASS( + vips_foreign_save_matrix_file_parent_class )->build( object ) ); +} + +static void +vips_foreign_save_matrix_file_class_init( + VipsForeignSaveMatrixFileClass *class ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( class ); + VipsObjectClass *object_class = (VipsObjectClass *) class; + gobject_class->set_property = vips_object_set_property; gobject_class->get_property = vips_object_get_property; object_class->nickname = "matrixsave"; - object_class->description = _( "save image to matrix file" ); - object_class->build = vips_foreign_save_matrix_build; - - foreign_class->suffs = vips__foreign_matrix_suffs; - - save_class->saveable = VIPS_SAVEABLE_MONO; - save_class->format_table = bandfmt_matrix; + object_class->build = vips_foreign_save_matrix_file_build; VIPS_ARG_STRING( class, "filename", 1, _( "Filename" ), _( "Filename to save to" ), VIPS_ARGUMENT_REQUIRED_INPUT, - G_STRUCT_OFFSET( VipsForeignSaveMatrix, filename ), + G_STRUCT_OFFSET( VipsForeignSaveMatrixFile, filename ), NULL ); + } static void -vips_foreign_save_matrix_init( VipsForeignSaveMatrix *matrix ) +vips_foreign_save_matrix_file_init( VipsForeignSaveMatrixFile *file ) +{ +} + +typedef struct _VipsForeignSaveMatrixTarget { + VipsForeignSaveMatrix parent_object; + + VipsTarget *target; +} VipsForeignSaveMatrixTarget; + +typedef VipsForeignSaveMatrixClass VipsForeignSaveMatrixTargetClass; + +G_DEFINE_TYPE( VipsForeignSaveMatrixTarget, vips_foreign_save_matrix_target, + vips_foreign_save_matrix_get_type() ); + +static int +vips_foreign_save_matrix_target_build( VipsObject *object ) +{ + VipsForeignSaveMatrix *matrix = (VipsForeignSaveMatrix *) object; + VipsForeignSaveMatrixTarget *target = + (VipsForeignSaveMatrixTarget *) object; + + if( target->target ) { + matrix->target = target->target; + g_object_ref( matrix->target ); + } + + return( VIPS_OBJECT_CLASS( + vips_foreign_save_matrix_target_parent_class )-> + build( object ) ); +} + +static void +vips_foreign_save_matrix_target_class_init( + VipsForeignSaveMatrixTargetClass *class ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( class ); + VipsObjectClass *object_class = (VipsObjectClass *) class; + + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + object_class->nickname = "matrixsave_target"; + object_class->build = vips_foreign_save_matrix_target_build; + + VIPS_ARG_OBJECT( class, "target", 1, + _( "Target" ), + _( "Target to save to" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsForeignSaveMatrixTarget, target ), + VIPS_TYPE_TARGET ); + +} + +static void +vips_foreign_save_matrix_target_init( VipsForeignSaveMatrixTarget *target ) +{ +} + +typedef struct _VipsForeignPrintMatrix { + VipsForeignSaveMatrix parent_object; + +} VipsForeignPrintMatrix; + +typedef VipsForeignSaveClass VipsForeignPrintMatrixClass; + +G_DEFINE_TYPE( VipsForeignPrintMatrix, vips_foreign_print_matrix, + vips_foreign_save_matrix_get_type() ); + +static int +vips_foreign_print_matrix_build( VipsObject *object ) +{ + VipsForeignSaveMatrix *matrix = (VipsForeignSaveMatrix *) object; + + if( !(matrix->target = vips_target_new_to_descriptor( 0 )) ) + return( -1 ); + + if( VIPS_OBJECT_CLASS( vips_foreign_print_matrix_parent_class )-> + build( object ) ) + return( -1 ); + + return( 0 ); +} + +static void +vips_foreign_print_matrix_class_init( VipsForeignPrintMatrixClass *class ) +{ + VipsObjectClass *object_class = (VipsObjectClass *) class; + + object_class->nickname = "matrixprint"; + object_class->description = _( "print matrix" ); + object_class->build = vips_foreign_print_matrix_build; +} + +static void +vips_foreign_print_matrix_init( VipsForeignPrintMatrix *matrix ) { } @@ -156,51 +361,29 @@ vips_matrixsave( VipsImage *in, const char *filename, ... ) return( result ); } -typedef struct _VipsForeignPrintMatrix { - VipsForeignSave parent_object; - -} VipsForeignPrintMatrix; - -typedef VipsForeignSaveClass VipsForeignPrintMatrixClass; - -G_DEFINE_TYPE( VipsForeignPrintMatrix, vips_foreign_print_matrix, - VIPS_TYPE_FOREIGN_SAVE ); - -static int -vips_foreign_print_matrix_build( VipsObject *object ) +/** + * vips_matrixsave_target: (method) + * @in: image to save + * @target: save image to this target + * @...: %NULL-terminated list of optional named arguments + * + * As vips_matrixsave(), but save to a target. + * + * See also: vips_matrixsave(). + * + * Returns: 0 on success, -1 on error. + */ +int +vips_matrixsave_target( VipsImage *in, VipsTarget *target, ... ) { - VipsForeignSave *save = (VipsForeignSave *) object; + va_list ap; + int result; - if( VIPS_OBJECT_CLASS( vips_foreign_print_matrix_parent_class )-> - build( object ) ) - return( -1 ); + va_start( ap, target ); + result = vips_call_split( "matrixsave_target", ap, in, target ); + va_end( ap ); - if( vips__matrix_write_file( save->ready, stdout ) ) - return( -1 ); - - return( 0 ); -} - -static void -vips_foreign_print_matrix_class_init( VipsForeignPrintMatrixClass *class ) -{ - VipsObjectClass *object_class = (VipsObjectClass *) class; - VipsForeignClass *foreign_class = (VipsForeignClass *) class; - VipsForeignSaveClass *save_class = (VipsForeignSaveClass *) class; - - object_class->nickname = "matrixprint"; - object_class->description = _( "print matrix" ); - object_class->build = vips_foreign_print_matrix_build; - - foreign_class->suffs = vips__foreign_matrix_suffs; - - save_class->saveable = VIPS_SAVEABLE_MONO; - save_class->format_table = bandfmt_matrix; -} - -static void -vips_foreign_print_matrix_init( VipsForeignPrintMatrix *matrix ) -{ + return( result ); } /** diff --git a/libvips/foreign/openslide2vips.c b/libvips/foreign/openslide2vips.c index 5a5a8c9f..4ad14861 100644 --- a/libvips/foreign/openslide2vips.c +++ b/libvips/foreign/openslide2vips.c @@ -51,6 +51,8 @@ * - reorganise to support invalidate on read error * 27/1/18 * - option to attach associated images as metadata + * 22/6/20 adamu + * - set libvips xres/yres from openslide mpp-x/mpp-y */ /* @@ -332,7 +334,7 @@ readslide_attach_associated( ReadSlide *rslide, VipsImage *image ) openslide_get_associated_image_dimensions( rslide->osr, *associated_name, &w, &h ); vips_image_init_fields( associated, w, h, 4, VIPS_FORMAT_UCHAR, - VIPS_CODING_NONE, VIPS_INTERPRETATION_RGB, 1.0, 1.0 ); + VIPS_CODING_NONE, VIPS_INTERPRETATION_sRGB, 1.0, 1.0 ); vips_image_pipelinev( associated, VIPS_DEMAND_STYLE_THINSTRIP, NULL ); @@ -361,6 +363,17 @@ readslide_attach_associated( ReadSlide *rslide, VipsImage *image ) return( 0 ); } +/* Read out a resolution field, converting to pixels per mm. + */ +static double +readslice_parse_res( ReadSlide *rslide, const char *name ) +{ + const char *value = openslide_get_property_value( rslide->osr, name ); + double mpp = g_ascii_strtod( value, NULL ); + + return( mpp == 0 ? 1.0 : 1000.0 / mpp ); +} + static int readslide_parse( ReadSlide *rslide, VipsImage *image ) { @@ -369,6 +382,8 @@ readslide_parse( ReadSlide *rslide, VipsImage *image ) const char *background; const char * const *properties; char *associated_names; + double xres; + double yres; rslide->osr = openslide_open( rslide->filename ); if( rslide->osr == NULL ) { @@ -493,21 +508,32 @@ readslide_parse( ReadSlide *rslide, VipsImage *image ) rslide->bounds.height = h; } - vips_image_init_fields( image, w, h, 4, VIPS_FORMAT_UCHAR, - VIPS_CODING_NONE, VIPS_INTERPRETATION_RGB, 1.0, 1.0 ); + /* Try to get resolution from openslide properties. + */ + xres = 1.0; + yres = 1.0; for( properties = openslide_get_property_names( rslide->osr ); - *properties != NULL; properties++ ) + *properties != NULL; properties++ ) { vips_image_set_string( image, *properties, openslide_get_property_value( rslide->osr, *properties ) ); + if( strcmp( *properties, "openslide.mpp-x" ) == 0 ) + xres = readslice_parse_res( rslide, *properties ); + if( strcmp( *properties, "openslide.mpp-y" ) == 0 ) + yres = readslice_parse_res( rslide, *properties ); + } + associated_names = g_strjoinv( ", ", (char **) openslide_get_associated_image_names( rslide->osr ) ); vips_image_set_string( image, "slide-associated-images", associated_names ); VIPS_FREE( associated_names ); + vips_image_init_fields( image, w, h, 4, VIPS_FORMAT_UCHAR, + VIPS_CODING_NONE, VIPS_INTERPRETATION_sRGB, xres, yres ); + return( 0 ); } diff --git a/libvips/foreign/pdfiumload.c b/libvips/foreign/pdfiumload.c index f0984cd5..c18e9521 100644 --- a/libvips/foreign/pdfiumload.c +++ b/libvips/foreign/pdfiumload.c @@ -39,6 +39,8 @@ /* TODO * + * - needs the reopen-after-minimise system that pdfload has, but we'll need + * to be able to actually build and test this before we can do that * - what about filename encodings * - need to test on Windows */ diff --git a/libvips/foreign/pdfload.c b/libvips/foreign/pdfload.c index 888f9d50..04caef60 100644 --- a/libvips/foreign/pdfload.c +++ b/libvips/foreign/pdfload.c @@ -10,8 +10,12 @@ * - use a much larger strip size, thanks bubba * 8/6/18 * - add background param - * 16/8/18 - * - shut down the input file as soon as we can [kleisauke] + * 16/8/18 [kleisauke] + * - shut down the input file as soon as we can + * 19/9/19 + * - reopen the input if we minimised too early + * 11/3/20 + * - move on top of VipsSource */ /* @@ -61,14 +65,38 @@ #include "pforeign.h" -#ifdef HAVE_POPPLER +/* TODO ... put minimise support back in. + */ + +#if defined(HAVE_POPPLER) #include #include +#define VIPS_TYPE_FOREIGN_LOAD_PDF (vips_foreign_load_pdf_get_type()) +#define VIPS_FOREIGN_LOAD_PDF( obj ) \ + (G_TYPE_CHECK_INSTANCE_CAST( (obj), \ + VIPS_TYPE_FOREIGN_LOAD_PDF, VipsForeignLoadPdf )) +#define VIPS_FOREIGN_LOAD_PDF_CLASS( klass ) \ + (G_TYPE_CHECK_CLASS_CAST( (klass), \ + VIPS_TYPE_FOREIGN_LOAD_PDF, VipsForeignLoadPdfClass)) +#define VIPS_IS_FOREIGN_LOAD_PDF( obj ) \ + (G_TYPE_CHECK_INSTANCE_TYPE( (obj), VIPS_TYPE_FOREIGN_LOAD_PDF )) +#define VIPS_IS_FOREIGN_LOAD_PDF_CLASS( klass ) \ + (G_TYPE_CHECK_CLASS_TYPE( (klass), VIPS_TYPE_FOREIGN_LOAD_PDF )) +#define VIPS_FOREIGN_LOAD_PDF_GET_CLASS( obj ) \ + (G_TYPE_INSTANCE_GET_CLASS( (obj), \ + VIPS_TYPE_FOREIGN_LOAD_PDF, VipsForeignLoadPdfClass )) + typedef struct _VipsForeignLoadPdf { VipsForeignLoad parent_object; + /* The VipsSource we load from, and the GInputStream we wrap around + * it. Set from subclasses. + */ + VipsSource *source; + GInputStream *stream; + /* Load this page. */ int page_no; @@ -113,24 +141,23 @@ typedef struct _VipsForeignLoadPdf { } VipsForeignLoadPdf; -typedef VipsForeignLoadClass VipsForeignLoadPdfClass; +typedef struct _VipsForeignLoadPdfClass { + VipsForeignLoadClass parent_class; + +} VipsForeignLoadPdfClass; G_DEFINE_ABSTRACT_TYPE( VipsForeignLoadPdf, vips_foreign_load_pdf, VIPS_TYPE_FOREIGN_LOAD ); -static void -vips_foreign_load_pdf_close( VipsForeignLoadPdf *pdf ) -{ - VIPS_UNREF( pdf->page ); - VIPS_UNREF( pdf->doc ); -} - static void vips_foreign_load_pdf_dispose( GObject *gobject ) { - VipsForeignLoadPdf *pdf = (VipsForeignLoadPdf *) gobject; + VipsForeignLoadPdf *pdf = VIPS_FOREIGN_LOAD_PDF( gobject ); - vips_foreign_load_pdf_close( pdf ); + VIPS_UNREF( pdf->page ); + VIPS_UNREF( pdf->doc ); + VIPS_UNREF( pdf->source ); + VIPS_UNREF( pdf->stream ); G_OBJECT_CLASS( vips_foreign_load_pdf_parent_class )-> dispose( gobject ); @@ -139,11 +166,20 @@ vips_foreign_load_pdf_dispose( GObject *gobject ) static int vips_foreign_load_pdf_build( VipsObject *object ) { - VipsForeignLoadPdf *pdf = (VipsForeignLoadPdf *) object; + VipsForeignLoadPdf *pdf = VIPS_FOREIGN_LOAD_PDF( object ); + + GError *error = NULL; if( !vips_object_argument_isset( object, "scale" ) ) pdf->scale = pdf->dpi / 72.0; + pdf->stream = vips_g_input_stream_new_from_source( pdf->source ); + if( !(pdf->doc = poppler_document_new_from_stream( pdf->stream, + vips_source_length( pdf->source ), NULL, NULL, &error )) ) { + vips_g_error( &error ); + return( -1 ); + } + if( VIPS_OBJECT_CLASS( vips_foreign_load_pdf_parent_class )-> build( object ) ) return( -1 ); @@ -168,7 +204,8 @@ vips_foreign_load_pdf_get_flags( VipsForeignLoad *load ) static int vips_foreign_load_pdf_get_page( VipsForeignLoadPdf *pdf, int page_no ) { - if( pdf->current_page != page_no ) { + if( pdf->current_page != page_no || + !pdf->page ) { VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( pdf ); VIPS_UNREF( pdf->page ); @@ -255,7 +292,7 @@ static int vips_foreign_load_pdf_header( VipsForeignLoad *load ) { VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( load ); - VipsForeignLoadPdf *pdf = (VipsForeignLoadPdf *) load; + VipsForeignLoadPdf *pdf = VIPS_FOREIGN_LOAD_PDF( load ); int top; int i; @@ -334,23 +371,16 @@ vips_foreign_load_pdf_header( VipsForeignLoad *load ) VIPS_AREA( pdf->background )->n )) ) return( -1 ); - return( 0 ); -} + vips_source_minimise( pdf->source ); -static void -vips_foreign_load_pdf_minimise( VipsObject *object, VipsForeignLoadPdf *pdf ) -{ - /* In seq mode, we can shut down the input at the end of computation. - */ - if( VIPS_FOREIGN_LOAD( pdf )->access == VIPS_ACCESS_SEQUENTIAL ) - vips_foreign_load_pdf_close( pdf ); + return( 0 ); } static int vips_foreign_load_pdf_generate( VipsRegion *or, void *seq, void *a, void *b, gboolean *stop ) { - VipsForeignLoadPdf *pdf = (VipsForeignLoadPdf *) a; + VipsForeignLoadPdf *pdf = VIPS_FOREIGN_LOAD_PDF( a ); VipsRect *r = &or->valid; int top; @@ -422,7 +452,7 @@ vips_foreign_load_pdf_generate( VipsRegion *or, static int vips_foreign_load_pdf_load( VipsForeignLoad *load ) { - VipsForeignLoadPdf *pdf = (VipsForeignLoadPdf *) load; + VipsForeignLoadPdf *pdf = VIPS_FOREIGN_LOAD_PDF( load ); VipsImage **t = (VipsImage **) vips_object_local_array( (VipsObject *) load, 2 ); @@ -434,10 +464,9 @@ vips_foreign_load_pdf_load( VipsForeignLoad *load ) */ t[0] = vips_image_new(); - /* Close input immediately at end of read. + /* Don't minimise on ::minimise (end of computation): we support + * threaded read, and minimise will happen outside the cache lock. */ - g_signal_connect( t[0], "minimise", - G_CALLBACK( vips_foreign_load_pdf_minimise ), pdf ); vips_foreign_load_pdf_set_image( pdf, t[0] ); if( vips_image_generate( t[0], @@ -477,6 +506,7 @@ vips_foreign_load_pdf_class_init( VipsForeignLoadPdfClass *class ) load_class->get_flags_filename = vips_foreign_load_pdf_get_flags_filename; load_class->get_flags = vips_foreign_load_pdf_get_flags; + load_class->header = vips_foreign_load_pdf_header; load_class->load = vips_foreign_load_pdf_load; VIPS_ARG_INT( class, "page", 20, @@ -557,31 +587,12 @@ vips_foreign_load_pdf_file_dispose( GObject *gobject ) static int vips_foreign_load_pdf_file_header( VipsForeignLoad *load ) { - VipsForeignLoadPdf *pdf = (VipsForeignLoadPdf *) load; VipsForeignLoadPdfFile *file = (VipsForeignLoadPdfFile *) load; - char *path; - GError *error = NULL; - - /* We need an absolute path for a URI. - */ - path = vips_realpath( file->filename ); - if( !(file->uri = g_filename_to_uri( path, NULL, &error )) ) { - g_free( path ); - vips_g_error( &error ); - return( -1 ); - } - g_free( path ); - - if( !(pdf->doc = poppler_document_new_from_file( - file->uri, NULL, &error )) ) { - vips_g_error( &error ); - return( -1 ); - } - VIPS_SETSTR( load->out->filename, file->filename ); - return( vips_foreign_load_pdf_header( load ) ); + return( VIPS_FOREIGN_LOAD_CLASS( + vips_foreign_load_pdf_file_parent_class )->header( load ) ); } static const char *vips_foreign_pdf_suffs[] = { @@ -589,6 +600,40 @@ static const char *vips_foreign_pdf_suffs[] = { NULL }; +static int +vips_foreign_load_pdf_file_build( VipsObject *object ) +{ + VipsForeignLoadPdf *pdf = VIPS_FOREIGN_LOAD_PDF( object ); + VipsForeignLoadPdfFile *file = (VipsForeignLoadPdfFile *) pdf; + +#ifdef DEBUG + printf( "vips_foreign_load_pdf_file_build: %s\n", file->filename ); +#endif /*DEBUG*/ + + if( file->filename ) { + char *path; + GError *error = NULL; + + /* We need an absolute path for a URI. + */ + path = vips_realpath( file->filename ); + if( !(file->uri = g_filename_to_uri( path, NULL, &error )) ) { + g_free( path ); + vips_g_error( &error ); + return( -1 ); + } + g_free( path ); + + if( !(pdf->source = + vips_source_new_from_file( file->filename )) ) + return( -1 ); + + } + + return( VIPS_OBJECT_CLASS( vips_foreign_load_pdf_file_parent_class )-> + build( object ) ); +} + static void vips_foreign_load_pdf_file_class_init( VipsForeignLoadPdfFileClass *class ) @@ -603,6 +648,8 @@ 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->build = vips_foreign_load_pdf_file_build; foreign_class->suffs = vips_foreign_pdf_suffs; @@ -638,21 +685,19 @@ G_DEFINE_TYPE( VipsForeignLoadPdfBuffer, vips_foreign_load_pdf_buffer, vips_foreign_load_pdf_get_type() ); static int -vips_foreign_load_pdf_buffer_header( VipsForeignLoad *load ) +vips_foreign_load_pdf_buffer_build( VipsObject *object ) { - VipsForeignLoadPdf *pdf = (VipsForeignLoadPdf *) load; - VipsForeignLoadPdfBuffer *buffer = - (VipsForeignLoadPdfBuffer *) load; + VipsForeignLoadPdf *pdf = VIPS_FOREIGN_LOAD_PDF( object ); + VipsForeignLoadPdfBuffer *buffer = (VipsForeignLoadPdfBuffer *) pdf; - GError *error = NULL; + if( buffer->buf && + !(pdf->source = vips_source_new_from_memory( + VIPS_AREA( buffer->buf )->data, + VIPS_AREA( buffer->buf )->length )) ) + return( -1 ); - if( !(pdf->doc = poppler_document_new_from_data( - buffer->buf->data, buffer->buf->length, NULL, &error )) ) { - vips_g_error( &error ); - return( -1 ); - } - - return( vips_foreign_load_pdf_header( load ) ); + return( VIPS_OBJECT_CLASS( vips_foreign_load_pdf_buffer_parent_class )-> + build( object ) ); } static void @@ -667,9 +712,10 @@ 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->build = vips_foreign_load_pdf_buffer_build; load_class->is_a_buffer = vips_foreign_load_pdf_is_a_buffer; - load_class->header = vips_foreign_load_pdf_buffer_header; VIPS_ARG_BOXED( class, "buffer", 1, _( "Buffer" ), @@ -685,7 +731,77 @@ vips_foreign_load_pdf_buffer_init( VipsForeignLoadPdfBuffer *buffer ) { } -#endif /*HAVE_POPPLER*/ +typedef struct _VipsForeignLoadPdfSource { + VipsForeignLoadPdf parent_object; + + VipsSource *source; + +} VipsForeignLoadPdfSource; + +typedef VipsForeignLoadPdfClass VipsForeignLoadPdfSourceClass; + +G_DEFINE_TYPE( VipsForeignLoadPdfSource, vips_foreign_load_pdf_source, + vips_foreign_load_pdf_get_type() ); + +static int +vips_foreign_load_pdf_source_build( VipsObject *object ) +{ + VipsForeignLoadPdf *pdf = VIPS_FOREIGN_LOAD_PDF( object ); + VipsForeignLoadPdfSource *source = (VipsForeignLoadPdfSource *) pdf; + + if( source->source ) { + pdf->source = source->source; + g_object_ref( pdf->source ); + } + + return( VIPS_OBJECT_CLASS( vips_foreign_load_pdf_source_parent_class )-> + build( object ) ); +} + +static gboolean +vips_foreign_load_pdf_source_is_a_source( VipsSource *source ) +{ + const unsigned char *p; + + return( (p = vips_source_sniff( source, 4 )) && + p[0] == '%' && + p[1] == 'P' && + p[2] == 'D' && + p[3] == 'F' ); +} + +static void +vips_foreign_load_pdf_source_class_init( + VipsForeignLoadPdfSourceClass *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 = "pdfload_source"; + object_class->description = _( "load PDF from source" ); + object_class->build = vips_foreign_load_pdf_source_build; + + load_class->is_a_source = vips_foreign_load_pdf_source_is_a_source; + + VIPS_ARG_OBJECT( class, "source", 1, + _( "Source" ), + _( "Source to load from" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadPdfSource, source ), + VIPS_TYPE_SOURCE ); + +} + +static void +vips_foreign_load_pdf_source_init( VipsForeignLoadPdfSource *source ) +{ +} + +#endif /*defined(HAVE_POPPLER)*/ /* Also used by the pdfium loader. */ @@ -821,3 +937,36 @@ vips_pdfload_buffer( void *buf, size_t len, VipsImage **out, ... ) return( result ); } +/** + * vips_pdfload_source: + * @source: source to load from + * @out: (out): image to write + * @...: %NULL-terminated list of optional named arguments + * + * Optional arguments: + * + * * @page: %gint, load this page, numbered from zero + * * @n: %gint, load this many pages + * * @dpi: %gdouble, render at this DPI + * * @scale: %gdouble, scale render by this factor + * * @background: #VipsArrayDouble background colour + * + * Exactly as vips_pdfload(), but read from a source. + * + * See also: vips_pdfload() + * + * Returns: 0 on success, -1 on error. + */ +int +vips_pdfload_source( VipsSource *source, VipsImage **out, ... ) +{ + va_list ap; + int result; + + va_start( ap, out ); + result = vips_call_split( "pdfload_source", ap, source, out ); + va_end( ap ); + + return( result ); +} + diff --git a/libvips/foreign/pforeign.h b/libvips/foreign/pforeign.h index 40f79e26..6adea1bc 100644 --- a/libvips/foreign/pforeign.h +++ b/libvips/foreign/pforeign.h @@ -49,11 +49,11 @@ void vips__tiff_init( void ); int vips__tiff_write( VipsImage *in, const char *filename, VipsForeignTiffCompression compression, int Q, - VipsForeignTiffPredictor predictor, + VipsForeignTiffPredictor predictor, char *profile, gboolean tile, int tile_width, int tile_height, gboolean pyramid, - gboolean squash, + int bitdepth, gboolean miniswhite, VipsForeignTiffResunit resunit, double xres, double yres, gboolean bigtiff, @@ -61,7 +61,10 @@ int vips__tiff_write( VipsImage *in, const char *filename, gboolean properties, gboolean strip, VipsRegionShrink region_shrink, - int level, gboolean lossless ); + int level, + gboolean lossless, + VipsForeignDzDepth depth, + gboolean subifd ); int vips__tiff_write_buf( VipsImage *in, void **obuf, size_t *olen, @@ -70,30 +73,24 @@ int vips__tiff_write_buf( VipsImage *in, char *profile, gboolean tile, int tile_width, int tile_height, gboolean pyramid, - gboolean squash, + int bitdepth, gboolean miniswhite, VipsForeignTiffResunit resunit, double xres, double yres, gboolean bigtiff, gboolean rgbjpeg, gboolean properties, gboolean strip, VipsRegionShrink region_shrink, - int level, gboolean lossless ); + int level, + gboolean lossless, + VipsForeignDzDepth depth, + gboolean subifd ); -int vips__tiff_read_header( const char *filename, VipsImage *out, - int page, int n, gboolean autorotate ); -int vips__tiff_read( const char *filename, VipsImage *out, - int page, int n, gboolean autorotate ); - -gboolean vips__istiff( const char *filename ); -gboolean vips__istifftiled( const char *filename ); - -gboolean vips__istiff_buffer( const void *buf, size_t len ); -gboolean vips__istifftiled_buffer( const void *buf, size_t len ); - -int vips__tiff_read_header_buffer( const void *buf, size_t len, VipsImage *out, - int page, int n, gboolean autorotate ); -int vips__tiff_read_buffer( const void *buf, size_t len, VipsImage *out, - int page, int n, gboolean autorotate ); +gboolean vips__istiff_source( VipsSource *source ); +gboolean vips__istifftiled_source( VipsSource *source ); +int vips__tiff_read_header_source( VipsSource *source, VipsImage *out, + int page, int n, gboolean autorotate, int subifd ); +int vips__tiff_read_source( VipsSource *source, VipsImage *out, + int page, int n, gboolean autorotate, int subifd ); extern const char *vips__foreign_tiff_suffs[]; @@ -152,65 +149,43 @@ int vips__mat_load( const char *filename, VipsImage *out ); int vips__mat_header( const char *filename, VipsImage *out ); int vips__mat_ismat( const char *filename ); -int vips__ppm_header( const char *name, VipsImage *out ); -int vips__ppm_load( const char *name, VipsImage *out ); -int vips__ppm_isppm( const char *filename ); -VipsForeignFlags vips__ppm_flags( const char *filename ); extern const char *vips__ppm_suffs[]; -int vips__ppm_save( VipsImage *in, const char *filename, - gboolean ascii, gboolean squash ); +int vips__ppm_save_target( VipsImage *in, VipsTarget *target, + gboolean ascii, gboolean squash ); +int vips__rad_israd( VipsSource *source ); +int vips__rad_header( VipsSource *source, VipsImage *out ); +int vips__rad_load( VipsSource *source, VipsImage *out ); -int vips__rad_israd( const char *filename ); -int vips__rad_header( const char *filename, VipsImage *out ); -int vips__rad_load( const char *filename, VipsImage *out ); - -int vips__rad_save( VipsImage *in, const char *filename ); -int vips__rad_save_buf( VipsImage *in, void **obuf, size_t *olen ); +int vips__rad_save( VipsImage *in, VipsTarget *target ); extern const char *vips__rad_suffs[]; extern const char *vips__jpeg_suffs[]; -int vips__jpeg_write_file( VipsImage *in, - const char *filename, int Q, const char *profile, +int vips__jpeg_write_target( VipsImage *in, VipsTarget *target, + int Q, const char *profile, gboolean optimize_coding, gboolean progressive, gboolean strip, - gboolean no_subsample, gboolean trellis_quant, - gboolean overshoot_deringing, gboolean optimize_scans, - int quant_table ); -int vips__jpeg_write_buffer( VipsImage *in, - void **obuf, size_t *olen, int Q, const char *profile, - gboolean optimize_coding, gboolean progressive, gboolean strip, - gboolean no_subsample, gboolean trellis_quant, - gboolean overshoot_deringing, gboolean optimize_scans, - int quant_table ); + gboolean trellis_quant, gboolean overshoot_deringing, + gboolean optimize_scans, int quant_table, + VipsForeignJpegSubsample subsample_mode ); -int vips__isjpeg_buffer( const void *buf, size_t len ); -int vips__isjpeg( const char *filename ); -int vips__jpeg_read_file( const char *name, VipsImage *out, - gboolean header_only, int shrink, gboolean fail, gboolean autorotate ); -int vips__jpeg_read_buffer( const void *buf, size_t len, VipsImage *out, +int vips__jpeg_read_source( VipsSource *source, VipsImage *out, gboolean header_only, int shrink, int fail, gboolean autorotate ); +int vips__isjpeg_source( VipsSource *source ); -int vips__png_header( const char *name, VipsImage *out ); -int vips__png_read( const char *name, VipsImage *out, gboolean fail ); -gboolean vips__png_ispng_buffer( const void *buf, size_t len ); -int vips__png_ispng( const char *filename ); -gboolean vips__png_isinterlaced( const char *filename ); -gboolean vips__png_isinterlaced_buffer( const void *buffer, size_t length ); -extern const char *vips__png_suffs[]; -int vips__png_read_buffer( const void *buffer, size_t length, VipsImage *out, +int vips__png_ispng_source( VipsSource *source ); +int vips__png_header_source( VipsSource *source, VipsImage *out ); +int vips__png_read_source( VipsSource *source, VipsImage *out, gboolean fail ); -int vips__png_header_buffer( const void *buffer, size_t length, VipsImage *out ); +gboolean vips__png_isinterlaced_source( VipsSource *source ); +extern const char *vips__png_suffs[]; -int vips__png_write( VipsImage *in, const char *filename, +int vips__png_write_target( VipsImage *in, VipsTarget *target, int compress, int interlace, const char *profile, VipsForeignPngFilter filter, gboolean strip, - gboolean palette, int colours, int Q, double dither ); -int vips__png_write_buf( VipsImage *in, - void **obuf, size_t *olen, int compression, int interlace, - const char *profile, VipsForeignPngFilter filter, gboolean strip, - gboolean palette, int colours, int Q, double dither ); + gboolean palette, int Q, double dither, + int bitdepth ); /* Map WEBP metadata names to vips names. */ @@ -224,26 +199,14 @@ extern const VipsWebPNames vips__webp_names[]; extern const int vips__n_webp_names; extern const char *vips__webp_suffs[]; -int vips__iswebp_buffer( const void *buf, size_t len ); -int vips__iswebp( const char *filename ); +int vips__iswebp_source( VipsSource *source ); -int vips__webp_read_file_header( const char *name, VipsImage *out, +int vips__webp_read_header_source( VipsSource *source, VipsImage *out, int page, int n, double scale ); -int vips__webp_read_file( const char *name, VipsImage *out, +int vips__webp_read_source( VipsSource *source, VipsImage *out, int page, int n, double scale ); -int vips__webp_read_buffer_header( const void *buf, size_t len, VipsImage *out, - int page, int n, double scale ); -int vips__webp_read_buffer( const void *buf, size_t len, VipsImage *out, - int page, int n, double scale ); - -int vips__webp_write_file( VipsImage *out, const char *filename, - int Q, gboolean lossless, VipsForeignWebpPreset preset, - gboolean smart_subsample, gboolean near_lossless, - int alpha_q, int reduction_effort, - gboolean min_size, int kmin, int kmax, - gboolean strip ); -int vips__webp_write_buffer( VipsImage *out, void **buf, size_t *len, +int vips__webp_write_target( VipsImage *image, VipsTarget *target, int Q, gboolean lossless, VipsForeignWebpPreset preset, gboolean smart_subsample, gboolean near_lossless, int alpha_q, int reduction_effort, diff --git a/libvips/foreign/pngload.c b/libvips/foreign/pngload.c index 9e04e98f..8092078e 100644 --- a/libvips/foreign/pngload.c +++ b/libvips/foreign/pngload.c @@ -50,29 +50,40 @@ #include "pforeign.h" -#ifdef HAVE_PNG +#if defined(HAVE_PNG) && !defined(HAVE_SPNG) typedef struct _VipsForeignLoadPng { VipsForeignLoad parent_object; - /* Filename for load. + /* Set by subclasses. */ - char *filename; + VipsSource *source; } VipsForeignLoadPng; typedef VipsForeignLoadClass VipsForeignLoadPngClass; -G_DEFINE_TYPE( VipsForeignLoadPng, vips_foreign_load_png, +G_DEFINE_ABSTRACT_TYPE( VipsForeignLoadPng, vips_foreign_load_png, VIPS_TYPE_FOREIGN_LOAD ); +static void +vips_foreign_load_png_dispose( GObject *gobject ) +{ + VipsForeignLoadPng *png = (VipsForeignLoadPng *) gobject; + + VIPS_UNREF( png->source ); + + G_OBJECT_CLASS( vips_foreign_load_png_parent_class )-> + dispose( gobject ); +} + static VipsForeignFlags -vips_foreign_load_png_get_flags_filename( const char *filename ) +vips_foreign_load_png_get_flags_source( VipsSource *source ) { VipsForeignFlags flags; flags = 0; - if( vips__png_isinterlaced( filename ) ) + if( vips__png_isinterlaced_source( source ) ) flags |= VIPS_FOREIGN_PARTIAL; else flags |= VIPS_FOREIGN_SEQUENTIAL; @@ -85,7 +96,21 @@ vips_foreign_load_png_get_flags( VipsForeignLoad *load ) { VipsForeignLoadPng *png = (VipsForeignLoadPng *) load; - return( vips_foreign_load_png_get_flags_filename( png->filename ) ); + return( vips_foreign_load_png_get_flags_source( png->source ) ); +} + +static VipsForeignFlags +vips_foreign_load_png_get_flags_filename( const char *filename ) +{ + VipsSource *source; + VipsForeignFlags flags; + + if( !(source = vips_source_new_from_file( filename )) ) + return( 0 ); + flags = vips_foreign_load_png_get_flags_source( source ); + VIPS_UNREF( source ); + + return( flags ); } static int @@ -93,11 +118,9 @@ vips_foreign_load_png_header( VipsForeignLoad *load ) { VipsForeignLoadPng *png = (VipsForeignLoadPng *) load; - if( vips__png_header( png->filename, load->out ) ) + if( vips__png_header_source( png->source, load->out ) ) return( -1 ); - VIPS_SETSTR( load->out->filename, png->filename ); - return( 0 ); } @@ -106,7 +129,7 @@ vips_foreign_load_png_load( VipsForeignLoad *load ) { VipsForeignLoadPng *png = (VipsForeignLoadPng *) load; - if( vips__png_read( png->filename, load->real, load->fail ) ) + if( vips__png_read_source( png->source, load->real, load->fail ) ) return( -1 ); return( 0 ); @@ -120,31 +143,21 @@ vips_foreign_load_png_class_init( VipsForeignLoadPngClass *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; + gobject_class->dispose = vips_foreign_load_png_dispose; - object_class->nickname = "pngload"; - object_class->description = _( "load png from file" ); - - foreign_class->suffs = vips__png_suffs; + object_class->nickname = "pngload_base"; + object_class->description = _( "load png base class" ); /* We are fast at is_a(), so high priority. */ foreign_class->priority = 200; - load_class->is_a = vips__png_ispng; load_class->get_flags_filename = vips_foreign_load_png_get_flags_filename; load_class->get_flags = vips_foreign_load_png_get_flags; load_class->header = vips_foreign_load_png_header; load_class->load = vips_foreign_load_png_load; - VIPS_ARG_STRING( class, "filename", 1, - _( "Filename" ), - _( "Filename to load from" ), - VIPS_ARGUMENT_REQUIRED_INPUT, - G_STRUCT_OFFSET( VipsForeignLoadPng, filename ), - NULL ); } static void @@ -152,59 +165,196 @@ vips_foreign_load_png_init( VipsForeignLoadPng *png ) { } +typedef struct _VipsForeignLoadPngSource { + VipsForeignLoadPng parent_object; + + /* Load from a source. + */ + VipsSource *source; + +} VipsForeignLoadPngSource; + +typedef VipsForeignLoadPngClass VipsForeignLoadPngSourceClass; + +G_DEFINE_TYPE( VipsForeignLoadPngSource, vips_foreign_load_png_source, + vips_foreign_load_png_get_type() ); + +static int +vips_foreign_load_png_source_build( VipsObject *object ) +{ + VipsForeignLoadPng *png = (VipsForeignLoadPng *) object; + VipsForeignLoadPngSource *source = (VipsForeignLoadPngSource *) object; + + if( source->source ) { + png->source = source->source; + g_object_ref( png->source ); + } + + if( VIPS_OBJECT_CLASS( vips_foreign_load_png_source_parent_class )-> + build( object ) ) + return( -1 ); + + return( 0 ); +} + +static gboolean +vips_foreign_load_png_source_is_a_source( VipsSource *source ) +{ + return( vips__png_ispng_source( source ) ); +} + +static void +vips_foreign_load_png_source_class_init( VipsForeignLoadPngSourceClass *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 = "pngload_source"; + object_class->description = _( "load png from source" ); + object_class->build = vips_foreign_load_png_source_build; + + load_class->is_a_source = vips_foreign_load_png_source_is_a_source; + + VIPS_ARG_OBJECT( class, "source", 1, + _( "Source" ), + _( "Source to load from" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadPngSource, source ), + VIPS_TYPE_SOURCE ); + +} + +static void +vips_foreign_load_png_source_init( VipsForeignLoadPngSource *source ) +{ +} + +typedef struct _VipsForeignLoadPngFile { + VipsForeignLoadPng parent_object; + + /* Filename for load. + */ + char *filename; + +} VipsForeignLoadPngFile; + +typedef VipsForeignLoadPngClass VipsForeignLoadPngFileClass; + +G_DEFINE_TYPE( VipsForeignLoadPngFile, vips_foreign_load_png_file, + vips_foreign_load_png_get_type() ); + +static int +vips_foreign_load_png_file_build( VipsObject *object ) +{ + VipsForeignLoadPng *png = (VipsForeignLoadPng *) object; + VipsForeignLoadPngFile *file = (VipsForeignLoadPngFile *) object; + + if( file->filename && + !(png->source = vips_source_new_from_file( file->filename )) ) + return( -1 ); + + if( VIPS_OBJECT_CLASS( vips_foreign_load_png_file_parent_class )-> + build( object ) ) + return( -1 ); + + return( 0 ); +} + +static gboolean +vips_foreign_load_png_file_is_a( const char *filename ) +{ + VipsSource *source; + gboolean result; + + if( !(source = vips_source_new_from_file( filename )) ) + return( FALSE ); + result = vips_foreign_load_png_source_is_a_source( source ); + VIPS_UNREF( source ); + + return( result ); +} + +static void +vips_foreign_load_png_file_class_init( VipsForeignLoadPngFileClass *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 = "pngload"; + object_class->description = _( "load png from file" ); + object_class->build = vips_foreign_load_png_file_build; + + foreign_class->suffs = vips__png_suffs; + + load_class->is_a = vips_foreign_load_png_file_is_a; + + VIPS_ARG_STRING( class, "filename", 1, + _( "Filename" ), + _( "Filename to load from" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadPngFile, filename ), + NULL ); +} + +static void +vips_foreign_load_png_file_init( VipsForeignLoadPngFile *file ) +{ +} + typedef struct _VipsForeignLoadPngBuffer { - VipsForeignLoad parent_object; + VipsForeignLoadPng parent_object; /* Load from a buffer. */ - VipsArea *buf; + VipsBlob *blob; } VipsForeignLoadPngBuffer; -typedef VipsForeignLoadClass VipsForeignLoadPngBufferClass; +typedef VipsForeignLoadPngClass VipsForeignLoadPngBufferClass; G_DEFINE_TYPE( VipsForeignLoadPngBuffer, vips_foreign_load_png_buffer, - VIPS_TYPE_FOREIGN_LOAD ); - -static VipsForeignFlags -vips_foreign_load_png_buffer_get_flags( VipsForeignLoad *load ) -{ - VipsForeignLoadPngBuffer *buffer = (VipsForeignLoadPngBuffer *) load; - - VipsForeignFlags flags; - - flags = 0; - if( vips__png_isinterlaced_buffer( buffer->buf->data, - buffer->buf->length ) ) - flags |= VIPS_FOREIGN_PARTIAL; - else - flags |= VIPS_FOREIGN_SEQUENTIAL; - - return( flags ); -} + vips_foreign_load_png_get_type() ); static int -vips_foreign_load_png_buffer_header( VipsForeignLoad *load ) +vips_foreign_load_png_buffer_build( VipsObject *object ) { - VipsForeignLoadPngBuffer *buffer = (VipsForeignLoadPngBuffer *) load; + VipsForeignLoadPng *png = (VipsForeignLoadPng *) object; + VipsForeignLoadPngBuffer *buffer = (VipsForeignLoadPngBuffer *) object; - if( vips__png_header_buffer( buffer->buf->data, buffer->buf->length, - load->out ) ) + if( buffer->blob && + !(png->source = vips_source_new_from_memory( + VIPS_AREA( buffer->blob )->data, + VIPS_AREA( buffer->blob )->length )) ) + return( -1 ); + + if( VIPS_OBJECT_CLASS( vips_foreign_load_png_buffer_parent_class )-> + build( object ) ) return( -1 ); return( 0 ); } -static int -vips_foreign_load_png_buffer_load( VipsForeignLoad *load ) +static gboolean +vips_foreign_load_png_buffer_is_a_buffer( const void *buf, size_t len ) { - VipsForeignLoadPngBuffer *buffer = (VipsForeignLoadPngBuffer *) load; + VipsSource *source; + gboolean result; - if( vips__png_read_buffer( buffer->buf->data, buffer->buf->length, - load->real, load->fail ) ) - return( -1 ); + if( !(source = vips_source_new_from_memory( buf, len )) ) + return( FALSE ); + result = vips_foreign_load_png_source_is_a_source( source ); + VIPS_UNREF( source ); - return( 0 ); + return( result ); } static void @@ -219,17 +369,15 @@ vips_foreign_load_png_buffer_class_init( VipsForeignLoadPngBufferClass *class ) object_class->nickname = "pngload_buffer"; object_class->description = _( "load png from buffer" ); + object_class->build = vips_foreign_load_png_buffer_build; - load_class->is_a_buffer = vips__png_ispng_buffer; - load_class->get_flags = vips_foreign_load_png_buffer_get_flags; - load_class->header = vips_foreign_load_png_buffer_header; - load_class->load = vips_foreign_load_png_buffer_load; + load_class->is_a_buffer = vips_foreign_load_png_buffer_is_a_buffer; VIPS_ARG_BOXED( class, "buffer", 1, _( "Buffer" ), _( "Buffer to load from" ), VIPS_ARGUMENT_REQUIRED_INPUT, - G_STRUCT_OFFSET( VipsForeignLoadPngBuffer, buf ), + G_STRUCT_OFFSET( VipsForeignLoadPngBuffer, blob ), VIPS_TYPE_BLOB ); } @@ -239,7 +387,7 @@ vips_foreign_load_png_buffer_init( VipsForeignLoadPngBuffer *buffer ) { } -#endif /*HAVE_PNG*/ +#endif /*defined(HAVE_PNG) && !defined(HAVE_SPNG)*/ /** * vips_pngload: @@ -277,11 +425,7 @@ vips_pngload( const char *filename, VipsImage **out, ... ) * @out: (out): image to write * @...: %NULL-terminated list of optional named arguments * - * Read a PNG-formatted memory block into a VIPS image. It can read all png - * images, including 8- and 16-bit images, 1 and 3 channel, with and without - * an alpha channel. - * - * Any ICC profile is read and attached to the VIPS image. + * Exactly as vips_pngload(), but read from a PNG-formatted memory block. * * You must not free the buffer while @out is active. The * #VipsObject::postclose signal on @out is a good place to free. @@ -310,4 +454,27 @@ vips_pngload_buffer( void *buf, size_t len, VipsImage **out, ... ) return( result ); } +/** + * vips_pngload_source: + * @source: source to load from + * @out: (out): image to write + * @...: %NULL-terminated list of optional named arguments + * + * Exactly as vips_pngload(), but read from a source. + * + * See also: vips_pngload(). + * + * Returns: 0 on success, -1 on error. + */ +int +vips_pngload_source( VipsSource *source, VipsImage **out, ... ) +{ + va_list ap; + int result; + va_start( ap, out ); + result = vips_call_split( "pngload_source", ap, source, out ); + va_end( ap ); + + return( result ); +} diff --git a/libvips/foreign/pngsave.c b/libvips/foreign/pngsave.c index ab576248..7113d972 100644 --- a/libvips/foreign/pngsave.c +++ b/libvips/foreign/pngsave.c @@ -6,6 +6,8 @@ * - compression should be 0-9, not 1-10 * 20/6/18 [felixbuenemann] * - support png8 palette write with palette, colours, Q, dither + * 24/6/20 + * - add @bitdepth, deprecate @colours */ /* @@ -63,9 +65,17 @@ typedef struct _VipsForeignSavePng { char *profile; VipsForeignPngFilter filter; gboolean palette; - int colours; int Q; double dither; + int bitdepth; + + /* Set by subclasses. + */ + VipsTarget *target; + + /* Deprecated. + */ + int colours; } VipsForeignSavePng; typedef VipsForeignSaveClass VipsForeignSavePngClass; @@ -73,6 +83,55 @@ typedef VipsForeignSaveClass VipsForeignSavePngClass; G_DEFINE_ABSTRACT_TYPE( VipsForeignSavePng, vips_foreign_save_png, VIPS_TYPE_FOREIGN_SAVE ); +static void +vips_foreign_save_png_dispose( GObject *gobject ) +{ + VipsForeignSavePng *png = (VipsForeignSavePng *) gobject; + + if( png->target ) + vips_target_finish( png->target ); + VIPS_UNREF( png->target ); + + G_OBJECT_CLASS( vips_foreign_save_png_parent_class )-> + dispose( gobject ); +} + +static int +vips_foreign_save_png_build( VipsObject *object ) +{ + VipsForeignSave *save = (VipsForeignSave *) object; + VipsForeignSavePng *png = (VipsForeignSavePng *) object; + + if( VIPS_OBJECT_CLASS( vips_foreign_save_png_parent_class )-> + build( object ) ) + return( -1 ); + + /* Deprecated "colours" arg just sets bitdepth large enough to hold + * that many colours. + */ + if( vips_object_argument_isset( object, "colours" ) ) + png->bitdepth = ceil( log2( png->colours ) ); + + if( !vips_object_argument_isset( object, "bitdepth" ) ) + png->bitdepth = + save->ready->BandFmt == VIPS_FORMAT_UCHAR ? 8 : 16; + + /* If this is a RGB or RGBA image and a low bit depth has been + * requested, enable palettization. + */ + if( save->ready->Bands > 2 && + png->bitdepth < 8 ) + png->palette = TRUE; + + if( vips__png_write_target( save->ready, png->target, + png->compression, png->interlace, png->profile, png->filter, + save->strip, png->palette, png->Q, png->dither, + png->bitdepth ) ) + return( -1 ); + + return( 0 ); +} + /* Save a bit of typing. */ #define UC VIPS_FORMAT_UCHAR @@ -99,11 +158,13 @@ vips_foreign_save_png_class_init( VipsForeignSavePngClass *class ) VipsForeignClass *foreign_class = (VipsForeignClass *) class; VipsForeignSaveClass *save_class = (VipsForeignSaveClass *) class; + gobject_class->dispose = vips_foreign_save_png_dispose; gobject_class->set_property = vips_object_set_property; gobject_class->get_property = vips_object_get_property; object_class->nickname = "pngsave_base"; object_class->description = _( "save png" ); + object_class->build = vips_foreign_save_png_build; foreign_class->suffs = vips__png_suffs; @@ -146,13 +207,6 @@ vips_foreign_save_png_class_init( VipsForeignSavePngClass *class ) G_STRUCT_OFFSET( VipsForeignSavePng, palette ), FALSE ); - VIPS_ARG_INT( class, "colours", 14, - _( "Colours" ), - _( "Max number of palette colours" ), - VIPS_ARGUMENT_OPTIONAL_INPUT, - G_STRUCT_OFFSET( VipsForeignSavePng, colours ), - 2, 256, 256 ); - VIPS_ARG_INT( class, "Q", 15, _( "Quality" ), _( "Quantisation quality" ), @@ -167,6 +221,20 @@ vips_foreign_save_png_class_init( VipsForeignSavePngClass *class ) G_STRUCT_OFFSET( VipsForeignSavePng, dither ), 0.0, 1.0, 1.0 ); + VIPS_ARG_INT( class, "bitdepth", 17, + _( "Bit depth" ), + _( "Write as a 1, 2, 4 or 8 bit image" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsForeignSavePng, bitdepth ), + 0, 8, 0 ); + + VIPS_ARG_INT( class, "colours", 14, + _( "Colours" ), + _( "Max number of palette colours" ), + VIPS_ARGUMENT_OPTIONAL_INPUT | VIPS_ARGUMENT_DEPRECATED, + G_STRUCT_OFFSET( VipsForeignSavePng, colours ), + 2, 256, 256 ); + } static void @@ -174,11 +242,64 @@ vips_foreign_save_png_init( VipsForeignSavePng *png ) { png->compression = 6; png->filter = VIPS_FOREIGN_PNG_FILTER_ALL; - png->colours = 256; png->Q = 100; png->dither = 1.0; } +typedef struct _VipsForeignSavePngTarget { + VipsForeignSavePng parent_object; + + VipsTarget *target; +} VipsForeignSavePngTarget; + +typedef VipsForeignSavePngClass VipsForeignSavePngTargetClass; + +G_DEFINE_TYPE( VipsForeignSavePngTarget, vips_foreign_save_png_target, + vips_foreign_save_png_get_type() ); + +static int +vips_foreign_save_png_target_build( VipsObject *object ) +{ + VipsForeignSavePng *png = (VipsForeignSavePng *) object; + VipsForeignSavePngTarget *target = (VipsForeignSavePngTarget *) object; + + png->target = target->target; + g_object_ref( png->target ); + + if( VIPS_OBJECT_CLASS( vips_foreign_save_png_target_parent_class )-> + build( object ) ) + return( -1 ); + + return( 0 ); +} + +static void +vips_foreign_save_png_target_class_init( VipsForeignSavePngTargetClass *class ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( class ); + VipsObjectClass *object_class = (VipsObjectClass *) class; + + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + object_class->nickname = "pngsave_target"; + object_class->description = _( "save image to target as PNG" ); + object_class->build = vips_foreign_save_png_target_build; + + VIPS_ARG_OBJECT( class, "target", 1, + _( "Target" ), + _( "Target to save to" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsForeignSavePngTarget, target ), + VIPS_TYPE_TARGET ); + +} + +static void +vips_foreign_save_png_target_init( VipsForeignSavePngTarget *target ) +{ +} + typedef struct _VipsForeignSavePngFile { VipsForeignSavePng parent_object; @@ -193,20 +314,16 @@ G_DEFINE_TYPE( VipsForeignSavePngFile, vips_foreign_save_png_file, static int vips_foreign_save_png_file_build( VipsObject *object ) { - VipsForeignSave *save = (VipsForeignSave *) object; VipsForeignSavePng *png = (VipsForeignSavePng *) object; - VipsForeignSavePngFile *png_file = (VipsForeignSavePngFile *) object; + VipsForeignSavePngFile *file = (VipsForeignSavePngFile *) object; + + if( !(png->target = vips_target_new_to_file( file->filename )) ) + return( -1 ); if( VIPS_OBJECT_CLASS( vips_foreign_save_png_file_parent_class )-> build( object ) ) return( -1 ); - if( vips__png_write( save->ready, - png_file->filename, png->compression, png->interlace, - png->profile, png->filter, save->strip, png->palette, - png->colours, png->Q, png->dither ) ) - return( -1 ); - return( 0 ); } @@ -250,27 +367,20 @@ G_DEFINE_TYPE( VipsForeignSavePngBuffer, vips_foreign_save_png_buffer, static int vips_foreign_save_png_buffer_build( VipsObject *object ) { - VipsForeignSave *save = (VipsForeignSave *) object; VipsForeignSavePng *png = (VipsForeignSavePng *) object; + VipsForeignSavePngBuffer *buffer = (VipsForeignSavePngBuffer *) object; - void *obuf; - size_t olen; VipsBlob *blob; + if( !(png->target = vips_target_new_to_memory()) ) + return( -1 ); + if( VIPS_OBJECT_CLASS( vips_foreign_save_png_buffer_parent_class )-> build( object ) ) return( -1 ); - if( vips__png_write_buf( save->ready, &obuf, &olen, - png->compression, png->interlace, png->profile, png->filter, - save->strip, png->palette, png->colours, png->Q, png->dither ) ) - return( -1 ); - - /* vips__png_write_buf() makes a buffer that needs g_free(), not - * vips_free(). - */ - blob = vips_blob_new( (VipsCallbackFn) g_free, obuf, olen ); - g_object_set( object, "buffer", blob, NULL ); + g_object_get( png->target, "blob", &blob, NULL ); + g_object_set( buffer, "buffer", blob, NULL ); vips_area_unref( VIPS_AREA( blob ) ); return( 0 ); @@ -312,14 +422,14 @@ vips_foreign_save_png_buffer_init( VipsForeignSavePngBuffer *buffer ) * * Optional arguments: * - * * @compression: compression level - * * @interlace: interlace image - * * @profile: ICC profile to embed + * * @compression: %gint, compression level + * * @interlace: %gboolean, interlace image + * * @profile: %gchararray, ICC profile to embed * * @filter: #VipsForeignPngFilter row filter flag(s) - * * @palette: enable quantisation to 8bpp palette - * * @colours: max number of palette colours for quantisation - * * @Q: quality for 8bpp quantisation (does not exceed @colours) - * * @dither: amount of dithering for 8bpp quantization + * * @palette: %gboolean, enable quantisation to 8bpp palette + * * @Q: %gint, quality for 8bpp quantisation + * * @dither: %gdouble, amount of dithering for 8bpp quantization + * * @bitdepth: %int, set write bit depth to 1, 2, 4 or 8 * * Write a VIPS image to a file as PNG. * @@ -345,13 +455,15 @@ vips_foreign_save_png_buffer_init( VipsForeignSavePngBuffer *buffer ) * alpha before saving. Images with more than one byte per band element are * saved as 16-bit PNG, others are saved as 8-bit PNG. * - * Set @palette to %TRUE to enable quantisation to an 8-bit per pixel palette - * image with alpha transparency support. If @colours is given, it limits the - * maximum number of palette entries. Similar to JPEG the quality can also be - * be changed with the @Q parameter which further reduces the palette size and - * @dither controls the amount of Floyd-Steinberg dithering. + * Set @palette to %TRUE to enable palette mode for RGB or RGBA images. A + * palette will be computed with enough space for @bitdepth (1, 2, 4 or 8) + * bits. Use @Q to set the optimisation effort, and @dither to set the degree of + * Floyd-Steinberg dithering. * This feature requires libvips to be compiled with libimagequant. * + * You can also set @bitdepth for mono and mono + alpha images, and the image + * will be quantized. + * * XMP metadata is written to the XMP chunk. PNG comments are written to * separate text chunks. * @@ -381,14 +493,14 @@ vips_pngsave( VipsImage *in, const char *filename, ... ) * * Optional arguments: * - * * @compression: compression level - * * @interlace: interlace image - * * @profile: ICC profile to embed - * * @filter: libpng row filter flag(s) - * * @palette: enable quantisation to 8bpp palette - * * @colours: max number of palette colours for quantisation - * * @Q: quality for 8bpp quantisation (does not exceed @colours) - * * @dither: amount of dithering for 8bpp quantization + * * @compression: %gint, compression level + * * @interlace: %gboolean, interlace image + * * @profile: %gchararray, ICC profile to embed + * * @filter: #VipsForeignPngFilter row filter flag(s) + * * @palette: %gboolean, enable quantisation to 8bpp palette + * * @Q: %gint, quality for 8bpp quantisation + * * @dither: %gdouble, amount of dithering for 8bpp quantization + * * @bitdepth: %int, set write bit depth to 1, 2, 4 or 8 * * As vips_pngsave(), but save to a memory buffer. * @@ -427,3 +539,39 @@ vips_pngsave_buffer( VipsImage *in, void **buf, size_t *len, ... ) return( result ); } + +/** + * vips_pngsave_target: (method) + * @in: image to save + * @target: save image to this target + * @...: %NULL-terminated list of optional named arguments + * + * Optional arguments: + * + * * @compression: compression level + * * @interlace: interlace image + * * @profile: ICC profile to embed + * * @filter: libpng row filter flag(s) + * * @palette: enable quantisation to 8bpp palette + * * @Q: quality for 8bpp quantisation + * * @dither: amount of dithering for 8bpp quantization + * * @bitdepth: %int, set write bit depth to 1, 2, 4 or 8 + * + * As vips_pngsave(), but save to a target. + * + * See also: vips_pngsave(), vips_image_write_to_target(). + * + * Returns: 0 on success, -1 on error. + */ +int +vips_pngsave_target( VipsImage *in, VipsTarget *target, ... ) +{ + va_list ap; + int result; + + va_start( ap, target ); + result = vips_call_split( "pngsave_target", ap, in, target ); + va_end( ap ); + + return( result ); +} diff --git a/libvips/foreign/ppm.c b/libvips/foreign/ppm.c deleted file mode 100644 index 7fe27c01..00000000 --- a/libvips/foreign/ppm.c +++ /dev/null @@ -1,835 +0,0 @@ -/* Read a ppm file. - * - * Stephen Chan ... original code - * - * 21/11/00 JC - * - hacked for VIPS - * - reads ppm/pgm/pbm - * - mmaps binary pgm/ppm - * - reads all ascii formats (slowly!) - * 22/11/00 JC - * - oops, ascii read was broken - * - does 16/32 bit ascii now as well - * 24/5/01 - * - im_ppm2vips_header() added - * 28/11/03 JC - * - better no-overshoot on tile loop - * 22/5/04 - * - does 16/32 bit binary too - * - tiny fix for missing file close on read error - * 19/8/05 - * - use im_raw2vips() for binary read - * 9/9/05 - * - tiny cleanups - * 3/11/07 - * - use im_wbuffer() for bg writes - * 4/2/10 - * - gtkdoc - * 1/5/10 - * - add PFM (portable float map) support - * 19/12/11 - * - rework as a set of fns ready to be called from a class - * 8/11/14 - * - add 1 bit write - * 4/6/15 - * - try to support DOS files under linux ... we have to look for \r\n - * linebreaks - * 29/7/19 Kyle-Kyle - * - fix a loop with malformed ppm - */ - -/* - - 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 - - */ - -#ifdef HAVE_CONFIG_H -#include -#endif /*HAVE_CONFIG_H*/ -#include - -#ifdef HAVE_PPM - -#include -#include -#include -#include -#include -#include - -#include -#include - -#include "pforeign.h" - -/* The largest number/field/whatever we can read. - */ -#define MAX_THING (80) - -static void -skip_line( FILE *fp ) -{ - int ch; - - while( (ch = vips__fgetc( fp )) != '\n' && - ch != EOF ) - ; -} - -static void -skip_white_space( FILE *fp ) -{ - int ch; - - while( isspace( ch = vips__fgetc( fp ) ) ) - ; - ungetc( ch, fp ); - - if( ch == '#' ) { - skip_line( fp ); - skip_white_space( fp ); - } -} - -static int -read_int( FILE *fp, int *i ) -{ - skip_white_space( fp ); - if( fscanf( fp, "%d", i ) != 1 ) { - vips_error( "ppm2vips", "%s", _( "bad int" ) ); - return( -1 ); - } - - return( 0 ); -} - -static int -read_float( FILE *fp, float *f ) -{ - skip_white_space( fp ); - if( fscanf( fp, "%f", f ) != 1 ) { - vips_error( "ppm2vips", "%s", _( "bad float" ) ); - return( -1 ); - } - - return( 0 ); -} - -/* ppm types. - */ -static char *magic_names[] = { - "P1", /* pbm ... 1 band 1 bit, ascii */ - "P2", /* pgm ... 1 band many bit, ascii */ - "P3", /* ppm ... 3 band many bit, ascii */ - "P4", /* pbm ... 1 band 1 bit, binary */ - "P5", /* pgm ... 1 band 8 bit, binary */ - "P6", /* ppm ... 3 band 8 bit, binary */ - "PF", /* pfm ... 3 band 32 bit, binary */ - "Pf" /* pfm ... 1 band 32 bit, binary */ -}; - -static int -read_header( FILE *fp, VipsImage *out, int *bits, int *ascii, int *msb_first ) -{ - int width, height, bands; - VipsBandFormat format; - VipsInterpretation interpretation; - int index; - char buf[MAX_THING]; - - /* Characteristics, indexed by ppm type. - */ - static int lookup_bits[] = { - 1, 8, 8, 1, 8, 8, 32, 32 - }; - static int lookup_bands[] = { - 1, 1, 3, 1, 1, 3, 3, 1 - }; - static int lookup_ascii[] = { - 1, 1, 1, 0, 0, 0, 0, 0 - }; - - /* Read in the magic number. - */ - buf[0] = vips__fgetc( fp ); - buf[1] = vips__fgetc( fp ); - buf[2] = '\0'; - - for( index = 0; index < VIPS_NUMBER( magic_names ); index++ ) - if( strcmp( magic_names[index], buf ) == 0 ) - break; - if( index == VIPS_NUMBER( magic_names ) ) { - vips_error( "ppm2vips", "%s", _( "bad magic number" ) ); - return( -1 ); - } - *bits = lookup_bits[index]; - bands = lookup_bands[index]; - *ascii = lookup_ascii[index]; - - /* Default ... can be changed below for PFM images. - */ - *msb_first = 0; - - /* Read in size. - */ - if( read_int( fp, &width ) || - read_int( fp, &height ) ) - return( -1 ); - - /* Read in max value / scale for >1 bit images. - */ - if( *bits > 1 ) { - if( index == 6 || index == 7 ) { - float scale; - - if( read_float( fp, &scale ) ) - return( -1 ); - - /* Scale > 0 means big-endian. - */ - *msb_first = scale > 0; - vips_image_set_double( out, - "pfm-scale", VIPS_FABS( scale ) ); - } - else { - int max_value; - - if( read_int( fp, &max_value ) ) - return( -1 ); - - if( max_value > 255 ) - *bits = 16; - if( max_value > 65535 ) - *bits = 32; - } - } - - /* For binary images, there is always exactly 1 more whitespace - * character before the data starts. - */ - if( !*ascii && - !isspace( vips__fgetc( fp ) ) ) { - vips_error( "ppm2vips", "%s", - _( "not whitespace before start of binary data" ) ); - return( -1 ); - } - - /* Choose a VIPS bandfmt. - */ - switch( *bits ) { - case 1: - case 8: - format = VIPS_FORMAT_UCHAR; - break; - - case 16: - format = VIPS_FORMAT_USHORT; - break; - - case 32: - if( index == 6 || index == 7 ) - format = VIPS_FORMAT_FLOAT; - else - format = VIPS_FORMAT_UINT; - break; - - default: - g_assert_not_reached(); - - /* Stop compiler warnings. - */ - format = VIPS_FORMAT_UCHAR; - } - - if( bands == 1 ) { - if( format == VIPS_FORMAT_USHORT ) - interpretation = VIPS_INTERPRETATION_GREY16; - else - interpretation = VIPS_INTERPRETATION_B_W; - } - else { - if( format == VIPS_FORMAT_USHORT ) - interpretation = VIPS_INTERPRETATION_RGB16; - else if( format == VIPS_FORMAT_UINT ) - interpretation = VIPS_INTERPRETATION_RGB; - else - interpretation = VIPS_INTERPRETATION_sRGB; - } - - vips_image_init_fields( out, - width, height, bands, format, - VIPS_CODING_NONE, interpretation, 1.0, 1.0 ); - - return( 0 ); -} - -/* Read a ppm/pgm file using mmap(). - */ -static int -read_mmap( FILE *fp, const char *filename, int msb_first, VipsImage *out ) -{ - const guint64 header_offset = ftell( fp ); - VipsImage *x = vips_image_new(); - VipsImage **t = (VipsImage **) - vips_object_local_array( VIPS_OBJECT( x ), 3 ); - - if( vips_rawload( filename, &t[0], - out->Xsize, out->Ysize, VIPS_IMAGE_SIZEOF_PEL( out ), - "offset", header_offset, - NULL ) || - vips_copy( t[0], &t[1], - "bands", out->Bands, - "format", out->BandFmt, - "coding", out->Coding, - NULL ) || - vips__byteswap_bool( t[1], &t[2], - vips_amiMSBfirst() != msb_first ) || - vips_image_write( t[2], out ) ) { - g_object_unref( x ); - return( -1 ); - } - g_object_unref( x ); - - return( 0 ); -} - -/* Read an ascii ppm/pgm file. - */ -static int -read_ascii( FILE *fp, VipsImage *out ) -{ - int x, y; - VipsPel *buf; - - if( !(buf = VIPS_ARRAY( out, VIPS_IMAGE_SIZEOF_LINE( out ), VipsPel )) ) - return( -1 ); - - for( y = 0; y < out->Ysize; y++ ) { - for( x = 0; x < out->Xsize * out->Bands; x++ ) { - int val; - - if( read_int( fp, &val ) ) - return( -1 ); - - switch( out->BandFmt ) { - case VIPS_FORMAT_UCHAR: - buf[x] = VIPS_CLIP( 0, val, 255 ); - break; - - case VIPS_FORMAT_USHORT: - ((unsigned short *) buf)[x] = - VIPS_CLIP( 0, val, 65535 ); - break; - - case VIPS_FORMAT_UINT: - ((unsigned int *) buf)[x] = val; - break; - - default: - g_assert_not_reached(); - } - } - - if( vips_image_write_line( out, y, buf ) ) - return( -1 ); - } - - return( 0 ); -} - -/* Read an ascii 1 bit file. - */ -static int -read_1bit_ascii( FILE *fp, VipsImage *out ) -{ - int x, y; - VipsPel *buf; - - if( !(buf = VIPS_ARRAY( out, VIPS_IMAGE_SIZEOF_LINE( out ), VipsPel )) ) - return( -1 ); - - for( y = 0; y < out->Ysize; y++ ) { - for( x = 0; x < out->Xsize * out->Bands; x++ ) { - int val; - - if( read_int( fp, &val ) ) - return( -1 ); - - if( val ) - buf[x] = 0; - else - buf[x] = 255; - } - - if( vips_image_write_line( out, y, buf ) ) - return( -1 ); - } - - return( 0 ); -} - -/* Read a 1 bit binary file. - */ -static int -read_1bit_binary( FILE *fp, VipsImage *out ) -{ - int x, y; - int bits; - VipsPel *buf; - - if( !(buf = VIPS_ARRAY( out, VIPS_IMAGE_SIZEOF_LINE( out ), VipsPel )) ) - return( -1 ); - - bits = fgetc( fp ); - for( y = 0; y < out->Ysize; y++ ) { - for( x = 0; x < out->Xsize * out->Bands; x++ ) { - buf[x] = (bits & 128) ? 0 : 255; - bits = VIPS_LSHIFT_INT( bits, 1 ); - if( (x & 7) == 7 ) - bits = fgetc( fp ); - } - - /* Skip any unaligned bits at end of line. - */ - if( (x & 7) != 0 ) - bits = fgetc( fp ); - - if( vips_image_write_line( out, y, buf ) ) - return( -1 ); - } - - return( 0 ); -} - -static int -parse_ppm( FILE *fp, const char *filename, VipsImage *out ) -{ - int bits; - int ascii; - int msb_first; - - if( read_header( fp, out, &bits, &ascii, &msb_first ) ) - return( -1 ); - - /* What sort of read are we doing? - */ - if( !ascii && bits >= 8 ) - return( read_mmap( fp, filename, msb_first, out ) ); - else if( !ascii && bits == 1 ) - return( read_1bit_binary( fp, out ) ); - else if( ascii && bits == 1 ) - return( read_1bit_ascii( fp, out ) ); - else - return( read_ascii( fp, out ) ); -} - -int -vips__ppm_header( const char *filename, VipsImage *out ) -{ - FILE *fp; - int bits; - int ascii; - int msb_first; - - if( !(fp = vips__file_open_read( filename, NULL, FALSE )) ) - return( -1 ); - - if( read_header( fp, out, &bits, &ascii, &msb_first ) ) { - fclose( fp ); - return( -1 ); - } - - fclose( fp ); - - return( 0 ); -} - -/* Can this PPM file be read with a mmap? - */ -static int -isppmmmap( const char *filename ) -{ - VipsImage *im; - FILE *fp; - int bits; - int ascii; - int msb_first; - - if( !(fp = vips__file_open_read( filename, NULL, FALSE )) ) - return( -1 ); - - im = vips_image_new(); - if( read_header( fp, im, &bits, &ascii, &msb_first ) ) { - g_object_unref( im ); - fclose( fp ); - - return( 0 ); - } - g_object_unref( im ); - fclose( fp ); - - return( !ascii && bits >= 8 ); -} - -int -vips__ppm_load( const char *filename, VipsImage *out ) -{ - FILE *fp; - - /* Note that we open in binary mode. If this is a binary PPM, we need - * to be able to mmap it. - */ - if( !(fp = vips__file_open_read( filename, NULL, FALSE )) ) - return( -1 ); - - if( parse_ppm( fp, filename, out ) ) { - fclose( fp ); - return( -1 ); - } - - fclose( fp ); - - return( 0 ); -} - -int -vips__ppm_isppm( const char *filename ) -{ - VipsPel buf[3]; - - if( vips__get_bytes( filename, buf, 2 ) == 2 ) { - int i; - - buf[2] = '\0'; - for( i = 0; i < VIPS_NUMBER( magic_names ); i++ ) - if( strcmp( (char *) buf, magic_names[i] ) == 0 ) - return( TRUE ); - } - - return( 0 ); -} - -/* ppm flags function. - */ -VipsForeignFlags -vips__ppm_flags( const char *filename ) -{ - VipsForeignFlags flags; - - flags = 0; - if( isppmmmap( filename ) ) - flags |= VIPS_FOREIGN_PARTIAL; - - return( flags ); -} - -const char *vips__ppm_suffs[] = { ".ppm", ".pgm", ".pbm", ".pfm", NULL }; - -struct _Write; - -typedef int (*write_fn)( struct _Write *write, VipsPel *p ); - -/* What we track during a PPM write. - */ -typedef struct _Write { - VipsImage *in; - FILE *fp; - char *name; - write_fn fn; -} Write; - -static void -write_destroy( Write *write ) -{ - VIPS_FREEF( fclose, write->fp ); - VIPS_FREE( write->name ); - - vips_free( write ); -} - -static Write * -write_new( VipsImage *in, const char *name ) -{ - Write *write; - - if( !(write = VIPS_NEW( NULL, Write )) ) - return( NULL ); - - write->in = in; - write->name = vips_strdup( NULL, name ); - write->fp = vips__file_open_write( name, FALSE ); - - if( !write->name || !write->fp ) { - write_destroy( write ); - return( NULL ); - } - - return( write ); -} - -static int -write_ppm_line_ascii( Write *write, VipsPel *p ) -{ - const int sk = VIPS_IMAGE_SIZEOF_PEL( write->in ); - int x, k; - - for( x = 0; x < write->in->Xsize; x++ ) { - for( k = 0; k < write->in->Bands; k++ ) { - switch( write->in->BandFmt ) { - case VIPS_FORMAT_UCHAR: - fprintf( write->fp, - "%d ", p[k] ); - break; - - case VIPS_FORMAT_USHORT: - fprintf( write->fp, - "%d ", ((unsigned short *) p)[k] ); - break; - - case VIPS_FORMAT_UINT: - fprintf( write->fp, - "%d ", ((unsigned int *) p)[k] ); - break; - - default: - g_assert_not_reached(); - } - } - - p += sk; - } - - if( !fprintf( write->fp, "\n" ) ) { - vips_error_system( errno, "vips2ppm", - "%s", _( "write error" ) ); - return( -1 ); - } - - return( 0 ); -} - -static int -write_ppm_line_ascii_squash( Write *write, VipsPel *p ) -{ - int x; - - for( x = 0; x < write->in->Xsize; x++ ) - fprintf( write->fp, "%d ", p[x] ? 0 : 1 ); - - if( !fprintf( write->fp, "\n" ) ) { - vips_error_system( errno, "vips2ppm", - "%s", _( "write error" ) ); - return( -1 ); - } - - return( 0 ); -} - -static int -write_ppm_line_binary( Write *write, VipsPel *p ) -{ - if( vips__file_write( p, VIPS_IMAGE_SIZEOF_LINE( write->in ), 1, - write->fp ) ) - return( -1 ); - - return( 0 ); -} - -static int -write_ppm_line_binary_squash( Write *write, VipsPel *p ) -{ - int x; - int bits; - int n_bits; - - bits = 0; - n_bits = 0; - for( x = 0; x < write->in->Xsize; x++ ) { - bits = VIPS_LSHIFT_INT( bits, 1 ); - n_bits += 1; - bits |= p[x] ? 0 : 1; - - if( n_bits == 8 ) { - if( fputc( bits, write->fp ) == EOF ) { - vips_error_system( errno, "vips2ppm", - "%s", _( "write error" ) ); - return( -1 ); - } - - bits = 0; - n_bits = 0; - } - } - - /* Flush any remaining bits in this line. - */ - if( n_bits ) { - if( fputc( bits, write->fp ) == EOF ) { - vips_error_system( errno, "vips2ppm", - "%s", _( "write error" ) ); - return( -1 ); - } - } - - return( 0 ); -} - -static int -write_ppm_block( VipsRegion *region, VipsRect *area, void *a ) -{ - Write *write = (Write *) a; - int i; - - for( i = 0; i < area->height; i++ ) { - VipsPel *p = VIPS_REGION_ADDR( region, 0, area->top + i ); - - if( write->fn( write, p ) ) - return( -1 ); - } - - return( 0 ); -} - -static int -write_ppm( Write *write, gboolean ascii, gboolean squash ) -{ - VipsImage *in = write->in; - - char *magic; - time_t timebuf; - - magic = "unset"; - if( in->BandFmt == VIPS_FORMAT_FLOAT && in->Bands == 3 ) - magic = "PF"; - else if( in->BandFmt == VIPS_FORMAT_FLOAT && in->Bands == 1 ) - magic = "Pf"; - else if( in->Bands == 1 && ascii && squash ) - magic = "P1"; - else if( in->Bands == 1 && ascii ) - magic = "P2"; - else if( in->Bands == 1 && !ascii && squash ) - magic = "P4"; - else if( in->Bands == 1 && !ascii ) - magic = "P5"; - else if( in->Bands == 3 && ascii ) - magic = "P3"; - else if( in->Bands == 3 && !ascii ) - magic = "P6"; - else - g_assert_not_reached(); - - fprintf( write->fp, "%s\n", magic ); - time( &timebuf ); - fprintf( write->fp, "#vips2ppm - %s\n", ctime( &timebuf ) ); - fprintf( write->fp, "%d %d\n", in->Xsize, in->Ysize ); - - if( !squash ) - switch( in->BandFmt ) { - case VIPS_FORMAT_UCHAR: - fprintf( write->fp, "%d\n", UCHAR_MAX ); - break; - - case VIPS_FORMAT_USHORT: - fprintf( write->fp, "%d\n", USHRT_MAX ); - break; - - case VIPS_FORMAT_UINT: - fprintf( write->fp, "%d\n", UINT_MAX ); - break; - - case VIPS_FORMAT_FLOAT: -{ - double scale; - - if( vips_image_get_double( in, "pfm-scale", &scale ) ) - scale = 1; - if( !vips_amiMSBfirst() ) - scale *= -1; - fprintf( write->fp, "%g\n", scale ); -} - break; - - default: - g_assert_not_reached(); - } - - if( squash ) - write->fn = ascii ? - write_ppm_line_ascii_squash : - write_ppm_line_binary_squash; - else - write->fn = ascii ? - write_ppm_line_ascii : - write_ppm_line_binary; - - if( vips_sink_disc( write->in, write_ppm_block, write ) ) - return( -1 ); - - return( 0 ); -} - -int -vips__ppm_save( VipsImage *in, const char *filename, - gboolean ascii, gboolean squash ) -{ - Write *write; - - if( vips_check_uintorf( "vips2ppm", in ) || - vips_check_bands_1or3( "vips2ppm", in ) || - vips_check_uncoded( "vips2ppm", in ) || - vips_image_pio_input( in ) ) - return( -1 ); - - if( ascii && - in->BandFmt == VIPS_FORMAT_FLOAT ) { - g_warning( "%s", - _( "float images must be binary -- disabling ascii" ) ); - ascii = FALSE; - } - - /* One bit images must come from a 8 bit, one band source. - */ - if( squash && - (in->Bands != 1 || - in->BandFmt != VIPS_FORMAT_UCHAR) ) { - g_warning( "%s", - _( "can only squash 1 band uchar images -- " - "disabling squash" ) ); - squash = FALSE; - } - - if( !(write = write_new( in, filename )) ) - return( -1 ); - - if( write_ppm( write, ascii, squash ) ) { - write_destroy( write ); - return( -1 ); - } - write_destroy( write ); - - return( 0 ); -} - -#endif /*HAVE_PPM*/ diff --git a/libvips/foreign/ppmload.c b/libvips/foreign/ppmload.c index da92b0e7..bcc2e79a 100644 --- a/libvips/foreign/ppmload.c +++ b/libvips/foreign/ppmload.c @@ -1,7 +1,44 @@ /* load ppm from a file * - * 5/12/11 - * - from tiffload.c + * Stephen Chan ... original code + * + * 21/11/00 JC + * - hacked for VIPS + * - reads ppm/pgm/pbm + * - mmaps binary pgm/ppm + * - reads all ascii formats (slowly!) + * 22/11/00 JC + * - oops, ascii read was broken + * - does 16/32 bit ascii now as well + * 24/5/01 + * - im_ppm2vips_header() added + * 28/11/03 JC + * - better no-overshoot on tile loop + * 22/5/04 + * - does 16/32 bit binary too + * - tiny fix for missing file close on read error + * 19/8/05 + * - use im_raw2vips() for binary read + * 9/9/05 + * - tiny cleanups + * 3/11/07 + * - use im_wbuffer() for bg writes + * 1/5/10 + * - add PFM (portable float map) support + * 19/12/11 + * - rework as a set of fns ready to be called from a class + * 8/11/14 + * - add 1 bit write + * 29/7/19 Kyle-Kyle + * - fix a loop with malformed ppm + * 13/11/19 + * - redone with source/target + * - sequential load, plus mmap for filename sources + * - faster plus lower memory use + * 02/02/20 + * - ban max_vaue < 0 + * 27/6/20 + * - add ppmload_source */ /* @@ -40,12 +77,12 @@ #endif /*HAVE_CONFIG_H*/ #include +#include #include #include #include #include -#include #include #include "pforeign.h" @@ -55,21 +92,259 @@ typedef struct _VipsForeignLoadPpm { VipsForeignLoad parent_object; - /* Filename for load. + /* The source we load from, and the buffered wrapper for it. */ - char *filename; + VipsSource *source; + VipsSbuf *sbuf; + + /* Properties of this ppm, from the header. + */ + int width; + int height; + int bands; + VipsBandFormat format; + VipsInterpretation interpretation; + float scale; + int max_value; + int index; /* ppm type .. index in magic_names[] */ + int bits; /* 1, 8, 16 or 32 */ + gboolean ascii; /* TRUE for ascii encoding */ + gboolean msb_first; /* TRUE if most sig byte is first */ + + gboolean have_read_header; } VipsForeignLoadPpm; typedef VipsForeignLoadClass VipsForeignLoadPpmClass; -G_DEFINE_TYPE( VipsForeignLoadPpm, vips_foreign_load_ppm, +G_DEFINE_ABSTRACT_TYPE( VipsForeignLoadPpm, vips_foreign_load_ppm, VIPS_TYPE_FOREIGN_LOAD ); -static VipsForeignFlags -vips_foreign_load_ppm_get_flags_filename( const char *filename ) +/* ppm types. + */ +static char *magic_names[] = { + "P1", /* pbm ... 1 band 1 bit, ascii */ + "P2", /* pgm ... 1 band many bit, ascii */ + "P3", /* ppm ... 3 band many bit, ascii */ + "P4", /* pbm ... 1 band 1 bit, binary */ + "P5", /* pgm ... 1 band 8 bit, binary */ + "P6", /* ppm ... 3 band 8 bit, binary */ + "PF", /* pfm ... 3 band 32 bit, binary */ + "Pf" /* pfm ... 1 band 32 bit, binary */ +}; + +/* Shared with ppmsave. + */ +const char *vips__ppm_suffs[] = { ".ppm", ".pgm", ".pbm", ".pfm", NULL }; + +static gboolean +vips_foreign_load_ppm_is_a_source( VipsSource *source ) { - return( (VipsForeignFlags) vips__ppm_flags( filename ) ); + const unsigned char *data; + + if( (data = vips_source_sniff( source, 2 )) ) { + int i; + + for( i = 0; i < VIPS_NUMBER( magic_names ); i++ ) + if( vips_isprefix( magic_names[i], (char *) data ) ) + return( TRUE ); + } + + return( FALSE ); +} + +static int +get_int( VipsSbuf *sbuf, int *i ) +{ + const char *txt; + + if( vips_sbuf_skip_whitespace( sbuf ) || + !(txt = vips_sbuf_get_non_whitespace( sbuf )) ) + return( -1 ); + + *i = atoi( txt ); + + return( 0 ); +} + +static int +get_float( VipsSbuf *sbuf, float *f ) +{ + const char *txt; + + if( vips_sbuf_skip_whitespace( sbuf ) || + !(txt = vips_sbuf_get_non_whitespace( sbuf )) ) + return( -1 ); + + /* We don't want the locale str -> float conversion. + */ + *f = g_ascii_strtod( txt, NULL ); + + return( 0 ); +} + +static void +vips_foreign_load_ppm_dispose( GObject *gobject ) +{ + VipsForeignLoadPpm *ppm = (VipsForeignLoadPpm *) gobject; + + VIPS_UNREF( ppm->sbuf ); + VIPS_UNREF( ppm->source ); + + G_OBJECT_CLASS( vips_foreign_load_ppm_parent_class )-> + dispose( gobject ); +} + +static int +vips_foreign_load_ppm_build( VipsObject *object ) +{ + VipsForeignLoadPpm *ppm = (VipsForeignLoadPpm *) object; + + if( ppm->source ) + ppm->sbuf = vips_sbuf_new_from_source( ppm->source ); + + if( VIPS_OBJECT_CLASS( vips_foreign_load_ppm_parent_class )-> + build( object ) ) + return( -1 ); + + return( 0 ); +} + +/* Scan the header into our class. + */ +static int +vips_foreign_load_ppm_parse_header( VipsForeignLoadPpm *ppm ) +{ + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( ppm ); + + int i; + char buf[2]; + + /* Characteristics, indexed by ppm type. + */ + static int lookup_bits[] = { + 1, 8, 8, 1, 8, 8, 32, 32 + }; + static int lookup_bands[] = { + 1, 1, 3, 1, 1, 3, 3, 1 + }; + static int lookup_ascii[] = { + 1, 1, 1, 0, 0, 0, 0, 0 + }; + + if( vips_source_rewind( ppm->source ) ) + return( -1 ); + + /* Read in the magic number. + */ + buf[0] = VIPS_SBUF_GETC( ppm->sbuf ); + buf[1] = VIPS_SBUF_GETC( ppm->sbuf ); + + for( i = 0; i < VIPS_NUMBER( magic_names ); i++ ) + if( vips_isprefix( magic_names[i], buf ) ) + break; + if( i == VIPS_NUMBER( magic_names ) ) { + vips_error( class->nickname, "%s", _( "bad magic number" ) ); + return( -1 ); + } + ppm->index = i; + ppm->bits = lookup_bits[i]; + ppm->bands = lookup_bands[i]; + ppm->ascii = lookup_ascii[i]; + + /* Default ... can be changed below for PFM images. + */ + ppm->msb_first = 0; + + /* Read in size. + */ + if( get_int( ppm->sbuf, &ppm->width ) || + get_int( ppm->sbuf, &ppm->height ) ) + return( -1 ); + + /* Read in max value / scale for >1 bit images. + */ + if( ppm->bits > 1 ) { + if( ppm->index == 6 || + ppm->index == 7 ) { + if( get_float( ppm->sbuf, &ppm->scale ) ) + return( -1 ); + + /* Scale > 0 means big-endian. + */ + ppm->msb_first = ppm->scale > 0; + } + else { + if( get_int( ppm->sbuf, &ppm->max_value ) ) + return( -1 ); + + /* max_value must be > 0 and <= 65535, according to + * the spec, but we allow up to 32 bits per pixel. + */ + if( ppm->max_value < 0 ) + ppm->max_value = 0; + + if( ppm->max_value > 255 ) + ppm->bits = 16; + if( ppm->max_value > 65535 ) + ppm->bits = 32; + } + } + + /* For binary images, there is always exactly 1 more whitespace + * character before the data starts. + */ + if( !ppm->ascii && + !isspace( VIPS_SBUF_GETC( ppm->sbuf ) ) ) { + vips_error( class->nickname, "%s", + _( "no whitespace before start of binary data" ) ); + return( -1 ); + } + + /* Choose a VIPS bandfmt. + */ + switch( ppm->bits ) { + case 1: + case 8: + ppm->format = VIPS_FORMAT_UCHAR; + break; + + case 16: + ppm->format = VIPS_FORMAT_USHORT; + break; + + case 32: + if( ppm->index == 6 || + ppm->index == 7 ) + ppm->format = VIPS_FORMAT_FLOAT; + else + ppm->format = VIPS_FORMAT_UINT; + break; + + default: + g_assert_not_reached(); + + /* Stop compiler warnings. + */ + ppm->format = VIPS_FORMAT_UCHAR; + } + + if( ppm->bands == 1 ) { + if( ppm->format == VIPS_FORMAT_USHORT ) + ppm->interpretation = VIPS_INTERPRETATION_GREY16; + else + ppm->interpretation = VIPS_INTERPRETATION_B_W; + } + else { + if( ppm->format == VIPS_FORMAT_USHORT ) + ppm->interpretation = VIPS_INTERPRETATION_RGB16; + else + ppm->interpretation = VIPS_INTERPRETATION_sRGB; + } + + ppm->have_read_header = TRUE; + + return( 0 ); } static VipsForeignFlags @@ -77,7 +352,46 @@ vips_foreign_load_ppm_get_flags( VipsForeignLoad *load ) { VipsForeignLoadPpm *ppm = (VipsForeignLoadPpm *) load; - return( vips_foreign_load_ppm_get_flags_filename( ppm->filename ) ); + VipsForeignFlags flags; + + flags = 0; + + /* If this source supports fast mmap and this PPM is >=8 bit binary, + * then we can mmap the file and support partial load. Otherwise, + * it's sequential. + */ + if( !ppm->have_read_header && + vips_foreign_load_ppm_parse_header( ppm ) ) + return( 0 ); + if( vips_source_is_mappable( ppm->source ) && + !ppm->ascii && + ppm->bits >= 8 ) + flags |= VIPS_FOREIGN_PARTIAL; + else + flags |= VIPS_FOREIGN_SEQUENTIAL; + + return( flags ); +} + +static void +vips_foreign_load_ppm_set_image( VipsForeignLoadPpm *ppm, VipsImage *image ) +{ + vips_image_init_fields( image, + ppm->width, ppm->height, ppm->bands, ppm->format, + VIPS_CODING_NONE, ppm->interpretation, 1.0, 1.0 ); + + vips_image_pipelinev( image, VIPS_DEMAND_STYLE_THINSTRIP, NULL ); + + if( ppm->index == 6 || + ppm->index == 7 ) + vips_image_set_double( image, + "pfm-scale", VIPS_FABS( ppm->scale ) ); + else + vips_image_set_double( image, + "ppm-max-value", VIPS_ABS( ppm->max_value ) ); + + VIPS_SETSTR( image->filename, + vips_connection_filename( VIPS_CONNECTION( ppm->sbuf->source ) ) ); } static int @@ -85,20 +399,231 @@ vips_foreign_load_ppm_header( VipsForeignLoad *load ) { VipsForeignLoadPpm *ppm = (VipsForeignLoadPpm *) load; - if( vips__ppm_header( ppm->filename, load->out ) ) + if( !ppm->have_read_header && + vips_foreign_load_ppm_parse_header( ppm ) ) + return( 0 ); + + vips_foreign_load_ppm_set_image( ppm, load->out ); + + vips_source_minimise( ppm->source ); + + return( 0 ); +} + +/* Read a ppm/pgm file using mmap(). + */ +static int +vips_foreign_load_ppm_map( VipsForeignLoadPpm *ppm, VipsImage *image ) +{ + VipsImage **t = (VipsImage **) + vips_object_local_array( VIPS_OBJECT( ppm ), 3 ); + + gint64 header_offset; + size_t length; + const void *data; + + vips_sbuf_unbuffer( ppm->sbuf ); + header_offset = vips_source_seek( ppm->source, 0, SEEK_CUR ); + data = vips_source_map( ppm->source, &length ); + if( header_offset < 0 || + !data ) + return( -1 ); + data += header_offset; + length -= header_offset; + + if( !(t[0] = vips_image_new_from_memory( data, length, + ppm->width, ppm->height, ppm->bands, ppm->format )) ) return( -1 ); - VIPS_SETSTR( load->out->filename, ppm->filename ); + if( vips__byteswap_bool( t[0], &t[1], + vips_amiMSBfirst() != ppm->msb_first ) || + vips_image_write( t[1], image ) ) + return( -1 ); return( 0 ); } +static int +vips_foreign_load_ppm_generate_binary( VipsRegion *or, + void *seq, void *a, void *b, gboolean *stop ) +{ + VipsRect *r = &or->valid; + VipsForeignLoadPpm *ppm = (VipsForeignLoadPpm *) a; + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( ppm ); + VipsImage *image = or->im; + size_t sizeof_line = VIPS_IMAGE_SIZEOF_LINE( image ); + + int y; + + for( y = 0; y < r->height; y++ ) { + VipsPel *q = VIPS_REGION_ADDR( or, 0, r->top + y ); + + size_t bytes_read; + + bytes_read = vips_source_read( ppm->source, q, sizeof_line ); + if( bytes_read != sizeof_line ) { + vips_error( class->nickname, + "%s", _( "file truncated" ) ); + return( -1 ); + } + } + + return( 0 ); +} + +static int +vips_foreign_load_ppm_generate_1bit_ascii( VipsRegion *or, + void *seq, void *a, void *b, gboolean *stop ) +{ + VipsRect *r = &or->valid; + VipsForeignLoadPpm *ppm = (VipsForeignLoadPpm *) a; + VipsImage *image = or->im; + + int x, y; + + for( y = 0; y < r->height; y++ ) { + VipsPel *q = VIPS_REGION_ADDR( or, 0, r->top + y ); + + for( x = 0; x < image->Xsize; x++ ) { + int val; + + if( get_int( ppm->sbuf, &val ) ) + return( -1 ); + + if( val ) + q[x] = 0; + else + q[x] = 255; + } + } + + return( 0 ); +} + +static int +vips_foreign_load_ppm_generate_1bit_binary( VipsRegion *or, + void *seq, void *a, void *b, gboolean *stop ) +{ + VipsRect *r = &or->valid; + VipsForeignLoadPpm *ppm = (VipsForeignLoadPpm *) a; + VipsImage *image = or->im; + + int x, y; + + for( y = 0; y < r->height; y++ ) { + VipsPel *q = VIPS_REGION_ADDR( or, 0, r->top + y ); + + int bits; + + /* Not needed, but stop a compiler warning. + */ + bits = 0; + + for( x = 0; x < image->Xsize; x++ ) { + if( (x & 7) == 0 ) + bits = VIPS_SBUF_GETC( ppm->sbuf ); + q[x] = (bits & 128) ? 0 : 255; + bits = VIPS_LSHIFT_INT( bits, 1 ); + } + } + + return( 0 ); +} + +static int +vips_foreign_load_ppm_generate_ascii_int( VipsRegion *or, + void *seq, void *a, void *b, gboolean *stop ) +{ + VipsRect *r = &or->valid; + VipsForeignLoadPpm *ppm = (VipsForeignLoadPpm *) a; + VipsImage *image = or->im; + int n_elements = image->Xsize * image->Bands; + + int i, y; + + for( y = 0; y < r->height; y++ ) { + VipsPel *q = VIPS_REGION_ADDR( or, r->left, r->top + y ); + + for( i = 0; i < n_elements; i++ ) { + int val; + + if( get_int( ppm->sbuf, &val ) ) + return( -1 ); + + switch( image->BandFmt ) { + case VIPS_FORMAT_UCHAR: + q[i] = VIPS_CLIP( 0, val, 255 ); + break; + + case VIPS_FORMAT_USHORT: + ((unsigned short *) q)[i] = + VIPS_CLIP( 0, val, 65535 ); + break; + + case VIPS_FORMAT_UINT: + ((unsigned int *) q)[i] = val; + break; + + default: + g_assert_not_reached(); + } + } + } + + return( 0 ); +} + +typedef int (*VipsPpmLoaderFn)( VipsForeignLoadPpm *ppm, VipsImage *image ); + static int vips_foreign_load_ppm_load( VipsForeignLoad *load ) { VipsForeignLoadPpm *ppm = (VipsForeignLoadPpm *) load; + VipsImage **t = (VipsImage **) + vips_object_local_array( (VipsObject *) load, 2 ); - if( vips__ppm_load( ppm->filename, load->real ) ) + if( !ppm->have_read_header && + vips_foreign_load_ppm_parse_header( ppm ) ) + return( 0 ); + + /* If the source is mappable and this is a binary file, we can map it. + */ + if( vips_source_is_mappable( ppm->source ) && + !ppm->ascii && + ppm->bits >= 8 ) { + if( vips_foreign_load_ppm_map( ppm, load->real ) ) + return( -1 ); + } + else { + VipsGenerateFn generate; + + /* What sort of read are we doing? + */ + if( !ppm->ascii && ppm->bits >= 8 ) { + generate = vips_foreign_load_ppm_generate_binary; + + /* The binary loader does not use the buffered IO + * object. + */ + vips_sbuf_unbuffer( ppm->sbuf ); + } + else if( !ppm->ascii && ppm->bits == 1 ) + generate = vips_foreign_load_ppm_generate_1bit_binary; + else if( ppm->ascii && ppm->bits == 1 ) + generate = vips_foreign_load_ppm_generate_1bit_ascii; + else + generate = vips_foreign_load_ppm_generate_ascii_int; + + t[0] = vips_image_new(); + vips_foreign_load_ppm_set_image( ppm, t[0] ); + if( vips_image_generate( t[0], + NULL, generate, NULL, ppm, NULL ) || + vips_sequential( t[0], &t[1], NULL ) || + vips_image_write( t[1], load->real ) ) + return( -1 ); + } + + if( vips_source_decode( ppm->source ) ) return( -1 ); return( 0 ); @@ -112,11 +637,13 @@ vips_foreign_load_ppm_class_init( VipsForeignLoadPpmClass *class ) VipsForeignClass *foreign_class = (VipsForeignClass *) class; VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class; + gobject_class->dispose = vips_foreign_load_ppm_dispose; gobject_class->set_property = vips_object_set_property; gobject_class->get_property = vips_object_get_property; - object_class->nickname = "ppmload"; - object_class->description = _( "load ppm from file" ); + object_class->nickname = "ppmload_base"; + object_class->description = _( "load ppm base class" ); + object_class->build = vips_foreign_load_ppm_build; foreign_class->suffs = vips__ppm_suffs; @@ -124,23 +651,146 @@ vips_foreign_load_ppm_class_init( VipsForeignLoadPpmClass *class ) */ foreign_class->priority = 200; - load_class->is_a = vips__ppm_isppm; - load_class->get_flags_filename = - vips_foreign_load_ppm_get_flags_filename; load_class->get_flags = vips_foreign_load_ppm_get_flags; load_class->header = vips_foreign_load_ppm_header; load_class->load = vips_foreign_load_ppm_load; +} + +static void +vips_foreign_load_ppm_init( VipsForeignLoadPpm *ppm ) +{ + ppm->scale = 1.0; +} + +typedef struct _VipsForeignLoadPpmFile { + VipsForeignLoadPpm parent_object; + + char *filename; + +} VipsForeignLoadPpmFile; + +typedef VipsForeignLoadPpmClass VipsForeignLoadPpmFileClass; + +G_DEFINE_TYPE( VipsForeignLoadPpmFile, vips_foreign_load_ppm_file, + vips_foreign_load_ppm_get_type() ); + +static gboolean +vips_foreign_load_ppm_file_is_a( const char *filename ) +{ + VipsSource *source; + gboolean result; + + if( !(source = vips_source_new_from_file( filename )) ) + return( FALSE ); + result = vips_foreign_load_ppm_is_a_source( source ); + VIPS_UNREF( source ); + + return( result ); +} + +static int +vips_foreign_load_ppm_file_build( VipsObject *object ) +{ + VipsForeignLoadPpmFile *file = (VipsForeignLoadPpmFile *) object; + VipsForeignLoadPpm *ppm = (VipsForeignLoadPpm *) object; + + if( file->filename ) { + if( !(ppm->source = + vips_source_new_from_file( file->filename )) ) + return( -1 ); + ppm->sbuf = vips_sbuf_new_from_source( ppm->source ); + } + + if( VIPS_OBJECT_CLASS( vips_foreign_load_ppm_file_parent_class )-> + build( object ) ) + return( -1 ); + + return( 0 ); +} + +static void +vips_foreign_load_ppm_file_class_init( VipsForeignLoadPpmClass *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 = "ppmload"; + object_class->description = _( "load ppm from file" ); + object_class->build = vips_foreign_load_ppm_file_build; + + load_class->is_a = vips_foreign_load_ppm_file_is_a; + VIPS_ARG_STRING( class, "filename", 1, _( "Filename" ), _( "Filename to load from" ), VIPS_ARGUMENT_REQUIRED_INPUT, - G_STRUCT_OFFSET( VipsForeignLoadPpm, filename ), + G_STRUCT_OFFSET( VipsForeignLoadPpmFile, filename ), NULL ); } static void -vips_foreign_load_ppm_init( VipsForeignLoadPpm *ppm ) +vips_foreign_load_ppm_file_init( VipsForeignLoadPpmFile *file ) +{ +} + +typedef struct _VipsForeignLoadPpmSource { + VipsForeignLoadPpm parent_object; + + VipsSource *source; + +} VipsForeignLoadPpmSource; + +typedef VipsForeignLoadPpmClass VipsForeignLoadPpmSourceClass; + +G_DEFINE_TYPE( VipsForeignLoadPpmSource, vips_foreign_load_ppm_source, + vips_foreign_load_ppm_get_type() ); + +static int +vips_foreign_load_ppm_source_build( VipsObject *object ) +{ + VipsForeignLoadPpm *ppm = (VipsForeignLoadPpm *) object; + VipsForeignLoadPpmSource *source = (VipsForeignLoadPpmSource *) object; + + if( source->source ) { + ppm->source = source->source; + g_object_ref( ppm->source ); + } + + if( VIPS_OBJECT_CLASS( vips_foreign_load_ppm_source_parent_class )-> + build( object ) ) + return( -1 ); + + return( 0 ); +} + +static void +vips_foreign_load_ppm_source_class_init( VipsForeignLoadPpmFileClass *class ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( class ); + VipsObjectClass *object_class = (VipsObjectClass *) class; + + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + object_class->nickname = "ppmload_source"; + object_class->build = vips_foreign_load_ppm_source_build; + + VIPS_ARG_OBJECT( class, "source", 1, + _( "Source" ), + _( "Source to load from" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadPpmSource, source ), + VIPS_TYPE_SOURCE ); + +} + +static void +vips_foreign_load_ppm_source_init( VipsForeignLoadPpmSource *source ) { } @@ -175,3 +825,35 @@ vips_ppmload( const char *filename, VipsImage **out, ... ) return( result ); } +/** + * vips_ppmload_source: + * @source: source to load + * @out: (out): output image + * @...: %NULL-terminated list of optional named arguments + * + * Optional arguments: + * + * * @skip: skip this many lines at start of file + * * @lines: read this many lines from file + * * @whitespace: set of whitespace characters + * * @separator: set of separator characters + * * @fail: %gboolean, fail on errors + * + * Exactly as vips_ppmload(), but read from a source. + * + * See also: vips_ppmload(). + * + * Returns: 0 on success, -1 on error. + */ +int +vips_ppmload_source( VipsSource *source, VipsImage **out, ... ) +{ + va_list ap; + int result; + + va_start( ap, out ); + result = vips_call_split( "ppmload_source", ap, source, out ); + va_end( ap ); + + return( result ); +} diff --git a/libvips/foreign/ppmsave.c b/libvips/foreign/ppmsave.c index 33f0cc4b..cbfaf787 100644 --- a/libvips/foreign/ppmsave.c +++ b/libvips/foreign/ppmsave.c @@ -2,6 +2,12 @@ * * 2/12/11 * - wrap a class around the ppm writer + * 13/11/19 + * - redone with targets + * 18/6/20 + * - add "bitdepth" param, cf. tiffsave + * 27/6/20 + * - add ppmsave_target */ /* @@ -46,36 +52,301 @@ #include #include +#include #include "pforeign.h" #ifdef HAVE_PPM +typedef struct _VipsForeignSavePpm VipsForeignSavePpm; + +typedef int (*VipsSavePpmFn)( VipsForeignSavePpm *, VipsImage *, VipsPel * ); + typedef struct _VipsForeignSavePpm { VipsForeignSave parent_object; - char *filename; + VipsTarget *target; gboolean ascii; + int bitdepth; + + VipsSavePpmFn fn; + + /* Deprecated. + */ gboolean squash; } VipsForeignSavePpm; typedef VipsForeignSaveClass VipsForeignSavePpmClass; -G_DEFINE_TYPE( VipsForeignSavePpm, vips_foreign_save_ppm, +G_DEFINE_ABSTRACT_TYPE( VipsForeignSavePpm, vips_foreign_save_ppm, VIPS_TYPE_FOREIGN_SAVE ); +static void +vips_foreign_save_ppm_dispose( GObject *gobject ) +{ + VipsForeignSavePpm *ppm = (VipsForeignSavePpm *) gobject; + + if( ppm->target ) + vips_target_finish( ppm->target ); + VIPS_UNREF( ppm->target ); + + G_OBJECT_CLASS( vips_foreign_save_ppm_parent_class )-> + dispose( gobject ); +} + +static int +vips_foreign_save_ppm_line_ascii( VipsForeignSavePpm *ppm, + VipsImage *image, VipsPel *p ) +{ + const int n_elements = image->Xsize * image->Bands; + + int i; + + for( i = 0; i < n_elements; i++ ) { + switch( image->BandFmt ) { + case VIPS_FORMAT_UCHAR: + vips_target_writef( ppm->target, + "%d ", p[i] ); + break; + + case VIPS_FORMAT_USHORT: + vips_target_writef( ppm->target, + "%d ", ((unsigned short *) p)[i] ); + break; + + case VIPS_FORMAT_UINT: + vips_target_writef( ppm->target, + "%d ", ((unsigned int *) p)[i] ); + break; + + default: + g_assert_not_reached(); + } + } + + if( vips_target_writes( ppm->target, "\n" ) ) + return( -1 ); + + return( 0 ); +} + +static int +vips_foreign_save_ppm_line_ascii_1bit( VipsForeignSavePpm *ppm, + VipsImage *image, VipsPel *p ) +{ + int x; + + for( x = 0; x < image->Xsize; x++ ) + vips_target_writef( ppm->target, "%d ", p[x] ? 0 : 1 ); + + if( vips_target_writes( ppm->target, "\n" ) ) + return( -1 ); + + return( 0 ); +} + +static int +vips_foreign_save_ppm_line_binary( VipsForeignSavePpm *ppm, + VipsImage *image, VipsPel *p ) +{ + if( vips_target_write( ppm->target, + p, VIPS_IMAGE_SIZEOF_LINE( image ) ) ) + return( -1 ); + + return( 0 ); +} + +static int +vips_foreign_save_ppm_line_binary_1bit( VipsForeignSavePpm *ppm, + VipsImage *image, VipsPel *p ) +{ + int x; + int bits; + int n_bits; + + bits = 0; + n_bits = 0; + for( x = 0; x < image->Xsize; x++ ) { + bits = VIPS_LSHIFT_INT( bits, 1 ); + n_bits += 1; + bits |= p[x] > 128 ? 0 : 1; + + if( n_bits == 8 ) { + if( VIPS_TARGET_PUTC( ppm->target, bits ) ) + return( -1 ); + + bits = 0; + n_bits = 0; + } + } + + /* Flush any remaining bits in this line. + */ + if( n_bits && + VIPS_TARGET_PUTC( ppm->target, bits ) ) + return( -1 ); + + return( 0 ); +} + +static int +vips_foreign_save_ppm_block( VipsRegion *region, VipsRect *area, void *a ) +{ + VipsForeignSavePpm *ppm = (VipsForeignSavePpm *) a; + VipsImage *image = region->im; + + int y; + + for( y = 0; y < area->height; y++ ) { + VipsPel *p = VIPS_REGION_ADDR( region, 0, area->top + y ); + + if( ppm->fn( ppm, image, p ) ) + return( -1 ); + } + + return( 0 ); +} + +static int +vips_foreign_save_ppm( VipsForeignSavePpm *ppm, VipsImage *image ) +{ + char *magic; + char *date; + + magic = "unset"; + if( image->BandFmt == VIPS_FORMAT_FLOAT && + image->Bands == 3 ) + magic = "PF"; + else if( image->BandFmt == VIPS_FORMAT_FLOAT && + image->Bands == 1 ) + magic = "Pf"; + else if( image->Bands == 1 && + ppm->ascii && + ppm->bitdepth ) + magic = "P1"; + else if( image->Bands == 1 && + ppm->ascii ) + magic = "P2"; + else if( image->Bands == 1 && + !ppm->ascii && + ppm->bitdepth ) + magic = "P4"; + else if( image->Bands == 1 && + !ppm->ascii ) + magic = "P5"; + else if( image->Bands == 3 && + ppm->ascii ) + magic = "P3"; + else if( image->Bands == 3 && + !ppm->ascii ) + magic = "P6"; + else + g_assert_not_reached(); + + vips_target_writef( ppm->target, "%s\n", magic ); + date = vips__get_iso8601(); + vips_target_writef( ppm->target, + "#vips2ppm - %s\n", date ); + g_free( date ); + vips_target_writef( ppm->target, + "%d %d\n", image->Xsize, image->Ysize ); + + if( !ppm->bitdepth ) + switch( image->BandFmt ) { + case VIPS_FORMAT_UCHAR: + vips_target_writef( ppm->target, + "%d\n", UCHAR_MAX ); + break; + + case VIPS_FORMAT_USHORT: + vips_target_writef( ppm->target, + "%d\n", USHRT_MAX ); + break; + + case VIPS_FORMAT_UINT: + vips_target_writef( ppm->target, + "%d\n", UINT_MAX ); + break; + + case VIPS_FORMAT_FLOAT: +{ + double scale; + char buf[G_ASCII_DTOSTR_BUF_SIZE]; + + if( vips_image_get_double( image, + "pfm-scale", &scale ) ) + scale = 1; + if( !vips_amiMSBfirst() ) + scale *= -1; + /* Need to be locale independent. + */ + g_ascii_dtostr( buf, G_ASCII_DTOSTR_BUF_SIZE, scale ); + vips_target_writes( ppm->target, buf ); +} + break; + + default: + g_assert_not_reached(); + } + + if( ppm->bitdepth ) + ppm->fn = ppm->ascii ? + vips_foreign_save_ppm_line_ascii_1bit : + vips_foreign_save_ppm_line_binary_1bit; + else + ppm->fn = ppm->ascii ? + vips_foreign_save_ppm_line_ascii : + vips_foreign_save_ppm_line_binary; + + if( vips_sink_disc( image, vips_foreign_save_ppm_block, ppm ) ) + return( -1 ); + + return( 0 ); +} + static int vips_foreign_save_ppm_build( VipsObject *object ) { VipsForeignSave *save = (VipsForeignSave *) object; VipsForeignSavePpm *ppm = (VipsForeignSavePpm *) object; + VipsImage *image; + if( VIPS_OBJECT_CLASS( vips_foreign_save_ppm_parent_class )-> build( object ) ) return( -1 ); - if( vips__ppm_save( save->ready, ppm->filename, - ppm->ascii, ppm->squash ) ) + /* Handle the deprecated squash parameter. + */ + if( vips_object_argument_isset( object, "squash" ) ) + ppm->bitdepth = 1; + + image = save->ready; + if( vips_check_uintorf( "vips2ppm", image ) || + vips_check_bands_1or3( "vips2ppm", image ) || + vips_check_uncoded( "vips2ppm", image ) || + vips_image_pio_input( image ) ) + return( -1 ); + + if( ppm->ascii && + image->BandFmt == VIPS_FORMAT_FLOAT ) { + g_warning( "%s", + _( "float images must be binary -- disabling ascii" ) ); + ppm->ascii = FALSE; + } + + /* One bit images must come from a 8 bit, one band source. + */ + if( ppm->bitdepth && + (image->Bands != 1 || + image->BandFmt != VIPS_FORMAT_UCHAR) ) { + g_warning( "%s", + _( "can only save 1 band uchar images as 1 bit -- " + "disabling 1 bit save" ) ); + ppm->bitdepth = 0; + } + + if( vips_foreign_save_ppm( ppm, image ) ) return( -1 ); return( 0 ); @@ -107,11 +378,12 @@ vips_foreign_save_ppm_class_init( VipsForeignSavePpmClass *class ) VipsForeignClass *foreign_class = (VipsForeignClass *) class; VipsForeignSaveClass *save_class = (VipsForeignSaveClass *) class; + gobject_class->dispose = vips_foreign_save_ppm_dispose; gobject_class->set_property = vips_object_set_property; gobject_class->get_property = vips_object_get_property; - object_class->nickname = "ppmsave"; - object_class->description = _( "save image to ppm file" ); + object_class->nickname = "ppmsave_base"; + object_class->description = _( "save to ppm" ); object_class->build = vips_foreign_save_ppm_build; foreign_class->suffs = vips__ppm_suffs; @@ -119,13 +391,6 @@ vips_foreign_save_ppm_class_init( VipsForeignSavePpmClass *class ) save_class->saveable = VIPS_SAVEABLE_RGB; save_class->format_table = bandfmt_ppm; - VIPS_ARG_STRING( class, "filename", 1, - _( "Filename" ), - _( "Filename to save to" ), - VIPS_ARGUMENT_REQUIRED_INPUT, - G_STRUCT_OFFSET( VipsForeignSavePpm, filename ), - NULL ); - VIPS_ARG_BOOL( class, "ascii", 10, _( "ASCII" ), _( "save as ascii" ), @@ -133,12 +398,20 @@ vips_foreign_save_ppm_class_init( VipsForeignSavePpmClass *class ) G_STRUCT_OFFSET( VipsForeignSavePpm, ascii ), FALSE ); + VIPS_ARG_INT( class, "bitdepth", 15, + _( "bitdepth" ), + _( "Write as a 1 bit image" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsForeignSavePpm, bitdepth ), + 0, 1, 0 ); + VIPS_ARG_BOOL( class, "squash", 11, _( "Squash" ), _( "save as one bit" ), - VIPS_ARGUMENT_OPTIONAL_INPUT, + VIPS_ARGUMENT_OPTIONAL_INPUT | VIPS_ARGUMENT_DEPRECATED, G_STRUCT_OFFSET( VipsForeignSavePpm, squash ), FALSE ); + } static void @@ -146,6 +419,113 @@ vips_foreign_save_ppm_init( VipsForeignSavePpm *ppm ) { } +typedef struct _VipsForeignSavePpmFile { + VipsForeignSavePpm parent_object; + + char *filename; +} VipsForeignSavePpmFile; + +typedef VipsForeignSavePpmClass VipsForeignSavePpmFileClass; + +G_DEFINE_TYPE( VipsForeignSavePpmFile, vips_foreign_save_ppm_file, + vips_foreign_save_ppm_get_type() ); + +static int +vips_foreign_save_ppm_file_build( VipsObject *object ) +{ + VipsForeignSavePpm *ppm = (VipsForeignSavePpm *) object; + VipsForeignSavePpmFile *file = (VipsForeignSavePpmFile *) object; + + if( file->filename && + !(ppm->target = vips_target_new_to_file( file->filename )) ) + return( -1 ); + + return( VIPS_OBJECT_CLASS( vips_foreign_save_ppm_file_parent_class )-> + build( object ) ); +} + +static void +vips_foreign_save_ppm_file_class_init( VipsForeignSavePpmFileClass *class ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( class ); + VipsObjectClass *object_class = (VipsObjectClass *) class; + + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + object_class->nickname = "ppmsave"; + object_class->description = _( "save image to ppm file" ); + object_class->build = vips_foreign_save_ppm_file_build; + + VIPS_ARG_STRING( class, "filename", 1, + _( "Filename" ), + _( "Filename to save to" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsForeignSavePpmFile, filename ), + NULL ); + +} + +static void +vips_foreign_save_ppm_file_init( VipsForeignSavePpmFile *file ) +{ +} + +typedef struct _VipsForeignSavePpmTarget { + VipsForeignSavePpm parent_object; + + VipsTarget *target; +} VipsForeignSavePpmTarget; + +typedef VipsForeignSavePpmClass VipsForeignSavePpmTargetClass; + +G_DEFINE_TYPE( VipsForeignSavePpmTarget, vips_foreign_save_ppm_target, + vips_foreign_save_ppm_get_type() ); + +static int +vips_foreign_save_ppm_target_build( VipsObject *object ) +{ + VipsForeignSavePpm *ppm = (VipsForeignSavePpm *) object; + VipsForeignSavePpmTarget *target = + (VipsForeignSavePpmTarget *) object; + + if( target->target ) { + ppm->target = target->target; + g_object_ref( ppm->target ); + } + + return( VIPS_OBJECT_CLASS( + vips_foreign_save_ppm_target_parent_class )-> + build( object ) ); +} + +static void +vips_foreign_save_ppm_target_class_init( + VipsForeignSavePpmTargetClass *class ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( class ); + VipsObjectClass *object_class = (VipsObjectClass *) class; + + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + object_class->nickname = "ppmsave_target"; + object_class->build = vips_foreign_save_ppm_target_build; + + VIPS_ARG_OBJECT( class, "target", 1, + _( "Target" ), + _( "Target to save to" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsForeignSavePpmTarget, target ), + VIPS_TYPE_TARGET ); + +} + +static void +vips_foreign_save_ppm_target_init( VipsForeignSavePpmTarget *target ) +{ +} + #endif /*HAVE_PPM*/ /** @@ -189,3 +569,28 @@ vips_ppmsave( VipsImage *in, const char *filename, ... ) return( result ); } + +/** + * vips_ppmsave_target: (method) + * @in: image to save + * @target: save image to this target + * @...: %NULL-terminated list of optional named arguments + * + * As vips_ppmsave(), but save to a target. + * + * See also: vips_ppmsave(). + * + * Returns: 0 on success, -1 on error. + */ +int +vips_ppmsave_target( VipsImage *in, VipsTarget *target, ... ) +{ + va_list ap; + int result; + + va_start( ap, target ); + result = vips_call_split( "ppmsave_target", ap, in, target ); + va_end( ap ); + + return( result ); +} diff --git a/libvips/foreign/radiance.c b/libvips/foreign/radiance.c index e8f0dc2b..d5ba070b 100644 --- a/libvips/foreign/radiance.c +++ b/libvips/foreign/radiance.c @@ -27,6 +27,8 @@ * 23/7/18 * - fix a buffer overflow for incorrectly coded old-style RLE * [HongxuChen] + * 6/11/19 + * - revise for VipsConnection */ /* @@ -475,9 +477,6 @@ char *buf; static const char FMTSTR[] = "FORMAT="; /* format identifier */ -static gethfunc mycheck; - - static int formatval( /* get format value (return true if format) */ char fmt[MAXFMTLEN], @@ -499,260 +498,48 @@ formatval( /* get format value (return true if format) */ } -static void -fputformat( /* put out a format value */ - const char *s, - FILE *fp -) -{ - fputs(FMTSTR, fp); - fputs(s, fp); - putc('\n', fp); -} - - static int getheader( /* get header from file */ - FILE *fp, + VipsSbuf *sbuf, gethfunc *f, void *p ) { - int rtotal = 0; - char buf[MAXLINE]; + for(;;) { + const char *line; - for ( ; ; ) { - int rval = 0; - buf[MAXLINE-2] = '\n'; - if (fgets(buf, MAXLINE, fp) == NULL) - return(-1); - if (buf[buf[0]=='\r'] == '\n') - return(rtotal); - if (buf[MAXLINE-2] != '\n') { - ungetc(buf[MAXLINE-2], fp); /* prevent false end */ - buf[MAXLINE-2] = '\0'; - } - if (f != NULL && (rval = (*f)(buf, p)) < 0) - return(-1); - rtotal += rval; - } -} - - -struct check { - FILE *fp; - char fs[MAXFMTLEN]; -}; - - -static int -mycheck( /* check a header line for format info. */ - char *s, - void *cp -) -{ - struct check *p = (struct check *) cp; - - if (!formatval(p->fs, s) && p->fp != NULL) { - fputs(s, p->fp); - } - return(0); -} - - -static int -globmatch( /* check for match of s against pattern p */ - const char *p, - const char *s -) -{ - int setmatch; - - do { - switch (*p) { - case '?': /* match any character */ - if (!*s++) - return(0); - break; - case '*': /* match any string */ - while (p[1] == '*') p++; - do - if ( (p[1]=='?') | (p[1]==*s) && - globmatch(p+1,s) ) - return(1); - while (*s++); - return(0); - case '[': /* character set */ - setmatch = *s == *++p; - if (!*p) - return(0); - while (*++p != ']') { - if (!*p) - return(0); - if (*p == '-') { - setmatch += (p[-1] <= *s && *s <= p[1]); - if (!*++p) - break; - } else - setmatch += (*p == *s); - } - if (!setmatch) - return(0); - s++; - break; - case '\\': /* literal next */ - p++; - /* fall through */ - default: /* normal character */ - if (*p != *s) - return(0); - s++; - break; - } - } while (*p++); - return(1); -} - - -/* - * Checkheader(fin,fmt,fout) returns a value of 1 if the input format - * matches the specification in fmt, 0 if no input format was found, - * and -1 if the input format does not match or there is an - * error reading the header. If fmt is empty, then -1 is returned - * if any input format is found (or there is an error), and 0 otherwise. - * If fmt contains any '*' or '?' characters, then checkheader - * does wildcard expansion and copies a matching result into fmt. - * Be sure that fmt is big enough to hold the match in such cases, - * and that it is not a static, read-only string! - * The input header (minus any format lines) is copied to fout - * if fout is not NULL. - */ - -static int -checkheader( - FILE *fin, - char fmt[MAXFMTLEN], - FILE *fout -) -{ - struct check cdat; - char *cp; - - cdat.fp = fout; - cdat.fs[0] = '\0'; - if (getheader(fin, mycheck, &cdat) < 0) - return(-1); - if (!cdat.fs[0]) - return(0); - for (cp = fmt; *cp; cp++) /* check for globbing */ - if ((*cp == '?') | (*cp == '*')) { - if (globmatch(fmt, cdat.fs)) { - strcpy(fmt, cdat.fs); - return(1); - } else - return(-1); - } - return(strcmp(fmt, cdat.fs) ? -1 : 1); /* literal match */ -} - -/* End copy-paste from Radiance sources. - */ - -#define BUFFER_SIZE (4096) -#define BUFFER_MARGIN (256) - -/* Read from a FILE with a rolling memory buffer ... this lets us reduce the - * number of fgetc() and gives us some very quick readahead. - */ - -typedef struct _Buffer { - unsigned char text[BUFFER_SIZE + BUFFER_MARGIN]; - int length; - int position; - FILE *fp; -} Buffer; - -static Buffer * -buffer_new( FILE *fp ) -{ - Buffer *buffer = g_new0( Buffer, 1 ); - - buffer->length = 0; - buffer->position = 0; - buffer->fp = fp; - - return( buffer ); -} - -static void -buffer_free( Buffer *buffer ) -{ - g_free( buffer ); -} - -/* Make sure there are at least @require bytes of readahead available. - */ -static int -buffer_need( Buffer *buffer, int require ) -{ - int remaining; - - g_assert( require < BUFFER_MARGIN ); - g_assert( buffer->length >= 0 ); - g_assert( buffer->position >= 0 ); - g_assert( buffer->position <= buffer->length ); - - remaining = buffer->length - buffer->position; - if( remaining < require ) { - size_t len; - - /* Areas can overlap. - */ - memmove( buffer->text, - buffer->text + buffer->position, remaining ); - buffer->position = 0; - buffer->length = remaining; - - g_assert( buffer->length < BUFFER_MARGIN ); - - len = fread( buffer->text + buffer->length, - 1, BUFFER_SIZE, buffer->fp ); - buffer->length += len; - remaining = buffer->length - buffer->position; - - if( remaining < require ) { - vips_error( "rad2vips", "%s", _( "end of file" ) ); + if( !(line = vips_sbuf_get_line( sbuf )) ) + return( -1 ); + if( strcmp( line, "" ) == 0 ) + /* Blank line. We've parsed the header successfully. + */ + break; + + if( f != NULL && + (*f)( (char *) line, p ) < 0 ) return( -1 ); - } } return( 0 ); } -#define BUFFER_FETCH(B) ((B)->text[(B)->position++]) -#define BUFFER_PEEK(B) ((B)->text[(B)->position]) - -/* Read a single scanlne, encoded in the old style. +/* Read a single scanline, encoded in the old style. */ static int -scanline_read_old( Buffer *buffer, COLR *scanline, int width ) +scanline_read_old( VipsSbuf *sbuf, COLR *scanline, int width ) { int rshift; - g_assert( buffer->length >= 0 ); - g_assert( buffer->position >= 0 ); - g_assert( buffer->position <= buffer->length ); - rshift = 0; while( width > 0 ) { - if( buffer_need( buffer, 4 ) ) + if( VIPS_SBUF_REQUIRE( sbuf, 4 ) ) return( -1 ); - scanline[0][RED] = BUFFER_FETCH( buffer ); - scanline[0][GRN] = BUFFER_FETCH( buffer ); - scanline[0][BLU] = BUFFER_FETCH( buffer ); - scanline[0][EXP] = BUFFER_FETCH( buffer ); + scanline[0][RED] = VIPS_SBUF_FETCH( sbuf ); + scanline[0][GRN] = VIPS_SBUF_FETCH( sbuf ); + scanline[0][BLU] = VIPS_SBUF_FETCH( sbuf ); + scanline[0][EXP] = VIPS_SBUF_FETCH( sbuf ); if( scanline[0][RED] == 1 && scanline[0][GRN] == 1 && @@ -781,33 +568,30 @@ scanline_read_old( Buffer *buffer, COLR *scanline, int width ) /* Read a single encoded scanline. */ static int -scanline_read( Buffer *buffer, COLR *scanline, int width ) +scanline_read( VipsSbuf *sbuf, COLR *scanline, int width ) { int i, j; - g_assert( buffer->length >= 0 ); - g_assert( buffer->position >= 0 ); - g_assert( buffer->position <= buffer->length ); - /* Detect old-style scanlines. */ if( width < MINELEN || width > MAXELEN ) - return( scanline_read_old( buffer, scanline, width ) ); + return( scanline_read_old( sbuf, scanline, width ) ); - if( buffer_need( buffer, 4 ) ) + if( VIPS_SBUF_REQUIRE( sbuf, 4 ) ) return( -1 ); - if( BUFFER_PEEK( buffer ) != 2 ) - return( scanline_read_old( buffer, scanline, width ) ); + if( VIPS_SBUF_PEEK( sbuf )[0] != 2 ) + return( scanline_read_old( sbuf, scanline, width ) ); - scanline[0][RED] = BUFFER_FETCH( buffer ); - scanline[0][GRN] = BUFFER_FETCH( buffer ); - scanline[0][BLU] = BUFFER_FETCH( buffer ); - scanline[0][EXP] = BUFFER_FETCH( buffer ); + scanline[0][RED] = VIPS_SBUF_FETCH( sbuf ); + scanline[0][GRN] = VIPS_SBUF_FETCH( sbuf ); + scanline[0][BLU] = VIPS_SBUF_FETCH( sbuf ); + scanline[0][EXP] = VIPS_SBUF_FETCH( sbuf ); if( scanline[0][GRN] != 2 || scanline[0][BLU] & 128 ) - return( scanline_read_old( buffer, scanline + 1, width - 1 ) ); + return( scanline_read_old( sbuf, + scanline + 1, width - 1 ) ); if( ((scanline[0][BLU] << 8) | scanline[0][EXP]) != width ) { vips_error( "rad2vips", "%s", _( "scanline length mismatch" ) ); @@ -819,10 +603,10 @@ scanline_read( Buffer *buffer, COLR *scanline, int width ) int code, len; gboolean run; - if( buffer_need( buffer, 2 ) ) + if( VIPS_SBUF_REQUIRE( sbuf, 2 ) ) return( -1 ); - code = BUFFER_FETCH( buffer ); + code = VIPS_SBUF_FETCH( sbuf ); run = code > 128; len = run ? code & 127 : code; @@ -834,16 +618,16 @@ scanline_read( Buffer *buffer, COLR *scanline, int width ) if( run ) { int val; - val = BUFFER_FETCH( buffer ); + val = VIPS_SBUF_FETCH( sbuf ); while( len-- ) scanline[j++][i] = val; } else { - if( buffer_need( buffer, len ) ) + if( VIPS_SBUF_REQUIRE( sbuf, len ) ) return( -1 ); while( len-- ) scanline[j++][i] = - BUFFER_FETCH( buffer ); + VIPS_SBUF_FETCH( sbuf ); } } @@ -923,92 +707,64 @@ rle_scanline_write( COLR *scanline, int width, } } -/* Write a single scanline. buffer is at least MAX_LINE bytes and is used to - * construct the RLE scanline. Don't allocate this on the stack so we don't - * die too horribly on small-stack libc. - */ -static int -scanline_write( unsigned char *buffer, COLR *scanline, int width, FILE *fp ) -{ - if( width < MINELEN || - width > MAXELEN ) - /* Write as a flat scanline. - */ - return( fwrite( scanline, sizeof( COLR ), width, fp ) - width ); - else { - /* An RLE scanline. - */ - int length; - - rle_scanline_write( scanline, width, buffer, &length ); - - g_assert( length <= MAX_LINE ); - - return( fwrite( buffer, 1, length, fp ) - length ); - } -} - /* What we track during radiance file read. */ typedef struct { - char *filename; + VipsSbuf *sbuf; VipsImage *out; - FILE *fin; char format[256]; double expos; COLOR colcor; double aspect; RGBPRIMS prims; RESOLU rs; - Buffer *buffer; } Read; int -vips__rad_israd( const char *filename ) +vips__rad_israd( VipsSource *source ) { - FILE *fin; - char format[256]; + VipsSbuf *sbuf; + const char *line; int result; -#ifdef DEBUG - printf( "israd: \"%s\"\n", filename ); -#endif /*DEBUG*/ + /* Just test that the first line is the magic string. + */ + sbuf = vips_sbuf_new_from_source( source ); + result = (line = vips_sbuf_get_line( sbuf )) && + strcmp( line, "#?RADIANCE" ) == 0; + VIPS_UNREF( sbuf ); - if( !(fin = vips__file_open_read( filename, NULL, FALSE )) ) - return( 0 ); - strcpy( format, PICFMT ); - result = checkheader( fin, format, NULL ); - fclose( fin ); - - return( result == 1 ); -} - -static void -read_minimise( VipsObject *object, Read *read ) -{ - VIPS_FREEF( buffer_free, read->buffer ); - VIPS_FREEF( fclose, read->fin ); + return( result ); } static void read_destroy( VipsObject *object, Read *read ) { - VIPS_FREE( read->filename ); + VIPS_UNREF( read->sbuf ); +} + +static void +read_minimise_cb( VipsObject *object, Read *read ) +{ + if( read->sbuf ) + vips_source_minimise( read->sbuf->source ); } static Read * -read_new( const char *filename, VipsImage *out ) +read_new( VipsSource *source, VipsImage *out ) { Read *read; int i; + if( vips_source_rewind( source ) ) + return( NULL ); + if( !(read = VIPS_NEW( out, Read )) ) return( NULL ); - read->filename = vips_strdup( NULL, filename ); + read->sbuf = vips_sbuf_new_from_source( source ); read->out = out; - read->fin = NULL; strcpy( read->format, COLRFMT ); read->expos = 1.0; for( i = 0; i < 3; i++ ) @@ -1022,16 +778,11 @@ read_new( const char *filename, VipsImage *out ) read->prims[2][1] = CIE_y_b; read->prims[3][0] = CIE_x_w; read->prims[3][1] = CIE_y_w; - read->buffer = NULL; - g_signal_connect( out, "minimise", - G_CALLBACK( read_minimise ), read ); g_signal_connect( out, "close", G_CALLBACK( read_destroy ), read ); - - if( !(read->fin = vips__file_open_read( filename, NULL, FALSE )) || - !(read->buffer = buffer_new( read->fin )) ) - return( NULL ); + g_signal_connect( out, "minimise", + G_CALLBACK( read_minimise_cb ), read ); return( read ); } @@ -1081,12 +832,15 @@ static int rad2vips_get_header( Read *read, VipsImage *out ) { VipsInterpretation interpretation; + const char *line; int width; int height; int i, j; - if( getheader( read->fin, (gethfunc *) rad2vips_process_line, read ) || - !fgetsresolu( &read->rs, read->fin ) ) { + if( getheader( read->sbuf, + (gethfunc *) rad2vips_process_line, read ) || + !(line = vips_sbuf_get_line( read->sbuf )) || + !str2resolu( &read->rs, (char *) line ) ) { vips_error( "rad2vips", "%s", _( "error reading radiance header" ) ); return( -1 ); @@ -1114,6 +868,10 @@ rad2vips_get_header( Read *read, VipsImage *out ) interpretation, 1, read->aspect ); + VIPS_SETSTR( out->filename, + vips_connection_filename( + VIPS_CONNECTION( read->sbuf->source ) ) ); + vips_image_pipelinev( out, VIPS_DEMAND_STYLE_THINSTRIP, NULL ); vips_image_set_string( out, "rad-format", read->format ); @@ -1135,18 +893,15 @@ rad2vips_get_header( Read *read, VipsImage *out ) } int -vips__rad_header( const char *filename, VipsImage *out ) +vips__rad_header( VipsSource *source, VipsImage *out ) { Read *read; -#ifdef DEBUG - printf( "rad2vips_header: reading \"%s\"\n", filename ); -#endif /*DEBUG*/ - - if( !(read = read_new( filename, out )) ) + if( !(read = read_new( source, out )) ) return( -1 ); if( rad2vips_get_header( read, read->out ) ) return( -1 ); + vips_source_minimise( source ); return( 0 ); } @@ -1171,7 +926,7 @@ rad2vips_generate( VipsRegion *or, COLR *buf = (COLR *) VIPS_REGION_ADDR( or, 0, r->top + y ); - if( scanline_read( read->buffer, buf, or->im->Xsize ) ) { + if( scanline_read( read->sbuf, buf, or->im->Xsize ) ) { vips_error( "rad2vips", _( "read error line %d" ), r->top + y ); VIPS_GATE_STOP( "rad2vips_generate: work" ); @@ -1185,7 +940,7 @@ rad2vips_generate( VipsRegion *or, } int -vips__rad_load( const char *filename, VipsImage *out ) +vips__rad_load( VipsSource *source, VipsImage *out ) { VipsImage **t = (VipsImage **) vips_object_local_array( VIPS_OBJECT( out ), 3 ); @@ -1193,10 +948,11 @@ vips__rad_load( const char *filename, VipsImage *out ) Read *read; #ifdef DEBUG - printf( "rad2vips: reading \"%s\"\n", filename ); + printf( "rad2vips: reading \"%s\"\n", + vips_connection_nick( VIPS_CONNECTION( source ) ) ); #endif /*DEBUG*/ - if( !(read = read_new( filename, out )) ) + if( !(read = read_new( source, out )) ) return( -1 ); t[0] = vips_image_new(); @@ -1211,6 +967,9 @@ vips__rad_load( const char *filename, VipsImage *out ) vips_image_write( t[1], out ) ) return( -1 ); + if( vips_source_decode( source ) ) + return( -1 ); + return( 0 ); } @@ -1218,11 +977,7 @@ vips__rad_load( const char *filename, VipsImage *out ) */ typedef struct { VipsImage *in; - - char *filename; - FILE *fout; - - VipsDbuf dbuf; + VipsTarget *target; char format[256]; double expos; @@ -1230,20 +985,20 @@ typedef struct { double aspect; RGBPRIMS prims; RESOLU rs; + unsigned char *line; } Write; static void write_destroy( Write *write ) { - VIPS_FREE( write->filename ); - VIPS_FREEF( fclose, write->fout ); - vips_dbuf_destroy( &write->dbuf ); + VIPS_FREE( write->line ); + VIPS_UNREF( write->target ); vips_free( write ); } static Write * -write_new( VipsImage *in ) +write_new( VipsImage *in, VipsTarget *target ) { Write *write; int i; @@ -1252,11 +1007,8 @@ write_new( VipsImage *in ) return( NULL ); write->in = in; - - write->filename = NULL; - write->fout = NULL; - - vips_dbuf_init( &write->dbuf ); + write->target = target; + g_object_ref( target ); strcpy( write->format, COLRFMT ); write->expos = 1.0; @@ -1272,6 +1024,11 @@ write_new( VipsImage *in ) write->prims[3][0] = CIE_x_w; write->prims[3][1] = CIE_y_w; + if( !(write->line = VIPS_ARRAY( NULL, MAX_LINE, unsigned char )) ) { + write_destroy( write ); + return( NULL ); + } + return( write ); } @@ -1286,7 +1043,8 @@ vips2rad_make_header( Write *write ) vips_image_get_double( write->in, "rad-expos", &write->expos ); if( vips_image_get_typeof( write->in, "rad-aspect" ) ) - vips_image_get_double( write->in, "rad-aspect", &write->aspect ); + vips_image_get_double( write->in, + "rad-aspect", &write->aspect ); if( vips_image_get_typeof( write->in, "rad-format" ) && !vips_image_get_string( write->in, "rad-format", &str ) ) @@ -1299,7 +1057,8 @@ vips2rad_make_header( Write *write ) for( i = 0; i < 3; i++ ) if( vips_image_get_typeof( write->in, colcor_name[i] ) && - !vips_image_get_double( write->in, colcor_name[i], &d ) ) + !vips_image_get_double( write->in, + colcor_name[i], &d ) ) write->colcor[i] = d; for( i = 0; i < 4; i++ ) @@ -1323,16 +1082,53 @@ vips2rad_put_header( Write *write ) { vips2rad_make_header( write ); - fprintf( write->fout, "#?RADIANCE\n" ); + vips_target_writes( write->target, "#?RADIANCE\n" ); + vips_target_writef( write->target, "%s%s\n", FMTSTR, write->format ); + vips_target_writef( write->target, "%s%e\n", EXPOSSTR, write->expos ); + vips_target_writef( write->target, + "%s %f %f %f\n", COLCORSTR, + write->colcor[RED], write->colcor[GRN], write->colcor[BLU] ); + vips_target_writef( write->target, + "SOFTWARE=vips %s\n", vips_version_string() ); + vips_target_writef( write->target, + "%s%f\n", ASPECTSTR, write->aspect ); + vips_target_writef( write->target, + "%s %.4f %.4f %.4f %.4f %.4f %.4f %.4f %.4f\n", + PRIMARYSTR, + write->prims[RED][CIEX], write->prims[RED][CIEY], + write->prims[GRN][CIEX], write->prims[GRN][CIEY], + write->prims[BLU][CIEX], write->prims[BLU][CIEY], + write->prims[WHT][CIEX], write->prims[WHT][CIEY] ); + vips_target_writes( write->target, "\n" ); + vips_target_writes( write->target, + resolu2str( resolu_buf, &write->rs ) ); - fputformat( write->format, write->fout ); - fputexpos( write->expos, write->fout ); - fputcolcor( write->colcor, write->fout ); - fprintf( write->fout, "SOFTWARE=vips %s\n", vips_version_string() ); - fputaspect( write->aspect, write->fout ); - fputprims( write->prims, write->fout ); - fputs( "\n", write->fout ); - fputsresolu( &write->rs, write->fout ); + return( 0 ); +} + +/* Write a single scanline to buffer. + */ +static int +scanline_write( Write *write, COLR *scanline, int width ) +{ + if( width < MINELEN || + width > MAXELEN ) { + /* Too large or small for RLE ... do a simple write. + */ + if( vips_target_write( write->target, + scanline, sizeof( COLR ) * width ) ) + return( -1 ); + } + else { + int length; + + /* An RLE scanline. + */ + rle_scanline_write( scanline, width, write->line, &length ); + + if( vips_target_write( write->target, write->line, length ) ) + return( -1 ); + } return( 0 ); } @@ -1341,23 +1137,12 @@ static int vips2rad_put_data_block( VipsRegion *region, VipsRect *area, void *a ) { Write *write = (Write *) a; - - size_t size; - unsigned char *buffer; int i; - /* You have to seek back after a write. - */ - buffer = vips_dbuf_get_write( &write->dbuf, &size ); - vips_dbuf_seek( &write->dbuf, 0, SEEK_SET ); - - g_assert( size >= MAX_LINE ); - for( i = 0; i < area->height; i++ ) { VipsPel *p = VIPS_REGION_ADDR( region, 0, area->top + i ); - if( scanline_write( buffer, - (COLR *) p, area->width, write->fout ) ) + if( scanline_write( write, (COLR *) p, area->width ) ) return( -1 ); } @@ -1374,124 +1159,7 @@ vips2rad_put_data( Write *write ) } int -vips__rad_save( VipsImage *in, const char *filename ) -{ - Write *write; - -#ifdef DEBUG - printf( "vips2rad: writing \"%s\"\n", filename ); -#endif /*DEBUG*/ - - if( vips_image_pio_input( in ) || - vips_check_coding_rad( "vips2rad", in ) ) - return( -1 ); - if( !(write = write_new( in )) ) - return( -1 ); - - write->filename = vips_strdup( NULL, filename ); - write->fout = vips__file_open_write( filename, FALSE ); - - /* scanline_write() needs a buffer to write compressed scanlines to. - * We use the dbuf ... why not. - */ - vips_dbuf_allocate( &write->dbuf, MAX_LINE ); - - if( !write->filename || - !write->fout || - vips2rad_put_header( write ) || - vips2rad_put_data( write ) ) { - write_destroy( write ); - return( -1 ); - } - write_destroy( write ); - - return( 0 ); -} - -static int -vips2rad_put_header_buf( Write *write ) -{ - vips2rad_make_header( write ); - - vips_dbuf_writef( &write->dbuf, "#?RADIANCE\n" ); - vips_dbuf_writef( &write->dbuf, "%s%s\n", FMTSTR, write->format ); - vips_dbuf_writef( &write->dbuf, "%s%e\n", EXPOSSTR, write->expos ); - vips_dbuf_writef( &write->dbuf, "%s %f %f %f\n", - COLCORSTR, - write->colcor[RED], write->colcor[GRN], write->colcor[BLU] ); - vips_dbuf_writef( &write->dbuf, "SOFTWARE=vips %s\n", - vips_version_string() ); - vips_dbuf_writef( &write->dbuf, "%s%f\n", ASPECTSTR, write->aspect ); - vips_dbuf_writef( &write->dbuf, - "%s %.4f %.4f %.4f %.4f %.4f %.4f %.4f %.4f\n", - PRIMARYSTR, - write->prims[RED][CIEX], write->prims[RED][CIEY], - write->prims[GRN][CIEX], write->prims[GRN][CIEY], - write->prims[BLU][CIEX], write->prims[BLU][CIEY], - write->prims[WHT][CIEX], write->prims[WHT][CIEY] ); - vips_dbuf_writef( &write->dbuf, "\n" ); - vips_dbuf_writef( &write->dbuf, "%s", - resolu2str( resolu_buf, &write->rs ) ); - - return( 0 ); -} - -/* Write a single scanline to buffer. - */ -static int -scanline_write_buf( Write *write, COLR *scanline, int width ) -{ - unsigned char *buffer; - size_t size; - int length; - - vips_dbuf_allocate( &write->dbuf, MAX_LINE ); - buffer = vips_dbuf_get_write( &write->dbuf, &size ); - - if( width < MINELEN || - width > MAXELEN ) { - /* Write as a flat scanline. - */ - length = sizeof( COLR ) * width; - memcpy( buffer, scanline, length ); - } - else - /* An RLE scanline. - */ - rle_scanline_write( scanline, width, buffer, &length ); - - vips_dbuf_seek( &write->dbuf, length - size, SEEK_CUR ); - - return( 0 ); -} - -static int -vips2rad_put_data_block_buf( VipsRegion *region, VipsRect *area, void *a ) -{ - Write *write = (Write *) a; - int i; - - for( i = 0; i < area->height; i++ ) { - VipsPel *p = VIPS_REGION_ADDR( region, 0, area->top + i ); - - if( scanline_write_buf( write, (COLR *) p, area->width ) ) - return( -1 ); - } - - return( 0 ); -} - -static int -vips2rad_put_data_buf( Write *write ) -{ - if( vips_sink_disc( write->in, vips2rad_put_data_block_buf, write ) ) - return( -1 ); - - return( 0 ); -} - -int -vips__rad_save_buf( VipsImage *in, void **obuf, size_t *olen ) +vips__rad_save( VipsImage *in, VipsTarget *target ) { Write *write; @@ -1500,18 +1168,18 @@ vips__rad_save_buf( VipsImage *in, void **obuf, size_t *olen ) #endif /*DEBUG*/ if( vips_image_pio_input( in ) || - vips_check_coding_rad( "vips2rad", in ) ) + vips_check_coding( "vips2rad", in, VIPS_CODING_RAD ) ) return( -1 ); - if( !(write = write_new( in )) ) + if( !(write = write_new( in, target )) ) return( -1 ); - if( vips2rad_put_header_buf( write ) || - vips2rad_put_data_buf( write ) ) { + if( vips2rad_put_header( write ) || + vips2rad_put_data( write ) ) { write_destroy( write ); return( -1 ); } - *obuf = vips_dbuf_steal( &write->dbuf, olen ); + vips_target_finish( target ); write_destroy( write ); diff --git a/libvips/foreign/radload.c b/libvips/foreign/radload.c index d2ab6503..7180f77c 100644 --- a/libvips/foreign/radload.c +++ b/libvips/foreign/radload.c @@ -55,31 +55,38 @@ typedef struct _VipsForeignLoadRad { VipsForeignLoad parent_object; - /* Filename for load. + /* Set by subclasses. */ - char *filename; + VipsSource *source; } VipsForeignLoadRad; typedef VipsForeignLoadClass VipsForeignLoadRadClass; -G_DEFINE_TYPE( VipsForeignLoadRad, vips_foreign_load_rad, +G_DEFINE_ABSTRACT_TYPE( VipsForeignLoadRad, vips_foreign_load_rad, VIPS_TYPE_FOREIGN_LOAD ); -static VipsForeignFlags -vips_foreign_load_rad_get_flags_filename( const char *filename ) +static void +vips_foreign_load_rad_dispose( GObject *gobject ) { - /* The rad reader supports sequential read. - */ - return( VIPS_FOREIGN_SEQUENTIAL ); + VipsForeignLoadRad *rad = (VipsForeignLoadRad *) gobject; + + VIPS_UNREF( rad->source ); + + G_OBJECT_CLASS( vips_foreign_load_rad_parent_class )-> + dispose( gobject ); } static VipsForeignFlags vips_foreign_load_rad_get_flags( VipsForeignLoad *load ) { - VipsForeignLoadRad *rad = (VipsForeignLoadRad *) load; + return( VIPS_FOREIGN_SEQUENTIAL ); +} - return( vips_foreign_load_rad_get_flags_filename( rad->filename ) ); +static VipsForeignFlags +vips_foreign_load_rad_get_flags_filename( const char *filename ) +{ + return( VIPS_FOREIGN_SEQUENTIAL ); } static int @@ -87,11 +94,9 @@ vips_foreign_load_rad_header( VipsForeignLoad *load ) { VipsForeignLoadRad *rad = (VipsForeignLoadRad *) load; - if( vips__rad_header( rad->filename, load->out ) ) + if( vips__rad_header( rad->source, load->out ) ) return( -1 ); - VIPS_SETSTR( load->out->filename, rad->filename ); - return( 0 ); } @@ -100,7 +105,7 @@ vips_foreign_load_rad_load( VipsForeignLoad *load ) { VipsForeignLoadRad *rad = (VipsForeignLoadRad *) load; - if( vips__rad_load( rad->filename, load->real ) ) + if( vips__rad_load( rad->source, load->real ) ) return( -1 ); return( 0 ); @@ -114,31 +119,21 @@ vips_foreign_load_rad_class_init( VipsForeignLoadRadClass *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; + gobject_class->dispose = vips_foreign_load_rad_dispose; - object_class->nickname = "radload"; - object_class->description = _( "load a Radiance image from a file" ); - - foreign_class->suffs = vips__rad_suffs; + object_class->nickname = "radload_base"; + object_class->description = _( "load rad base class" ); /* is_a() is not that quick ... lower the priority. */ foreign_class->priority = -50; - load_class->is_a = vips__rad_israd; load_class->get_flags_filename = vips_foreign_load_rad_get_flags_filename; load_class->get_flags = vips_foreign_load_rad_get_flags; load_class->header = vips_foreign_load_rad_header; load_class->load = vips_foreign_load_rad_load; - VIPS_ARG_STRING( class, "filename", 1, - _( "Filename" ), - _( "Filename to load from" ), - VIPS_ARGUMENT_REQUIRED_INPUT, - G_STRUCT_OFFSET( VipsForeignLoadRad, filename ), - NULL ); } static void @@ -146,6 +141,228 @@ vips_foreign_load_rad_init( VipsForeignLoadRad *rad ) { } +typedef struct _VipsForeignLoadRadSource { + VipsForeignLoadRad parent_object; + + /* Load from a source. + */ + VipsSource *source; + +} VipsForeignLoadRadSource; + +typedef VipsForeignLoadRadClass VipsForeignLoadRadSourceClass; + +G_DEFINE_TYPE( VipsForeignLoadRadSource, vips_foreign_load_rad_source, + vips_foreign_load_rad_get_type() ); + +static int +vips_foreign_load_rad_source_build( VipsObject *object ) +{ + VipsForeignLoadRad *rad = (VipsForeignLoadRad *) object; + VipsForeignLoadRadSource *source = (VipsForeignLoadRadSource *) object; + + if( source->source ) { + rad->source = source->source; + g_object_ref( rad->source ); + } + + if( VIPS_OBJECT_CLASS( vips_foreign_load_rad_source_parent_class )-> + build( object ) ) + return( -1 ); + + return( 0 ); +} + +static gboolean +vips_foreign_load_rad_source_is_a_source( VipsSource *source ) +{ + return( vips__rad_israd( source ) ); +} + +static void +vips_foreign_load_rad_source_class_init( VipsForeignLoadRadSourceClass *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 = "radload_source"; + object_class->description = _( "load rad from source" ); + object_class->build = vips_foreign_load_rad_source_build; + + load_class->is_a_source = vips_foreign_load_rad_source_is_a_source; + + VIPS_ARG_OBJECT( class, "source", 1, + _( "Source" ), + _( "Source to load from" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadRadSource, source ), + VIPS_TYPE_SOURCE ); + +} + +static void +vips_foreign_load_rad_source_init( VipsForeignLoadRadSource *source ) +{ +} + +typedef struct _VipsForeignLoadRadFile { + VipsForeignLoadRad parent_object; + + /* Filename for load. + */ + char *filename; + +} VipsForeignLoadRadFile; + +typedef VipsForeignLoadRadClass VipsForeignLoadRadFileClass; + +G_DEFINE_TYPE( VipsForeignLoadRadFile, vips_foreign_load_rad_file, + vips_foreign_load_rad_get_type() ); + +static int +vips_foreign_load_rad_file_build( VipsObject *object ) +{ + VipsForeignLoadRad *rad = (VipsForeignLoadRad *) object; + VipsForeignLoadRadFile *file = (VipsForeignLoadRadFile *) object; + + if( file->filename && + !(rad->source = vips_source_new_from_file( file->filename )) ) + return( -1 ); + + if( VIPS_OBJECT_CLASS( vips_foreign_load_rad_file_parent_class )-> + build( object ) ) + return( -1 ); + + return( 0 ); +} + +static int +vips_foreign_load_rad_file_is_a( const char *filename ) +{ + VipsSource *source; + int result; + + if( !(source = vips_source_new_from_file( filename )) ) + return( -1 ); + result = vips_foreign_load_rad_source_is_a_source( source ); + VIPS_UNREF( source ); + + return( result ); +} + +static void +vips_foreign_load_rad_file_class_init( VipsForeignLoadRadFileClass *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 = "radload"; + object_class->description = _( "load a Radiance image from a file" ); + object_class->build = vips_foreign_load_rad_file_build; + + foreign_class->suffs = vips__rad_suffs; + + load_class->is_a = vips_foreign_load_rad_file_is_a; + + VIPS_ARG_STRING( class, "filename", 1, + _( "Filename" ), + _( "Filename to load from" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadRadFile, filename ), + NULL ); +} + +static void +vips_foreign_load_rad_file_init( VipsForeignLoadRadFile *file ) +{ +} + +typedef struct _VipsForeignLoadRadBuffer { + VipsForeignLoadRad parent_object; + + /* Load from a buffer. + */ + VipsBlob *blob; + +} VipsForeignLoadRadBuffer; + +typedef VipsForeignLoadRadClass VipsForeignLoadRadBufferClass; + +G_DEFINE_TYPE( VipsForeignLoadRadBuffer, vips_foreign_load_rad_buffer, + vips_foreign_load_rad_get_type() ); + +static int +vips_foreign_load_rad_buffer_build( VipsObject *object ) +{ + VipsForeignLoadRad *rad = (VipsForeignLoadRad *) object; + VipsForeignLoadRadBuffer *buffer = (VipsForeignLoadRadBuffer *) object; + + if( buffer->blob && + !(rad->source = vips_source_new_from_memory( + VIPS_AREA( buffer->blob )->data, + VIPS_AREA( buffer->blob )->length )) ) + return( -1 ); + + if( VIPS_OBJECT_CLASS( vips_foreign_load_rad_file_parent_class )-> + build( object ) ) + return( -1 ); + + return( 0 ); +} + +static gboolean +vips_foreign_load_rad_buffer_is_a_buffer( const void *buf, size_t len ) +{ + VipsSource *source; + gboolean result; + + if( !(source = vips_source_new_from_memory( buf, len )) ) + return( FALSE ); + result = vips_foreign_load_rad_source_is_a_source( source ); + VIPS_UNREF( source ); + + return( result ); +} + +static void +vips_foreign_load_rad_buffer_class_init( VipsForeignLoadRadBufferClass *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 = "radload_buffer"; + object_class->description = _( "load rad from buffer" ); + object_class->build = vips_foreign_load_rad_buffer_build; + + load_class->is_a_buffer = vips_foreign_load_rad_buffer_is_a_buffer; + + VIPS_ARG_BOXED( class, "buffer", 1, + _( "Buffer" ), + _( "Buffer to load from" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadRadBuffer, blob ), + VIPS_TYPE_BLOB ); + +} + +static void +vips_foreign_load_rad_buffer_init( VipsForeignLoadRadBuffer *buffer ) +{ +} + #endif /*HAVE_RADIANCE*/ /** @@ -185,3 +402,64 @@ vips_radload( const char *filename, VipsImage **out, ... ) return( result ); } +/** + * vips_radload_buffer: + * @buf: (array length=len) (element-type guint8): memory area to load + * @len: (type gsize): size of memory area + * @out: (out): image to write + * @...: %NULL-terminated list of optional named arguments + * + * Exactly as vips_radload(), but read from a HDR-formatted memory block. + * + * You must not free the buffer while @out is active. The + * #VipsObject::postclose signal on @out is a good place to free. + * + * See also: vips_radload(). + * + * Returns: 0 on success, -1 on error. + */ +int +vips_radload_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( "radload_buffer", ap, blob, out ); + va_end( ap ); + + vips_area_unref( VIPS_AREA( blob ) ); + + return( result ); +} + +/** + * vips_radload_source: + * @source: source to load from + * @out: (out): output image + * @...: %NULL-terminated list of optional named arguments + * + * Exactly as vips_radload(), but read from a source. + * + * See also: vips_radload(). + * + * Returns: 0 on success, -1 on error. + */ +int +vips_radload_source( VipsSource *source, VipsImage **out, ... ) +{ + va_list ap; + int result; + + va_start( ap, out ); + result = vips_call_split( "radload_source", ap, source, out ); + va_end( ap ); + + return( result ); +} + diff --git a/libvips/foreign/radsave.c b/libvips/foreign/radsave.c index 4571a9c6..e2dc9c00 100644 --- a/libvips/foreign/radsave.c +++ b/libvips/foreign/radsave.c @@ -56,7 +56,6 @@ typedef struct _VipsForeignSaveRad { VipsForeignSave parent_object; - char *filename; } VipsForeignSaveRad; typedef VipsForeignSaveClass VipsForeignSaveRadClass; @@ -85,14 +84,10 @@ static int vips_foreign_save_rad_format_table[10] = { static void vips_foreign_save_rad_class_init( VipsForeignSaveRadClass *class ) { - GObjectClass *gobject_class = G_OBJECT_CLASS( class ); VipsObjectClass *object_class = (VipsObjectClass *) class; VipsForeignClass *foreign_class = (VipsForeignClass *) class; VipsForeignSaveClass *save_class = (VipsForeignSaveClass *) class; - gobject_class->set_property = vips_object_set_property; - gobject_class->get_property = vips_object_get_property; - object_class->nickname = "radsave_base"; object_class->description = _( "save Radiance" ); @@ -127,12 +122,19 @@ vips_foreign_save_rad_file_build( VipsObject *object ) VipsForeignSave *save = (VipsForeignSave *) object; VipsForeignSaveRadFile *file = (VipsForeignSaveRadFile *) object; + VipsTarget *target; + if( VIPS_OBJECT_CLASS( vips_foreign_save_rad_file_parent_class )-> build( object ) ) return( -1 ); - if( vips__rad_save( save->ready, file->filename ) ) + if( !(target = vips_target_new_to_file( file->filename )) ) return( -1 ); + if( vips__rad_save( save->ready, target ) ) { + VIPS_UNREF( target ); + return( -1 ); + } + VIPS_UNREF( target ); return( 0 ); } @@ -163,6 +165,60 @@ vips_foreign_save_rad_file_init( VipsForeignSaveRadFile *file ) { } +typedef struct _VipsForeignSaveRadTarget { + VipsForeignSaveRad parent_object; + + VipsTarget *target; +} VipsForeignSaveRadTarget; + +typedef VipsForeignSaveRadClass VipsForeignSaveRadTargetClass; + +G_DEFINE_TYPE( VipsForeignSaveRadTarget, vips_foreign_save_rad_target, + vips_foreign_save_rad_get_type() ); + +static int +vips_foreign_save_rad_target_build( VipsObject *object ) +{ + VipsForeignSave *save = (VipsForeignSave *) object; + VipsForeignSaveRadTarget *target = (VipsForeignSaveRadTarget *) object; + + if( VIPS_OBJECT_CLASS( vips_foreign_save_rad_target_parent_class )-> + build( object ) ) + return( -1 ); + + if( vips__rad_save( save->ready, target->target ) ) + return( -1 ); + + return( 0 ); +} + +static void +vips_foreign_save_rad_target_class_init( VipsForeignSaveRadTargetClass *class ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( class ); + VipsObjectClass *object_class = (VipsObjectClass *) class; + + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + object_class->nickname = "radsave_target"; + object_class->description = _( "save image to Radiance target" ); + object_class->build = vips_foreign_save_rad_target_build; + + VIPS_ARG_OBJECT( class, "target", 1, + _( "Target" ), + _( "Target to save to" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsForeignSaveRadTarget, target ), + VIPS_TYPE_TARGET ); + +} + +static void +vips_foreign_save_rad_target_init( VipsForeignSaveRadTarget *target ) +{ +} + typedef struct _VipsForeignSaveRadBuffer { VipsForeignSaveRad parent_object; @@ -179,24 +235,27 @@ vips_foreign_save_rad_buffer_build( VipsObject *object ) { VipsForeignSave *save = (VipsForeignSave *) object; - void *obuf; - size_t olen; + VipsTarget *target; VipsBlob *blob; if( VIPS_OBJECT_CLASS( vips_foreign_save_rad_buffer_parent_class )-> build( object ) ) return( -1 ); - if( vips__rad_save_buf( save->ready, &obuf, &olen ) ) + if( !(target = vips_target_new_to_memory()) ) return( -1 ); - /* vips__rad_save_buf() makes a buffer that needs g_free(), not - * vips_free(). - */ - blob = vips_blob_new( (VipsCallbackFn) g_free, obuf, olen ); - g_object_set( object, "buffer", blob, NULL ); + if( vips__rad_save( save->ready, target ) ) { + VIPS_UNREF( target ); + return( -1 ); + } + + g_object_get( target, "blob", &blob, NULL ); + g_object_set( save, "buffer", blob, NULL ); vips_area_unref( VIPS_AREA( blob ) ); + VIPS_UNREF( target ); + return( 0 ); } @@ -299,3 +358,28 @@ vips_radsave_buffer( VipsImage *in, void **buf, size_t *len, ... ) return( result ); } + +/** + * vips_radsave_target: (method) + * @in: image to save + * @target: save image to this target + * @...: %NULL-terminated list of optional named arguments + * + * As vips_radsave(), but save to a target. + * + * See also: vips_radsave(). + * + * Returns: 0 on success, -1 on error. + */ +int +vips_radsave_target( VipsImage *in, VipsTarget *target, ... ) +{ + va_list ap; + int result; + + va_start( ap, target ); + result = vips_call_split( "radsave_target", ap, in, target ); + va_end( ap ); + + return( result ); +} diff --git a/libvips/foreign/rawsave.c b/libvips/foreign/rawsave.c index 7b4bfafa..1d6d5abc 100644 --- a/libvips/foreign/rawsave.c +++ b/libvips/foreign/rawsave.c @@ -126,11 +126,17 @@ vips_foreign_save_raw_build( VipsObject *object ) return( 0 ); } +static const char *vips_foreign_save_raw_suffs[] = { + ".raw", + NULL +}; + static void vips_foreign_save_raw_class_init( VipsForeignSaveRawClass *class ) { GObjectClass *gobject_class = G_OBJECT_CLASS( class ); VipsObjectClass *object_class = (VipsObjectClass *) class; + VipsForeignClass *foreign_class = (VipsForeignClass *) class; VipsForeignSaveClass *save_class = (VipsForeignSaveClass *) class; gobject_class->dispose = vips_foreign_save_raw_dispose; @@ -141,6 +147,8 @@ vips_foreign_save_raw_class_init( VipsForeignSaveRawClass *class ) object_class->description = _( "save image to raw file" ); object_class->build = vips_foreign_save_raw_build; + foreign_class->suffs = vips_foreign_save_raw_suffs; + save_class->saveable = VIPS_SAVEABLE_ANY; VIPS_ARG_STRING( class, "filename", 1, @@ -238,6 +246,7 @@ vips_foreign_save_raw_fd_class_init( VipsForeignSaveRawFdClass *class ) { GObjectClass *gobject_class = G_OBJECT_CLASS( class ); VipsObjectClass *object_class = (VipsObjectClass *) class; + VipsForeignClass *foreign_class = (VipsForeignClass *) class; VipsForeignSaveClass *save_class = (VipsForeignSaveClass *) class; gobject_class->set_property = vips_object_set_property; @@ -247,6 +256,8 @@ vips_foreign_save_raw_fd_class_init( VipsForeignSaveRawFdClass *class ) object_class->description = _( "write raw image to file descriptor" ); object_class->build = vips_foreign_save_raw_fd_build; + foreign_class->suffs = vips_foreign_save_raw_suffs; + save_class->saveable = VIPS_SAVEABLE_ANY; VIPS_ARG_INT( class, "fd", 1, diff --git a/libvips/foreign/spngload.c b/libvips/foreign/spngload.c new file mode 100644 index 00000000..cd0d4537 --- /dev/null +++ b/libvips/foreign/spngload.c @@ -0,0 +1,825 @@ +/* load PNG with libspng + * + * 1/5/20 + * - from pngload.c + */ + +/* + + 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 + +#include +#include +#include + +#include "pforeign.h" + +#ifdef HAVE_SPNG + +#include + +typedef struct _VipsForeignLoadPng { + VipsForeignLoad parent_object; + + /* Set by subclasses. + */ + VipsSource *source; + + spng_ctx *ctx; + struct spng_ihdr ihdr; + enum spng_format fmt; + int bands; + VipsInterpretation interpretation; + VipsBandFormat format; + int y_pos; + +} VipsForeignLoadPng; + +typedef VipsForeignLoadClass VipsForeignLoadPngClass; + +G_DEFINE_ABSTRACT_TYPE( VipsForeignLoadPng, vips_foreign_load_png, + VIPS_TYPE_FOREIGN_LOAD ); + +static void +vips_foreign_load_png_dispose( GObject *gobject ) +{ + VipsForeignLoadPng *png = (VipsForeignLoadPng *) gobject; + + VIPS_FREEF( spng_ctx_free, png->ctx ); + VIPS_UNREF( png->source ); + + G_OBJECT_CLASS( vips_foreign_load_png_parent_class )-> + dispose( gobject ); +} + +static int +vips_foreign_load_png_stream( spng_ctx *ctx, void *user, + void *dest, size_t length ) +{ + VipsSource *source = VIPS_SOURCE( user ); + + gint64 bytes_read; + + bytes_read = vips_source_read( source, dest, length ); + if( bytes_read < 0 ) + return( SPNG_IO_ERROR ); + if( bytes_read < length ) + return( SPNG_IO_EOF); + + return( 0 ); +} + +static VipsForeignFlags +vips_foreign_load_png_get_flags_source( VipsSource *source ) +{ + spng_ctx *ctx; + struct spng_ihdr ihdr; + VipsForeignFlags flags; + + ctx = spng_ctx_new( SPNG_CTX_IGNORE_ADLER32 ); + spng_set_crc_action( ctx, SPNG_CRC_USE, SPNG_CRC_USE ); + spng_set_png_stream( ctx, + vips_foreign_load_png_stream, source ); + if( spng_get_ihdr( ctx, &ihdr ) ) { + spng_ctx_free( ctx ); + return( 0 ); + } + spng_ctx_free( ctx ); + + flags = 0; + if( ihdr.interlace_method != SPNG_INTERLACE_NONE ) + flags |= VIPS_FOREIGN_PARTIAL; + else + flags |= VIPS_FOREIGN_SEQUENTIAL; + + return( flags ); +} + +static VipsForeignFlags +vips_foreign_load_png_get_flags( VipsForeignLoad *load ) +{ + VipsForeignLoadPng *png = (VipsForeignLoadPng *) load; + + return( vips_foreign_load_png_get_flags_source( png->source ) ); +} + +static VipsForeignFlags +vips_foreign_load_png_get_flags_filename( const char *filename ) +{ + VipsSource *source; + VipsForeignFlags flags; + + if( !(source = vips_source_new_from_file( filename )) ) + return( 0 ); + flags = vips_foreign_load_png_get_flags_source( source ); + VIPS_UNREF( source ); + + return( flags ); +} + +/* Set the png text data as metadata on the vips image. These are always + * null-terminated strings. + */ +static void +vips_foreign_load_png_set_text( VipsImage *out, + int i, const char *key, const char *value ) +{ +#ifdef DEBUG + printf( "vips_foreign_load_png_set_text: key %s, value %s\n", + key, value ); +#endif /*DEBUG*/ + + if( strcmp( key, "XML:com.adobe.xmp" ) == 0 ) { + /* Save as an XMP tag. This must be a BLOB, for compatibility + * for things like the XMP blob that the tiff loader adds. + * + * Note that this will remove the null-termination from the + * string. We must carefully reattach this. + */ + vips_image_set_blob_copy( out, + VIPS_META_XMP_NAME, value, strlen( value ) ); + } + else { + char name[256]; + + /* Save as a string comment. Some PNGs have EXIF data as + * text segments, unfortunately. + */ + vips_snprintf( name, 256, "png-comment-%d-%s", i, key ); + + vips_image_set_string( out, name, value ); + } +} + +static void +vips_foreign_load_png_set_header( VipsForeignLoadPng *png, VipsImage *image ) +{ + double xres, yres; + struct spng_iccp iccp; + struct spng_exif exif; + struct spng_phys phys; + guint32 n_text; + + /* Get resolution. Default to 72 pixels per inch. + */ + xres = (72.0 / 2.54 * 100.0); + yres = (72.0 / 2.54 * 100.0); + if( !spng_get_phys( png->ctx, &phys ) ) { + /* There's phys.units, but it's always 0, meaning pixels per + * metre. + */ + xres = phys.ppu_x / 1000.0; + yres = phys.ppu_y / 1000.0; + } + + vips_image_init_fields( image, + png->ihdr.width, png->ihdr.height, png->bands, + png->format, VIPS_CODING_NONE, png->interpretation, + xres, yres ); + + VIPS_SETSTR( image->filename, + vips_connection_filename( VIPS_CONNECTION( png->source ) ) ); + + if( png->ihdr.interlace_method == SPNG_INTERLACE_NONE ) + /* Sequential mode needs thinstrip to work with things like + * vips_shrink(). + */ + vips_image_pipelinev( image, + VIPS_DEMAND_STYLE_THINSTRIP, NULL ); + else + /* Interlaced images are read via a huge memory buffer. + */ + vips_image_pipelinev( image, VIPS_DEMAND_STYLE_ANY, NULL ); + + if( !spng_get_iccp( png->ctx, &iccp ) ) + vips_image_set_blob_copy( image, + VIPS_META_ICC_NAME, iccp.profile, iccp.profile_len ); + + if( !spng_get_text( png->ctx, NULL, &n_text ) ) { + struct spng_text *text; + + text = VIPS_ARRAY( VIPS_OBJECT( png ), + n_text, struct spng_text ); + if( !spng_get_text( png->ctx, text, &n_text ) ) { + guint32 i; + + for( i = 0; i < n_text; i++ ) + /* .text is always a null-terminated C string. + */ + vips_foreign_load_png_set_text( image, + i, text[i].keyword, text[i].text ); + } + } + + if( !spng_get_exif( png->ctx, &exif ) ) + vips_image_set_blob_copy( image, VIPS_META_EXIF_NAME, + exif.data, exif.length ); + + /* Attach original palette bit depth, if any, as metadata. + */ + if( png->ihdr.color_type == SPNG_COLOR_TYPE_INDEXED ) + vips_image_set_int( image, + "palette-bit-depth", png->ihdr.bit_depth ); + + /* Let our caller know. These are very expensive to decode. + */ + if( png->ihdr.interlace_method != SPNG_INTERLACE_NONE ) + vips_image_set_int( image, "interlaced", 1 ); +} + +static int +vips_foreign_load_png_header( VipsForeignLoad *load ) +{ + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( load ); + VipsForeignLoadPng *png = (VipsForeignLoadPng *) load; + + int flags; + int error; + struct spng_trns trns; + + /* In non-fail mode, ignore CRC errors. + */ + flags = 0; + if( !load->fail ) + flags |= SPNG_CTX_IGNORE_ADLER32; + png->ctx = spng_ctx_new( flags ); + if( !load->fail ) + /* Ignore and don't calculate checksums. + */ + spng_set_crc_action( png->ctx, SPNG_CRC_USE, SPNG_CRC_USE ); + + /* Set limits to avoid decompression bombs. Set chunk limits to 60mb + * -- we've seen 50mb XMP blocks in the wild. + * + * No need to test the decoded image size -- the user can do that if + * they wish. + */ + spng_set_image_limits( png->ctx, VIPS_MAX_COORD, VIPS_MAX_COORD ); + spng_set_chunk_limits( png->ctx, 60 * 1024 * 1024, 60 * 1024 * 1024 ); + + if( vips_source_rewind( png->source ) ) + return( -1 ); + spng_set_png_stream( png->ctx, + vips_foreign_load_png_stream, png->source ); + if( (error = spng_get_ihdr( png->ctx, &png->ihdr )) ) { + vips_error( class->nickname, "%s", spng_strerror( error ) ); + return( -1 ); + } + + /* + printf( "width: %d\nheight: %d\nbit depth: %d\ncolor type: %d\n", + png->ihdr.width, png->ihdr.height, + png->ihdr.bit_depth, png->ihdr.color_type ); + printf( "compression method: %d\nfilter method: %d\n" + "interlace method: %d\n", + png->ihdr.compression_method, png->ihdr.filter_method, + png->ihdr.interlace_method ); + */ + + /* Just convert to host-endian if nothing else applies. + */ + png->fmt = SPNG_FMT_PNG; + + switch( png->ihdr.color_type ) { + case SPNG_COLOR_TYPE_INDEXED: + png->bands = 3; + break; + + case SPNG_COLOR_TYPE_GRAYSCALE_ALPHA: + case SPNG_COLOR_TYPE_GRAYSCALE: + png->bands = 1; + break; + + case SPNG_COLOR_TYPE_TRUECOLOR: + case SPNG_COLOR_TYPE_TRUECOLOR_ALPHA: + png->bands = 3; + break; + + default: + vips_error( class->nickname, "%s", _( "unknown color type" ) ); + return( -1 ); + } + + /* Set libvips format and interpretation. + */ + if( png->ihdr.bit_depth > 8 ) { + if( png->bands < 3 ) + png->interpretation = VIPS_INTERPRETATION_GREY16; + else + png->interpretation = VIPS_INTERPRETATION_RGB16; + + png->format = VIPS_FORMAT_USHORT; + } + else { + if( png->bands < 3 ) + png->interpretation = VIPS_INTERPRETATION_B_W; + else + png->interpretation = VIPS_INTERPRETATION_sRGB; + + png->format = VIPS_FORMAT_UCHAR; + } + + /* Expand palette images. + */ + if( png->ihdr.color_type == SPNG_COLOR_TYPE_INDEXED ) + png->fmt = SPNG_FMT_RGB8; + + /* Expand <8 bit images to full bytes. + */ + if( png->ihdr.color_type == SPNG_COLOR_TYPE_GRAYSCALE && + png->ihdr.bit_depth < 8 ) + png->fmt = SPNG_FMT_G8; + + /* Expand transparency. + * + * The _ALPHA types should not have the optional trns chunk (they + * always have a transparent band), see + * https://www.w3.org/TR/2003/REC-PNG-20031110/#11tRNS + */ + if( png->ihdr.color_type == SPNG_COLOR_TYPE_GRAYSCALE_ALPHA || + png->ihdr.color_type == SPNG_COLOR_TYPE_TRUECOLOR_ALPHA ) + png->bands += 1; + else if( !spng_get_trns( png->ctx, &trns ) ) { + png->bands += 1; + + if( png->ihdr.color_type == SPNG_COLOR_TYPE_TRUECOLOR ) { + if( png->ihdr.bit_depth == 16 ) + png->fmt = SPNG_FMT_RGBA16; + else + png->fmt = SPNG_FMT_RGBA8; + } + else if( png->ihdr.color_type == SPNG_COLOR_TYPE_INDEXED ) + png->fmt = SPNG_FMT_RGBA8; + else if( png->ihdr.color_type == SPNG_COLOR_TYPE_GRAYSCALE ) { + if( png->ihdr.bit_depth == 16 ) + png->fmt = SPNG_FMT_GA16; + else + png->fmt = SPNG_FMT_GA8; + } + } + + vips_source_minimise( png->source ); + + vips_foreign_load_png_set_header( png, load->out ); + + return( 0 ); +} + +static void +vips_foreign_load_png_minimise( VipsObject *object, VipsForeignLoadPng *png ) +{ + vips_source_minimise( png->source ); +} + +static int +vips_foreign_load_png_generate( VipsRegion *or, + void *seq, void *a, void *b, gboolean *stop ) +{ + VipsRect *r = &or->valid; + VipsForeignLoad *load = VIPS_FOREIGN_LOAD( a ); + VipsForeignLoadPng *png = (VipsForeignLoadPng *) load; + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( png ); + + int y; + int error; + +#ifdef DEBUG + printf( "vips_foreign_load_png_generate: line %d, %d rows\n", + r->top, r->height ); + printf( "vips_foreign_load_png_generate: y_top = %d\n", png->y_pos ); +#endif /*DEBUG*/ + + /* We're inside a tilecache where tiles are the full image width, so + * this should always be true. + */ + g_assert( r->left == 0 ); + g_assert( r->width == or->im->Xsize ); + g_assert( VIPS_RECT_BOTTOM( r ) <= or->im->Ysize ); + + /* Tiles should always be a strip in height, unless it's the final + * strip. + */ + g_assert( r->height == VIPS_MIN( VIPS__FATSTRIP_HEIGHT, + or->im->Ysize - r->top ) ); + + /* And check that y_pos is correct. It should be, since we are inside + * a vips_sequential(). + */ + if( r->top != png->y_pos ) { + vips_error( class->nickname, + _( "out of order read at line %d" ), png->y_pos ); + return( -1 ); + } + + for( y = 0; y < r->height; y++ ) { + error = spng_decode_row( png->ctx, + VIPS_REGION_ADDR( or, 0, r->top + y ), + VIPS_REGION_SIZEOF_LINE( or ) ); + /* libspng returns EOI when successfully reading the + * final line of input. + */ + if( error != 0 && + error != SPNG_EOI ) { + /* We've failed to read some pixels. Knock this + * operation out of cache. + */ + vips_operation_invalidate( VIPS_OPERATION( png ) ); + +#ifdef DEBUG + printf( "vips_foreign_load_png_generate:\n" ); + printf( " spng_decode_row() failed, line %d\n", + r->top + y ); + printf( " thread %p\n", g_thread_self() ); + printf( " error %s\n", spng_strerror( error ) ); +#endif /*DEBUG*/ + + /* And bail if fail is on. + */ + if( load->fail ) { + vips_error( class->nickname, + "%s", _( "libpng read error" ) ); + return( -1 ); + } + } + + png->y_pos += 1; + } + + return( 0 ); +} + +static int +vips_foreign_load_png_load( VipsForeignLoad *load ) +{ + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( load ); + VipsForeignLoadPng *png = (VipsForeignLoadPng *) load; + VipsImage **t = (VipsImage **) + vips_object_local_array( VIPS_OBJECT( load ), 3 ); + + enum spng_decode_flags flags; + int error; + + if( vips_source_decode( png->source ) ) + return( -1 ); + + /* Decode transparency, if available. + */ + flags = SPNG_DECODE_TRNS; + + if( png->ihdr.interlace_method != SPNG_INTERLACE_NONE ) { + /* Arg awful interlaced image. We have to load to a huge mem + * buffer, then copy to out. + */ + t[0] = vips_image_new_memory(); + vips_foreign_load_png_set_header( png, t[0] ); + if( vips_image_write_prepare( t[0] ) ) + return( -1 ); + + if( (error = spng_decode_image( png->ctx, + VIPS_IMAGE_ADDR( t[0], 0, 0 ), + VIPS_IMAGE_SIZEOF_IMAGE( t[0] ), + png->fmt, flags )) ) { + vips_error( class->nickname, + "%s", spng_strerror( error ) ); + return( -1 ); + } + + /* We've now finished reading the file. + */ + vips_source_minimise( png->source ); + + if( vips_image_write( t[0], load->real ) ) + return( -1 ); + } + else { + t[0] = vips_image_new(); + vips_foreign_load_png_set_header( png, t[0] ); + + /* We can decode these progressively. + */ + flags |= SPNG_DECODE_PROGRESSIVE; + + if( (error = spng_decode_image( png->ctx, NULL, 0, + png->fmt, flags )) ) { + vips_error( class->nickname, + "%s", spng_strerror( error ) ); + return( -1 ); + } + + /* Close input immediately at end of read. + */ + g_signal_connect( t[0], "minimise", + G_CALLBACK( vips_foreign_load_png_minimise ), png ); + + if( vips_image_generate( t[0], + NULL, vips_foreign_load_png_generate, NULL, + png, NULL ) || + vips_sequential( t[0], &t[1], + "tile_height", VIPS__FATSTRIP_HEIGHT, + NULL ) || + vips_image_write( t[1], load->real ) ) + return( -1 ); + } + + return( 0 ); +} + +static void +vips_foreign_load_png_class_init( VipsForeignLoadPngClass *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->dispose = vips_foreign_load_png_dispose; + + object_class->nickname = "pngload_base"; + object_class->description = _( "load png base class" ); + + /* We are fast at is_a(), so high priority. + */ + foreign_class->priority = 200; + + load_class->get_flags_filename = + vips_foreign_load_png_get_flags_filename; + load_class->get_flags = vips_foreign_load_png_get_flags; + load_class->header = vips_foreign_load_png_header; + load_class->load = vips_foreign_load_png_load; + +} + +static void +vips_foreign_load_png_init( VipsForeignLoadPng *png ) +{ +} + +typedef struct _VipsForeignLoadPngSource { + VipsForeignLoadPng parent_object; + + /* Load from a source. + */ + VipsSource *source; + +} VipsForeignLoadPngSource; + +typedef VipsForeignLoadPngClass VipsForeignLoadPngSourceClass; + +G_DEFINE_TYPE( VipsForeignLoadPngSource, vips_foreign_load_png_source, + vips_foreign_load_png_get_type() ); + +static int +vips_foreign_load_png_source_build( VipsObject *object ) +{ + VipsForeignLoadPng *png = (VipsForeignLoadPng *) object; + VipsForeignLoadPngSource *source = (VipsForeignLoadPngSource *) object; + + if( source->source ) { + png->source = source->source; + g_object_ref( png->source ); + } + + if( VIPS_OBJECT_CLASS( vips_foreign_load_png_source_parent_class )-> + build( object ) ) + return( -1 ); + + return( 0 ); +} + +static gboolean +vips_foreign_load_png_source_is_a_source( VipsSource *source ) +{ + static unsigned char signature[8] = { 137, 80, 78, 71, 13, 10, 26, 10 }; + + const unsigned char *p; + + if( (p = vips_source_sniff( source, 8 )) && + memcmp( p, signature, 8 ) == 0 ) + return( TRUE ); + + return( FALSE ); +} + +static void +vips_foreign_load_png_source_class_init( VipsForeignLoadPngSourceClass *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 = "pngload_source"; + object_class->description = _( "load png from source" ); + object_class->build = vips_foreign_load_png_source_build; + + load_class->is_a_source = vips_foreign_load_png_source_is_a_source; + + VIPS_ARG_OBJECT( class, "source", 1, + _( "Source" ), + _( "Source to load from" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadPngSource, source ), + VIPS_TYPE_SOURCE ); + +} + +static void +vips_foreign_load_png_source_init( VipsForeignLoadPngSource *source ) +{ +} + +typedef struct _VipsForeignLoadPngFile { + VipsForeignLoadPng parent_object; + + /* Filename for load. + */ + char *filename; + +} VipsForeignLoadPngFile; + +typedef VipsForeignLoadPngClass VipsForeignLoadPngFileClass; + +G_DEFINE_TYPE( VipsForeignLoadPngFile, vips_foreign_load_png_file, + vips_foreign_load_png_get_type() ); + +static int +vips_foreign_load_png_file_build( VipsObject *object ) +{ + VipsForeignLoadPng *png = (VipsForeignLoadPng *) object; + VipsForeignLoadPngFile *file = (VipsForeignLoadPngFile *) object; + + if( file->filename && + !(png->source = vips_source_new_from_file( file->filename )) ) + return( -1 ); + + if( VIPS_OBJECT_CLASS( vips_foreign_load_png_file_parent_class )-> + build( object ) ) + return( -1 ); + + return( 0 ); +} + +static gboolean +vips_foreign_load_png_file_is_a( const char *filename ) +{ + VipsSource *source; + gboolean result; + + if( !(source = vips_source_new_from_file( filename )) ) + return( FALSE ); + result = vips_foreign_load_png_source_is_a_source( source ); + VIPS_UNREF( source ); + + return( result ); +} + +const char *vips_foreign_load_png_file_suffs[] = { ".png", NULL }; + +static void +vips_foreign_load_png_file_class_init( VipsForeignLoadPngFileClass *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 = "pngload"; + object_class->description = _( "load png from file" ); + object_class->build = vips_foreign_load_png_file_build; + + foreign_class->suffs = vips_foreign_load_png_file_suffs; + + load_class->is_a = vips_foreign_load_png_file_is_a; + + VIPS_ARG_STRING( class, "filename", 1, + _( "Filename" ), + _( "Filename to load from" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadPngFile, filename ), + NULL ); +} + +static void +vips_foreign_load_png_file_init( VipsForeignLoadPngFile *file ) +{ +} + +typedef struct _VipsForeignLoadPngBuffer { + VipsForeignLoadPng parent_object; + + /* Load from a buffer. + */ + VipsBlob *blob; + +} VipsForeignLoadPngBuffer; + +typedef VipsForeignLoadPngClass VipsForeignLoadPngBufferClass; + +G_DEFINE_TYPE( VipsForeignLoadPngBuffer, vips_foreign_load_png_buffer, + vips_foreign_load_png_get_type() ); + +static int +vips_foreign_load_png_buffer_build( VipsObject *object ) +{ + VipsForeignLoadPng *png = (VipsForeignLoadPng *) object; + VipsForeignLoadPngBuffer *buffer = (VipsForeignLoadPngBuffer *) object; + + if( buffer->blob && + !(png->source = vips_source_new_from_memory( + VIPS_AREA( buffer->blob )->data, + VIPS_AREA( buffer->blob )->length )) ) + return( -1 ); + + if( VIPS_OBJECT_CLASS( vips_foreign_load_png_buffer_parent_class )-> + build( object ) ) + return( -1 ); + + return( 0 ); +} + +static gboolean +vips_foreign_load_png_buffer_is_a_buffer( const void *buf, size_t len ) +{ + VipsSource *source; + gboolean result; + + if( !(source = vips_source_new_from_memory( buf, len )) ) + return( FALSE ); + result = vips_foreign_load_png_source_is_a_source( source ); + VIPS_UNREF( source ); + + return( result ); +} + +static void +vips_foreign_load_png_buffer_class_init( VipsForeignLoadPngBufferClass *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 = "pngload_buffer"; + object_class->description = _( "load png from buffer" ); + object_class->build = vips_foreign_load_png_buffer_build; + + load_class->is_a_buffer = vips_foreign_load_png_buffer_is_a_buffer; + + VIPS_ARG_BOXED( class, "buffer", 1, + _( "Buffer" ), + _( "Buffer to load from" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadPngBuffer, blob ), + VIPS_TYPE_BLOB ); + +} + +static void +vips_foreign_load_png_buffer_init( VipsForeignLoadPngBuffer *buffer ) +{ +} + +#endif /*HAVE_SPNG*/ diff --git a/libvips/foreign/svgload.c b/libvips/foreign/svgload.c index f1a8f2b1..212c76a7 100644 --- a/libvips/foreign/svgload.c +++ b/libvips/foreign/svgload.c @@ -64,10 +64,10 @@ #include #include -#include #include +#include -#ifdef HAVE_RSVG +#if defined(HAVE_RSVG) #include #include @@ -150,7 +150,7 @@ vips_foreign_load_svg_is_a( const void *buf, size_t len ) /* If the buffer looks like a zip, deflate to here and then search * that for 0 ); @@ -280,9 +282,10 @@ vips_foreign_load_svg_parse( VipsForeignLoadSvg *svg, VipsImage *out ) * cairo instead. */ svg->cairo_scale = scale; - width = width * scale; - height = height * scale; - } else { + width = VIPS_ROUND_UINT( width * scale ); + height = VIPS_ROUND_UINT( height * scale ); + } + else { /* SVG with width and height reports correctly scaled * dimensions. */ @@ -411,6 +414,7 @@ vips_foreign_load_svg_class_init( VipsForeignLoadSvgClass *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->dispose = vips_foreign_load_svg_dispose; @@ -420,6 +424,10 @@ vips_foreign_load_svg_class_init( VipsForeignLoadSvgClass *class ) object_class->nickname = "svgload"; object_class->description = _( "load SVG with rsvg" ); + /* is_a() is not that quick ... lower the priority. + */ + foreign_class->priority = -5; + load_class->get_flags_filename = vips_foreign_load_svg_get_flags_filename; load_class->get_flags = vips_foreign_load_svg_get_flags; @@ -456,6 +464,104 @@ vips_foreign_load_svg_init( VipsForeignLoadSvg *svg ) svg->cairo_scale = 1.0; } +typedef struct _VipsForeignLoadSvgSource { + VipsForeignLoadSvg parent_object; + + /* Load from a source. + */ + VipsSource *source; + +} VipsForeignLoadSvgSource; + +typedef VipsForeignLoadClass VipsForeignLoadSvgSourceClass; + +G_DEFINE_TYPE( VipsForeignLoadSvgSource, vips_foreign_load_svg_source, + vips_foreign_load_svg_get_type() ); + +gboolean +vips_foreign_load_svg_source_is_a_source( VipsSource *source ) +{ + unsigned char *data; + size_t bytes_read; + + if( (bytes_read = vips_source_sniff_at_most( source, + &data, SVG_HEADER_SIZE )) <= 0 ) + return( FALSE ); + + return( vips_foreign_load_svg_is_a( data, bytes_read ) ); +} + +static int +vips_foreign_load_svg_source_header( VipsForeignLoad *load ) +{ + VipsForeignLoadSvg *svg = (VipsForeignLoadSvg *) load; + VipsForeignLoadSvgSource *source = + (VipsForeignLoadSvgSource *) load; + RsvgHandleFlags flags = svg->unlimited ? RSVG_HANDLE_FLAG_UNLIMITED : 0; + + GError *error = NULL; + + GInputStream *gstream; + + if( vips_source_rewind( source->source ) ) + return( -1 ); + + gstream = vips_g_input_stream_new_from_source( source->source ); + if( !(svg->page = rsvg_handle_new_from_stream_sync( + gstream, NULL, flags, NULL, &error )) ) { + g_object_unref( gstream ); + vips_g_error( &error ); + return( -1 ); + } + g_object_unref( gstream ); + + return( vips_foreign_load_svg_header( load ) ); +} + +static int +vips_foreign_load_svg_source_load( VipsForeignLoad *load ) +{ + VipsForeignLoadSvgSource *source = (VipsForeignLoadSvgSource *) load; + + if( vips_source_rewind( source->source ) || + vips_foreign_load_svg_load( load ) || + vips_source_decode( source->source ) ) + return( -1 ); + + return( 0 ); +} + +static void +vips_foreign_load_svg_source_class_init( VipsForeignLoadSvgSourceClass *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 = "svgload_source"; + object_class->description = _( "load svg from source" ); + + load_class->is_a_source = vips_foreign_load_svg_source_is_a_source; + load_class->header = vips_foreign_load_svg_source_header; + load_class->load = vips_foreign_load_svg_source_load; + + VIPS_ARG_OBJECT( class, "source", 1, + _( "Source" ), + _( "Source to load from" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadSvgSource, source ), + VIPS_TYPE_SOURCE ); + +} + +static void +vips_foreign_load_svg_source_init( VipsForeignLoadSvgSource *source ) +{ +} + typedef struct _VipsForeignLoadSvgFile { VipsForeignLoadSvg parent_object; @@ -705,3 +811,28 @@ vips_svgload_buffer( void *buf, size_t len, VipsImage **out, ... ) return( result ); } +/** + * vips_svgload_source: + * @source: source to load from + * @out: (out): image to write + * @...: %NULL-terminated list of optional named arguments + * + * Exactly as vips_svgload(), but read from a source. + * + * See also: vips_svgload(). + * + * Returns: 0 on success, -1 on error. + */ +int +vips_svgload_source( VipsSource *source, VipsImage **out, ... ) +{ + va_list ap; + int result; + + va_start( ap, out ); + result = vips_call_split( "svgload_source", ap, source, out ); + va_end( ap ); + + return( result ); +} + diff --git a/libvips/foreign/tiff.c b/libvips/foreign/tiff.c index b231a5ba..82fc78d9 100644 --- a/libvips/foreign/tiff.c +++ b/libvips/foreign/tiff.c @@ -132,128 +132,60 @@ vips__tiff_openout( const char *path, gboolean bigtiff ) return( tif ); } -/* Open TIFF for input from a file. +/* TIFF input from a vips source. */ -TIFF * -vips__tiff_openin( const char *path ) -{ - /* No mmap --- no performance advantage with libtiff, and it burns up - * our VM if the tiff file is large. - */ - const char *mode = "rm"; - - TIFF *tif; - -#ifdef DEBUG - printf( "vips__tiff_openin( \"%s\" )\n", path ); -#endif /*DEBUG*/ - - /* Need the utf-16 version on Windows. - */ -#ifdef OS_WIN32 -{ - GError *error = NULL; - wchar_t *path16; - - if( !(path16 = (wchar_t *) - g_utf8_to_utf16( path, -1, NULL, NULL, &error )) ) { - vips_g_error( &error ); - return( NULL ); - } - - tif = TIFFOpenW( path16, mode ); - - g_free( path16 ); -} -#else /*!OS_WIN32*/ - tif = TIFFOpen( path, mode ); -#endif /*OS_WIN32*/ - - if( !tif ) { - vips_error( "tiff", - _( "unable to open \"%s\" for input" ), path ); - return( NULL ); - } - - return( tif ); -} - -/* TIFF input from a memory buffer. - */ - -typedef struct _VipsTiffOpeninBuffer { - size_t position; - const void *data; - size_t length; -} VipsTiffOpeninBuffer; static tsize_t -openin_buffer_read( thandle_t st, tdata_t data, tsize_t size ) +openin_source_read( thandle_t st, tdata_t data, tsize_t size ) { - VipsTiffOpeninBuffer *buffer = (VipsTiffOpeninBuffer *) st; + VipsSource *source = VIPS_SOURCE( st ); - size_t available; - size_t copied; - - if( buffer->position > buffer->length ) { - vips_error( "openin_buffer_read", - "%s", _( "read beyond end of buffer" ) ); - return( 0 ); - } - - available = buffer->length - buffer->position; - copied = VIPS_MIN( size, available ); - memcpy( data, - (unsigned char *) buffer->data + buffer->position, copied ); - buffer->position += copied; - - return( copied ); + return( vips_source_read( source, data, size ) ); } static tsize_t -openin_buffer_write( thandle_t st, tdata_t buffer, tsize_t size ) +openin_source_write( thandle_t st, tdata_t buffer, tsize_t size ) { g_assert_not_reached(); return( 0 ); } -static int -openin_buffer_close( thandle_t st ) +static toff_t +openin_source_seek( thandle_t st, toff_t position, int whence ) { + VipsSource *source = VIPS_SOURCE( st ); + + /* toff_t is usually uint64, with -1 cast to uint64 to indicate error. + */ + return( (toff_t) vips_source_seek( source, position, whence ) ); +} + +static int +openin_source_close( thandle_t st ) +{ + VipsSource *source = VIPS_SOURCE( st ); + + VIPS_UNREF( source ); + return( 0 ); } -/* After calling this, ->pos is not bound by the size of the buffer, it can - * have any positive value. - */ static toff_t -openin_buffer_seek( thandle_t st, toff_t position, int whence ) +openin_source_length( thandle_t st ) { - VipsTiffOpeninBuffer *buffer = (VipsTiffOpeninBuffer *) st; + VipsSource *source = VIPS_SOURCE( st ); - if( whence == SEEK_SET ) - buffer->position = position; - else if( whence == SEEK_CUR ) - buffer->position += position; - else if( whence == SEEK_END ) - buffer->position = buffer->length + position; - else - g_assert_not_reached(); - - return( buffer->position ); -} - -static toff_t -openin_buffer_size( thandle_t st ) -{ - VipsTiffOpeninBuffer *buffer = (VipsTiffOpeninBuffer *) st; - - return( buffer->length ); + /* libtiff will use this to get file size if tags like StripByteCounts + * are missing. + * + * toff_t is usually uint64, with -1 cast to uint64 to indicate error. + */ + return( (toff_t) vips_source_length( source ) ); } static int -openin_buffer_map( thandle_t st, tdata_t *start, toff_t *len ) +openin_source_map( thandle_t st, tdata_t *start, toff_t *len ) { g_assert_not_reached(); @@ -261,7 +193,7 @@ openin_buffer_map( thandle_t st, tdata_t *start, toff_t *len ) } static void -openin_buffer_unmap( thandle_t st, tdata_t start, toff_t len ) +openin_source_unmap( thandle_t st, tdata_t start, toff_t len ) { g_assert_not_reached(); @@ -269,34 +201,35 @@ openin_buffer_unmap( thandle_t st, tdata_t start, toff_t len ) } TIFF * -vips__tiff_openin_buffer( VipsImage *image, const void *data, size_t length ) +vips__tiff_openin_source( VipsSource *source ) { - VipsTiffOpeninBuffer *buffer; TIFF *tiff; #ifdef DEBUG - printf( "vips__tiff_openin_buffer:\n" ); + printf( "vips__tiff_openin_source:\n" ); #endif /*DEBUG*/ - buffer = VIPS_NEW( image, VipsTiffOpeninBuffer ); - buffer->position = 0; - buffer->data = data; - buffer->length = length; + if( vips_source_rewind( source ) ) + return( NULL ); - if( !(tiff = TIFFClientOpen( "memory input", "rm", - (thandle_t) buffer, - openin_buffer_read, - openin_buffer_write, - openin_buffer_seek, - openin_buffer_close, - openin_buffer_size, - openin_buffer_map, - openin_buffer_unmap )) ) { - vips_error( "vips__tiff_openin_buffer", "%s", - _( "unable to open memory buffer for input" ) ); + if( !(tiff = TIFFClientOpen( "source input", "rm", + (thandle_t) source, + openin_source_read, + openin_source_write, + openin_source_seek, + openin_source_close, + openin_source_length, + openin_source_map, + openin_source_unmap )) ) { + vips_error( "vips__tiff_openin_source", "%s", + _( "unable to open source for input" ) ); return( NULL ); } + /* Unreffed on close(), see above. + */ + g_object_ref( source ); + return( tiff ); } @@ -383,7 +316,7 @@ openout_buffer_seek( thandle_t st, toff_t position, int whence ) } static toff_t -openout_buffer_size( thandle_t st ) +openout_buffer_length( thandle_t st ) { g_assert_not_reached(); @@ -435,7 +368,7 @@ vips__tiff_openout_buffer( VipsImage *image, openout_buffer_write, openout_buffer_seek, openout_buffer_close, - openout_buffer_size, + openout_buffer_length, openout_buffer_map, openout_buffer_unmap )) ) { vips_error( "vips__tiff_openout_buffer", "%s", diff --git a/libvips/foreign/tiff.h b/libvips/foreign/tiff.h index e86f8c37..56ff149c 100644 --- a/libvips/foreign/tiff.h +++ b/libvips/foreign/tiff.h @@ -37,11 +37,9 @@ extern "C" { #endif /*__cplusplus*/ -TIFF *vips__tiff_openout( const char *path, gboolean bigtiff ); -TIFF *vips__tiff_openin( const char *path ); +TIFF *vips__tiff_openin_source( VipsSource *source ); -TIFF *vips__tiff_openin_buffer( VipsImage *image, - const void *data, size_t length ); +TIFF *vips__tiff_openout( const char *path, gboolean bigtiff ); TIFF *vips__tiff_openout_buffer( VipsImage *image, gboolean bigtiff, void **out_data, size_t *out_length ); diff --git a/libvips/foreign/tiff2vips.c b/libvips/foreign/tiff2vips.c index e9586d2a..6bde6f31 100644 --- a/libvips/foreign/tiff2vips.c +++ b/libvips/foreign/tiff2vips.c @@ -189,8 +189,18 @@ * 7/6/19 * - istiff reads the first directory rather than just testing the magic * number, so it ignores more TIFF-like, but not TIFF images - * 20/7/19 - * - use "minimise" for early shutdown, rather than read Y position + * 17/10/19 + * - switch to source input + * 18/11/19 + * - support ASSOCALPHA in any alpha band + * 27/1/20 + * - read logluv images as XYZ + * 11/4/20 petoor + * - better handling of aligned reads in multipage tiffs + * 28/5/20 + * - add subifd + * 6/6/20 MathemanFlo + * - support 2 and 4 bit greyscale load */ /* @@ -221,6 +231,7 @@ */ /* +#define DEBUG_VERBOSE #define DEBUG */ @@ -252,13 +263,18 @@ typedef struct _RtiffHeader { int samples_per_pixel; int bits_per_sample; int photometric_interpretation; + int inkset; int sample_format; gboolean separate; int orientation; - gboolean premultiplied; + + /* If there's a premultiplied alpha, the band we need to + * unpremultiply with. -1 for no unpremultiplication. + */ + int alpha_band; uint16 compression; - /* Result of TIFFIsTiled(). + /* Is this directory tiled. */ gboolean tiled; @@ -287,6 +303,19 @@ typedef struct _RtiffHeader { */ uint32 read_height; tsize_t read_size; + + /* Scale factor to get absolute cd/m2 from XYZ. + */ + double stonits; + + /* Number of subifds, if any. + */ + int subifd_count; + + /* Optional IMAGEDESCRIPTION. + */ + char *image_description; + } RtiffHeader; /* Scanline-type process function. @@ -300,11 +329,12 @@ typedef void (*scanline_process_fn)( struct _Rtiff *, typedef struct _Rtiff { /* Parameters. */ - char *filename; + VipsSource *source; VipsImage *out; int page; int n; gboolean autorotate; + int subifd; /* The TIFF we read. */ @@ -488,6 +518,7 @@ static void rtiff_free( Rtiff *rtiff ) { VIPS_FREEF( TIFFClose, rtiff->tiff ); + VIPS_UNREF( rtiff->source ); } static void @@ -497,32 +528,34 @@ rtiff_close_cb( VipsObject *object, Rtiff *rtiff ) } static void -rtiff_minimise_cb( VipsObject *object, Rtiff *rtiff ) +rtiff_minimise_cb( VipsImage *image, Rtiff *rtiff ) { -#ifdef DEBUG - printf( "rtiff_minimise_cb: %p minimise\n", rtiff ); -#endif /*DEBUG*/ - - /* Close early for non-tiled TIFFs. Tiled TIFFs are read randomly, so - * the end of a loop doesn't mean the tiff won't be used again. + /* We must not minimised tiled images. These can be read from many + * threads, and this minimise handler is not inside the lock that our + * tilecache is using to guarantee single-threaded access to our + * source. */ - if( !rtiff->header.tiled ) - rtiff_free( rtiff ); + if( !rtiff->header.tiled && + rtiff->source ) + vips_source_minimise( rtiff->source ); } static Rtiff * -rtiff_new( VipsImage *out, int page, int n, gboolean autorotate ) +rtiff_new( VipsSource *source, VipsImage *out, + int page, int n, gboolean autorotate, int subifd ) { Rtiff *rtiff; if( !(rtiff = VIPS_NEW( out, Rtiff )) ) return( NULL ); - rtiff->filename = NULL; + g_object_ref( source ); + rtiff->source = source; rtiff->out = out; rtiff->page = page; rtiff->n = n; rtiff->autorotate = autorotate; + rtiff->subifd = subifd; rtiff->tiff = NULL; rtiff->n_pages = 0; rtiff->current_page = -1; @@ -535,11 +568,11 @@ rtiff_new( VipsImage *out, int page, int n, gboolean autorotate ) g_signal_connect( out, "close", G_CALLBACK( rtiff_close_cb ), rtiff ); - - g_signal_connect( out, "minimise", + g_signal_connect( out, "minimise", G_CALLBACK( rtiff_minimise_cb ), rtiff ); - if( rtiff->page < 0 || rtiff->page > 1000000 ) { + if( rtiff->page < 0 || + rtiff->page > 1000000 ) { vips_error( "tiff2vips", _( "bad page number %d" ), rtiff->page ); return( NULL ); @@ -555,6 +588,9 @@ rtiff_new( VipsImage *out, int page, int n, gboolean autorotate ) return( NULL ); } + if( !(rtiff->tiff = vips__tiff_openin_source( source )) ) + return( NULL ); + return( rtiff ); } @@ -563,13 +599,13 @@ rtiff_strip_read( Rtiff *rtiff, int strip, tdata_t buf ) { tsize_t length; -#ifdef DEBUG +#ifdef DEBUG_VERBOSE printf( "rtiff_strip_read: reading strip %d\n", strip ); -#endif /*DEBUG*/ +#endif /*DEBUG_VERBOSE*/ if( rtiff->header.read_scanlinewise ) length = TIFFReadScanline( rtiff->tiff, - buf, strip, (tsize_t) -1 ); + buf, strip, (tsample_t) 0 ); else length = TIFFReadEncodedStrip( rtiff->tiff, strip, buf, (tsize_t) -1 ); @@ -583,12 +619,32 @@ rtiff_strip_read( Rtiff *rtiff, int strip, tdata_t buf ) return( 0 ); } +/* We need to hint to libtiff what format we'd like pixels in. + */ +static void +rtiff_set_decode_format( Rtiff *rtiff ) +{ + /* Ask for YCbCr->RGB for jpg data. + */ + if( rtiff->header.compression == COMPRESSION_JPEG ) + TIFFSetField( rtiff->tiff, + TIFFTAG_JPEGCOLORMODE, JPEGCOLORMODE_RGB ); + + /* Ask for SGI LOGLUV as 3xfloat. + */ + if( rtiff->header.photometric_interpretation == + PHOTOMETRIC_LOGLUV ) + TIFFSetField( rtiff->tiff, + TIFFTAG_SGILOGDATAFMT, SGILOGDATAFMT_FLOAT ); +} + static int rtiff_set_page( Rtiff *rtiff, int page ) { if( rtiff->current_page != page ) { #ifdef DEBUG - printf( "rtiff_set_page: selecting page %d\n", page ); + printf( "rtiff_set_page: selecting page %d, subifd %d\n", + page, rtiff->subifd ); #endif /*DEBUG*/ if( !TIFFSetDirectory( rtiff->tiff, page ) ) { @@ -597,7 +653,41 @@ rtiff_set_page( Rtiff *rtiff, int page ) return( -1 ); } + if( rtiff->subifd >= 0 ) { + int subifd_count; + toff_t *subifd_offsets; + + if( !TIFFGetField( rtiff->tiff, TIFFTAG_SUBIFD, + &subifd_count, &subifd_offsets ) ) { + vips_error( "tiff2vips", + "%s", _( "no SUBIFD tag" ) ); + return( -1 ); + } + + if( subifd_count <= 0 || + rtiff->subifd >= subifd_count ) { + vips_error( "tiff2vips", + _( "subifd %d out of range, " + "only 0-%d available" ), + rtiff->subifd, + subifd_count - 1 ); + return( -1 ); + } + + if( !TIFFSetSubDirectory( rtiff->tiff, + subifd_offsets[rtiff->subifd] ) ) { + vips_error( "tiff2vips", + "%s", _( "subdirectory unreadable" ) ); + return( -1 ); + } + } + rtiff->current_page = page; + + /* These can get unset when we change directories. Make sure + * they are set again. + */ + rtiff_set_decode_format( rtiff ); } return( 0 ); @@ -613,7 +703,9 @@ rtiff_n_pages( Rtiff *rtiff ) for( n = 1; TIFFReadDirectory( rtiff->tiff ); n++ ) ; - (void) TIFFSetDirectory( rtiff->tiff, rtiff->current_page ); + /* Make sure the nest set_page() will set the directory. + */ + rtiff->current_page = -1; #ifdef DEBUG printf( "rtiff_n_pages: found %d pages\n", n ); @@ -801,6 +893,57 @@ rtiff_parse_labpack( Rtiff *rtiff, VipsImage *out ) return( 0 ); } + +/* Per-scanline process function for 8-bit VIPS_CODING_LAB to 16-bit LabS with + * alpha. + */ +static void +rtiff_lab_with_alpha_line( Rtiff *rtiff, + VipsPel *q, VipsPel *p, int n, void *dummy ) +{ + int samples_per_pixel = rtiff->header.samples_per_pixel; + + unsigned char *p1; + short *q1; + int x; + + p1 = (unsigned char *) p; + q1 = (short *) q; + for( x = 0; x < n; x++ ) { + int i; + + q1[0] = ((unsigned int) p1[0]) * 32767 / 255; + q1[1] = ((short) p1[1]) << 8; + q1[2] = ((short) p1[2]) << 8; + + for( i = 3; i < samples_per_pixel; i++ ) + q1[i] = (p1[i] << 8) + p1[i]; + + q1 += samples_per_pixel; + p1 += samples_per_pixel; + } +} + +/* Read an 8-bit LAB image with alpha bands into 16-bit LabS. + */ +static int +rtiff_parse_lab_with_alpha( Rtiff *rtiff, VipsImage *out ) +{ + if( rtiff_check_min_samples( rtiff, 4 ) || + rtiff_check_bits( rtiff, 8 ) || + rtiff_check_interpretation( rtiff, PHOTOMETRIC_CIELAB ) ) + return( -1 ); + + out->Bands = rtiff->header.samples_per_pixel; + out->BandFmt = VIPS_FORMAT_SHORT; + out->Coding = VIPS_CODING_NONE; + out->Type = VIPS_INTERPRETATION_LABS; + + rtiff->sfn = rtiff_lab_with_alpha_line; + + return( 0 ); +} + /* Per-scanline process function for LABS. */ static void @@ -816,7 +959,7 @@ rtiff_labs_line( Rtiff *rtiff, VipsPel *q, VipsPel *p, int n, void *dummy ) p1 = (unsigned short *) p; q1 = (short *) q; for( x = 0; x < n; x++ ) { - /* We use a signed int16 for L. + /* We use signed int16 for L. */ q1[0] = p1[0] >> 1; @@ -848,45 +991,108 @@ rtiff_parse_labs( Rtiff *rtiff, VipsImage *out ) return( 0 ); } -/* Per-scanline process function for 1 bit images. +/* libtiff delivers logluv as illuminant-free 0-1 XYZ in 3 x float. */ static void -rtiff_onebit_line( Rtiff *rtiff, VipsPel *q, VipsPel *p, int n, void *flg ) +rtiff_logluv_line( Rtiff *rtiff, VipsPel *q, VipsPel *p, int n, void *dummy ) { - int photometric_interpretation = - rtiff->header.photometric_interpretation; + int samples_per_pixel = rtiff->header.samples_per_pixel; - int x, i, z; - VipsPel bits; + float *p1; + float *q1; + int x; + int i; - int black = photometric_interpretation == PHOTOMETRIC_MINISBLACK ? - 0 : 255; - int white = black ^ 0xff; + p1 = (float *) p; + q1 = (float *) q; + for( x = 0; x < n; x++ ) { + q1[0] = VIPS_D65_X0 * p1[0]; + q1[1] = VIPS_D65_Y0 * p1[1]; + q1[2] = VIPS_D65_Z0 * p1[2]; - /* (sigh) how many times have I written this? - */ - x = 0; - for( i = 0; i < (n >> 3); i++ ) { - bits = (VipsPel) p[i]; + for( i = 3; i < samples_per_pixel; i++ ) + q1[i] = p1[i]; - for( z = 0; z < 8; z++ ) { - q[x] = (bits & 128) ? white : black; - bits <<= 1; - x += 1; - } - } - - /* Do last byte in line. - */ - if( n & 7 ) { - bits = p[i]; - for( z = 0; z < (n & 7); z++ ) { - q[x + z] = (bits & 128) ? white : black; - bits <<= 1; - } + q1 += samples_per_pixel; + p1 += samples_per_pixel; } } +/* LOGLUV images arrive from libtiff as float xyz. + */ +static int +rtiff_parse_logluv( Rtiff *rtiff, VipsImage *out ) +{ + if( rtiff_check_min_samples( rtiff, 3 ) || + rtiff_check_interpretation( rtiff, PHOTOMETRIC_LOGLUV ) ) + return( -1 ); + + out->Bands = rtiff->header.samples_per_pixel; + out->BandFmt = VIPS_FORMAT_FLOAT; + out->Coding = VIPS_CODING_NONE; + out->Type = VIPS_INTERPRETATION_XYZ; + + rtiff->sfn = rtiff_logluv_line; + + return( 0 ); +} + +/* Make a N-bit scanline process function. We pass in the code to expand the + * bits down the byte since this does not generalize well. + */ +#define NBIT_LINE( N, EXPAND ) \ +static void \ +rtiff_ ## N ## bit_line( Rtiff *rtiff, \ + VipsPel *q, VipsPel *p, int n, void *flg ) \ +{ \ + int photometric = rtiff->header.photometric_interpretation; \ + int mask = photometric == PHOTOMETRIC_MINISBLACK ? 0 : 0xff; \ + int bps = rtiff->header.bits_per_sample; \ + int load = 8 / bps - 1; \ + \ + int x; \ + VipsPel bits; \ + \ + /* Stop a compiler warning. \ + */ \ + bits = 0; \ + \ + for( x = 0; x < n; x++ ) { \ + if( (x & load) == 0 ) \ + /* Flip the bits for miniswhite. \ + */ \ + bits = *p++ ^ mask; \ + \ + EXPAND( q[x], bits ); \ + \ + bits <<= bps; \ + } \ +} + +/* Expand the top bit down a byte. Use a sign-extending shift. + */ +#define EXPAND1( Q, BITS ) G_STMT_START { \ + (Q) = (((signed char) (BITS & 128)) >> 7); \ +} G_STMT_END + +/* Expand the top two bits down a byte. Shift down, then expand up. + */ +#define EXPAND2( Q, BITS ) G_STMT_START { \ + VipsPel twobits = BITS >> 6; \ + VipsPel fourbits = twobits | (twobits << 2); \ + Q = fourbits | (fourbits << 4); \ +} G_STMT_END + +/* Expand the top four bits down a byte. + */ +#define EXPAND4( Q, BITS ) G_STMT_START { \ + Q = (BITS & 0xf0) | (BITS >> 4); \ +} G_STMT_END + +NBIT_LINE( 1, EXPAND1 ) +NBIT_LINE( 2, EXPAND2 ) +NBIT_LINE( 4, EXPAND4 ) + /* Read a 1-bit TIFF image. */ static int @@ -901,7 +1107,45 @@ rtiff_parse_onebit( Rtiff *rtiff, VipsImage *out ) out->Coding = VIPS_CODING_NONE; out->Type = VIPS_INTERPRETATION_B_W; - rtiff->sfn = rtiff_onebit_line; + rtiff->sfn = rtiff_1bit_line; + + return( 0 ); +} + +/* Read a 2-bit TIFF image. + */ +static int +rtiff_parse_twobit( Rtiff *rtiff, VipsImage *out ) +{ + if( rtiff_check_samples( rtiff, 1 ) || + rtiff_check_bits( rtiff, 2 ) ) + return( -1 ); + + out->Bands = 1; + out->BandFmt = VIPS_FORMAT_UCHAR; + out->Coding = VIPS_CODING_NONE; + out->Type = VIPS_INTERPRETATION_B_W; + + rtiff->sfn = rtiff_2bit_line; + + return( 0 ); +} + +/* Read a 4-bit TIFF image. + */ +static int +rtiff_parse_fourbit( Rtiff *rtiff, VipsImage *out ) +{ + if( rtiff_check_samples( rtiff, 1 ) || + rtiff_check_bits( rtiff, 4 ) ) + return( -1 ); + + out->Bands = 1; + out->BandFmt = VIPS_FORMAT_UCHAR; + out->Coding = VIPS_CODING_NONE; + out->Type = VIPS_INTERPRETATION_B_W; + + rtiff->sfn = rtiff_4bit_line; return( 0 ); } @@ -1273,6 +1517,7 @@ rtiff_parse_copy( Rtiff *rtiff, VipsImage *out ) int samples_per_pixel = rtiff->header.samples_per_pixel; int photometric_interpretation = rtiff->header.photometric_interpretation; + int inkset = rtiff->header.inkset; if( rtiff_non_fractional( rtiff ) ) return( -1 ); @@ -1296,14 +1541,15 @@ rtiff_parse_copy( Rtiff *rtiff, VipsImage *out ) else out->Type = VIPS_INTERPRETATION_sRGB; } - - if( samples_per_pixel >= 3 && + else if( samples_per_pixel >= 3 && photometric_interpretation == PHOTOMETRIC_CIELAB ) out->Type = VIPS_INTERPRETATION_LAB; - - if( samples_per_pixel >= 4 && - photometric_interpretation == PHOTOMETRIC_SEPARATED ) + else if( photometric_interpretation == PHOTOMETRIC_SEPARATED && + samples_per_pixel >= 4 && + inkset == INKSET_CMYK ) out->Type = VIPS_INTERPRETATION_CMYK; + else + out->Type = VIPS_INTERPRETATION_MULTIBAND; rtiff->sfn = rtiff_memcpy_line; rtiff->client = out; @@ -1328,18 +1574,31 @@ rtiff_pick_reader( Rtiff *rtiff ) int bits_per_sample = rtiff->header.bits_per_sample; int photometric_interpretation = rtiff->header.photometric_interpretation; + int samples_per_pixel = rtiff->header.samples_per_pixel; if( photometric_interpretation == PHOTOMETRIC_CIELAB ) { - if( bits_per_sample == 8 ) - return( rtiff_parse_labpack ); + if( bits_per_sample == 8 ) { + if( samples_per_pixel > 3 ) + return( rtiff_parse_lab_with_alpha ); + else + return( rtiff_parse_labpack ); + } if( bits_per_sample == 16 ) return( rtiff_parse_labs ); } + if( photometric_interpretation == PHOTOMETRIC_LOGLUV ) + return( rtiff_parse_logluv ); + if( photometric_interpretation == PHOTOMETRIC_MINISWHITE || photometric_interpretation == PHOTOMETRIC_MINISBLACK ) { - if( bits_per_sample == 1 ) - return( rtiff_parse_onebit ); + + if( bits_per_sample == 1) + return ( rtiff_parse_onebit ); + else if ( bits_per_sample == 2 ) + return ( rtiff_parse_twobit); + else if ( bits_per_sample == 4 ) + return ( rtiff_parse_fourbit); else return( rtiff_parse_greyscale ); } @@ -1359,20 +1618,25 @@ rtiff_set_header( Rtiff *rtiff, VipsImage *out ) uint32 data_length; void *data; - /* Request YCbCr expansion. libtiff complains if you do this for - * non-jpg images. - */ - if( rtiff->header.compression == COMPRESSION_JPEG ) - TIFFSetField( rtiff->tiff, - TIFFTAG_JPEGCOLORMODE, JPEGCOLORMODE_RGB ); + rtiff_set_decode_format( rtiff ); + + if( rtiff->header.photometric_interpretation == PHOTOMETRIC_LOGLUV ) + vips_image_set_double( out, "stonits", rtiff->header.stonits ); out->Xsize = rtiff->header.width; out->Ysize = rtiff->header.height * rtiff->n; + VIPS_SETSTR( out->filename, + vips_connection_filename( VIPS_CONNECTION( rtiff->source ) ) ); + if( rtiff->n > 1 ) vips_image_set_int( out, VIPS_META_PAGE_HEIGHT, rtiff->header.height ); + if( rtiff->header.subifd_count > 0 ) + vips_image_set_int( out, + VIPS_META_N_SUBIFDS, rtiff->header.subifd_count ); + vips_image_set_int( out, VIPS_META_N_PAGES, rtiff->n_pages ); /* Even though we could end up serving tiled data, always hint @@ -1381,17 +1645,6 @@ rtiff_set_header( Rtiff *rtiff, VipsImage *out ) */ vips_image_pipelinev( out, VIPS_DEMAND_STYLE_THINSTRIP, NULL ); -#ifdef DEBUG - printf( "rtiff_set_header: header.samples_per_pixel = %d\n", - rtiff->header.samples_per_pixel ); - printf( "rtiff_set_header: header.bits_per_sample = %d\n", - rtiff->header.bits_per_sample ); - printf( "rtiff_set_header: header.sample_format = %d\n", - rtiff->header.sample_format ); - printf( "rtiff_set_header: header.orientation = %d\n", - rtiff->header.orientation ); -#endif /*DEBUG*/ - /* We have a range of output paths. Look at the tiff header and try to * route the input image to the best output path. */ @@ -1401,18 +1654,16 @@ rtiff_set_header( Rtiff *rtiff, VipsImage *out ) /* Read any ICC profile. */ if( TIFFGetField( rtiff->tiff, - TIFFTAG_ICCPROFILE, &data_length, &data ) ) { + TIFFTAG_ICCPROFILE, &data_length, &data ) ) vips_image_set_blob_copy( out, VIPS_META_ICC_NAME, data, data_length ); - } /* Read any XMP metadata. */ if( TIFFGetField( rtiff->tiff, - TIFFTAG_XMLPACKET, &data_length, &data ) ) { + TIFFTAG_XMLPACKET, &data_length, &data ) ) vips_image_set_blob_copy( out, VIPS_META_XMP_NAME, data, data_length ); - } /* Read any IPTC metadata. */ @@ -1430,20 +1681,13 @@ rtiff_set_header( Rtiff *rtiff, VipsImage *out ) /* Read any photoshop metadata. */ if( TIFFGetField( rtiff->tiff, - TIFFTAG_PHOTOSHOP, &data_length, &data ) ) { + TIFFTAG_PHOTOSHOP, &data_length, &data ) ) vips_image_set_blob_copy( out, VIPS_META_PHOTOSHOP_NAME, data, data_length ); - } - /* IMAGEDESCRIPTION often has useful metadata. - */ - if( TIFFGetField( rtiff->tiff, TIFFTAG_IMAGEDESCRIPTION, &data ) ) { - /* libtiff makes sure that data is null-terminated and contains - * no embedded null characters. - */ - vips_image_set_string( out, - VIPS_META_IMAGEDESCRIPTION, (char *) data ); - } + if( rtiff->header.image_description ) + vips_image_set_string( out, VIPS_META_IMAGEDESCRIPTION, + rtiff->header.image_description ); if( get_resolution( rtiff->tiff, out ) ) return( -1 ); @@ -1475,6 +1719,10 @@ rtiff_seq_start( VipsImage *out, void *a, void *b ) static int rtiff_read_tile( Rtiff *rtiff, tdata_t *buf, int x, int y ) { +#ifdef DEBUG_VERBOSE + printf( "rtiff_read_tile: x = %d, y = %d\n", x, y ); +#endif /*DEBUG_VERBOSE*/ + if( TIFFReadTile( rtiff->tiff, buf, x, y, 0, 0 ) < 0 ) { vips_foreign_load_invalidate( rtiff->out ); return( -1 ); @@ -1489,10 +1737,14 @@ rtiff_read_tile( Rtiff *rtiff, tdata_t *buf, int x, int y ) * region. */ static int -rtiff_fill_region_aligned( VipsRegion *out, void *seq, void *a, void *b ) +rtiff_fill_region_aligned( VipsRegion *out, + void *seq, void *a, void *b, gboolean *stop ) { Rtiff *rtiff = (Rtiff *) a; VipsRect *r = &out->valid; + int page_height = rtiff->header.height; + int page_no = r->top / page_height; + int page_y = r->top % page_height; g_assert( (r->left % rtiff->header.tile_width) == 0 ); g_assert( (r->top % rtiff->header.tile_height) == 0 ); @@ -1500,23 +1752,17 @@ rtiff_fill_region_aligned( VipsRegion *out, void *seq, void *a, void *b ) g_assert( r->height == rtiff->header.tile_height ); g_assert( VIPS_REGION_LSKIP( out ) == VIPS_REGION_SIZEOF_LINE( out ) ); -#ifdef DEBUG - printf( "rtiff_fill_region_aligned: left = %d, top = %d\n", - r->left, r->top ); -#endif /*DEBUG*/ - - VIPS_GATE_START( "rtiff_fill_region_aligned: work" ); +#ifdef DEBUG_VERBOSE + printf( "rtiff_fill_region_aligned:\n" ); +#endif /*DEBUG_VERBOSE*/ /* Read that tile directly into the vips tile. */ - if( rtiff_read_tile( rtiff, - (tdata_t *) VIPS_REGION_ADDR( out, r->left, r->top ), - r->left, r->top ) ) { - VIPS_GATE_STOP( "rtiff_fill_region_aligned: work" ); + if( rtiff_set_page( rtiff, rtiff->page + page_no ) || + rtiff_read_tile( rtiff, + (tdata_t *) VIPS_REGION_ADDR( out, r->left, r->top ), + r->left, page_y ) ) return( -1 ); - } - - VIPS_GATE_STOP( "rtiff_fill_region_aligned: work" ); return( 0 ); } @@ -1524,30 +1770,22 @@ rtiff_fill_region_aligned( VipsRegion *out, void *seq, void *a, void *b ) /* Loop over the output region, painting in tiles from the file. */ static int -rtiff_fill_region( VipsRegion *out, +rtiff_fill_region_unaligned( VipsRegion *out, void *seq, void *a, void *b, gboolean *stop ) { tdata_t *buf = (tdata_t *) seq; Rtiff *rtiff = (Rtiff *) a; int tile_width = rtiff->header.tile_width; int tile_height = rtiff->header.tile_height; + int page_height = rtiff->header.height; int tile_row_size = rtiff->header.tile_row_size; VipsRect *r = &out->valid; int x, y, z; - /* Special case: we are filling a single tile exactly sized to match - * the tiff tile and we have no repacking to do for this format. - */ - if( rtiff->memcpy && - r->left % tile_width == 0 && - r->top % tile_height == 0 && - r->width == tile_width && - r->height == tile_height && - VIPS_REGION_LSKIP( out ) == VIPS_REGION_SIZEOF_LINE( out ) ) - return( rtiff_fill_region_aligned( out, seq, a, b ) ); - - VIPS_GATE_START( "rtiff_fill_region: work" ); +#ifdef DEBUG_VERBOSE + printf( "rtiff_fill_region_unaligned:\n" ); +#endif /*DEBUG_VERBOSE*/ y = 0; while( y < r->height ) { @@ -1556,7 +1794,7 @@ rtiff_fill_region( VipsRegion *out, /* Not necessary, but it stops static analyzers complaining * about a used-before-set. */ - tile.height = 0; + hit.height = 0; x = 0; while( x < r->width ) { @@ -1564,8 +1802,8 @@ rtiff_fill_region( VipsRegion *out, * file page number ... add the number of the start * page to get that. */ - int page_no = (r->top + y) / rtiff->header.height; - int page_y = (r->top + y) % rtiff->header.height; + int page_no = (r->top + y) / page_height; + int page_y = (r->top + y) % page_height; /* Coordinate of the tile on this page that xy falls in. */ @@ -1573,10 +1811,8 @@ rtiff_fill_region( VipsRegion *out, int ys = (page_y / tile_height) * tile_height; if( rtiff_set_page( rtiff, rtiff->page + page_no ) || - rtiff_read_tile( rtiff, buf, xs, ys ) ) { - VIPS_GATE_STOP( "rtiff_fill_region: work" ); + rtiff_read_tile( rtiff, buf, xs, ys ) ) return( -1 ); - } /* Position of tile on the page. */ @@ -1595,7 +1831,7 @@ rtiff_fill_region( VipsRegion *out, /* To image coordinates. */ - tile.top += page_no * rtiff->header.height; + tile.top += page_no * page_height; /* And clip again by this region. */ @@ -1622,10 +1858,64 @@ rtiff_fill_region( VipsRegion *out, q, p, hit.width, rtiff->client ); } - x += tile.width; + x += hit.width; } - y += tile.height; + /* This will be the same for all tiles in the row we've just + * done. + */ + y += hit.height; + } + + return( 0 ); +} + +/* Loop over the output region, painting in tiles from the file. + */ +static int +rtiff_fill_region( VipsRegion *out, + void *seq, void *a, void *b, gboolean *stop ) +{ + Rtiff *rtiff = (Rtiff *) a; + int tile_width = rtiff->header.tile_width; + int tile_height = rtiff->header.tile_height; + int page_width = rtiff->header.width; + int page_height = rtiff->header.height; + VipsRect *r = &out->valid; + int page_no = r->top / page_height; + int page_y = r->top % page_height; + + VipsGenerateFn generate; + +#ifdef DEBUG_VERBOSE + printf( "rtiff_fill_region: left = %d, top = %d, " + "width = %d, height = %d\n", + r->left, r->top, r->width, r->height ); +#endif /*DEBUG_VERBOSE*/ + + /* Special case: we are filling a single cache tile exactly sized to + * match the tiff tile, and we have no repacking to do for this format. + * + * If we are not on the first page, pages must be a multiple of the + * tile size of we'll miss alignment. + */ + if( (page_no == 0 || page_height % tile_height == 0) && + r->left % tile_width == 0 && + r->top % tile_height == 0 && + r->width == tile_width && + r->height == tile_height && + r->left + tile_width <= page_width && + page_y + tile_height <= page_height && + VIPS_REGION_LSKIP( out ) == VIPS_REGION_SIZEOF_LINE( out ) ) + generate = rtiff_fill_region_aligned; + else + generate = rtiff_fill_region_unaligned; + + VIPS_GATE_START( "rtiff_fill_region: work" ); + + if( generate( out, seq, a, b, stop ) ) { + VIPS_GATE_STOP( "rtiff_fill_region: work" ); + return( -1 ); } VIPS_GATE_STOP( "rtiff_fill_region: work" ); @@ -1641,56 +1931,18 @@ rtiff_seq_stop( void *seq, void *a, void *b ) return( 0 ); } -/* Auto-rotate handling. - */ -static int -rtiff_autorotate( Rtiff *rtiff, VipsImage *in, VipsImage **out ) -{ - VipsAngle angle = vips_autorot_get_angle( in ); - - if( rtiff->autorotate && - angle != VIPS_ANGLE_D0 ) { - /* Need to copy to memory or disc, we have to stay seq. - */ - const guint64 image_size = VIPS_IMAGE_SIZEOF_IMAGE( in ); - const guint64 disc_threshold = vips_get_disc_threshold(); - - VipsImage *x; - - if( image_size > disc_threshold ) - x = vips_image_new_temp_file( "%s.v" ); - else - x = vips_image_new_memory(); - - if( vips_image_write( in, x ) || - vips_rot( x, out, angle, NULL ) ) { - g_object_unref( x ); - return( -1 ); - } - g_object_unref( x ); - - /* We must remove the tag to prevent accidental - * double rotations. - */ - vips_autorot_remove_angle( *out ); - } - else { - *out = in; - g_object_ref( in ); - } - - return( 0 ); -} - /* Unpremultiply associative alpha, if any. */ static int rtiff_unpremultiply( Rtiff *rtiff, VipsImage *in, VipsImage **out ) { - if( rtiff->header.premultiplied ) { + if( rtiff->header.alpha_band != -1 ) { VipsImage *x; - if( vips_unpremultiply( in, &x, NULL ) || + if( + vips_unpremultiply( in, &x, + "alpha_band", rtiff->header.alpha_band, + NULL ) || vips_cast( x, out, in->BandFmt, NULL ) ) { g_object_unref( x ); return( -1 ); @@ -1716,6 +1968,8 @@ rtiff_read_tilewise( Rtiff *rtiff, VipsImage *out ) VipsImage **t = (VipsImage **) vips_object_local_array( VIPS_OBJECT( out ), 4 ); + VipsImage *in; + #ifdef DEBUG printf( "tiff2vips: rtiff_read_tilewise\n" ); #endif /*DEBUG*/ @@ -1755,21 +2009,31 @@ rtiff_read_tilewise( Rtiff *rtiff, VipsImage *out ) */ vips_image_pipelinev( t[0], VIPS_DEMAND_STYLE_THINSTRIP, NULL ); - if( vips_image_generate( t[0], - rtiff_seq_start, rtiff_fill_region, rtiff_seq_stop, - rtiff, NULL ) ) - return( -1 ); - - /* Copy to out, adding a cache. Enough tiles for two complete rows. + /* Generate to out, adding a cache. Enough tiles for two complete rows. */ - if( vips_tilecache( t[0], &t[1], - "tile_width", tile_width, - "tile_height", tile_height, - "max_tiles", 2 * (1 + t[0]->Xsize / tile_width), - NULL ) || - rtiff_autorotate( rtiff, t[1], &t[2] ) || - rtiff_unpremultiply( rtiff, t[2], &t[3] ) || - vips_image_write( t[3], out ) ) + if( + vips_image_generate( t[0], + rtiff_seq_start, rtiff_fill_region, rtiff_seq_stop, + rtiff, NULL ) || + vips_tilecache( t[0], &t[1], + "tile_width", tile_width, + "tile_height", tile_height, + "max_tiles", 2 * (1 + t[0]->Xsize / tile_width), + NULL ) || + rtiff_unpremultiply( rtiff, t[1], &t[2] ) ) + return( -1 ); + in = t[2]; + + /* Only do this if we have to. + */ + if( rtiff->autorotate && + vips_image_get_orientation( in ) != 1 ) { + if( vips_autorot( in, &t[3], NULL ) ) + return( -1 ); + in = t[3]; + } + + if( vips_image_write( in, out ) ) return( -1 ); return( 0 ); @@ -1831,6 +2095,7 @@ static int rtiff_stripwise_generate( VipsRegion *or, void *seq, void *a, void *b, gboolean *stop ) { + VipsImage *out = or->im; Rtiff *rtiff = (Rtiff *) a; int read_height = rtiff->header.read_height; int page_height = rtiff->header.height; @@ -1839,11 +2104,11 @@ rtiff_stripwise_generate( VipsRegion *or, int y; -#ifdef DEBUG +#ifdef DEBUG_VERBOSE printf( "rtiff_stripwise_generate: top = %d, height = %d\n", r->top, r->height ); printf( "rtiff_stripwise_generate: y_top = %d\n", rtiff->y_pos ); -#endif /*DEBUG*/ +#endif /*DEBUG_VERBOSE*/ /* We're inside a tilecache where tiles are the full image width, so * this should always be true. @@ -1867,7 +2132,8 @@ rtiff_stripwise_generate( VipsRegion *or, */ if( r->top != rtiff->y_pos ) { vips_error( "tiff2vips", - _( "out of order read at line %d" ), rtiff->y_pos ); + _( "out of order read -- at line %d, " + "but line %d requested" ), rtiff->y_pos, r->top ); return( -1 ); } @@ -1893,17 +2159,17 @@ rtiff_stripwise_generate( VipsRegion *or, */ image.left = 0; image.top = 0; - image.width = rtiff->out->Xsize; - image.height = rtiff->out->Ysize; + image.width = out->Xsize; + image.height = out->Ysize; page.left = 0; page.top = page_height * ((r->top + y) / page_height); - page.width = rtiff->out->Xsize; + page.width = out->Xsize; page.height = page_height; strip.left = 0; strip.top = page.top + strip_no * read_height; - strip.width = rtiff->out->Xsize; + strip.width = out->Xsize; strip.height = read_height; /* Clip strip against page and image ... the final strip will @@ -1971,15 +2237,6 @@ rtiff_stripwise_generate( VipsRegion *or, rtiff->y_pos += hit.height; } - /* Shut down the input file as soon as we can. - */ - if( rtiff->y_pos >= or->im->Ysize ) { -#ifdef DEBUG - printf( "rtiff_stripwise_generate: early shutdown\n" ); -#endif /*DEBUG*/ - rtiff_free( rtiff ); - } - VIPS_GATE_STOP( "rtiff_stripwise_generate: work" ); return( 0 ); @@ -1997,6 +2254,8 @@ rtiff_read_stripwise( Rtiff *rtiff, VipsImage *out ) VipsImage **t = (VipsImage **) vips_object_local_array( VIPS_OBJECT( out ), 4 ); + VipsImage *in; + #ifdef DEBUG printf( "tiff2vips: rtiff_read_stripwise\n" ); #endif /*DEBUG*/ @@ -2007,23 +2266,10 @@ rtiff_read_stripwise( Rtiff *rtiff, VipsImage *out ) vips_image_pipelinev( t[0], VIPS_DEMAND_STYLE_THINSTRIP, NULL ); -#ifdef DEBUG - printf( "rtiff_read_stripwise: header.rows_per_strip = %u\n", - rtiff->header.rows_per_strip ); - printf( "rtiff_read_stripwise: header.strip_size = %zd\n", - rtiff->header.strip_size ); - printf( "rtiff_read_stripwise: header.number_of_strips = %d\n", - rtiff->header.number_of_strips ); - printf( "rtiff_read_stripwise: header.read_height = %u\n", - rtiff->header.read_height ); - printf( "rtiff_read_stripwise: header.read_size = %zd\n", - rtiff->header.read_size ); -#endif /*DEBUG*/ - /* Double check: in memcpy mode, the vips linesize should exactly * match the tiff line size. */ - if( rtiff->memcpy ) { + if( rtiff->memcpy ) { size_t vips_line_size; /* Lines are smaller in plane-separated mode. @@ -2085,9 +2331,20 @@ rtiff_read_stripwise( Rtiff *rtiff, VipsImage *out ) vips_sequential( t[0], &t[1], "tile_height", rtiff->header.read_height, NULL ) || - rtiff_autorotate( rtiff, t[1], &t[2] ) || - rtiff_unpremultiply( rtiff, t[2], &t[3] ) || - vips_image_write( t[3], out ) ) + rtiff_unpremultiply( rtiff, t[1], &t[2] ) ) + return( -1 ); + in = t[2]; + + /* Only do this if we have to. + */ + if( rtiff->autorotate && + vips_image_get_orientation( in ) != 1 ) { + if( vips_autorot( in, &t[3], NULL ) ) + return( -1 ); + in = t[3]; + } + + if( vips_image_write( in, out ) ) return( -1 ); return( 0 ); @@ -2100,27 +2357,36 @@ rtiff_header_read( Rtiff *rtiff, RtiffHeader *header ) { uint16 extra_samples_count; uint16 *extra_samples_types; + toff_t *subifd_offsets; + char *image_description; - if( !tfget32( rtiff->tiff, TIFFTAG_IMAGEWIDTH, &header->width ) || - !tfget32( rtiff->tiff, TIFFTAG_IMAGELENGTH, &header->height ) || - !tfget16( rtiff->tiff, - TIFFTAG_SAMPLESPERPIXEL, &header->samples_per_pixel ) || - !tfget16( rtiff->tiff, - TIFFTAG_BITSPERSAMPLE, &header->bits_per_sample ) || - !tfget16( rtiff->tiff, - TIFFTAG_PHOTOMETRIC, - &header->photometric_interpretation ) ) + if( !tfget32( rtiff->tiff, TIFFTAG_IMAGEWIDTH, + &header->width ) || + !tfget32( rtiff->tiff, TIFFTAG_IMAGELENGTH, + &header->height ) || + !tfget16( rtiff->tiff, TIFFTAG_SAMPLESPERPIXEL, + &header->samples_per_pixel ) || + !tfget16( rtiff->tiff, TIFFTAG_BITSPERSAMPLE, + &header->bits_per_sample ) || + !tfget16( rtiff->tiff, TIFFTAG_PHOTOMETRIC, + &header->photometric_interpretation ) || + !tfget16( rtiff->tiff, TIFFTAG_INKSET, + &header->inkset ) ) return( -1 ); TIFFGetFieldDefaulted( rtiff->tiff, TIFFTAG_COMPRESSION, &header->compression ); - if( header->compression == COMPRESSION_JPEG ) - /* We want to always expand subsampled YCBCR images to full - * RGB. - */ - TIFFSetField( rtiff->tiff, - TIFFTAG_JPEGCOLORMODE, JPEGCOLORMODE_RGB ); - else if( header->photometric_interpretation == PHOTOMETRIC_YCBCR ) { + + /* We must set this here since it'll change the value of scanline_size. + */ + rtiff_set_decode_format( rtiff ); + + /* Request YCbCr expansion. libtiff complains if you do this for + * non-jpg images. We must set this here since it changes the result + * of scanline_size. + */ + if( header->compression != COMPRESSION_JPEG && + header->photometric_interpretation == PHOTOMETRIC_YCBCR ) { /* We rely on the jpg decompressor to upsample chroma * subsampled images. If there is chroma subsampling but * no jpg compression, we have to give up. @@ -2139,6 +2405,20 @@ rtiff_header_read( Rtiff *rtiff, RtiffHeader *header ) } } + if( header->photometric_interpretation == PHOTOMETRIC_LOGLUV ) { + if( header->compression != COMPRESSION_SGILOG && + header->compression != COMPRESSION_SGILOG24 ) { + vips_error( "tiff2vips", + "%s", _( "not SGI-compressed LOGLUV" ) ); + return( -1 ); + } + } + + /* For logluv, the calibration factor to get to absolute luminance. + */ + if( !TIFFGetField( rtiff->tiff, TIFFTAG_STONITS, &header->stonits ) ) + header->stonits = 1.0; + /* Arbitrary sanity-checking limits. */ if( header->width <= 0 || @@ -2172,10 +2452,42 @@ rtiff_header_read( Rtiff *rtiff, RtiffHeader *header ) header->separate = TRUE; } + /* Stays zero if there's no SUBIFD. + */ + TIFFGetField( rtiff->tiff, TIFFTAG_SUBIFD, + &header->subifd_count, &subifd_offsets ); + + /* IMAGEDESCRIPTION often has useful metadata. libtiff makes sure + * that data is null-terminated and contains no embedded null + * characters. + */ + if( TIFFGetField( rtiff->tiff, + TIFFTAG_IMAGEDESCRIPTION, &image_description ) ) + header->image_description = + vips_strdup( VIPS_OBJECT( rtiff->out ), + image_description ); + /* Tiles and strip images have slightly different fields. */ header->tiled = TIFFIsTiled( rtiff->tiff ); +#ifdef DEBUG + printf( "rtiff_header_read: header.width = %d\n", + header->width ); + printf( "rtiff_header_read: header.height = %d\n", + header->height ); + printf( "rtiff_header_read: header.samples_per_pixel = %d\n", + header->samples_per_pixel ); + printf( "rtiff_header_read: header.bits_per_sample = %d\n", + header->bits_per_sample ); + printf( "rtiff_header_read: header.sample_format = %d\n", + header->sample_format ); + printf( "rtiff_header_read: header.orientation = %d\n", + header->orientation ); + printf( "rtiff_header_read: header.tiled = %d\n", + header->tiled ); +#endif /*DEBUG*/ + if( header->tiled ) { if( !tfget32( rtiff->tiff, TIFFTAG_TILEWIDTH, &header->tile_width ) || @@ -2183,6 +2495,13 @@ rtiff_header_read( Rtiff *rtiff, RtiffHeader *header ) TIFFTAG_TILELENGTH, &header->tile_height ) ) return( -1 ); +#ifdef DEBUG + printf( "rtiff_header_read: header.tile_width = %d\n", + header->tile_width ); + printf( "rtiff_header_read: header.tile_height = %d\n", + header->tile_height ); +#endif /*DEBUG*/ + /* Arbitrary sanity-checking limits. */ if( header->tile_width <= 0 || @@ -2197,6 +2516,13 @@ rtiff_header_read( Rtiff *rtiff, RtiffHeader *header ) header->tile_size = TIFFTileSize( rtiff->tiff ); header->tile_row_size = TIFFTileRowSize( rtiff->tiff ); +#ifdef DEBUG + printf( "rtiff_header_read: header.tile_size = %zd\n", + header->tile_size ); + printf( "rtiff_header_read: header.tile_row_size = %zd\n", + header->tile_row_size ); +#endif /*DEBUG*/ + /* Fuzzed TIFFs can give crazy values for tile_size. Sanity * check at 100mb per tile. */ @@ -2225,6 +2551,17 @@ rtiff_header_read( Rtiff *rtiff, RtiffHeader *header ) header->scanline_size = TIFFScanlineSize( rtiff->tiff ); header->number_of_strips = TIFFNumberOfStrips( rtiff->tiff ); +#ifdef DEBUG + printf( "rtiff_header_read: header.rows_per_strip = %d\n", + header->rows_per_strip ); + printf( "rtiff_header_read: header.strip_size = %zd\n", + header->strip_size ); + printf( "rtiff_header_read: header.scanline_size = %zd\n", + header->scanline_size ); + printf( "rtiff_header_read: header.number_of_strips = %d\n", + header->number_of_strips ); +#endif /*DEBUG*/ + /* libtiff has two strip-wise readers. TIFFReadEncodedStrip() * decompresses an entire strip to memory. It's fast, but it * will need a lot of ram if the strip is large. @@ -2263,6 +2600,15 @@ rtiff_header_read( Rtiff *rtiff, RtiffHeader *header ) header->read_size = header->strip_size; } +#ifdef DEBUG + printf( "rtiff_header_read: header.read_scanlinewise = %d\n", + header->read_scanlinewise ); + printf( "rtiff_header_read: header.read_height = %d\n", + header->read_height ); + printf( "rtiff_header_read: header.read_size = %zd\n", + header->read_size ); +#endif /*DEBUG*/ + /* Stop some compiler warnings. */ header->tile_width = 0; @@ -2273,8 +2619,25 @@ rtiff_header_read( Rtiff *rtiff, RtiffHeader *header ) TIFFGetFieldDefaulted( rtiff->tiff, TIFFTAG_EXTRASAMPLES, &extra_samples_count, &extra_samples_types ); - header->premultiplied = extra_samples_count > 0 && - extra_samples_types[0] == EXTRASAMPLE_ASSOCALPHA; + + header->alpha_band = -1; + if( extra_samples_count > 0 ) { + /* There must be exactly one band which is + * EXTRASAMPLE_ASSOCALPHA. Note which one it is so we can + * unpremultiply with the right channel. + */ + int i; + + for( i = 0; i < extra_samples_count; i++ ) + if( extra_samples_types[i] == EXTRASAMPLE_ASSOCALPHA ) { + if( header->alpha_band != -1 ) + g_warning( "%s", _( "more than one " + "alpha -- ignoring" ) ); + + header->alpha_band = header->samples_per_pixel - + extra_samples_count + i; + } + } return( 0 ); } @@ -2313,7 +2676,8 @@ static int rtiff_header_read_all( Rtiff *rtiff ) { #ifdef DEBUG - printf( "tiff2vips: reading header for page %d ...\n", rtiff->page ); + printf( "rtiff_header_read_all: " + "reading header for page %d ...\n", rtiff->page ); #endif /*DEBUG*/ if( rtiff_set_page( rtiff, rtiff->page ) || @@ -2335,7 +2699,8 @@ rtiff_header_read_all( Rtiff *rtiff ) RtiffHeader header; #ifdef DEBUG - printf( "tiff2vips: verifying header for page %d ...\n", + printf( "rtiff_header_read_all: " + "verifying header for page %d ...\n", rtiff->page + i ); #endif /*DEBUG*/ @@ -2355,126 +2720,17 @@ rtiff_header_read_all( Rtiff *rtiff ) return( 0 ); } -static Rtiff * -rtiff_new_filename( const char *filename, VipsImage *out, - int page, int n, gboolean autorotate ) -{ - Rtiff *rtiff; - - if( !(rtiff = rtiff_new( out, page, n, autorotate )) || - !(rtiff->tiff = vips__tiff_openin( filename )) || - rtiff_header_read_all( rtiff ) ) - return( NULL ); - - rtiff->filename = vips_strdup( VIPS_OBJECT( out ), filename ); - - return( rtiff ); -} - -static Rtiff * -rtiff_new_buffer( const void *buf, size_t len, VipsImage *out, - int page, int n, gboolean autorotate ) -{ - Rtiff *rtiff; - - if( !(rtiff = rtiff_new( out, page, n, autorotate )) || - !(rtiff->tiff = vips__tiff_openin_buffer( out, buf, len )) || - rtiff_header_read_all( rtiff ) ) - return( NULL ); - - return( rtiff ); -} - -/* For istiffpyramid(), see vips_thumbnail_get_tiff_pyramid(). - */ - -int -vips__tiff_read( const char *filename, VipsImage *out, - int page, int n, gboolean autorotate ) -{ - Rtiff *rtiff; - -#ifdef DEBUG - printf( "tiff2vips: libtiff version is \"%s\"\n", TIFFGetVersion() ); - printf( "tiff2vips: libtiff starting for %s\n", filename ); -#endif /*DEBUG*/ - - vips__tiff_init(); - - if( !(rtiff = rtiff_new_filename( filename, out, - page, n, autorotate )) ) - return( -1 ); - - if( rtiff->header.tiled ) { - if( rtiff_read_tilewise( rtiff, out ) ) - return( -1 ); - } - else { - if( rtiff_read_stripwise( rtiff, out ) ) - return( -1 ); - } - - return( 0 ); -} - -/* On a header-only read, we can just swap width/height if orientation is 6 or - * 8. - */ -static void -vips__tiff_read_header_orientation( Rtiff *rtiff, VipsImage *out ) -{ - int orientation; - - if( rtiff->autorotate && - vips_image_get_typeof( out, VIPS_META_ORIENTATION ) && - !vips_image_get_int( out, - VIPS_META_ORIENTATION, &orientation ) ) { - if( orientation == 3 || - orientation == 6 ) - VIPS_SWAP( int, out->Xsize, out->Ysize ); - - /* We must remove VIPS_META_ORIENTATION to prevent accidental - * double rotations. - */ - vips_image_remove( out, VIPS_META_ORIENTATION ); - } -} - -int -vips__tiff_read_header( const char *filename, VipsImage *out, - int page, int n, gboolean autorotate ) -{ - Rtiff *rtiff; - - vips__tiff_init(); - - if( !(rtiff = - rtiff_new_filename( filename, out, page, n, autorotate )) ) - return( -1 ); - - if( rtiff_set_header( rtiff, out ) ) - return( -1 ); - - vips__tiff_read_header_orientation( rtiff, out ); - - /* Just a header read: we can free the tiff read early and save an fd. - */ - rtiff_free( rtiff ); - - return( 0 ); -} - typedef gboolean (*TiffPropertyFn)( TIFF *tif ); static gboolean -vips__testtiff( const char *filename, TiffPropertyFn fn ) +vips__testtiff_source( VipsSource *source, TiffPropertyFn fn ) { TIFF *tif; gboolean property; vips__tiff_init(); - if( !(tif = vips__tiff_openin( filename )) ) { + if( !(tif = vips__tiff_openin_source( source )) ) { vips_error_clear(); return( FALSE ); } @@ -2487,85 +2743,60 @@ vips__testtiff( const char *filename, TiffPropertyFn fn ) } gboolean -vips__testtiff_buffer( const void *buf, size_t len, TiffPropertyFn fn ) +vips__istiff_source( VipsSource *source ) { - VipsImage *im; - TIFF *tif; - gboolean property; - - vips__tiff_init(); - - im = vips_image_new(); - - if( !(tif = vips__tiff_openin_buffer( im, buf, len )) ) { - g_object_unref( im ); - vips_error_clear(); - return( FALSE ); - } - - property = fn ? fn( tif ) : TRUE; - - TIFFClose( tif ); - g_object_unref( im ); - - return( property ); + return( vips__testtiff_source( source, NULL ) ); } gboolean -vips__istifftiled( const char *filename ) +vips__istifftiled_source( VipsSource *source ) { - return( vips__testtiff( filename, TIFFIsTiled ) ); -} - -/* We test for TIFF by trying to read the first directory. We could just test - * the magic number, but many formats (eg. ARW) use a TIFF-like container and - * we don't want to open those with vips tiffload. - */ -gboolean -vips__istiff( const char *filename ) -{ - return( vips__testtiff( filename, NULL ) ); -} - -gboolean -vips__istiff_buffer( const void *buf, size_t len ) -{ - return( vips__testtiff_buffer( buf, len, NULL ) ); + return( vips__testtiff_source( source, TIFFIsTiled ) ); } int -vips__tiff_read_header_buffer( const void *buf, size_t len, VipsImage *out, - int page, int n, gboolean autorotate ) +vips__tiff_read_header_source( VipsSource *source, VipsImage *out, + int page, int n, gboolean autorotate, int subifd ) { Rtiff *rtiff; vips__tiff_init(); - if( !(rtiff = rtiff_new_buffer( buf, len, out, page, n, autorotate )) ) + if( !(rtiff = rtiff_new( source, out, page, n, autorotate, subifd )) || + rtiff_header_read_all( rtiff ) ) return( -1 ); if( rtiff_set_header( rtiff, out ) ) return( -1 ); - vips__tiff_read_header_orientation( rtiff, out ); + if( rtiff->autorotate && + vips_image_get_orientation_swap( out ) ) { + VIPS_SWAP( int, out->Xsize, out->Ysize ); + vips_autorot_remove_angle( out ); + } + + /* We never call vips_source_decode() since we need to be able to + * seek() the whole way through the file. Just minimise instead, + */ + vips_source_minimise( source ); return( 0 ); } int -vips__tiff_read_buffer( const void *buf, size_t len, - VipsImage *out, int page, int n, gboolean autorotate ) +vips__tiff_read_source( VipsSource *source, VipsImage *out, + int page, int n, gboolean autorotate, int subifd ) { Rtiff *rtiff; #ifdef DEBUG printf( "tiff2vips: libtiff version is \"%s\"\n", TIFFGetVersion() ); - printf( "tiff2vips: libtiff starting for buffer %p\n", buf ); #endif /*DEBUG*/ vips__tiff_init(); - if( !(rtiff = rtiff_new_buffer( buf, len, out, page, n, autorotate )) ) + if( !(rtiff = rtiff_new( source, out, page, n, autorotate, subifd )) || + rtiff_header_read_all( rtiff ) ) return( -1 ); if( rtiff->header.tiled ) { @@ -2577,32 +2808,12 @@ vips__tiff_read_buffer( const void *buf, size_t len, return( -1 ); } + /* We never call vips_source_decode() since we need to be able to + * seek() the whole way through the file. Just minimise instead, + */ + vips_source_minimise( source ); + return( 0 ); } -gboolean -vips__istifftiled_buffer( const void *buf, size_t len ) -{ - VipsImage *im; - TIFF *tif; - gboolean tiled; - - vips__tiff_init(); - - im = vips_image_new(); - - if( !(tif = vips__tiff_openin_buffer( im, buf, len )) ) { - g_object_unref( im ); - vips_error_clear(); - return( FALSE ); - } - - tiled = TIFFIsTiled( tif ); - - TIFFClose( tif ); - g_object_unref( im ); - - return( tiled ); -} - #endif /*HAVE_TIFF*/ diff --git a/libvips/foreign/tiffload.c b/libvips/foreign/tiffload.c index 0271d94d..58cbb6ab 100644 --- a/libvips/foreign/tiffload.c +++ b/libvips/foreign/tiffload.c @@ -57,6 +57,10 @@ typedef struct _VipsForeignLoadTiff { VipsForeignLoad parent_object; + /* Set by subclasses. + */ + VipsSource *source; + /* Load this page. */ int page; @@ -65,6 +69,10 @@ typedef struct _VipsForeignLoadTiff { */ int n; + /* Select subifd index. -1 for main image. + */ + int subifd; + /* Autorotate using orientation tag. */ gboolean autorotate; @@ -76,11 +84,84 @@ typedef VipsForeignLoadClass VipsForeignLoadTiffClass; G_DEFINE_ABSTRACT_TYPE( VipsForeignLoadTiff, vips_foreign_load_tiff, VIPS_TYPE_FOREIGN_LOAD ); +static void +vips_foreign_load_tiff_dispose( GObject *gobject ) +{ + VipsForeignLoadTiff *tiff = (VipsForeignLoadTiff *) gobject; + + VIPS_UNREF( tiff->source ); + + G_OBJECT_CLASS( vips_foreign_load_tiff_parent_class )-> + dispose( gobject ); +} + +static VipsForeignFlags +vips_foreign_load_tiff_get_flags_source( VipsSource *source ) +{ + VipsForeignFlags flags; + + flags = 0; + if( vips__istifftiled_source( source ) ) + flags |= VIPS_FOREIGN_PARTIAL; + else + flags |= VIPS_FOREIGN_SEQUENTIAL; + + return( flags ); +} + +static VipsForeignFlags +vips_foreign_load_tiff_get_flags_filename( const char *filename ) +{ + VipsSource *source; + VipsForeignFlags flags; + + if( !(source = vips_source_new_from_file( filename )) ) + return( 0 ); + flags = vips_foreign_load_tiff_get_flags_source( source ); + VIPS_UNREF( source ); + + return( flags ); +} + +static VipsForeignFlags +vips_foreign_load_tiff_get_flags( VipsForeignLoad *load ) +{ + VipsForeignLoadTiff *tiff = (VipsForeignLoadTiff *) load; + + return( vips_foreign_load_tiff_get_flags_source( tiff->source ) ); +} + +static int +vips_foreign_load_tiff_header( VipsForeignLoad *load ) +{ + VipsForeignLoadTiff *tiff = (VipsForeignLoadTiff *) load; + + if( vips__tiff_read_header_source( tiff->source, load->out, + tiff->page, tiff->n, tiff->autorotate, tiff->subifd ) ) + return( -1 ); + + return( 0 ); +} + +static int +vips_foreign_load_tiff_load( VipsForeignLoad *load ) +{ + VipsForeignLoadTiff *tiff = (VipsForeignLoadTiff *) load; + + if( vips__tiff_read_source( tiff->source, load->real, + tiff->page, tiff->n, tiff->autorotate, tiff->subifd ) ) + return( -1 ); + + return( 0 ); +} + static void vips_foreign_load_tiff_class_init( VipsForeignLoadTiffClass *class ) { GObjectClass *gobject_class = G_OBJECT_CLASS( class ); VipsObjectClass *object_class = (VipsObjectClass *) class; + VipsForeignClass *foreign_class = (VipsForeignClass *) class; + VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class; /* Other libraries may be using libtiff, we want to capture tiff * warning and error as soon as we can. @@ -89,12 +170,23 @@ vips_foreign_load_tiff_class_init( VipsForeignLoadTiffClass *class ) */ vips__tiff_init(); + gobject_class->dispose = vips_foreign_load_tiff_dispose; gobject_class->set_property = vips_object_set_property; gobject_class->get_property = vips_object_get_property; object_class->nickname = "tiffload_base"; object_class->description = _( "load tiff" ); + /* We are fast, but must test after openslideload. + */ + foreign_class->priority = 50; + + load_class->get_flags_filename = + vips_foreign_load_tiff_get_flags_filename; + load_class->get_flags = vips_foreign_load_tiff_get_flags; + load_class->header = vips_foreign_load_tiff_header; + load_class->load = vips_foreign_load_tiff_load; + VIPS_ARG_INT( class, "page", 20, _( "Page" ), _( "Load this page from the image" ), @@ -115,6 +207,14 @@ vips_foreign_load_tiff_class_init( VipsForeignLoadTiffClass *class ) VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET( VipsForeignLoadTiff, autorotate ), FALSE ); + + VIPS_ARG_INT( class, "subifd", 21, + _( "subifd" ), + _( "Select subifd index" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadTiff, subifd ), + -1, 100000, -1 ); + } static void @@ -122,6 +222,77 @@ vips_foreign_load_tiff_init( VipsForeignLoadTiff *tiff ) { tiff->page = 0; tiff->n = 1; + tiff->subifd = -1; +} + +typedef struct _VipsForeignLoadTiffSource { + VipsForeignLoadTiff parent_object; + + /* Load from a source. + */ + VipsSource *source; + +} VipsForeignLoadTiffSource; + +typedef VipsForeignLoadTiffClass VipsForeignLoadTiffSourceClass; + +G_DEFINE_TYPE( VipsForeignLoadTiffSource, vips_foreign_load_tiff_source, + vips_foreign_load_tiff_get_type() ); + +static int +vips_foreign_load_tiff_source_build( VipsObject *object ) +{ + VipsForeignLoadTiff *tiff = (VipsForeignLoadTiff *) object; + VipsForeignLoadTiffSource *source = + (VipsForeignLoadTiffSource *) object; + + if( source->source ) { + tiff->source = source->source; + g_object_ref( tiff->source ); + } + + if( VIPS_OBJECT_CLASS( vips_foreign_load_tiff_source_parent_class )-> + build( object ) ) + return( -1 ); + + return( 0 ); +} + +static gboolean +vips_foreign_load_tiff_source_is_a_source( VipsSource *source ) +{ + return( vips__istiff_source( source ) ); +} + +static void +vips_foreign_load_tiff_source_class_init( + VipsForeignLoadTiffSourceClass *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 = "tiffload_source"; + object_class->description = _( "load tiff from source" ); + object_class->build = vips_foreign_load_tiff_source_build; + + load_class->is_a_source = vips_foreign_load_tiff_source_is_a_source; + + VIPS_ARG_OBJECT( class, "source", 1, + _( "Source" ), + _( "Source to load from" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadTiffSource, source ), + VIPS_TYPE_SOURCE ); + +} + +static void +vips_foreign_load_tiff_source_init( VipsForeignLoadTiffSource *source ) +{ } typedef struct _VipsForeignLoadTiffFile { @@ -138,57 +309,38 @@ typedef VipsForeignLoadTiffClass VipsForeignLoadTiffFileClass; G_DEFINE_TYPE( VipsForeignLoadTiffFile, vips_foreign_load_tiff_file, vips_foreign_load_tiff_get_type() ); -static VipsForeignFlags -vips_foreign_load_tiff_file_get_flags_filename( const char *filename ) -{ - VipsForeignFlags flags; - - flags = 0; - if( vips__istifftiled( filename ) ) - flags |= VIPS_FOREIGN_PARTIAL; - else - flags |= VIPS_FOREIGN_SEQUENTIAL; - - return( flags ); -} - -static VipsForeignFlags -vips_foreign_load_tiff_file_get_flags( VipsForeignLoad *load ) -{ - VipsForeignLoadTiffFile *file = (VipsForeignLoadTiffFile *) load; - - return( vips_foreign_load_tiff_file_get_flags_filename( - file->filename ) ); -} - static int -vips_foreign_load_tiff_file_header( VipsForeignLoad *load ) +vips_foreign_load_tiff_file_build( VipsObject *object ) { - VipsForeignLoadTiff *tiff = (VipsForeignLoadTiff *) load; - VipsForeignLoadTiffFile *file = (VipsForeignLoadTiffFile *) load; + VipsForeignLoadTiff *tiff = (VipsForeignLoadTiff *) object; + VipsForeignLoadTiffFile *file = (VipsForeignLoadTiffFile *) object; - if( vips__tiff_read_header( file->filename, load->out, - tiff->page, tiff->n, tiff->autorotate ) ) + if( file->filename && + !(tiff->source = + vips_source_new_from_file( file->filename )) ) return( -1 ); - VIPS_SETSTR( load->out->filename, file->filename ); - - return( 0 ); -} - -static int -vips_foreign_load_tiff_file_load( VipsForeignLoad *load ) -{ - VipsForeignLoadTiff *tiff = (VipsForeignLoadTiff *) load; - VipsForeignLoadTiffFile *file = (VipsForeignLoadTiffFile *) load; - - if( vips__tiff_read( file->filename, load->real, - tiff->page, tiff->n, tiff->autorotate ) ) + if( VIPS_OBJECT_CLASS( vips_foreign_load_tiff_file_parent_class )-> + build( object ) ) return( -1 ); return( 0 ); } +static gboolean +vips_foreign_load_tiff_file_is_a( const char *filename ) +{ + VipsSource *source; + gboolean result; + + if( !(source = vips_source_new_from_file( filename )) ) + return( FALSE ); + result = vips_foreign_load_tiff_source_is_a_source( source ); + VIPS_UNREF( source ); + + return( result ); +} + const char *vips__foreign_tiff_suffs[] = { ".tif", ".tiff", NULL }; static void @@ -204,19 +356,11 @@ vips_foreign_load_tiff_file_class_init( VipsForeignLoadTiffFileClass *class ) object_class->nickname = "tiffload"; object_class->description = _( "load tiff from file" ); - - /* We are fast, but must test after openslideload. - */ - foreign_class->priority = 50; + object_class->build = vips_foreign_load_tiff_file_build; foreign_class->suffs = vips__foreign_tiff_suffs; - load_class->is_a = vips__istiff; - load_class->get_flags_filename = - vips_foreign_load_tiff_file_get_flags_filename; - load_class->get_flags = vips_foreign_load_tiff_file_get_flags; - load_class->header = vips_foreign_load_tiff_file_header; - load_class->load = vips_foreign_load_tiff_file_load; + load_class->is_a = vips_foreign_load_tiff_file_is_a; VIPS_ARG_STRING( class, "filename", 1, _( "Filename" ), @@ -236,7 +380,7 @@ typedef struct _VipsForeignLoadTiffBuffer { /* Load from a buffer. */ - VipsArea *buf; + VipsBlob *blob; } VipsForeignLoadTiffBuffer; @@ -245,48 +389,38 @@ typedef VipsForeignLoadTiffClass VipsForeignLoadTiffBufferClass; G_DEFINE_TYPE( VipsForeignLoadTiffBuffer, vips_foreign_load_tiff_buffer, vips_foreign_load_tiff_get_type() ); -static VipsForeignFlags -vips_foreign_load_tiff_buffer_get_flags( VipsForeignLoad *load ) -{ - VipsForeignLoadTiffBuffer *buffer = (VipsForeignLoadTiffBuffer *) load; - - VipsForeignFlags flags; - - flags = 0; - if( vips__istifftiled_buffer( buffer->buf->data, buffer->buf->length ) ) - flags |= VIPS_FOREIGN_PARTIAL; - else - flags |= VIPS_FOREIGN_SEQUENTIAL; - - return( flags ); -} - static int -vips_foreign_load_tiff_buffer_header( VipsForeignLoad *load ) +vips_foreign_load_tiff_buffer_build( VipsObject *object ) { - VipsForeignLoadTiff *tiff = (VipsForeignLoadTiff *) load; - VipsForeignLoadTiffBuffer *buffer = (VipsForeignLoadTiffBuffer *) load; + VipsForeignLoadTiff *tiff = (VipsForeignLoadTiff *) object; + VipsForeignLoadTiffBuffer *buffer = + (VipsForeignLoadTiffBuffer *) object; - if( vips__tiff_read_header_buffer( - buffer->buf->data, buffer->buf->length, load->out, - tiff->page, tiff->n, tiff->autorotate ) ) + if( buffer->blob && + !(tiff->source = vips_source_new_from_memory( + VIPS_AREA( buffer->blob )->data, + VIPS_AREA( buffer->blob )->length )) ) + return( -1 ); + + if( VIPS_OBJECT_CLASS( vips_foreign_load_tiff_buffer_parent_class )-> + build( object ) ) return( -1 ); return( 0 ); } -static int -vips_foreign_load_tiff_buffer_load( VipsForeignLoad *load ) +static gboolean +vips_foreign_load_tiff_buffer_is_a_buffer( const void *buf, size_t len ) { - VipsForeignLoadTiff *tiff = (VipsForeignLoadTiff *) load; - VipsForeignLoadTiffBuffer *buffer = (VipsForeignLoadTiffBuffer *) load; + VipsSource *source; + gboolean result; - if( vips__tiff_read_buffer( - buffer->buf->data, buffer->buf->length, load->real, - tiff->page, tiff->n, tiff->autorotate ) ) - return( -1 ); + if( !(source = vips_source_new_from_memory( buf, len )) ) + return( FALSE ); + result = vips_foreign_load_tiff_source_is_a_source( source ); + VIPS_UNREF( source ); - return( 0 ); + return( result ); } static void @@ -302,17 +436,15 @@ vips_foreign_load_tiff_buffer_class_init( object_class->nickname = "tiffload_buffer"; object_class->description = _( "load tiff from buffer" ); + object_class->build = vips_foreign_load_tiff_buffer_build; - load_class->is_a_buffer = vips__istiff_buffer; - load_class->get_flags = vips_foreign_load_tiff_buffer_get_flags; - load_class->header = vips_foreign_load_tiff_buffer_header; - load_class->load = vips_foreign_load_tiff_buffer_load; + load_class->is_a_buffer = vips_foreign_load_tiff_buffer_is_a_buffer; VIPS_ARG_BOXED( class, "buffer", 1, _( "Buffer" ), _( "Buffer to load from" ), VIPS_ARGUMENT_REQUIRED_INPUT, - G_STRUCT_OFFSET( VipsForeignLoadTiffBuffer, buf ), + G_STRUCT_OFFSET( VipsForeignLoadTiffBuffer, blob ), VIPS_TYPE_BLOB ); } @@ -335,10 +467,11 @@ vips_foreign_load_tiff_buffer_init( VipsForeignLoadTiffBuffer *buffer ) * * @n: %gint, load this many pages * * @autorotate: %gboolean, use orientation tag to rotate the image * during load + * * @subifd: %gint, select this subifd index * * Read a TIFF file into a VIPS image. It is a full baseline TIFF 6 reader, - * with extensions for tiled images, multipage images, LAB colour space, - * pyramidal images and JPEG compression. including CMYK and YCbCr. + * with extensions for tiled images, multipage images, XYZ and LAB colour + * space, pyramidal images and JPEG compression, including CMYK and YCbCr. * * @page means load this page from the file. By default the first page (page * 0) is read. @@ -359,6 +492,14 @@ vips_foreign_load_tiff_buffer_init( VipsForeignLoadTiffBuffer *buffer ) * operations will use #VIPS_META_ORIENTATION, if present, to set the * orientation of output images. * + * If @autorotate is TRUE, the image will be rotated upright during load and + * no metadata attached. This can be very slow. + * + * If @subifd is -1 (the default), the main image is selected for each page. + * If it is 0 or greater and there is a SUBIFD tag, the indexed SUBIFD is + * selected. This can be used to read lower resolution layers from + * bioformats-style image pyramids. + * * Any ICC profile is read and attached to the VIPS image as * #VIPS_META_ICC_NAME. Any XMP metadata is read and attached to the image * as #VIPS_META_XMP_NAME. Any IPTC is attached as #VIPS_META_IPTC_NAME. The @@ -396,6 +537,7 @@ vips_tiffload( const char *filename, VipsImage **out, ... ) * * @n: %gint, load this many pages * * @autorotate: %gboolean, use orientation tag to rotate the image * during load + * * @subifd: %gint, select this subifd index * * Read a TIFF-formatted memory block into a VIPS image. Exactly as * vips_tiffload(), but read from a memory source. @@ -426,3 +568,36 @@ vips_tiffload_buffer( void *buf, size_t len, VipsImage **out, ... ) return( result ); } + +/** + * vips_tiffload_source: + * @source: source to load + * @out: (out): image to write + * @...: %NULL-terminated list of optional named arguments + * + * Optional arguments: + * + * * @page: %gint, load this page + * * @n: %gint, load this many pages + * * @autorotate: %gboolean, use orientation tag to rotate the image + * during load + * * @subifd: %gint, select this subifd index + * + * Exactly as vips_tiffload(), but read from a source. + * + * See also: vips_tiffload(). + * + * Returns: 0 on success, -1 on error. + */ +int +vips_tiffload_source( VipsSource *source, VipsImage **out, ... ) +{ + va_list ap; + int result; + + va_start( ap, out ); + result = vips_call_split( "tiffload_source", ap, source, out ); + va_end( ap ); + + return( result ); +} diff --git a/libvips/foreign/tiffsave.c b/libvips/foreign/tiffsave.c index 341193cc..83d9fd94 100644 --- a/libvips/foreign/tiffsave.c +++ b/libvips/foreign/tiffsave.c @@ -19,6 +19,13 @@ * - add @level and @lossless * 4/9/18 [f--f] * - xres/yres params were in pixels/cm + * 26/1/20 + * - add "depth" to set pyr depth + * 12/5/20 + * - add "subifd" to create pyr layers as sub-directories + * 8/6/20 + * - add bitdepth support for 2 and 4 bit greyscale images + * - deprecate "squash" */ /* @@ -85,6 +92,7 @@ typedef struct _VipsForeignSaveTiff { int tile_height; gboolean pyramid; gboolean squash; + int bitdepth; gboolean miniswhite; VipsForeignTiffResunit resunit; double xres; @@ -95,6 +103,9 @@ typedef struct _VipsForeignSaveTiff { VipsRegionShrink region_shrink; int level; gboolean lossless; + VipsForeignDzDepth depth; + gboolean subifd; + } VipsForeignSaveTiff; typedef VipsForeignSaveClass VipsForeignSaveTiffClass; @@ -159,7 +170,7 @@ vips_foreign_save_tiff_build( VipsObject *object ) /* resunit param overrides resunit metadata. */ - if( vips_object_argument_isset( object, "resunit" ) && + if( !vips_object_argument_isset( object, "resunit" ) && vips_image_get_typeof( save->ready, VIPS_META_RESOLUTION_UNIT ) && !vips_image_get_string( save->ready, @@ -253,13 +264,6 @@ vips_foreign_save_tiff_class_init( VipsForeignSaveTiffClass *class ) G_STRUCT_OFFSET( VipsForeignSaveTiff, pyramid ), FALSE ); - VIPS_ARG_BOOL( class, "squash", 14, - _( "Squash" ), - _( "Squash images down to 1 bit" ), - VIPS_ARGUMENT_OPTIONAL_INPUT, - G_STRUCT_OFFSET( VipsForeignSaveTiff, squash ), - FALSE ); - VIPS_ARG_BOOL( class, "miniswhite", 14, _( "Miniswhite" ), _( "Use 0 for white in 1-bit images" ), @@ -267,41 +271,41 @@ vips_foreign_save_tiff_class_init( VipsForeignSaveTiffClass *class ) G_STRUCT_OFFSET( VipsForeignSaveTiff, miniswhite ), FALSE ); - VIPS_ARG_ENUM( class, "resunit", 15, + VIPS_ARG_INT( class, "bitdepth", 15, + _( "bitdepth" ), + _( "Write as a 1, 2, 4 or 8 bit image" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsForeignSaveTiff, bitdepth ), + 0, 8, 0 ); + + VIPS_ARG_ENUM( class, "resunit", 16, _( "Resolution unit" ), _( "Resolution unit" ), VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET( VipsForeignSaveTiff, resunit ), VIPS_TYPE_FOREIGN_TIFF_RESUNIT, VIPS_FOREIGN_TIFF_RESUNIT_CM ); - VIPS_ARG_DOUBLE( class, "xres", 16, + VIPS_ARG_DOUBLE( class, "xres", 17, _( "Xres" ), _( "Horizontal resolution in pixels/mm" ), VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET( VipsForeignSaveTiff, xres ), 0.001, 1000000, 1 ); - VIPS_ARG_DOUBLE( class, "yres", 17, + VIPS_ARG_DOUBLE( class, "yres", 18, _( "Yres" ), _( "Vertical resolution in pixels/mm" ), VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET( VipsForeignSaveTiff, yres ), 0.001, 1000000, 1 ); - VIPS_ARG_BOOL( class, "bigtiff", 18, + VIPS_ARG_BOOL( class, "bigtiff", 19, _( "Bigtiff" ), _( "Write a bigtiff image" ), VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET( VipsForeignSaveTiff, bigtiff ), FALSE ); - VIPS_ARG_BOOL( class, "rgbjpeg", 20, - _( "RGB JPEG" ), - _( "Output RGB JPEG rather than YCbCr" ), - VIPS_ARGUMENT_OPTIONAL_INPUT | VIPS_ARGUMENT_DEPRECATED, - G_STRUCT_OFFSET( VipsForeignSaveTiff, rgbjpeg ), - FALSE ); - VIPS_ARG_BOOL( class, "properties", 21, _( "Properties" ), _( "Write a properties document to IMAGEDESCRIPTION" ), @@ -324,12 +328,40 @@ vips_foreign_save_tiff_class_init( VipsForeignSaveTiffClass *class ) 1, 22, 10 ); VIPS_ARG_BOOL( class, "lossless", 24, - _( "lossless" ), + _( "Lossless" ), _( "Enable WEBP lossless mode" ), VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET( VipsForeignSaveTiff, lossless ), FALSE ); + VIPS_ARG_ENUM( class, "depth", 25, + _( "Depth" ), + _( "Pyramid depth" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsForeignSaveTiff, depth ), + VIPS_TYPE_FOREIGN_DZ_DEPTH, VIPS_FOREIGN_DZ_DEPTH_ONETILE ); + + VIPS_ARG_BOOL( class, "subifd", 24, + _( "Sub-IFD" ), + _( "Save pyr layers as sub-IFDs" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsForeignSaveTiff, subifd ), + FALSE ); + + VIPS_ARG_BOOL( class, "rgbjpeg", 20, + _( "RGB JPEG" ), + _( "Output RGB JPEG rather than YCbCr" ), + VIPS_ARGUMENT_OPTIONAL_INPUT | VIPS_ARGUMENT_DEPRECATED, + G_STRUCT_OFFSET( VipsForeignSaveTiff, rgbjpeg ), + FALSE ); + + VIPS_ARG_BOOL( class, "squash", 14, + _( "Squash" ), + _( "Squash images down to 1 bit" ), + VIPS_ARGUMENT_OPTIONAL_INPUT | VIPS_ARGUMENT_DEPRECATED, + G_STRUCT_OFFSET( VipsForeignSaveTiff, squash ), + FALSE ); + } static void @@ -346,6 +378,8 @@ vips_foreign_save_tiff_init( VipsForeignSaveTiff *tiff ) tiff->region_shrink = VIPS_REGION_SHRINK_MEAN; tiff->level = 10; tiff->lossless = FALSE; + tiff->depth = VIPS_FOREIGN_DZ_DEPTH_ONETILE; + tiff->bitdepth = 0; } typedef struct _VipsForeignSaveTiffFile { @@ -370,12 +404,19 @@ vips_foreign_save_tiff_file_build( VipsObject *object ) build( object ) ) return( -1 ); + /* Handle the deprecated squash parameter. + */ + if( vips_object_argument_isset( object, "squash" ) ) + /* We set that even in the case of LAB to LABQ. + */ + tiff->bitdepth = 1; + if( vips__tiff_write( save->ready, file->filename, tiff->compression, tiff->Q, tiff->predictor, tiff->profile, tiff->tile, tiff->tile_width, tiff->tile_height, tiff->pyramid, - tiff->squash, + tiff->bitdepth, tiff->miniswhite, tiff->resunit, tiff->xres, tiff->yres, tiff->bigtiff, @@ -384,7 +425,9 @@ vips_foreign_save_tiff_file_build( VipsObject *object ) save->strip, tiff->region_shrink, tiff->level, - tiff->lossless ) ) + tiff->lossless, + tiff->depth, + tiff->subifd ) ) return( -1 ); return( 0 ); @@ -446,7 +489,7 @@ vips_foreign_save_tiff_buffer_build( VipsObject *object ) tiff->profile, tiff->tile, tiff->tile_width, tiff->tile_height, tiff->pyramid, - tiff->squash, + tiff->bitdepth, tiff->miniswhite, tiff->resunit, tiff->xres, tiff->yres, tiff->bigtiff, @@ -455,7 +498,9 @@ vips_foreign_save_tiff_buffer_build( VipsObject *object ) save->strip, tiff->region_shrink, tiff->level, - tiff->lossless ) ) + tiff->lossless, + tiff->depth, + tiff->subifd ) ) return( -1 ); /* vips__tiff_write_buf() makes a buffer that needs g_free(), not @@ -469,7 +514,8 @@ vips_foreign_save_tiff_buffer_build( VipsObject *object ) } static void -vips_foreign_save_tiff_buffer_class_init( VipsForeignSaveTiffBufferClass *class ) +vips_foreign_save_tiff_buffer_class_init( + VipsForeignSaveTiffBufferClass *class ) { GObjectClass *gobject_class = G_OBJECT_CLASS( class ); VipsObjectClass *object_class = (VipsObjectClass *) class; @@ -507,23 +553,23 @@ vips_foreign_save_tiff_buffer_init( VipsForeignSaveTiffBuffer *buffer ) * * @compression: use this #VipsForeignTiffCompression * * @Q: %gint quality factor * * @predictor: use this #VipsForeignTiffPredictor - * * @profile: filename of ICC profile to attach - * * @tile: set %TRUE to write a tiled tiff + * * @profile: %gchararray, filename of ICC profile to attach + * * @tile: %gboolean, set %TRUE to write a tiled tiff * * @tile_width: %gint for tile size * * @tile_height: %gint for tile size - * * @pyramid: set %TRUE to write an image pyramid - * * @squash: set %TRUE to squash 8-bit images down to 1 bit - * * @miniswhite: set %TRUE to write 1-bit images as MINISWHITE + * * @pyramid: %gboolean, write an image pyramid + * * @bitdepth: %int, change bit depth to 1,2, or 4 bit + * * @miniswhite: %gboolean, write 1-bit images as MINISWHITE * * @resunit: #VipsForeignTiffResunit for resolution unit * * @xres: %gdouble horizontal resolution in pixels/mm * * @yres: %gdouble vertical resolution in pixels/mm - * * @bigtiff: set %TRUE to write a BigTiff file - * * @properties: set %TRUE to write an IMAGEDESCRIPTION tag - * * @strip: set %TRUE to block metadata save - * * @page_height: %gint for page height for multi-page save + * * @bigtiff: %gboolean, write a BigTiff file + * * @properties: %gboolean, set %TRUE to write an IMAGEDESCRIPTION tag * * @region_shrink: #VipsRegionShrink How to shrink each 2x2 region. * * @level: %gint, Zstd compression level - * * @lossless: set %TRUE for WebP losssless mode + * * @lossless: %gboolean, WebP losssless mode + * * @depth: #VipsForeignDzDepth how deep to make the pyramid + * * @subifd: %gboolean write pyr layers as sub-ifds * * Write a VIPS image to a file as TIFF. * @@ -538,6 +584,10 @@ vips_foreign_save_tiff_buffer_init( VipsForeignSaveTiffBuffer *buffer ) * good for 1-bit images, and deflate is the best lossless compression TIFF * can do. * + * XYZ images are automatically saved as libtiff LOGLUV with SGILOG compression. + * Float LAB images are saved as float CIELAB. Set @bitdepth to save as 8-bit + * CIELAB. + * * Use @Q to set the JPEG compression factor. Default 75. * * User @level to set the ZSTD compression level. Use @lossless to @@ -565,13 +615,23 @@ vips_foreign_save_tiff_buffer_init( VipsForeignSaveTiffBuffer *buffer ) * default each 2x2 block is just averaged, but you can set MODE or MEDIAN as * well. * - * Set @squash to make 8-bit uchar images write as 1-bit TIFFs. Values >128 - * are written as white, values <=128 as black. Normally vips will write - * MINISBLACK TIFFs where black is a 0 bit, but if you set @miniswhite, it - * will use 0 for a white bit. Many pre-press applications only work with - * images which use this sense. @miniswhite only affects one-bit images, it - * does nothing for greyscale images. + * By default, the pyramid stops when the image is small enough to fit in one + * tile. Use @depth to stop when the image fits in one pixel, or to only write + * a single layer. * + * Set @bitdepth to save 8-bit uchar images as 1, 2 or 4-bit TIFFs. + * In case of depth 1: Values >128 are written as white, values <=128 as black. + * Normally vips will write MINISBLACK TIFFs where black is a 0 bit, but if you + * set @miniswhite, it will use 0 for a white bit. Many pre-press applications + * only work with images which use this sense. @miniswhite only affects one-bit + * images, it does nothing for greyscale images. + * In case of depth 2: The same holds but values < 64 are written as black. + * For 64 <= values < 128 they are written as dark grey, for 128 <= values < 192 + * they are written as light gray and values above are written as white. + * In case @miniswhite is set to true this behavior is inverted. + * In case of depth 4: values < 16 are written as black, and so on for the + * lighter shades. In case @miniswhite is set to true this behavior is inverted. + * * Use @resunit to override the default resolution unit. * The default * resolution unit is taken from the header field @@ -597,6 +657,10 @@ vips_foreign_save_tiff_buffer_init( VipsForeignSaveTiffBuffer *buffer ) * #VIPS_META_PHOTOSHOP_NAME (if set) is used to set the value of the PHOTOSHOP * tag. * + * By default, pyramid layers are saved as consecutive pages. + * Set @subifd to save pyramid layers as sub-directories of the main image. + * Setting this option can improve compatibility with formats like OME. + * * See also: vips_tiffload(), vips_image_write_to_file(). * * Returns: 0 on success, -1 on error. @@ -626,23 +690,23 @@ vips_tiffsave( VipsImage *in, const char *filename, ... ) * * @compression: use this #VipsForeignTiffCompression * * @Q: %gint quality factor * * @predictor: use this #VipsForeignTiffPredictor - * * @profile: filename of ICC profile to attach - * * @tile: set %TRUE to write a tiled tiff + * * @profile: %gchararray, filename of ICC profile to attach + * * @tile: %gboolean, set %TRUE to write a tiled tiff * * @tile_width: %gint for tile size * * @tile_height: %gint for tile size - * * @pyramid: set %TRUE to write an image pyramid - * * @squash: set %TRUE to squash 8-bit images down to 1 bit - * * @miniswhite: set %TRUE to write 1-bit images as MINISWHITE + * * @pyramid: %gboolean, write an image pyramid + * * @bitdepth: %int, set write bit depth to 1, 2, 4 or 8 + * * @miniswhite: %gboolean, write 1-bit images as MINISWHITE * * @resunit: #VipsForeignTiffResunit for resolution unit * * @xres: %gdouble horizontal resolution in pixels/mm * * @yres: %gdouble vertical resolution in pixels/mm - * * @bigtiff: set %TRUE to write a BigTiff file - * * @properties: set %TRUE to write an IMAGEDESCRIPTION tag - * * @strip: set %TRUE to block metadata save - * * @page_height: %gint for page height for multi-page save + * * @bigtiff: %gboolean, write a BigTiff file + * * @properties: %gboolean, set %TRUE to write an IMAGEDESCRIPTION tag * * @region_shrink: #VipsRegionShrink How to shrink each 2x2 region. * * @level: %gint, Zstd compression level - * * @lossless: set %TRUE for WebP losssless mode + * * @lossless: %gboolean, WebP losssless mode + * * @depth: #VipsForeignDzDepth how deep to make the pyramid + * * @subifd: %gboolean write pyr layers as sub-ifds * * As vips_tiffsave(), but save to a memory buffer. * diff --git a/libvips/foreign/vips2jpeg.c b/libvips/foreign/vips2jpeg.c index 03b97de5..cd11e453 100644 --- a/libvips/foreign/vips2jpeg.c +++ b/libvips/foreign/vips2jpeg.c @@ -79,7 +79,7 @@ * 26/5/16 * - switch to new orientation tag * 9/7/16 - * - turn off chroma subsample for Q > 90 + * - turn off chroma subsample for Q >= 90 * 7/11/16 * - move exif handling out to exif.c * 27/2/17 @@ -90,6 +90,10 @@ * - fix another leak with error during buffer output * 19/7/19 * - ignore large XMP + * 14/10/19 + * - revise for target IO + * 18/2/20 Elad-Laufer + * - add subsample_mode, deprecate no_subsample */ /* @@ -207,9 +211,9 @@ static void write_destroy( Write *write ) { jpeg_destroy_compress( &write->cinfo ); - VIPS_FREEF( fclose, write->eman.fp ); VIPS_FREE( write->row_pointer ); VIPS_UNREF( write->inverted ); + VIPS_UNREF( write->in ); g_free( write ); } @@ -222,7 +226,7 @@ write_new( VipsImage *in ) if( !(write = g_new0( Write, 1 )) ) return( NULL ); - write->in = in; + write->in = NULL; write->row_pointer = NULL; write->cinfo.err = jpeg_std_error( &write->eman.pub ); write->cinfo.dest = NULL; @@ -231,6 +235,12 @@ write_new( VipsImage *in ) write->eman.fp = NULL; write->inverted = NULL; + if( vips_copy( in, &write->in, NULL ) || + vips__exif_update( write->in ) ) { + write_destroy( write ); + return( NULL ); + } + return( write ); } @@ -321,8 +331,7 @@ write_xmp( Write *write ) static int write_exif( Write *write ) { - if( vips__exif_update( write->in ) || - write_blob( write, VIPS_META_EXIF_NAME, JPEG_APP0 + 1 ) ) + if( write_blob( write, VIPS_META_EXIF_NAME, JPEG_APP0 + 1 ) ) return( -1 ); return( 0 ); @@ -472,8 +481,9 @@ write_jpeg_block( VipsRegion *region, VipsRect *area, void *a ) static int write_vips( Write *write, int qfac, const char *profile, gboolean optimize_coding, gboolean progressive, gboolean strip, - gboolean no_subsample, gboolean trellis_quant, - gboolean overshoot_deringing, gboolean optimize_scans, int quant_table ) + gboolean trellis_quant, gboolean overshoot_deringing, + gboolean optimize_scans, int quant_table, + VipsForeignJpegSubsample subsample_mode ) { VipsImage *in; J_COLOR_SPACE space; @@ -620,14 +630,12 @@ write_vips( Write *write, int qfac, const char *profile, if( progressive ) jpeg_simple_progression( &write->cinfo ); - /* Turn off chroma subsampling. Follow IM and do it automatically for - * high Q. - */ - if( no_subsample || - qfac > 90 ) { + if( subsample_mode == VIPS_FOREIGN_JPEG_SUBSAMPLE_OFF || + (subsample_mode == VIPS_FOREIGN_JPEG_SUBSAMPLE_AUTO && + qfac >= 90) ) { int i; - for( i = 0; i < in->Bands; i++ ) { + for( i = 0; i < in->Bands; i++ ) { write->cinfo.comp_info[i].h_samp_factor = 1; write->cinfo.comp_info[i].v_samp_factor = 1; } @@ -680,59 +688,8 @@ write_vips( Write *write, int qfac, const char *profile, return( 0 ); } -/* Write an image to a jpeg file. - */ -int -vips__jpeg_write_file( VipsImage *in, - const char *filename, int Q, const char *profile, - gboolean optimize_coding, gboolean progressive, gboolean strip, - gboolean no_subsample, gboolean trellis_quant, - gboolean overshoot_deringing, gboolean optimize_scans, int quant_table ) -{ - Write *write; +#define TARGET_BUFFER_SIZE (4096) - if( !(write = write_new( in )) ) - return( -1 ); - - if( setjmp( write->eman.jmp ) ) { - /* Here for longjmp() from new_error_exit(). - */ - write_destroy( write ); - - return( -1 ); - } - - /* Can't do this in write_new(), has to be after we've made the - * setjmp(). - */ - jpeg_create_compress( &write->cinfo ); - - /* Make output. - */ - if( !(write->eman.fp = vips__file_open_write( filename, FALSE )) ) { - write_destroy( write ); - return( -1 ); - } - jpeg_stdio_dest( &write->cinfo, write->eman.fp ); - - /* Convert! - */ - if( write_vips( write, - Q, profile, optimize_coding, progressive, strip, no_subsample, - trellis_quant, overshoot_deringing, optimize_scans, - quant_table ) ) { - write_destroy( write ); - return( -1 ); - } - write_destroy( write ); - - return( 0 ); -} - -/* Just like the above, but we write to a memory buffer. - * - * A memory buffer for the compressed image. - */ typedef struct { /* Public jpeg fields. */ @@ -743,133 +700,95 @@ typedef struct { /* Build the output area here. */ - VipsDbuf dbuf; + VipsTarget *target; - /* Write the generated area here. + /* Our output buffer. */ - void **obuf; /* Allocated buffer, and size */ - size_t *olen; -} OutputBuffer; + unsigned char buf[TARGET_BUFFER_SIZE]; +} Dest; -/* Buffer full method ... allocate a new output block. This is only called - * when the output area is exactly full. +/* Buffer full method. This is only called when the output area is exactly + * full. */ -METHODDEF(boolean) +static jboolean empty_output_buffer( j_compress_ptr cinfo ) { - OutputBuffer *buf = (OutputBuffer *) cinfo->dest; + Dest *dest = (Dest *) cinfo->dest; - size_t size; + if( vips_target_write( dest->target, + dest->buf, TARGET_BUFFER_SIZE ) ) + ERREXIT( cinfo, JERR_FILE_WRITE ); - vips_dbuf_allocate( &buf->dbuf, 10000 ); - buf->pub.next_output_byte = - (JOCTET *) vips_dbuf_get_write( &buf->dbuf, &size ); - buf->pub.free_in_buffer = size; + dest->pub.next_output_byte = dest->buf; + dest->pub.free_in_buffer = TARGET_BUFFER_SIZE; - /* TRUE means we've made some more space. - */ - return( 1 ); + return( TRUE ); } /* Init dest method. */ -METHODDEF(void) +static void init_destination( j_compress_ptr cinfo ) { - empty_output_buffer( cinfo ); + Dest *dest = (Dest *) cinfo->dest; + + dest->pub.next_output_byte = dest->buf; + dest->pub.free_in_buffer = TARGET_BUFFER_SIZE; } -/* Free the buffer writer. +/* Flush any remaining bytes to the output. */ static void -buf_destroy( j_compress_ptr cinfo ) -{ - if( cinfo->dest ) { - OutputBuffer *buf = (OutputBuffer *) cinfo->dest; - - vips_dbuf_destroy( &buf->dbuf ); - } -} - -/* Cleanup. Copy the set of blocks out as a big lump. This is only called by - * libjpeg on successful write --- you must call buf_destroy() explicitly to - * release resources. - */ -METHODDEF(void) term_destination( j_compress_ptr cinfo ) { - OutputBuffer *buf = (OutputBuffer *) cinfo->dest; + Dest *dest = (Dest *) cinfo->dest; - size_t size; + if( vips_target_write( dest->target, + dest->buf, TARGET_BUFFER_SIZE - dest->pub.free_in_buffer ) ) + ERREXIT( cinfo, JERR_FILE_WRITE ); - /* We probably won't have filled the area that was last allocated in - * empty_output_buffer(). Chop the data size down to the length that - * was actually written. - */ - vips_dbuf_seek( &buf->dbuf, -buf->pub.free_in_buffer, SEEK_END ); - vips_dbuf_truncate( &buf->dbuf ); - - *(buf->obuf) = vips_dbuf_steal( &buf->dbuf, &size ); - *(buf->olen) = size; + vips_target_finish( dest->target ); } /* Set dest to one of our objects. */ static void -buf_dest( j_compress_ptr cinfo, void **obuf, size_t *olen ) +target_dest( j_compress_ptr cinfo, VipsTarget *target ) { - OutputBuffer *buf; + Dest *dest; - /* The destination object is made permanent so that multiple JPEG - * images can be written to the same file without re-executing - * jpeg_stdio_dest. This makes it dangerous to use this manager and - * a different destination manager serially with the same JPEG object, - * because their private object sizes may be different. - * - * Caveat programmer. - */ if( !cinfo->dest ) { /* first time for this JPEG object? */ cinfo->dest = (struct jpeg_destination_mgr *) (*cinfo->mem->alloc_small) ( (j_common_ptr) cinfo, JPOOL_PERMANENT, - sizeof( OutputBuffer ) ); + sizeof( Dest ) ); } - buf = (OutputBuffer *) cinfo->dest; - buf->pub.init_destination = init_destination; - buf->pub.empty_output_buffer = empty_output_buffer; - buf->pub.term_destination = term_destination; - - /* Save output parameters. - */ - vips_dbuf_init( &buf->dbuf ); - buf->obuf = obuf; - buf->olen = olen; + dest = (Dest *) cinfo->dest; + dest->pub.init_destination = init_destination; + dest->pub.empty_output_buffer = empty_output_buffer; + dest->pub.term_destination = term_destination; + dest->target = target; } int -vips__jpeg_write_buffer( VipsImage *in, - void **obuf, size_t *olen, int Q, const char *profile, +vips__jpeg_write_target( VipsImage *in, VipsTarget *target, + int Q, const char *profile, gboolean optimize_coding, gboolean progressive, - gboolean strip, gboolean no_subsample, gboolean trellis_quant, - gboolean overshoot_deringing, gboolean optimize_scans, int quant_table ) + gboolean strip, gboolean trellis_quant, + gboolean overshoot_deringing, gboolean optimize_scans, + int quant_table, VipsForeignJpegSubsample subsample_mode) { Write *write; if( !(write = write_new( in )) ) return( -1 ); - /* Clear output parameters. - */ - *obuf = NULL; - *olen = 0; - /* Make jpeg compression object. */ if( setjmp( write->eman.jmp ) ) { /* Here for longjmp() during write_vips(). */ - buf_destroy( &write->cinfo ); write_destroy( write ); return( -1 ); @@ -878,20 +797,17 @@ vips__jpeg_write_buffer( VipsImage *in, /* Attach output. */ - buf_dest( &write->cinfo, obuf, olen ); + target_dest( &write->cinfo, target ); /* Convert! Write errors come back here as an error return. */ if( write_vips( write, - Q, profile, optimize_coding, progressive, strip, no_subsample, + Q, profile, optimize_coding, progressive, strip, trellis_quant, overshoot_deringing, optimize_scans, - quant_table ) ) { - buf_destroy( &write->cinfo ); + quant_table, subsample_mode ) ) { write_destroy( write ); - return( -1 ); } - buf_destroy( &write->cinfo ); write_destroy( write ); return( 0 ); diff --git a/libvips/foreign/vips2tiff.c b/libvips/foreign/vips2tiff.c index fca3d64e..5ff5834d 100644 --- a/libvips/foreign/vips2tiff.c +++ b/libvips/foreign/vips2tiff.c @@ -185,6 +185,18 @@ * 8/7/19 * - add webp and zstd support * - add @level and @lossless + * 18/12/19 + * - "squash" now squashes 3-band float LAB down to LABQ + * 26/1/20 + * - add "depth" to set pyr depth + * 27/1/20 + * - write XYZ images as logluv + * 7/2/20 [jclavoie-jive] + * - add PAGENUMBER support + * 23/5/20 + * - add support for subifd pyramid layers + * 6/6/20 MathemanFlo + * - add bitdepth support for 2 and 4 bit greyscale images */ /* @@ -239,6 +251,21 @@ #include "pforeign.h" #include "tiff.h" +/* TODO: + * + * - add a flag for plane-separate write + * + * At the moment, we write bioformats-style TIFFs by splitting bands up, + * making a toilet-roll image and writing out in pages. The TIFFs we make + * are not tagged as plane-separate and do not have (eg.) RGB photometric + * interpretation. Moreover, when working from an RGB source, we'll end + * up reading the input three times. + * + * A write-plane-separate flag to the TIFF writer could let us set the + * photometric interpretation correctly, and save all planes in a single + * pass before doing a final gather sweep. + */ + /* Max number of alpha channels we allow. */ #define MAX_ALPHA (64) @@ -287,7 +314,11 @@ struct _Layer { /* A TIFF image in the process of being written. */ struct _Wtiff { - VipsImage *im; /* Original input image */ + VipsImage *input; /* Original input image */ + + /* Image transformed ready for write. + */ + VipsImage *ready; /* File to write to, or NULL. */ @@ -308,7 +339,7 @@ struct _Wtiff { int tile; /* Tile or not */ int tilew, tileh; /* Tile size */ int pyramid; /* Wtiff pyramid */ - int onebit; /* Wtiff as 1-bit TIFF */ + int bitdepth; /* Write as 1, 2 or 4 bit */ int miniswhite; /* Wtiff as 0 == white */ int resunit; /* Resolution unit (inches or cm) */ double xres; /* Resolution in X */ @@ -321,12 +352,17 @@ struct _Wtiff { VipsRegionShrink region_shrink; /* How to shrink regions */ int level; /* zstd compression level */ gboolean lossless; /* webp lossless mode */ + VipsForeignDzDepth depth; /* Pyr depth */ + gboolean subifd; /* Write pyr layers into subifds */ /* True if we've detected a toilet-roll image, plus the page height, - * which has been checked to be a factor of im->Ysize. + * which has been checked to be a factor of im->Ysize. page_number + * starts at zero and ticks up as we write each page. */ gboolean toilet_roll; int page_height; + int page_number; + int n_pages; /* The height of the TIFF we write. Equal to page_height in toilet * roll mode. @@ -379,75 +415,98 @@ embed_profile_meta( TIFF *tif, VipsImage *im ) return( 0 ); } -static Layer * -wtiff_layer_new( Wtiff *wtiff, Layer *above, int width, int height ) +static void +wtiff_layer_init( Wtiff *wtiff, Layer **layer, Layer *above, + int width, int height ) { - Layer *layer; + if( !*layer ) { + *layer = VIPS_NEW( wtiff->ready, Layer ); + (*layer)->wtiff = wtiff; + (*layer)->width = width; + (*layer)->height = height; - layer = VIPS_NEW( wtiff->im, Layer ); - layer->wtiff = wtiff; - layer->width = width; - layer->height = height; + if( !above ) + /* Top of pyramid. + */ + (*layer)->sub = 1; + else + (*layer)->sub = above->sub * 2; - if( !above ) - /* Top of pyramid. + (*layer)->lname = NULL; + (*layer)->buf = NULL; + (*layer)->len = 0; + (*layer)->tif = NULL; + (*layer)->image = NULL; + (*layer)->write_y = 0; + (*layer)->y = 0; + (*layer)->strip = NULL; + (*layer)->copy = NULL; + + (*layer)->below = NULL; + (*layer)->above = above; + + /* The name for the top layer is the output filename. + * + * We need lname to be freed automatically: it has to stay + * alive until after wtiff_gather(). */ - layer->sub = 1; - else - layer->sub = above->sub * 2; + if( wtiff->filename ) { + if( !above ) + (*layer)->lname = vips_strdup( + VIPS_OBJECT( wtiff->ready ), + wtiff->filename ); + else { + char *lname; - layer->lname = NULL; - layer->buf = NULL; - layer->len = 0; - layer->tif = NULL; - layer->image = NULL; - layer->write_y = 0; - layer->y = 0; - layer->strip = NULL; - layer->copy = NULL; + lname = vips__temp_name( "%s.tif" ); + (*layer)->lname = vips_strdup( + VIPS_OBJECT( wtiff->ready ), + lname ); + g_free( lname ); + } + } - layer->below = NULL; - layer->above = above; + /* + printf( "wtiff_layer_init: sub = %d, width = %d, height = %d\n", + (*layer)->sub, width, height ); + */ + } - /* - printf( "wtiff_layer_new: sub = %d, width = %d, height = %d\n", - layer->sub, width, height ); - */ + if( wtiff->pyramid ) { + int limitw, limith; + + switch( wtiff->depth ) { + case VIPS_FOREIGN_DZ_DEPTH_ONEPIXEL: + limitw = limith = 1; + break; + + case VIPS_FOREIGN_DZ_DEPTH_ONETILE: + limitw = wtiff->tilew; + limith = wtiff->tileh; + break; + + case VIPS_FOREIGN_DZ_DEPTH_ONE: + limitw = wtiff->ready->Xsize; + limith = wtiff->ready->Ysize; + break; + + default: + g_assert_not_reached(); + } - if( wtiff->pyramid ) /* We make another layer if the image is too large to fit in a * single tile, and if neither axis is greater than 1. * * Very tall or wide images might end up with a smallest layer * larger than one tile. */ - if( (layer->width > wtiff->tilew || - layer->height > wtiff->tileh) && - layer->width > 1 && - layer->height > 1 ) - layer->below = wtiff_layer_new( wtiff, layer, + if( ((*layer)->width > limitw || + (*layer)->height > limith) && + (*layer)->width > 1 && + (*layer)->height > 1 ) + wtiff_layer_init( wtiff, &(*layer)->below, *layer, width / 2, height / 2 ); - - /* The name for the top layer is the output filename. - * - * We need lname to be freed automatically: it has to stay - * alive until after wtiff_gather(). - */ - if( wtiff->filename ) { - if( !above ) - layer->lname = vips_strdup( VIPS_OBJECT( wtiff->im ), - wtiff->filename ); - else { - char *lname; - - lname = vips__temp_name( "%s.tif" ); - layer->lname = - vips_strdup( VIPS_OBJECT( wtiff->im ), lname ); - g_free( lname ); - } } - - return( layer ); } static int @@ -458,8 +517,8 @@ wtiff_embed_profile( Wtiff *wtiff, TIFF *tif ) return( -1 ); if( !wtiff->icc_profile && - vips_image_get_typeof( wtiff->im, VIPS_META_ICC_NAME ) && - embed_profile_meta( tif, wtiff->im ) ) + vips_image_get_typeof( wtiff->ready, VIPS_META_ICC_NAME ) && + embed_profile_meta( tif, wtiff->ready ) ) return( -1 ); return( 0 ); @@ -471,9 +530,10 @@ wtiff_embed_xmp( Wtiff *wtiff, TIFF *tif ) const void *data; size_t size; - if( !vips_image_get_typeof( wtiff->im, VIPS_META_XMP_NAME ) ) + if( !vips_image_get_typeof( wtiff->ready, VIPS_META_XMP_NAME ) ) return( 0 ); - if( vips_image_get_blob( wtiff->im, VIPS_META_XMP_NAME, &data, &size ) ) + if( vips_image_get_blob( wtiff->ready, VIPS_META_XMP_NAME, + &data, &size ) ) return( -1 ); TIFFSetField( tif, TIFFTAG_XMLPACKET, size, data ); @@ -490,9 +550,9 @@ wtiff_embed_iptc( Wtiff *wtiff, TIFF *tif ) const void *data; size_t size; - if( !vips_image_get_typeof( wtiff->im, VIPS_META_IPTC_NAME ) ) + if( !vips_image_get_typeof( wtiff->ready, VIPS_META_IPTC_NAME ) ) return( 0 ); - if( vips_image_get_blob( wtiff->im, VIPS_META_IPTC_NAME, + if( vips_image_get_blob( wtiff->ready, VIPS_META_IPTC_NAME, &data, &size ) ) return( -1 ); @@ -522,10 +582,10 @@ wtiff_embed_photoshop( Wtiff *wtiff, TIFF *tif ) const void *data; size_t size; - if( !vips_image_get_typeof( wtiff->im, VIPS_META_PHOTOSHOP_NAME ) ) + if( !vips_image_get_typeof( wtiff->ready, VIPS_META_PHOTOSHOP_NAME ) ) return( 0 ); - if( vips_image_get_blob( wtiff->im, - VIPS_META_PHOTOSHOP_NAME, &data, &size ) ) + if( vips_image_get_blob( wtiff->ready, VIPS_META_PHOTOSHOP_NAME, + &data, &size ) ) return( -1 ); TIFFSetField( tif, TIFFTAG_PHOTOSHOP, size, data ); @@ -545,7 +605,7 @@ wtiff_embed_imagedescription( Wtiff *wtiff, TIFF *tif ) if( wtiff->properties ) { char *doc; - if( !(doc = vips__xml_properties( wtiff->im )) ) + if( !(doc = vips__xml_properties( wtiff->ready )) ) return( -1 ); TIFFSetField( tif, TIFFTAG_IMAGEDESCRIPTION, doc ); g_free( doc ); @@ -553,10 +613,10 @@ wtiff_embed_imagedescription( Wtiff *wtiff, TIFF *tif ) else { const char *imagedescription; - if( !vips_image_get_typeof( wtiff->im, + if( !vips_image_get_typeof( wtiff->ready, VIPS_META_IMAGEDESCRIPTION ) ) return( 0 ); - if( vips_image_get_string( wtiff->im, + if( vips_image_get_string( wtiff->ready, VIPS_META_IMAGEDESCRIPTION, &imagedescription ) ) return( -1 ); TIFFSetField( tif, TIFFTAG_IMAGEDESCRIPTION, imagedescription ); @@ -576,9 +636,13 @@ wtiff_write_header( Wtiff *wtiff, Layer *layer ) { TIFF *tif = layer->tif; - int format; int orientation; +#ifdef DEBUG + printf( "wtiff_write_header: sub %d, width %d, height %d\n", + layer->sub, layer->width, layer->height ); +#endif /*DEBUG*/ + /* Output base header fields. */ TIFFSetField( tif, TIFFTAG_IMAGEWIDTH, layer->width ); @@ -620,25 +684,26 @@ wtiff_write_header( Wtiff *wtiff, Layer *layer ) wtiff_embed_imagedescription( wtiff, tif ) ) return( -1 ); - if( vips_image_get_typeof( wtiff->im, VIPS_META_ORIENTATION ) && - !vips_image_get_int( wtiff->im, + if( vips_image_get_typeof( wtiff->ready, VIPS_META_ORIENTATION ) && + !vips_image_get_int( wtiff->ready, VIPS_META_ORIENTATION, &orientation ) ) TIFFSetField( tif, TIFFTAG_ORIENTATION, orientation ); /* And colour fields. */ - if( wtiff->im->Coding == VIPS_CODING_LABQ ) { + if( wtiff->ready->Coding == VIPS_CODING_LABQ ) { TIFFSetField( tif, TIFFTAG_SAMPLESPERPIXEL, 3 ); TIFFSetField( tif, TIFFTAG_BITSPERSAMPLE, 8 ); TIFFSetField( tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_CIELAB ); } - else if( wtiff->onebit ) { + else if( wtiff->bitdepth == 1 || wtiff->bitdepth == 2 || + wtiff->bitdepth == 4 ) { TIFFSetField( tif, TIFFTAG_SAMPLESPERPIXEL, 1 ); - TIFFSetField( tif, TIFFTAG_BITSPERSAMPLE, 1 ); - TIFFSetField( tif, TIFFTAG_PHOTOMETRIC, - wtiff->miniswhite ? - PHOTOMETRIC_MINISWHITE : - PHOTOMETRIC_MINISBLACK ); + TIFFSetField( tif, TIFFTAG_BITSPERSAMPLE, wtiff->bitdepth ); + TIFFSetField( tif, TIFFTAG_PHOTOMETRIC, + wtiff->miniswhite ? + PHOTOMETRIC_MINISWHITE : + PHOTOMETRIC_MINISBLACK ); } else { int photometric; @@ -650,58 +715,71 @@ wtiff_write_header( Wtiff *wtiff, Layer *layer ) int alpha_bands; - TIFFSetField( tif, TIFFTAG_SAMPLESPERPIXEL, wtiff->im->Bands ); + TIFFSetField( tif, TIFFTAG_SAMPLESPERPIXEL, + wtiff->ready->Bands ); TIFFSetField( tif, TIFFTAG_BITSPERSAMPLE, - vips_format_sizeof( wtiff->im->BandFmt ) << 3 ); + vips_format_sizeof( wtiff->ready->BandFmt ) << 3 ); - if( wtiff->im->Bands < 3 ) { + if( wtiff->ready->Type == VIPS_INTERPRETATION_B_W || + wtiff->ready->Type == VIPS_INTERPRETATION_GREY16 || + wtiff->ready->Bands < 3 ) { /* Mono or mono + alpha. */ - photometric = wtiff->miniswhite ? - PHOTOMETRIC_MINISWHITE : + photometric = wtiff->miniswhite ? + PHOTOMETRIC_MINISWHITE : PHOTOMETRIC_MINISBLACK; colour_bands = 1; } - else { - /* Could be: RGB, CMYK, LAB, perhaps with extra alpha. + else if( wtiff->ready->Type == VIPS_INTERPRETATION_LAB || + wtiff->ready->Type == VIPS_INTERPRETATION_LABS ) { + photometric = PHOTOMETRIC_CIELAB; + colour_bands = 3; + } + else if( wtiff->input->Type == VIPS_INTERPRETATION_XYZ ) { + double stonits; + + photometric = PHOTOMETRIC_LOGLUV; + /* Tell libtiff we will write as float XYZ. */ - if( wtiff->im->Type == VIPS_INTERPRETATION_LAB || - wtiff->im->Type == VIPS_INTERPRETATION_LABS ) { - photometric = PHOTOMETRIC_CIELAB; - colour_bands = 3; - } - else if( wtiff->im->Type == VIPS_INTERPRETATION_CMYK && - wtiff->im->Bands >= 4 ) { - photometric = PHOTOMETRIC_SEPARATED; - TIFFSetField( tif, - TIFFTAG_INKSET, INKSET_CMYK ); - colour_bands = 4; - } - else if( wtiff->compression == COMPRESSION_JPEG && - wtiff->im->Bands == 3 && - wtiff->im->BandFmt == VIPS_FORMAT_UCHAR && - (!wtiff->rgbjpeg && wtiff->Q < 90) ) { - /* This signals to libjpeg that it can do - * YCbCr chrominance subsampling from RGB, not - * that we will supply the image as YCbCr. - */ - photometric = PHOTOMETRIC_YCBCR; - TIFFSetField( tif, TIFFTAG_JPEGCOLORMODE, - JPEGCOLORMODE_RGB ); - colour_bands = 3; - } - else { - /* Some kind of generic multi-band image .. - * save the first three bands as RGB, the rest - * as alpha. - */ - photometric = PHOTOMETRIC_RGB; - colour_bands = 3; - } + TIFFSetField( tif, + TIFFTAG_SGILOGDATAFMT, SGILOGDATAFMT_FLOAT ); + stonits = 1.0; + if( vips_image_get_typeof( wtiff->ready, "stonits" ) ) + vips_image_get_double( wtiff->ready, + "stonits", &stonits ); + TIFFSetField( tif, TIFFTAG_STONITS, stonits ); + colour_bands = 3; + } + else if( wtiff->ready->Type == VIPS_INTERPRETATION_CMYK && + wtiff->ready->Bands >= 4 ) { + photometric = PHOTOMETRIC_SEPARATED; + TIFFSetField( tif, TIFFTAG_INKSET, INKSET_CMYK ); + colour_bands = 4; + } + else if( wtiff->compression == COMPRESSION_JPEG && + wtiff->ready->Bands == 3 && + wtiff->ready->BandFmt == VIPS_FORMAT_UCHAR && + (!wtiff->rgbjpeg && wtiff->Q < 90) ) { + /* This signals to libjpeg that it can do + * YCbCr chrominance subsampling from RGB, not + * that we will supply the image as YCbCr. + */ + photometric = PHOTOMETRIC_YCBCR; + TIFFSetField( tif, TIFFTAG_JPEGCOLORMODE, + JPEGCOLORMODE_RGB ); + colour_bands = 3; + } + else { + /* Some kind of generic multi-band image with three or + * more bands ... save the first three bands as RGB, + * the rest as alpha. + */ + photometric = PHOTOMETRIC_RGB; + colour_bands = 3; } alpha_bands = VIPS_CLIP( 0, - wtiff->im->Bands - colour_bands, MAX_ALPHA ); + wtiff->ready->Bands - colour_bands, MAX_ALPHA ); if( alpha_bands > 0 ) { uint16 v[MAX_ALPHA]; int i; @@ -733,18 +811,33 @@ wtiff_write_header( Wtiff *wtiff, Layer *layer ) */ TIFFSetField( tif, TIFFTAG_SUBFILETYPE, FILETYPE_REDUCEDIMAGE ); + if( wtiff->toilet_roll ) { + /* One page of many. + */ + TIFFSetField( tif, TIFFTAG_SUBFILETYPE, FILETYPE_PAGE ); + + TIFFSetField( tif, TIFFTAG_PAGENUMBER, + wtiff->page_number, wtiff->n_pages ); + } + /* Sample format. + * + * Don't set for logluv: libtiff does this for us. */ - format = SAMPLEFORMAT_UINT; - if( vips_band_format_isuint( wtiff->im->BandFmt ) ) + if( wtiff->input->Type != VIPS_INTERPRETATION_XYZ ) { + int format; + format = SAMPLEFORMAT_UINT; - else if( vips_band_format_isint( wtiff->im->BandFmt ) ) - format = SAMPLEFORMAT_INT; - else if( vips_band_format_isfloat( wtiff->im->BandFmt ) ) - format = SAMPLEFORMAT_IEEEFP; - else if( vips_band_format_iscomplex( wtiff->im->BandFmt ) ) - format = SAMPLEFORMAT_COMPLEXIEEEFP; - TIFFSetField( tif, TIFFTAG_SAMPLEFORMAT, format ); + if( vips_band_format_isuint( wtiff->ready->BandFmt ) ) + format = SAMPLEFORMAT_UINT; + else if( vips_band_format_isint( wtiff->ready->BandFmt ) ) + format = SAMPLEFORMAT_INT; + else if( vips_band_format_isfloat( wtiff->ready->BandFmt ) ) + format = SAMPLEFORMAT_IEEEFP; + else if( vips_band_format_iscomplex( wtiff->ready->BandFmt ) ) + format = SAMPLEFORMAT_COMPLEXIEEEFP; + TIFFSetField( tif, TIFFTAG_SAMPLEFORMAT, format ); + } return( 0 ); } @@ -779,40 +872,56 @@ wtiff_allocate_layers( Wtiff *wtiff ) { Layer *layer; + g_assert( wtiff->layer ); + for( layer = wtiff->layer; layer; layer = layer->below ) { - layer->image = vips_image_new(); - if( vips_image_pipelinev( layer->image, - VIPS_DEMAND_STYLE_ANY, wtiff->im, NULL ) ) - return( -1 ); - layer->image->Xsize = layer->width; - layer->image->Ysize = layer->height; + if( !layer->image ) { + layer->image = vips_image_new(); + if( vips_image_pipelinev( layer->image, + VIPS_DEMAND_STYLE_ANY, wtiff->ready, NULL ) ) + return( -1 ); + layer->image->Xsize = layer->width; + layer->image->Ysize = layer->height; - layer->strip = vips_region_new( layer->image ); - layer->copy = vips_region_new( layer->image ); + layer->strip = vips_region_new( layer->image ); + layer->copy = vips_region_new( layer->image ); - /* The regions will get used in the bg thread callback, so - * make sure we don't own them. - */ - vips__region_no_ownership( layer->strip ); - vips__region_no_ownership( layer->copy ); + /* The regions will get used in the bg thread callback, + * so make sure we don't own them. + */ + vips__region_no_ownership( layer->strip ); + vips__region_no_ownership( layer->copy ); + + if( layer->lname ) + layer->tif = vips__tiff_openout( + layer->lname, wtiff->bigtiff ); + else { + layer->tif = vips__tiff_openout_buffer( + wtiff->ready, wtiff->bigtiff, + &layer->buf, &layer->len ); + } + if( !layer->tif ) + return( -1 ); + } if( wtiff_layer_rewind( wtiff, layer ) ) return( -1 ); - if( layer->lname ) - layer->tif = vips__tiff_openout( - layer->lname, wtiff->bigtiff ); - else { - layer->tif = vips__tiff_openout_buffer( wtiff->im, - wtiff->bigtiff, &layer->buf, &layer->len ); - } - if( !layer->tif ) - return( -1 ); - if( wtiff_write_header( wtiff, layer ) ) return( -1 ); } + if( !wtiff->tbuf ) { + if( wtiff->tile ) + wtiff->tbuf = vips_malloc( NULL, + TIFFTileSize( wtiff->layer->tif ) ); + else + wtiff->tbuf = vips_malloc( NULL, + TIFFScanlineSize( wtiff->layer->tif ) ); + if( !wtiff->tbuf ) + return( -1 ); + } + return( 0 ); } @@ -869,9 +978,12 @@ wtiff_free( Wtiff *wtiff ) { wtiff_delete_temps( wtiff ); + VIPS_UNREF( wtiff->ready ); VIPS_FREEF( vips_free, wtiff->tbuf ); VIPS_FREEF( layer_free_all, wtiff->layer ); VIPS_FREEF( vips_free, wtiff->icc_profile ); + VIPS_FREE( wtiff->filename ); + VIPS_FREE( wtiff ); } static int @@ -920,14 +1032,40 @@ get_resunit( VipsForeignTiffResunit resunit ) return( -1 ); } +/* Get the image ready to be written. + */ +static int +ready_to_write( Wtiff *wtiff ) +{ + if( vips_check_coding_known( "vips2tiff", wtiff->input ) ) + return( -1 ); + + /* "squash" float LAB down to LABQ. + */ + if( wtiff->bitdepth && + wtiff->input->Bands == 3 && + wtiff->input->BandFmt == VIPS_FORMAT_FLOAT && + wtiff->input->Type == VIPS_INTERPRETATION_LAB ) { + if( vips_Lab2LabQ( wtiff->input, &wtiff->ready, NULL ) ) + return( -1 ); + wtiff->bitdepth = 0; + } + else { + wtiff->ready = wtiff->input; + g_object_ref( wtiff->ready ); + } + + return( 0 ); +} + static Wtiff * -wtiff_new( VipsImage *im, const char *filename, +wtiff_new( VipsImage *input, const char *filename, VipsForeignTiffCompression compression, int Q, VipsForeignTiffPredictor predictor, char *profile, gboolean tile, int tile_width, int tile_height, gboolean pyramid, - gboolean squash, + int bitdepth, gboolean miniswhite, VipsForeignTiffResunit resunit, double xres, double yres, gboolean bigtiff, @@ -935,15 +1073,18 @@ wtiff_new( VipsImage *im, const char *filename, gboolean properties, gboolean strip, VipsRegionShrink region_shrink, - int level, gboolean lossless ) + int level, + gboolean lossless, + VipsForeignDzDepth depth, + gboolean subifd ) { Wtiff *wtiff; - if( !(wtiff = VIPS_NEW( im, Wtiff )) ) + if( !(wtiff = VIPS_NEW( NULL, Wtiff )) ) return( NULL ); - wtiff->im = im; - wtiff->filename = filename ? - vips_strdup( VIPS_OBJECT( im ), filename ) : NULL; + wtiff->input = input; + wtiff->ready = NULL; + wtiff->filename = filename ? vips_strdup( NULL, filename ) : NULL; wtiff->layer = NULL; wtiff->tbuf = NULL; wtiff->compression = get_compression( compression ); @@ -953,7 +1094,7 @@ wtiff_new( VipsImage *im, const char *filename, wtiff->tilew = tile_width; wtiff->tileh = tile_height; wtiff->pyramid = pyramid; - wtiff->onebit = squash; + wtiff->bitdepth = bitdepth; wtiff->miniswhite = miniswhite; wtiff->resunit = get_resunit( resunit ); wtiff->xres = xres; @@ -966,51 +1107,48 @@ wtiff_new( VipsImage *im, const char *filename, wtiff->region_shrink = region_shrink; wtiff->level = level; wtiff->lossless = lossless; + wtiff->depth = depth; + wtiff->subifd = subifd; wtiff->toilet_roll = FALSE; - wtiff->page_height = vips_image_get_page_height( im ); - wtiff->image_height = im->Ysize; + wtiff->page_height = vips_image_get_page_height( input ); + wtiff->page_number = 0; + wtiff->n_pages = 1; + wtiff->image_height = input->Ysize; + + /* Any pre-processing on the image. + */ + if( ready_to_write( wtiff ) ) { + wtiff_free( wtiff ); + return( NULL ); + } + + /* XYZ images are written as libtiff LOGLUV. + */ + if( wtiff->ready->Type == VIPS_INTERPRETATION_XYZ ) + wtiff->compression = COMPRESSION_SGILOG; /* Multipage image? */ - if( wtiff->page_height < im->Ysize ) { + if( wtiff->page_height < wtiff->ready->Ysize ) { #ifdef DEBUG printf( "wtiff_new: detected toilet roll image, " "page-height=%d\n", wtiff->page_height ); printf( "wtiff_new: pages=%d\n", - im->Ysize / wtiff->page_height ); + wtiff->ready->Ysize / wtiff->page_height ); #endif/*DEBUG*/ wtiff->toilet_roll = TRUE; wtiff->image_height = wtiff->page_height; - - /* We can't pyramid toilet roll images. - */ - if( wtiff->pyramid ) { - g_warning( "%s", - _( "can't pyramid multi page images --- " - "disabling pyramid" ) ); - wtiff->pyramid = FALSE; - } - } - - /* In strip mode we use tileh to set rowsperstrip, and that does not - * have the multiple-of-16 restriction. - */ - if( tile ) { - if( (wtiff->tilew & 0xf) != 0 || - (wtiff->tileh & 0xf) != 0 ) { - vips_error( "vips2tiff", - "%s", _( "tile size not a multiple of 16" ) ); - return( NULL ); - } + wtiff->n_pages = wtiff->ready->Ysize / wtiff->page_height; } /* We can only pyramid LABQ and non-complex images. */ if( wtiff->pyramid ) { - if( im->Coding == VIPS_CODING_NONE && - vips_band_format_iscomplex( im->BandFmt ) ) { + if( wtiff->ready->Coding == VIPS_CODING_NONE && + vips_band_format_iscomplex( wtiff->ready->BandFmt ) ) { + wtiff_free( wtiff ); vips_error( "vips2tiff", "%s", _( "can only pyramid LABQ and " "non-complex images" ) ); @@ -1018,31 +1156,78 @@ wtiff_new( VipsImage *im, const char *filename, } } - /* Only 1-bit-ize 8 bit mono images. + /* Pyramid images must be tiled. */ - if( wtiff->onebit && - (im->Coding != VIPS_CODING_NONE || - im->BandFmt != VIPS_FORMAT_UCHAR || - im->Bands != 1) ) { - g_warning( "%s", - _( "can only squash 1 band uchar images -- " - "disabling squash" ) ); - wtiff->onebit = 0; + if( wtiff->pyramid && + !wtiff->tile ) + wtiff->tile = TRUE; + + /* Multi-page pyramids must be in subifd mode. + */ + if( wtiff->pyramid && + wtiff->toilet_roll ) + wtiff->subifd = TRUE; + + /* If compression is off and we're writing a >4gb image, automatically + * enable bigtiff. + * + * This won't always work. If the image data is just under 4gb but + * there's a lot of metadata, we could be pushed over the 4gb limit. + */ + if( wtiff->compression == COMPRESSION_NONE && + VIPS_IMAGE_SIZEOF_IMAGE( wtiff->ready ) > UINT_MAX ) + wtiff->bigtiff = TRUE; + + /* In strip mode we use tileh to set rowsperstrip, and that does not + * have the multiple-of-16 restriction. + */ + if( wtiff->tile ) { + if( (wtiff->tilew & 0xf) != 0 || + (wtiff->tileh & 0xf) != 0 ) { + wtiff_free( wtiff ); + vips_error( "vips2tiff", + "%s", _( "tile size not a multiple of 16" ) ); + return( NULL ); + } } - if( wtiff->onebit && + /* Depth 8 is handled above. + */ + if( wtiff->bitdepth && + !(wtiff->bitdepth == 1 || + wtiff->bitdepth == 2 || + wtiff->bitdepth == 4) ) { + g_warning( "%s", + _( "bitdepth 1, 2 or 4 only -- disabling bitdepth") ); + wtiff->bitdepth = 0; + } + + /* Can only have byte fractional bit depths for 8 bit mono. + * 3-band float should have been packed above. + */ + if( wtiff->bitdepth && + !(wtiff->ready->Coding == VIPS_CODING_NONE && + wtiff->ready->BandFmt == VIPS_FORMAT_UCHAR && + wtiff->ready->Bands == 1) ) { + g_warning( "%s", + ( "can only set bitdepth for 1-band uchar and " + "3-band float lab -- disabling bitdepth" ) ); + wtiff->bitdepth = 0; + } + + if( wtiff->bitdepth && wtiff->compression == COMPRESSION_JPEG ) { g_warning( "%s", - _( "can't have 1-bit JPEG -- disabling JPEG" ) ); + _( "can't have <8 bit JPEG -- disabling JPEG" ) ); wtiff->compression = COMPRESSION_NONE; } /* We can only MINISWHITE non-complex images of 1 or 2 bands. */ if( wtiff->miniswhite && - (im->Coding != VIPS_CODING_NONE || - vips_band_format_iscomplex( im->BandFmt ) || - im->Bands > 2) ) { + (wtiff->ready->Coding != VIPS_CODING_NONE || + vips_band_format_iscomplex( wtiff->ready->BandFmt ) || + wtiff->ready->Bands > 2) ) { g_warning( "%s", _( "can only save non-complex greyscale images " "as miniswhite -- disabling miniswhite" ) ); @@ -1066,48 +1251,17 @@ wtiff_new( VipsImage *im, const char *filename, /* Sizeof a line of bytes in the TIFF tile. */ - if( im->Coding == VIPS_CODING_LABQ ) + if( wtiff->ready->Coding == VIPS_CODING_LABQ ) wtiff->tls = wtiff->tilew * 3; - else if( wtiff->onebit ) + else if( wtiff->bitdepth == 1 ) wtiff->tls = VIPS_ROUND_UP( wtiff->tilew, 8 ) / 8; + else if( wtiff->bitdepth == 2 ) + wtiff->tls = VIPS_ROUND_UP( wtiff->tilew, 4 ) / 4; + else if( wtiff->bitdepth == 4 ) + wtiff->tls = VIPS_ROUND_UP( wtiff->tilew, 2 ) / 2; else - wtiff->tls = VIPS_IMAGE_SIZEOF_PEL( im ) * wtiff->tilew; - - /* If compression is off and we're writing a >4gb image, automatically - * enable bigtiff. - * - * This won't always work. If the image data is just under 4gb but - * there's a lot of metadata, we could be pushed over the 4gb limit. - */ - if( wtiff->compression == COMPRESSION_NONE && - VIPS_IMAGE_SIZEOF_IMAGE( wtiff->im ) > UINT_MAX && - !wtiff->bigtiff ) { - g_warning( "%s", _( "image over 4gb, enabling bigtiff" ) ); - wtiff->bigtiff = TRUE; - } - - /* Build the pyramid framework. - */ - wtiff->layer = wtiff_layer_new( wtiff, NULL, - im->Xsize, wtiff->image_height ); - - /* Fill all the layers. - */ - if( wtiff_allocate_layers( wtiff ) ) { - wtiff_free( wtiff ); - return( NULL ); - } - - if( tile ) - wtiff->tbuf = vips_malloc( NULL, - TIFFTileSize( wtiff->layer->tif ) ); - else - wtiff->tbuf = vips_malloc( NULL, - TIFFScanlineSize( wtiff->layer->tif ) ); - if( !wtiff->tbuf ) { - wtiff_free( wtiff ); - return( NULL ); - } + wtiff->tls = VIPS_IMAGE_SIZEOF_PEL( wtiff->ready ) * + wtiff->tilew; return( wtiff ); } @@ -1117,51 +1271,53 @@ wtiff_new( VipsImage *im, const char *filename, static void LabQ2LabC( VipsPel *q, VipsPel *p, int n ) { - int x; + int x; - for( x = 0; x < n; x++ ) { - /* Get most significant 8 bits of lab. - */ - q[0] = p[0]; - q[1] = p[1]; - q[2] = p[2]; + for( x = 0; x < n; x++ ) { + /* Get most significant 8 bits of lab. + */ + q[0] = p[0]; + q[1] = p[1]; + q[2] = p[2]; - p += 4; - q += 3; - } + p += 4; + q += 3; + } } -/* Pack 8 bit VIPS to 1 bit TIFF. +/* Pack 8 bit VIPS to N bit TIFF. */ static void -eightbit2onebit( Wtiff *wtiff, VipsPel *q, VipsPel *p, int n ) +eightbit2nbit( Wtiff *wtiff, VipsPel *q, VipsPel *p, int n ) { - int x; - VipsPel bits; - /* Invert in miniswhite mode. */ - int white = wtiff->miniswhite ? 0 : 1; - int black = white ^ 1; + VipsPel mask = wtiff->miniswhite ? 255 : 0; + int pixel_mask = 8 / wtiff->bitdepth - 1; + int shift = 8 - wtiff->bitdepth; + + VipsPel bits; + int x; bits = 0; for( x = 0; x < n; x++ ) { - bits <<= 1; - if( p[x] > 128 ) - bits |= white; - else - bits |= black; + bits <<= wtiff->bitdepth; + bits |= p[x] >> shift; - if( (x & 0x7) == 0x7 ) { - *q++ = bits; - bits = 0; - } + if( (x & pixel_mask) == pixel_mask ) + *q++ = bits ^ mask; } /* Any left-over bits? Need to be left-aligned. */ - if( (x & 0x7) != 0 ) - *q++ = bits << (8 - (x & 0x7)); + if( (x & pixel_mask) != 0 ) { + /* The number of bits we've collected in bits and must + * left-align and flush. + */ + int collected_bits = (x & pixel_mask) << (wtiff->bitdepth - 1); + + *q++ = (bits ^ mask) << (8 - collected_bits); + } } /* Swap the sense of the first channel, if necessary. @@ -1193,7 +1349,7 @@ eightbit2onebit( Wtiff *wtiff, VipsPel *q, VipsPel *p, int n ) static void invert_band0( Wtiff *wtiff, VipsPel *q, VipsPel *p, int n ) { - VipsImage *im = wtiff->im; + VipsImage *im = wtiff->ready; gboolean invert = wtiff->miniswhite; int x, i; @@ -1236,22 +1392,51 @@ invert_band0( Wtiff *wtiff, VipsPel *q, VipsPel *p, int n ) /* Convert VIPS LABS to TIFF 16 bit LAB. */ static void -LabS2Lab16( VipsPel *q, VipsPel *p, int n ) +LabS2Lab16( VipsPel *q, VipsPel *p, int n, int samples_per_pixel ) { - int x; short *p1 = (short *) p; unsigned short *q1 = (unsigned short *) q; - for( x = 0; x < n; x++ ) { - /* TIFF uses unsigned 16 bit ... move zero, scale up L. - */ - q1[0] = VIPS_LSHIFT_INT( (int) p1[0], 1 ); - q1[1] = p1[1]; - q1[2] = p1[2]; + int x; - p1 += 3; - q1 += 3; - } + for( x = 0; x < n; x++ ) { + int i; + + /* LABS L can be negative. + */ + q1[0] = VIPS_LSHIFT_INT( VIPS_MAX( 0, p1[0] ), 1 ); + + for( i = 1; i < samples_per_pixel; i++ ) + q1[i] = p1[i]; + + q1 += samples_per_pixel; + p1 += samples_per_pixel; + } +} + +/* Convert VIPS D65 XYZ to TIFF scaled float illuminant-free xyz. + */ +static void +XYZ2tiffxyz( VipsPel *q, VipsPel *p, int n, int samples_per_pixel ) +{ + float *p1 = (float *) p; + float *q1 = (float *) q; + + int x; + + for( x = 0; x < n; x++ ) { + int i; + + q1[0] = p1[0] / VIPS_D65_X0; + q1[1] = p1[1] / VIPS_D65_Y0; + q1[2] = p1[2] / VIPS_D65_Z0; + + for( i = 3; i < samples_per_pixel; i++ ) + q1[i] = p1[i]; + + q1 += samples_per_pixel; + p1 += samples_per_pixel; + } } /* Pack the pixels in @area from @in into a TIFF tile buffer. @@ -1277,20 +1462,22 @@ wtiff_pack2tiff( Wtiff *wtiff, Layer *layer, for( y = area->top; y < VIPS_RECT_BOTTOM( area ); y++ ) { VipsPel *p = (VipsPel *) VIPS_REGION_ADDR( in, area->left, y ); - if( wtiff->im->Coding == VIPS_CODING_LABQ ) + if( wtiff->ready->Coding == VIPS_CODING_LABQ ) LabQ2LabC( q, p, area->width ); - else if( wtiff->onebit ) - eightbit2onebit( wtiff, q, p, area->width ); + else if( wtiff->bitdepth > 0 ) + eightbit2nbit( wtiff, q, p, area->width ); + else if( wtiff->input->Type == VIPS_INTERPRETATION_XYZ ) + XYZ2tiffxyz( q, p, area->width, in->im->Bands ); else if( (in->im->Bands == 1 || in->im->Bands == 2) && wtiff->miniswhite ) invert_band0( wtiff, q, p, area->width ); - else if( wtiff->im->BandFmt == VIPS_FORMAT_SHORT && - wtiff->im->Type == VIPS_INTERPRETATION_LABS ) - LabS2Lab16( q, p, area->width ); + else if( wtiff->ready->BandFmt == VIPS_FORMAT_SHORT && + wtiff->ready->Type == VIPS_INTERPRETATION_LABS ) + LabS2Lab16( q, p, area->width, in->im->Bands ); else memcpy( q, p, area->width * - VIPS_IMAGE_SIZEOF_PEL( wtiff->im ) ); + VIPS_IMAGE_SIZEOF_PEL( wtiff->ready ) ); q += wtiff->tls; } @@ -1369,11 +1556,15 @@ wtiff_layer_write_strip( Wtiff *wtiff, Layer *layer, VipsRegion *strip ) } else if( im->BandFmt == VIPS_FORMAT_SHORT && im->Type == VIPS_INTERPRETATION_LABS ) { - LabS2Lab16( wtiff->tbuf, p, im->Xsize ); + LabS2Lab16( wtiff->tbuf, p, im->Xsize, im->Bands ); p = wtiff->tbuf; } - else if( wtiff->onebit ) { - eightbit2onebit( wtiff, wtiff->tbuf, p, im->Xsize ); + else if( wtiff->input->Type == VIPS_INTERPRETATION_XYZ ) { + XYZ2tiffxyz( wtiff->tbuf, p, im->Xsize, im->Bands ); + p = wtiff->tbuf; + } + else if( wtiff->bitdepth > 0 ) { + eightbit2nbit( wtiff, wtiff->tbuf, p, im->Xsize ); p = wtiff->tbuf; } else if( (im->Bands == 1 || im->Bands == 2) && @@ -1541,10 +1732,10 @@ write_strip( VipsRegion *region, VipsRect *area, void *a ) Wtiff *wtiff = (Wtiff *) a; Layer *layer = wtiff->layer; -#ifdef DEBUG +#ifdef DEBUG_VERBOSE printf( "write_strip: strip at %d, height %d\n", area->top, area->height ); -#endif/*DEBUG*/ +#endif/*DEBUG_VERBOSE*/ for(;;) { VipsRect *to = &layer->strip->valid; @@ -1603,8 +1794,9 @@ write_strip( VipsRegion *region, VipsRect *area, void *a ) static int wtiff_copy_tiff( Wtiff *wtiff, TIFF *out, TIFF *in ) { - uint32 i32; - uint16 i16; + uint32 ui32; + uint16 ui16; + uint16 ui16_2; float f; tdata_t buf; ttile_t tile; @@ -1613,25 +1805,28 @@ wtiff_copy_tiff( Wtiff *wtiff, TIFF *out, TIFF *in ) /* All the fields we might have set. */ - CopyField( TIFFTAG_IMAGEWIDTH, i32 ); - CopyField( TIFFTAG_IMAGELENGTH, i32 ); - CopyField( TIFFTAG_PLANARCONFIG, i16 ); - CopyField( TIFFTAG_ORIENTATION, i16 ); + CopyField( TIFFTAG_IMAGEWIDTH, ui32 ); + CopyField( TIFFTAG_IMAGELENGTH, ui32 ); + CopyField( TIFFTAG_PLANARCONFIG, ui16 ); + CopyField( TIFFTAG_ORIENTATION, ui16 ); CopyField( TIFFTAG_XRESOLUTION, f ); CopyField( TIFFTAG_YRESOLUTION, f ); - CopyField( TIFFTAG_RESOLUTIONUNIT, i16 ); - CopyField( TIFFTAG_COMPRESSION, i16 ); - CopyField( TIFFTAG_SAMPLESPERPIXEL, i16 ); - CopyField( TIFFTAG_BITSPERSAMPLE, i16 ); - CopyField( TIFFTAG_PHOTOMETRIC, i16 ); - CopyField( TIFFTAG_ORIENTATION, i16 ); - CopyField( TIFFTAG_TILEWIDTH, i32 ); - CopyField( TIFFTAG_TILELENGTH, i32 ); - CopyField( TIFFTAG_ROWSPERSTRIP, i32 ); - CopyField( TIFFTAG_SUBFILETYPE, i32 ); + CopyField( TIFFTAG_RESOLUTIONUNIT, ui16 ); + CopyField( TIFFTAG_COMPRESSION, ui16 ); + CopyField( TIFFTAG_SAMPLESPERPIXEL, ui16 ); + CopyField( TIFFTAG_BITSPERSAMPLE, ui16 ); + CopyField( TIFFTAG_PHOTOMETRIC, ui16 ); + CopyField( TIFFTAG_ORIENTATION, ui16 ); + CopyField( TIFFTAG_TILEWIDTH, ui32 ); + CopyField( TIFFTAG_TILELENGTH, ui32 ); + CopyField( TIFFTAG_ROWSPERSTRIP, ui32 ); + CopyField( TIFFTAG_SUBFILETYPE, ui32 ); - if( TIFFGetField( in, TIFFTAG_EXTRASAMPLES, &i16, &a ) ) - TIFFSetField( out, TIFFTAG_EXTRASAMPLES, i16, a ); + if( TIFFGetField( in, TIFFTAG_EXTRASAMPLES, &ui16, &a ) ) + TIFFSetField( out, TIFFTAG_EXTRASAMPLES, ui16, a ); + + if( TIFFGetField( in, TIFFTAG_PAGENUMBER, &ui16, &ui16_2 ) ) + TIFFSetField( out, TIFFTAG_PAGENUMBER, ui16, ui16_2 ); /* TIFFTAG_JPEGQUALITY is a pesudo-tag, so we can't copy it. * Set explicitly from Wtiff. @@ -1641,8 +1836,8 @@ wtiff_copy_tiff( Wtiff *wtiff, TIFF *out, TIFF *in ) /* Only for three-band, 8-bit images. */ - if( wtiff->im->Bands == 3 && - wtiff->im->BandFmt == VIPS_FORMAT_UCHAR ) { + if( wtiff->ready->Bands == 3 && + wtiff->ready->BandFmt == VIPS_FORMAT_UCHAR ) { /* Enable rgb->ycbcr conversion in the jpeg write. */ if( !wtiff->rgbjpeg && @@ -1702,7 +1897,7 @@ wtiff_copy_tiff( Wtiff *wtiff, TIFF *out, TIFF *in ) return( 0 ); } -/* Append all of the lower layers we wrote to the output. +/* Append all of the layers we wrote to the output. */ static int wtiff_gather( Wtiff *wtiff ) @@ -1713,26 +1908,36 @@ wtiff_gather( Wtiff *wtiff ) wtiff->layer->below ) for( layer = wtiff->layer->below; layer; layer = layer->below ) { + VipsSource *source; TIFF *in; #ifdef DEBUG - printf( "Appending layer %s ...\n", layer->lname ); + printf( "appending layer %s ...\n", layer->lname ); #endif /*DEBUG*/ if( layer->lname ) { - if( !(in = vips__tiff_openin( layer->lname )) ) + if( !(source = vips_source_new_from_file( + layer->lname )) ) return( -1 ); } else { - if( !(in = vips__tiff_openin_buffer( wtiff->im, - layer->buf, layer->len )) ) + if( !(source = vips_source_new_from_memory( + layer->buf, layer->len )) ) return( -1 ); } + if( !(in = vips__tiff_openin_source( source )) ) { + VIPS_UNREF( source ); + return( -1 ); + } + + VIPS_UNREF( source ); + if( wtiff_copy_tiff( wtiff, wtiff->layer->tif, in ) ) { TIFFClose( in ); return( -1 ); } + TIFFClose( in ); if( !TIFFWriteDirectory( wtiff->layer->tif ) ) @@ -1742,96 +1947,132 @@ wtiff_gather( Wtiff *wtiff ) return( 0 ); } -/* Three types of write: single image, multipage and pyramid. +/* Write one page from our input image, optionally pyramiding it. + */ +static int +wtiff_write_page( Wtiff *wtiff, VipsImage *page ) +{ +#ifdef DEBUG + printf( "wtiff_write_page:\n" ); +#endif /*DEBUG*/ + + /* Init the pyramid framework for this page. This will just make a + * single layer if we're not pyramiding. + */ + wtiff_layer_init( wtiff, &wtiff->layer, NULL, + page->Xsize, page->Ysize ); + + /* Fill all the layers and write the TIFF headers. + */ + if( wtiff_allocate_layers( wtiff ) ) + return( -1 ); + + /* In ifd mode, we write the pyramid layers as subdirectories of this + * page. + */ + if( wtiff->subifd ) { + int n_layers; + toff_t *subifd_offsets; + Layer *p; + +#ifdef DEBUG + printf( "wtiff_write_page: OME pyr mode\n" ); +#endif /*DEBUG*/ + + /* This magic tag makes the n_layers directories we write + * after this one into subdirectories. We set the offsets to 0 + * and libtiff will fill them in automatically. + */ + for( n_layers = 0, p = wtiff->layer->below; p; p = p->below ) + n_layers += 1; + subifd_offsets = VIPS_ARRAY( NULL, n_layers, toff_t ); + memset( subifd_offsets, 0, n_layers * sizeof( toff_t ) ); + TIFFSetField( wtiff->layer->tif, TIFFTAG_SUBIFD, + n_layers, subifd_offsets ); + g_free( subifd_offsets ); + } + + if( vips_sink_disc( page, write_strip, wtiff ) ) + return( -1 ); + + if( !TIFFWriteDirectory( wtiff->layer->tif ) ) + return( -1 ); + + /* Append any pyr layers, if necessary. + */ + if( wtiff->layer->below ) { + /* Free any lower pyramid resources ... this will + * TIFFClose() (but not delete) the smaller layers + * ready for us to read from them again. + */ + layer_free_all( wtiff->layer->below ); + + /* Append smaller layers to the main file. + */ + if( wtiff_gather( wtiff ) ) + return( -1 ); + + /* We can delete any temps now ready for the next page. + */ + wtiff_delete_temps( wtiff ); + + /* And free all lower pyr layers ready to be rebuilt for the + * next page. + */ + VIPS_FREEF( layer_free_all, wtiff->layer->below ); + } + + return( 0 ); +} + +/* Write all pages. */ static int wtiff_write_image( Wtiff *wtiff ) { - if( wtiff->toilet_roll ) { - int y; + int y; + + for( y = 0; y < wtiff->ready->Ysize; y += wtiff->page_height ) { + VipsImage *page; #ifdef DEBUG - printf( "wtiff_write_image: toilet-roll mode\n" ); + printf( "writing page %d ...\n", wtiff->page_number ); #endif /*DEBUG*/ - y = 0; - for(;;) { - VipsImage *page; - - if( vips_crop( wtiff->im, &page, - 0, y, wtiff->im->Xsize, wtiff->page_height, - NULL ) ) - return( -1 ); - if( vips_sink_disc( page, write_strip, wtiff ) ) { - g_object_unref( page ); - return( -1 ); - } + if( vips_crop( wtiff->ready, &page, + 0, y, wtiff->ready->Xsize, wtiff->page_height, + NULL ) ) + return( -1 ); + if( wtiff_write_page( wtiff, page ) ) { g_object_unref( page ); - - y += wtiff->page_height; - if( y >= wtiff->im->Ysize ) - break; - - if( !TIFFWriteDirectory( wtiff->layer->tif ) || - wtiff_layer_rewind( wtiff, - wtiff->layer ) || - wtiff_write_header( wtiff, - wtiff->layer ) ) - return( -1 ); + return( -1 ); } - } - else if( wtiff->pyramid ) { -#ifdef DEBUG - printf( "wtiff_write_image: pyramid mode\n" ); -#endif /*DEBUG*/ + g_object_unref( page ); - if( vips_sink_disc( wtiff->im, write_strip, wtiff ) ) - return( -1 ); - - if( !TIFFWriteDirectory( wtiff->layer->tif ) ) - return( -1 ); - - if( wtiff->pyramid ) { - /* Free lower pyramid resources ... this will - * TIFFClose() (but not delete) the smaller layers - * ready for us to read from them again. - */ - if( wtiff->layer->below ) - layer_free_all( wtiff->layer->below ); - - /* Append smaller layers to the main file. - */ - if( wtiff_gather( wtiff ) ) - return( -1 ); - } - } - else { -#ifdef DEBUG - printf( "wtiff_write_image: single-image mode\n" ); -#endif /*DEBUG*/ - - if( vips_sink_disc( wtiff->im, write_strip, wtiff ) ) - return( -1 ); + wtiff->page_number += 1; } return( 0 ); } int -vips__tiff_write( VipsImage *in, const char *filename, +vips__tiff_write( VipsImage *input, const char *filename, VipsForeignTiffCompression compression, int Q, VipsForeignTiffPredictor predictor, char *profile, gboolean tile, int tile_width, int tile_height, gboolean pyramid, - gboolean squash, + int bitdepth, gboolean miniswhite, VipsForeignTiffResunit resunit, double xres, double yres, gboolean bigtiff, gboolean rgbjpeg, gboolean properties, gboolean strip, VipsRegionShrink region_shrink, - int level, gboolean lossless ) + int level, + gboolean lossless, + VipsForeignDzDepth depth, + gboolean subifd ) { Wtiff *wtiff; @@ -1841,14 +2082,12 @@ vips__tiff_write( VipsImage *in, const char *filename, vips__tiff_init(); - if( vips_check_coding_known( "vips2tiff", in ) ) - return( -1 ); - - if( !(wtiff = wtiff_new( in, filename, + if( !(wtiff = wtiff_new( input, filename, compression, Q, predictor, profile, - tile, tile_width, tile_height, pyramid, squash, + tile, tile_width, tile_height, pyramid, bitdepth, miniswhite, resunit, xres, yres, bigtiff, rgbjpeg, - properties, strip, region_shrink, level, lossless )) ) + properties, strip, region_shrink, level, lossless, depth, + subifd )) ) return( -1 ); if( wtiff_write_image( wtiff ) ) { @@ -1862,34 +2101,35 @@ vips__tiff_write( VipsImage *in, const char *filename, } int -vips__tiff_write_buf( VipsImage *in, +vips__tiff_write_buf( VipsImage *input, void **obuf, size_t *olen, VipsForeignTiffCompression compression, int Q, VipsForeignTiffPredictor predictor, char *profile, gboolean tile, int tile_width, int tile_height, gboolean pyramid, - gboolean squash, + int bitdepth, gboolean miniswhite, VipsForeignTiffResunit resunit, double xres, double yres, gboolean bigtiff, gboolean rgbjpeg, gboolean properties, gboolean strip, VipsRegionShrink region_shrink, - int level, gboolean lossless ) + int level, + gboolean lossless, + VipsForeignDzDepth depth, + gboolean subifd ) { Wtiff *wtiff; vips__tiff_init(); - if( vips_check_coding_known( "vips2tiff", in ) ) - return( -1 ); - - if( !(wtiff = wtiff_new( in, NULL, + if( !(wtiff = wtiff_new( input, NULL, compression, Q, predictor, profile, - tile, tile_width, tile_height, pyramid, squash, + tile, tile_width, tile_height, pyramid, bitdepth, miniswhite, resunit, xres, yres, bigtiff, rgbjpeg, - properties, strip, region_shrink, level, lossless )) ) + properties, strip, region_shrink, level, lossless, depth, + subifd )) ) return( -1 ); wtiff->obuf = obuf; diff --git a/libvips/foreign/vips2webp.c b/libvips/foreign/vips2webp.c index a1dda939..c63f2a8d 100644 --- a/libvips/foreign/vips2webp.c +++ b/libvips/foreign/vips2webp.c @@ -16,6 +16,8 @@ * - support array of delays * 8/7/19 * - set loop even if we strip + * 14/10/19 + * - revise for target IO */ /* @@ -135,6 +137,7 @@ vips_webp_write_unset( VipsWebPWrite *write ) WebPMemoryWriterClear( &write->memory_writer ); VIPS_FREEF( WebPAnimEncoderDelete, write->enc ); VIPS_FREEF( WebPMuxDelete, write->mux ); + VIPS_UNREF( write->image ); } static int @@ -145,7 +148,7 @@ vips_webp_write_init( VipsWebPWrite *write, VipsImage *image, gboolean min_size, int kmin, int kmax, gboolean strip ) { - write->image = image; + write->image = NULL; write->Q = Q; write->lossless = lossless; write->preset = preset; @@ -161,6 +164,14 @@ vips_webp_write_init( VipsWebPWrite *write, VipsImage *image, write->enc = NULL; write->mux = NULL; + /* Rebuild exif on image. We must do this on a copy. + */ + if( vips_copy( image, &write->image, NULL ) || + vips__exif_update( write->image ) ) { + vips_webp_write_unset( write ); + return( -1 ); + } + if( !WebPConfigInit( &write->config ) ) { vips_webp_write_unset( write ); vips_error( "vips2webp", @@ -319,6 +330,7 @@ write_webp_anim( VipsWebPWrite *write, VipsImage *image, int page_height ) /* New images have an array of ints instead. */ + delay = NULL; if( vips_image_get_typeof( image, "delay" ) && vips_image_get_array_int( image, "delay", &delay, &delay_length ) ) @@ -359,7 +371,7 @@ write_webp_anim( VipsWebPWrite *write, VipsImage *image, int page_height ) timestamp_ms += gif_delay * 10; } - /* Closes encoder and add last frame delay. + /* Closes encoder and adds last frame delay. */ if( !WebPAnimEncoderAdd( write->enc, NULL, timestamp_ms, NULL ) ) { @@ -388,14 +400,14 @@ write_webp_anim( VipsWebPWrite *write, VipsImage *image, int page_height ) } static int -write_webp( VipsWebPWrite *write, VipsImage *image ) +write_webp( VipsWebPWrite *write ) { - int page_height = vips_image_get_page_height( image ); + int page_height = vips_image_get_page_height( write->image ); - if( page_height < image->Ysize ) - return( write_webp_anim( write, image, page_height ) ); + if( page_height < write->image->Ysize ) + return( write_webp_anim( write, write->image, page_height ) ); else - return( write_webp_single( write, image ) ); + return( write_webp_single( write, write->image ) ); } static void @@ -435,7 +447,7 @@ vips_webp_set_chunk( VipsWebPWrite *write, } static int -vips_webp_add_chunks( VipsWebPWrite *write, VipsImage *image ) +vips_webp_add_chunks( VipsWebPWrite *write ) { int i; @@ -443,11 +455,11 @@ vips_webp_add_chunks( VipsWebPWrite *write, VipsImage *image ) const char *vips_name = vips__webp_names[i].vips; const char *webp_name = vips__webp_names[i].webp; - if( vips_image_get_typeof( image, vips_name ) ) { + if( vips_image_get_typeof( write->image, vips_name ) ) { const void *data; size_t length; - if( vips_image_get_blob( image, + if( vips_image_get_blob( write->image, vips_name, &data, &length ) || vips_webp_set_chunk( write, webp_name, data, length ) ) @@ -459,15 +471,10 @@ vips_webp_add_chunks( VipsWebPWrite *write, VipsImage *image ) } static int -vips_webp_add_metadata( VipsWebPWrite *write, VipsImage *image, gboolean strip ) +vips_webp_add_metadata( VipsWebPWrite *write ) { WebPData data; - /* Rebuild the EXIF block, if any, ready for writing. - */ - if( vips__exif_update( image ) ) - return( -1 ); - data.bytes = write->memory_writer.mem; data.size = write->memory_writer.size; @@ -478,19 +485,29 @@ vips_webp_add_metadata( VipsWebPWrite *write, VipsImage *image, gboolean strip ) return( -1 ); } - if( vips_image_get_typeof( image, "gif-loop" ) ) { - int gif_loop; + if( vips_image_get_typeof( write->image, "loop" ) ) { + int loop; - if( vips_image_get_int( image, "gif-loop", &gif_loop ) ) + if( vips_image_get_int( write->image, "loop", &loop ) ) return( -1 ); - vips_webp_set_count( write, gif_loop ); + vips_webp_set_count( write, loop ); + } + /* DEPRECATED "gif-loop" + */ + else if ( vips_image_get_typeof( write->image, "gif-loop" ) ) { + int gif_loop; + + if( vips_image_get_int( write->image, "gif-loop", &gif_loop ) ) + return( -1 ); + + vips_webp_set_count( write, gif_loop == 0 ? 0 : gif_loop + 1 ); } /* Add extra metadata. */ - if( !strip && - vips_webp_add_chunks( write, image ) ) + if( !write->strip && + vips_webp_add_chunks( write ) ) return( -1 ); if( WebPMuxAssemble( write->mux, &data ) != WEBP_MUX_OK ) { @@ -508,52 +525,7 @@ vips_webp_add_metadata( VipsWebPWrite *write, VipsImage *image, gboolean strip ) } int -vips__webp_write_file( VipsImage *image, const char *filename, - int Q, gboolean lossless, VipsForeignWebpPreset preset, - gboolean smart_subsample, gboolean near_lossless, - int alpha_q, int reduction_effort, - gboolean min_size, int kmin, int kmax, - gboolean strip ) -{ - VipsWebPWrite write; - FILE *fp; - - if( vips_webp_write_init( &write, image, - Q, lossless, preset, smart_subsample, near_lossless, - alpha_q, reduction_effort, min_size, kmin, kmax, strip ) ) - return( -1 ); - - if( write_webp( &write, image ) ) { - vips_webp_write_unset( &write ); - return( -1 ); - } - - if( vips_webp_add_metadata( &write, image, strip ) ) { - vips_webp_write_unset( &write ); - return( -1 ); - } - - if( !(fp = vips__file_open_write( filename, FALSE )) ) { - vips_webp_write_unset( &write ); - return( -1 ); - } - - if( vips__file_write( - write.memory_writer.mem, write.memory_writer.size, 1, fp ) ) { - fclose( fp ); - vips_webp_write_unset( &write ); - return( -1 ); - } - - fclose( fp ); - - vips_webp_write_unset( &write ); - - return( 0 ); -} - -int -vips__webp_write_buffer( VipsImage *image, void **obuf, size_t *olen, +vips__webp_write_target( VipsImage *image, VipsTarget *target, int Q, gboolean lossless, VipsForeignWebpPreset preset, gboolean smart_subsample, gboolean near_lossless, int alpha_q, int reduction_effort, @@ -567,21 +539,23 @@ vips__webp_write_buffer( VipsImage *image, void **obuf, size_t *olen, alpha_q, reduction_effort, min_size, kmin, kmax, strip ) ) return( -1 ); - if( write_webp( &write, image ) ) { + if( write_webp( &write ) ) { vips_webp_write_unset( &write ); return( -1 ); } - if( vips_webp_add_metadata( &write, image, strip ) ) { + if( vips_webp_add_metadata( &write ) ) { vips_webp_write_unset( &write ); return( -1 ); } - *obuf = write.memory_writer.mem; - *olen = write.memory_writer.size; - write.memory_writer.mem = NULL; - write.memory_writer.size = 0; - write.memory_writer.max_size = 0; + if( vips_target_write( target, + write.memory_writer.mem, write.memory_writer.size ) ) { + vips_webp_write_unset( &write ); + return( -1 ); + } + + vips_target_finish( target ); vips_webp_write_unset( &write ); diff --git a/libvips/foreign/vipspng.c b/libvips/foreign/vipspng.c index 1ee5aba4..c5c3e730 100644 --- a/libvips/foreign/vipspng.c +++ b/libvips/foreign/vipspng.c @@ -73,6 +73,12 @@ * - support xmp read/write * 20/4/19 * - allow huge xmp metadata + * 7/10/19 + * - restart after minimise + * 14/10/19 + * - revise for connection IO + * 11/5/20 + * - only warn for saving bad profiles, don't fail */ /* @@ -155,6 +161,8 @@ user_warning_function( png_structp png_ptr, png_const_charp warning_msg ) g_warning( "%s", warning_msg ); } +#define INPUT_BUFFER_SIZE (4096) + /* What we track during a PNG read. */ typedef struct { @@ -167,15 +175,14 @@ typedef struct { png_infop pInfo; png_bytep *row_pointer; - /* For FILE input. - */ - FILE *fp; + VipsSource *source; - /* For memory input. + /* read() to this buffer, copy to png as required. libpng does many + * very small reads and we want to avoid a syscall for each one. */ - const void *buffer; - size_t length; - size_t read_pos; + unsigned char input_buffer[INPUT_BUFFER_SIZE]; + unsigned char *next_byte; + gint64 bytes_in_buffer; } Read; @@ -184,9 +191,13 @@ typedef struct { static void read_destroy( Read *read ) { - VIPS_FREEF( fclose, read->fp ); + /* We never call png_read_end(), perhaps we should. It can fail on + * truncated files, so we'd need a setjmp(). + */ + if( read->pPng ) png_destroy_read_struct( &read->pPng, &read->pInfo, NULL ); + VIPS_UNREF( read->source ); VIPS_FREE( read->row_pointer ); } @@ -197,23 +208,50 @@ read_close_cb( VipsImage *out, Read *read ) } static void -read_minimise_cb( VipsImage *out, Read *read ) +read_minimise_cb( VipsImage *image, Read *read ) { - /* Catch errors from png_read_end(). This can fail on a truncated - * file. - */ - if( read->pPng ) { - /* Catch and ignore error returns from png_read_end(). - */ - if( !setjmp( png_jmpbuf( read->pPng ) ) ) - png_read_end( read->pPng, NULL ); - } + if( read->source ) + vips_source_minimise( read->source ); +} - read_destroy( read ); +static void +vips_png_read_source( png_structp pPng, png_bytep data, png_size_t length ) +{ + Read *read = png_get_io_ptr( pPng ); + +#ifdef DEBUG + printf( "vips_png_read_source: read %zd bytes\n", length ); +#endif /*DEBUG*/ + + /* libpng makes many small reads, which hurts performance if you do a + * syscall for each one. Read via our own buffer. + */ + while( length > 0 ) { + gint64 bytes_available; + + if( read->bytes_in_buffer <= 0 ) { + gint64 bytes_read; + + bytes_read = vips_source_read( read->source, + read->input_buffer, INPUT_BUFFER_SIZE ); + if( bytes_read <= 0 ) + png_error( pPng, "not enough data" ); + + read->next_byte = read->input_buffer; + read->bytes_in_buffer = bytes_read; + } + + bytes_available = VIPS_MIN( read->bytes_in_buffer, length ); + memcpy( data, read->next_byte, bytes_available ); + data += bytes_available; + length -= bytes_available; + read->next_byte += bytes_available; + read->bytes_in_buffer -= bytes_available; + } } static Read * -read_new( VipsImage *out, gboolean fail ) +read_new( VipsSource *source, VipsImage *out, gboolean fail ) { Read *read; @@ -227,14 +265,12 @@ read_new( VipsImage *out, gboolean fail ) read->pPng = NULL; read->pInfo = NULL; read->row_pointer = NULL; - read->fp = NULL; - read->buffer = NULL; - read->length = 0; - read->read_pos = 0; + read->source = source; + g_object_ref( source ); g_signal_connect( out, "close", G_CALLBACK( read_close_cb ), read ); - g_signal_connect( out, "minimise", + g_signal_connect( out, "minimise", G_CALLBACK( read_minimise_cb ), read ); if( !(read->pPng = png_create_read_struct( @@ -242,21 +278,27 @@ read_new( VipsImage *out, gboolean fail ) user_error_function, user_warning_function )) ) return( NULL ); -#ifdef PNG_SKIP_sRGB_CHECK_PROFILE - /* Prevent libpng (>=1.6.11) verifying sRGB profiles. + /* Prevent libpng (>=1.6.11) verifying sRGB profiles. Many PNGs have + * broken profiles, but we still want to be able to open them. */ +#ifdef PNG_SKIP_sRGB_CHECK_PROFILE png_set_option( read->pPng, PNG_SKIP_sRGB_CHECK_PROFILE, PNG_OPTION_ON ); #endif /*PNG_SKIP_sRGB_CHECK_PROFILE*/ -#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION - /* Disable CRC checking in fuzzing mode. + /* Disable CRC checking in fuzzing mode. Most fuzzed images will have + * bad CRCs so this check would break fuzzing. */ +#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION png_set_crc_action( read->pPng, PNG_CRC_QUIET_USE, PNG_CRC_QUIET_USE ); #endif /*FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION*/ - /* Catch PNG errors from png_create_info_struct(). + if( vips_source_rewind( source ) ) + return( NULL ); + png_set_read_fn( read->pPng, read, vips_png_read_source ); + + /* Catch PNG errors from png_read_info() etc. */ if( setjmp( png_jmpbuf( read->pPng ) ) ) return( NULL ); @@ -264,43 +306,14 @@ read_new( VipsImage *out, gboolean fail ) if( !(read->pInfo = png_create_info_struct( read->pPng )) ) return( NULL ); - return( read ); -} - -static void -read_info( Read *read ) -{ /* By default, libpng refuses to open files with a metadata chunk * larger than 8mb. We've seen real files with 20mb, so set 50mb. */ #ifdef HAVE_PNG_SET_CHUNK_MALLOC_MAX png_set_chunk_malloc_max( read->pPng, 50 * 1024 * 1024 ); #endif /*HAVE_PNG_SET_CHUNK_MALLOC_MAX*/ + png_read_info( read->pPng, read->pInfo ); -} - -static Read * -read_new_filename( VipsImage *out, const char *name, gboolean fail ) -{ - Read *read; - - if( !(read = read_new( out, fail )) ) - return( NULL ); - - read->name = vips_strdup( VIPS_OBJECT( out ), name ); - - if( !(read->fp = vips__file_open_read( name, NULL, FALSE )) ) - return( NULL ); - - /* Catch PNG errors from png_read_info(). - */ - if( setjmp( png_jmpbuf( read->pPng ) ) ) { - read_destroy( read ); - return( NULL ); - } - - png_init_io( read->pPng, read->fp ); - read_info( read ); return( read ); } @@ -342,7 +355,7 @@ static int png2vips_header( Read *read, VipsImage *out ) { png_uint_32 width, height; - int bit_depth, color_type; + int bitdepth, color_type; int interlace_type; png_uint_32 res_x, res_y; @@ -372,7 +385,7 @@ png2vips_header( Read *read, VipsImage *out ) return( -1 ); png_get_IHDR( read->pPng, read->pInfo, - &width, &height, &bit_depth, &color_type, + &width, &height, &bitdepth, &color_type, &interlace_type, NULL, NULL ); /* png_get_channels() gives us 1 band for palette images ... so look @@ -400,7 +413,7 @@ png2vips_header( Read *read, VipsImage *out ) return( -1 ); } - if( bit_depth > 8 ) { + if( bitdepth > 8 ) { if( bands < 3 ) interpretation = VIPS_INTERPRETATION_GREY16; else @@ -435,13 +448,13 @@ png2vips_header( Read *read, VipsImage *out ) /* Expand <8 bit images to full bytes. */ if( color_type == PNG_COLOR_TYPE_GRAY && - bit_depth < 8 ) + bitdepth < 8 ) png_set_expand_gray_1_2_4_to_8( read->pPng ); /* If we're an INTEL byte order machine and this is 16bits, we need * to swap bytes. */ - if( bit_depth > 8 && + if( bitdepth > 8 && !vips_amiMSBfirst() ) png_set_swap( read->pPng ); @@ -467,11 +480,13 @@ png2vips_header( Read *read, VipsImage *out ) */ vips_image_init_fields( out, width, height, bands, - bit_depth > 8 ? - VIPS_FORMAT_USHORT : VIPS_FORMAT_UCHAR, + bitdepth > 8 ? VIPS_FORMAT_USHORT : VIPS_FORMAT_UCHAR, VIPS_CODING_NONE, interpretation, Xres, Yres ); + VIPS_SETSTR( out->filename, + vips_connection_filename( VIPS_CONNECTION( read->source ) ) ); + /* Uninterlaced images will be read in seq mode. Interlaced images are * read via a huge memory buffer. */ @@ -540,25 +555,7 @@ png2vips_header( Read *read, VipsImage *out ) /* Attach original palette bit depth, if any, as metadata. */ if( color_type == PNG_COLOR_TYPE_PALETTE ) - vips_image_set_int( out, "palette-bit-depth", bit_depth ); - - return( 0 ); -} - -/* Read a PNG file header into a VIPS header. - */ -int -vips__png_header( const char *name, VipsImage *out ) -{ - Read *read; - - if( !(read = read_new_filename( out, name, TRUE )) || - png2vips_header( read, out ) ) - return( -1 ); - - /* Just a header read: we can free the read early and save an fd. - */ - read_destroy( read ); + vips_image_set_int( out, "palette-bit-depth", bitdepth ); return( 0 ); } @@ -587,8 +584,6 @@ png2vips_interlace( Read *read, VipsImage *out ) png_read_image( read->pPng, read->row_pointer ); - png_read_end( read->pPng, NULL ); - read_destroy( read ); return( 0 ); @@ -619,7 +614,7 @@ png2vips_generate( VipsRegion *or, * strip. */ g_assert( r->height == VIPS_MIN( VIPS__FATSTRIP_HEIGHT, - or->im->Ysize - r->top ) ); + or->im->Ysize - r->top ) ); /* And check that y_pos is correct. It should be, since we are inside * a vips_sequential(). @@ -665,49 +660,9 @@ png2vips_generate( VipsRegion *or, read->y_pos += 1; } - /* Catch errors from png_read_end(). This can fail on a truncated - * file. - */ - if( setjmp( png_jmpbuf( read->pPng ) ) ) { - if( read->fail ) { - vips_error( "vipspng", "%s", _( "libpng read error" ) ); - return( -1 ); - } - - return( 0 ); - } - - /* Early close to free the fd as soon as we can. - */ - if( read->y_pos >= read->out->Ysize ) { - png_read_end( read->pPng, NULL ); - read_destroy( read ); - } - return( 0 ); } -/* Interlaced PNGs need to be entirely decompressed into memory then can be - * served partially from there. Non-interlaced PNGs may be read sequentially. - */ -gboolean -vips__png_isinterlaced( const char *filename ) -{ - VipsImage *image; - Read *read; - int interlace_type; - - image = vips_image_new(); - if( !(read = read_new_filename( image, filename, TRUE )) ) { - g_object_unref( image ); - return( -1 ); - } - interlace_type = png_get_interlace_type( read->pPng, read->pInfo ); - g_object_unref( image ); - - return( interlace_type != PNG_INTERLACE_NONE ); -} - static int png2vips_image( Read *read, VipsImage *out ) { @@ -741,108 +696,41 @@ png2vips_image( Read *read, VipsImage *out ) return( 0 ); } -int -vips__png_read( const char *filename, VipsImage *out, gboolean fail ) -{ - Read *read; - -#ifdef DEBUG - printf( "vips__png_read: reading \"%s\"\n", filename ); -#endif /*DEBUG*/ - - if( !(read = read_new_filename( out, filename, fail )) || - png2vips_image( read, out ) ) - return( -1 ); - -#ifdef DEBUG - printf( "vips__png_read: done\n" ); -#endif /*DEBUG*/ - - return( 0 ); -} - gboolean -vips__png_ispng_buffer( const void *buf, size_t len ) +vips__png_ispng_source( VipsSource *source ) { - if( len >= 8 && - !png_sig_cmp( (png_bytep) buf, 0, 8 ) ) + const unsigned char *p; + + if( (p = vips_source_sniff( source, 8 )) && + !png_sig_cmp( (png_bytep) p, 0, 8 ) ) return( TRUE ); return( FALSE ); } int -vips__png_ispng( const char *filename ) -{ - unsigned char buf[8]; - - return( vips__get_bytes( filename, buf, 8 ) == 8 && - vips__png_ispng_buffer( buf, 8 ) ); -} - -static void -vips_png_read_buffer( png_structp pPng, png_bytep data, png_size_t length ) -{ - Read *read = png_get_io_ptr( pPng ); - -#ifdef DEBUG - printf( "vips_png_read_buffer: read %zd bytes\n", length ); -#endif /*DEBUG*/ - - if( read->read_pos + length > read->length ) - png_error( pPng, "not enough data in buffer" ); - - memcpy( data, (VipsPel *) read->buffer + read->read_pos, length ); - read->read_pos += length; -} - -static Read * -read_new_buffer( VipsImage *out, const void *buffer, size_t length, - gboolean fail ) +vips__png_header_source( VipsSource *source, VipsImage *out ) { Read *read; - if( !(read = read_new( out, fail )) ) - return( NULL ); - - read->length = length; - read->buffer = buffer; - - png_set_read_fn( read->pPng, read, vips_png_read_buffer ); - - /* Catch PNG errors from png_read_info(). - */ - if( setjmp( png_jmpbuf( read->pPng ) ) ) { - read_destroy( read ); - return( NULL ); - } - - read_info( read ); - - return( read ); -} - -int -vips__png_header_buffer( const void *buffer, size_t length, VipsImage *out ) -{ - Read *read; - - if( !(read = read_new_buffer( out, buffer, length, TRUE )) || - png2vips_header( read, out ) ) + if( !(read = read_new( source, out, TRUE )) || + png2vips_header( read, out ) ) return( -1 ); + vips_source_minimise( source ); + return( 0 ); } int -vips__png_read_buffer( const void *buffer, size_t length, VipsImage *out, - gboolean fail ) +vips__png_read_source( VipsSource *source, VipsImage *out, gboolean fail ) { Read *read; - if( !(read = read_new_buffer( out, buffer, length, fail )) || - png2vips_image( read, out ) ) - return( -1 ); + if( !(read = read_new( source, out, fail )) || + png2vips_image( read, out ) || + vips_source_decode( source ) ) + return( -1 ); return( 0 ); } @@ -851,7 +739,7 @@ vips__png_read_buffer( const void *buffer, size_t length, VipsImage *out, * served partially from there. Non-interlaced PNGs may be read sequentially. */ gboolean -vips__png_isinterlaced_buffer( const void *buffer, size_t length ) +vips__png_isinterlaced_source( VipsSource *source ) { VipsImage *image; Read *read; @@ -859,7 +747,7 @@ vips__png_isinterlaced_buffer( const void *buffer, size_t length ) image = vips_image_new(); - if( !(read = read_new_buffer( image, buffer, length, TRUE )) ) { + if( !(read = read_new( source, image, TRUE )) ) { g_object_unref( image ); return( -1 ); } @@ -877,8 +765,7 @@ typedef struct { VipsImage *in; VipsImage *memory; - FILE *fp; - VipsDbuf dbuf; + VipsTarget *target; png_structp pPng; png_infop pInfo; @@ -888,55 +775,69 @@ typedef struct { static void write_finish( Write *write ) { - VIPS_FREEF( fclose, write->fp ); VIPS_UNREF( write->memory ); - vips_dbuf_destroy( &write->dbuf ); + if( write->target ) + vips_target_finish( write->target ); + VIPS_UNREF( write->target ); if( write->pPng ) png_destroy_write_struct( &write->pPng, &write->pInfo ); + VIPS_FREE( write->row_pointer ); + VIPS_FREE( write ); } static void -write_destroy( VipsImage *out, Write *write ) +user_write_data( png_structp pPng, png_bytep data, png_size_t length ) { - write_finish( write ); + Write *write = (Write *) png_get_io_ptr( pPng ); + + if( vips_target_write( write->target, data, length ) ) + png_error( pPng, "not enough data" ); } static Write * -write_new( VipsImage *in ) +write_new( VipsImage *in, VipsTarget *target ) { Write *write; - if( !(write = VIPS_NEW( in, Write )) ) + if( !(write = VIPS_NEW( NULL, Write )) ) return( NULL ); memset( write, 0, sizeof( Write ) ); write->in = in; write->memory = NULL; - write->fp = NULL; - vips_dbuf_init( &write->dbuf ); - g_signal_connect( in, "close", - G_CALLBACK( write_destroy ), write ); + write->target = target; + g_object_ref( target ); - if( !(write->row_pointer = VIPS_ARRAY( in, in->Ysize, png_bytep )) ) + if( !(write->row_pointer = VIPS_ARRAY( NULL, in->Ysize, png_bytep )) ) return( NULL ); if( !(write->pPng = png_create_write_struct( PNG_LIBPNG_VER_STRING, NULL, - user_error_function, user_warning_function )) ) + user_error_function, user_warning_function )) ) { + write_finish( write ); return( NULL ); + } -#ifdef PNG_SKIP_sRGB_CHECK_PROFILE - /* Prevent libpng (>=1.6.11) verifying sRGB profiles. + /* Prevent libpng (>=1.6.11) verifying sRGB profiles. We are often + * asked to copy images containing bad profiles, and this check would + * prevent that. */ +#ifdef PNG_SKIP_sRGB_CHECK_PROFILE png_set_option( write->pPng, PNG_SKIP_sRGB_CHECK_PROFILE, PNG_OPTION_ON ); #endif /*PNG_SKIP_sRGB_CHECK_PROFILE*/ + png_set_write_fn( write->pPng, write, user_write_data, NULL ); + /* Catch PNG errors from png_create_info_struct(). */ - if( setjmp( png_jmpbuf( write->pPng ) ) ) + if( setjmp( png_jmpbuf( write->pPng ) ) ) { + write_finish( write ); return( NULL ); + } - if( !(write->pInfo = png_create_info_struct( write->pPng )) ) + if( !(write->pInfo = png_create_info_struct( write->pPng )) ) { + write_finish( write ); return( NULL ); + } return( write ); } @@ -954,7 +855,7 @@ write_png_block( VipsRegion *region, VipsRect *area, void *a ) g_assert( area->width == region->im->Xsize ); g_assert( area->top + area->height <= region->im->Ysize ); - /* Catch PNG errors. Yuk. + /* Catch PNG errors. */ if( setjmp( png_jmpbuf( write->pPng ) ) ) return( -1 ); @@ -1022,11 +923,11 @@ static int write_vips( Write *write, int compress, int interlace, const char *profile, VipsForeignPngFilter filter, gboolean strip, - gboolean palette, int colours, int Q, double dither ) + gboolean palette, int Q, double dither, + int bitdepth ) { VipsImage *in = write->in; - int bit_depth; int color_type; int interlace_type; int i, nb_passes; @@ -1068,8 +969,6 @@ write_vips( Write *write, */ png_set_filter( write->pPng, 0, filter ); - bit_depth = in->BandFmt == VIPS_FORMAT_UCHAR ? 8 : 16; - switch( in->Bands ) { case 1: color_type = PNG_COLOR_TYPE_GRAY; break; case 2: color_type = PNG_COLOR_TYPE_GRAY_ALPHA; break; @@ -1085,12 +984,8 @@ write_vips( Write *write, #ifdef HAVE_IMAGEQUANT /* Enable image quantisation to paletted 8bpp PNG if colours is set. */ - if( palette ) { - g_assert( colours >= 2 && - colours <= 256 ); - bit_depth = 8; + if( palette ) color_type = PNG_COLOR_TYPE_PALETTE; - } #else if( palette ) g_warning( "%s", @@ -1100,7 +995,7 @@ write_vips( Write *write, interlace_type = interlace ? PNG_INTERLACE_ADAM7 : PNG_INTERLACE_NONE; png_set_IHDR( write->pPng, write->pInfo, - in->Xsize, in->Ysize, bit_depth, color_type, interlace_type, + in->Xsize, in->Ysize, bitdepth, color_type, interlace_type, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT ); /* Set resolution. libpng uses pixels per meter. @@ -1147,10 +1042,28 @@ write_vips( Write *write, "of ICC profile\n", length ); #endif /*DEBUG*/ - png_set_iCCP( write->pPng, write->pInfo, "icc", - PNG_COMPRESSION_TYPE_BASE, - (void *) data, length ); + /* We need to ignore any errors from png_set_iCCP() + * since we want to drop incompatible profiles rather + * than simply failing. + */ + if( setjmp( png_jmpbuf( write->pPng ) ) ) { + /* Silent ignore of error. + */ + g_warning( "bad ICC profile not saved" ); + } + else { + /* This will jump back to the line above on + * error. + */ + png_set_iCCP( write->pPng, write->pInfo, "icc", + PNG_COMPRESSION_TYPE_BASE, + (void *) data, length ); + } + /* And restore the setjmp. + */ + if( setjmp( png_jmpbuf( write->pPng ) ) ) + return( -1 ); } if( vips_image_get_typeof( in, VIPS_META_XMP_NAME ) ) { @@ -1187,7 +1100,7 @@ write_vips( Write *write, int trans_count; if( vips__quantise_image( in, &im_index, &im_palette, - colours, Q, dither ) ) + 1 << bitdepth, Q, dither ) ) return( -1 ); palette_count = im_palette->Xsize; @@ -1247,10 +1160,14 @@ write_vips( Write *write, /* If we're an intel byte order CPU and this is a 16bit image, we need * to swap bytes. */ - if( bit_depth > 8 && + if( bitdepth > 8 && !vips_amiMSBfirst() ) png_set_swap( write->pPng ); + /* If bitdepth is 1/2/4, pack pixels into bytes. + */ + png_set_packing( write->pPng ); + if( interlace ) nb_passes = png_set_interlace_handling( write->pPng ); else @@ -1273,80 +1190,26 @@ write_vips( Write *write, } int -vips__png_write( VipsImage *in, const char *filename, - int compress, int interlace, const char *profile, - VipsForeignPngFilter filter, gboolean strip, - gboolean palette, int colours, int Q, double dither ) -{ - Write *write; - -#ifdef DEBUG - printf( "vips__png_write: writing \"%s\"\n", filename ); -#endif /*DEBUG*/ - - if( !(write = write_new( in )) ) - return( -1 ); - - /* Make output. - */ - if( !(write->fp = vips__file_open_write( filename, FALSE )) ) - return( -1 ); - png_init_io( write->pPng, write->fp ); - - /* Convert it! - */ - if( write_vips( write, - compress, interlace, profile, filter, strip, palette, - colours, Q, dither ) ) { - vips_error( "vips2png", - _( "unable to write \"%s\"" ), filename ); - - return( -1 ); - } - - write_finish( write ); - -#ifdef DEBUG - printf( "vips__png_write: done\n" ); -#endif /*DEBUG*/ - - return( 0 ); -} - -static void -user_write_data( png_structp png_ptr, png_bytep data, png_size_t length ) -{ - Write *write = (Write *) png_get_io_ptr( png_ptr ); - - vips_dbuf_write( &write->dbuf, data, length ); -} - -int -vips__png_write_buf( VipsImage *in, - void **obuf, size_t *olen, int compression, int interlace, +vips__png_write_target( VipsImage *in, VipsTarget *target, + int compression, int interlace, const char *profile, VipsForeignPngFilter filter, gboolean strip, - gboolean palette, int colours, int Q, double dither ) + gboolean palette, int Q, double dither, + int bitdepth ) { Write *write; - if( !(write = write_new( in )) ) + if( !(write = write_new( in, target )) ) return( -1 ); - png_set_write_fn( write->pPng, write, user_write_data, NULL ); - - /* Convert it! - */ if( write_vips( write, compression, interlace, profile, filter, strip, palette, - colours, Q, dither ) ) { + Q, dither, bitdepth ) ) { + write_finish( write ); vips_error( "vips2png", - "%s", _( "unable to write to buffer" ) ); - + "%s", _( "unable to write to target" ) ); return( -1 ); } - *obuf = vips_dbuf_steal( &write->dbuf, olen ); - write_finish( write ); return( 0 ); diff --git a/libvips/foreign/webp2vips.c b/libvips/foreign/webp2vips.c index 90bf3a04..2bf6edba 100644 --- a/libvips/foreign/webp2vips.c +++ b/libvips/foreign/webp2vips.c @@ -24,6 +24,8 @@ * - disable alpha output if all frame fill the canvas and are solid * 6/7/19 [deftomat] * - support array of delays + * 14/10/19 + * - revise for source IO */ /* @@ -79,15 +81,11 @@ /* What we track during a read. */ typedef struct { - /* File source. - */ - char *filename; + VipsSource *source; - /* Memory source. We use gint64 rather than size_t since we use - * vips_file_length() and vips__mmap() for file sources. + /* The data we load, as a webp object. */ - const void *data; - gint64 length; + WebPData data; /* Load this page (frame number). */ @@ -128,10 +126,6 @@ typedef struct { */ int *delays; - /* If we are opening a file object, the fd. - */ - int fd; - /* Parse with this. */ WebPDemuxer *demux; @@ -317,57 +311,29 @@ vips_image_paint_image( VipsImage *frame, } int -vips__iswebp_buffer( const void *buf, size_t len ) +vips__iswebp_source( VipsSource *source ) { + const unsigned char *p; + /* WebP is "RIFF xxxx WEBP" at the start, so we need 12 bytes. */ - if( len >= 12 && - vips_isprefix( "RIFF", (char *) buf ) && - vips_isprefix( "WEBP", (char *) buf + 8 ) ) + if( (p = vips_source_sniff( source, 12 )) && + vips_isprefix( "RIFF", (char *) p ) && + vips_isprefix( "WEBP", (char *) p + 8 ) ) return( 1 ); return( 0 ); } -int -vips__iswebp( const char *filename ) -{ - /* Magic number, see above. - */ - unsigned char header[12]; - - if( vips__get_bytes( filename, header, 12 ) == 12 && - vips__iswebp_buffer( header, 12 ) ) - return( 1 ); - - return( 0 ); -} - -static void -read_minimise( Read *read ) +static int +read_free( Read *read ) { WebPDemuxReleaseIterator( &read->iter ); VIPS_UNREF( read->frame ); VIPS_FREEF( WebPDemuxDelete, read->demux ); WebPFreeDecBuffer( &read->config.output ); - if( read->fd > 0 && - read->data && - read->length > 0 ) { - vips__munmap( read->data, read->length ); - read->data = NULL; - read->length = 0; - } - - VIPS_FREEF( vips_tracked_close, read->fd ); -} - -static int -read_free( Read *read ) -{ - read_minimise( read ); - - VIPS_FREE( read->filename ); + VIPS_UNREF( read->source ); VIPS_FREE( read->delays ); VIPS_FREE( read ); @@ -375,46 +341,34 @@ read_free( Read *read ) } static Read * -read_new( const char *filename, const void *data, size_t length, - int page, int n, double scale ) +read_new( VipsSource *source, int page, int n, double scale ) { Read *read; if( !(read = VIPS_NEW( NULL, Read )) ) return( NULL ); - read->filename = g_strdup( filename ); - read->data = data; - read->length = length; + read->source = source; + g_object_ref( source ); read->page = page; read->n = n; read->scale = scale; read->delays = NULL; - read->fd = 0; read->demux = NULL; read->frame = NULL; read->dispose_method = WEBP_MUX_DISPOSE_NONE; read->frame_no = 0; - if( read->filename ) { - /* libwebp makes streaming from a file source very hard. We - * have to read to a full memory buffer, then copy to out. - * - * mmap the input file, it's slightly quicker. - */ - if( (read->fd = vips__open_image_read( read->filename )) < 0 || - (read->length = vips_file_length( read->fd )) < 0 || - !(read->data = vips__mmap( read->fd, - FALSE, read->length, 0 )) ) { - read_free( read ); - return( NULL ); - } - } - WebPInitDecoderConfig( &read->config ); read->config.options.use_threads = 1; read->config.output.is_external_memory = 1; + if( !(read->data.bytes = + vips_source_map( source, &read->data.size )) ) { + read_free( read ); + return( NULL ); + } + return( read ); } @@ -430,13 +384,10 @@ const int vips__n_webp_names = VIPS_NUMBER( vips__webp_names ); static int read_header( Read *read, VipsImage *out ) { - WebPData data; int flags; int i; - data.bytes = read->data; - data.size = read->length; - if( !(read->demux = WebPDemux( &data )) ) { + if( !(read->demux = WebPDemux( &read->data )) ) { vips_error( "webp", "%s", _( "unable to parse image" ) ); return( -1 ); } @@ -481,7 +432,16 @@ read_header( Read *read, VipsImage *out ) printf( "webp2vips: frame_count = %d\n", read->frame_count ); #endif /*DEBUG*/ - vips_image_set_int( out, "gif-loop", loop_count ); + vips_image_set_int( out, "loop", loop_count ); + + /* DEPRECATED "gif-loop" + * + * Not the correct behavior as loop=1 became gif-loop=0 + * but we want to keep the old behavior untouched! + */ + vips_image_set_int( out, "gif-loop", + loop_count == 0 ? 0 : loop_count - 1 ); + vips_image_set_int( out, VIPS_META_PAGE_HEIGHT, read->frame_height ); @@ -495,7 +455,7 @@ read_header( Read *read, VipsImage *out ) do { g_assert( iter.frame_num >= 1 && - iter.frame_num < read->frame_count ); + iter.frame_num <= read->frame_count ); read->delays[iter.frame_num - 1] = iter.duration; @@ -589,6 +549,8 @@ read_header( Read *read, VipsImage *out ) VIPS_INTERPRETATION_sRGB, 1.0, 1.0 ); vips_image_pipelinev( out, VIPS_DEMAND_STYLE_THINSTRIP, NULL ); + VIPS_SETSTR( out->filename, + vips_connection_filename( VIPS_CONNECTION( read->source ) ) ); if( !WebPDemuxGetFrame( read->demux, 1, &read->iter ) ) { vips_error( "webp", @@ -599,28 +561,6 @@ read_header( Read *read, VipsImage *out ) return( 0 ); } -int -vips__webp_read_file_header( const char *filename, VipsImage *out, - int page, int n, double scale ) -{ - Read *read; - - if( !(read = read_new( filename, NULL, 0, page, n, scale )) ) { - vips_error( "webp2vips", - _( "unable to open \"%s\"" ), filename ); - return( -1 ); - } - - if( read_header( read, out ) ) { - read_free( read ); - return( -1 ); - } - - read_free( read ); - - return( 0 ); -} - /* Read a single frame -- a width * height block of pixels. This will get * blended into the accumulator at some offset. */ @@ -803,12 +743,6 @@ read_webp_generate( VipsRegion *or, return( 0 ); } -static void -read_minimise_cb( VipsObject *object, Read *read ) -{ - read_minimise( read ); -} - static int read_image( Read *read, VipsImage *out ) { @@ -819,9 +753,6 @@ read_image( Read *read, VipsImage *out ) if( read_header( read, t[0] ) ) return( -1 ); - g_signal_connect( t[0], "minimise", - G_CALLBACK( read_minimise_cb ), read ); - if( vips_image_generate( t[0], NULL, read_webp_generate, NULL, read, NULL ) || vips_sequential( t[0], &t[1], NULL ) || @@ -832,38 +763,13 @@ read_image( Read *read, VipsImage *out ) } int -vips__webp_read_file( const char *filename, VipsImage *out, +vips__webp_read_header_source( VipsSource *source, VipsImage *out, int page, int n, double scale ) { Read *read; - if( !(read = read_new( filename, NULL, 0, page, n, scale )) ) { - vips_error( "webp2vips", - _( "unable to open \"%s\"" ), filename ); + if( !(read = read_new( source, page, n, scale )) ) return( -1 ); - } - - if( read_image( read, out ) ) { - read_free( read ); - return( -1 ); - } - - read_free( read ); - - return( 0 ); -} - -int -vips__webp_read_buffer_header( const void *buf, size_t len, VipsImage *out, - int page, int n, double scale ) -{ - Read *read; - - if( !(read = read_new( NULL, buf, len, page, n, scale )) ) { - vips_error( "webp2vips", - "%s", _( "unable to open buffer" ) ); - return( -1 ); - } if( read_header( read, out ) ) { read_free( read ); @@ -876,16 +782,13 @@ vips__webp_read_buffer_header( const void *buf, size_t len, VipsImage *out, } int -vips__webp_read_buffer( const void *buf, size_t len, VipsImage *out, +vips__webp_read_source( VipsSource *source, VipsImage *out, int page, int n, double scale ) { Read *read; - if( !(read = read_new( NULL, buf, len, page, n, scale )) ) { - vips_error( "webp2vips", - "%s", _( "unable to open buffer" ) ); + if( !(read = read_new( source, page, n, scale )) ) return( -1 ); - } if( read_image( read, out ) ) { read_free( read ); diff --git a/libvips/foreign/webpload.c b/libvips/foreign/webpload.c index 545fc793..5a83fcf1 100644 --- a/libvips/foreign/webpload.c +++ b/libvips/foreign/webpload.c @@ -58,6 +58,10 @@ typedef struct _VipsForeignLoadWebp { VipsForeignLoad parent_object; + /* Set by subclasses. + */ + VipsSource *source; + /* Load this page (frame number). */ int page; @@ -80,17 +84,67 @@ typedef VipsForeignLoadClass VipsForeignLoadWebpClass; G_DEFINE_ABSTRACT_TYPE( VipsForeignLoadWebp, vips_foreign_load_webp, VIPS_TYPE_FOREIGN_LOAD ); +static void +vips_foreign_load_webp_dispose( GObject *gobject ) +{ + VipsForeignLoadWebp *webp = (VipsForeignLoadWebp *) gobject; + + VIPS_UNREF( webp->source ); + + G_OBJECT_CLASS( vips_foreign_load_webp_parent_class )-> + dispose( gobject ); +} + +static int +vips_foreign_load_webp_build( VipsObject *object ) +{ + VipsForeignLoadWebp *webp = (VipsForeignLoadWebp *) object; + + /* BC for the old API. + */ + if( !vips_object_argument_isset( VIPS_OBJECT( webp ), "scale" ) && + vips_object_argument_isset( VIPS_OBJECT( webp ), "shrink" ) && + webp->shrink != 0 ) + webp->scale = 1.0 / webp->shrink; + + if( VIPS_OBJECT_CLASS( vips_foreign_load_webp_parent_class )-> + build( object ) ) + return( -1 ); + + return( 0 ); +} + static VipsForeignFlags vips_foreign_load_webp_get_flags( VipsForeignLoad *load ) { return( 0 ); } -static int -vips_foreign_load_webp_build( VipsObject *object ) +static VipsForeignFlags +vips_foreign_load_webp_get_flags_filename( const char *filename ) { - if( VIPS_OBJECT_CLASS( vips_foreign_load_webp_parent_class )-> - build( object ) ) + return( 0 ); +} + +static int +vips_foreign_load_webp_header( VipsForeignLoad *load ) +{ + VipsForeignLoadWebp *webp = (VipsForeignLoadWebp *) load; + + if( vips__webp_read_header_source( webp->source, load->out, + webp->page, webp->n, webp->scale ) ) + return( -1 ); + + return( 0 ); +} + +static int +vips_foreign_load_webp_load( VipsForeignLoad *load ) +{ + VipsForeignLoadWebp *webp = (VipsForeignLoadWebp *) load; + + if( vips__webp_read_source( webp->source, load->real, + webp->page, webp->n, webp->scale ) ) return( -1 ); return( 0 ); @@ -101,8 +155,10 @@ vips_foreign_load_webp_class_init( VipsForeignLoadWebpClass *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->dispose = vips_foreign_load_webp_dispose; gobject_class->set_property = vips_object_set_property; gobject_class->get_property = vips_object_get_property; @@ -110,7 +166,15 @@ vips_foreign_load_webp_class_init( VipsForeignLoadWebpClass *class ) object_class->description = _( "load webp" ); object_class->build = vips_foreign_load_webp_build; + /* is_a() is not that quick ... lower the priority. + */ + foreign_class->priority = -50; + + load_class->get_flags_filename = + vips_foreign_load_webp_get_flags_filename; load_class->get_flags = vips_foreign_load_webp_get_flags; + load_class->header = vips_foreign_load_webp_header; + load_class->load = vips_foreign_load_webp_load; VIPS_ARG_INT( class, "page", 20, _( "Page" ), @@ -153,6 +217,68 @@ vips_foreign_load_webp_init( VipsForeignLoadWebp *webp ) webp->scale = 1.0; } +typedef struct _VipsForeignLoadWebpSource { + VipsForeignLoadWebp parent_object; + + VipsSource *source; + +} VipsForeignLoadWebpSource; + +typedef VipsForeignLoadWebpClass VipsForeignLoadWebpSourceClass; + +G_DEFINE_TYPE( VipsForeignLoadWebpSource, vips_foreign_load_webp_source, + vips_foreign_load_webp_get_type() ); + +static int +vips_foreign_load_webp_source_build( VipsObject *object ) +{ + VipsForeignLoadWebp *webp = (VipsForeignLoadWebp *) object; + VipsForeignLoadWebpSource *source = + (VipsForeignLoadWebpSource *) object; + + if( source->source ) { + webp->source = source->source; + g_object_ref( webp->source ); + } + + if( VIPS_OBJECT_CLASS( vips_foreign_load_webp_source_parent_class )-> + build( object ) ) + return( -1 ); + + return( 0 ); +} + +static void +vips_foreign_load_webp_source_class_init( + VipsForeignLoadWebpSourceClass *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 = "webpload_source"; + object_class->description = _( "load webp from source" ); + object_class->build = vips_foreign_load_webp_source_build; + + load_class->is_a_source = vips__iswebp_source; + + VIPS_ARG_OBJECT( class, "source", 1, + _( "Source" ), + _( "Source to load from" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadWebpSource, source ), + VIPS_TYPE_SOURCE ); + +} + +static void +vips_foreign_load_webp_source_init( VipsForeignLoadWebpSource *buffer ) +{ +} + typedef struct _VipsForeignLoadWebpFile { VipsForeignLoadWebp parent_object; @@ -167,51 +293,36 @@ typedef VipsForeignLoadWebpClass VipsForeignLoadWebpFileClass; G_DEFINE_TYPE( VipsForeignLoadWebpFile, vips_foreign_load_webp_file, vips_foreign_load_webp_get_type() ); -static VipsForeignFlags -vips_foreign_load_webp_file_get_flags_filename( const char *filename ) +static int +vips_foreign_load_webp_file_build( VipsObject *object ) { + VipsForeignLoadWebp *webp = (VipsForeignLoadWebp *) object; + VipsForeignLoadWebpFile *file = (VipsForeignLoadWebpFile *) object; + + if( file->filename && + !(webp->source = + vips_source_new_from_file( file->filename )) ) + return( -1 ); + + if( VIPS_OBJECT_CLASS( vips_foreign_load_webp_file_parent_class )-> + build( object ) ) + return( -1 ); + return( 0 ); } static gboolean vips_foreign_load_webp_file_is_a( const char *filename ) { - return( vips__iswebp( filename ) ); -} + VipsSource *source; + gboolean result; -static int -vips_foreign_load_webp_file_header( VipsForeignLoad *load ) -{ - VipsForeignLoadWebp *webp = (VipsForeignLoadWebp *) load; - VipsForeignLoadWebpFile *file = (VipsForeignLoadWebpFile *) load; + if( !(source = vips_source_new_from_file( filename )) ) + return( FALSE ); + result = vips__iswebp_source( source ); + VIPS_UNREF( source ); - /* BC for the old API. - */ - if( !vips_object_argument_isset( VIPS_OBJECT( load ), "scale" ) && - vips_object_argument_isset( VIPS_OBJECT( load ), "shrink" ) && - webp->shrink != 0 ) - webp->scale = 1.0 / webp->shrink; - - if( vips__webp_read_file_header( file->filename, load->out, - webp->page, webp->n, webp->scale ) ) - return( -1 ); - - VIPS_SETSTR( load->out->filename, file->filename ); - - return( 0 ); -} - -static int -vips_foreign_load_webp_file_load( VipsForeignLoad *load ) -{ - VipsForeignLoadWebp *webp = (VipsForeignLoadWebp *) load; - VipsForeignLoadWebpFile *file = (VipsForeignLoadWebpFile *) load; - - if( vips__webp_read_file( file->filename, load->real, - webp->page, webp->n, webp->scale ) ) - return( -1 ); - - return( 0 ); + return( result ); } const char *vips__webp_suffs[] = { ".webp", NULL }; @@ -229,14 +340,11 @@ vips_foreign_load_webp_file_class_init( VipsForeignLoadWebpFileClass *class ) object_class->nickname = "webpload"; object_class->description = _( "load webp from file" ); + object_class->build = vips_foreign_load_webp_file_build; foreign_class->suffs = vips__webp_suffs; - load_class->get_flags_filename = - vips_foreign_load_webp_file_get_flags_filename; load_class->is_a = vips_foreign_load_webp_file_is_a; - load_class->header = vips_foreign_load_webp_file_header; - load_class->load = vips_foreign_load_webp_file_load; VIPS_ARG_STRING( class, "filename", 1, _( "Filename" ), @@ -256,7 +364,7 @@ typedef struct _VipsForeignLoadWebpBuffer { /* Load from a buffer. */ - VipsArea *buf; + VipsBlob *blob; } VipsForeignLoadWebpBuffer; @@ -266,31 +374,37 @@ G_DEFINE_TYPE( VipsForeignLoadWebpBuffer, vips_foreign_load_webp_buffer, vips_foreign_load_webp_get_type() ); static int -vips_foreign_load_webp_buffer_header( VipsForeignLoad *load ) +vips_foreign_load_webp_buffer_build( VipsObject *object ) { - VipsForeignLoadWebp *webp = (VipsForeignLoadWebp *) load; - VipsForeignLoadWebpBuffer *buffer = (VipsForeignLoadWebpBuffer *) load; + VipsForeignLoadWebp *webp = (VipsForeignLoadWebp *) object; + VipsForeignLoadWebpBuffer *buffer = + (VipsForeignLoadWebpBuffer *) object; - if( vips__webp_read_buffer_header( buffer->buf->data, - buffer->buf->length, load->out, - webp->page, webp->n, webp->scale ) ) + if( buffer->blob && + !(webp->source = vips_source_new_from_memory( + VIPS_AREA( buffer->blob )->data, + VIPS_AREA( buffer->blob )->length )) ) + return( -1 ); + + if( VIPS_OBJECT_CLASS( vips_foreign_load_webp_buffer_parent_class )-> + build( object ) ) return( -1 ); return( 0 ); } -static int -vips_foreign_load_webp_buffer_load( VipsForeignLoad *load ) +static gboolean +vips_foreign_load_webp_buffer_is_a_buffer( const void *buf, size_t len ) { - VipsForeignLoadWebp *webp = (VipsForeignLoadWebp *) load; - VipsForeignLoadWebpBuffer *buffer = (VipsForeignLoadWebpBuffer *) load; + VipsSource *source; + gboolean result; - if( vips__webp_read_buffer( buffer->buf->data, buffer->buf->length, - load->real, - webp->page, webp->n, webp->scale ) ) - return( -1 ); + if( !(source = vips_source_new_from_memory( buf, len )) ) + return( FALSE ); + result = vips__iswebp_source( source ); + VIPS_UNREF( source ); - return( 0 ); + return( result ); } static void @@ -299,7 +413,6 @@ vips_foreign_load_webp_buffer_class_init( { 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; @@ -307,20 +420,15 @@ vips_foreign_load_webp_buffer_class_init( object_class->nickname = "webpload_buffer"; object_class->description = _( "load webp from buffer" ); + object_class->build = vips_foreign_load_webp_buffer_build; - /* is_a() is not that quick ... lower the priority. - */ - foreign_class->priority = -50; - - load_class->is_a_buffer = vips__iswebp_buffer; - load_class->header = vips_foreign_load_webp_buffer_header; - load_class->load = vips_foreign_load_webp_buffer_load; + load_class->is_a_buffer = vips_foreign_load_webp_buffer_is_a_buffer; VIPS_ARG_BOXED( class, "buffer", 1, _( "Buffer" ), _( "Buffer to load from" ), VIPS_ARGUMENT_REQUIRED_INPUT, - G_STRUCT_OFFSET( VipsForeignLoadWebpBuffer, buf ), + G_STRUCT_OFFSET( VipsForeignLoadWebpBuffer, blob ), VIPS_TYPE_BLOB ); } @@ -416,3 +524,34 @@ vips_webpload_buffer( void *buf, size_t len, VipsImage **out, ... ) return( result ); } + +/** + * vips_webpload_source: + * @source: source to load from + * @out: (out): image to write + * @...: %NULL-terminated list of optional named arguments + * + * Optional arguments: + * + * * @page: %gint, page (frame) to read + * * @n: %gint, load this many pages + * * @scale: %gdouble, scale by this much on load + * + * Exactly as vips_webpload(), but read from a source. + * + * See also: vips_webpload() + * + * Returns: 0 on success, -1 on error. + */ +int +vips_webpload_source( VipsSource *source, VipsImage **out, ... ) +{ + va_list ap; + int result; + + va_start( ap, out ); + result = vips_call_split( "webpload_source", ap, source, out ); + va_end( ap ); + + return( result ); +} diff --git a/libvips/foreign/webpsave.c b/libvips/foreign/webpsave.c index 21320f5e..ef08e8a2 100644 --- a/libvips/foreign/webpsave.c +++ b/libvips/foreign/webpsave.c @@ -219,6 +219,69 @@ vips_foreign_save_webp_init( VipsForeignSaveWebp *webp ) webp->kmax = INT_MAX; } +typedef struct _VipsForeignSaveWebpTarget { + VipsForeignSaveWebp parent_object; + + VipsTarget *target; + +} VipsForeignSaveWebpTarget; + +typedef VipsForeignSaveWebpClass VipsForeignSaveWebpTargetClass; + +G_DEFINE_TYPE( VipsForeignSaveWebpTarget, vips_foreign_save_webp_target, + vips_foreign_save_webp_get_type() ); + +static int +vips_foreign_save_webp_target_build( VipsObject *object ) +{ + VipsForeignSave *save = (VipsForeignSave *) object; + VipsForeignSaveWebp *webp = (VipsForeignSaveWebp *) object; + VipsForeignSaveWebpTarget *target = + (VipsForeignSaveWebpTarget *) object; + + if( VIPS_OBJECT_CLASS( vips_foreign_save_webp_target_parent_class )-> + build( object ) ) + return( -1 ); + + if( vips__webp_write_target( save->ready, target->target, + webp->Q, webp->lossless, webp->preset, + webp->smart_subsample, webp->near_lossless, + webp->alpha_q, webp->reduction_effort, + webp->min_size, webp->kmin, webp->kmax, + save->strip ) ) + return( -1 ); + + return( 0 ); +} + +static void +vips_foreign_save_webp_target_class_init( + VipsForeignSaveWebpTargetClass *class ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( class ); + VipsObjectClass *object_class = (VipsObjectClass *) class; + + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + object_class->nickname = "webpsave_target"; + object_class->description = _( "save image to webp target" ); + object_class->build = vips_foreign_save_webp_target_build; + + VIPS_ARG_OBJECT( class, "target", 1, + _( "Target" ), + _( "Target to save to" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsForeignSaveWebpTarget, target ), + VIPS_TYPE_TARGET ); +} + +static void +vips_foreign_save_webp_target_init( VipsForeignSaveWebpTarget *target ) +{ +} + + typedef struct _VipsForeignSaveWebpFile { VipsForeignSaveWebp parent_object; @@ -240,17 +303,24 @@ vips_foreign_save_webp_file_build( VipsObject *object ) VipsForeignSaveWebp *webp = (VipsForeignSaveWebp *) object; VipsForeignSaveWebpFile *file = (VipsForeignSaveWebpFile *) object; + VipsTarget *target; + if( VIPS_OBJECT_CLASS( vips_foreign_save_webp_file_parent_class )-> build( object ) ) return( -1 ); - if( vips__webp_write_file( save->ready, file->filename, + if( !(target = vips_target_new_to_file( file->filename )) ) + return( -1 ); + if( vips__webp_write_target( save->ready, target, webp->Q, webp->lossless, webp->preset, webp->smart_subsample, webp->near_lossless, webp->alpha_q, webp->reduction_effort, webp->min_size, webp->kmin, webp->kmax, - save->strip ) ) + save->strip ) ) { + VIPS_UNREF( target ); return( -1 ); + } + VIPS_UNREF( target ); return( 0 ); } @@ -300,30 +370,35 @@ vips_foreign_save_webp_buffer_build( VipsObject *object ) { VipsForeignSave *save = (VipsForeignSave *) object; VipsForeignSaveWebp *webp = (VipsForeignSaveWebp *) object; - VipsForeignSaveWebpBuffer *file = (VipsForeignSaveWebpBuffer *) object; + VipsForeignSaveWebpBuffer *buffer = + (VipsForeignSaveWebpBuffer *) object; - void *obuf; - size_t olen; + VipsTarget *target; VipsBlob *blob; if( VIPS_OBJECT_CLASS( vips_foreign_save_webp_buffer_parent_class )-> build( object ) ) return( -1 ); - if( vips__webp_write_buffer( save->ready, &obuf, &olen, + if( !(target = vips_target_new_to_memory()) ) + return( -1 ); + + if( vips__webp_write_target( save->ready, target, webp->Q, webp->lossless, webp->preset, webp->smart_subsample, webp->near_lossless, webp->alpha_q, webp->reduction_effort, webp->min_size, webp->kmin, webp->kmax, - save->strip ) ) + save->strip ) ) { + VIPS_UNREF( target ); return( -1 ); + } - /* obuf is a g_free() buffer, not vips_free(). - */ - blob = vips_blob_new( (VipsCallbackFn) g_free, obuf, olen ); - g_object_set( file, "buffer", blob, NULL ); + g_object_get( target, "blob", &blob, NULL ); + g_object_set( buffer, "buffer", blob, NULL ); vips_area_unref( VIPS_AREA( blob ) ); + VIPS_UNREF( target ); + return( 0 ); } @@ -370,31 +445,40 @@ vips_foreign_save_webp_mime_build( VipsObject *object ) VipsForeignSave *save = (VipsForeignSave *) object; VipsForeignSaveWebp *webp = (VipsForeignSaveWebp *) object; - void *obuf; - size_t olen; + VipsTarget *target; + VipsBlob *blob; + void *data; + size_t len; if( VIPS_OBJECT_CLASS( vips_foreign_save_webp_mime_parent_class )-> build( object ) ) return( -1 ); - if( vips__webp_write_buffer( save->ready, &obuf, &olen, + if( !(target = vips_target_new_to_memory()) ) + return( -1 ); + + if( vips__webp_write_target( save->ready, target, webp->Q, webp->lossless, webp->preset, webp->smart_subsample, webp->near_lossless, webp->alpha_q, webp->reduction_effort, webp->min_size, webp->kmin, webp->kmax, - save->strip ) ) - return( -1 ); - - printf( "Content-length: %zu\r\n", olen ); - printf( "Content-type: image/webp\r\n" ); - printf( "\r\n" ); - if( fwrite( obuf, sizeof( char ), olen, stdout ) != olen ) { - vips_error( "VipsWebp", "%s", _( "error writing output" ) ); + save->strip ) ) { + VIPS_UNREF( target ); return( -1 ); } + + g_object_get( target, "blob", &blob, NULL ); + data = VIPS_AREA( blob )->data; + len = VIPS_AREA( blob )->length; + vips_area_unref( VIPS_AREA( blob ) ); + + printf( "Content-length: %zu\r\n", len ); + printf( "Content-type: image/webp\r\n" ); + printf( "\r\n" ); + (void) fwrite( data, sizeof( char ), len, stdout ); fflush( stdout ); - g_free( obuf ); + VIPS_UNREF( target ); return( 0 ); } @@ -466,7 +550,7 @@ vips_foreign_save_webp_mime_init( VipsForeignSaveWebpMime *mime ) * frames between frames. Setting 0 means no keyframes. By default, keyframes * are disabled. * - * Use the metadata items `gif-loop` and `delay` to set the number of + * Use the metadata items `loop` and `delay` to set the number of * loops for the animation and the frame delays. * * The writer will attach ICC, EXIF and XMP metadata, unless @strip is set to @@ -585,3 +669,42 @@ vips_webpsave_mime( VipsImage *in, ... ) return( result ); } + +/** + * vips_webpsave_target: (method) + * @in: image to save + * @target: save image to this target + * @...: %NULL-terminated list of optional named arguments + * + * Optional arguments: + * + * * @Q: %gint, quality factor + * * @lossless: %gboolean, enables lossless compression + * * @preset: #VipsForeignWebpPreset, choose lossy compression preset + * * @smart_subsample: %gboolean, enables high quality chroma subsampling + * * @near_lossless: %gboolean, preprocess in lossless mode (controlled by Q) + * * @alpha_q: %gint, set alpha quality in lossless mode + * * @reduction_effort: %gint, level of CPU effort to reduce file size + * * @min_size: %gboolean, minimise size + * * @kmin: %gint, minimum number of frames between keyframes + * * @kmax: %gint, maximum number of frames between keyframes + * * @strip: %gboolean, remove all metadata from image + * + * As vips_webpsave(), but save to a target. + * + * See also: vips_webpsave(). + * + * Returns: 0 on success, -1 on error. + */ +int +vips_webpsave_target( VipsImage *in, VipsTarget *target, ... ) +{ + va_list ap; + int result; + + va_start( ap, target ); + result = vips_call_split( "webpsave_target", ap, in, target ); + va_end( ap ); + + return( result ); +} diff --git a/libvips/histogram/hist_local.c b/libvips/histogram/hist_local.c index 9022ef4e..df9ed10d 100644 --- a/libvips/histogram/hist_local.c +++ b/libvips/histogram/hist_local.c @@ -346,6 +346,7 @@ vips_hist_local_class_init( VipsHistLocalClass *class ) { GObjectClass *gobject_class = G_OBJECT_CLASS( class ); VipsObjectClass *object_class = (VipsObjectClass *) class; + VipsOperationClass *operation_class = VIPS_OPERATION_CLASS( class ); gobject_class->set_property = vips_object_set_property; gobject_class->get_property = vips_object_get_property; @@ -354,6 +355,8 @@ vips_hist_local_class_init( VipsHistLocalClass *class ) object_class->description = _( "local histogram equalisation" ); object_class->build = vips_hist_local_build; + operation_class->flags = VIPS_OPERATION_SEQUENTIAL; + VIPS_ARG_IMAGE( class, "in", 1, _( "Input" ), _( "Input image" ), diff --git a/libvips/histogram/hist_norm.c b/libvips/histogram/hist_norm.c index b4de016d..89029fd0 100644 --- a/libvips/histogram/hist_norm.c +++ b/libvips/histogram/hist_norm.c @@ -77,7 +77,7 @@ vips_hist_norm_build( VipsObject *object ) VipsHistNorm *norm = (VipsHistNorm *) object; VipsImage **t = (VipsImage **) vips_object_local_array( object, 3 ); - guint64 px; + guint64 new_max; int bands; double *a, *b; int y; @@ -95,13 +95,13 @@ vips_hist_norm_build( VipsObject *object ) /* Scale each channel by px / channel max */ - px = VIPS_IMAGE_N_PELS( norm->in ); + new_max = VIPS_IMAGE_N_PELS( norm->in ) - 1; bands = norm->in->Bands; if( !(a = VIPS_ARRAY( object, bands, double )) || !(b = VIPS_ARRAY( object, bands, double )) ) return( -1 ); for( y = 0; y < bands; y++ ) { - a[y] = px / *VIPS_MATRIX( t[0], 1, y + 1 ); + a[y] = new_max / *VIPS_MATRIX( t[0], 1, y + 1 ); b[y] = 0; } @@ -110,9 +110,9 @@ vips_hist_norm_build( VipsObject *object ) /* Make output format as small as we can. */ - if( px <= 256 ) + if( new_max <= 255 ) fmt = VIPS_FORMAT_UCHAR; - else if( px <= 65536 ) + else if( new_max <= 65535 ) fmt = VIPS_FORMAT_USHORT; else fmt = VIPS_FORMAT_UINT; @@ -161,8 +161,9 @@ vips_hist_norm_init( VipsHistNorm *hist_norm ) * @out: (out): output image * @...: %NULL-terminated list of optional named arguments * - * Normalise histogram ... normalise range to make it square (ie. max == - * number of elements). Normalise each band separately. + * Normalise histogram. The maximum of each band becomes equal to the maximum + * index, so for example the max for a uchar image becomes 255. + * Normalise each band separately. * * See also: vips_hist_cum(). * diff --git a/libvips/histogram/percent.c b/libvips/histogram/percent.c index 7aa32c2d..45da79fa 100644 --- a/libvips/histogram/percent.c +++ b/libvips/histogram/percent.c @@ -141,9 +141,14 @@ vips_percent_init( VipsPercent *percent ) * @...: %NULL-terminated list of optional named arguments * * vips_percent() returns (through the @threshold parameter) the threshold - * above which there are @percent values of @in. If for example percent=10, the - * number of pels of the input image with values greater than @threshold - * will correspond to 10% of all pels of the image. + * below which there are @percent values of @in. For example: + * + * |[ + * $ vips precent k2.jpg 90 + * 214 + * ]| + * + * Means that 90% of pixels in `k2.jpg` have a value less than 214. * * The function works for uchar and ushort images only. It can be used * to threshold the scaled result of a filtering operation. diff --git a/libvips/include/vips/Makefile.am b/libvips/include/vips/Makefile.am index 6d296905..5465ff17 100644 --- a/libvips/include/vips/Makefile.am +++ b/libvips/include/vips/Makefile.am @@ -1,4 +1,6 @@ pkginclude_HEADERS = \ + connection.h \ + sbuf.h \ basic.h \ type.h \ gate.h \ @@ -48,9 +50,6 @@ pkginclude_HEADERS = \ vips7compat.h \ vips.h -vipsc++.h: - vips --cpph all > vipsc++.h - EXTRA_DIST = version.h.in internal.h enumtemplate # the headers we scan for enums etc. diff --git a/libvips/include/vips/almostdeprecated.h b/libvips/include/vips/almostdeprecated.h index 26d90937..1bde7c84 100644 --- a/libvips/include/vips/almostdeprecated.h +++ b/libvips/include/vips/almostdeprecated.h @@ -292,6 +292,8 @@ void vips_info( const char *domain, const char *fmt, ... ) __attribute__((format(printf, 2, 3))); void vips_vinfo( const char *domain, const char *fmt, va_list ap ); +VipsAngle vips_autorot_get_angle( VipsImage *image ); + #ifdef __cplusplus } #endif /*__cplusplus*/ diff --git a/libvips/include/vips/arithmetic.h b/libvips/include/vips/arithmetic.h index ce95399a..3c9c7aa9 100644 --- a/libvips/include/vips/arithmetic.h +++ b/libvips/include/vips/arithmetic.h @@ -185,14 +185,14 @@ int vips_multiply( VipsImage *left, VipsImage *right, VipsImage **out, ... ) int vips_divide( VipsImage *left, VipsImage *right, VipsImage **out, ... ) __attribute__((sentinel)); int vips_linear( VipsImage *in, VipsImage **out, - double *a, double *b, int n, ... ) + const double *a, const double *b, int n, ... ) __attribute__((sentinel)); int vips_linear1( VipsImage *in, VipsImage **out, double a, double b, ... ) __attribute__((sentinel)); int vips_remainder( VipsImage *left, VipsImage *right, VipsImage **out, ... ) __attribute__((sentinel)); int vips_remainder_const( VipsImage *in, VipsImage **out, - double *c, int n, ... ) + const double *c, int n, ... ) __attribute__((sentinel)); int vips_remainder_const1( VipsImage *in, VipsImage **out, double c, ... ) @@ -279,19 +279,25 @@ int vips_more( VipsImage *left, VipsImage *right, VipsImage **out, ... ) int vips_moreeq( VipsImage *left, VipsImage *right, VipsImage **out, ... ) __attribute__((sentinel)); int vips_relational_const( VipsImage *in, VipsImage **out, - VipsOperationRelational relational, double *c, int n, ... ) + VipsOperationRelational relational, const double *c, int n, ... ) __attribute__((sentinel)); -int vips_equal_const( VipsImage *in, VipsImage **out, double *c, int n, ... ) +int vips_equal_const( VipsImage *in, VipsImage **out, + const double *c, int n, ... ) __attribute__((sentinel)); -int vips_notequal_const( VipsImage *in, VipsImage **out, double *c, int n, ... ) +int vips_notequal_const( VipsImage *in, VipsImage **out, + const double *c, int n, ... ) __attribute__((sentinel)); -int vips_less_const( VipsImage *in, VipsImage **out, double *c, int n, ... ) +int vips_less_const( VipsImage *in, VipsImage **out, + const double *c, int n, ... ) __attribute__((sentinel)); -int vips_lesseq_const( VipsImage *in, VipsImage **out, double *c, int n, ... ) +int vips_lesseq_const( VipsImage *in, VipsImage **out, + const double *c, int n, ... ) __attribute__((sentinel)); -int vips_more_const( VipsImage *in, VipsImage **out, double *c, int n, ... ) +int vips_more_const( VipsImage *in, VipsImage **out, + const double *c, int n, ... ) __attribute__((sentinel)); -int vips_moreeq_const( VipsImage *in, VipsImage **out, double *c, int n, ... ) +int vips_moreeq_const( VipsImage *in, VipsImage **out, + const double *c, int n, ... ) __attribute__((sentinel)); int vips_relational_const1( VipsImage *in, VipsImage **out, VipsOperationRelational relational, double c, ... ) @@ -324,17 +330,22 @@ int vips_rshift( VipsImage *left, VipsImage *right, VipsImage **out, ... ) __attribute__((sentinel)); int vips_boolean_const( VipsImage *in, VipsImage **out, - VipsOperationBoolean boolean, double *c, int n, ... ) + VipsOperationBoolean boolean, const double *c, int n, ... ) __attribute__((sentinel)); -int vips_andimage_const( VipsImage *in, VipsImage **out, double *c, int n, ... ) +int vips_andimage_const( VipsImage *in, VipsImage **out, + const double *c, int n, ... ) __attribute__((sentinel)); -int vips_orimage_const( VipsImage *in, VipsImage **out, double *c, int n, ... ) +int vips_orimage_const( VipsImage *in, VipsImage **out, + const double *c, int n, ... ) __attribute__((sentinel)); -int vips_eorimage_const( VipsImage *in, VipsImage **out, double *c, int n, ... ) +int vips_eorimage_const( VipsImage *in, VipsImage **out, + const double *c, int n, ... ) __attribute__((sentinel)); -int vips_lshift_const( VipsImage *in, VipsImage **out, double *c, int n, ... ) +int vips_lshift_const( VipsImage *in, VipsImage **out, + const double *c, int n, ... ) __attribute__((sentinel)); -int vips_rshift_const( VipsImage *in, VipsImage **out, double *c, int n, ... ) +int vips_rshift_const( VipsImage *in, VipsImage **out, + const double *c, int n, ... ) __attribute__((sentinel)); int vips_boolean_const1( VipsImage *in, VipsImage **out, VipsOperationBoolean boolean, double c, ... ) @@ -358,11 +369,13 @@ int vips_pow( VipsImage *left, VipsImage *right, VipsImage **out, ... ) int vips_wop( VipsImage *left, VipsImage *right, VipsImage **out, ... ) __attribute__((sentinel)); int vips_math2_const( VipsImage *in, VipsImage **out, - VipsOperationMath2 math2, double *c, int n, ... ) + VipsOperationMath2 math2, const double *c, int n, ... ) __attribute__((sentinel)); -int vips_pow_const( VipsImage *in, VipsImage **out, double *c, int n, ... ) +int vips_pow_const( VipsImage *in, VipsImage **out, + const double *c, int n, ... ) __attribute__((sentinel)); -int vips_wop_const( VipsImage *in, VipsImage **out, double *c, int n, ... ) +int vips_wop_const( VipsImage *in, VipsImage **out, + const double *c, int n, ... ) __attribute__((sentinel)); int vips_math2_const1( VipsImage *in, VipsImage **out, VipsOperationMath2 math2, double c, ... ) diff --git a/libvips/include/vips/basic.h b/libvips/include/vips/basic.h index a0adf4b4..b17cfa7a 100644 --- a/libvips/include/vips/basic.h +++ b/libvips/include/vips/basic.h @@ -71,6 +71,11 @@ typedef enum { char *vips_path_filename7( const char *path ); char *vips_path_mode7( const char *path ); +struct _VipsImage; +typedef struct _VipsImage VipsImage; +struct _VipsRegion; +typedef struct _VipsRegion VipsRegion; + #ifdef __cplusplus } #endif /*__cplusplus*/ diff --git a/libvips/include/vips/connection.h b/libvips/include/vips/connection.h new file mode 100644 index 00000000..17a070c9 --- /dev/null +++ b/libvips/include/vips/connection.h @@ -0,0 +1,438 @@ +/* A byte source/sink .. it can be a pipe, socket, or perhaps a node.js stream. + * + * J.Cupitt, 19/6/14 + */ + +/* + + 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 + + */ + +#ifndef VIPS_CONNECTION_H +#define VIPS_CONNECTION_H + +#ifdef __cplusplus +extern "C" { +#endif /*__cplusplus*/ + +#define VIPS_TYPE_CONNECTION (vips_connection_get_type()) +#define VIPS_CONNECTION( obj ) \ + (G_TYPE_CHECK_INSTANCE_CAST( (obj), \ + VIPS_TYPE_CONNECTION, VipsConnection )) +#define VIPS_CONNECTION_CLASS( klass ) \ + (G_TYPE_CHECK_CLASS_CAST( (klass), \ + VIPS_TYPE_CONNECTION, VipsConnectionClass)) +#define VIPS_IS_CONNECTION( obj ) \ + (G_TYPE_CHECK_INSTANCE_TYPE( (obj), VIPS_TYPE_CONNECTION )) +#define VIPS_IS_CONNECTION_CLASS( klass ) \ + (G_TYPE_CHECK_CLASS_TYPE( (klass), VIPS_TYPE_CONNECTION )) +#define VIPS_CONNECTION_GET_CLASS( obj ) \ + (G_TYPE_INSTANCE_GET_CLASS( (obj), \ + VIPS_TYPE_CONNECTION, VipsConnectionClass )) + +/* Communicate with something like a socket or pipe. + */ +typedef struct _VipsConnection { + VipsObject parent_object; + + /*< private >*/ + + /* Read/write this fd if connected to a system pipe/socket. Override + * ::read() and ::write() to do something else. + */ + int descriptor; + + /* A descriptor we close with vips_tracked_close(). + */ + int tracked_descriptor; + + /* A descriptor we close with close(). + */ + int close_descriptor; + + /* If descriptor is a file, the filename we opened. Handy for error + * messages. + */ + char *filename; + +} VipsConnection; + +typedef struct _VipsConnectionClass { + VipsObjectClass parent_class; + +} VipsConnectionClass; + +GType vips_connection_get_type( void ); + +const char *vips_connection_filename( VipsConnection *connection ); +const char *vips_connection_nick( VipsConnection *connection ); + +void vips_pipe_read_limit_set( gint64 limit ); + +#define VIPS_TYPE_SOURCE (vips_source_get_type()) +#define VIPS_SOURCE( obj ) \ + (G_TYPE_CHECK_INSTANCE_CAST( (obj), \ + VIPS_TYPE_SOURCE, VipsSource )) +#define VIPS_SOURCE_CLASS( klass ) \ + (G_TYPE_CHECK_CLASS_CAST( (klass), \ + VIPS_TYPE_SOURCE, VipsSourceClass)) +#define VIPS_IS_SOURCE( obj ) \ + (G_TYPE_CHECK_INSTANCE_TYPE( (obj), VIPS_TYPE_SOURCE )) +#define VIPS_IS_SOURCE_CLASS( klass ) \ + (G_TYPE_CHECK_CLASS_TYPE( (klass), VIPS_TYPE_SOURCE )) +#define VIPS_SOURCE_GET_CLASS( obj ) \ + (G_TYPE_INSTANCE_GET_CLASS( (obj), \ + VIPS_TYPE_SOURCE, VipsSourceClass )) + +/* Read from something like a socket, file or memory area and present the data + * with a unified seek / read / map interface. + * + * During the header phase, we save data from unseekable sources in a buffer + * so readers can rewind and read again. We don't buffer data during the + * decode stage. + */ +typedef struct _VipsSource { + VipsConnection parent_object; + + /* We have two phases: + * + * During the header phase, we save bytes read from the input (if this + * is an unseekable source) so that we can rewind and try again, if + * necessary. + * + * Once we reach decode phase, we no longer support rewind and the + * buffer of saved data is discarded. + */ + gboolean decode; + + /* TRUE if this input is something like a pipe. These don't support + * seek or map -- all you can do is read() bytes sequentially. + * + * If you attempt to map or get the size of a pipe-style input, it'll + * get read entirely into memory. Seeks will cause read up to the seek + * point. + */ + gboolean have_tested_seek; + gboolean is_pipe; + + /* The current read point and length. + * + * length is -1 for is_pipe sources. + * + * off_t can be 32 bits on some platforms, so make sure we have a + * full 64. + */ + gint64 read_position; + gint64 length; + + /*< private >*/ + + /* For sources where we have the whole image in memory (from a memory + * buffer, from mmaping the file, from reading the pipe into memory), + * a pointer to the start. + */ + const void *data; + + /* For is_pipe sources, save data read during header phase here. If + * we rewind and try again, serve data from this until it runs out. + * + * If we need to force the whole pipe into memory, read everything to + * this and put a copy pf the pointer in data. + */ + GByteArray *header_bytes; + + /* Save the first few bytes here for file type sniffing. + */ + GByteArray *sniff; + + /* For a memory source, the blob we read from. + */ + VipsBlob *blob; + + /* If we mmaped the file, whet we need to unmmap on finalize. + */ + void *mmap_baseaddr; + size_t mmap_length; + +} VipsSource; + +typedef struct _VipsSourceClass { + VipsConnectionClass parent_class; + + /* Subclasses can define these to implement other source methods. + */ + + /* Read from the source into the supplied buffer, args exactly as + * read(2). Set errno on error. + * + * We must return gint64, since ssize_t is often defined as unsigned + * on Windows. + */ + gint64 (*read)( VipsSource *, void *, size_t ); + + /* Seek to a certain position, args exactly as lseek(2). Set errno on + * error. + * + * Unseekable sources should always return -1. VipsSource will then + * seek by _read()ing bytes into memory as required. + * + * We have to use int64 rather than off_t, since we must work on + * Windows, where off_t can be 32-bits. + */ + gint64 (*seek)( VipsSource *, gint64, int ); + +} VipsSourceClass; + +GType vips_source_get_type( void ); + +VipsSource *vips_source_new_from_descriptor( int descriptor ); +VipsSource *vips_source_new_from_file( const char *filename ); +VipsSource *vips_source_new_from_blob( VipsBlob *blob ); +VipsSource *vips_source_new_from_memory( const void *data, size_t size ); +VipsSource *vips_source_new_from_options( const char *options ); + +void vips_source_minimise( VipsSource *source ); +int vips_source_unminimise( VipsSource *source ); +int vips_source_decode( VipsSource *source ); +gint64 vips_source_read( VipsSource *source, void *data, size_t length ); +gboolean vips_source_is_mappable( VipsSource *source ); +const void *vips_source_map( VipsSource *source, size_t *length ); +VipsBlob *vips_source_map_blob( VipsSource *source ); +gint64 vips_source_seek( VipsSource *source, gint64 offset, int whence ); +int vips_source_rewind( VipsSource *source ); +gint64 vips_source_sniff_at_most( VipsSource *source, + unsigned char **data, size_t length ); +unsigned char *vips_source_sniff( VipsSource *source, size_t length ); +gint64 vips_source_length( VipsSource *source ); + +#define VIPS_TYPE_SOURCE_CUSTOM (vips_source_custom_get_type()) +#define VIPS_SOURCE_CUSTOM( obj ) \ + (G_TYPE_CHECK_INSTANCE_CAST( (obj), \ + VIPS_TYPE_SOURCE_CUSTOM, VipsSourceCustom )) +#define VIPS_SOURCE_CUSTOM_CLASS( klass ) \ + (G_TYPE_CHECK_CLASS_CAST( (klass), \ + VIPS_TYPE_SOURCE_CUSTOM, VipsSourceCustomClass)) +#define VIPS_IS_SOURCE_CUSTOM( obj ) \ + (G_TYPE_CHECK_INSTANCE_TYPE( (obj), VIPS_TYPE_SOURCE_CUSTOM )) +#define VIPS_IS_SOURCE_CUSTOM_CLASS( klass ) \ + (G_TYPE_CHECK_CLASS_TYPE( (klass), VIPS_TYPE_SOURCE_CUSTOM )) +#define VIPS_SOURCE_CUSTOM_GET_CLASS( obj ) \ + (G_TYPE_INSTANCE_GET_CLASS( (obj), \ + VIPS_TYPE_SOURCE_CUSTOM, VipsSourceCustomClass )) + +/* Subclass of source_custom with signals for handlers. This is supposed to be + * useful for language bindings. + */ +typedef struct _VipsSourceCustom { + VipsSource parent_object; + +} VipsSourceCustom; + +typedef struct _VipsSourceCustomClass { + VipsSourceClass parent_class; + + /* The action signals clients can use to implement read and seek. + * We must use gint64 everywhere since there's no G_TYPE_SIZE. + */ + + gint64 (*read)( VipsSourceCustom *, void *, gint64 ); + gint64 (*seek)( VipsSourceCustom *, gint64, int ); + +} VipsSourceCustomClass; + +GType vips_source_custom_get_type( void ); +VipsSourceCustom *vips_source_custom_new( void ); + +/* A GInputStream that's actually a VipsSource under the hood. This lets us + * hook librsvg up to libvips using the GInputStream interface. + */ + +#define VIPS_TYPE_G_INPUT_STREAM (vips_g_input_stream_get_type()) +#define VIPS_G_INPUT_STREAM( obj ) \ + (G_TYPE_CHECK_INSTANCE_CAST( (obj), \ + VIPS_TYPE_G_INPUT_STREAM, VipsGInputStream )) +#define VIPS_G_INPUT_STREAM_CLASS( klass ) \ + (G_TYPE_CHECK_CLASS_CAST( (klass), \ + VIPS_TYPE_G_INPUT_STREAM, VipsGInputStreamClass)) +#define VIPS_IS_G_INPUT_STREAM( obj ) \ + (G_TYPE_CHECK_INSTANCE_TYPE( (obj), VIPS_TYPE_G_INPUT_STREAM )) +#define VIPS_IS_G_INPUT_STREAM_CLASS( klass ) \ + (G_TYPE_CHECK_CLASS_TYPE( (klass), VIPS_TYPE_G_INPUT_STREAM )) +#define VIPS_G_INPUT_STREAM_GET_CLASS( obj ) \ + (G_TYPE_INSTANCE_GET_CLASS( (obj), \ + VIPS_TYPE_G_INPUT_STREAM, VipsGInputStreamClass )) + +/* GInputStream <--> VipsSource + */ +typedef struct _VipsGInputStream { + GInputStream parent_instance; + + /*< private >*/ + + /* The VipsSource we wrap. + */ + VipsSource *source; + +} VipsGInputStream; + +typedef struct _VipsGInputStreamClass { + GInputStreamClass parent_class; + +} VipsGInputStreamClass; + +GInputStream *vips_g_input_stream_new_from_source( VipsSource *source ); + +#define VIPS_TYPE_TARGET (vips_target_get_type()) +#define VIPS_TARGET( obj ) \ + (G_TYPE_CHECK_INSTANCE_CAST( (obj), \ + VIPS_TYPE_TARGET, VipsTarget )) +#define VIPS_TARGET_CLASS( klass ) \ + (G_TYPE_CHECK_CLASS_CAST( (klass), \ + VIPS_TYPE_TARGET, VipsTargetClass)) +#define VIPS_IS_TARGET( obj ) \ + (G_TYPE_CHECK_INSTANCE_TYPE( (obj), VIPS_TYPE_TARGET )) +#define VIPS_IS_TARGET_CLASS( klass ) \ + (G_TYPE_CHECK_CLASS_TYPE( (klass), VIPS_TYPE_TARGET )) +#define VIPS_TARGET_GET_CLASS( obj ) \ + (G_TYPE_INSTANCE_GET_CLASS( (obj), \ + VIPS_TYPE_TARGET, VipsTargetClass )) + +/* PNG writes in 8kb chunks, so we need to be a little larger than that. + */ +#define VIPS_TARGET_BUFFER_SIZE (8500) + +/* Output to something like a socket, pipe or memory area. + */ +typedef struct _VipsTarget { + VipsConnection parent_object; + + /*< private >*/ + + /* This target should write to memory. + */ + gboolean memory; + + /* The target has been finished and can no longer be written. + */ + gboolean finished; + + /* Write memory output here. + */ + GByteArray *memory_buffer; + + /* And return memory via this blob. + */ + VipsBlob *blob; + + /* Buffer small writes here. write_point is the index of the next + * character to write. + */ + unsigned char output_buffer[VIPS_TARGET_BUFFER_SIZE]; + int write_point; + +} VipsTarget; + +typedef struct _VipsTargetClass { + VipsConnectionClass parent_class; + + /* Write to output. Args exactly as write(2). + * + * We must return gint64, since ssize_t is often defined as unsigned + * on Windows. + */ + gint64 (*write)( VipsTarget *, const void *, size_t ); + + /* Output has been generated, so do any clearing up, + * eg. copy the bytes we saved in memory to the target blob. + */ + void (*finish)( VipsTarget * ); + +} VipsTargetClass; + +GType vips_target_get_type( void ); + +VipsTarget *vips_target_new_to_descriptor( int descriptor ); +VipsTarget *vips_target_new_to_file( const char *filename ); +VipsTarget *vips_target_new_to_memory( void ); +int vips_target_write( VipsTarget *target, const void *data, size_t length ); +void vips_target_finish( VipsTarget *target ); +unsigned char *vips_target_steal( VipsTarget *target, size_t *length ); +char *vips_target_steal_text( VipsTarget *target ); + +int vips_target_putc( VipsTarget *target, int ch ); +#define VIPS_TARGET_PUTC( S, C ) ( \ + (S)->write_point < VIPS_TARGET_BUFFER_SIZE ? \ + ((S)->output_buffer[(S)->write_point++] = (C), 0) : \ + vips_target_putc( (S), (C) ) \ +) +int vips_target_writes( VipsTarget *target, const char *str ); +int vips_target_writef( VipsTarget *target, const char *fmt, ... ) + __attribute__((format(printf, 2, 3))); +int vips_target_write_amp( VipsTarget *target, const char *str ); + +#define VIPS_TYPE_TARGET_CUSTOM (vips_target_custom_get_type()) +#define VIPS_TARGET_CUSTOM( obj ) \ + (G_TYPE_CHECK_INSTANCE_CAST( (obj), \ + VIPS_TYPE_TARGET_CUSTOM, VipsTargetCustom )) +#define VIPS_TARGET_CUSTOM_CLASS( klass ) \ + (G_TYPE_CHECK_CLASS_CAST( (klass), \ + VIPS_TYPE_TARGET_CUSTOM, VipsTargetCustomClass)) +#define VIPS_IS_TARGET_CUSTOM( obj ) \ + (G_TYPE_CHECK_INSTANCE_TYPE( (obj), VIPS_TYPE_TARGET_CUSTOM )) +#define VIPS_IS_TARGET_CUSTOM_CLASS( klass ) \ + (G_TYPE_CHECK_CLASS_TYPE( (klass), VIPS_TYPE_TARGET_CUSTOM )) +#define VIPS_TARGET_CUSTOM_GET_CLASS( obj ) \ + (G_TYPE_INSTANCE_GET_CLASS( (obj), \ + VIPS_TYPE_TARGET_CUSTOM, VipsTargetCustomClass )) + +#define VIPS_TARGET_CUSTOM_BUFFER_SIZE (4096) + +/* Output to something like a socket, pipe or memory area. + */ +typedef struct _VipsTargetCustom { + VipsTarget parent_object; + +} VipsTargetCustom; + +typedef struct _VipsTargetCustomClass { + VipsTargetClass parent_class; + + /* The action signals clients can use to implement write and finish. + * We must use gint64 everywhere since there's no G_TYPE_SIZE. + */ + + gint64 (*write)( VipsTargetCustom *, const void *, gint64 ); + void (*finish)( VipsTargetCustom * ); + +} VipsTargetCustomClass; + +GType vips_target_custom_get_type( void ); +VipsTargetCustom *vips_target_custom_new( void ); + +#ifdef __cplusplus +} +#endif /*__cplusplus*/ + +#endif /*VIPS_CONNECTION_H*/ diff --git a/libvips/include/vips/conversion.h b/libvips/include/vips/conversion.h index 109b456e..9dfaa521 100644 --- a/libvips/include/vips/conversion.h +++ b/libvips/include/vips/conversion.h @@ -101,6 +101,7 @@ typedef enum { VIPS_INTERESTING_ATTENTION, VIPS_INTERESTING_LOW, VIPS_INTERESTING_HIGH, + VIPS_INTERESTING_ALL, VIPS_INTERESTING_LAST } VipsInteresting; @@ -191,7 +192,6 @@ int vips_rot270( VipsImage *in, VipsImage **out, ... ) __attribute__((sentinel)); int vips_rot45( VipsImage *in, VipsImage **out, ... ) __attribute__((sentinel)); -VipsAngle vips_autorot_get_angle( VipsImage *image ); void vips_autorot_remove_angle( VipsImage *image ); int vips_autorot( VipsImage *in, VipsImage **out, ... ) __attribute__((sentinel)); diff --git a/libvips/include/vips/dbuf.h b/libvips/include/vips/dbuf.h index 787b505d..bb8fca47 100644 --- a/libvips/include/vips/dbuf.h +++ b/libvips/include/vips/dbuf.h @@ -68,7 +68,8 @@ size_t vips_dbuf_read( VipsDbuf *dbuf, unsigned char *data, size_t size ); unsigned char *vips_dbuf_get_write( VipsDbuf *dbuf, size_t *size ); gboolean vips_dbuf_write( VipsDbuf *dbuf, const unsigned char *data, size_t size ); -gboolean vips_dbuf_writef( VipsDbuf *dbuf, const char *fmt, ... ); +gboolean vips_dbuf_writef( VipsDbuf *dbuf, const char *fmt, ... ) + __attribute__((format(printf, 2, 3))); gboolean vips_dbuf_write_amp( VipsDbuf *dbuf, const char *str ); void vips_dbuf_reset( VipsDbuf *dbuf ); void vips_dbuf_destroy( VipsDbuf *dbuf ); diff --git a/libvips/include/vips/enumtypes.h b/libvips/include/vips/enumtypes.h index 2de73711..bc7fbb6d 100644 --- a/libvips/include/vips/enumtypes.h +++ b/libvips/include/vips/enumtypes.h @@ -58,6 +58,8 @@ GType vips_foreign_flags_get_type (void) G_GNUC_CONST; #define VIPS_TYPE_FOREIGN_FLAGS (vips_foreign_flags_get_type()) GType vips_saveable_get_type (void) G_GNUC_CONST; #define VIPS_TYPE_SAVEABLE (vips_saveable_get_type()) +GType vips_foreign_jpeg_subsample_get_type (void) G_GNUC_CONST; +#define VIPS_TYPE_FOREIGN_JPEG_SUBSAMPLE (vips_foreign_jpeg_subsample_get_type()) GType vips_foreign_webp_preset_get_type (void) G_GNUC_CONST; #define VIPS_TYPE_FOREIGN_WEBP_PRESET (vips_foreign_webp_preset_get_type()) GType vips_foreign_tiff_compression_get_type (void) G_GNUC_CONST; diff --git a/libvips/include/vips/foreign.h b/libvips/include/vips/foreign.h index 432394fc..944f802b 100644 --- a/libvips/include/vips/foreign.h +++ b/libvips/include/vips/foreign.h @@ -184,13 +184,21 @@ typedef struct _VipsForeignLoadClass { */ gboolean (*is_a_buffer)( const void *data, size_t size ); + /* Is a stream in this format. + * + * This function should return %TRUE if the stream contains an image of + * this type. + */ + gboolean (*is_a_source)( VipsSource *source ); + /* Get the flags from a filename. * * This function should examine the file and return a set * of flags. If you don't define it, vips will default to 0 (no flags * set). * - * This operation is necessary for vips7 compatibility. + * This method is necessary for vips7 compatibility. Don't define + * it if you don't need vips7. */ VipsForeignFlags (*get_flags_filename)( const char *filename ); @@ -210,8 +218,7 @@ typedef struct _VipsForeignLoadClass { * @header() needs to set the dhint on the image .. otherwise you get * the default SMALLTILE. * - * Return 0 for success, -1 for error, setting - * vips_error(). + * Return 0 for success, -1 for error, setting vips_error(). */ int (*header)( VipsForeignLoad *load ); @@ -233,11 +240,14 @@ GType vips_foreign_load_get_type(void); const char *vips_foreign_find_load( const char *filename ); const char *vips_foreign_find_load_buffer( const void *data, size_t size ); +const char *vips_foreign_find_load_source( VipsSource *source ); VipsForeignFlags vips_foreign_flags( const char *loader, const char *filename ); gboolean vips_foreign_is_a( const char *loader, const char *filename ); gboolean vips_foreign_is_a_buffer( const char *loader, const void *data, size_t size ); +gboolean vips_foreign_is_a_source( const char *loader, + VipsSource *source ); void vips_foreign_load_invalidate( VipsImage *image ); @@ -344,6 +354,7 @@ GType vips_foreign_save_get_type(void); const char *vips_foreign_find_save( const char *filename ); gchar **vips_foreign_get_suffixes( void ); const char *vips_foreign_find_save_buffer( const char *suffix ); +const char *vips_foreign_find_save_target( const char *suffix ); int vips_vipsload( const char *filename, VipsImage **out, ... ) __attribute__((sentinel)); @@ -353,11 +364,28 @@ int vips_vipssave( VipsImage *in, const char *filename, ... ) int vips_openslideload( const char *filename, VipsImage **out, ... ) __attribute__((sentinel)); +/** + * VipsForeignJpegSubsample: + * @VIPS_FOREIGN_JPEG_SUBSAMPLE_AUTO: default preset + * @VIPS_FOREIGN_JPEG_SUBSAMPLE_ON: always perform subsampling + * @VIPS_FOREIGN_JPEG_SUBSAMPLE_OFF: never perform subsampling + * + * Set jpeg subsampling mode. + */ +typedef enum { + VIPS_FOREIGN_JPEG_SUBSAMPLE_AUTO, + VIPS_FOREIGN_JPEG_SUBSAMPLE_ON, + VIPS_FOREIGN_JPEG_SUBSAMPLE_OFF, + VIPS_FOREIGN_JPEG_SUBSAMPLE_LAST +} VipsForeignJpegSubsample; + int vips_jpegload( const char *filename, VipsImage **out, ... ) __attribute__((sentinel)); int vips_jpegload_buffer( void *buf, size_t len, VipsImage **out, ... ) __attribute__((sentinel)); +int vips_jpegsave_target( VipsImage *in, VipsTarget *target, ... ) + __attribute__((sentinel)); int vips_jpegsave( VipsImage *in, const char *filename, ... ) __attribute__((sentinel)); int vips_jpegsave_buffer( VipsImage *in, void **buf, size_t *len, ... ) @@ -386,11 +414,15 @@ typedef enum { VIPS_FOREIGN_WEBP_PRESET_LAST } VipsForeignWebpPreset; +int vips_webpload_source( VipsSource *source, VipsImage **out, ... ) + __attribute__((sentinel)); int vips_webpload( const char *filename, VipsImage **out, ... ) __attribute__((sentinel)); int vips_webpload_buffer( void *buf, size_t len, VipsImage **out, ... ) __attribute__((sentinel)); +int vips_webpsave_target( VipsImage *in, VipsTarget *target, ... ) + __attribute__((sentinel)); int vips_webpsave( VipsImage *in, const char *filename, ... ) __attribute__((sentinel)); int vips_webpsave_buffer( VipsImage *in, void **buf, size_t *len, ... ) @@ -413,7 +445,7 @@ int vips_webpsave_mime( VipsImage *in, ... ) * * Use @Q to set the jpeg compression level, default 75. * - * Use @prediction to set the lzw or deflate prediction, default none. + * Use @predictor to set the lzw or deflate prediction, default horizontal. * * Use @lossless to set WEBP lossless compression. * @@ -432,7 +464,7 @@ typedef enum { } VipsForeignTiffCompression; /** - * VipsForeignTiffPredictor: + * VipsForeignTiffPoor: * @VIPS_FOREIGN_TIFF_PREDICTOR_NONE: no prediction * @VIPS_FOREIGN_TIFF_PREDICTOR_HORIZONTAL: horizontal differencing * @VIPS_FOREIGN_TIFF_PREDICTOR_FLOAT: float predictor @@ -464,6 +496,8 @@ int vips_tiffload( const char *filename, VipsImage **out, ... ) __attribute__((sentinel)); int vips_tiffload_buffer( void *buf, size_t len, VipsImage **out, ... ) __attribute__((sentinel)); +int vips_tiffload_source( VipsSource *source, VipsImage **out, ... ) + __attribute__((sentinel)); int vips_tiffsave( VipsImage *in, const char *filename, ... ) __attribute__((sentinel)); int vips_tiffsave_buffer( VipsImage *in, void **buf, size_t *len, ... ) @@ -490,13 +524,21 @@ int vips_rawsave_fd( VipsImage *in, int fd, ... ) int vips_csvload( const char *filename, VipsImage **out, ... ) __attribute__((sentinel)); +int vips_csvload_source( VipsSource *source, VipsImage **out, ... ) + __attribute__((sentinel)); int vips_csvsave( VipsImage *in, const char *filename, ... ) __attribute__((sentinel)); +int vips_csvsave_target( VipsImage *in, VipsTarget *target, ... ) + __attribute__((sentinel)); int vips_matrixload( const char *filename, VipsImage **out, ... ) __attribute__((sentinel)); +int vips_matrixload_source( VipsSource *source, VipsImage **out, ... ) + __attribute__((sentinel)); int vips_matrixsave( VipsImage *in, const char *filename, ... ) __attribute__((sentinel)); +int vips_matrixsave_target( VipsImage *in, VipsTarget *target, ... ) + __attribute__((sentinel)); int vips_matrixprint( VipsImage *in, ... ) __attribute__((sentinel)); @@ -530,10 +572,14 @@ typedef enum /*< flags >*/ { VIPS_FOREIGN_PNG_FILTER_ALL = 0xF8 } VipsForeignPngFilter; +int vips_pngload_source( VipsSource *source, VipsImage **out, ... ) + __attribute__((sentinel)); int vips_pngload( const char *filename, VipsImage **out, ... ) __attribute__((sentinel)); int vips_pngload_buffer( void *buf, size_t len, VipsImage **out, ... ) __attribute__((sentinel)); +int vips_pngsave_target( VipsImage *in, VipsTarget *target, ... ) + __attribute__((sentinel)); int vips_pngsave( VipsImage *in, const char *filename, ... ) __attribute__((sentinel)); int vips_pngsave_buffer( VipsImage *in, void **buf, size_t *len, ... ) @@ -541,23 +587,35 @@ int vips_pngsave_buffer( VipsImage *in, void **buf, size_t *len, ... ) int vips_ppmload( const char *filename, VipsImage **out, ... ) __attribute__((sentinel)); +int vips_ppmload_source( VipsSource *source, VipsImage **out, ... ) + __attribute__((sentinel)); int vips_ppmsave( VipsImage *in, const char *filename, ... ) __attribute__((sentinel)); +int vips_ppmsave_target( VipsImage *in, VipsTarget *target, ... ) + __attribute__((sentinel)); int vips_matload( const char *filename, VipsImage **out, ... ) __attribute__((sentinel)); +int vips_radload_source( VipsSource *source, VipsImage **out, ... ) + __attribute__((sentinel)); int vips_radload( const char *filename, VipsImage **out, ... ) __attribute__((sentinel)); +int vips_radload_buffer( void *buf, size_t len, VipsImage **out, ... ) + __attribute__((sentinel)); int vips_radsave( VipsImage *in, const char *filename, ... ) __attribute__((sentinel)); int vips_radsave_buffer( VipsImage *in, void **buf, size_t *len, ... ) __attribute__((sentinel)); +int vips_radsave_target( VipsImage *in, VipsTarget *target, ... ) + __attribute__((sentinel)); int vips_pdfload( const char *filename, VipsImage **out, ... ) __attribute__((sentinel)); int vips_pdfload_buffer( void *buf, size_t len, VipsImage **out, ... ) __attribute__((sentinel)); +int vips_pdfload_source( VipsSource *source, VipsImage **out, ... ) + __attribute__((sentinel)); int vips_svgload( const char *filename, VipsImage **out, ... ) __attribute__((sentinel)); @@ -568,15 +626,21 @@ int vips_gifload( const char *filename, VipsImage **out, ... ) __attribute__((sentinel)); int vips_gifload_buffer( void *buf, size_t len, VipsImage **out, ... ) __attribute__((sentinel)); +int vips_gifload_source( VipsSource *source, VipsImage **out, ... ) + __attribute__((sentinel)); int vips_heifload( const char *filename, VipsImage **out, ... ) __attribute__((sentinel)); int vips_heifload_buffer( void *buf, size_t len, VipsImage **out, ... ) __attribute__((sentinel)); +int vips_heifload_source( VipsSource *source, VipsImage **out, ... ) + __attribute__((sentinel)); int vips_heifsave( VipsImage *in, const char *filename, ... ) __attribute__((sentinel)); int vips_heifsave_buffer( VipsImage *in, void **buf, size_t *len, ... ) __attribute__((sentinel)); +int vips_heifsave_target( VipsImage *in, VipsTarget *target, ... ) + __attribute__((sentinel)); int vips_niftiload( const char *filename, VipsImage **out, ... ) __attribute__((sentinel)); @@ -588,6 +652,7 @@ int vips_niftisave( VipsImage *in, const char *filename, ... ) * @VIPS_FOREIGN_DZ_LAYOUT_DZ: use DeepZoom directory layout * @VIPS_FOREIGN_DZ_LAYOUT_ZOOMIFY: use Zoomify directory layout * @VIPS_FOREIGN_DZ_LAYOUT_GOOGLE: use Google maps directory layout + * @VIPS_FOREIGN_DZ_LAYOUT_IIIF: use IIIF directory layout * * What directory layout and metadata standard to use. */ @@ -595,6 +660,7 @@ typedef enum { VIPS_FOREIGN_DZ_LAYOUT_DZ, VIPS_FOREIGN_DZ_LAYOUT_ZOOMIFY, VIPS_FOREIGN_DZ_LAYOUT_GOOGLE, + VIPS_FOREIGN_DZ_LAYOUT_IIIF, VIPS_FOREIGN_DZ_LAYOUT_LAST } VipsForeignDzLayout; diff --git a/libvips/include/vips/header.h b/libvips/include/vips/header.h index d4b3f6dc..11e9ea3f 100644 --- a/libvips/include/vips/header.h +++ b/libvips/include/vips/header.h @@ -150,6 +150,13 @@ extern "C" { */ #define VIPS_META_N_PAGES "n-pages" +/** + * VIPS_META_N_SUBIFDS: + * + * If set, the number of subifds in the first page of the file. + */ +#define VIPS_META_N_SUBIFDS "n-subifds" + guint64 vips_format_sizeof( VipsBandFormat format ); guint64 vips_format_sizeof_unsafe( VipsBandFormat format ); @@ -171,6 +178,9 @@ double vips_image_get_scale( const VipsImage *image ); double vips_image_get_offset( const VipsImage *image ); int vips_image_get_page_height( VipsImage *image ); int vips_image_get_n_pages( VipsImage *image ); +int vips_image_get_n_subifds( VipsImage *image ); +int vips_image_get_orientation( VipsImage *image ); +gboolean vips_image_get_orientation_swap( VipsImage *image ); const void *vips_image_get_data( VipsImage *image ); void vips_image_init_fields( VipsImage *image, diff --git a/libvips/include/vips/histogram.h b/libvips/include/vips/histogram.h index 039b9d94..98d18fc7 100644 --- a/libvips/include/vips/histogram.h +++ b/libvips/include/vips/histogram.h @@ -40,8 +40,6 @@ extern "C" { int vips_maplut( VipsImage *in, VipsImage **out, VipsImage *lut, ... ) __attribute__((sentinel)); -int vips_mapimage( VipsImage *in, VipsImage **out, VipsImage **lut, int n, ... ) - __attribute__((sentinel)); int vips_percent( VipsImage *in, double percent, int *threshold, ... ) __attribute__((sentinel)); int vips_stdif( VipsImage *in, VipsImage **out, int width, int height, ... ) diff --git a/libvips/include/vips/image.h b/libvips/include/vips/image.h index be5dc94a..d400ffc4 100644 --- a/libvips/include/vips/image.h +++ b/libvips/include/vips/image.h @@ -133,9 +133,6 @@ typedef enum { VIPS_ACCESS_LAST } VipsAccess; -struct _VipsImage; -struct _VipsRegion; - typedef void *(*VipsStartFn)( struct _VipsImage *out, void *a, void *b ); typedef int (*VipsGenerateFn)( struct _VipsRegion *out, void *seq, void *a, void *b, gboolean *stop ); @@ -450,13 +447,16 @@ VipsImage *vips_image_new_from_memory_copy( const void *data, size_t size, VipsImage *vips_image_new_from_buffer( const void *buf, size_t len, const char *option_string, ... ) __attribute__((sentinel)); +VipsImage *vips_image_new_from_source( VipsSource *source, + const char *option_string, ... ) __attribute__((sentinel)); VipsImage *vips_image_new_matrix( int width, int height ); VipsImage *vips_image_new_matrixv( int width, int height, ... ); VipsImage *vips_image_new_matrix_from_array( int width, int height, const double *array, int size ); VipsImage *vips_image_matrix_from_array( int width, int height, const double *array, int size ); -VipsImage *vips_image_new_from_image( VipsImage *image, const double *c, int n ); +VipsImage *vips_image_new_from_image( VipsImage *image, + const double *c, int n ); VipsImage *vips_image_new_from_image1( VipsImage *image, double c ); void vips_image_set_delete_on_close( VipsImage *image, @@ -470,6 +470,9 @@ int vips_image_write_to_file( VipsImage *image, const char *name, ... ) int vips_image_write_to_buffer( VipsImage *in, const char *suffix, void **buf, size_t *size, ... ) __attribute__((sentinel)); +int vips_image_write_to_target( VipsImage *in, + const char *suffix, VipsTarget *target, ... ) + __attribute__((sentinel)); void *vips_image_write_to_memory( VipsImage *in, size_t *size ); int vips_image_decode_predict( VipsImage *in, diff --git a/libvips/include/vips/internal.h b/libvips/include/vips/internal.h index 3ecd3f1c..e9a78640 100644 --- a/libvips/include/vips/internal.h +++ b/libvips/include/vips/internal.h @@ -114,6 +114,7 @@ void vips__threadpool_init( void ); void vips__cache_init( void ); +void vips__sink_screen_init( void ); void vips__print_renders( void ); void vips__type_leak( void ); @@ -133,9 +134,7 @@ void vips__link_break_all( VipsImage *im ); void *vips__link_map( VipsImage *image, gboolean upstream, VipsSListMap2Fn fn, void *a, void *b ); -char *vips__b64_encode( const unsigned char *data, size_t data_length ); -unsigned char *vips__b64_decode( const char *buffer, size_t *data_length ); - +gboolean vips__mmap_supported( int fd ); void *vips__mmap( int fd, int writeable, size_t length, gint64 offset ); int vips__munmap( const void *start, size_t length ); int vips_mapfile( VipsImage * ); diff --git a/libvips/include/vips/memory.h b/libvips/include/vips/memory.h index bdcd0db7..99b26e8f 100644 --- a/libvips/include/vips/memory.h +++ b/libvips/include/vips/memory.h @@ -74,7 +74,7 @@ size_t vips_tracked_get_mem( void ); size_t vips_tracked_get_mem_highwater( void ); int vips_tracked_get_allocs( void ); -int vips_tracked_open( const char *pathname, int flags, ... ); +int vips_tracked_open( const char *pathname, int flags, mode_t mode ); int vips_tracked_close( int fd ); int vips_tracked_get_files( void ); diff --git a/libvips/include/vips/mosaicing.h b/libvips/include/vips/mosaicing.h index dd877d42..4a18068c 100644 --- a/libvips/include/vips/mosaicing.h +++ b/libvips/include/vips/mosaicing.h @@ -51,6 +51,36 @@ int vips_mosaic1( VipsImage *ref, VipsImage *sec, VipsImage **out, int xr2, int yr2, int xs2, int ys2, ... ) __attribute__((sentinel)); +/* TODO(kleisauke): Convert these to pure vips8 classes? */ +int vips_correl( VipsImage *ref, VipsImage *sec, + int xref, int yref, int xsec, int ysec, + int hwindowsize, int hsearchsize, + double *correlation, int *x, int *y/*, ...*/ ) + /*__attribute__((sentinel))*/; + +int vips_lrmerge( VipsImage *ref, VipsImage *sec, VipsImage *out, + int dx, int dy, int mwidth/*, ...*/ ) + /*__attribute__((sentinel))*/; +int vips_tbmerge( VipsImage *ref, VipsImage *sec, VipsImage *out, + int dx, int dy, int mwidth/*, ...*/ ) + /*__attribute__((sentinel))*/; + +int vips_lrmosaic( VipsImage *ref, VipsImage *sec, VipsImage *out, + int bandno, + int xref, int yref, int xsec, int ysec, + int hwindowsize, int hsearchsize, + int balancetype, + int mwidth/*, ...*/ ) + /*__attribute__((sentinel))*/; + +int vips_tbmosaic( VipsImage *ref, VipsImage *sec, VipsImage *out, + int bandno, + int xref, int yref, int xsec, int ysec, + int hwindowsize, int hsearchsize, + int balancetype, + int mwidth/*, ...*/ ) + /*__attribute__((sentinel))*/; + int vips_match( VipsImage *ref, VipsImage *sec, VipsImage **out, int xr1, int yr1, int xs1, int ys1, int xr2, int yr2, int xs2, int ys2, ... ) @@ -62,6 +92,9 @@ int vips_remosaic( VipsImage *in, VipsImage **out, const char *old_str, const char *new_str, ... ) __attribute__((sentinel)); +int vips_matrixinvert( VipsImage *m, VipsImage **out, ... ) + __attribute__((sentinel)); + #ifdef __cplusplus } diff --git a/libvips/include/vips/object.h b/libvips/include/vips/object.h index 82fdfd43..ff500a61 100644 --- a/libvips/include/vips/object.h +++ b/libvips/include/vips/object.h @@ -112,11 +112,11 @@ VIPS_ARGUMENT_OPTIONAL_OUTPUT Eg. the x pos of the image minimum pspec, (VipsArgumentFlags) (FLAGS), (PRIORITY), (OFFSET) ); \ } -#define VIPS_ARG_INTERPOLATE( CLASS, NAME, PRIORITY, LONG, DESC, FLAGS, OFFSET ) { \ +#define VIPS_ARG_OBJECT( CLASS, NAME, PRIORITY, LONG, DESC, FLAGS, OFFSET, TYPE ) { \ GParamSpec *pspec; \ \ pspec = g_param_spec_object( (NAME), (LONG), (DESC), \ - VIPS_TYPE_INTERPOLATE, \ + TYPE, \ (GParamFlags) (G_PARAM_READWRITE) ); \ g_object_class_install_property( G_OBJECT_CLASS( CLASS ), \ vips_argument_get_id(), pspec ); \ @@ -124,6 +124,9 @@ VIPS_ARGUMENT_OPTIONAL_OUTPUT Eg. the x pos of the image minimum pspec, (VipsArgumentFlags) (FLAGS), (PRIORITY), (OFFSET) ); \ } +#define VIPS_ARG_INTERPOLATE( CLASS, NAME, PRIORITY, LONG, DESC, FLAGS, OFFSET ) \ + VIPS_ARG_OBJECT( CLASS, NAME, PRIORITY, LONG, DESC, FLAGS, OFFSET, VIPS_TYPE_INTERPOLATE ) + #define VIPS_ARG_BOOL( CLASS, NAME, PRIORITY, LONG, DESC, \ FLAGS, OFFSET, VALUE ) { \ GParamSpec *pspec; \ diff --git a/libvips/include/vips/private.h b/libvips/include/vips/private.h index e28735fc..8716cf36 100644 --- a/libvips/include/vips/private.h +++ b/libvips/include/vips/private.h @@ -192,6 +192,8 @@ int vips__view_image( struct _VipsImage *image ); */ extern int _vips__argument_id; +void vips__meta_init( void ); + #ifdef __cplusplus } #endif /*__cplusplus*/ diff --git a/libvips/include/vips/region.h b/libvips/include/vips/region.h index 2a68bb17..d52a3fa7 100644 --- a/libvips/include/vips/region.h +++ b/libvips/include/vips/region.h @@ -60,6 +60,9 @@ extern "C" { * @VIPS_REGION_SHRINK_MEAN: use the average * @VIPS_REGION_SHRINK_MEDIAN: use the median * @VIPS_REGION_SHRINK_MODE: use the mode + * @VIPS_REGION_SHRINK_MAX: use the maximum + * @VIPS_REGION_SHRINK_MIN: use the minimum + * @VIPS_REGION_SHRINK_NEAREST: use the top-left pixel * * How to calculate the output pixels when shrinking a 2x2 region. */ @@ -67,6 +70,9 @@ typedef enum { VIPS_REGION_SHRINK_MEAN, VIPS_REGION_SHRINK_MEDIAN, VIPS_REGION_SHRINK_MODE, + VIPS_REGION_SHRINK_MAX, + VIPS_REGION_SHRINK_MIN, + VIPS_REGION_SHRINK_NEAREST, VIPS_REGION_SHRINK_LAST } VipsRegionShrink; diff --git a/libvips/include/vips/renameE b/libvips/include/vips/renameE new file mode 100644 index 00000000..26d90937 --- /dev/null +++ b/libvips/include/vips/renameE @@ -0,0 +1,299 @@ +/* Old and broken stuff that we still enable by default, but don't document + * and certainly don't recommend. + * + * 30/6/09 + * - from vips.h + */ + +/* + + 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 + + */ + +#ifndef IM_ALMOSTDEPRECATED_H +#define IM_ALMOSTDEPRECATED_H + +#ifdef __cplusplus +extern "C" { +#endif /*__cplusplus*/ + +/* Was public, now deprecated. + */ +typedef enum { + IM_BBITS_BYTE = 8, + IM_BBITS_SHORT = 16, + IM_BBITS_INT = 32, + IM_BBITS_FLOAT = 32, + IM_BBITS_COMPLEX = 64, + IM_BBITS_DOUBLE = 64, + IM_BBITS_DPCOMPLEX = 128 +} VipsBBits; + +/* Used to define a region of interest for im_extract() etc. Too boring to be + * public API, see im_extract_area() etc. + */ +typedef struct { + int xstart; + int ystart; + int xsize; + int ysize; + int chsel; /* 1 2 3 or 0, for r g b or all respectively + *(channel select) */ +} IMAGE_BOX; + +int im_extract( IMAGE *, IMAGE *, IMAGE_BOX * ); +DOUBLEMASK *im_measure( IMAGE *im, IMAGE_BOX *box, int h, int v, + int *sel, int nsel, const char *name ); + +gboolean im_isuint( IMAGE *im ); +gboolean im_isint( IMAGE *im ); +gboolean im_isfloat( IMAGE *im ); +gboolean im_isscalar( IMAGE *im ); +gboolean im_iscomplex( IMAGE *im ); + +int im_c2ps( IMAGE *in, IMAGE *out ); + +int im_clip( IMAGE *in, IMAGE *out ); + +#define MASK_IDEAL_HIGHPASS IM_MASK_IDEAL_HIGHPASS +#define MASK_IDEAL_LOWPASS IM_MASK_IDEAL_LOWPASS +#define MASK_BUTTERWORTH_HIGHPASS IM_MASK_BUTTERWORTH_HIGHPASS +#define MASK_BUTTERWORTH_LOWPASS IM_MASK_BUTTERWORTH_LOWPASS +#define MASK_GAUSS_HIGHPASS IM_MASK_GAUSS_HIGHPASS +#define MASK_GAUSS_LOWPASS IM_MASK_GAUSS_LOWPASS + +#define MASK_IDEAL_RINGPASS IM_MASK_IDEAL_RINGPASS +#define MASK_IDEAL_RINGREJECT IM_MASK_IDEAL_RINGREJECT +#define MASK_BUTTERWORTH_RINGPASS IM_MASK_BUTTERWORTH_RINGPASS +#define MASK_BUTTERWORTH_RINGREJECT IM_MASK_BUTTERWORTH_RINGREJECT +#define MASK_GAUSS_RINGPASS IM_MASK_GAUSS_RINGPASS +#define MASK_GAUSS_RINGREJECT IM_MASK_GAUSS_RINGREJECT + +#define MASK_IDEAL_BANDPASS IM_MASK_IDEAL_BANDPASS +#define MASK_IDEAL_BANDREJECT IM_MASK_IDEAL_BANDREJECT +#define MASK_BUTTERWORTH_BANDPASS IM_MASK_BUTTERWORTH_BANDPASS +#define MASK_BUTTERWORTH_BANDREJECT IM_MASK_BUTTERWORTH_BANDREJECT +#define MASK_GAUSS_BANDPASS IM_MASK_GAUSS_BANDPASS +#define MASK_GAUSS_BANDREJECT IM_MASK_GAUSS_BANDREJECT + +#define MASK_FRACTAL_FLT IM_MASK_FRACTAL_FLT + +#define MaskType ImMaskType + +/* Copy and swap types. + */ +typedef enum { + IM_ARCH_NATIVE, + IM_ARCH_BYTE_SWAPPED, + IM_ARCH_LSB_FIRST, + IM_ARCH_MSB_FIRST +} im_arch_type; + +gboolean im_isnative( im_arch_type arch ); +int im_copy_from( IMAGE *in, IMAGE *out, im_arch_type architecture ); + +/* Backwards compatibility macros. + */ +#define im_clear_error_string() im_error_clear() +#define im_errorstring() im_error_buffer() + +/* Deprecated API. + */ +void im_errormsg( const char *fmt, ... ) + __attribute__((format(printf, 1, 2))); +void im_verrormsg( const char *fmt, va_list ap ); +void im_errormsg_system( int err, const char *fmt, ... ) + __attribute__((format(printf, 2, 3))); +void im_diagnostics( const char *fmt, ... ) + __attribute__((format(printf, 1, 2))); +void im_warning( const char *fmt, ... ) + __attribute__((format(printf, 1, 2))); + +int im_iterate( VipsImage *im, + VipsStartFn start, im_generate_fn generate, VipsStopFn stop, + void *a, void *b +); + +/* Async rendering. + */ +int im_render_priority( VipsImage *in, VipsImage *out, VipsImage *mask, + int width, int height, int max, + int priority, + void (*notify)( VipsImage *, VipsRect *, void * ), void *client ); +int im_cache( VipsImage *in, VipsImage *out, int width, int height, int max ); + +/* Deprecated operations. + */ +int im_cmulnorm( IMAGE *in1, IMAGE *in2, IMAGE *out ); +int im_fav4( IMAGE **, IMAGE * ); +int im_gadd( double, IMAGE *, double, IMAGE *, double, IMAGE *); +int im_litecor( IMAGE *, IMAGE *, IMAGE *, int, double ); +int im_render_fade( IMAGE *in, IMAGE *out, IMAGE *mask, + int width, int height, int max, + int fps, int steps, + int priority, + void (*notify)( IMAGE *, VipsRect *, void * ), void *client ); +int im_render( IMAGE *in, IMAGE *out, IMAGE *mask, + int width, int height, int max, + void (*notify)( IMAGE *, VipsRect *, void * ), void *client ); + +int im_cooc_matrix( IMAGE *im, IMAGE *m, + int xp, int yp, int xs, int ys, int dx, int dy, int flag ); +int im_cooc_asm( IMAGE *m, double *asmoment ); +int im_cooc_contrast( IMAGE *m, double *contrast ); +int im_cooc_correlation( IMAGE *m, double *correlation ); +int im_cooc_entropy( IMAGE *m, double *entropy ); + +int im_glds_matrix( IMAGE *im, IMAGE *m, + int xpos, int ypos, int xsize, int ysize, int dx, int dy ); +int im_glds_asm( IMAGE *m, double *asmoment ); +int im_glds_contrast( IMAGE *m, double *contrast ); +int im_glds_entropy( IMAGE *m, double *entropy ); +int im_glds_mean( IMAGE *m, double *mean ); + +int im_dif_std(IMAGE *im, int xpos, int ypos, int xsize, int ysize, int dx, int dy, double *pmean, double *pstd); +int im_simcontr( IMAGE *out, int xsize, int ysize ); +int im_spatres( IMAGE *in, IMAGE *out, int step ); + +int im_stretch3( IMAGE *in, IMAGE *out, double dx, double dy ); + +/* Renamed operations. + */ + +/* arithmetic + */ +int im_remainderconst_vec( IMAGE *in, IMAGE *out, int n, double *c ); + +/* boolean + */ +int im_andconst( IMAGE *, IMAGE *, double ); +int im_and_vec( IMAGE *, IMAGE *, int, double * ); +int im_orconst( IMAGE *, IMAGE *, double ); +int im_or_vec( IMAGE *, IMAGE *, int, double * ); +int im_eorconst( IMAGE *, IMAGE *, double ); +int im_eor_vec( IMAGE *, IMAGE *, int, double * ); + +/* mosaicing + */ +int im_affine( IMAGE *in, IMAGE *out, + double a, double b, double c, double d, double dx, double dy, + int ox, int oy, int ow, int oh ); +int im_similarity( IMAGE *in, IMAGE *out, + double a, double b, double dx, double dy ); +int im_similarity_area( IMAGE *in, IMAGE *out, + double a, double b, double dx, double dy, + int ox, int oy, int ow, int oh ); + +/* colour + */ +int im_icc_export( IMAGE *in, IMAGE *out, + const char *output_profile_filename, int intent ); + +/* conversion + */ +int im_clip2dcm( IMAGE *in, IMAGE *out ); +int im_clip2cm( IMAGE *in, IMAGE *out ); +int im_clip2us( IMAGE *in, IMAGE *out ); +int im_clip2ui( IMAGE *in, IMAGE *out ); +int im_clip2s( IMAGE *in, IMAGE *out ); +int im_clip2i( IMAGE *in, IMAGE *out ); +int im_clip2d( IMAGE *in, IMAGE *out ); +int im_clip2f( IMAGE *in, IMAGE *out ); +int im_clip2c( IMAGE *in, IMAGE *out ); + +int im_slice( IMAGE *in, IMAGE *out, double, double ); +int im_thresh( IMAGE *in, IMAGE *out, double ); + +int im_print( const char *message ); + +int im_convsub( IMAGE *in, IMAGE *out, INTMASK *mask, int xskip, int yskip ); + +int im_bernd( const char *tiffname, int x, int y, int w, int h ); + +int im_resize_linear( IMAGE *, IMAGE *, int, int ); + +int im_convf( IMAGE *in, IMAGE *out, DOUBLEMASK *mask ); +int im_convsepf( IMAGE *in, IMAGE *out, DOUBLEMASK *mask ); +int im_conv_raw( IMAGE *in, IMAGE *out, INTMASK *mask ); +int im_convf_raw( IMAGE *in, IMAGE *out, DOUBLEMASK *mask ); +int im_convsep_raw( IMAGE *in, IMAGE *out, INTMASK *mask ); +int im_convsepf_raw( IMAGE *in, IMAGE *out, DOUBLEMASK *mask ); +int im_fastcor_raw( IMAGE *in, IMAGE *ref, IMAGE *out ); +int im_spcor_raw( IMAGE *in, IMAGE *ref, IMAGE *out ); +int im_gradcor_raw( IMAGE *in, IMAGE *ref, IMAGE *out ); +int im_contrast_surface_raw( IMAGE *in, IMAGE *out, + int half_win_size, int spacing ); + +int im_stdif_raw( IMAGE *in, IMAGE *out, + double a, double m0, double b, double s0, int xwin, int ywin ); +int im_lhisteq_raw( IMAGE *in, IMAGE *out, int xwin, int ywin ); + +int im_erode_raw( IMAGE *in, IMAGE *out, INTMASK *m ); +int im_dilate_raw( IMAGE *in, IMAGE *out, INTMASK *m ); +int im_rank_raw( IMAGE *in, IMAGE *out, int xsize, int ysize, int order ); + +/* inplace + */ +int im_circle( IMAGE *im, int cx, int cy, int radius, int intensity ); +int im_line( IMAGE *, int, int, int, int, int ); +int im_segment( IMAGE *test, IMAGE *mask, int *segments ); +int im_paintrect( IMAGE *im, VipsRect *r, PEL *ink ); +int im_insertplace( IMAGE *main, IMAGE *sub, int x, int y ); + +int im_flood_copy( IMAGE *in, IMAGE *out, int x, int y, PEL *ink ); +int im_flood_blob_copy( IMAGE *in, IMAGE *out, int x, int y, PEL *ink ); +int im_flood_other_copy( IMAGE *test, IMAGE *mark, IMAGE *out, + int x, int y, int serial ); + +int im_flood( IMAGE *im, int x, int y, PEL *ink, VipsRect *dout ); +int im_flood_blob( IMAGE *im, int x, int y, PEL *ink, VipsRect *dout ); +int im_flood_other( IMAGE *test, IMAGE *mark, + int x, int y, int serial, VipsRect *dout ); + +int im_fastline( IMAGE *im, int x1, int y1, int x2, int y2, PEL *pel ); +int im_fastlineuser( IMAGE *im, + int x1, int y1, int x2, int y2, + VipsPlotFn fn, void *client1, void *client2, void *client3 ); + +int im_plotmask( IMAGE *im, int ix, int iy, PEL *ink, PEL *mask, VipsRect *r ); +int im_readpoint( IMAGE *im, int x, int y, PEL *pel ); +int im_plotpoint( IMAGE *im, int x, int y, PEL *pel ); + +int im_smudge( IMAGE *image, int ix, int iy, VipsRect *r ); +int im_smear( IMAGE *im, int ix, int iy, VipsRect *r ); + +void vips_warn( const char *domain, const char *fmt, ... ) + __attribute__((format(printf, 2, 3))); +void vips_vwarn( const char *domain, const char *fmt, va_list ap ); +void vips_info_set( gboolean info ); +void vips_info( const char *domain, const char *fmt, ... ) + __attribute__((format(printf, 2, 3))); +void vips_vinfo( const char *domain, const char *fmt, va_list ap ); + +#ifdef __cplusplus +} +#endif /*__cplusplus*/ + +#endif /*IM_ALMOSTDEPRECATED_H*/ diff --git a/libvips/include/vips/resample.h b/libvips/include/vips/resample.h index d6ec0d1b..3ad2ffe3 100644 --- a/libvips/include/vips/resample.h +++ b/libvips/include/vips/resample.h @@ -79,6 +79,9 @@ int vips_thumbnail_buffer( void *buf, size_t len, VipsImage **out, __attribute__((sentinel)); int vips_thumbnail_image( VipsImage *in, VipsImage **out, int width, ... ) __attribute__((sentinel)); +int vips_thumbnail_source( VipsSource *source, VipsImage **out, + int width, ... ) + __attribute__((sentinel)); int vips_similarity( VipsImage *in, VipsImage **out, ... ) __attribute__((sentinel)); diff --git a/libvips/include/vips/sbuf.h b/libvips/include/vips/sbuf.h new file mode 100644 index 00000000..b52458bb --- /dev/null +++ b/libvips/include/vips/sbuf.h @@ -0,0 +1,130 @@ +/* Buffered inputput from a VipsSource + * + * J.Cupitt, 18/11/19 + */ + +/* + + 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 + + */ + +#ifndef VIPS_SBUF_H +#define VIPS_SBUF_H + +#ifdef __cplusplus +extern "C" { +#endif /*__cplusplus*/ + +#define VIPS_TYPE_SBUF (vips_sbuf_get_type()) +#define VIPS_SBUF( obj ) \ + (G_TYPE_CHECK_INSTANCE_CAST( (obj), \ + VIPS_TYPE_SBUF, VipsSbuf )) +#define VIPS_SBUF_CLASS( klass ) \ + (G_TYPE_CHECK_CLASS_CAST( (klass), \ + VIPS_TYPE_SBUF, VipsSbufClass)) +#define VIPS_IS_SBUF( obj ) \ + (G_TYPE_CHECK_INSTANCE_TYPE( (obj), VIPS_TYPE_SBUF )) +#define VIPS_IS_SBUF_CLASS( klass ) \ + (G_TYPE_CHECK_CLASS_TYPE( (klass), VIPS_TYPE_SBUF )) +#define VIPS_SBUF_GET_CLASS( obj ) \ + (G_TYPE_INSTANCE_GET_CLASS( (obj), \ + VIPS_TYPE_SBUF, VipsSbufClass )) + +#define VIPS_SBUF_BUFFER_SIZE (4096) + +/* Layer over source: read with an input buffer. + * + * Libraries like libjpeg do their own input buffering and need raw IO, but + * others, like radiance, need to parse the input into lines. A buffered read + * class is very convenient. + */ +typedef struct _VipsSbuf { + VipsObject parent_object; + + /*< private >*/ + + /* The VipsSource we wrap. + */ + VipsSource *source; + + /* The +1 means there's always a \0 byte at the end. + * + * Unsigned char, since we don't want >127 to be -ve. + * + * chars_in_buffer is how many chars we have in input_buffer, + * read_point is the current read position in that buffer. + */ + unsigned char input_buffer[VIPS_SBUF_BUFFER_SIZE + 1]; + int chars_in_buffer; + int read_point; + + /* Build lines of text here. + */ + unsigned char line[VIPS_SBUF_BUFFER_SIZE + 1]; + +} VipsSbuf; + +typedef struct _VipsSbufClass { + VipsObjectClass parent_class; + +} VipsSbufClass; + +GType vips_sbuf_get_type( void ); + +VipsSbuf *vips_sbuf_new_from_source( VipsSource *source ); + +void vips_sbuf_unbuffer( VipsSbuf *sbuf ); + +int vips_sbuf_getc( VipsSbuf *sbuf ); +#define VIPS_SBUF_GETC( S ) ( \ + (S)->read_point < (S)->chars_in_buffer ? \ + (S)->input_buffer[(S)->read_point++] : \ + vips_sbuf_getc( S ) \ +) +void vips_sbuf_ungetc( VipsSbuf *sbuf ); +#define VIPS_SBUF_UNGETC( S ) { \ + if( (S)->read_point > 0 ) \ + (S)->read_point -= 1; \ +} + +int vips_sbuf_require( VipsSbuf *sbuf, int require ); +#define VIPS_SBUF_REQUIRE( S, R ) ( \ + (S)->read_point + (R) <= (S)->chars_in_buffer ? \ + 0 : \ + vips_sbuf_require( (S), (R) ) \ +) +#define VIPS_SBUF_PEEK( S ) ((S)->input_buffer + (S)->read_point) +#define VIPS_SBUF_FETCH( S ) ((S)->input_buffer[(S)->read_point++]) + +const char *vips_sbuf_get_line( VipsSbuf *sbuf ); +char *vips_sbuf_get_line_copy( VipsSbuf *sbuf ); +const char *vips_sbuf_get_non_whitespace( VipsSbuf *sbuf ); +int vips_sbuf_skip_whitespace( VipsSbuf *sbuf ); + +#ifdef __cplusplus +} +#endif /*__cplusplus*/ + +#endif /*VIPS_SBUF_H*/ diff --git a/libvips/include/vips/type.h b/libvips/include/vips/type.h index 9e578ab3..ba7512ea 100644 --- a/libvips/include/vips/type.h +++ b/libvips/include/vips/type.h @@ -74,9 +74,12 @@ typedef struct _VipsArea { int count; GMutex *lock; - /* Things like ICC profiles need their own free functions. + /* Things like ICC profiles need their own free functions. + * + * Set client to anything you like -- VipsArea doesn't use this. */ VipsCallbackFn free_fn; + void *client; /* If we are holding an array (for example, an array of double), the * GType of the elements and their size. 0 for not known. @@ -161,6 +164,8 @@ VipsBlob *vips_blob_new( VipsCallbackFn free_fn, const void *data, size_t length ); VipsBlob *vips_blob_copy( const void *data, size_t length ); const void *vips_blob_get( VipsBlob *blob, size_t *length ); +void vips_blob_set( VipsBlob *blob, + VipsCallbackFn free_fn, const void *data, size_t length ); GType vips_blob_get_type(void); /** diff --git a/libvips/include/vips/util.h b/libvips/include/vips/util.h index 0d456c71..6cceac82 100644 --- a/libvips/include/vips/util.h +++ b/libvips/include/vips/util.h @@ -254,7 +254,7 @@ int vips_filename_suffix_match( const char *path, const char *suffixes[] ); gint64 vips_file_length( int fd ); int vips__write( int fd, const void *buf, size_t count ); -int vips__open( const char *filename, int flags, ... ); +int vips__open( const char *filename, int flags, mode_t mode ); int vips__open_read( const char *filename ); FILE *vips__fopen( const char *filename, const char *mode ); @@ -266,8 +266,8 @@ char *vips__file_read( FILE *fp, const char *name, size_t *length_out ); char *vips__file_read_name( const char *name, const char *fallback_dir, size_t *length_out ); int vips__file_write( void *data, size_t size, size_t nmemb, FILE *stream ); -guint64 vips__get_bytes( const char *filename, - unsigned char buf[], guint64 len ); +gint64 vips__get_bytes( const char *filename, + unsigned char buf[], gint64 len ); int vips__fgetc( FILE *fp ); GValue *vips__gvalue_ref_string_new( const char *text ); @@ -276,10 +276,13 @@ GSList *vips__gslist_gvalue_copy( const GSList *list ); GSList *vips__gslist_gvalue_merge( GSList *a, const GSList *b ); char *vips__gslist_gvalue_get( const GSList *list ); -int vips__seek( int fd, gint64 pos ); +gint64 vips__seek_no_error( int fd, gint64 pos, int whence ); +gint64 vips__seek( int fd, gint64 pos, int whence ); int vips__ftruncate( int fd, gint64 pos ); int vips_existsf( const char *name, ... ) __attribute__((format(printf, 1, 2))); +int vips_isdirf( const char *name, ... ) + __attribute__((format(printf, 1, 2))); int vips_mkdirf( const char *name, ... ) __attribute__((format(printf, 1, 2))); int vips_rmdirf( const char *name, ... ) @@ -343,6 +346,10 @@ guint32 vips__random_add( guint32 seed, int value ); const char *vips__icc_dir( void ); const char *vips__windows_prefix( void ); +char *vips__get_iso8601( void ); + +int vips_strtod( const char *str, double *out ); + #ifdef __cplusplus } #endif /*__cplusplus*/ diff --git a/libvips/include/vips/vector.h b/libvips/include/vips/vector.h index 0bbe3504..3752395a 100644 --- a/libvips/include/vips/vector.h +++ b/libvips/include/vips/vector.h @@ -37,15 +37,17 @@ /* If we are building with -fcf-protection (run-time checking of * indirect jumps) then Orc won't work. Make sure it's off. * - * Orc may support -fcf-protection in the future, but does not in June 2019. - * * https://gcc.gnu.org/onlinedocs/gcc/\ * Instrumentation-Options.html#index-fcf-protection * https://gitlab.freedesktop.org/gstreamer/orc/issues/17 + * + * orc 0.4.30 and later work with cf-protection. */ #ifdef __CET__ +#ifndef HAVE_ORC_CF_PROTECTION #undef HAVE_ORC #endif +#endif #ifdef HAVE_ORC #include diff --git a/libvips/include/vips/version.h.in b/libvips/include/vips/version.h.in index 465f3f8e..1732ff96 100644 --- a/libvips/include/vips/version.h.in +++ b/libvips/include/vips/version.h.in @@ -16,6 +16,8 @@ #define VIPS_LIBRARY_REVISION (@LIBRARY_REVISION@) #define VIPS_LIBRARY_AGE (@LIBRARY_AGE@) +#define VIPS_CONFIG "@VIPS_CONFIG@" + /** * VIPS_SONAME: * diff --git a/libvips/include/vips/vips.h b/libvips/include/vips/vips.h index 0bedf6f5..20d77a43 100644 --- a/libvips/include/vips/vips.h +++ b/libvips/include/vips/vips.h @@ -89,6 +89,10 @@ extern "C" { #include #include +/* Needed for VipsGInputStream. + */ +#include + /* If we're being parsed by SWIG, remove gcc attributes. */ #ifdef SWIG @@ -113,6 +117,8 @@ extern "C" { #include #include #include +#include +#include #include #include diff --git a/libvips/include/vips/vips7compat.h b/libvips/include/vips/vips7compat.h index 5e893cec..7c1f01d0 100644 --- a/libvips/include/vips/vips7compat.h +++ b/libvips/include/vips/vips7compat.h @@ -1051,24 +1051,6 @@ int im_filename_suffix_match( const char *path, const char *suffixes[] ); char *im_getnextoption( char **in ); char *im_getsuboption( const char *buf ); -int im_lrmerge( VipsImage *ref, VipsImage *sec, VipsImage *out, - int dx, int dy, int mwidth ); -int im_tbmerge( VipsImage *ref, VipsImage *sec, VipsImage *out, - int dx, int dy, int mwidth ); - -int im_lrmosaic( VipsImage *ref, VipsImage *sec, VipsImage *out, - int bandno, - int xref, int yref, int xsec, int ysec, - int hwindowsize, int hsearchsize, - int balancetype, - int mwidth ); -int im_tbmosaic( VipsImage *ref, VipsImage *sec, VipsImage *out, - int bandno, - int xref, int yref, int xsec, int ysec, - int hwindowsize, int hsearchsize, - int balancetype, - int mwidth ); - int im_match_linear( VipsImage *ref, VipsImage *sec, VipsImage *out, int xr1, int yr1, int xs1, int ys1, int xr2, int yr2, int xs2, int ys2 ); @@ -1149,13 +1131,14 @@ int im__colour_unary( const char *domain, VipsImage **im__insert_base( const char *domain, VipsImage *in1, VipsImage *in2, VipsImage *out ); -int im__find_lroverlap( VipsImage *ref_in, VipsImage *sec_in, VipsImage *out, +/* TODO(kleisauke): These are also defined in pmosaicing.h */ +int vips__find_lroverlap( VipsImage *ref_in, VipsImage *sec_in, VipsImage *out, int bandno_in, int xref, int yref, int xsec, int ysec, int halfcorrelation, int halfarea, int *dx0, int *dy0, double *scale1, double *angle1, double *dx1, double *dy1 ); -int im__find_tboverlap( VipsImage *ref_in, VipsImage *sec_in, VipsImage *out, +int vips__find_tboverlap( VipsImage *ref_in, VipsImage *sec_in, VipsImage *out, int bandno_in, int xref, int yref, int xsec, int ysec, int halfcorrelation, int halfarea, diff --git a/libvips/iofuncs/Makefile.am b/libvips/iofuncs/Makefile.am index 0cbfdf9a..9f825d53 100644 --- a/libvips/iofuncs/Makefile.am +++ b/libvips/iofuncs/Makefile.am @@ -1,6 +1,13 @@ noinst_LTLIBRARIES = libiofuncs.la libiofuncs_la_SOURCES = \ + ginputsource.c \ + connection.c \ + source.c \ + sourcecustom.c \ + target.c \ + targetcustom.c \ + sbuf.c \ dbuf.c \ reorder.c \ vipsmarshal.h \ @@ -9,7 +16,6 @@ libiofuncs_la_SOURCES = \ gate.c \ enumtypes.c \ object.c \ - base64.c \ error.c \ image.c \ vips.c \ @@ -36,9 +42,9 @@ libiofuncs_la_SOURCES = \ system.c \ buffer.c -vipsmarshal.h: +vipsmarshal.h: vipsmarshal.list glib-genmarshal --prefix=vips --header vipsmarshal.list > vipsmarshal.h -vipsmarshal.c: +vipsmarshal.c: vipsmarshal.list echo "#include \"vipsmarshal.h\"" > vipsmarshal.c glib-genmarshal --prefix=vips --body vipsmarshal.list >> vipsmarshal.c diff --git a/libvips/iofuncs/base64.c b/libvips/iofuncs/base64.c deleted file mode 100644 index 3e7b30c4..00000000 --- a/libvips/iofuncs/base64.c +++ /dev/null @@ -1,317 +0,0 @@ -/* base64.c -- Encode/decode integers in base64 format - * Created: Mon Sep 23 16:55:12 1996 by faith@dict.org - * Revised: Sat Mar 30 12:02:36 2002 by faith@dict.org - * Copyright 1996, 2002 Rickard E. Faith (faith@dict.org) - * - * This library is free software; you can redistribute it and/or modify it - * under the terms of the GNU Library General Public License as published - * by the Free Software Foundation; either version 2 of the License, or (at - * your option) any later version. - * - * This library 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 - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301 USA - * - * \section{Base-64 Routines} - * - * \intro These routines use the 64-character subset of International - * Alphabet IA5 discussed in RFC 1421 (printeable encoding) and RFC 1522 - * (base64 MIME). - * - - Value Encoding Value Encoding Value Encoding Value Encoding - 0 A 17 R 34 i 51 z - 1 B 18 S 35 j 52 0 - 2 C 19 T 36 k 53 1 - 3 D 20 U 37 l 54 2 - 4 E 21 V 38 m 55 3 - 5 F 22 W 39 n 56 4 - 6 G 23 X 40 o 57 5 - 7 H 24 Y 41 p 58 6 - 8 I 25 Z 42 q 59 7 - 9 J 26 a 43 r 60 8 - 10 K 27 b 44 s 61 9 - 11 L 28 c 45 t 62 + - 12 M 29 d 46 u 63 / - 13 N 30 e 47 v - 14 O 31 f 48 w (pad) = - 15 P 32 g 49 x - 16 Q 33 h 50 y - * - */ - -/* - - Hacked for VIPS ... does any length object (not just ints), formats - base64 into 70 character lines, output to a malloc'd buffer. - - VIPS uses this to write BLOBs (like ICC profiles, for example) to the - XML that follows an image. - -Modified on: -23/7/07 JC - - oop, needed a slightly larger worst-case buffer in im__b64_encode() - -12/5/09 - - fix signed/unsigned warning - -25/3/11 - - move to vips_ namespace - -31/5/15 - - oops siged/unsigned mess-up meant we were not padding correctly - -20/3/19 - - larger output allocate - - better max size check - - */ - -/* -#define DEBUG - */ - -#ifdef HAVE_CONFIG_H -#include -#endif /*HAVE_CONFIG_H*/ -#include - -#include -#include -#include -#include - -#include -#include - -#ifdef HAVE_BASE64_ENCODE - -/* glib 2.12 adds these. - */ -char * -vips__b64_encode( const unsigned char *data, size_t data_length ) -{ - return( g_base64_encode( data, data_length ) ); -} - -unsigned char * -vips__b64_decode( const char *buffer, size_t *data_length ) -{ - return( g_base64_decode( buffer, data_length ) ); -} - -#else /*!HAVE_BASE64_ENCODE*/ - -static unsigned char b64_list[] = -"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - -#define XX 100 - -static unsigned char b64_index[256] = { - XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, - XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, - XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,62, XX,XX,XX,63, - 52,53,54,55, 56,57,58,59, 60,61,XX,XX, XX,XX,XX,XX, - XX, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14, - 15,16,17,18, 19,20,21,22, 23,24,25,XX, XX,XX,XX,XX, - XX,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40, - 41,42,43,44, 45,46,47,48, 49,50,51,XX, XX,XX,XX,XX, - XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, - XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, - XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, - XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, - XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, - XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, - XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, - XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, -}; - -/* Read (up to) 3 bytes from in. Be careful about byte ordering :-/ we need to - * end up with in[2] in the bottom few bits. - */ -static int -read24( const unsigned char *in, int remaining ) -{ - int bits; - int i; - - bits = 0; - for( i = 0; i < 3; i++ ) { - bits = VIPS_LSHIFT_INT( bits, 8 ); - if( remaining > 0 ) { - bits |= in[i]; - remaining -= 1; - } - } - - return( bits ); -} - -/* Output (up to) 24 bits as four base64 chars. Pad with '=' characters. - */ -static void -encode24( char *p, int bits, int remaining ) -{ - int i; - - for( i = 0; i < 4; i++ ) { - if( remaining <= 0 ) - p[i] = '='; - else { - /* Take the top 6 bits of 24. - */ - p[i] = b64_list[(bits >> 18) & 63]; - bits = VIPS_LSHIFT_INT( bits, 6 ); - remaining -= 6; - } - } -} - -/* Output to a malloc'd buffer, NULL on error. Try to be simple and reliable, - * rather than quick. - */ -char * -vips__b64_encode( const unsigned char *data, size_t data_length ) -{ - /* Worst case: 1.333 chars per byte, plus 10% for extra carriage - * returns and stuff, plus the final \n\0. - */ - const size_t output_data_length = data_length * 44 / 30 + 10; - - char *buffer; - char *p; - int i; - int cursor; - - if( data_length > 10 * 1024 * 1024 ) { - /* We shouldn't really be used for large amounts of data, plus - * we are using int offsets. - * - * A large ICC profile can be 1MB, so allow 10MB. - */ - vips_error( "vips__b64_encode", "%s", _( "too much data" ) ); - return( NULL ); - } - if( !(buffer = vips_malloc( NULL, output_data_length )) ) - return( NULL ); - - p = buffer; - *p++ = '\n'; - cursor = 0; - - for( i = 0; i < data_length; i += 3 ) { - int remaining = data_length - i; - int bits; - - bits = read24( data + i, remaining ); - encode24( p, bits, remaining * 8 ); - p += 4; - cursor += 4; - - if( cursor >= 76 ) { - *p++ = '\n'; - cursor = 0; - } - } - if( cursor > 0 ) - *p++ = '\n'; - *p++ = '\0'; - - g_assert( (size_t) (p - buffer) < output_data_length ); - -#ifdef DEBUG -{ - unsigned int total; - - /* Calculate a very simple checksum for debugging. - */ - for( total = 0, i = 0; i < data_length; i++ ) - total += data[i]; - - printf( "vips__b64_encode: length = %zu, checksum 0x%x\n", - data_length, total & 0xffff ); -} -#endif /*DEBUG*/ - - return( buffer ); -} - -/* Decode base64 back to binary in a malloc'd buffer. NULL on error. - */ -unsigned char * -vips__b64_decode( const char *buffer, size_t *data_length ) -{ - const size_t buffer_length = strlen( buffer ); - - /* Worst case. Add one, since we don't want to return NULL for an empty - * input string, it would look like an error return. - */ - const size_t output_data_length = 1 + buffer_length * 3 / 4; - - unsigned char *data; - unsigned char *p; - unsigned int bits; - int nbits; - int i; - - /* A large ICC profile can be a couple of MB, so 10 should be plenty. - */ - if( output_data_length > 10 * 1024 * 1024 ) { - /* We shouldn't really be used for large amounts of data, plus - * we are using an int for offset. - */ - vips_error( "vips__b64_decode", "%s", _( "too much data" ) ); - return( NULL ); - } - - if( !(data = vips_malloc( NULL, output_data_length )) ) - return( NULL ); - - p = data; - bits = 0; - nbits = 0; - - for( i = 0; i < buffer_length; i++ ) { - unsigned int val; - - if( (val = b64_index[(int) buffer[i]]) != XX ) { - bits = VIPS_LSHIFT_INT( bits, 6 ); - bits |= val; - nbits += 6; - - if( nbits >= 8 ) { - *p++ = (bits >> (nbits - 8)) & 0xff; - nbits -= 8; - } - } - } - - g_assert( (size_t) (p - data) < output_data_length ); - - if( data_length ) - *data_length = p - data; - -#ifdef DEBUG -{ - unsigned int total; - - /* Calculate a very simple checksum for debugging. - */ - for( total = 0, i = 0; i < p - data; i++ ) - total += data[i]; - - printf( "vips__b64_decode: length = %d, checksum 0x%x\n", - p - data, total & 0xffff ); -} -#endif /*DEBUG*/ - - return( data ); -} - -#endif /*HAVE_BASE64_ENCODE*/ diff --git a/libvips/iofuncs/cache.c b/libvips/iofuncs/cache.c index 25951a7e..3ec40966 100644 --- a/libvips/iofuncs/cache.c +++ b/libvips/iofuncs/cache.c @@ -4,6 +4,8 @@ * - try to make it compile on centos5 * 7/7/12 * - add a lock so we can run operations from many threads + * 28/11/19 [MaxKellermann] + * - make invalidate advisory rather than immediate */ /* @@ -76,7 +78,7 @@ gboolean vips__cache_trace = FALSE; * It was 10,000, but this was too high for batch-style applications with * little reuse. */ -static int vips_cache_max = 1000; +static int vips_cache_max = 100; /* How many tracked open files we allow before we start dropping cache. */ @@ -131,6 +133,11 @@ typedef struct _VipsOperationCacheEntry { * we can disconnect when we drop an operation. */ gulong invalidate_id; + + /* Set if someone thinks this cache entry should be dropped. + */ + gboolean invalid; + } VipsOperationCacheEntry; /* Pass in the pspec so we can get the generic type. For example, a @@ -536,6 +543,11 @@ vips_object_unref_arg( VipsObject *object, static void vips_cache_unref( VipsOperation *operation ) { +#ifdef DEBUG + printf( "vips_cache_unref: " ); + vips_object_print_summary( VIPS_OBJECT( operation ) ); +#endif /*DEBUG*/ + (void) vips_argument_map( VIPS_OBJECT( operation ), vips_object_unref_arg, NULL, NULL ); g_object_unref( operation ); @@ -550,7 +562,8 @@ vips_cache_remove( VipsOperation *operation ) g_hash_table_lookup( vips_cache_table, operation ); #ifdef DEBUG - printf( "vips_cache_remove: trimming %p\n", operation ); + printf( "vips_cache_remove: " ); + vips_object_print_summary( VIPS_OBJECT( operation ) ); #endif /*DEBUG*/ g_assert( entry ); @@ -595,7 +608,12 @@ vips_operation_touch( VipsOperation *operation ) g_hash_table_lookup( vips_cache_table, operation ); vips_cache_time += 1; - entry->time = vips_cache_time; + + /* Don't up the time for invalid items -- we want them to fall out of + * cache. + */ + if( !entry->invalid ) + entry->time = vips_cache_time; } /* Ref an operation for the cache. The operation itself, plus all the output @@ -604,32 +622,52 @@ vips_operation_touch( VipsOperation *operation ) static void vips_cache_ref( VipsOperation *operation ) { +#ifdef DEBUG + printf( "vips_cache_ref: " ); + vips_object_print_summary( VIPS_OBJECT( operation ) ); +#endif /*DEBUG*/ + g_object_ref( operation ); (void) vips_argument_map( VIPS_OBJECT( operation ), vips_object_ref_arg, NULL, NULL ); vips_operation_touch( operation ); } +static void +vips_cache_invalidate_cb( VipsOperation *operation, + VipsOperationCacheEntry *entry ) +{ +#ifdef DEBUG + printf( "vips_cache_invalidate_cb: " ); + vips_object_print_summary( VIPS_OBJECT( operation ) ); +#endif /*DEBUG*/ + + entry->invalid = TRUE; +} + static void vips_cache_insert( VipsOperation *operation ) { VipsOperationCacheEntry *entry = g_new( VipsOperationCacheEntry, 1 ); #ifdef VIPS_DEBUG - printf( "vips_cache_insert: adding %p to cache\n", operation ); + printf( "vips_cache_insert: adding to cache" ); + vips_object_print_dump( VIPS_OBJECT( operation ) ); #endif /*VIPS_DEBUG*/ entry->operation = operation; entry->time = 0; entry->invalidate_id = 0; + entry->invalid = FALSE; g_hash_table_insert( vips_cache_table, operation, entry ); vips_cache_ref( operation ); - /* If the operation signals "invalidate", we must drop it. + /* If the operation signals "invalidate", we must tag this cache entry + * for removal. */ entry->invalidate_id = g_signal_connect( operation, "invalidate", - G_CALLBACK( vips_cache_remove ), NULL ); + G_CALLBACK( vips_cache_invalidate_cb ), entry ); } static void * @@ -662,6 +700,10 @@ vips_cache_get_first( void ) void vips_cache_drop_all( void ) { +#ifdef VIPS_DEBUG + printf( "vips_cache_drop_all:\n" ); +#endif /*VIPS_DEBUG*/ + g_mutex_lock( vips_cache_lock ); if( vips_cache_table ) { @@ -726,7 +768,8 @@ vips_cache_trim( void ) vips_tracked_get_mem() > vips_cache_max_mem) && (operation = vips_cache_get_lru()) ) { #ifdef DEBUG - printf( "vips_cache_trim: trimming %p\n", operation ); + printf( "vips_cache_trim: trimming " ); + vips_object_print_summary( VIPS_OBJECT( operation ) ); #endif /*DEBUG*/ vips_cache_remove( operation ); @@ -763,13 +806,22 @@ vips_cache_operation_lookup( VipsOperation *operation ) result = NULL; if( (hit = g_hash_table_lookup( vips_cache_table, operation )) ) { - if( vips__cache_trace ) { - printf( "vips cache*: " ); - vips_object_print_summary( VIPS_OBJECT( operation ) ); + if( hit->invalid ) { + /* There but has been tagged for removal. + */ + vips_cache_remove( hit->operation ); + hit = NULL; } + else { + if( vips__cache_trace ) { + printf( "vips cache*: " ); + vips_object_print_summary( + VIPS_OBJECT( operation ) ); + } - result = hit->operation; - vips_cache_ref( result ); + result = hit->operation; + vips_cache_ref( result ); + } } g_mutex_unlock( vips_cache_lock ); diff --git a/libvips/iofuncs/connection.c b/libvips/iofuncs/connection.c new file mode 100644 index 00000000..5d0653e1 --- /dev/null +++ b/libvips/iofuncs/connection.c @@ -0,0 +1,175 @@ +/* A byte source/sink .. it can be a pipe, file descriptor, memory area, + * socket, node.js stream, etc. + * + * J.Cupitt, 19/6/14 + */ + +/* + + 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 VIPS_DEBUG + */ + +#ifdef HAVE_CONFIG_H +#include +#endif /*HAVE_CONFIG_H*/ +#include + +#include +#include +#ifdef HAVE_UNISTD_H +#include +#endif /*HAVE_UNISTD_H*/ +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/** + * SECTION: connection + * @short_description: a source/sink of bytes, perhaps a network socket + * @stability: Stable + * @see_also: foreign + * @include: vips/vips.h + * @title: VipsConnection + * + * A #VipsConnection is a source or sink of bytes for something like jpeg + * loading, see for example vips_jpegload_source(). + * + * It can be connected to a network socket, for example, or perhaps + * a node.js stream, or to an area of memory. + * + * Subclass to add other input sources. Use #VipsSourceCustom and + * #VipsTargetCustom to make a source or target with action signals for + * ::read, ::write and ::seek. + */ + +/** + * VipsConnection: + * + * A #VipsConnection is a source or sink of bytes for something like jpeg + * loading. It can be connected to a network socket, for example. + */ + +G_DEFINE_ABSTRACT_TYPE( VipsConnection, vips_connection, VIPS_TYPE_OBJECT ); + +static void +vips_connection_finalize( GObject *gobject ) +{ + VipsConnection *connection = (VipsConnection *) gobject; + +#ifdef VIPS_DEBUG + VIPS_DEBUG_MSG( "vips_connection_finalize: " ); + vips_object_print_name( VIPS_OBJECT( gobject ) ); + VIPS_DEBUG_MSG( "\n" ); +#endif /*VIPS_DEBUG*/ + + if( connection->tracked_descriptor >= 0 ) { + VIPS_DEBUG_MSG( " tracked_close()\n" ); + vips_tracked_close( connection->tracked_descriptor ); + connection->tracked_descriptor = -1; + connection->descriptor = -1; + } + + if( connection->close_descriptor >= 0 ) { + VIPS_DEBUG_MSG( " close()\n" ); + close( connection->close_descriptor ); + connection->close_descriptor = -1; + connection->descriptor = -1; + } + + VIPS_FREE( connection->filename ); + + G_OBJECT_CLASS( vips_connection_parent_class )->finalize( gobject ); +} + +static void +vips_connection_class_init( VipsConnectionClass *class ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( class ); + + gobject_class->finalize = vips_connection_finalize; + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + VIPS_ARG_INT( class, "descriptor", 1, + _( "Descriptor" ), + _( "File descriptor for read or write" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsConnection, descriptor ), + -1, 1000000000, 0 ); + + VIPS_ARG_STRING( class, "filename", 2, + _( "Filename" ), + _( "Name of file to open" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsConnection, filename ), + NULL ); + +} + +static void +vips_connection_init( VipsConnection *connection ) +{ + connection->descriptor = -1; + connection->tracked_descriptor = -1; + connection->close_descriptor = -1; +} + +/** + * vips_connection_filename: + * @connection: connection to operate on + * + * Returns: any filename associated with this connection, or NULL. + */ +const char * +vips_connection_filename( VipsConnection *connection ) +{ + return( connection->filename ); +} + +/** + * vips_connection_nick: + * @connection: connection to operate on + * + * Returns: a string describing this connection which could be displayed to a + * user. + */ +const char * +vips_connection_nick( VipsConnection *connection ) +{ + return( connection->filename ? + connection->filename : + VIPS_OBJECT( connection )->nickname ); +} diff --git a/libvips/iofuncs/enumtypes.c b/libvips/iofuncs/enumtypes.c index a79b0bb8..a4adbe30 100644 --- a/libvips/iofuncs/enumtypes.c +++ b/libvips/iofuncs/enumtypes.c @@ -366,6 +366,7 @@ vips_interesting_get_type( void ) {VIPS_INTERESTING_ATTENTION, "VIPS_INTERESTING_ATTENTION", "attention"}, {VIPS_INTERESTING_LOW, "VIPS_INTERESTING_LOW", "low"}, {VIPS_INTERESTING_HIGH, "VIPS_INTERESTING_HIGH", "high"}, + {VIPS_INTERESTING_ALL, "VIPS_INTERESTING_ALL", "all"}, {VIPS_INTERESTING_LAST, "VIPS_INTERESTING_LAST", "last"}, {0, NULL, NULL} }; @@ -499,6 +500,25 @@ vips_saveable_get_type( void ) return( etype ); } GType +vips_foreign_jpeg_subsample_get_type( void ) +{ + static GType etype = 0; + + if( etype == 0 ) { + static const GEnumValue values[] = { + {VIPS_FOREIGN_JPEG_SUBSAMPLE_AUTO, "VIPS_FOREIGN_JPEG_SUBSAMPLE_AUTO", "auto"}, + {VIPS_FOREIGN_JPEG_SUBSAMPLE_ON, "VIPS_FOREIGN_JPEG_SUBSAMPLE_ON", "on"}, + {VIPS_FOREIGN_JPEG_SUBSAMPLE_OFF, "VIPS_FOREIGN_JPEG_SUBSAMPLE_OFF", "off"}, + {VIPS_FOREIGN_JPEG_SUBSAMPLE_LAST, "VIPS_FOREIGN_JPEG_SUBSAMPLE_LAST", "last"}, + {0, NULL, NULL} + }; + + etype = g_enum_register_static( "VipsForeignJpegSubsample", values ); + } + + return( etype ); +} +GType vips_foreign_webp_preset_get_type( void ) { static GType etype = 0; @@ -612,6 +632,7 @@ vips_foreign_dz_layout_get_type( void ) {VIPS_FOREIGN_DZ_LAYOUT_DZ, "VIPS_FOREIGN_DZ_LAYOUT_DZ", "dz"}, {VIPS_FOREIGN_DZ_LAYOUT_ZOOMIFY, "VIPS_FOREIGN_DZ_LAYOUT_ZOOMIFY", "zoomify"}, {VIPS_FOREIGN_DZ_LAYOUT_GOOGLE, "VIPS_FOREIGN_DZ_LAYOUT_GOOGLE", "google"}, + {VIPS_FOREIGN_DZ_LAYOUT_IIIF, "VIPS_FOREIGN_DZ_LAYOUT_IIIF", "iiif"}, {VIPS_FOREIGN_DZ_LAYOUT_LAST, "VIPS_FOREIGN_DZ_LAYOUT_LAST", "last"}, {0, NULL, NULL} }; @@ -902,6 +923,9 @@ vips_region_shrink_get_type( void ) {VIPS_REGION_SHRINK_MEAN, "VIPS_REGION_SHRINK_MEAN", "mean"}, {VIPS_REGION_SHRINK_MEDIAN, "VIPS_REGION_SHRINK_MEDIAN", "median"}, {VIPS_REGION_SHRINK_MODE, "VIPS_REGION_SHRINK_MODE", "mode"}, + {VIPS_REGION_SHRINK_MAX, "VIPS_REGION_SHRINK_MAX", "max"}, + {VIPS_REGION_SHRINK_MIN, "VIPS_REGION_SHRINK_MIN", "min"}, + {VIPS_REGION_SHRINK_NEAREST, "VIPS_REGION_SHRINK_NEAREST", "nearest"}, {VIPS_REGION_SHRINK_LAST, "VIPS_REGION_SHRINK_LAST", "last"}, {0, NULL, NULL} }; diff --git a/libvips/iofuncs/ginputsource.c b/libvips/iofuncs/ginputsource.c new file mode 100644 index 00000000..dedc20f0 --- /dev/null +++ b/libvips/iofuncs/ginputsource.c @@ -0,0 +1,301 @@ +/* A GInputStream that links to a VipsSource under the hood. + * + * 10/11/19 kleisauke + */ + +/* + + 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 +#include +#include + +#include +#include +#include + +#include + +static void vips_g_input_stream_seekable_iface_init( GSeekableIface *iface ); + +G_DEFINE_TYPE_WITH_CODE( VipsGInputStream, vips_g_input_stream, + G_TYPE_INPUT_STREAM, G_IMPLEMENT_INTERFACE( G_TYPE_SEEKABLE, + vips_g_input_stream_seekable_iface_init ) ) + +enum { + PROP_0, + PROP_STREAM +}; + +static void +vips_g_input_stream_get_property( GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec ) +{ + VipsGInputStream *gstream = VIPS_G_INPUT_STREAM( object ); + + switch( prop_id ) { + case PROP_STREAM: + g_value_set_object( value, gstream->source ); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID( object, prop_id, pspec ); + } +} + +static void +vips_g_input_stream_set_property( GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec ) +{ + VipsGInputStream *gstream = VIPS_G_INPUT_STREAM( object ); + + switch( prop_id ) { + case PROP_STREAM: + gstream->source = g_value_dup_object( value ); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID( object, prop_id, pspec ); + } +} + +static void +vips_g_input_stream_finalize( GObject *object ) +{ + VipsGInputStream *gstream = VIPS_G_INPUT_STREAM( object ); + + VIPS_FREEF( g_object_unref, gstream->source ); + + G_OBJECT_CLASS( vips_g_input_stream_parent_class )->finalize( object ); +} + +static goffset +vips_g_input_stream_tell( GSeekable *seekable ) +{ + VipsSource *source = VIPS_G_INPUT_STREAM( seekable )->source; + + goffset pos; + + VIPS_DEBUG_MSG( "vips_g_input_stream_tell:\n" ); + + pos = vips_source_seek( source, 0, SEEK_CUR ); + if( pos == -1 ) + return( 0 ); + + return( pos ); +} + +static gboolean +vips_g_input_stream_can_seek( GSeekable *seekable ) +{ + VipsSource *source = VIPS_G_INPUT_STREAM( seekable )->source; + + VIPS_DEBUG_MSG( "vips_g_input_stream_can_seek: %d\n", + !source->is_pipe ); + + return( !source->is_pipe ); +} + +static int +seek_type_to_lseek( GSeekType type ) +{ + switch( type ) { + default: + case G_SEEK_CUR: + return( SEEK_CUR ); + case G_SEEK_SET: + return( SEEK_SET ); + case G_SEEK_END: + return( SEEK_END ); + } +} + +static gboolean +vips_g_input_stream_seek( GSeekable *seekable, goffset offset, + GSeekType type, GCancellable *cancellable, GError **error ) +{ + VipsSource *source = VIPS_G_INPUT_STREAM( seekable )->source; + + VIPS_DEBUG_MSG( "vips_g_input_stream_seek: offset = %" G_GINT64_FORMAT + ", type = %d\n", offset, type ); + + if( vips_source_seek( source, offset, + seek_type_to_lseek( type ) ) == -1 ) { + g_set_error( error, G_IO_ERROR, + G_IO_ERROR_FAILED, + _( "Error while seeking: %s" ), + vips_error_buffer() ); + return( FALSE ); + } + + + return( TRUE ); +} + +static gboolean +vips_g_input_stream_can_truncate( GSeekable *seekable ) +{ + return( FALSE ); +} + +static gboolean +vips_g_input_stream_truncate( GSeekable *seekable, goffset offset, + GCancellable *cancellable, GError **error ) +{ + g_set_error_literal( error, + G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + _( "Cannot truncate VipsGInputStream" ) ); + + return( FALSE ); +} + +static gssize +vips_g_input_stream_read( GInputStream *stream, void *buffer, gsize count, + GCancellable *cancellable, GError **error ) +{ + VipsSource *source; + gssize res; + + source = VIPS_G_INPUT_STREAM( stream )->source; + + VIPS_DEBUG_MSG( "vips_g_input_stream_read: count: %zd\n", count ); + + if( g_cancellable_set_error_if_cancelled( cancellable, error ) ) + return( -1 ); + + if( (res = vips_source_read( source, buffer, count )) == -1 ) + g_set_error( error, G_IO_ERROR, + G_IO_ERROR_FAILED, + _( "Error while reading: %s" ), + vips_error_buffer() ); + + return( res ); +} + +static gssize +vips_g_input_stream_skip( GInputStream *stream, gsize count, + GCancellable *cancellable, GError **error ) +{ + VipsSource *source; + gssize position; + + source = VIPS_G_INPUT_STREAM( stream )->source; + + VIPS_DEBUG_MSG( "vips_g_input_stream_skip: count: %zd\n", count ); + + if( g_cancellable_set_error_if_cancelled( cancellable, error ) ) + return( -1 ); + + position = vips_source_seek( source, count, SEEK_CUR ); + if( position == -1 ) { + g_set_error( error, G_IO_ERROR, + G_IO_ERROR_FAILED, + _( "Error while seeking: %s" ), + vips_error_buffer() ); + return( -1 ); + } + + return( position ); +} + +static gboolean +vips_g_input_stream_close( GInputStream *stream, + GCancellable *cancellable, GError **error ) +{ + VipsGInputStream *gstream = VIPS_G_INPUT_STREAM( stream ); + + vips_source_minimise( gstream->source ); + + return( TRUE ); +} + +static void +vips_g_input_stream_seekable_iface_init( GSeekableIface *iface ) +{ + iface->tell = vips_g_input_stream_tell; + iface->can_seek = vips_g_input_stream_can_seek; + iface->seek = vips_g_input_stream_seek; + iface->can_truncate = vips_g_input_stream_can_truncate; + iface->truncate_fn = vips_g_input_stream_truncate; +} + +static void +vips_g_input_stream_class_init( VipsGInputStreamClass *class ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( class ); + GInputStreamClass *istream_class = G_INPUT_STREAM_CLASS( class ); + + gobject_class->finalize = vips_g_input_stream_finalize; + gobject_class->get_property = vips_g_input_stream_get_property; + gobject_class->set_property = vips_g_input_stream_set_property; + + istream_class->read_fn = vips_g_input_stream_read; + istream_class->skip = vips_g_input_stream_skip; + istream_class->close_fn = vips_g_input_stream_close; + + g_object_class_install_property( gobject_class, PROP_STREAM, + g_param_spec_object( "input", + _( "Input" ), + _( "Stream to wrap" ), + VIPS_TYPE_SOURCE, G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS ) ); + +} + +static void +vips_g_input_stream_init( VipsGInputStream *gstream ) +{ +} + +/** + * vips_g_input_stream_new_from_source: + * @source: vips source to wrap + * + * Create a new #GInputStream wrapping a #VipsSource. This is useful for + * loaders like SVG and PDF which support GInput methods. + * + * Returns: a new #GInputStream + */ +GInputStream * +vips_g_input_stream_new_from_source( VipsSource *source ) +{ + return( g_object_new( VIPS_TYPE_G_INPUT_STREAM, + "input", source, + NULL ) ); +} diff --git a/libvips/iofuncs/header.c b/libvips/iofuncs/header.c index 79bf8b1a..4109b5d4 100644 --- a/libvips/iofuncs/header.c +++ b/libvips/iofuncs/header.c @@ -36,6 +36,8 @@ * - add vips_image_get_n_pages() * 20/6/19 * - add vips_image_get/set_array_int() + * 31/1/19 + * - lock for metadata changes */ /* @@ -130,6 +132,11 @@ * these types, it can be copied between images efficiently. */ +/* Use in various small places where we need a mutex and it's not worth + * making a private one. + */ +static GMutex *vips__meta_lock = NULL; + /* We have to keep the gtype as a string, since we statically init this. */ typedef struct _HeaderField { @@ -443,12 +450,12 @@ vips_image_guess_format( const VipsImage *image ) break; case VIPS_INTERPRETATION_sRGB: + case VIPS_INTERPRETATION_RGB: format = VIPS_FORMAT_UCHAR; break; case VIPS_INTERPRETATION_XYZ: case VIPS_INTERPRETATION_LAB: - case VIPS_INTERPRETATION_RGB: case VIPS_INTERPRETATION_CMC: case VIPS_INTERPRETATION_LCH: case VIPS_INTERPRETATION_HSV: @@ -519,8 +526,7 @@ vips_image_get_interpretation( const VipsImage *image ) return( image->Type ); } -/* Try to pick a sane value for interpretation, assuming Type has been set - * incorrectly. +/* Try to guess a sane value for interpretation. */ static VipsInterpretation vips_image_default_interpretation( const VipsImage *image ) @@ -529,7 +535,7 @@ vips_image_default_interpretation( const VipsImage *image ) case VIPS_CODING_LABQ: return( VIPS_INTERPRETATION_LABQ ); case VIPS_CODING_RAD: - return( VIPS_INTERPRETATION_RGB ); + return( VIPS_INTERPRETATION_sRGB ); default: break; } @@ -578,7 +584,7 @@ vips_image_guess_interpretation( const VipsImage *image ) break; case VIPS_CODING_RAD: - if( image->Type != VIPS_INTERPRETATION_RGB ) + if( image->Type != VIPS_INTERPRETATION_sRGB ) sane = FALSE; break; @@ -717,7 +723,8 @@ vips_image_get_yoffset( const VipsImage *image ) * vips_image_get_filename: (method) * @image: image to get from * - * Returns: the name of the file the image was loaded from. + * Returns: the name of the file the image was loaded from, or NULL if there + * is no filename. */ const char * vips_image_get_filename( const VipsImage *image ) @@ -811,7 +818,7 @@ vips_image_get_page_height( VipsImage *image ) * vips_image_get_n_pages: (method) * @image: image to get from * - * Fetch and sanity-check VIPS_META_N_PAGES. Default to 1 if not present or + * Fetch and sanity-check #VIPS_META_N_PAGES. Default to 1 if not present or * crazy. * * This is the number of pages in the image file, not the number of pages that @@ -833,6 +840,70 @@ vips_image_get_n_pages( VipsImage *image ) return( 1 ); } +/** + * vips_image_get_n_subifds: (method) + * @image: image to get from + * + * Fetch and sanity-check #VIPS_META_N_SUBIFDS. Default to 0 if not present or + * crazy. + * + * Returns: the number of subifds in the image file + */ +int +vips_image_get_n_subifds( VipsImage *image ) +{ + int n_subifds; + + if( vips_image_get_typeof( image, VIPS_META_N_SUBIFDS ) && + !vips_image_get_int( image, VIPS_META_N_SUBIFDS, &n_subifds ) && + n_subifds > 1 && + n_subifds < 1000 ) + return( n_subifds ); + + return( 0 ); +} + +/** + * vips_image_get_orientation: (method) + * @image: image to get from + * + * Fetch and sanity-check #VIPS_META_ORIENTATION. Default to 1 (no rotate, + * no flip) if not present or crazy. + * + * Returns: the image orientation. + */ +int +vips_image_get_orientation( VipsImage *image ) +{ + int orientation; + + if( vips_image_get_typeof( image, VIPS_META_ORIENTATION ) && + !vips_image_get_int( image, VIPS_META_ORIENTATION, + &orientation ) && + orientation > 0 && + orientation < 9 ) + return( orientation ); + + return( 1 ); +} + +/** + * vips_image_get_orientation_swap: (method) + * @image: image to get from + * + * Return %TRUE if applying the orientation would swap width and height. + * + * Returns: if width/height will swap + */ +gboolean +vips_image_get_orientation_swap( VipsImage *image ) +{ + int orientation = vips_image_get_orientation( image ); + + return( orientation >= 5 && + orientation <= 8 ); +} + /** * vips_image_get_data: (method) * @image: image to get data for @@ -927,11 +998,14 @@ static int meta_cp( VipsImage *dst, const VipsImage *src ) { if( src->meta ) { - /* Loop, copying fields. + /* We lock with vips_image_set() to stop races in highly- + * threaded applications. */ + g_mutex_lock( vips__meta_lock ); meta_init( dst ); vips_slist_map2( src->meta_traverse, (VipsSListMap2Fn) meta_cp_field, dst, NULL ); + g_mutex_unlock( vips__meta_lock ); } return( 0 ); @@ -1019,8 +1093,17 @@ vips_image_set( VipsImage *image, const char *name, GValue *value ) g_assert( name ); g_assert( value ); + /* We lock between modifying metadata and copying metadata between + * images, see meta_cp(). + * + * This prevents modification of metadata by one thread racing with + * metadata copy on another -- this can lead to crashes in + * highly-threaded applications. + */ + g_mutex_lock( vips__meta_lock ); meta_init( image ); (void) meta_new( image, name, value ); + g_mutex_unlock( vips__meta_lock ); /* If we're setting an EXIF data block, we need to automatically expand * out all the tags. This will set things like xres/yres too. @@ -1035,6 +1118,7 @@ vips_image_set( VipsImage *image, const char *name, GValue *value ) #ifdef DEBUG meta_sanity( image ); #endif /*DEBUG*/ + } /* Unforunately gvalue seems to have no way of doing this. Just handle the vips @@ -1212,18 +1296,32 @@ vips_image_get_typeof( const VipsImage *image, const char *name ) gboolean vips_image_remove( VipsImage *image, const char *name ) { - if( image->meta && - g_hash_table_remove( image->meta, name ) ) - return( TRUE ); + gboolean result; - return( FALSE ); + result = FALSE; + + if( image->meta ) { + /* We lock between modifying metadata and copying metadata + * between images, see meta_cp(). + * + * This prevents modification of metadata by one thread + * racing with metadata copy on another -- this can lead to + * crashes in highly-threaded applications. + */ + g_mutex_lock( vips__meta_lock ); + result = g_hash_table_remove( image->meta, name ); + g_mutex_unlock( vips__meta_lock ); + } + + return( result ); } /* Deprecated header fields we hide from _map. */ static const char *vips_image_header_deprecated[] = { "ipct-data", - "gif-delay" + "gif-delay", + "gif-loop" }; static void * @@ -1981,3 +2079,12 @@ vips_image_get_history( VipsImage *image ) return( image->Hist ? image->Hist : "" ); } + +/* Called during vips_init(). + */ +void +vips__meta_init( void ) +{ + if( !vips__meta_lock ) + vips__meta_lock = vips_g_mutex_new(); +} diff --git a/libvips/iofuncs/image.c b/libvips/iofuncs/image.c index ce7bf1fc..c5d51b23 100644 --- a/libvips/iofuncs/image.c +++ b/libvips/iofuncs/image.c @@ -18,6 +18,8 @@ * - better rules for hasalpha * 9/10/18 * - fix up vips_image_dump(), it was still using ints not enums + * 10/12/19 + * - add vips_image_new_from_source() / vips_image_write_to_target() */ /* @@ -228,9 +230,8 @@ * three-band float image of type #VIPS_INTERPRETATION_LAB should have its * pixels interpreted as coordinates in CIE Lab space. * - * These values are set by operations as hints to user-interfaces built on top - * of VIPS to help them show images to the user in a meaningful way. - * Operations do not use these values to decide their action. + * RGB and sRGB are treated in the same way. Use the colourspace functions if + * you want some other behaviour. * * The gaps in numbering are historical and must be maintained. Allocate * new numbers from the end. @@ -670,9 +671,6 @@ vips_image_sanity( VipsObject *object, VipsBuf *buf ) { VipsImage *image = VIPS_IMAGE( object ); - if( !image->filename ) - vips_buf_appends( buf, "NULL filename\n" ); - /* All 0 means im has been inited but never used. */ if( image->Xsize != 0 || @@ -803,8 +801,12 @@ static void vips_image_add_progress( VipsImage *image ) { if( vips__progress || +#if VIPS_ENABLE_DEPRECATED g_getenv( "VIPS_PROGRESS" ) || g_getenv( "IM_PROGRESS" ) ) { +#else + g_getenv( "VIPS_PROGRESS" ) ) { +#endif /* Keep the %complete we displayed last time here. */ @@ -960,7 +962,7 @@ vips_image_build( VipsObject *object ) if( image->Bands == 1 ) image->Type = VIPS_INTERPRETATION_B_W; else if( image->Bands == 3 ) - image->Type = VIPS_INTERPRETATION_RGB; + image->Type = VIPS_INTERPRETATION_sRGB; else image->Type = VIPS_INTERPRETATION_MULTIBAND; @@ -993,7 +995,7 @@ vips_image_build( VipsObject *object ) if( image->Bands == 1 ) image->Type = VIPS_INTERPRETATION_B_W; else if( image->Bands == 3 ) - image->Type = VIPS_INTERPRETATION_RGB; + image->Type = VIPS_INTERPRETATION_sRGB; else image->Type = VIPS_INTERPRETATION_MULTIBAND; @@ -1911,18 +1913,19 @@ vips_image_new_from_file( const char *name, ... ) char option_string[VIPS_PATH_MAX]; const char *operation_name; va_list ap; - VipsImage *out; int result; + VipsImage *out; vips_check_init(); vips__filename_split8( name, filename, option_string ); + if( !(operation_name = vips_foreign_find_load( filename )) ) return( NULL ); va_start( ap, name ); - result = vips_call_split_option_string( operation_name, option_string, - ap, filename, &out ); + result = vips_call_split_option_string( operation_name, + option_string, ap, filename, &out ); va_end( ap ); if( result ) @@ -2047,11 +2050,7 @@ vips_image_new_from_memory( const void *data, size_t size, return( NULL ); } - /* Allow len == 0, meaning don't check. Used for im_image() - * compatibility. - */ - if( size > 0 && - size < VIPS_IMAGE_SIZEOF_IMAGE( image ) ) { + if( size < VIPS_IMAGE_SIZEOF_IMAGE( image ) ) { vips_error( "VipsImage", _( "memory area too small --- " "should be %" G_GINT64_FORMAT " bytes, " @@ -2140,14 +2139,15 @@ vips_image_new_from_buffer( const void *buf, size_t len, const char *option_string, ... ) { const char *operation_name; - VipsBlob *blob; va_list ap; int result; VipsImage *out; + VipsBlob *blob; vips_check_init(); - if( !(operation_name = vips_foreign_find_load_buffer( buf, len )) ) + if( !(operation_name = + vips_foreign_find_load_buffer( buf, len )) ) return( NULL ); /* We don't take a copy of the data or free it. @@ -2155,7 +2155,7 @@ vips_image_new_from_buffer( const void *buf, size_t len, blob = vips_blob_new( NULL, buf, len ); va_start( ap, option_string ); - result = vips_call_split_option_string( operation_name, + result = vips_call_split_option_string( operation_name, option_string, ap, blob, &out ); va_end( ap ); @@ -2167,6 +2167,94 @@ vips_image_new_from_buffer( const void *buf, size_t len, return( out ); } +/** + * vips_image_new_from_source: (constructor) + * @source: (transfer none): source to fetch image from + * @option_string: set of extra options as a string + * @...: %NULL-terminated list of optional named arguments + * + * Loads an image from the formatted source @input, + * loader recommended by vips_foreign_find_load_source(). + * + * Load options may be given in @option_string as "[name=value,...]" or given as + * a NULL-terminated list of name-value pairs at the end of the arguments. + * Options given in the function call override options given in the string. + * + * See also: vips_image_write_to_target(). + * + * Returns: (transfer full): the new #VipsImage, or %NULL on error. + */ +VipsImage * +vips_image_new_from_source( VipsSource *source, + const char *option_string, ... ) +{ + const char *filename = + vips_connection_filename( VIPS_CONNECTION( source ) ); + + const char *operation_name; + va_list ap; + int result; + VipsImage *out; + + vips_check_init(); + + vips_error_freeze(); + operation_name = vips_foreign_find_load_source( source ); + vips_error_thaw(); + + if( operation_name ) { + va_start( ap, option_string ); + result = vips_call_split_option_string( operation_name, + option_string, ap, source, &out ); + va_end( ap ); + } + else if( filename ) { + /* Try with the old file-based loaders. + */ + if( !(operation_name = vips_foreign_find_load( filename )) ) + return( NULL ); + + va_start( ap, option_string ); + result = vips_call_split_option_string( operation_name, + option_string, ap, filename, &out ); + va_end( ap ); + } + else if( vips_source_is_mappable( source ) ) { + /* Try with the old buffer-based loaders. + */ + VipsBlob *blob; + const void *buf; + size_t len; + + if( !(blob = vips_source_map_blob( source )) ) + return( NULL ); + + buf = vips_blob_get( blob, &len ); + if( !(operation_name = + vips_foreign_find_load_buffer( buf, len )) ) { + vips_area_unref( VIPS_AREA( blob ) ); + return( NULL ); + } + + va_start( ap, option_string ); + result = vips_call_split_option_string( operation_name, + option_string, ap, blob, &out ); + va_end( ap ); + + vips_area_unref( VIPS_AREA( blob ) ); + } + else { + vips_error( "VipsImage", + "%s", _( "unable to load source" ) ); + result = -1; + } + + if( result ) + return( NULL ); + + return( out ); +} + /** * vips_image_new_matrix: (constructor) * @width: image width @@ -2429,8 +2517,12 @@ vips_get_disc_threshold( void ) */ threshold = 100 * 1024 * 1024; +#if VIPS_ENABLE_DEPRECATED if( (env = g_getenv( "VIPS_DISC_THRESHOLD" )) || (env = g_getenv( "IM_DISC_THRESHOLD" )) ) +#else + if( (env = g_getenv( "VIPS_DISC_THRESHOLD" )) ) +#endif threshold = vips__parse_size( env ); if( vips__disc_threshold ) @@ -2586,14 +2678,38 @@ vips_image_write_to_file( VipsImage *image, const char *name, ... ) va_list ap; int result; + /* Save with the new target API if we can. Fall back to the older + * mechanism in case the loader we need has not been converted yet. + * + * We need to hide any errors from this first phase. + */ vips__filename_split8( name, filename, option_string ); - if( !(operation_name = vips_foreign_find_save( filename )) ) - return( -1 ); - va_start( ap, name ); - result = vips_call_split_option_string( operation_name, option_string, - ap, image, filename ); - va_end( ap ); + vips_error_freeze(); + operation_name = vips_foreign_find_save_target( filename ); + vips_error_thaw(); + + if( operation_name ) { + VipsTarget *target; + + if( !(target = vips_target_new_to_file( filename )) ) + return( -1 ); + + va_start( ap, name ); + result = vips_call_split_option_string( operation_name, + option_string, ap, image, target ); + va_end( ap ); + + VIPS_UNREF( target ); + } + else if( (operation_name = vips_foreign_find_save( filename )) ) { + va_start( ap, name ); + result = vips_call_split_option_string( operation_name, + option_string, ap, image, filename ); + va_end( ap ); + } + else + return( -1 ); return( result ); } @@ -2634,20 +2750,44 @@ vips_image_write_to_buffer( VipsImage *in, int result; vips__filename_split8( suffix, filename, option_string ); - if( !(operation_name = vips_foreign_find_save_buffer( filename )) ) - return( -1 ); - va_start( ap, size ); - result = vips_call_split_option_string( operation_name, option_string, - ap, in, &blob ); - va_end( ap ); + if( (operation_name = vips_foreign_find_save_target( filename )) ) { + VipsTarget *target; - if( result ) + if( !(target = vips_target_new_to_memory()) ) + return( -1 ); + + va_start( ap, size ); + result = vips_call_split_option_string( operation_name, + option_string, ap, in, target ); + va_end( ap ); + + if( result ) { + VIPS_UNREF( target ); + return( -1 ); + } + + g_object_get( target, "blob", &blob, NULL ); + VIPS_UNREF( target ); + } + else if( (operation_name = + vips_foreign_find_save_buffer( filename )) ) { + + va_start( ap, size ); + result = vips_call_split_option_string( operation_name, + option_string, ap, in, &blob ); + va_end( ap ); + + if( result ) + return( -1 ); + } + else return( -1 ); *buf = NULL; if( size ) *size = 0; + if( blob ) { if( buf ) { *buf = VIPS_AREA( blob )->data; @@ -2662,6 +2802,51 @@ vips_image_write_to_buffer( VipsImage *in, return( 0 ); } +/** + * vips_image_write_to_target: (method) + * @in: image to write + * @suffix: format to write + * @target: target to write to + * @...: %NULL-terminated list of optional named arguments + * + * Writes @in to @output in format @suffix. + * + * Save options may be appended to @suffix as "[name=value,...]" or given as + * a NULL-terminated list of name-value pairs at the end of the arguments. + * Options given in the function call override options given in the filename. + * + * You can call the various save operations directly if you wish, see + * vips_jpegsave_target(), for example. + * + * See also: vips_image_write_to_file(). + * + * Returns: 0 on success, -1 on error + */ +int +vips_image_write_to_target( VipsImage *in, + const char *suffix, VipsTarget *target, ... ) +{ + char filename[VIPS_PATH_MAX]; + char option_string[VIPS_PATH_MAX]; + const char *operation_name; + va_list ap; + int result; + + vips__filename_split8( suffix, filename, option_string ); + if( !(operation_name = vips_foreign_find_save_target( filename )) ) + return( -1 ); + + va_start( ap, target ); + result = vips_call_split_option_string( operation_name, option_string, + ap, in, target ); + va_end( ap ); + + if( result ) + return( -1 ); + + return( 0 ); +} + /** * vips_image_write_to_memory: (method) * @in: image to write @@ -2837,7 +3022,8 @@ vips_image_isMSBfirst( VipsImage *image ) * * Return %TRUE if @image represents a file on disc in some way. */ -gboolean vips_image_isfile( VipsImage *image ) +gboolean +vips_image_isfile( VipsImage *image ) { switch( image->dtype ) { case VIPS_IMAGE_MMAPIN: @@ -2878,7 +3064,7 @@ vips_image_ispartial( VipsImage *image ) * @image: image to check * * Look at an image's interpretation and see if it has extra alpha bands. For - * example, a 4-band #VIPS_INTERPRETATION_RGB would, but a six-band + * example, a 4-band #VIPS_INTERPRETATION_sRGB would, but a six-band * #VIPS_INTERPRETATION_MULTIBAND would not. * * Return %TRUE if @image has an alpha channel. @@ -3357,6 +3543,9 @@ vips__image_wio_output( VipsImage *image ) * After calling this function you can both read and write the image with * VIPS_IMAGE_ADDR(). * + * This method is called for you by the base class of the draw operations, + * there's no need to call it yourself. + * * Since this function modifies @image, it is not thread-safe. Only call it on * images which you are sure have not been shared with another thread. * All in-place operations are inherently not thread-safe, so you need to take diff --git a/libvips/iofuncs/init.c b/libvips/iofuncs/init.c index 57dd8aee..1ea409f6 100644 --- a/libvips/iofuncs/init.c +++ b/libvips/iofuncs/init.c @@ -98,7 +98,10 @@ #include #include #include + +#if VIPS_ENABLE_DEPRECATED #include +#endif /* abort() on the first warning or error. */ @@ -127,6 +130,8 @@ int vips__leak = 0; GQuark vips__image_pixels_quark = 0; #endif /*DEBUG_LEAK*/ +static gint64 vips_pipe_read_limit = 1024 * 1024 * 1024; + /** * vips_get_argv0: * @@ -310,6 +315,32 @@ set_stacksize( guint64 size ) #endif /*HAVE_PTHREAD_DEFAULT_NP*/ } +static void +vips_verbose( void ) +{ + /* Older glibs were showing G_LOG_LEVEL_{INFO,DEBUG} messages + * by default + */ +#if GLIB_CHECK_VERSION ( 2, 31, 0 ) + const char *old; + + old = g_getenv( "G_MESSAGES_DEBUG" ); + + if( !old ) { + g_setenv( "G_MESSAGES_DEBUG", G_LOG_DOMAIN, TRUE ); + } + else if( !g_str_equal( old, "all" ) && + !g_strrstr( old, G_LOG_DOMAIN ) ) { + char *new; + + new = g_strconcat( old, " ", G_LOG_DOMAIN, NULL ); + g_setenv( "G_MESSAGES_DEBUG", new, TRUE ); + + g_free( new ); + } +#endif /*GLIB_CHECK_VERSION( 2, 31, 0 )*/ +} + /** * vips_init: * @argv0: name of application @@ -330,6 +361,11 @@ vips_init( const char *argv0 ) extern GType write_thread_state_get_type( void ); extern GType sink_memory_thread_state_get_type( void ); extern GType render_thread_state_get_type( void ); + extern GType vips_source_get_type( void ); + extern GType vips_source_custom_get_type( void ); + extern GType vips_target_get_type( void ); + extern GType vips_target_custom_get_type( void ); + extern GType vips_g_input_stream_get_type( void ); static gboolean started = FALSE; static gboolean done = FALSE; @@ -377,13 +413,17 @@ vips_init( const char *argv0 ) #endif /*HAVE_THREAD_NEW*/ vips__threadpool_init(); + vips__sink_screen_init(); vips__buffer_init(); + vips__meta_init(); /* This does an unsynchronised static hash table init on first call -- * we have to make sure we do this single-threaded. See: * https://github.com/openslide/openslide/issues/161 */ +#if !GLIB_CHECK_VERSION( 2, 48, 1 ) (void) g_get_language_names(); +#endif if( !vips__global_lock ) vips__global_lock = vips_g_mutex_new(); @@ -423,19 +463,24 @@ vips_init( const char *argv0 ) g_free( locale ); bind_textdomain_codeset( GETTEXT_PACKAGE, "UTF-8" ); - /* Deprecated, this is just for compat. - */ +#if VIPS_ENABLE_DEPRECATED if( g_getenv( "VIPS_INFO" ) || g_getenv( "IM_INFO" ) ) - vips_info_set( TRUE ); - +#else + if( g_getenv( "VIPS_INFO" ) ) +#endif + vips_verbose(); if( g_getenv( "VIPS_PROFILE" ) ) vips_profile_set( TRUE ); - - /* Default various settings from env. - */ + if( g_getenv( "VIPS_LEAK" ) ) + vips_leak_set( TRUE ); if( g_getenv( "VIPS_TRACE" ) ) vips_cache_set_trace( TRUE ); + if( g_getenv( "VIPS_PIPE_READ_LIMIT" ) ) + vips_pipe_read_limit = + g_ascii_strtoll( g_getenv( "VIPS_PIPE_READ_LIMIT" ), + NULL, 10 ); + vips_pipe_read_limit_set( vips_pipe_read_limit ); /* Register base vips types. */ @@ -444,9 +489,16 @@ vips_init( const char *argv0 ) (void) write_thread_state_get_type(); (void) sink_memory_thread_state_get_type(); (void) render_thread_state_get_type(); + (void) vips_source_get_type(); + (void) vips_source_custom_get_type(); + (void) vips_target_get_type(); + (void) vips_target_custom_get_type(); vips__meta_init_types(); vips__interpolate_init(); + +#if VIPS_ENABLE_DEPRECATED im__format_init(); +#endif /* Start up operator cache. */ @@ -471,6 +523,7 @@ vips_init( const char *argv0 ) vips_morphology_operation_init(); vips_draw_operation_init(); vips_mosaicing_operation_init(); + vips_g_input_stream_get_type(); /* Load any vips8 plugins from the vips libdir. Keep going, even if * some plugins fail to load. @@ -478,6 +531,7 @@ vips_init( const char *argv0 ) (void) vips_load_plugins( "%s/vips-plugins-%d.%d", libdir, VIPS_MAJOR_VERSION, VIPS_MINOR_VERSION ); +#if VIPS_ENABLE_DEPRECATED /* Load up any vips7 plugins in the vips libdir. We don't error on * failure, it's too annoying to have VIPS refuse to start because of * a broken plugin. @@ -495,6 +549,7 @@ vips_init( const char *argv0 ) g_warning( "%s", vips_error_buffer() ); vips_error_clear(); } +#endif /* Get the run-time compiler going. */ @@ -527,9 +582,13 @@ vips_init( const char *argv0 ) * set up if you are using libvips from something like Ruby. Allow this * env var hack as a workaround. */ +#if VIPS_ENABLE_DEPRECATED if( g_getenv( "VIPS_WARNING" ) || g_getenv( "IM_WARNING" ) ) - g_log_set_handler( "VIPS", G_LOG_LEVEL_WARNING, +#else + if( g_getenv( "VIPS_WARNING" ) ) +#endif + g_log_set_handler( G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, empty_log_handler, NULL ); /* Set a minimum stacksize, if we can. @@ -634,7 +693,9 @@ vips_shutdown( void ) vips_cache_drop_all(); +#if VIPS_ENABLE_DEPRECATED im_close_plugins(); +#endif /* Mustn't run this more than once. Don't use the VIPS_GATE macro, * since we don't for gate start. @@ -692,7 +753,7 @@ static gboolean vips_lib_info_cb( const gchar *option_name, const gchar *value, gpointer data, GError **error ) { - vips_info_set( TRUE ); + vips_verbose(); return( TRUE ); } @@ -724,6 +785,15 @@ vips_lib_version_cb( const gchar *option_name, const gchar *value, exit( 0 ); } +static gboolean +vips_lib_config_cb( const gchar *option_name, const gchar *value, + gpointer data, GError **error ) +{ + printf( "%s\n", VIPS_CONFIG ); + vips_shutdown(); + exit( 0 ); +} + static gboolean vips_cache_max_cb( const gchar *option_name, const gchar *value, gpointer data, GError **error ) @@ -806,6 +876,12 @@ static GOptionEntry option_entries[] = { { "vips-version", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, (gpointer) &vips_lib_version_cb, N_( "print libvips version" ), NULL }, + { "vips-config", 0, G_OPTION_FLAG_NO_ARG, + G_OPTION_ARG_CALLBACK, (gpointer) &vips_lib_config_cb, + N_( "print libvips config" ), NULL }, + { "vips-pipe-read-limit", 0, 0, + G_OPTION_ARG_INT64, (gpointer) &vips_pipe_read_limit, + N_( "read at most this many bytes from a pipe" ), NULL }, { NULL } }; @@ -871,7 +947,7 @@ extract_prefix( const char *dir, const char *name ) for( i = 0; i < (int) strlen( vname ); i++ ) if( vips_isprefix( G_DIR_SEPARATOR_S "." G_DIR_SEPARATOR_S, vname + i ) ) - memcpy( vname + i, vname + i + 2, + memmove( vname + i, vname + i + 2, strlen( vname + i + 2 ) + 1 ); if( vips_ispostfix( vname, G_DIR_SEPARATOR_S "." ) ) vname[strlen( vname ) - 2] = '\0'; @@ -998,7 +1074,8 @@ guess_prefix( const char *argv0, const char *name ) /* Try to guess from cwd. Only if this is a relative path, though. */ - if( !g_path_is_absolute( argv0 ) ) { + if( argv0 && + !g_path_is_absolute( argv0 ) ) { char *dir; char full_path[VIPS_PATH_MAX]; char *resolved; @@ -1183,8 +1260,8 @@ vips_version( int flag ) * vips_leak_set: * @leak: turn leak checking on or off * - * Turn on or off vips leak checking. See also --vips-leak and - * vips_add_option_entries(). + * Turn on or off vips leak checking. See also --vips-leak, + * vips_add_option_entries() and the `VIPS_LEAK` environment variable. * * You should call this very early in your program. */ diff --git a/libvips/iofuncs/mapfile.c b/libvips/iofuncs/mapfile.c index 33911aac..8dda21e6 100644 --- a/libvips/iofuncs/mapfile.c +++ b/libvips/iofuncs/mapfile.c @@ -90,6 +90,59 @@ #include #endif /*OS_WIN32*/ +/* Does this fd support mmap. Pipes won't, for example. + */ +gboolean +vips__mmap_supported( int fd ) +{ + void *baseaddr; + size_t length = 4096; + off_t offset = 0; + +#ifdef OS_WIN32 +{ + HANDLE hFile = (HANDLE) _get_osfhandle( fd ); + + DWORD flProtect; + HANDLE hMMFile; + + DWORD dwDesiredAccess; + ULARGE_INTEGER quad; + DWORD dwFileOffsetHigh; + DWORD dwFileOffsetLow; + + flProtect = PAGE_READONLY; + if( !(hMMFile = CreateFileMapping( hFile, + NULL, flProtect, 0, 0, NULL )) ) + return( FALSE ); + + dwDesiredAccess = FILE_MAP_READ; + quad.QuadPart = offset; + dwFileOffsetLow = quad.LowPart; + dwFileOffsetHigh = quad.HighPart; + if( !(baseaddr = (char *)MapViewOfFile( hMMFile, dwDesiredAccess, + dwFileOffsetHigh, dwFileOffsetLow, length )) ) { + CloseHandle( hMMFile ); + return( FALSE ); + } + CloseHandle( hMMFile ); + UnmapViewOfFile( baseaddr ); +} +#else /*!OS_WIN32*/ +{ + int prot = PROT_READ; + int flags = MAP_SHARED; + + baseaddr = mmap( 0, length, prot, flags, fd, (off_t) offset ); + if( baseaddr == MAP_FAILED ) + return( FALSE ); + munmap( baseaddr, length ); +} +#endif /*OS_WIN32*/ + + return( TRUE ); +} + void * vips__mmap( int fd, int writeable, size_t length, gint64 offset ) { @@ -288,6 +341,10 @@ vips_remapfilerw( VipsImage *image ) { void *baseaddr; +#ifdef DEBUG + printf( "vips_remapfilerw:\n" ); +#endif /*DEBUG*/ + #ifdef OS_WIN32 { HANDLE hFile = (HANDLE) _get_osfhandle( image->fd ); diff --git a/libvips/iofuncs/memory.c b/libvips/iofuncs/memory.c index 9f9b55f8..2233b17b 100644 --- a/libvips/iofuncs/memory.c +++ b/libvips/iofuncs/memory.c @@ -356,32 +356,25 @@ vips_tracked_malloc( size_t size ) * vips_tracked_open: * @pathname: name of file to open * @flags: flags for open() - * @...: open mode + * @mode: open mode * - * Exactly as open(2), but the number of files current open via + * Exactly as open(2), but the number of files currently open via * vips_tracked_open() is available via vips_tracked_get_files(). This is used * by the vips operation cache to drop cache when the number of files * available is low. * * You must only close the file descriptor with vips_tracked_close(). * + * @pathname should be utf8. + * * See also: vips_tracked_close(), vips_tracked_get_files(). * * Returns: a file descriptor, or -1 on error. */ int -vips_tracked_open( const char *pathname, int flags, ... ) +vips_tracked_open( const char *pathname, int flags, mode_t mode ) { int fd; - mode_t mode; - va_list ap; - - /* mode_t is promoted to int in ..., so we have to pull it out as an - * int. - */ - va_start( ap, flags ); - mode = va_arg( ap, int ); - va_end( ap ); if( (fd = vips__open( pathname, flags, mode )) == -1 ) return( -1 ); diff --git a/libvips/iofuncs/object.c b/libvips/iofuncs/object.c index 39567f53..bbb6ac3c 100644 --- a/libvips/iofuncs/object.c +++ b/libvips/iofuncs/object.c @@ -71,11 +71,11 @@ * * Functional class creation Vips objects have a very * regular lifecycle: initialise, build, use, destroy. They behave rather like - * function calls and are free of side-effects. + * function calls and are free of side-effects. * * Run-time introspection Vips objects can be fully - * introspected at run-time. There is not need for separate source-code - * analysis. + * introspected at run-time. There is no need for separate source-code + * analysis. * * Command-line interface Any vips object can be run from * the command-line with the `vips` driver program. @@ -1886,29 +1886,48 @@ vips_object_set_argument_from_string( VipsObject *object, VipsImage *out; VipsOperationFlags flags; VipsAccess access; - - if( !value ) { - vips_object_no_value( object, name ); - return( -1 ); - } + char filename[VIPS_PATH_MAX]; + char option_string[VIPS_PATH_MAX]; flags = 0; if( VIPS_IS_OPERATION( object ) ) flags = vips_operation_get_flags( VIPS_OPERATION( object ) ); - /* Read the filename. - */ - if( flags & (VIPS_OPERATION_SEQUENTIAL_UNBUFFERED | - VIPS_OPERATION_SEQUENTIAL) ) + if( flags & + (VIPS_OPERATION_SEQUENTIAL_UNBUFFERED | + VIPS_OPERATION_SEQUENTIAL) ) access = VIPS_ACCESS_SEQUENTIAL; else access = VIPS_ACCESS_RANDOM; - if( !(out = vips_image_new_from_file( value, - "access", access, - NULL )) ) + if( !value ) { + vips_object_no_value( object, name ); return( -1 ); + } + vips__filename_split8( value, filename, option_string ); + + if( strcmp( "stdin", filename ) == 0 ) { + VipsSource *source; + + if( !(source = + vips_source_new_from_descriptor( 0 )) ) + return( -1 ); + if( !(out = vips_image_new_from_source( source, + option_string, + "access", access, + NULL )) ) { + VIPS_UNREF( source ); + return( -1 ); + } + VIPS_UNREF( source ); + } + else { + if( !(out = vips_image_new_from_file( value, + "access", access, + NULL )) ) + return( -1 ); + } g_value_init( &gvalue, VIPS_TYPE_IMAGE ); g_value_set_object( &gvalue, out ); @@ -1918,6 +1937,25 @@ vips_object_set_argument_from_string( VipsObject *object, */ g_object_unref( out ); } + else if( g_type_is_a( otype, VIPS_TYPE_SOURCE ) ) { + VipsSource *source; + + if( !value ) { + vips_object_no_value( object, name ); + return( -1 ); + } + + if( !(source = vips_source_new_from_options( value )) ) + return( -1 ); + + g_value_init( &gvalue, VIPS_TYPE_SOURCE ); + g_value_set_object( &gvalue, source ); + + /* Setting gvalue will have upped @out's count again, + * go back to 1 so that gvalue has the only ref. + */ + g_object_unref( source ); + } else if( g_type_is_a( otype, VIPS_TYPE_ARRAY_IMAGE ) ) { /* We have to have a special case for this, we can't just rely * on transform_g_string_array_image(), since we need to be @@ -2166,14 +2204,36 @@ vips_object_get_argument_to_string( VipsObject *object, if( g_type_is_a( otype, VIPS_TYPE_IMAGE ) ) { VipsImage *in; -/* Pull out the image and write it. - */ - g_object_get( object, name, &in, NULL ); - if( vips_image_write_to_file( in, arg, NULL ) ) { - g_object_unref( in ); - return( -1 ); + char filename[VIPS_PATH_MAX]; + char option_string[VIPS_PATH_MAX]; + + vips__filename_split8( arg, filename, option_string ); + + if( vips_isprefix( ".", filename ) ) { + VipsTarget *target; + + if( !(target = vips_target_new_to_descriptor( 1 )) ) + return( -1 ); + g_object_get( object, name, &in, NULL ); + if( vips_image_write_to_target( in, + arg, target, NULL ) ) { + VIPS_UNREF( in ); + VIPS_UNREF( target ); + return( -1 ); + } + VIPS_UNREF( in ); + VIPS_UNREF( target ); + } + else { + /* Pull out the image and write it. + */ + g_object_get( object, name, &in, NULL ); + if( vips_image_write_to_file( in, arg, NULL ) ) { + VIPS_UNREF( in ); + return( -1 ); + } + VIPS_UNREF( in ); } - g_object_unref( in ); } else if( g_type_is_a( otype, VIPS_TYPE_OBJECT ) && (oclass = g_type_class_ref( otype )) && diff --git a/libvips/iofuncs/operation.c b/libvips/iofuncs/operation.c index 2195bacf..b93a45f9 100644 --- a/libvips/iofuncs/operation.c +++ b/libvips/iofuncs/operation.c @@ -611,7 +611,7 @@ vips_operation_get_flags( VipsOperation *operation ) void vips_operation_class_print_usage( VipsOperationClass *operation_class ) { - char str[2048]; + char str[4096]; VipsBuf buf = VIPS_BUF_STATIC( str ); operation_class->usage( operation_class, &buf ); @@ -1245,7 +1245,8 @@ typedef struct _VipsCall { static const char * vips_call_get_arg( VipsCall *call, int i ) { - if( i < 0 || i >= call->argc ) { + if( i < 0 || + i >= call->argc ) { vips_error( VIPS_OBJECT_GET_CLASS( call->operation )->nickname, "%s", _( "too few arguments" ) ); return( NULL ); diff --git a/libvips/iofuncs/region.c b/libvips/iofuncs/region.c index fb9f7386..50b4dc83 100644 --- a/libvips/iofuncs/region.c +++ b/libvips/iofuncs/region.c @@ -1235,6 +1235,8 @@ vips_region_shrink_uncoded_mean( VipsRegion *from, * IS stable with respect to the initial arrangement of input values */ #define SHRINK_TYPE_MEDIAN( TYPE ) { \ + int ls = VIPS_REGION_LSKIP( from ); \ + \ for( x = 0; x < target->width; x++ ) { \ TYPE *tp = (TYPE *) p; \ TYPE *tp1 = (TYPE *) (p + ls); \ @@ -1254,50 +1256,6 @@ vips_region_shrink_uncoded_mean( VipsRegion *from, } \ } -/* Generate area @target in @to using pixels in @from. Non-complex. - */ -static void -vips_region_shrink_uncoded_median( VipsRegion *from, - VipsRegion *to, const VipsRect *target ) -{ - int ls = VIPS_REGION_LSKIP( from ); - int ps = VIPS_IMAGE_SIZEOF_PEL( from->im ); - int nb = from->im->Bands; - - int x, y, z; - - for( y = 0; y < target->height; y++ ) { - VipsPel *p = VIPS_REGION_ADDR( from, - target->left * 2, (target->top + y) * 2 ); - VipsPel *q = VIPS_REGION_ADDR( to, - target->left, target->top + y ); - - /* Process this line of pels. - */ - switch( from->im->BandFmt ) { - case VIPS_FORMAT_UCHAR: - SHRINK_TYPE_MEDIAN( unsigned char ); break; - case VIPS_FORMAT_CHAR: - SHRINK_TYPE_MEDIAN( signed char ); break; - case VIPS_FORMAT_USHORT: - SHRINK_TYPE_MEDIAN( unsigned short ); break; - case VIPS_FORMAT_SHORT: - SHRINK_TYPE_MEDIAN( signed short ); break; - case VIPS_FORMAT_UINT: - SHRINK_TYPE_MEDIAN( unsigned int ); break; - case VIPS_FORMAT_INT: - SHRINK_TYPE_MEDIAN( signed int ); break; - case VIPS_FORMAT_FLOAT: - SHRINK_TYPE_MEDIAN( float ); break; - case VIPS_FORMAT_DOUBLE: - SHRINK_TYPE_MEDIAN( double ); break; - - default: - g_assert_not_reached(); - } - } -} - /* This method is implemented so as to perform well and to always select an * output pixel from one of the input pixels. As such we make only the * following guarantees: @@ -1309,6 +1267,8 @@ vips_region_shrink_uncoded_median( VipsRegion *from, * IS stable with respect to the initial arrangement of input values */ #define SHRINK_TYPE_MODE( TYPE ) { \ + int ls = VIPS_REGION_LSKIP( from ); \ + \ for( x = 0; x < target->width; x++ ) { \ TYPE *tp = (TYPE *) p; \ TYPE *tp1 = (TYPE *) (p + ls); \ @@ -1327,57 +1287,112 @@ vips_region_shrink_uncoded_median( VipsRegion *from, tq[z] = v[index]; \ } \ \ - /* Move on two pels in input. \ - */ \ p += ps << 1; \ q += ps; \ } \ } -/* Generate area @target in @to using pixels in @from. Non-complex. - */ -static void -vips_region_shrink_uncoded_mode( VipsRegion *from, - VipsRegion *to, const VipsRect *target ) -{ - int ls = VIPS_REGION_LSKIP( from ); - int ps = VIPS_IMAGE_SIZEOF_PEL( from->im ); - int nb = from->im->Bands; - - int x, y, z; - - for( y = 0; y < target->height; y++ ) { - VipsPel *p = VIPS_REGION_ADDR( from, - target->left * 2, (target->top + y) * 2 ); - VipsPel *q = VIPS_REGION_ADDR( to, - target->left, target->top + y ); - - /* Process this line of pels. - */ - switch( from->im->BandFmt ) { - case VIPS_FORMAT_UCHAR: - SHRINK_TYPE_MODE( unsigned char ); break; - case VIPS_FORMAT_CHAR: - SHRINK_TYPE_MODE( signed char ); break; - case VIPS_FORMAT_USHORT: - SHRINK_TYPE_MODE( unsigned short ); break; - case VIPS_FORMAT_SHORT: - SHRINK_TYPE_MODE( signed short ); break; - case VIPS_FORMAT_UINT: - SHRINK_TYPE_MODE( unsigned int ); break; - case VIPS_FORMAT_INT: - SHRINK_TYPE_MODE( signed int ); break; - case VIPS_FORMAT_FLOAT: - SHRINK_TYPE_MODE( float ); break; - case VIPS_FORMAT_DOUBLE: - SHRINK_TYPE_MODE( double ); break; - - default: - g_assert_not_reached(); - } - } +#define SHRINK_TYPE_MAX( TYPE ) { \ + int ls = VIPS_REGION_LSKIP( from ); \ + \ + for( x = 0; x < target->width; x++ ) { \ + TYPE *tp = (TYPE *) p; \ + TYPE *tp1 = (TYPE *) (p + ls); \ + TYPE *tq = (TYPE *) q; \ + \ + for( z = 0; z < nb; z++ ) { \ + tq[z] = VIPS_MAX( \ + VIPS_MAX( tp[z], tp[z + nb] ), \ + VIPS_MAX( tp1[z], tp1[z + nb] ) \ + ); \ + } \ + \ + p += ps << 1; \ + q += ps; \ + } \ } +#define SHRINK_TYPE_MIN( TYPE ) { \ + int ls = VIPS_REGION_LSKIP( from ); \ + \ + for( x = 0; x < target->width; x++ ) { \ + TYPE *tp = (TYPE *) p; \ + TYPE *tp1 = (TYPE *) (p + ls); \ + TYPE *tq = (TYPE *) q; \ + \ + for( z = 0; z < nb; z++ ) { \ + tq[z] = VIPS_MIN( \ + VIPS_MIN( tp[z], tp[z + nb] ), \ + VIPS_MIN( tp1[z], tp1[z + nb] ) \ + ); \ + } \ + \ + p += ps << 1; \ + q += ps; \ + } \ +} + +#define SHRINK_TYPE_NEAREST( TYPE ) { \ + for( x = 0; x < target->width; x++ ) { \ + TYPE *tp = (TYPE *) p; \ + TYPE *tq = (TYPE *) q; \ + \ + for( z = 0; z < nb; z++ ) \ + tq[z] = tp[z]; \ + \ + p += ps << 1; \ + q += ps; \ + } \ +} + +#define VIPS_REGION_SHRINK( OP ) \ +static void \ +vips_region_shrink_uncoded_ ## OP( VipsRegion *from, \ + VipsRegion *to, const VipsRect *target ) \ +{ \ + int ps = VIPS_IMAGE_SIZEOF_PEL( from->im ); \ + int nb = from->im->Bands; \ + \ + int x, y, z; \ + \ + for( y = 0; y < target->height; y++ ) { \ + VipsPel *p = VIPS_REGION_ADDR( from, \ + target->left * 2, (target->top + y) * 2 ); \ + VipsPel *q = VIPS_REGION_ADDR( to, \ + target->left, target->top + y ); \ + \ + /* Process this line of pels. \ + */ \ + switch( from->im->BandFmt ) { \ + case VIPS_FORMAT_UCHAR: \ + SHRINK_TYPE_ ## OP( unsigned char ); break; \ + case VIPS_FORMAT_CHAR: \ + SHRINK_TYPE_ ## OP( signed char ); break; \ + case VIPS_FORMAT_USHORT: \ + SHRINK_TYPE_ ## OP( unsigned short ); break; \ + case VIPS_FORMAT_SHORT: \ + SHRINK_TYPE_ ## OP( signed short ); break; \ + case VIPS_FORMAT_UINT: \ + SHRINK_TYPE_ ## OP( unsigned int ); break; \ + case VIPS_FORMAT_INT: \ + SHRINK_TYPE_ ## OP( signed int ); break; \ + case VIPS_FORMAT_FLOAT: \ + SHRINK_TYPE_ ## OP( float ); break; \ + case VIPS_FORMAT_DOUBLE: \ + SHRINK_TYPE_ ## OP( double ); break; \ + \ + default: \ + g_assert_not_reached(); \ + } \ + } \ +} + +VIPS_REGION_SHRINK( MAX ); +VIPS_REGION_SHRINK( MIN ); +VIPS_REGION_SHRINK( MODE ); +VIPS_REGION_SHRINK( MEDIAN ); +VIPS_REGION_SHRINK( NEAREST ); + /* Generate area @target in @to using pixels in @from. Non-complex. */ static void @@ -1388,11 +1403,25 @@ vips_region_shrink_uncoded( VipsRegion *from, case VIPS_REGION_SHRINK_MEAN: vips_region_shrink_uncoded_mean( from, to, target ); break; + case VIPS_REGION_SHRINK_MEDIAN: - vips_region_shrink_uncoded_median( from, to, target ); + vips_region_shrink_uncoded_MEDIAN( from, to, target ); break; + case VIPS_REGION_SHRINK_MODE: - vips_region_shrink_uncoded_mode( from, to, target ); + vips_region_shrink_uncoded_MODE( from, to, target ); + break; + + case VIPS_REGION_SHRINK_MAX: + vips_region_shrink_uncoded_MAX( from, to, target ); + break; + + case VIPS_REGION_SHRINK_MIN: + vips_region_shrink_uncoded_MIN( from, to, target ); + break; + + case VIPS_REGION_SHRINK_NEAREST: + vips_region_shrink_uncoded_NEAREST( from, to, target ); break; default: diff --git a/libvips/iofuncs/sbuf.c b/libvips/iofuncs/sbuf.c new file mode 100644 index 00000000..1a366000 --- /dev/null +++ b/libvips/iofuncs/sbuf.c @@ -0,0 +1,538 @@ +/* Buffered input from a source. + * + * J.Cupitt, 18/11/19 + */ + +/* + + 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 VIPS_DEBUG + */ + +#ifdef HAVE_CONFIG_H +#include +#endif /*HAVE_CONFIG_H*/ +#include + +#include +#include +#include +#ifdef HAVE_UNISTD_H +#include +#endif /*HAVE_UNISTD_H*/ +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/** + * SECTION: sbuf + * @short_description: buffered read from a source + * @stability: Stable + * @see_also: foreign + * @include: vips/vips.h + * @title: VipsSbuf + * + * #VipsSbuf wraps up a #VipsSource and provides a set of calls for + * text-oriented buffered reading. You can fetch lines of text, skip + * whitespace, and so on. + * + * It is useful for implementing things like CSV readers, for example. + */ + +G_DEFINE_TYPE( VipsSbuf, vips_sbuf, VIPS_TYPE_OBJECT ); + +static void +vips_sbuf_class_init( VipsSbufClass *class ) +{ + VipsObjectClass *object_class = VIPS_OBJECT_CLASS( class ); + GObjectClass *gobject_class = G_OBJECT_CLASS( class ); + + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + object_class->nickname = "sbuf"; + object_class->description = _( "buffered source" ); + + VIPS_ARG_OBJECT( class, "input", 1, + _( "Input" ), + _( "Source to load from" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsSbuf, source ), + VIPS_TYPE_SOURCE ); + +} + +static void +vips_sbuf_init( VipsSbuf *sbuf ) +{ + sbuf->read_point = 0; + sbuf->chars_in_buffer = 0; + sbuf->input_buffer[0] = '\0'; +} + +/** + * vips_sbuf_new_from_source: + * @source: source to operate on + * + * Create a VipsSbuf wrapping a source. + * + * Returns: a new #VipsSbuf + */ +VipsSbuf * +vips_sbuf_new_from_source( VipsSource *source ) +{ + VipsSbuf *sbuf; + + g_assert( source ); + + sbuf = VIPS_SBUF( g_object_new( VIPS_TYPE_SBUF, + "input", source, + NULL ) ); + + if( vips_object_build( VIPS_OBJECT( sbuf ) ) ) { + VIPS_UNREF( sbuf ); + return( NULL ); + } + + return( sbuf ); +} + +/** + * vips_sbuf_unbuffer: + * @sbuf: source to operate on + * + * Discard the input buffer and reset the read point. You must call this + * before using read or seek on the underlying #VipsSource class. + */ +void +vips_sbuf_unbuffer( VipsSbuf *sbuf ) +{ + /* We'd read ahead a little way -- seek backwards by that amount. + */ + vips_source_seek( sbuf->source, + sbuf->read_point - sbuf->chars_in_buffer, SEEK_CUR ); + sbuf->read_point = 0; + sbuf->chars_in_buffer = 0; +} + +/* Returns -1 on error, 0 on EOF, otherwise bytes read. + */ +static gint64 +vips_sbuf_refill( VipsSbuf *sbuf ) +{ + gint64 bytes_read; + + VIPS_DEBUG_MSG( "vips_sbuf_refill:\n" ); + + /* We should not discard any unread bytes. + */ + g_assert( sbuf->read_point == sbuf->chars_in_buffer ); + + bytes_read = vips_source_read( sbuf->source, + sbuf->input_buffer, VIPS_SBUF_BUFFER_SIZE ); + if( bytes_read == -1 ) + return( -1 ); + + sbuf->read_point = 0; + sbuf->chars_in_buffer = bytes_read; + + /* Always add a null byte so we can use strchr() etc. on lines. This is + * safe because input_buffer is VIPS_SBUF_BUFFER_SIZE + 1 bytes. + */ + sbuf->input_buffer[bytes_read] = '\0'; + + return( bytes_read ); +} + +/** + * vips_sbuf_getc: + * @sbuf: source to operate on + * + * Fetch the next character from the source. + * + * If you can, use the macro VIPS_SBUF_GETC() instead for speed. + * + * Returns: the next char from @sbuf, -1 on read error or EOF. + */ +int +vips_sbuf_getc( VipsSbuf *sbuf ) +{ + if( sbuf->read_point == sbuf->chars_in_buffer && + vips_sbuf_refill( sbuf ) <= 0 ) + return( -1 ); + + g_assert( sbuf->read_point < sbuf->chars_in_buffer ); + + return( sbuf->input_buffer[sbuf->read_point++] ); +} + +/** + * VIPS_SBUF_GETC: + * @sbuf: source to operate on + * + * Fetch the next character from the source. + * + * Returns: the next char from @sbuf, -1 on read error or EOF. + */ + +/** + * vips_sbuf_ungetc: + * @sbuf: source to operate on + * + * The opposite of vips_sbuf_getc(): undo the previous getc. + * + * unget more than one character is undefined. Unget at the start of the file + * does nothing. + * + * If you can, use the macro VIPS_SBUF_UNGETC() instead for speed. + */ +void +vips_sbuf_ungetc( VipsSbuf *sbuf ) +{ + if( sbuf->read_point > 0 ) + sbuf->read_point -= 1; +} + +/** + * VIPS_SBUF_UNGETC: + * @sbuf: source to operate on + * + * The opposite of vips_sbuf_getc(): undo the previous getc. + * + * unget more than one character is undefined. Unget at the start of the file + * does nothing. + */ + +/** + * vips_sbuf_require: + * @sbuf: source to operate on + * @require: make sure we have at least this many chars available + * + * Make sure there are at least @require bytes of readahead available. + * + * Returns: 0 on success, -1 on error or EOF. + */ +int +vips_sbuf_require( VipsSbuf *sbuf, int require ) +{ + g_assert( require < VIPS_SBUF_BUFFER_SIZE ); + g_assert( sbuf->chars_in_buffer >= 0 ); + g_assert( sbuf->chars_in_buffer <= VIPS_SBUF_BUFFER_SIZE ); + g_assert( sbuf->read_point >= 0 ); + g_assert( sbuf->read_point <= sbuf->chars_in_buffer ); + + VIPS_DEBUG_MSG( "vips_sbuf_require: %d\n", require ); + + if( sbuf->read_point + require > sbuf->chars_in_buffer ) { + /* Areas can overlap, so we must memmove(). + */ + memmove( sbuf->input_buffer, + sbuf->input_buffer + sbuf->read_point, + sbuf->chars_in_buffer - sbuf->read_point ); + sbuf->chars_in_buffer -= sbuf->read_point; + sbuf->read_point = 0; + + while( require > sbuf->chars_in_buffer ) { + unsigned char *to = sbuf->input_buffer + + sbuf->chars_in_buffer; + int space_available = + VIPS_SBUF_BUFFER_SIZE - + sbuf->chars_in_buffer; + size_t bytes_read; + + if( (bytes_read = vips_source_read( sbuf->source, + to, space_available )) == -1 ) + return( -1 ); + if( bytes_read == 0 ) { + vips_error( + vips_connection_nick( VIPS_CONNECTION( + sbuf->source ) ), + "%s", _( "end of file" ) ); + return( -1 ); + } + + to[bytes_read] = '\0'; + sbuf->chars_in_buffer += bytes_read; + } + } + + return( 0 ); +} + +/** + * VIPS_SBUF_REQUIRE: + * @sbuf: source to operate on + * @require: need this many characters + * + * Make sure at least @require characters are available for + * VIPS_SBUF_PEEK() and VIPS_SBUF_FETCH(). + * + * Returns: 0 on success, -1 on read error or EOF. + */ + +/** + * VIPS_SBUF_PEEK: + * @sbuf: source to operate on + * + * After a successful VIPS_SBUF_REQUIRE(), you can index this to get + * require characters of input. + * + * Returns: a pointer to the next requre characters of input. + */ + +/** + * VIPS_SBUF_FETCH: + * @sbuf: source to operate on + * + * After a successful VIPS_SBUF_REQUIRE(), you can use this require times + * to fetch characters of input. + * + * Returns: the next input character. + */ + +/** + * vips_sbuf_get_line: + * @sbuf: source to operate on + * + * Fetch the next line of text from @sbuf and return it. The end of + * line character (or characters, for DOS files) are removed, and the string + * is terminated with a null (`\0` character). + * + * Returns NULL on end of file or read error. + * + * If the line is longer than some arbitrary (but large) limit, it is + * truncated. If you need to be able to read very long lines, use the + * slower vips_sbuf_get_line_copy(). + * + * The return value is owned by @sbuf and must not be freed. It + * is valid until the next get call to @sbuf. + * + * Returns: the next line of text, or NULL on EOF or read error. + */ +const char * +vips_sbuf_get_line( VipsSbuf *sbuf ) +{ + int write_point; + int space_remaining; + int ch; + + VIPS_DEBUG_MSG( "vips_sbuf_get_line:\n" ); + + write_point = 0; + space_remaining = VIPS_SBUF_BUFFER_SIZE; + + while( (ch = VIPS_SBUF_GETC( sbuf )) != -1 && + ch != '\n' && + space_remaining > 0 ) { + sbuf->line[write_point] = ch; + write_point += 1; + space_remaining -= 1; + } + sbuf->line[write_point] = '\0'; + + /* If we hit EOF immediately, return EOF. + */ + if( ch == -1 && + write_point == 0 ) + return( NULL ); + + /* If the final char in the buffer is \r, this is probably a DOS file + * and we should remove that too. + * + * There's a chance this could incorrectly remove \r in very long + * lines, but ignore this. + */ + if( write_point > 0 && + sbuf->line[write_point - 1] == '\r' ) + sbuf->line[write_point - 1] = '\0'; + /* If we filled the output line without seeing \n, keep going to the + * next \n. + */ + if( ch != '\n' && + space_remaining == 0 ) { + while( (ch = VIPS_SBUF_GETC( sbuf )) != -1 && + ch != '\n' ) + ; + } + + VIPS_DEBUG_MSG( " %s\n", sbuf->line ); + + return( (const char *) sbuf->line ); +} + +/** + * vips_sbuf_get_line_copy: + * @sbuf: source to operate on + * + * Fetch the next line of text from @sbuf and return it. The end of + * line character (or characters, for DOS files) are removed, and the string + * is terminated with a null (`\0` character). + * + * The return result must be freed with g_free(). + * + * This is slower than vips_sbuf_get_line(), but can work with lines of + * any length. + * + * Returns: the next line of text, or NULL on EOF or read error. + */ +char * +vips_sbuf_get_line_copy( VipsSbuf *sbuf ) +{ + static const unsigned char null = '\0'; + + VIPS_DEBUG_MSG( "vips_sbuf_get_line_copy:\n" ); + + GByteArray *buffer; + int ch; + char *result; + + buffer = g_byte_array_new(); + + while( (ch = VIPS_SBUF_GETC( sbuf )) != -1 && + ch != '\n' ) { + unsigned char c = ch; + + g_byte_array_append( buffer, &c, 1 ); + } + + /* Immediate EOF. + */ + if( ch == -1 && + buffer->len == 0 ) { + VIPS_FREEF( g_byte_array_unref, buffer ); + return( NULL ); + } + + /* If the character before the \n was \r, this is probably a DOS file + * and we should remove the \r. + */ + if( ch == '\n' && + buffer->len > 0 && + buffer->data[buffer->len - 1] == '\r' ) + g_byte_array_set_size( buffer, buffer->len - 1 ); + + g_byte_array_append( buffer, &null, 1 ); + + result = (char *) g_byte_array_free( buffer, FALSE ); + + VIPS_DEBUG_MSG( " %s\n", result ); + + return( result ); +} + +/** + * vips_sbuf_get_non_whitespace: + * @sbuf: source to operate on + * + * Fetch the next chunk of non-whitespace text from the source, and + * null-terminate it. + * + * After this, the next getc will be the first char of the next block of + * whitespace (or EOF). + * + * If the first getc is whitespace, stop instantly and return the empty + * string. + * + * If the item is longer than some arbitrary (but large) limit, it is + * truncated. + * + * The return value is owned by @sbuf and must not be freed. It + * is valid until the next get call to @sbuf. + * + * Returns: the next block of non-whitespace, or NULL on EOF or read error. + */ +const char * +vips_sbuf_get_non_whitespace( VipsSbuf *sbuf ) +{ + int ch; + int i; + + for( i = 0; i < VIPS_SBUF_BUFFER_SIZE && + !isspace( ch = VIPS_SBUF_GETC( sbuf ) ) && + ch != EOF; i++ ) + sbuf->line[i] = ch; + sbuf->line[i] = '\0'; + + /* If we stopped before seeing any whitespace, skip to the end of the + * block of non-whitespace. + */ + if( !isspace( ch ) ) + while( !isspace( ch = VIPS_SBUF_GETC( sbuf ) ) && + ch != EOF ) + ; + + /* If we finally stopped on whitespace, step back one so the next get + * will be whitespace (or EOF). + */ + if( isspace( ch ) ) + VIPS_SBUF_UNGETC( sbuf ); + + return( (const char *) sbuf->line ); +} + +/** + * vips_sbuf_skip_whitespace: + * @sbuf: source to operate on + * + * After this, the next getc will be the first char of the next block of + * non-whitespace (or EOF). + * + * Also skip comments, ie. from any '#' character to the end of the line. + * + * Returns: 0 on success, or -1 on EOF. + */ +int +vips_sbuf_skip_whitespace( VipsSbuf *sbuf ) +{ + int ch; + + do { + ch = VIPS_SBUF_GETC( sbuf ); + + /* # skip comments too. + */ + if( ch == '#' ) { + /* Probably EOF. + */ + if( !vips_sbuf_get_line( sbuf ) ) + return( -1 ); + ch = VIPS_SBUF_GETC( sbuf ); + } + } while( isspace( ch ) ); + + VIPS_SBUF_UNGETC( sbuf ); + + return( 0 ); +} diff --git a/libvips/iofuncs/sinkdisc.c b/libvips/iofuncs/sinkdisc.c index acf3ff5e..33647b63 100644 --- a/libvips/iofuncs/sinkdisc.c +++ b/libvips/iofuncs/sinkdisc.c @@ -254,7 +254,7 @@ wbuffer_flush( Write *write ) if( write->buf->area.top > 0 ) { vips_semaphore_down( &write->buf_back->done ); - /* Previous write suceeded? + /* Previous write succeeded? */ if( write->buf_back->write_errno ) { vips_error_system( write->buf_back->write_errno, diff --git a/libvips/iofuncs/sinkscreen.c b/libvips/iofuncs/sinkscreen.c index 4d57e9be..e36644a3 100644 --- a/libvips/iofuncs/sinkscreen.c +++ b/libvips/iofuncs/sinkscreen.c @@ -1029,19 +1029,16 @@ render_thread_main( void *client ) return( NULL ); } -static void -vips_sink_screen_init( void ) +void +vips__sink_screen_init( void ) { g_assert( !render_thread ); g_assert( !render_dirty_lock ); render_dirty_lock = vips_g_mutex_new(); + vips_semaphore_init( &n_render_dirty_sem, 0, "n_render_dirty" ); render_thread = vips_g_thread_new( "sink_screen", render_thread_main, NULL ); - vips_semaphore_init( &n_render_dirty_sem, 0, "n_render_dirty" ); - - g_assert( render_thread ); - g_assert( render_dirty_lock ); } /** @@ -1101,12 +1098,8 @@ vips_sink_screen( VipsImage *in, VipsImage *out, VipsImage *mask, int priority, VipsSinkNotify notify_fn, void *a ) { - static GOnce once = G_ONCE_INIT; - Render *render; - VIPS_ONCE( &once, (GThreadFunc) vips_sink_screen_init, NULL ); - if( tile_width <= 0 || tile_height <= 0 || max_tiles < -1 ) { vips_error( "vips_sink_screen", "%s", _( "bad parameters" ) ); diff --git a/libvips/iofuncs/source.c b/libvips/iofuncs/source.c new file mode 100644 index 00000000..b20c5b58 --- /dev/null +++ b/libvips/iofuncs/source.c @@ -0,0 +1,1272 @@ +/* A byte source/sink .. it can be a pipe, file descriptor, memory area, + * socket, node.js stream, etc. + * + * 19/6/14 + * + * 3/2/20 + * - add vips_pipe_read_limit_set() + */ + +/* + + 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 + + */ + +/* TODO + * + * - can we map and then close the fd? how about on Windows? + */ + +/* +#define VIPS_DEBUG +#define TEST_SANITY + */ + +#ifdef HAVE_CONFIG_H +#include +#endif /*HAVE_CONFIG_H*/ +#include + +#include +#include +#ifdef HAVE_UNISTD_H +#include +#endif /*HAVE_UNISTD_H*/ +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/* Try to make an O_BINARY ... sometimes need the leading '_'. + */ +#ifdef BINARY_OPEN +#ifndef O_BINARY +#ifdef _O_BINARY +#define O_BINARY _O_BINARY +#endif /*_O_BINARY*/ +#endif /*!O_BINARY*/ +#endif /*BINARY_OPEN*/ + +/* If we have O_BINARY, add it to a mode flags set. + */ +#ifdef O_BINARY +#define BINARYIZE(M) ((M) | O_BINARY) +#else /*!O_BINARY*/ +#define BINARYIZE(M) (M) +#endif /*O_BINARY*/ + +#define MODE_READ BINARYIZE (O_RDONLY) +#define MODE_READWRITE BINARYIZE (O_RDWR) +#define MODE_WRITE BINARYIZE (O_WRONLY | O_CREAT | O_TRUNC) + +/* -1 on a pipe isn't actually unbounded. Have a limit to prevent + * huge sources accidentally filling memory. + * + * This can be configured with vips_pipe_read_limit_set(). + */ +static gint64 vips__pipe_read_limit = 1024 * 1024 * 1024; + +/** + * vips_pipe_read_limit_set: + * @limit: maximum number of bytes to buffer from a pipe + * + * If a source does not support mmap or seek and the source is + * used with a loader that can only work from memory, then the data will be + * automatically read into memory to EOF before the loader starts. This can + * produce high memory use if the descriptor represents a large object. + * + * Use vips_pipe_read_limit_set() to limit the size of object that + * will be read in this way. The default is 1GB. + * + * Set a value of -1 to mean no limit. + * + * See also: `--vips-pipe-read-limit` and the environment variable + * `VIPS_PIPE_READ_LIMIT`. + */ +void +vips_pipe_read_limit_set( gint64 limit ) +{ + vips__pipe_read_limit = limit; +} + +G_DEFINE_TYPE( VipsSource, vips_source, VIPS_TYPE_CONNECTION ); + +/* We can't test for seekability or length during _build, since the read and + * seek signal handlers might not have been connected yet. Instead, we test + * when we first need to know. + */ +static int +vips_source_test_features( VipsSource *source ) +{ + VipsSourceClass *class = VIPS_SOURCE_GET_CLASS( source ); + + if( source->have_tested_seek ) + return( 0 ); + source->have_tested_seek = TRUE; + + VIPS_DEBUG_MSG( "vips_source_test_features: testing seek ..\n" ); + + /* We'll need a descriptor to test. + */ + if( vips_source_unminimise( source ) ) + return( -1 ); + + /* Can we seek this input? + * + * We need to call the method directly rather than via + * vips_source_seek() etc. or we might trigger seek emulation. + */ + if( source->data || + class->seek( source, 0, SEEK_CUR ) != -1 ) { + gint64 length; + + VIPS_DEBUG_MSG( " seekable source\n" ); + + /* We should be able to get the length of seekable + * objects. + */ + if( (length = vips_source_length( source )) == -1 ) + return( -1 ); + + source->length = length; + + /* If we can seek, we won't need to save header bytes. + */ + VIPS_FREEF( g_byte_array_unref, source->header_bytes ); + } + else { + /* Not seekable. This must be some kind of pipe. + */ + VIPS_DEBUG_MSG( " not seekable\n" ); + source->is_pipe = TRUE; + } + + return( 0 ); +} + +#ifdef TEST_SANITY +static void +vips_source_sanity( VipsSource *source ) +{ + if( source->data ) { + /* Not a pipe (can map and seek). + */ + g_assert( !source->is_pipe ); + + /* Read position must lie within the buffer. + */ + g_assert( source->read_position >= 0 ); + g_assert( source->read_position <= source->length ); + + /* After we're done with the header, the sniff buffer should + * be gone. + */ + g_assert( !source->decode || + !source->sniff ); + + /* Have length. + */ + g_assert( source->length != -1 ); + } + else if( source->is_pipe ) { + if( source->decode ) { + /* Reading pixel data. + */ + g_assert( !source->header_bytes ); + g_assert( !source->sniff ); + } + else { + /* Reading header data. + */ + g_assert( source->header_bytes ); + g_assert( source->read_position >= 0 ); + g_assert( source->read_position <= + source->header_bytes->len ); + } + + /* No length available. + */ + g_assert( source->length == -1 ); + } + else { + /* Something like a seekable file. + */ + + /* After we're done with the header, the sniff buffer should + * be gone. + */ + if( source->decode ) { + g_assert( !source->sniff ); + } + + /* Once we've tested seek, the read position must lie within + * the file. + */ + if( source->have_tested_seek ) { + g_assert( source->length != -1 ); + g_assert( source->read_position >= 0 ); + g_assert( source->read_position <= source->length ); + } + + /* Supports minimise, so if descriptor is -1, we must have a + * filename we can reopen. + */ + g_assert( VIPS_CONNECTION( source )->descriptor != -1 || + (VIPS_CONNECTION( source )->filename && + VIPS_CONNECTION( source )->descriptor) ); + } +} +#endif /*TEST_SANITY*/ + +#ifdef TEST_SANITY +#define SANITY( S ) vips_source_sanity( S ) +#warning "sanity tests on in source.c" +#else /*!TEST_SANITY*/ +#define SANITY( S ) +#endif /*TEST_SANITY*/ + +static void +vips_source_finalize( GObject *gobject ) +{ + VipsSource *source = VIPS_SOURCE( gobject ); + + VIPS_FREEF( g_byte_array_unref, source->header_bytes ); + VIPS_FREEF( g_byte_array_unref, source->sniff ); + if( source->mmap_baseaddr ) { + vips__munmap( source->mmap_baseaddr, source->mmap_length ); + source->mmap_baseaddr = NULL; + } + + G_OBJECT_CLASS( vips_source_parent_class )->finalize( gobject ); +} + +static int +vips_source_build( VipsObject *object ) +{ + VipsConnection *connection = VIPS_CONNECTION( object ); + VipsSource *source = VIPS_SOURCE( object ); + + VIPS_DEBUG_MSG( "vips_source_build: %p\n", source ); + + if( VIPS_OBJECT_CLASS( vips_source_parent_class )-> + build( object ) ) + return( -1 ); + + if( vips_object_argument_isset( object, "filename" ) && + vips_object_argument_isset( object, "descriptor" ) ) { + vips_error( vips_connection_nick( connection ), + "%s", _( "don't set 'filename' and 'descriptor'" ) ); + return( -1 ); + } + + /* unminimise will open the filename. + */ + if( vips_object_argument_isset( object, "filename" ) && + vips_source_unminimise( source ) ) + return( -1 ); + + if( vips_object_argument_isset( object, "descriptor" ) ) { + connection->descriptor = dup( connection->descriptor ); + connection->close_descriptor = connection->descriptor; + } + + if( vips_object_argument_isset( object, "blob" ) ) { + size_t length; + + source->data = vips_blob_get( source->blob, &length ); + source->length = VIPS_MIN( length, G_MAXSSIZE ); + } + + return( 0 ); +} + +static gint64 +vips_source_read_real( VipsSource *source, void *data, size_t length ) +{ + VipsConnection *connection = VIPS_CONNECTION( source ); + + gint64 bytes_read; + + VIPS_DEBUG_MSG( "vips_source_read_real:\n" ); + + do { + bytes_read = read( connection->descriptor, data, length ); + } while( bytes_read < 0 && errno == EINTR ); + + return( bytes_read ); +} + +static gint64 +vips_source_seek_real( VipsSource *source, gint64 offset, int whence ) +{ + VipsConnection *connection = VIPS_CONNECTION( source ); + + gint64 new_pos; + + VIPS_DEBUG_MSG( "vips_source_seek_real:\n" ); + + /* Like _read_real(), we must not set a vips_error. We need to use the + * vips__seek() wrapper so we can seek long files on Windows. + */ + new_pos = vips__seek_no_error( connection->descriptor, offset, whence ); + + return( new_pos ); +} + +static void +vips_source_class_init( VipsSourceClass *class ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( class ); + VipsObjectClass *object_class = VIPS_OBJECT_CLASS( class ); + + gobject_class->finalize = vips_source_finalize; + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + object_class->nickname = "source"; + object_class->description = _( "input source" ); + + object_class->build = vips_source_build; + + class->read = vips_source_read_real; + class->seek = vips_source_seek_real; + + VIPS_ARG_BOXED( class, "blob", 3, + _( "Blob" ), + _( "Blob to load from" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsSource, blob ), + VIPS_TYPE_BLOB ); + +} + +static void +vips_source_init( VipsSource *source ) +{ + source->length = -1; + source->sniff = g_byte_array_new(); + source->header_bytes = g_byte_array_new(); +} + +/** + * vips_source_new_from_descriptor: + * @descriptor: read from this file descriptor + * + * Create an source attached to a file descriptor. @descriptor is + * closed with close() when source is finalized. + * + * Returns: a new source. + */ +VipsSource * +vips_source_new_from_descriptor( int descriptor ) +{ + VipsSource *source; + + VIPS_DEBUG_MSG( "vips_source_new_from_descriptor: %d\n", + descriptor ); + + source = VIPS_SOURCE( g_object_new( VIPS_TYPE_SOURCE, + "descriptor", descriptor, + NULL ) ); + + if( vips_object_build( VIPS_OBJECT( source ) ) ) { + VIPS_UNREF( source ); + return( NULL ); + } + + SANITY( source ); + + return( source ); +} + +/** + * vips_source_new_from_file: + * @descriptor: read from this filename + * + * Create an source attached to a file. + * + * If this descriptor does not support mmap and the source is + * used with a loader that can only work from memory, then the data will be + * automatically read into memory to EOF before the loader starts. This can + * produce high memory use if the descriptor represents a large object. + * + * Use vips_pipe_read_limit_set() to limit the size of object that + * will be read in this way. The default is 1GB. + * + * Returns: a new source. + */ +VipsSource * +vips_source_new_from_file( const char *filename ) +{ + VipsSource *source; + + VIPS_DEBUG_MSG( "vips_source_new_from_file: %s\n", + filename ); + + source = VIPS_SOURCE( g_object_new( VIPS_TYPE_SOURCE, + "filename", filename, + NULL ) ); + + if( vips_object_build( VIPS_OBJECT( source ) ) ) { + VIPS_UNREF( source ); + return( NULL ); + } + + SANITY( source ); + + return( source ); +} + +/** + * vips_source_new_from_blob: + * @blob: memory area to load + * + * Create a source attached to an area of memory. + * + * Returns: a new source. + */ +VipsSource * +vips_source_new_from_blob( VipsBlob *blob ) +{ + VipsSource *source; + + VIPS_DEBUG_MSG( "vips_source_new_from_blob: %p\n", blob ); + + source = VIPS_SOURCE( g_object_new( VIPS_TYPE_SOURCE, + "blob", blob, + NULL ) ); + + if( vips_object_build( VIPS_OBJECT( source ) ) ) { + VIPS_UNREF( source ); + return( NULL ); + } + + SANITY( source ); + + return( source ); +} + +/** + * vips_source_new_from_memory: + * @data: memory area to load + * @length: size of memory area + * + * Create a source attached to an area of memory. + * + * You must not free @data while the source is active. + * + * Returns: a new source. + */ +VipsSource * +vips_source_new_from_memory( const void *data, size_t length ) +{ + VipsSource *source; + VipsBlob *blob; + + VIPS_DEBUG_MSG( "vips_source_new_from_buffer: " + "%p, length = %zd\n", data, length ); + + /* We don't take a copy of the data or free it. + */ + blob = vips_blob_new( NULL, data, length ); + + source = vips_source_new_from_blob( blob ); + + vips_area_unref( VIPS_AREA( blob ) ); + + SANITY( source ); + + return( source ); +} + +/** + * vips_source_new_from_options: + * @options: option string + * + * Create a source from an option string. + * + * Returns: a new source. + */ +VipsSource * +vips_source_new_from_options( const char *options ) +{ + VipsSource *source; + + VIPS_DEBUG_MSG( "vips_source_new_from_options: %s\n", options ); + + source = VIPS_SOURCE( g_object_new( VIPS_TYPE_SOURCE, NULL ) ); + + if( vips_object_set_from_string( VIPS_OBJECT( source ), options ) || + vips_object_build( VIPS_OBJECT( source ) ) ) { + VIPS_UNREF( source ); + return( NULL ); + } + + SANITY( source ); + + return( source ); +} + +/** + * vips_source_minimise: + * @source: source to operate on + * + * Minimise the source. As many resources as can be safely removed are + * removed. Use vips_source_unminimise() to restore the source if you wish to + * use it again. + * + * Loaders should call this in response to the minimise signal on their output + * image. + * + * Returns: 0 on success, or -1 on error. + */ +void +vips_source_minimise( VipsSource *source ) +{ + VipsConnection *connection = VIPS_CONNECTION( source ); + + VIPS_DEBUG_MSG( "vips_source_minimise:\n" ); + + SANITY( source ); + + (void) vips_source_test_features( source ); + + if( connection->filename && + connection->descriptor != -1 && + connection->tracked_descriptor == connection->descriptor && + !source->is_pipe ) { + VIPS_DEBUG_MSG( " tracked_close()\n" ); + vips_tracked_close( connection->tracked_descriptor ); + connection->tracked_descriptor = -1; + connection->descriptor = -1; + } + + SANITY( source ); +} + +/** + * vips_source_unminimise: + * @source: source to operate on + * + * Restore the source after minimisation. This is called at the start + * of every source method, so loaders should not usually need this. + * + * See also: vips_source_minimise(). + * + * Returns: 0 on success, or -1 on error. + */ +int +vips_source_unminimise( VipsSource *source ) +{ + VipsConnection *connection = VIPS_CONNECTION( source ); + + VIPS_DEBUG_MSG( "vips_source_unminimise:\n" ); + + if( connection->descriptor == -1 && + connection->tracked_descriptor == -1 && + connection->filename ) { + int fd; + + if( (fd = vips_tracked_open( connection->filename, + MODE_READ, 0 )) == -1 ) { + vips_error_system( errno, + vips_connection_nick( connection ), + "%s", _( "unable to open for read" ) ); + return( -1 ); + } + + connection->tracked_descriptor = fd; + connection->descriptor = fd; + + VIPS_DEBUG_MSG( "vips_source_unminimise: " + "restoring read position %" G_GINT64_FORMAT "\n", + source->read_position ); + if( vips__seek( connection->descriptor, + source->read_position, SEEK_SET ) == -1 ) + return( -1 ); + } + + return( 0 ); +} + +/** + * vips_source_decode: + * @source: source to operate on + * + * Signal the end of header read and the start of the pixel decode phase. + * After this, you can no longer seek on this source. + * + * Loaders should call this at the end of header read. + * + * See also: vips_source_unminimise(). + * + * Returns: 0 on success, -1 on error. + */ +int +vips_source_decode( VipsSource *source ) +{ + VIPS_DEBUG_MSG( "vips_source_decode:\n" ); + + SANITY( source ); + + /* We have finished reading the header. We can discard the header bytes + * we saved. + */ + if( !source->decode ) { + source->decode = TRUE; + VIPS_FREEF( g_byte_array_unref, source->header_bytes ); + VIPS_FREEF( g_byte_array_unref, source->sniff ); + } + + vips_source_minimise( source ); + + SANITY( source ); + + return( 0 ); +} + +/** + * vips_source_read: + * @source: source to operate on + * @buffer: store bytes here + * @length: length of @buffer in bytes + * + * Read up to @length bytes from @source and store the bytes in @buffer. + * Return the number of bytes actually read. If all bytes have been read from + * the file, return 0. + * + * Arguments exactly as read(2). + * + * Returns: the number of bytes read, 0 on end of file, -1 on error. + */ +gint64 +vips_source_read( VipsSource *source, void *buffer, size_t length ) +{ + VipsSourceClass *class = VIPS_SOURCE_GET_CLASS( source ); + + gint64 total_read; + + VIPS_DEBUG_MSG( "vips_source_read:\n" ); + + SANITY( source ); + + if( vips_source_unminimise( source ) || + vips_source_test_features( source ) ) + return( -1 ); + + total_read = 0; + + if( source->data ) { + /* The whole thing is in memory somehow. + */ + gint64 available = VIPS_MIN( length, + source->length - source->read_position ); + + VIPS_DEBUG_MSG( " %zd bytes from memory\n", available ); + memcpy( buffer, + source->data + source->read_position, available ); + source->read_position += available; + total_read += available; + } + else { + /* Some kind of filesystem or custom source. + * + * Get what we can from header_bytes. We may need to read + * some more after this. + */ + if( source->header_bytes && + source->read_position < source->header_bytes->len ) { + gint64 available = VIPS_MIN( length, + source->header_bytes->len - + source->read_position ); + + VIPS_DEBUG_MSG( " %zd bytes from cache\n", + available ); + memcpy( buffer, + source->header_bytes->data + + source->read_position, + available ); + source->read_position += available; + buffer += available; + length -= available; + total_read += available; + } + + /* Any more bytes requested? Call the read() vfunc. + */ + if( length > 0 ) { + gint64 bytes_read; + + VIPS_DEBUG_MSG( " calling class->read()\n" ); + bytes_read = class->read( source, buffer, length ); + VIPS_DEBUG_MSG( " %zd bytes from read()\n", + bytes_read ); + if( bytes_read == -1 ) { + vips_error_system( errno, + vips_connection_nick( + VIPS_CONNECTION( source ) ), + "%s", _( "read error" ) ); + return( -1 ); + } + + /* We need to save bytes if we're in header mode and + * we can't seek or map. + */ + if( source->header_bytes && + source->is_pipe && + !source->decode && + bytes_read > 0 ) + g_byte_array_append( source->header_bytes, + buffer, bytes_read ); + + source->read_position += bytes_read; + total_read += bytes_read; + } + } + + VIPS_DEBUG_MSG( " %zd bytes total\n", total_read ); + + SANITY( source ); + + return( total_read ); +} + +/* Read to a position. -1 means read to end of source. Does not change + * read_position. + */ +static int +vips_source_pipe_read_to_position( VipsSource *source, gint64 target ) +{ + const char *nick = vips_connection_nick( VIPS_CONNECTION( source ) ); + + gint64 old_read_position; + unsigned char buffer[4096]; + + VIPS_DEBUG_MSG( "vips_source_pipe_read_position: %" G_GINT64_FORMAT + "\n", target ); + + g_assert( !source->decode ); + g_assert( source->header_bytes ); + g_assert( source->is_pipe ); + + if( target != -1 && + (target < 0 || + (source->length != -1 && + target > source->length)) ) { + vips_error( nick, + _( "bad read to %" G_GINT64_FORMAT ), target ); + return( -1 ); + } + + old_read_position = source->read_position; + + while( target == -1 || + source->read_position < target ) { + gint64 bytes_read; + + bytes_read = vips_source_read( source, buffer, 4096 ); + if( bytes_read == -1 ) + return( -1 ); + if( bytes_read == 0 ) + break; + + if( target == -1 && + vips__pipe_read_limit != -1 && + source->read_position > vips__pipe_read_limit ) { + vips_error( nick, "%s", _( "pipe too long" ) ); + return( -1 ); + } + } + + source->read_position = old_read_position; + + return( 0 ); +} + +/* Convert a seekable source that can't be mapped (eg. a custom input with a + * seek method) into a memory source. + */ +static int +vips_source_read_to_memory( VipsSource *source ) +{ + GByteArray *byte_array; + gint64 read_position; + unsigned char *q; + + VIPS_DEBUG_MSG( "vips_source_read_to_memory:\n" ); + + g_assert( !source->is_pipe ); + g_assert( !source->blob ); + g_assert( !source->header_bytes ); + g_assert( source->length >= 0 ); + + if( vips_source_rewind( source ) ) + return( -1 ); + + /* We know the length, so we can size the buffer correctly and read + * directly to it. + */ + byte_array = g_byte_array_new(); + g_byte_array_set_size( byte_array, source->length ); + + /* Read in a series of chunks to reduce stress upstream. + */ + read_position = 0; + q = byte_array->data; + while( read_position < source->length ) { + gint64 bytes_read; + + bytes_read = vips_source_read( source, q, + VIPS_MAX( 4096, source->length - read_position ) ); + if( bytes_read == -1 ) { + VIPS_FREEF( g_byte_array_unref, byte_array ); + return( -1 ); + } + if( bytes_read == 0 ) + break; + + read_position += bytes_read; + q += bytes_read; + } + + /* Steal the byte_array pointer and turn into a memory source. + * + * We save byte_array in the header_bytes field to get it freed when + * we are freed. + */ + source->data = byte_array->data; + source->is_pipe = FALSE; + source->header_bytes = byte_array; + + vips_source_minimise( source ); + + return( 0 ); +} + +/* Read the entire pipe into memory and turn this into a memory source. + */ +static int +vips_source_pipe_to_memory( VipsSource *source ) +{ + VIPS_DEBUG_MSG( "vips_source_pipe_to_memory:\n" ); + + g_assert( source->is_pipe ); + g_assert( !source->blob ); + g_assert( !source->decode ); + g_assert( source->header_bytes ); + + if( vips_source_pipe_read_to_position( source, -1 ) ) + return( -1 ); + + /* Steal the header_bytes pointer and turn into a memory source. + */ + source->length = source->header_bytes->len; + source->data = source->header_bytes->data; + source->is_pipe = FALSE; + + /* TODO ... we could close more fds here. + */ + vips_source_minimise( source ); + + return( 0 ); +} + +static int +vips_source_descriptor_to_memory( VipsSource *source ) +{ + VipsConnection *connection = VIPS_CONNECTION( source ); + + VIPS_DEBUG_MSG( "vips_source_descriptor_to_memory:\n" ); + + g_assert( !source->blob ); + g_assert( !source->mmap_baseaddr ); + + if( !(source->mmap_baseaddr = vips__mmap( connection->descriptor, + FALSE, source->length, 0 )) ) + return( -1 ); + + /* And it's now a memory source. + */ + source->data = source->mmap_baseaddr; + source->mmap_length = source->length; + + return( 0 ); +} + +/** + * vips_source_is_mappable: + * @source: source to operate on + * + * Some sources can be efficiently mapped into memory. + * You can still use vips_source_map() if this function returns %FALSE, + * but it will be slow. + * + * Returns: %TRUE if the source can be efficiently mapped into memory. + */ +gboolean +vips_source_is_mappable( VipsSource *source ) +{ + if( vips_source_unminimise( source ) || + vips_source_test_features( source ) ) + return( -1 ); + + /* Already a memory object, or there's a filename we can map, or + * there's a seekable descriptor. + */ + return( source->data || + VIPS_CONNECTION( source )->filename || + (!source->is_pipe && + VIPS_CONNECTION( source )->descriptor != -1) ); +} + +/** + * vips_source_map: + * @source: source to operate on + * @length_out: return the file length here, or NULL + * + * Map the source entirely into memory and return a pointer to the + * start. If @length_out is non-NULL, the source size is written to it. + * + * This operation can take a long time. Use vips_source_is_mappable() to + * check if a source can be mapped efficiently. + * + * The pointer is valid for as long as @source is alive. + * + * Returns: a pointer to the start of the file contents, or NULL on error. + */ +const void * +vips_source_map( VipsSource *source, size_t *length_out ) +{ + VIPS_DEBUG_MSG( "vips_source_map:\n" ); + + SANITY( source ); + + if( vips_source_unminimise( source ) || + vips_source_test_features( source ) ) + return( NULL ); + + if( !source->data ) { + /* Seekable descriptors can simply be mapped. Seekable sources + * can be read. All other sources must be streamed into memory. + */ + if( vips_source_is_mappable( source ) ) { + if( vips_source_descriptor_to_memory( source ) ) + return( NULL ); + } + else if( !source->is_pipe ) { + if( vips_source_read_to_memory( source ) ) + return( NULL ); + } + else { + if( vips_source_pipe_to_memory( source ) ) + return( NULL ); + } + } + + if( length_out ) + *length_out = source->length; + + SANITY( source ); + + return( source->data ); +} + +static int +vips_source_map_cb( void *a, void *b ) +{ + VipsArea *area = VIPS_AREA( b ); + GObject *gobject = G_OBJECT( area->client ); + + VIPS_UNREF( gobject ); + + return( 0 ); +} + +/** + * vips_source_map_blob: + * @source: source to operate on + * + * Just like vips_source_map(), but return a #VipsBlob containing the + * pointer. @source will stay alive as long as the result is alive. + * + * Returns: a new #VipsBlob containing the data, or NULL on error. + */ +VipsBlob * +vips_source_map_blob( VipsSource *source ) +{ + const void *buf; + size_t len; + VipsBlob *blob; + + if( !(buf = vips_source_map( source, &len )) || + !(blob = vips_blob_new( vips_source_map_cb, buf, len )) ) + return( NULL ); + + /* The source must stay alive until the blob is done. + */ + g_object_ref( source ); + VIPS_AREA( blob )->client = source; + + return( blob ); +} + +/** + * vips_source_seek: + * @source: source to operate on + * @offset: seek by this offset + * @whence: seek relative to this point + * + * Move the file read position. You can't call this after pixel decode starts. + * The arguments are exactly as lseek(2). + * + * Returns: the new file position, or -1 on error. + */ +gint64 +vips_source_seek( VipsSource *source, gint64 offset, int whence ) +{ + const char *nick = vips_connection_nick( VIPS_CONNECTION( source ) ); + VipsSourceClass *class = VIPS_SOURCE_GET_CLASS( source ); + + gint64 new_pos; + + VIPS_DEBUG_MSG( "vips_source_seek: offset = %" G_GINT64_FORMAT + ", whence = %d\n", offset, whence ); + + if( vips_source_unminimise( source ) || + vips_source_test_features( source ) ) + return( -1 ); + + if( source->data ) { + switch( whence ) { + case SEEK_SET: + new_pos = offset; + break; + + case SEEK_CUR: + new_pos = source->read_position + offset; + break; + + case SEEK_END: + new_pos = source->length + offset; + break; + + default: + vips_error( nick, "%s", _( "bad 'whence'" ) ); + return( -1 ); + } + } + else if( source->is_pipe ) { + switch( whence ) { + case SEEK_SET: + new_pos = offset; + break; + + case SEEK_CUR: + new_pos = source->read_position + offset; + break; + + case SEEK_END: + /* We have to read the whole source into memory to get + * the length. + */ + if( source->length == -1 && + vips_source_pipe_to_memory( source ) ) + return( -1 ); + + new_pos = source->length + offset; + break; + + default: + vips_error( nick, "%s", _( "bad 'whence'" ) ); + return( -1 ); + } + } + else { + if( (new_pos = class->seek( source, offset, whence )) == -1 ) + return( -1 ); + } + + /* Don't allow out of range seeks. + */ + if( new_pos < 0 || + (source->length != -1 && + new_pos > source->length) ) { + vips_error( nick, + _( "bad seek to %" G_GINT64_FORMAT ), new_pos ); + return( -1 ); + } + + /* For pipes, we have to fake seek by reading to that point. + */ + if( source->is_pipe && + vips_source_pipe_read_to_position( source, new_pos ) ) + return( -1 ); + + source->read_position = new_pos; + + VIPS_DEBUG_MSG( " new_pos = %" G_GINT64_FORMAT "\n", new_pos ); + + return( new_pos ); +} + +/** + * vips_source_rewind: + * @source: source to operate on + * + * Rewind the source to the start. You can't do this after pixel decode phase + * starts. + * + * Returns: 0 on success, or -1 on error. + */ +int +vips_source_rewind( VipsSource *source ) +{ + VIPS_DEBUG_MSG( "vips_source_rewind:\n" ); + + SANITY( source ); + + if( vips_source_test_features( source ) || + vips_source_seek( source, 0, SEEK_SET ) != 0 ) + return( -1 ); + + SANITY( source ); + + return( 0 ); +} + +/** + * vips_source_length: + * @source: source to operate on + * + * Return the length in bytes of the source. Unseekable sources, for + * example pipes, will have to be read entirely into memory before the length + * can be found, so this operation can take a long time. + * + * Returns: number of bytes in source, or -1 on error. + */ +gint64 +vips_source_length( VipsSource *source ) +{ + gint64 length; + gint64 read_position; + + VIPS_DEBUG_MSG( "vips_source_length:\n" ); + + if( vips_source_test_features( source ) ) + return( -1 ); + + read_position = vips_source_seek( source, 0, SEEK_CUR ); + length = vips_source_seek( source, 0, SEEK_END ); + vips_source_seek( source, read_position, SEEK_SET ); + + return( length ); +} + +/** + * vips_source_sniff_at_most: + * @source: peek this source + * @data: return a pointer to the bytes read here + * @length: max number of bytes to read + * + * Attempt to sniff at most @length bytes from the start of the source. A + * pointer to the bytes is returned in @data. The number of bytes actually + * read is returned -- it may be less than @length if the file is shorter than + * @length. A negative number indicates a read error. + * + * Returns: number of bytes read, or -1 on error. + */ +gint64 +vips_source_sniff_at_most( VipsSource *source, + unsigned char **data, size_t length ) +{ + unsigned char *q; + gint64 read_position; + + VIPS_DEBUG_MSG( "vips_source_sniff_at_most: %zd bytes\n", length ); + + SANITY( source ); + + if( vips_source_test_features( source ) || + vips_source_rewind( source ) ) + return( -1 ); + + g_byte_array_set_size( source->sniff, length ); + + read_position = 0; + q = source->sniff->data; + while( read_position < length ) { + gint64 bytes_read; + + bytes_read = vips_source_read( source, q, + length - read_position ); + if( bytes_read == -1 ) + return( -1 ); + if( bytes_read == 0 ) + break; + + read_position += bytes_read; + q += bytes_read; + } + + SANITY( source ); + + *data = source->sniff->data; + + return( read_position ); +} + +/** + * vips_source_sniff: + * @source: sniff this source + * @length: number of bytes to sniff + * + * Return a pointer to the first few bytes of the file. If the file is too + * short, return NULL. + * + * Returns: a pointer to the bytes at the start of the file, or NULL on error. + */ +unsigned char * +vips_source_sniff( VipsSource *source, size_t length ) +{ + unsigned char *data; + gint64 bytes_read; + + if( vips_source_test_features( source ) ) + return( NULL ); + + bytes_read = vips_source_sniff_at_most( source, &data, length ); + if( bytes_read == -1 ) + return( NULL ); + if( bytes_read < length ) + return( NULL ); + + return( data ); +} diff --git a/libvips/iofuncs/sourcecustom.c b/libvips/iofuncs/sourcecustom.c new file mode 100644 index 00000000..78e0e2fe --- /dev/null +++ b/libvips/iofuncs/sourcecustom.c @@ -0,0 +1,240 @@ +/* A Source subclass with signals you can easily hook up to other input + * sources. + * + * J.Cupitt, 21/11/19 + */ + +/* + + 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 VIPS_DEBUG + */ + +#ifdef HAVE_CONFIG_H +#include +#endif /*HAVE_CONFIG_H*/ +#include + +#include +#include +#ifdef HAVE_UNISTD_H +#include +#endif /*HAVE_UNISTD_H*/ +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "vipsmarshal.h" + +G_DEFINE_TYPE( VipsSourceCustom, vips_source_custom, VIPS_TYPE_SOURCE ); + +/* Our signals. + */ +enum { + SIG_SEEK, + SIG_READ, + SIG_LAST +}; + +static guint vips_source_custom_signals[SIG_LAST] = { 0 }; + +static gint64 +vips_source_custom_read_real( VipsSource *source, + void *buffer, size_t length ) +{ + gint64 bytes_read; + + VIPS_DEBUG_MSG( "vips_source_custom_read_real:\n" ); + + /* Return this value (error) if there's no attached handler. + */ + bytes_read = 0; + + g_signal_emit( source, vips_source_custom_signals[SIG_READ], 0, + buffer, (gint64) length, &bytes_read ); + + VIPS_DEBUG_MSG( " vips_source_custom_read_real, seen %zd bytes\n", + bytes_read ); + + return( bytes_read ); +} + +static gint64 +vips_source_custom_seek_real( VipsSource *source, + gint64 offset, int whence ) +{ + GValue args[3] = { { 0 } }; + GValue result = { 0 }; + gint64 new_position; + + VIPS_DEBUG_MSG( "vips_source_custom_seek_real:\n" ); + + /* Set the signal args. + */ + g_value_init( &args[0], G_TYPE_OBJECT ); + g_value_set_object( &args[0], source ); + g_value_init( &args[1], G_TYPE_INT64 ); + g_value_set_int64( &args[1], offset ); + g_value_init( &args[2], G_TYPE_INT ); + g_value_set_int( &args[2], whence ); + + /* Set the default value if no handlers are attached. + */ + g_value_init( &result, G_TYPE_INT64 ); + g_value_set_int64( &result, -1 ); + + /* We need to use this signal interface since we want a default value + * if no handlers are attached. + */ + g_signal_emitv( (const GValue *) &args, + vips_source_custom_signals[SIG_SEEK], 0, &result ); + + new_position = g_value_get_int64( &result ); + + g_value_unset( &args[0] ); + g_value_unset( &args[1] ); + g_value_unset( &args[2] ); + g_value_unset( &result ); + + VIPS_DEBUG_MSG( " vips_source_custom_seek_real, seen new pos %zd\n", + new_position ); + + return( new_position ); +} + +static gint64 +vips_source_custom_read_signal_real( VipsSourceCustom *source_custom, + void *data, gint64 length ) +{ + VIPS_DEBUG_MSG( "vips_source_custom_read_signal_real:\n" ); + + return( 0 ); +} + +static gint64 +vips_source_custom_seek_signal_real( VipsSourceCustom *source_custom, + gint64 offset, int whence ) +{ + VIPS_DEBUG_MSG( "vips_source_custom_seek_signal_real:\n" ); + + return( -1 ); +} + +static void +vips_source_custom_class_init( VipsSourceCustomClass *class ) +{ + VipsObjectClass *object_class = VIPS_OBJECT_CLASS( class ); + VipsSourceClass *source_class = VIPS_SOURCE_CLASS( class ); + + object_class->nickname = "source_custom"; + object_class->description = _( "Custom source" ); + + source_class->read = vips_source_custom_read_real; + source_class->seek = vips_source_custom_seek_real; + + class->read = vips_source_custom_read_signal_real; + class->seek = vips_source_custom_seek_signal_real; + + /** + * VipsSourceCustom::read: + * @source_custom: the source being operated on + * @buffer: %gpointer, buffer to fill + * @size: %gint64, size of buffer + * + * This signal is emitted to read bytes from the source into @buffer. + * + * Returns: the number of bytes read. + */ + vips_source_custom_signals[SIG_READ] = g_signal_new( "read", + G_TYPE_FROM_CLASS( class ), + G_SIGNAL_ACTION, + G_STRUCT_OFFSET( VipsSourceCustomClass, read ), + NULL, NULL, + vips_INT64__POINTER_INT64, + G_TYPE_INT64, 2, + G_TYPE_POINTER, G_TYPE_INT64 ); + + /** + * VipsSourceCustom::seek: + * @source_custom: the source being operated on + * @offset: %gint64, seek offset + * @whence: %gint, seek origin + * + * This signal is emitted to seek the source. The handler should + * change the source position appropriately. + * + * The handler for an unseekable source should always return -1. + * + * Returns: the new seek position. + */ + vips_source_custom_signals[SIG_SEEK] = g_signal_new( "seek", + G_TYPE_FROM_CLASS( class ), + G_SIGNAL_ACTION, + G_STRUCT_OFFSET( VipsSourceCustomClass, seek ), + NULL, NULL, + vips_INT64__INT64_INT, + G_TYPE_INT64, 2, + G_TYPE_INT64, G_TYPE_INT ); + +} + +static void +vips_source_custom_init( VipsSourceCustom *source_custom ) +{ +} + +/** + * vips_source_custom_new: + * + * Create a #VipsSourceCustom. Attach signals to implement read and seek. + * + * Returns: a new #VipsSourceCustom + */ +VipsSourceCustom * +vips_source_custom_new( void ) +{ + VipsSourceCustom *source_custom; + + VIPS_DEBUG_MSG( "vips_source_custom_new:\n" ); + + source_custom = VIPS_SOURCE_CUSTOM( g_object_new( VIPS_TYPE_SOURCE_CUSTOM, NULL ) ); + + if( vips_object_build( VIPS_OBJECT( source_custom ) ) ) { + VIPS_UNREF( source_custom ); + return( NULL ); + } + + return( source_custom ); +} diff --git a/libvips/iofuncs/target.c b/libvips/iofuncs/target.c new file mode 100644 index 00000000..11ca2fc6 --- /dev/null +++ b/libvips/iofuncs/target.c @@ -0,0 +1,610 @@ +/* A byte source/sink .. it can be a pipe, file descriptor, memory area, + * socket, node.js stream, etc. + * + * J.Cupitt, 19/6/14 + */ + +/* + + 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 VIPS_DEBUG + */ + +#ifdef HAVE_CONFIG_H +#include +#endif /*HAVE_CONFIG_H*/ +#include + +#include +#include +#ifdef HAVE_UNISTD_H +#include +#endif /*HAVE_UNISTD_H*/ +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/* Try to make an O_BINARY ... sometimes need the leading '_'. + */ +#ifdef BINARY_OPEN +#ifndef O_BINARY +#ifdef _O_BINARY +#define O_BINARY _O_BINARY +#endif /*_O_BINARY*/ +#endif /*!O_BINARY*/ +#endif /*BINARY_OPEN*/ + +/* If we have O_BINARY, add it to a mode flags set. + */ +#ifdef O_BINARY +#define BINARYIZE(M) ((M) | O_BINARY) +#else /*!O_BINARY*/ +#define BINARYIZE(M) (M) +#endif /*O_BINARY*/ + +#define MODE_READ BINARYIZE (O_RDONLY) +#define MODE_READWRITE BINARYIZE (O_RDWR) +#define MODE_WRITE BINARYIZE (O_WRONLY | O_CREAT | O_TRUNC) + +G_DEFINE_TYPE( VipsTarget, vips_target, VIPS_TYPE_CONNECTION ); + +static void +vips_target_finalize( GObject *gobject ) +{ + VipsTarget *target = VIPS_TARGET( gobject ); + + VIPS_DEBUG_MSG( "vips_target_finalize:\n" ); + + VIPS_FREEF( g_byte_array_unref, target->memory_buffer ); + if( target->blob ) { + vips_area_unref( VIPS_AREA( target->blob ) ); + target->blob = NULL; + } + + G_OBJECT_CLASS( vips_target_parent_class )->finalize( gobject ); +} + +static int +vips_target_build( VipsObject *object ) +{ + VipsConnection *connection = VIPS_CONNECTION( object ); + VipsTarget *target = VIPS_TARGET( object ); + + VIPS_DEBUG_MSG( "vips_target_build: %p\n", connection ); + + if( VIPS_OBJECT_CLASS( vips_target_parent_class )->build( object ) ) + return( -1 ); + + if( vips_object_argument_isset( object, "filename" ) && + vips_object_argument_isset( object, "descriptor" ) ) { + vips_error( vips_connection_nick( connection ), + "%s", _( "don't set 'filename' and 'descriptor'" ) ); + return( -1 ); + } + + if( connection->filename ) { + const char *filename = connection->filename; + + int fd; + + /* 0644 is rw user, r group and other. + */ + if( (fd = vips_tracked_open( filename, + MODE_WRITE, 0644 )) == -1 ) { + vips_error_system( errno, + vips_connection_nick( connection ), + "%s", _( "unable to open for write" ) ); + return( -1 ); + } + + connection->tracked_descriptor = fd; + connection->descriptor = fd; + } + else if( vips_object_argument_isset( object, "descriptor" ) ) { + connection->descriptor = dup( connection->descriptor ); + connection->close_descriptor = connection->descriptor; + } + else if( target->memory ) { + target->memory_buffer = g_byte_array_new(); + } + + return( 0 ); +} + +static gint64 +vips_target_write_real( VipsTarget *target, const void *data, size_t length ) +{ + VipsConnection *connection = VIPS_CONNECTION( target ); + + VIPS_DEBUG_MSG( "vips_target_write_real: %zd bytes\n", length ); + + return( write( connection->descriptor, data, length ) ); +} + +static void +vips_target_finish_real( VipsTarget *target ) +{ + VIPS_DEBUG_MSG( "vips_target_finish_real:\n" ); +} + +static void +vips_target_class_init( VipsTargetClass *class ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( class ); + VipsObjectClass *object_class = VIPS_OBJECT_CLASS( class ); + + gobject_class->finalize = vips_target_finalize; + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + object_class->nickname = "target"; + object_class->description = _( "Target" ); + + object_class->build = vips_target_build; + + class->write = vips_target_write_real; + class->finish = vips_target_finish_real; + + VIPS_ARG_BOOL( class, "memory", 3, + _( "Memory" ), + _( "File descriptor should output to memory" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsTarget, memory ), + FALSE ); + + /* SET_ALWAYS means that blob is set by C and the obj system is not + * involved in creation or destruction. It can be read at any time. + */ + VIPS_ARG_BOXED( class, "blob", 4, + _( "Blob" ), + _( "Blob to save to" ), + VIPS_ARGUMENT_SET_ALWAYS, + G_STRUCT_OFFSET( VipsTarget, blob ), + VIPS_TYPE_BLOB ); + +} + +static void +vips_target_init( VipsTarget *target ) +{ + target->blob = vips_blob_new( NULL, NULL, 0 ); + target->write_point = 0; +} + +/** + * vips_target_new_to_descriptor: + * @descriptor: write to this file descriptor + * + * Create a target attached to a file descriptor. + * @descriptor is kept open until the target is finalized. + * + * See also: vips_target_new_to_file(). + * + * Returns: a new target. + */ +VipsTarget * +vips_target_new_to_descriptor( int descriptor ) +{ + VipsTarget *target; + + VIPS_DEBUG_MSG( "vips_target_new_to_descriptor: %d\n", + descriptor ); + + target = VIPS_TARGET( g_object_new( VIPS_TYPE_TARGET, + "descriptor", descriptor, + NULL ) ); + + if( vips_object_build( VIPS_OBJECT( target ) ) ) { + VIPS_UNREF( target ); + return( NULL ); + } + + return( target ); +} + +/** + * vips_target_new_to_file: + * @filename: write to this file + * + * Create a target attached to a file. + * + * Returns: a new target. + */ +VipsTarget * +vips_target_new_to_file( const char *filename ) +{ + VipsTarget *target; + + VIPS_DEBUG_MSG( "vips_target_new_to_file: %s\n", + filename ); + + target = VIPS_TARGET( g_object_new( VIPS_TYPE_TARGET, + "filename", filename, + NULL ) ); + + if( vips_object_build( VIPS_OBJECT( target ) ) ) { + VIPS_UNREF( target ); + return( NULL ); + } + + return( target ); +} + +/** + * vips_target_new_to_memory: + * + * Create a target which will write to a memory area. Read from @blob to get + * memory. + * + * See also: vips_target_new_to_file(). + * + * Returns: a new #VipsConnection + */ +VipsTarget * +vips_target_new_to_memory( void ) +{ + VipsTarget *target; + + VIPS_DEBUG_MSG( "vips_target_new_to_memory:\n" ); + + target = VIPS_TARGET( g_object_new( VIPS_TYPE_TARGET, + "memory", TRUE, + NULL ) ); + + if( vips_object_build( VIPS_OBJECT( target ) ) ) { + VIPS_UNREF( target ); + return( NULL ); + } + + return( target ); +} + +static int +vips_target_write_unbuffered( VipsTarget *target, + const void *data, size_t length ) +{ + VipsTargetClass *class = VIPS_TARGET_GET_CLASS( target ); + + VIPS_DEBUG_MSG( "vips_target_write_unbuffered:\n" ); + + if( target->finished ) + return( 0 ); + + if( target->memory_buffer ) + g_byte_array_append( target->memory_buffer, data, length ); + else + while( length > 0 ) { + gint64 bytes_written; + + bytes_written = class->write( target, data, length ); + + /* n == 0 isn't strictly an error, but we treat it as + * one to make sure we don't get stuck in this loop. + */ + if( bytes_written <= 0 ) { + vips_error_system( errno, + vips_connection_nick( + VIPS_CONNECTION( target ) ), + "%s", _( "write error" ) ); + return( -1 ); + } + + length -= bytes_written; + data += bytes_written; + } + + return( 0 ); +} + +static int +vips_target_flush( VipsTarget *target ) +{ + g_assert( target->write_point >= 0 ); + g_assert( target->write_point <= VIPS_TARGET_BUFFER_SIZE ); + + VIPS_DEBUG_MSG( "vips_target_flush:\n" ); + + if( target->write_point > 0 ) { + if( vips_target_write_unbuffered( target, + target->output_buffer, target->write_point ) ) + return( -1 ); + target->write_point = 0; + } + + return( 0 ); +} + +/** + * vips_target_write: + * @target: target to operate on + * @buffer: bytes to write + * @length: length of @buffer in bytes + * + * Write @length bytes from @buffer to the output. + * + * Returns: 0 on success, -1 on error. + */ +int +vips_target_write( VipsTarget *target, const void *buffer, size_t length ) +{ + VIPS_DEBUG_MSG( "vips_target_write: %zd bytes\n", length ); + + if( length > VIPS_TARGET_BUFFER_SIZE - target->write_point && + vips_target_flush( target ) ) + return( -1 ); + + if( length > VIPS_TARGET_BUFFER_SIZE - target->write_point ) { + /* Still too large? Do an unbuffered write. + */ + if( vips_target_write_unbuffered( target, buffer, length ) ) + return( -1 ); + } + else { + memcpy( target->output_buffer + target->write_point, + buffer, length ); + target->write_point += length; + } + + return( 0 ); +} + +/** + * vips_target_finish: + * @target: target to operate on + * @buffer: bytes to write + * @length: length of @buffer in bytes + * + * Call this at the end of write to make the target do any cleaning up. You + * can call it many times. + * + * After a target has been finished, further writes will do nothing. + */ +void +vips_target_finish( VipsTarget *target ) +{ + VipsTargetClass *class = VIPS_TARGET_GET_CLASS( target ); + + VIPS_DEBUG_MSG( "vips_target_finish:\n" ); + + if( target->finished ) + return; + + (void) vips_target_flush( target ); + + /* Move the target buffer into the blob so it can be read out. + */ + if( target->memory_buffer ) { + unsigned char *data; + size_t length; + + length = target->memory_buffer->len; + data = g_byte_array_free( target->memory_buffer, FALSE ); + target->memory_buffer = NULL; + vips_blob_set( target->blob, + (VipsCallbackFn) g_free, data, length ); + } + else + class->finish( target ); + + target->finished = TRUE; +} + +/** + * vips_target_steal: + * @target: target to operate on + * @length: return number of bytes of data + * + * Memory targets only (see vips_target_new_to_memory()). Steal all data + * written to the target so far, and finish it. + * + * You must free the returned pointer with g_free(). + * + * The data is NOT automatically null-terminated. vips_target_putc() a '\0' + * before calling this to get a null-terminated string. + * + * Returns: (array length=length) (element-type guint8) (transfer full): the + * data + */ +unsigned char * +vips_target_steal( VipsTarget *target, size_t *length ) +{ + unsigned char *data; + + (void) vips_target_flush( target ); + + if( !target->memory_buffer || + target->finished ) { + if( length ) + *length = target->memory_buffer->len; + + return( NULL ); + } + + if( length ) + *length = target->memory_buffer->len; + data = g_byte_array_free( target->memory_buffer, FALSE ); + target->memory_buffer = NULL; + + /* We must have a valid byte array or finish will fail. + */ + target->memory_buffer = g_byte_array_new(); + + vips_target_finish( target ); + + return( data ); +} + +/** + * vips_target_steal_text: + * @target: target to operate on + * + * As vips_target_steal_text(), but return a null-terminated string. + * + * Returns: (transfer full): target contents as a null-terminated string. + */ +char * +vips_target_steal_text( VipsTarget *target ) +{ + vips_target_putc( target, '\0' ); + + return( (char *) vips_target_steal( target, NULL ) ); +} + +/** + * vips_target_putc: + * @target: target to operate on + * @ch: character to write + * + * Write a single character @ch to @target. See the macro VIPS_TARGET_PUTC() + * for a faster way to do this. + * + * Returns: 0 on success, -1 on error. + */ +int +vips_target_putc( VipsTarget *target, int ch ) +{ + VIPS_DEBUG_MSG( "vips_target_putc: %d\n", ch ); + + if( target->write_point >= VIPS_TARGET_BUFFER_SIZE && + vips_target_flush( target ) ) + return( -1 ); + + target->output_buffer[target->write_point++] = ch; + + return( 0 ); +} + +/** + * vips_target_writes: + * @target: target to operate on + * @str: string to write + * + * Write a null-terminated string to @target. + * + * Returns: 0 on success, and -1 on error. + */ +int +vips_target_writes( VipsTarget *target, const char *str ) +{ + return( vips_target_write( target, + (unsigned char *) str, strlen( str ) ) ); +} + +/** + * vips_target_writef: + * @target: target to operate on + * @fmt: printf()-style format string + * @...: arguments to format string + * + * Format the string and write to @target. + * + * Returns: 0 on success, and -1 on error. + */ +int +vips_target_writef( VipsTarget *target, const char *fmt, ... ) +{ + va_list ap; + char *line; + int result; + + va_start( ap, fmt ); + line = g_strdup_vprintf( fmt, ap ); + va_end( ap ); + + result = vips_target_writes( target, line ); + + g_free( line ); + + return( result ); +} + +/** + * vips_target_write_amp: + * @target: target to operate on + * @str: string to write + * + * Write @str to @target, but escape stuff that xml hates in text. Our + * argument string is utf-8. + * + * XML rules: + * + * - We must escape &<> + * - Don't escape \n, \t, \r + * - Do escape the other ASCII codes. + * + * Returns: 0 on success, -1 on error. + */ +int +vips_target_write_amp( VipsTarget *target, const char *str ) +{ + const char *p; + + for( p = str; *p; p++ ) + if( *p < 32 && + *p != '\n' && + *p != '\t' && + *p != '\r' ) { + /* You'd think we could output "%x;", but xml + * 1.0 parsers barf on that. xml 1.1 allows this, but + * there are almost no parsers. + * + * U+2400 onwards are unicode glyphs for the ASCII + * control characters, so we can use them -- thanks + * electroly. + */ + if( vips_target_writef( target, + "&#x%04x;", 0x2400 + *p ) ) + return( -1 ); + } + else if( *p == '<' ) { + if( vips_target_writes( target, "<" ) ) + return( -1 ); + } + else if( *p == '>' ) { + if( vips_target_writes( target, ">" ) ) + return( -1 ); + } + else if( *p == '&' ) { + if( vips_target_writes( target, "&" ) ) + return( -1 ); + } + else { + if( VIPS_TARGET_PUTC( target, *p ) ) + return( -1 ); + } + + return( 0 ); +} + diff --git a/libvips/iofuncs/targetcustom.c b/libvips/iofuncs/targetcustom.c new file mode 100644 index 00000000..c16200a5 --- /dev/null +++ b/libvips/iofuncs/targetcustom.c @@ -0,0 +1,194 @@ +/* A Target subclass with signals you can easily hook up to other output + * sources. + * + * J.Cupitt, 21/11/19 + */ + +/* + + 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 VIPS_DEBUG + */ + +#ifdef HAVE_CONFIG_H +#include +#endif /*HAVE_CONFIG_H*/ +#include + +#include +#include +#ifdef HAVE_UNISTD_H +#include +#endif /*HAVE_UNISTD_H*/ +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "vipsmarshal.h" + +G_DEFINE_TYPE( VipsTargetCustom, vips_target_custom, VIPS_TYPE_TARGET ); + +/* Our signals. + */ +enum { + SIG_WRITE, + SIG_FINISH, + SIG_LAST +}; + +static guint vips_target_custom_signals[SIG_LAST] = { 0 }; + +static gint64 +vips_target_custom_write_real( VipsTarget *target, + const void *data, size_t length ) +{ + gint64 bytes_written; + + VIPS_DEBUG_MSG( "vips_target_custom_write_real:\n" ); + + /* Return value if no attached handler. + */ + bytes_written = 0; + + g_signal_emit( target, vips_target_custom_signals[SIG_WRITE], 0, + data, (gint64) length, &bytes_written ); + + VIPS_DEBUG_MSG( " %zd\n", bytes_written ); + + return( bytes_written ); +} + +static void +vips_target_custom_finish_real( VipsTarget *target ) +{ + VIPS_DEBUG_MSG( "vips_target_custom_seek_real:\n" ); + + g_signal_emit( target, vips_target_custom_signals[SIG_FINISH], 0 ); +} + +static gint64 +vips_target_custom_write_signal_real( VipsTargetCustom *target_custom, + const void *data, gint64 length ) +{ + VIPS_DEBUG_MSG( "vips_target_custom_write_signal_real:\n" ); + + return( 0 ); +} + +static void +vips_target_custom_finish_signal_real( VipsTargetCustom *target_custom ) +{ + VIPS_DEBUG_MSG( "vips_target_custom_finish_signal_real:\n" ); +} + +static void +vips_target_custom_class_init( VipsTargetCustomClass *class ) +{ + VipsObjectClass *object_class = VIPS_OBJECT_CLASS( class ); + VipsTargetClass *target_class = VIPS_TARGET_CLASS( class ); + + object_class->nickname = "target_custom"; + object_class->description = _( "Custom target" ); + + target_class->write = vips_target_custom_write_real; + target_class->finish = vips_target_custom_finish_real; + + class->write = vips_target_custom_write_signal_real; + class->finish = vips_target_custom_finish_signal_real; + + /** + * VipsTargetCustom::write: + * @target_custom: the target being operated on + * @data: %pointer, bytes to write + * @length: %gint64, number of bytes + * + * This signal is emitted to write bytes to the target. + * + * Returns: the number of bytes written. + */ + vips_target_custom_signals[SIG_WRITE] = g_signal_new( "write", + G_TYPE_FROM_CLASS( class ), + G_SIGNAL_ACTION, + G_STRUCT_OFFSET( VipsTargetCustomClass, write ), + NULL, NULL, + vips_INT64__POINTER_INT64, + G_TYPE_INT64, 2, + G_TYPE_POINTER, G_TYPE_INT64 ); + + /** + * VipsTargetCustom::finish: + * @target_custom: the target being operated on + * + * This signal is emitted at the end of write. The target should do + * any finishing necessary. + */ + vips_target_custom_signals[SIG_FINISH] = g_signal_new( "finish", + G_TYPE_FROM_CLASS( class ), + G_SIGNAL_ACTION, + G_STRUCT_OFFSET( VipsTargetCustomClass, finish ), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0 ); + +} + +static void +vips_target_custom_init( VipsTargetCustom *target_custom ) +{ +} + +/** + * vips_target_custom_new: + * + * Create a #VipsTargetCustom. Attach signals to implement write and finish. + * + * Returns: a new #VipsTargetCustom + */ +VipsTargetCustom * +vips_target_custom_new( void ) +{ + VipsTargetCustom *target_custom; + + VIPS_DEBUG_MSG( "vips_target_custom_new:\n" ); + + target_custom = VIPS_TARGET_CUSTOM( g_object_new( VIPS_TYPE_TARGET_CUSTOM, NULL ) ); + + if( vips_object_build( VIPS_OBJECT( target_custom ) ) ) { + VIPS_UNREF( target_custom ); + return( NULL ); + } + + return( target_custom ); +} diff --git a/libvips/iofuncs/threadpool.c b/libvips/iofuncs/threadpool.c index b815d605..92497af3 100644 --- a/libvips/iofuncs/threadpool.c +++ b/libvips/iofuncs/threadpool.c @@ -85,6 +85,7 @@ * @stability: Stable * @see_also: generate * @include: vips/vips.h + * @title: VipsThreadpool * * vips_threadpool_run() loops a set of threads over an image. Threads take it * in turns to allocate units of work (a unit might be a tile in an image), @@ -314,6 +315,13 @@ vips_concurrency_set( int concurrency ) static int get_num_processors( void ) { +#if GLIB_CHECK_VERSION( 2, 48, 1 ) + /* We could use g_get_num_processors when GLib >= 2.48.1, see: + * https://gitlab.gnome.org/GNOME/glib/commit/999711abc82ea3a698d05977f9f91c0b73957f7f + * https://gitlab.gnome.org/GNOME/glib/commit/2149b29468bb99af3c29d5de61f75aad735082dc + */ + return( g_get_num_processors() ); +#else int nproc; nproc = 1; @@ -354,24 +362,33 @@ get_num_processors( void ) { /* Count the CPUs currently available to this process. */ + SYSTEM_INFO sysinfo; DWORD_PTR process_cpus; DWORD_PTR system_cpus; + /* This *never* fails, use it as fallback + */ + GetNativeSystemInfo( &sysinfo ); + nproc = (int) sysinfo.dwNumberOfProcessors; + if( GetProcessAffinityMask( GetCurrentProcess(), &process_cpus, &system_cpus ) ) { - unsigned int count; + unsigned int af_count; - for( count = 0; process_cpus != 0; process_cpus >>= 1 ) + for( af_count = 0; process_cpus != 0; process_cpus >>= 1 ) if( process_cpus & 1 ) - count++; + af_count++; - if( count > 0 ) - nproc = count; + /* Prefer affinity-based result, if available + */ + if( af_count > 0 ) + nproc = af_count; } } #endif /*OS_WIN32*/ return( nproc ); +#endif /*!GLIB_CHECK_VERSION( 2, 48, 1 )*/ } /** @@ -409,8 +426,12 @@ vips_concurrency_get( void ) */ if( vips__concurrency > 0 ) nthr = vips__concurrency; +#if VIPS_ENABLE_DEPRECATED else if( ((str = g_getenv( "VIPS_CONCURRENCY" )) || (str = g_getenv( "IM_CONCURRENCY" ))) && +#else + else if( (str = g_getenv( "VIPS_CONCURRENCY" )) && +#endif (x = atoi( str )) > 0 ) nthr = x; else diff --git a/libvips/iofuncs/type.c b/libvips/iofuncs/type.c index d28282b3..810e8be5 100644 --- a/libvips/iofuncs/type.c +++ b/libvips/iofuncs/type.c @@ -171,6 +171,18 @@ vips_area_copy( VipsArea *area ) return( area ); } +void +vips_area_free( VipsArea *area ) +{ + if( area->free_fn && + area->data ) { + area->free_fn( area->data, area ); + area->free_fn = NULL; + } + + area->data = NULL; +} + void vips_area_unref( VipsArea *area ) { @@ -191,11 +203,7 @@ vips_area_unref( VipsArea *area ) } if( area->count == 0 ) { - if( area->free_fn && area->data ) { - area->free_fn( area->data, area ); - area->data = NULL; - area->free_fn = NULL; - } + vips_area_free( area ); g_mutex_unlock( area->lock ); @@ -662,7 +670,8 @@ vips_blob_copy( const void *data, size_t length ) * * See also: vips_blob_new(). * - * Returns: (array length=length) (element-type guint8) (transfer none): the data + * Returns: (array length=length) (element-type guint8) (transfer none): the + * data */ const void * vips_blob_get( VipsBlob *blob, size_t *length ) @@ -671,6 +680,36 @@ vips_blob_get( VipsBlob *blob, size_t *length ) length, NULL, NULL, NULL ) ); } +/* vips_blob_set: + * @blob: #VipsBlob to set + * @free_fn: (scope async) (allow-none): @data will be freed with this function + * @data: (array length=length) (element-type guint8) (transfer full): data to store + * @length: number of bytes in @data + * + * Any old data is freed and new data attached. + * + * It's sometimes useful to be able to create blobs as empty and then fill + * them later. + * + * See also: vips_blob_new(). + */ +void +vips_blob_set( VipsBlob *blob, + VipsCallbackFn free_fn, const void *data, size_t length ) +{ + VipsArea *area = VIPS_AREA( blob ); + + g_mutex_lock( area->lock ); + + vips_area_free( area ); + + area->free_fn = free_fn; + area->length = length; + area->data = (void *) data; + + g_mutex_unlock( area->lock ); +} + /* Transform a blob to a G_TYPE_STRING. */ static void @@ -696,7 +735,7 @@ transform_blob_save_string( const GValue *src_value, GValue *dest_value ) char *b64; blob = vips_value_get_blob( src_value, &length ); - if( (b64 = vips__b64_encode( blob, length )) ) { + if( (b64 = g_base64_encode( blob, length )) ) { vips_value_set_save_string( dest_value, b64 ); vips_free( b64 ); } @@ -715,7 +754,7 @@ transform_save_string_blob( const GValue *src_value, GValue *dest_value ) size_t length; b64 = vips_value_get_save_string( src_value ); - if( (blob = vips__b64_decode( b64, &length )) ) + if( (blob = g_base64_decode( b64, &length )) ) vips_value_set_blob( dest_value, (VipsCallbackFn) vips_free, blob, length ); else diff --git a/libvips/iofuncs/util.c b/libvips/iofuncs/util.c index efb4643e..f99deb81 100644 --- a/libvips/iofuncs/util.c +++ b/libvips/iofuncs/util.c @@ -357,10 +357,46 @@ vips_isprefix( const char *a, const char *b ) return( TRUE ); } +/* Exactly like strcspn(), but allow \ as an escape character. + * + * strspne( "hello world", " " ) == 5 + * strspne( "hello\\ world", " " ) == 12 + */ +static size_t +strcspne( const char *s, const char *reject ) +{ + size_t skip; + + /* If \ is one of the reject chars, no need for any looping. + */ + if( strchr( reject, '\\' ) ) + return( strcspn( s, reject ) ); + + skip = 0; + for(;;) { + skip += strcspn( s + skip, reject ); + + /* s[skip] is at the start of the string, or the end, or on a + * break character. + */ + if( skip == 0 || + !s[skip] || + s[skip - 1] != '\\' ) + break; + + /* So skip points at break char and we have a '\' in the char + * before. Step over the break. + */ + skip += 1; + } + + return( skip ); +} + /* Like strtok(). Give a string and a list of break characters. Then: * - skip initial break characters * - EOS? return NULL - * - skip a series of non-break characters + * - skip a series of non-break characters, allow `\` as a break escape * - write a '\0' over the next break character and return a pointer to the * char after that * @@ -388,15 +424,22 @@ vips_isprefix( const char *a, const char *b ) * * for( i = 0; p; p = vips_break_token( p, " " ) ) * v[i] = atoi( p ); + * + * You can use \ to escape breaks, for example: + * + * vips_break_token( "hello\ world", " " ) will see a single token containing + * a space. The \ characters are squashed out. */ char * vips_break_token( char *str, const char *brk ) { char *p; + char *q; /* Is the string empty? If yes, return NULL immediately. */ - if( !str || !*str ) + if( !str || + !*str ) return( NULL ); /* Skip initial break characters. @@ -409,9 +452,9 @@ vips_break_token( char *str, const char *brk ) return( NULL ); /* We have a token ... search for the first break character after the - * token. + * token. strcspne() allows '\' to escape breaks, see above. */ - p += strcspn( p, brk ); + p += strcspne( p, brk ); /* Is there string left? */ @@ -423,6 +466,17 @@ vips_break_token( char *str, const char *brk ) p += strspn( p, brk ); } + /* There may be escaped break characters in str. Loop, squashing them + * out. + */ + for( q = strchr( str, '\\' ); q && *q; q = strchr( q, '\\' ) ) { + memmove( q, q + 1, strlen( q ) ); + + /* If there's \\, we don't want to squash out the second \. + */ + q += 1; + } + return( p ); } @@ -444,8 +498,8 @@ vips_vsnprintf( char *str, size_t size, const char *format, va_list ap ) */ if( size > MAX_BUF ) vips_error_exit( "panic: buffer overflow " - "(request to write %d bytes to buffer of %d bytes)", - size, MAX_BUF ); + "(request to write %lu bytes to buffer of %d bytes)", + (unsigned long) size, MAX_BUF ); n = vsprintf( buf, format, ap ); if( n > MAX_BUF ) vips_error_exit( "panic: buffer overflow " @@ -570,15 +624,17 @@ vips__set_create_time( int fd ) /* open() with a utf8 filename, setting errno. */ int -vips__open( const char *filename, int flags, ... ) +vips__open( const char *filename, int flags, mode_t mode ) { int fd; - mode_t mode; - va_list ap; - va_start( ap, flags ); - mode = va_arg( ap, int ); - va_end( ap ); + /* Various bad things happen if you accidentally open a directory as a + * file. + */ + if( g_file_test( filename, G_FILE_TEST_IS_DIR ) ) { + errno = EISDIR; + return( -1 ); + } fd = g_open( filename, flags, mode ); @@ -593,7 +649,7 @@ vips__open( const char *filename, int flags, ... ) int vips__open_read( const char *filename ) { - return( vips__open( filename, MODE_READONLY ) ); + return( vips__open( filename, MODE_READONLY, 0 ) ); } /* fopen() with utf8 filename and mode, setting errno. @@ -727,8 +783,11 @@ vips__file_read( FILE *fp, const char *filename, size_t *length_out ) do { char *str2; + /* Again, a 1gb sanity limit. + */ size += 1024; - if( !(str2 = realloc( str, size )) ) { + if( size > 1024 * 1024 * 1024 || + !(str2 = realloc( str, size )) ) { free( str ); vips_error( "vips__file_read", "%s", _( "out of memory" ) ); @@ -817,13 +876,13 @@ vips__file_write( void *data, size_t size, size_t nmemb, FILE *stream ) * types, so we must read binary. * * Return the number of bytes actually read (the file might be shorter than - * len), or 0 for error. + * len), or -1 for error. */ -guint64 -vips__get_bytes( const char *filename, unsigned char buf[], guint64 len ) +gint64 +vips__get_bytes( const char *filename, unsigned char buf[], gint64 len ) { int fd; - guint64 bytes_read; + gint64 bytes_read; /* File may not even exist (for tmp images for example!) * so no hasty messages. And the file might be truncated, so no error @@ -1025,31 +1084,38 @@ vips__gslist_gvalue_get( const GSList *list ) return( all ); } +gint64 +vips__seek_no_error( int fd, gint64 pos, int whence ) +{ + gint64 new_pos; + +#ifdef OS_WIN32 + new_pos = _lseeki64( fd, pos, whence ); +#else /*!OS_WIN32*/ + /* On error, eg. opening a directory and seeking to the end, lseek() + * on linux seems to return 9223372036854775807 ((1 << 63) - 1) + * rather than (off_t) -1 for reasons I don't understand. + */ + new_pos = lseek( fd, pos, whence ); +#endif /*OS_WIN32*/ + + return( new_pos ); +} + /* Need our own seek(), since lseek() on win32 can't do long files. */ -int -vips__seek( int fd, gint64 pos ) +gint64 +vips__seek( int fd, gint64 pos, int whence ) { -#ifdef OS_WIN32 -{ - HANDLE hFile = (HANDLE) _get_osfhandle( fd ); - LARGE_INTEGER p; + gint64 new_pos; - p.QuadPart = pos; - if( !SetFilePointerEx( hFile, p, NULL, FILE_BEGIN ) ) { - vips_error_system( GetLastError(), "vips__seek", + if( (new_pos = vips__seek_no_error( fd, pos, whence )) == -1 ) { + vips_error_system( errno, "vips__seek", "%s", _( "unable to seek" ) ); return( -1 ); } -} -#else /*!OS_WIN32*/ - if( lseek( fd, pos, SEEK_SET ) == (off_t) -1 ) { - vips_error( "vips__seek", "%s", _( "unable to seek" ) ); - return( -1 ); - } -#endif /*OS_WIN32*/ - return( 0 ); + return( new_pos ); } /* Need our own ftruncate(), since ftruncate() on win32 can't do long files. @@ -1065,10 +1131,8 @@ vips__ftruncate( int fd, gint64 pos ) #ifdef OS_WIN32 { HANDLE hFile = (HANDLE) _get_osfhandle( fd ); - LARGE_INTEGER p; - p.QuadPart = pos; - if( vips__seek( fd, pos ) ) + if( vips__seek( fd, pos, SEEK_SET ) == -1 ) return( -1 ); if( !SetEndOfFile( hFile ) ) { vips_error_system( GetLastError(), "vips__ftruncate", @@ -1087,29 +1151,44 @@ vips__ftruncate( int fd, gint64 pos ) return( 0 ); } -/* TRUE if file exists. +/* TRUE if file exists. True for directories as well. */ gboolean vips_existsf( const char *name, ... ) { va_list ap; char *path; - int result; + gboolean result; va_start( ap, name ); path = g_strdup_vprintf( name, ap ); va_end( ap ); - result = g_access( path, R_OK ); + result = g_file_test( path, G_FILE_TEST_EXISTS ); g_free( path ); - /* access() can fail for various reasons, especially under things - * like selinux. Only return FALSE if we are certain the file does not - * exist. - */ - return( result == 0 || - errno != ENOENT ); + return( result ); +} + +/* TRUE if file exists and is a directory. + */ +gboolean +vips_isdirf( const char *name, ... ) +{ + va_list ap; + char *path; + gboolean result; + + va_start( ap, name ); + path = g_strdup_vprintf( name, ap ); + va_end( ap ); + + result = g_file_test( path, G_FILE_TEST_IS_DIR ); + + g_free( path ); + + return( result ); } #ifdef OS_WIN32 @@ -1603,14 +1682,23 @@ vips__temp_dir( void ) char * vips__temp_name( const char *format ) { - static int serial = 0; + static int global_serial = 0; char file[FILENAME_MAX]; char file2[FILENAME_MAX]; char *name; + /* Old glibs named this differently. + */ + int serial = +#if GLIB_CHECK_VERSION( 2, 30, 0 ) + g_atomic_int_add( &global_serial, 1 ); +#else + g_atomic_int_exchange_and_add( &global_serial, 1 ); +#endif + vips_snprintf( file, FILENAME_MAX, "vips-%d-%u", - serial++, g_random_int() ); + serial, g_random_int() ); vips_snprintf( file2, FILENAME_MAX, format, file ); name = g_build_filename( vips__temp_dir(), file2, NULL ); @@ -1873,25 +1961,10 @@ vips_realpath( const char *path ) { char *real; -#ifdef HAVE_REALPATH -{ - char buf[PATH_MAX]; - - /* More modern realpath() allow NULL for the second param, but we want - * to work with older libc as well. + /* It'd be nice to use realpath here, but sadly that won't work on + * linux systems with grsec, since it works by opening /proc/self/fd. */ - if( !(real = realpath( path, buf )) ) { - vips_error_system( errno, "vips_realpath", - "%s", _( "unable to form filename" ) ); - return( NULL ); - } - /* We must return a path that can be freed with g_free(). - */ - real = g_strdup( real ); -} -#else /*!HAVE_REALPATH*/ -{ if( !g_path_is_absolute( path ) ) { char *cwd; @@ -1901,8 +1974,6 @@ vips_realpath( const char *path ) } else real = g_strdup( path ); -} -#endif return( real ); } @@ -2000,3 +2071,58 @@ vips__windows_prefix( void ) return( (const char *) g_once( &once, (GThreadFunc) vips__windows_prefix_once, NULL ) ); } + +char * +vips__get_iso8601( void ) +{ + char *date; + +#ifdef HAVE_DATE_TIME_FORMAT_ISO8601 +{ + GDateTime *now; + + now = g_date_time_new_now_local(); + date = g_date_time_format_iso8601( now ); + g_date_time_unref( now ); +} +#else /*!HAVE_DATE_TIME_FORMAT_ISO8601*/ +{ + GTimeVal now; + + g_get_current_time( &now ); + date = g_time_val_to_iso8601( &now ); +} +#endif /*HAVE_DATE_TIME_FORMAT_ISO8601*/ + + return( date ); +} + +/* Convert a string to a double in the ASCII locale (ie. decimal point is + * "."). + */ +int +vips_strtod( const char *str, double *out ) +{ + const char *p; + + *out = 0; + + /* The str we fetched must contain at least 1 digit. This + * helps stop us trying to convert "MATLAB" (for example) to + * a number and getting zero. + */ + for( p = str; *p; p++ ) + if( isdigit( *p ) ) + break; + if( !*p ) + return( -1 ); + + /* This will fail for out of range numbers, like 1e343434, but + * is quite happy with eg. "banana". + */ + *out = g_ascii_strtod( str, NULL ); + if( errno ) + return( -1 ); + + return( 0 ); +} diff --git a/libvips/iofuncs/vector.c b/libvips/iofuncs/vector.c index 1595b13d..5af1e61e 100644 --- a/libvips/iofuncs/vector.c +++ b/libvips/iofuncs/vector.c @@ -54,6 +54,7 @@ #ifdef HAVE_CONFIG_H #include #endif /*HAVE_CONFIG_H*/ + #include #include @@ -63,7 +64,7 @@ #include #include -/* Cleared by the command-line --vips-novector switch and the IM_NOVECTOR env +/* Cleared by the command-line --vips-novector switch and the VIPS_NOVECTOR env * var. */ gboolean vips__vector_enabled = TRUE; @@ -98,11 +99,15 @@ vips_vector_init( void ) orc_debug_set_level( 99 ); #endif /*DEBUG_ORC*/ - /* Look for the environment variable IM_NOVECTOR and use that to turn + /* Look for the environment variable VIPS_NOVECTOR and use that to turn * off as well. */ +#if VIPS_ENABLE_DEPRECATED if( g_getenv( "VIPS_NOVECTOR" ) || g_getenv( "IM_NOVECTOR" ) ) +#else + if( g_getenv( "VIPS_NOVECTOR" ) ) +#endif vips__vector_enabled = FALSE; #endif /*HAVE_ORC*/ diff --git a/libvips/iofuncs/vips.c b/libvips/iofuncs/vips.c index 5b43f101..16e74759 100644 --- a/libvips/iofuncs/vips.c +++ b/libvips/iofuncs/vips.c @@ -165,11 +165,11 @@ vips__open_image_read( const char *filename ) * work. When we later mmap this file, we set read-only, so there * is little danger of scrubbing over files we own. */ - fd = vips_tracked_open( filename, MODE_READWRITE ); + fd = vips_tracked_open( filename, MODE_READWRITE, 0 ); if( fd == -1 ) /* Open read-write failed. Fall back to open read-only. */ - fd = vips_tracked_open( filename, MODE_READONLY ); + fd = vips_tracked_open( filename, MODE_READONLY, 0 ); if( fd == -1 ) { vips_error_system( errno, "VipsImage", @@ -208,7 +208,7 @@ vips__open_image_write( const char *filename, gboolean temp ) g_info( "vips__open_image_write: opening with O_TMPFILE" ); dirname = g_path_get_dirname( filename ); - fd = vips_tracked_open( dirname, O_TMPFILE | O_RDWR , 0666 ); + fd = vips_tracked_open( dirname, O_TMPFILE | O_RDWR , 0644 ); g_free( dirname ); if( fd < 0 ) @@ -230,7 +230,7 @@ vips__open_image_write( const char *filename, gboolean temp ) if( fd < 0 ) { g_info( "vips__open_image_write: simple open" ); - fd = vips_tracked_open( filename, flags, 0666 ); + fd = vips_tracked_open( filename, flags, 0644 ); } if( fd < 0 ) { @@ -485,7 +485,7 @@ read_chunk( int fd, gint64 offset, size_t length ) { char *buf; - if( vips__seek( fd, offset ) ) + if( vips__seek( fd, offset, SEEK_SET ) == -1 ) return( NULL ); if( !(buf = vips_malloc( NULL, length + 1 )) ) return( NULL ); @@ -549,8 +549,8 @@ parser_read_fd( XML_Parser parser, int fd ) { const int chunk_size = 1024; - ssize_t bytes_read; - ssize_t len; + gint64 bytes_read; + gint64 len; bytes_read = 0; @@ -563,7 +563,7 @@ parser_read_fd( XML_Parser parser, int fd ) return( -1 ); } len = read( fd, buf, chunk_size ); - if( len == (ssize_t) -1 ) { + if( len == -1 ) { vips_error( "VipsImage", "%s", _( "read error while fetching XML" ) ); return( -1 ); @@ -755,7 +755,7 @@ readhist( VipsImage *im ) XML_Parser parser; VipsExpatParse vep; - if( vips__seek( im->fd, image_pixel_length( im ) ) ) + if( vips__seek( im->fd, image_pixel_length( im ), SEEK_SET ) == -1 ) return( -1 ); parser = XML_ParserCreate( "UTF-8" ); @@ -797,7 +797,7 @@ vips__write_extension_block( VipsImage *im, void *buf, int size ) } if( vips__ftruncate( im->fd, psize ) || - vips__seek( im->fd, psize ) ) + vips__seek( im->fd, psize, SEEK_SET ) == -1 ) return( -1 ); if( vips__write( im->fd, buf, size ) ) return( -1 ); @@ -813,7 +813,7 @@ vips__write_extension_block( VipsImage *im, void *buf, int size ) /* Append a string to a buffer, but escape " as \". */ static void -dbuf_write_quotes( VipsDbuf *dbuf, const char *str ) +target_write_quotes( VipsTarget *target, const char *str ) { const char *p; size_t len; @@ -821,14 +821,14 @@ dbuf_write_quotes( VipsDbuf *dbuf, const char *str ) for( p = str; *p; p += len ) { len = strcspn( p, "\"" ); - vips_dbuf_write( dbuf, (unsigned char *) p, len ); + vips_target_write( target, (unsigned char *) p, len ); if( p[len] == '"' ) - vips_dbuf_writef( dbuf, "\\" ); + vips_target_writes( target, "\\" ); } } static void * -build_xml_meta( VipsMeta *meta, VipsDbuf *dbuf ) +build_xml_meta( VipsMeta *meta, VipsTarget *target ) { GType type = G_VALUE_TYPE( &meta->value ); @@ -853,13 +853,13 @@ build_xml_meta( VipsMeta *meta, VipsDbuf *dbuf ) */ str = vips_value_get_save_string( &save_value ); if( g_utf8_validate( str, -1, NULL ) ) { - vips_dbuf_writef( dbuf, + vips_target_writef( target, " name ); - vips_dbuf_writef( dbuf, "\">" ); - vips_dbuf_write_amp( dbuf, str ); - vips_dbuf_writef( dbuf, "\n" ); + target_write_quotes( target, meta->name ); + vips_target_writes( target, "\">" ); + vips_target_write_amp( target, str ); + vips_target_writes( target, "\n" ); } g_value_unset( &save_value ); @@ -873,46 +873,51 @@ build_xml_meta( VipsMeta *meta, VipsDbuf *dbuf ) static char * build_xml( VipsImage *image ) { - VipsDbuf dbuf; + VipsTarget *target; const char *str; + char *result; - vips_dbuf_init( &dbuf ); + target = vips_target_new_to_memory(); - vips_dbuf_writef( &dbuf, "\n" ); - vips_dbuf_writef( &dbuf, "\n", + vips_target_writef( target, "\n" ); + vips_target_writef( target, "\n", NAMESPACE_URI, VIPS_MAJOR_VERSION, VIPS_MINOR_VERSION, VIPS_MICRO_VERSION ); - vips_dbuf_writef( &dbuf, "
\n" ); + vips_target_writef( target, "
\n" ); str = vips_image_get_history( image ); if( g_utf8_validate( str, -1, NULL ) ) { - vips_dbuf_writef( &dbuf, + vips_target_writef( target, " ", g_type_name( VIPS_TYPE_REF_STRING ) ); - vips_dbuf_write_amp( &dbuf, str ); - vips_dbuf_writef( &dbuf, "\n" ); + vips_target_write_amp( target, str ); + vips_target_writef( target, "\n" ); } - vips_dbuf_writef( &dbuf, "
\n" ); - vips_dbuf_writef( &dbuf, " \n" ); + vips_target_writef( target, "
\n" ); + vips_target_writef( target, " \n" ); if( vips_slist_map2( image->meta_traverse, - (VipsSListMap2Fn) build_xml_meta, &dbuf, NULL ) ) { - vips_dbuf_destroy( &dbuf ); + (VipsSListMap2Fn) build_xml_meta, target, NULL ) ) { + VIPS_UNREF( target ); return( NULL ); } - vips_dbuf_writef( &dbuf, " \n" ); - vips_dbuf_writef( &dbuf, "
\n" ); + vips_target_writef( target, " \n" ); + vips_target_writef( target, "
\n" ); - return( (char *) vips_dbuf_steal( &dbuf, NULL ) ); + result = vips_target_steal_text( target ); + + VIPS_UNREF( target ); + + return( result ); } static void * vips__xml_properties_meta( VipsImage *image, const char *field, GValue *value, void *a ) { - VipsDbuf *dbuf = (VipsDbuf *) a; + VipsTarget *target = (VipsTarget *) a; GType type = G_VALUE_TYPE( value ); const char *str; @@ -928,19 +933,19 @@ vips__xml_properties_meta( VipsImage *image, if( !g_value_transform( value, &save_value ) ) { vips_error( "VipsImage", "%s", _( "error transforming to save format" ) ); - return( dbuf ); + return( target ); } str = vips_value_get_save_string( &save_value ); - vips_dbuf_writef( dbuf, " \n" ); - vips_dbuf_writef( dbuf, " " ); - vips_dbuf_write_amp( dbuf, field ); - vips_dbuf_writef( dbuf, "\n" ); - vips_dbuf_writef( dbuf, " ", + vips_target_writef( target, " \n" ); + vips_target_writef( target, " " ); + vips_target_write_amp( target, field ); + vips_target_writef( target, "\n" ); + vips_target_writef( target, " ", g_type_name( type ) ); - vips_dbuf_write_amp( dbuf, str ); - vips_dbuf_writef( dbuf, "\n" ); - vips_dbuf_writef( dbuf, " \n" ); + vips_target_write_amp( target, str ); + vips_target_writef( target, "\n" ); + vips_target_writef( target, " \n" ); g_value_unset( &save_value ); } @@ -954,32 +959,36 @@ vips__xml_properties_meta( VipsImage *image, char * vips__xml_properties( VipsImage *image ) { - VipsDbuf dbuf; - GTimeVal now; + VipsTarget *target; char *date; + char *result; - vips_dbuf_init( &dbuf ); + date = vips__get_iso8601(); - g_get_current_time( &now ); - date = g_time_val_to_iso8601( &now ); - vips_dbuf_writef( &dbuf, "\n" ); - vips_dbuf_writef( &dbuf, "\n" ); + vips_target_writef( target, "\n", NAMESPACE_URI, date, VIPS_MAJOR_VERSION, VIPS_MINOR_VERSION, VIPS_MICRO_VERSION ); - g_free( date ); - vips_dbuf_writef( &dbuf, " \n" ); + vips_target_writef( target, " \n" ); - if( vips_image_map( image, vips__xml_properties_meta, &dbuf ) ) { - vips_dbuf_destroy( &dbuf ); + g_free( date ); + + if( vips_image_map( image, vips__xml_properties_meta, target ) ) { + VIPS_UNREF( target ); return( NULL ); } - vips_dbuf_writef( &dbuf, " \n" ); - vips_dbuf_writef( &dbuf, "\n" ); + vips_target_writef( target, " \n" ); + vips_target_writef( target, "\n" ); - return( (char *) vips_dbuf_steal( &dbuf, NULL ) ); + result = vips_target_steal_text( target ); + + VIPS_UNREF( target ); + + return( result ); } /* Append XML to output fd. @@ -1032,7 +1041,7 @@ vips_image_open_input( VipsImage *image ) return( -1 ); } - vips__seek( image->fd, 0 ); + vips__seek( image->fd, 0, SEEK_SET ); if( read( image->fd, header, VIPS_SIZEOF_HEADER ) != VIPS_SIZEOF_HEADER || vips__read_header_bytes( image, header ) ) { diff --git a/libvips/iofuncs/vipsmarshal.c b/libvips/iofuncs/vipsmarshal.c index 655ffb32..a84aea1c 100644 --- a/libvips/iofuncs/vipsmarshal.c +++ b/libvips/iofuncs/vipsmarshal.c @@ -1,7 +1,6 @@ #include "vipsmarshal.h" - -#include - +/* This file is generated by glib-genmarshal, do not modify it. This code is licensed under the same license as the containing project. Note that it links to GLib, so must comply with the LGPL linking clauses. */ +#include #ifdef G_ENABLE_DEBUG #define g_marshal_value_peek_boolean(v) g_value_get_boolean (v) @@ -49,21 +48,20 @@ #define g_marshal_value_peek_variant(v) (v)->data[0].v_pointer #endif /* !G_ENABLE_DEBUG */ - -/* INT:VOID (vipsmarshal.list:25) */ +/* INT: VOID (vipsmarshal.list:25) */ void vips_INT__VOID (GClosure *closure, - GValue *return_value G_GNUC_UNUSED, + GValue *return_value, guint n_param_values, const GValue *param_values, gpointer invocation_hint G_GNUC_UNUSED, gpointer marshal_data) { - typedef gint (*GMarshalFunc_INT__VOID) (gpointer data1, - gpointer data2); - register GMarshalFunc_INT__VOID callback; - register GCClosure *cc = (GCClosure*) closure; - register gpointer data1, data2; + typedef gint (*GMarshalFunc_INT__VOID) (gpointer data1, + gpointer data2); + GCClosure *cc = (GCClosure *) closure; + gpointer data1, data2; + GMarshalFunc_INT__VOID callback; gint v_return; g_return_if_fail (return_value != NULL); @@ -87,3 +85,85 @@ vips_INT__VOID (GClosure *closure, g_value_set_int (return_value, v_return); } +/* INT64: INT64, INT (vipsmarshal.list:26) */ +void +vips_INT64__INT64_INT (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint G_GNUC_UNUSED, + gpointer marshal_data) +{ + typedef gint64 (*GMarshalFunc_INT64__INT64_INT) (gpointer data1, + gint64 arg1, + gint arg2, + gpointer data2); + GCClosure *cc = (GCClosure *) closure; + gpointer data1, data2; + GMarshalFunc_INT64__INT64_INT callback; + gint64 v_return; + + g_return_if_fail (return_value != NULL); + g_return_if_fail (n_param_values == 3); + + if (G_CCLOSURE_SWAP_DATA (closure)) + { + data1 = closure->data; + data2 = g_value_peek_pointer (param_values + 0); + } + else + { + data1 = g_value_peek_pointer (param_values + 0); + data2 = closure->data; + } + callback = (GMarshalFunc_INT64__INT64_INT) (marshal_data ? marshal_data : cc->callback); + + v_return = callback (data1, + g_marshal_value_peek_int64 (param_values + 1), + g_marshal_value_peek_int (param_values + 2), + data2); + + g_value_set_int64 (return_value, v_return); +} + +/* INT64: POINTER, INT64 (vipsmarshal.list:27) */ +void +vips_INT64__POINTER_INT64 (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint G_GNUC_UNUSED, + gpointer marshal_data) +{ + typedef gint64 (*GMarshalFunc_INT64__POINTER_INT64) (gpointer data1, + gpointer arg1, + gint64 arg2, + gpointer data2); + GCClosure *cc = (GCClosure *) closure; + gpointer data1, data2; + GMarshalFunc_INT64__POINTER_INT64 callback; + gint64 v_return; + + g_return_if_fail (return_value != NULL); + g_return_if_fail (n_param_values == 3); + + if (G_CCLOSURE_SWAP_DATA (closure)) + { + data1 = closure->data; + data2 = g_value_peek_pointer (param_values + 0); + } + else + { + data1 = g_value_peek_pointer (param_values + 0); + data2 = closure->data; + } + callback = (GMarshalFunc_INT64__POINTER_INT64) (marshal_data ? marshal_data : cc->callback); + + v_return = callback (data1, + g_marshal_value_peek_pointer (param_values + 1), + g_marshal_value_peek_int64 (param_values + 2), + data2); + + g_value_set_int64 (return_value, v_return); +} + diff --git a/libvips/iofuncs/vipsmarshal.h b/libvips/iofuncs/vipsmarshal.h index 2f3e711f..06f7d675 100644 --- a/libvips/iofuncs/vipsmarshal.h +++ b/libvips/iofuncs/vipsmarshal.h @@ -1,20 +1,39 @@ +/* This file is generated by glib-genmarshal, do not modify it. This code is licensed under the same license as the containing project. Note that it links to GLib, so must comply with the LGPL linking clauses. */ +#ifndef __VIPS_MARSHAL_H__ +#define __VIPS_MARSHAL_H__ -#ifndef __vips_MARSHAL_H__ -#define __vips_MARSHAL_H__ - -#include +#include G_BEGIN_DECLS -/* INT:VOID (vipsmarshal.list:25) */ -extern void vips_INT__VOID (GClosure *closure, +/* INT: VOID (vipsmarshal.list:25) */ +extern +void vips_INT__VOID (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data); + +/* INT64: INT64, INT (vipsmarshal.list:26) */ +extern +void vips_INT64__INT64_INT (GClosure *closure, GValue *return_value, guint n_param_values, const GValue *param_values, gpointer invocation_hint, gpointer marshal_data); +/* INT64: POINTER, INT64 (vipsmarshal.list:27) */ +extern +void vips_INT64__POINTER_INT64 (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data); + + G_END_DECLS -#endif /* __vips_MARSHAL_H__ */ - +#endif /* __VIPS_MARSHAL_H__ */ diff --git a/libvips/iofuncs/vipsmarshal.list b/libvips/iofuncs/vipsmarshal.list index 0ce2c772..016bfc2c 100644 --- a/libvips/iofuncs/vipsmarshal.list +++ b/libvips/iofuncs/vipsmarshal.list @@ -23,5 +23,5 @@ # BOOL deprecated alias for BOOLEAN INT: VOID - - +INT64: INT64, INT +INT64: POINTER, INT64 diff --git a/libvips/morphology/Makefile.am b/libvips/morphology/Makefile.am index 1f1dcc46..2429a965 100644 --- a/libvips/morphology/Makefile.am +++ b/libvips/morphology/Makefile.am @@ -6,7 +6,6 @@ libmorphology_la_SOURCES = \ pmorphology.h \ countlines.c \ rank.c \ - hitmiss.c \ morph.c \ labelregions.c diff --git a/libvips/morphology/hitmiss.c b/libvips/morphology/hitmiss.c deleted file mode 100644 index e43aaff4..00000000 --- a/libvips/morphology/hitmiss.c +++ /dev/null @@ -1,808 +0,0 @@ -/* morphological operators - * - * 19/9/95 JC - * - rewritten - * 6/7/99 JC - * - small tidies - * 7/4/04 - * - now uses im_embed() with edge stretching on the input, not - * the output - * - sets Xoffset / Yoffset - * 21/4/08 - * - only rebuild the buffer offsets if bpl changes - * - small cleanups - * 25/10/10 - * - start again from the Orc'd im_conv - * 29/10/10 - * - use VipsVector - * - do erode as well - * 7/11/10 - * - gtk-doc - * - do (!=0) to make uchar, if we're not given uchar - * 28/6/13 - * - oops, fix !=0 code - */ - -/* - - 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_VERBOSE -#define DEBUG - */ - -#ifdef HAVE_CONFIG_H -#include -#endif /*HAVE_CONFIG_H*/ -#include - -#include -#include -#include - -#include -#include -#include - -/* The two operators we implement. They are more hit-miss, really. - */ -typedef enum { - ERODE, - DILATE -} MorphOp; - -/* We can't run more than this many passes. Larger than this and we - * fall back to C. - */ -#define MAX_PASS (10) - -/* A pass with a vector. - */ -typedef struct { - int first; /* The index of the first mask coff we use */ - int last; /* The index of the last mask coff we use */ - - int r; /* Set previous result in this var */ - - /* The code we generate for this section of this mask. - */ - VipsVector *vector; -} Pass; - -/* Our parameters. - */ -typedef struct { - IMAGE *in; - IMAGE *out; - INTMASK *mask; /* Copy of mask arg */ - MorphOp op; - - /* The passes we generate for this mask. - */ - int n_pass; - Pass pass[MAX_PASS]; -} Morph; - -static void -pass_free( Morph *morph ) -{ - int i; - - for( i = 0; i < morph->n_pass; i++ ) - IM_FREEF( vips_vector_free, morph->pass[i].vector ); - morph->n_pass = 0; -} - -static int -morph_close( Morph *morph ) -{ - IM_FREEF( im_free_imask, morph->mask ); - pass_free( morph ); - - return( 0 ); -} - -#define TEMP( N, S ) vips_vector_temporary( v, N, S ) -#define SCANLINE( N, P, S ) vips_vector_source_scanline( v, N, P, S ) -#define CONST( N, V, S ) vips_vector_constant( v, N, V, S ) -#define ASM2( OP, A, B ) vips_vector_asm2( v, OP, A, B ) -#define ASM3( OP, A, B, C ) vips_vector_asm3( v, OP, A, B, C ) - -/* Generate code for a section of the mask. first is the index we start - * at, we set last to the index of the last one we use before we run - * out of intermediates / constants / parameters / sources or mask - * coefficients. - * - * 0 for success, -1 on error. - */ -static int -pass_compile_section( Pass *pass, Morph *morph, gboolean first_pass ) -{ - INTMASK *mask = morph->mask; - const int n_mask = mask->xsize * mask->ysize; - - VipsVector *v; - char offset[256]; - char source[256]; - char zero[256]; - char one[256]; - int i; - - pass->vector = v = vips_vector_new( "morph", 1 ); - - /* The value we fetch from the image, the accumulated sum. - */ - TEMP( "value", 1 ); - TEMP( "sum", 1 ); - - CONST( zero, 0, 1 ); - CONST( one, 255, 1 ); - - /* Init the sum. If this is the first pass, it's a constant. If this - * is a later pass, we have to init the sum from the result - * of the previous pass. - */ - if( first_pass ) { - if( morph->op == DILATE ) - ASM2( "copyb", "sum", zero ); - else - ASM2( "copyb", "sum", one ); - } - else { - /* "r" is the result of the previous pass. - */ - pass->r = vips_vector_source_name( v, "r", 1 ); - ASM2( "loadb", "sum", "r" ); - } - - for( i = pass->first; i < n_mask; i++ ) { - int x = i % mask->xsize; - int y = i / mask->xsize; - - /* Exclude don't-care elements. - */ - if( mask->coeff[i] == 128 ) - continue; - - /* The source. sl0 is the first scanline in the mask. - */ - SCANLINE( source, y, 1 ); - - /* The offset, only for non-first-columns though. - */ - if( x > 0 ) { - CONST( offset, morph->in->Bands * x, 1 ); - ASM3( "loadoffb", "value", source, offset ); - } - else - ASM2( "loadb", "value", source ); - - /* Join to our sum. If the mask element is zero, we have to - * add an extra negate. - */ - if( morph->op == DILATE ) { - if( !mask->coeff[i] ) - ASM3( "xorb", "value", "value", one ); - ASM3( "orb", "sum", "sum", "value" ); - } - else { - if( !mask->coeff[i] ) { - /* You'd think we could use andnb, but it - * fails on some machines with some orc - * versions :( - */ - ASM3( "xorb", "value", "value", one ); - ASM3( "andb", "sum", "sum", "value" ); - } - else - ASM3( "andb", "sum", "sum", "value" ); - } - - if( vips_vector_full( v ) ) - break; - } - - pass->last = i; - - ASM2( "copyb", "d1", "sum" ); - - if( !vips_vector_compile( v ) ) - return( -1 ); - -#ifdef DEBUG - printf( "done matrix coeffs %d to %d\n", pass->first, pass->last ); - vips_vector_print( v ); -#endif /*DEBUG*/ - - return( 0 ); -} - -/* Generate a set of passes. - */ -static int -pass_compile( Morph *morph ) -{ - INTMASK *mask = morph->mask; - const int n_mask = mask->xsize * mask->ysize; - - int i; - Pass *pass; - -#ifdef DEBUG - printf( "morph: generating vector code\n" ); -#endif /*DEBUG*/ - - /* Generate passes until we've used up the whole mask. - */ - for( i = 0;;) { - /* Skip any don't-care coefficients at the start of the mask - * region. - */ - for( ; i < n_mask && mask->coeff[i] == 128; i++ ) - ; - if( i == n_mask ) - break; - - /* Allocate space for another pass. - */ - if( morph->n_pass == MAX_PASS ) - return( -1 ); - pass = &morph->pass[morph->n_pass]; - morph->n_pass += 1; - - pass->first = i; - pass->last = i; - pass->r = -1; - - if( pass_compile_section( pass, morph, morph->n_pass == 1 ) ) - return( -1 ); - i = pass->last + 1; - - if( i >= n_mask ) - break; - } - - return( 0 ); -} - -static Morph * -morph_new( IMAGE *in, IMAGE *out, INTMASK *mask, MorphOp op ) -{ - const int n_mask = mask->xsize * mask->ysize; - - Morph *morph; - int i; - - /* If in is not uchar, do (!=0) to make a uchar image. - */ - if( in->BandFmt != IM_BANDFMT_UCHAR ) { - IMAGE *t; - - if( !(t = im_open_local( out, "morph_new", "p" )) || - im_notequalconst( in, t, 0 ) ) - return( NULL ); - - in = t; - } - - if( im_piocheck( in, out ) || - im_check_uncoded( "morph", in ) || - im_check_format( "morph", in, IM_BANDFMT_UCHAR ) || - im_check_imask( "morph", mask ) ) - return( NULL ); - for( i = 0; i < n_mask; i++ ) - if( mask->coeff[i] != 0 && - mask->coeff[i] != 128 && - mask->coeff[i] != 255 ) { - im_error( "morph", - _( "bad mask element (%d " - "should be 0, 128 or 255)" ), - mask->coeff[i] ); - return( NULL ); - } - - if( !(morph = IM_NEW( out, Morph )) ) - return( NULL ); - - morph->in = in; - morph->out = out; - morph->mask = NULL; - morph->op = op; - - morph->n_pass = 0; - for( i = 0; i < MAX_PASS; i++ ) - morph->pass[i].vector = NULL; - - if( im_add_close_callback( out, - (im_callback_fn) morph_close, morph, NULL ) || - !(morph->mask = im_dup_imask( mask, "morph" )) ) - return( NULL ); - - /* Generate code for this mask / image, if possible. - */ - if( vips_vector_isenabled() ) { - if( pass_compile( morph ) ) - pass_free( morph ); - } - - return( morph ); -} - -/* Our sequence value. - */ -typedef struct { - Morph *morph; - REGION *ir; /* Input region */ - - int *soff; /* Offsets we check for set */ - int ss; /* ... and number we check for set */ - int *coff; /* Offsets we check for clear */ - int cs; /* ... and number we check for clear */ - - int last_bpl; /* Avoid recalcing offsets, if we can */ - - /* In vector mode we need a pair of intermediate buffers to keep the - * results of each pass in. - */ - void *t1; - void *t2; -} MorphSequence; - -/* Free a sequence value. - */ -static int -morph_stop( void *vseq, void *a, void *b ) -{ - MorphSequence *seq = (MorphSequence *) vseq; - - IM_FREEF( im_region_free, seq->ir ); - IM_FREE( seq->t1 ); - IM_FREE( seq->t2 ); - - return( 0 ); -} - -/* Morph start function. - */ -static void * -morph_start( IMAGE *out, void *a, void *b ) -{ - IMAGE *in = (IMAGE *) a; - Morph *morph = (Morph *) b; - int n_mask = morph->mask->xsize * morph->mask->ysize; - int sz = IM_IMAGE_N_ELEMENTS( in ); - - MorphSequence *seq; - - if( !(seq = IM_NEW( out, MorphSequence )) ) - return( NULL ); - - /* Init! - */ - seq->morph = morph; - seq->ir = NULL; - seq->soff = NULL; - seq->ss = 0; - seq->coff = NULL; - seq->cs = 0; - seq->last_bpl = -1; - seq->t1 = NULL; - seq->t2 = NULL; - - /* Attach region and arrays. - */ - seq->ir = im_region_create( in ); - seq->soff = IM_ARRAY( out, n_mask, int ); - seq->coff = IM_ARRAY( out, n_mask, int ); - seq->t1 = IM_ARRAY( NULL, sz, VipsPel ); - seq->t2 = IM_ARRAY( NULL, sz, VipsPel ); - if( !seq->ir || !seq->soff || !seq->coff || !seq->t1 || !seq->t2 ) { - morph_stop( seq, in, NULL ); - return( NULL ); - } - - return( seq ); -} - -/* Dilate! - */ -static int -dilate_gen( REGION *or, void *vseq, void *a, void *b ) -{ - MorphSequence *seq = (MorphSequence *) vseq; - Morph *morph = (Morph *) b; - INTMASK *mask = morph->mask; - REGION *ir = seq->ir; - - int *soff = seq->soff; - int *coff = seq->coff; - - Rect *r = &or->valid; - Rect s; - int le = r->left; - int to = r->top; - int bo = IM_RECT_BOTTOM( r ); - int sz = IM_REGION_N_ELEMENTS( or ); - - int *t; - - int x, y; - int result, i; - - /* Prepare the section of the input image we need. A little larger - * than the section of the output image we are producing. - */ - s = *r; - s.width += mask->xsize - 1; - s.height += mask->ysize - 1; - if( im_prepare( ir, &s ) ) - return( -1 ); - -#ifdef DEBUG_VERBOSE - printf( "dilate_gen: preparing %dx%d@%dx%d pixels\n", - s.width, s.height, s.left, s.top ); -#endif /*DEBUG_VERBOSE*/ - - /* Scan mask, building offsets we check when processing. Only do this - * if the bpl has changed since the previous im_prepare(). - */ - if( seq->last_bpl != IM_REGION_LSKIP( ir ) ) { - seq->last_bpl = IM_REGION_LSKIP( ir ); - - seq->ss = 0; - seq->cs = 0; - for( t = mask->coeff, y = 0; y < mask->ysize; y++ ) - for( x = 0; x < mask->xsize; x++, t++ ) - switch( *t ) { - case 255: - soff[seq->ss++] = - IM_REGION_ADDR( ir, - x + le, y + to ) - - IM_REGION_ADDR( ir, le, to ); - break; - - case 128: - break; - - case 0: - coff[seq->cs++] = - IM_REGION_ADDR( ir, - x + le, y + to ) - - IM_REGION_ADDR( ir, le, to ); - break; - - default: - g_assert_not_reached(); - } - } - - /* Dilate! - */ - for( y = to; y < bo; y++ ) { - VipsPel *p = IM_REGION_ADDR( ir, le, y ); - VipsPel *q = IM_REGION_ADDR( or, le, y ); - - /* Loop along line. - */ - for( x = 0; x < sz; x++, q++, p++ ) { - /* Search for a hit on the set list. - */ - result = 0; - for( i = 0; i < seq->ss; i++ ) - if( p[soff[i]] ) { - /* Found a match! - */ - result = 255; - break; - } - - /* No set pixels ... search for a hit in the clear - * pixels. - */ - if( !result ) - for( i = 0; i < seq->cs; i++ ) - if( !p[coff[i]] ) { - /* Found a match! - */ - result = 255; - break; - } - - *q = result; - - } - } - - return( 0 ); -} - -/* Erode! - */ -static int -erode_gen( REGION *or, void *vseq, void *a, void *b ) -{ - MorphSequence *seq = (MorphSequence *) vseq; - Morph *morph = (Morph *) b; - INTMASK *mask = morph->mask; - REGION *ir = seq->ir; - - int *soff = seq->soff; - int *coff = seq->coff; - - Rect *r = &or->valid; - Rect s; - int le = r->left; - int to = r->top; - int bo = IM_RECT_BOTTOM(r); - int sz = IM_REGION_N_ELEMENTS( or ); - - int *t; - - int x, y; - int result, i; - - /* Prepare the section of the input image we need. A little larger - * than the section of the output image we are producing. - */ - s = *r; - s.width += mask->xsize - 1; - s.height += mask->ysize - 1; - if( im_prepare( ir, &s ) ) - return( -1 ); - -#ifdef DEBUG_VERBOSE - printf( "erode_gen: preparing %dx%d@%dx%d pixels\n", - s.width, s.height, s.left, s.top ); -#endif /*DEBUG_VERBOSE*/ - - /* Scan mask, building offsets we check when processing. Only do this - * if the bpl has changed since the previous im_prepare(). - */ - if( seq->last_bpl != IM_REGION_LSKIP( ir ) ) { - seq->last_bpl = IM_REGION_LSKIP( ir ); - - seq->ss = 0; - seq->cs = 0; - for( t = mask->coeff, y = 0; y < mask->ysize; y++ ) - for( x = 0; x < mask->xsize; x++, t++ ) - switch( *t ) { - case 255: - soff[seq->ss++] = - IM_REGION_ADDR( ir, - x + le, y + to ) - - IM_REGION_ADDR( ir, le, to ); - break; - - case 128: - break; - - case 0: - coff[seq->cs++] = - IM_REGION_ADDR( ir, - x + le, y + to ) - - IM_REGION_ADDR( ir, le, to ); - break; - - default: - g_assert_not_reached(); - } - } - - /* Erode! - */ - for( y = to; y < bo; y++ ) { - VipsPel *p = IM_REGION_ADDR( ir, le, y ); - VipsPel *q = IM_REGION_ADDR( or, le, y ); - - /* Loop along line. - */ - for( x = 0; x < sz; x++, q++, p++ ) { - /* Check all set pixels are set. - */ - result = 255; - for( i = 0; i < seq->ss; i++ ) - if( !p[soff[i]] ) { - /* Found a mismatch! - */ - result = 0; - break; - } - - /* Check all clear pixels are clear. - */ - if( result ) - for( i = 0; i < seq->cs; i++ ) - if( p[coff[i]] ) { - result = 0; - break; - } - - *q = result; - } - } - - return( 0 ); -} - -/* The vector codepath. - */ -static int -morph_vector_gen( REGION *or, void *vseq, void *a, void *b ) -{ - MorphSequence *seq = (MorphSequence *) vseq; - Morph *morph = (Morph *) b; - INTMASK *mask = morph->mask; - REGION *ir = seq->ir; - Rect *r = &or->valid; - int sz = IM_REGION_N_ELEMENTS( or ); - - Rect s; - int y, j; - VipsExecutor executor[MAX_PASS]; - - /* Prepare the section of the input image we need. A little larger - * than the section of the output image we are producing. - */ - s = *r; - s.width += mask->xsize - 1; - s.height += mask->ysize - 1; - if( im_prepare( ir, &s ) ) - return( -1 ); - -#ifdef DEBUG_VERBOSE - printf( "morph_vector_gen: preparing %dx%d@%dx%d pixels\n", - s.width, s.height, s.left, s.top ); -#endif /*DEBUG_VERBOSE*/ - - for( j = 0; j < morph->n_pass; j++ ) - vips_executor_set_program( &executor[j], - morph->pass[j].vector, sz ); - - for( y = 0; y < r->height; y++ ) { - for( j = 0; j < morph->n_pass; j++ ) { - void *d; - - /* The last pass goes to the output image, - * intermediate passes go to t2. - */ - if( j == morph->n_pass - 1 ) - d = IM_REGION_ADDR( or, r->left, r->top + y ); - else - d = seq->t2; - - vips_executor_set_scanline( &executor[j], - ir, r->left, r->top + y ); - vips_executor_set_array( &executor[j], - morph->pass[j].r, seq->t1 ); - vips_executor_set_destination( &executor[j], d ); - vips_executor_run( &executor[j] ); - - IM_SWAP( void *, seq->t1, seq->t2 ); - } - } - - return( 0 ); -} - -/* Morph an image. - */ -static int -morphology( IMAGE *in, IMAGE *out, INTMASK *mask, MorphOp op ) -{ - Morph *morph; - im_generate_fn generate; - - /* Check parameters. - */ - if( !(morph = morph_new( in, out, mask, op )) ) - return( -1 ); - - /* Prepare output. Consider a 7x7 mask and a 7x7 image --- the output - * would be 1x1. - */ - if( im_cp_desc( morph->out, morph->in ) ) - return( -1 ); - morph->out->Xsize -= morph->mask->xsize - 1; - morph->out->Ysize -= morph->mask->ysize - 1; - if( morph->out->Xsize <= 0 || - morph->out->Ysize <= 0 ) { - im_error( "morph", "%s", _( "image too small for mask" ) ); - return( -1 ); - } - - if( morph->n_pass ) { - generate = morph_vector_gen; - -#ifdef DEBUG - printf( "morph_vector_gen: %d passes\n", morph->n_pass ); -#endif /*DEBUG*/ - } - else if( morph->op == DILATE ) - generate = dilate_gen; - else - generate = erode_gen; - - if( im_demand_hint( morph->out, IM_SMALLTILE, morph->in, NULL ) || - im_generate( morph->out, - morph_start, generate, morph_stop, morph->in, morph ) ) - return( -1 ); - - morph->out->Xoffset = -morph->mask->xsize / 2; - morph->out->Yoffset = -morph->mask->ysize / 2; - - return( 0 ); -} - -/* Keep the _raw versions for compat. - */ -int -im_dilate_raw( IMAGE *in, IMAGE *out, INTMASK *mask ) -{ - return( morphology( in, out, mask, DILATE ) ); -} - -int -im_erode_raw( IMAGE *in, IMAGE *out, INTMASK *mask ) -{ - return( morphology( in, out, mask, ERODE ) ); -} - -int -im_dilate( IMAGE *in, IMAGE *out, INTMASK *mask ) -{ - IMAGE *t1 = im_open_local( out, "im_dilate:1", "p" ); - - if( !t1 || - im_embed( in, t1, 1, mask->xsize / 2, mask->ysize / 2, - in->Xsize + mask->xsize - 1, - in->Ysize + mask->ysize - 1 ) || - morphology( t1, out, mask, DILATE ) ) - return( -1 ); - - out->Xoffset = 0; - out->Yoffset = 0; - - return( 0 ); -} - -int -im_erode( IMAGE *in, IMAGE *out, INTMASK *mask ) -{ - IMAGE *t1 = im_open_local( out, "im_erode:1", "p" ); - - if( !t1 || - im_embed( in, t1, 1, mask->xsize / 2, mask->ysize / 2, - in->Xsize + mask->xsize - 1, - in->Ysize + mask->ysize - 1 ) || - morphology( t1, out, mask, ERODE ) ) - return( -1 ); - - out->Xoffset = 0; - out->Yoffset = 0; - - return( 0 ); -} diff --git a/libvips/morphology/morph.c b/libvips/morphology/morph.c index db4e94f1..7f7ffc48 100644 --- a/libvips/morphology/morph.c +++ b/libvips/morphology/morph.c @@ -1,7 +1,31 @@ /* morphology * + * 19/9/95 JC + * - rewritten + * 6/7/99 JC + * - small tidies + * 7/4/04 + * - now uses im_embed() with edge stretching on the input, not + * the output + * - sets Xoffset / Yoffset + * 21/4/08 + * - only rebuild the buffer offsets if bpl changes + * - small cleanups + * 25/10/10 + * - start again from the Orc'd im_conv + * 29/10/10 + * - use VipsVector + * - do erode as well + * 7/11/10 + * - gtk-doc + * - do (!=0) to make uchar, if we're not given uchar + * 28/6/13 + * - oops, fix !=0 code * 23/10/13 * - from vips_conv() + * 25/2/20 kleisauke + * - rewritten as a class + * - merged with hitmiss */ /* @@ -31,24 +55,41 @@ */ -/* This is a simple wrapper over the old vips7 functions. At some point we - * should rewrite this as a pure vips8 class and redo the vips7 functions as - * wrappers over this. - */ - #ifdef HAVE_CONFIG_H #include #endif /*HAVE_CONFIG_H*/ #include #include +#include +#include #include -#include +#include +#include #include #include "pmorphology.h" +/* We can't run more than this many passes. Larger than this and we + * fall back to C. + * TODO: Could this be raised to 20? Just like convi. + */ +#define MAX_PASS (10) + +/* A pass with a vector. + */ +typedef struct { + int first; /* The index of the first mask coff we use */ + int last; /* The index of the last mask coff we use */ + + int r; /* Set previous result in this var */ + + /* The code we generate for this section of this mask. + */ + VipsVector *vector; +} Pass; + /** * VipsOperationMorphology: * @VIPS_OPERATION_MORPHOLOGY_ERODE: true if all set @@ -70,59 +111,684 @@ typedef struct { */ VipsImage *M; + int n_point; /* w * h for our matrix */ + + int *coeff; /* Mask coefficients */ + + /* The passes we generate for this mask. + */ + int n_pass; + Pass pass[MAX_PASS]; } VipsMorph; typedef VipsMorphologyClass VipsMorphClass; G_DEFINE_TYPE( VipsMorph, vips_morph, VIPS_TYPE_MORPHOLOGY ); +/* Our sequence value. + */ +typedef struct { + VipsMorph *morph; + VipsRegion *ir; /* Input region */ + + int *soff; /* Offsets we check for set */ + int ss; /* ... and number we check for set */ + int *coff; /* Offsets we check for clear */ + int cs; /* ... and number we check for clear */ + + int last_bpl; /* Avoid recalcing offsets, if we can */ + + /* In vector mode we need a pair of intermediate buffers to keep the + * results of each pass in. + */ + void *t1; + void *t2; +} VipsMorphSequence; + +static void +vips_morph_compile_free( VipsMorph *morph ) +{ + int i; + + for( i = 0; i < morph->n_pass; i++ ) + VIPS_FREEF( vips_vector_free, morph->pass[i].vector ); + morph->n_pass = 0; +} + +static void +vips_morph_dispose( GObject *gobject ) +{ + VipsMorph *morph = (VipsMorph *) gobject; + +#ifdef DEBUG + printf( "vips_morph_dispose: " ); + vips_object_print_name( VIPS_OBJECT( gobject ) ); + printf( "\n" ); +#endif /*DEBUG*/ + + vips_morph_compile_free( morph ); + + G_OBJECT_CLASS( vips_morph_parent_class )->dispose( gobject ); +} + +/* Free a sequence value. + */ +static int +vips_morph_stop( void *vseq, void *a, void *b ) +{ + VipsMorphSequence *seq = (VipsMorphSequence *) vseq; + + VIPS_UNREF( seq->ir ); + VIPS_FREE( seq->t1 ); + VIPS_FREE( seq->t2 ); + + return( 0 ); +} + +/* Morph start function. + */ +static void * +vips_morph_start( VipsImage *out, void *a, void *b ) +{ + VipsImage *in = (VipsImage *) a; + VipsMorph *morph = (VipsMorph *) b; + + VipsMorphSequence *seq; + + if( !(seq = VIPS_NEW( out, VipsMorphSequence )) ) + return( NULL ); + + /* Init! + */ + seq->morph = morph; + seq->ir = NULL; + seq->soff = NULL; + seq->ss = 0; + seq->coff = NULL; + seq->cs = 0; + seq->last_bpl = -1; + seq->t1 = NULL; + seq->t2 = NULL; + + seq->ir = vips_region_new( in ); + + /* C mode. + */ + seq->soff = VIPS_ARRAY( out, morph->n_point, int ); + seq->coff = VIPS_ARRAY( out, morph->n_point, int ); + + if( !seq->soff || + !seq->coff ) { + vips_morph_stop( seq, in, morph ); + return( NULL ); + } + + /* Vector mode. + */ + if( morph->n_pass ) { + seq->t1 = VIPS_ARRAY( NULL, + VIPS_IMAGE_N_ELEMENTS( in ), VipsPel ); + seq->t2 = VIPS_ARRAY( NULL, + VIPS_IMAGE_N_ELEMENTS( in ), VipsPel ); + + if( !seq->t1 || + !seq->t2 ) { + vips_morph_stop( seq, in, morph ); + return( NULL ); + } + } + + return( seq ); +} + +#define TEMP( N, S ) vips_vector_temporary( v, N, S ) +#define SCANLINE( N, P, S ) vips_vector_source_scanline( v, N, P, S ) +#define CONST( N, V, S ) vips_vector_constant( v, N, V, S ) +#define ASM2( OP, A, B ) vips_vector_asm2( v, OP, A, B ) +#define ASM3( OP, A, B, C ) vips_vector_asm3( v, OP, A, B, C ) + +/* Generate code for a section of the mask. first is the index we start + * at, we set last to the index of the last one we use before we run + * out of intermediates / constants / parameters / sources or mask + * coefficients. + * + * 0 for success, -1 on error. + */ +static int +vips_morph_compile_section( VipsMorph *morph, Pass *pass, gboolean first_pass ) +{ + VipsMorphology *morphology = (VipsMorphology *) morph; + VipsImage *M = morph->M; + + VipsVector *v; + char offset[256]; + char source[256]; + char zero[256]; + char one[256]; + int i; + + pass->vector = v = vips_vector_new( "morph", 1 ); + + /* The value we fetch from the image, the accumulated sum. + */ + TEMP( "value", 1 ); + TEMP( "sum", 1 ); + + CONST( zero, 0, 1 ); + CONST( one, 255, 1 ); + + /* Init the sum. If this is the first pass, it's a constant. If this + * is a later pass, we have to init the sum from the result + * of the previous pass. + */ + if( first_pass ) { + if( morph->morph == VIPS_OPERATION_MORPHOLOGY_DILATE ) + ASM2( "copyb", "sum", zero ); + else + ASM2( "copyb", "sum", one ); + } + else { + /* "r" is the result of the previous pass. + */ + pass->r = vips_vector_source_name( v, "r", 1 ); + ASM2( "loadb", "sum", "r" ); + } + + for( i = pass->first; i < morph->n_point; i++ ) { + int x = i % M->Xsize; + int y = i / M->Xsize; + + /* Exclude don't-care elements. + */ + if( morph->coeff[i] == 128 ) + continue; + + /* The source. sl0 is the first scanline in the mask. + */ + SCANLINE( source, y, 1 ); + + /* The offset, only for non-first-columns though. + */ + if( x > 0 ) { + CONST( offset, morphology->in->Bands * x, 1 ); + ASM3( "loadoffb", "value", source, offset ); + } + else + ASM2( "loadb", "value", source ); + + /* Join to our sum. If the mask element is zero, we have to + * add an extra negate. + */ + if( morph->morph == VIPS_OPERATION_MORPHOLOGY_DILATE ) { + if( !morph->coeff[i] ) + ASM3( "xorb", "value", "value", one ); + ASM3( "orb", "sum", "sum", "value" ); + } + else { + if( !morph->coeff[i] ) { + /* You'd think we could use andnb, but it + * fails on some machines with some orc + * versions :( + */ + ASM3( "xorb", "value", "value", one ); + ASM3( "andb", "sum", "sum", "value" ); + } + else + ASM3( "andb", "sum", "sum", "value" ); + } + + if( vips_vector_full( v ) ) + break; + } + + pass->last = i; + + ASM2( "copyb", "d1", "sum" ); + + if( !vips_vector_compile( v ) ) + return( -1 ); + +#ifdef DEBUG + printf( "done matrix coeffs %d to %d\n", pass->first, pass->last ); + vips_vector_print( v ); +#endif /*DEBUG*/ + + return( 0 ); +} + +/* Generate a set of passes. + */ +static int +vips_morph_compile( VipsMorph *morph ) +{ + int i; + Pass *pass; + +#ifdef DEBUG + printf( "vips_morph_compile: generating vector code\n" ); +#endif /*DEBUG*/ + + /* Generate passes until we've used up the whole mask. + */ + for( i = 0;;) { + /* Skip any don't-care coefficients at the start of the mask + * region. + */ + for( ; i < morph->n_point && morph->coeff[i] == 128; i++ ) + ; + if( i == morph->n_point ) + break; + + /* Allocate space for another pass. + */ + if( morph->n_pass == MAX_PASS ) + return( -1 ); + pass = &morph->pass[morph->n_pass]; + morph->n_pass += 1; + + pass->first = i; + pass->last = i; + pass->r = -1; + + if( vips_morph_compile_section( morph, pass, morph->n_pass == 1 ) ) + return( -1 ); + i = pass->last + 1; + + if( i >= morph->n_point ) + break; + } + + return( 0 ); +} + +/* Dilate! + */ +static int +vips_dilate_gen( VipsRegion *or, + void *vseq, void *a, void *b, gboolean *stop ) +{ + VipsMorphSequence *seq = (VipsMorphSequence *) vseq; + VipsMorph *morph = (VipsMorph *) b; + VipsImage *M = morph->M; + VipsRegion *ir = seq->ir; + int * restrict t = morph->coeff; + + int *soff = seq->soff; + int *coff = seq->coff; + + VipsRect *r = &or->valid; + int le = r->left; + int to = r->top; + int bo = VIPS_RECT_BOTTOM( r ); + int sz = VIPS_REGION_N_ELEMENTS( or ); + + VipsRect s; + int x, y; + int result, i; + + /* Prepare the section of the input image we need. A little larger + * than the section of the output image we are producing. + */ + s = *r; + s.width += M->Xsize - 1; + s.height += M->Ysize - 1; + if( vips_region_prepare( ir, &s ) ) + return( -1 ); + +#ifdef DEBUG_VERBOSE + printf( "vips_dilate_gen: preparing %dx%d@%dx%d pixels\n", + s.width, s.height, s.left, s.top ); +#endif /*DEBUG_VERBOSE*/ + + /* Scan mask, building offsets we check when processing. Only do this + * if the bpl has changed since the previous vips_region_prepare(). + */ + if( seq->last_bpl != VIPS_REGION_LSKIP( ir ) ) { + seq->last_bpl = VIPS_REGION_LSKIP( ir ); + + seq->ss = 0; + seq->cs = 0; + for( y = 0; y < M->Ysize; y++ ) + for( x = 0; x < M->Xsize; x++ ) + switch( t[x] ) { + case 255: + soff[seq->ss++] = + VIPS_REGION_ADDR( ir, + x + le, y + to ) - + VIPS_REGION_ADDR( ir, le, to ); + break; + + case 128: + break; + + case 0: + coff[seq->cs++] = + VIPS_REGION_ADDR( ir, + x + le, y + to ) - + VIPS_REGION_ADDR( ir, le, to ); + break; + + default: + g_assert_not_reached(); + } + } + + /* Dilate! + */ + for( y = to; y < bo; y++ ) { + VipsPel *p = VIPS_REGION_ADDR( ir, le, y ); + VipsPel *q = VIPS_REGION_ADDR( or, le, y ); + + /* Loop along line. + */ + for( x = 0; x < sz; x++, q++, p++ ) { + /* Search for a hit on the set list. + */ + result = 0; + for( i = 0; i < seq->ss; i++ ) + if( p[soff[i]] ) { + /* Found a match! + */ + result = 255; + break; + } + + /* No set pixels ... search for a hit in the clear + * pixels. + */ + if( !result ) + for( i = 0; i < seq->cs; i++ ) + if( !p[coff[i]] ) { + /* Found a match! + */ + result = 255; + break; + } + + *q = result; + } + } + + return( 0 ); +} + +/* Erode! + */ +static int +vips_erode_gen( VipsRegion *or, + void *vseq, void *a, void *b, gboolean *stop ) +{ + VipsMorphSequence *seq = (VipsMorphSequence *) vseq; + VipsMorph *morph = (VipsMorph *) b; + VipsImage *M = morph->M; + VipsRegion *ir = seq->ir; + int * restrict t = morph->coeff; + + int *soff = seq->soff; + int *coff = seq->coff; + + VipsRect *r = &or->valid; + int le = r->left; + int to = r->top; + int bo = VIPS_RECT_BOTTOM( r ); + int sz = VIPS_REGION_N_ELEMENTS( or ); + + VipsRect s; + int x, y; + int result, i; + + /* Prepare the section of the input image we need. A little larger + * than the section of the output image we are producing. + */ + s = *r; + s.width += M->Xsize - 1; + s.height += M->Ysize - 1; + if( vips_region_prepare( ir, &s ) ) + return( -1 ); + +#ifdef DEBUG_VERBOSE + printf( "vips_erode_gen: preparing %dx%d@%dx%d pixels\n", + s.width, s.height, s.left, s.top ); +#endif /*DEBUG_VERBOSE*/ + + /* Scan mask, building offsets we check when processing. Only do this + * if the bpl has changed since the previous vips_region_prepare(). + */ + if( seq->last_bpl != VIPS_REGION_LSKIP( ir ) ) { + seq->last_bpl = VIPS_REGION_LSKIP( ir ); + + seq->ss = 0; + seq->cs = 0; + for( y = 0; y < M->Ysize; y++ ) + for( x = 0; x < M->Xsize; x++ ) + switch( t[x] ) { + case 255: + soff[seq->ss++] = + VIPS_REGION_ADDR( ir, + x + le, y + to ) - + VIPS_REGION_ADDR( ir, le, to ); + break; + + case 128: + break; + + case 0: + coff[seq->cs++] = + VIPS_REGION_ADDR( ir, + x + le, y + to ) - + VIPS_REGION_ADDR( ir, le, to ); + break; + + default: + g_assert_not_reached(); + } + } + + /* Erode! + */ + for( y = to; y < bo; y++ ) { + VipsPel *p = VIPS_REGION_ADDR( ir, le, y ); + VipsPel *q = VIPS_REGION_ADDR( or, le, y ); + + /* Loop along line. + */ + for( x = 0; x < sz; x++, q++, p++ ) { + /* Check all set pixels are set. + */ + result = 255; + for( i = 0; i < seq->ss; i++ ) + if( !p[soff[i]] ) { + /* Found a mismatch! + */ + result = 0; + break; + } + + /* Check all clear pixels are clear. + */ + if( result ) + for( i = 0; i < seq->cs; i++ ) + if( p[coff[i]] ) { + result = 0; + break; + } + + *q = result; + } + } + + return( 0 ); +} + +/* The vector codepath. + */ +static int +vips_morph_gen_vector( VipsRegion *or, + void *vseq, void *a, void *b, gboolean *stop ) +{ + VipsMorphSequence *seq = (VipsMorphSequence *) vseq; + VipsMorph *morph = (VipsMorph *) b; + VipsImage *M = morph->M; + VipsRegion *ir = seq->ir; + VipsRect *r = &or->valid; + int sz = VIPS_REGION_N_ELEMENTS( or ); + + VipsRect s; + int y, j; + VipsExecutor executor[MAX_PASS]; + + /* Prepare the section of the input image we need. A little larger + * than the section of the output image we are producing. + */ + s = *r; + s.width += M->Xsize - 1; + s.height += M->Ysize - 1; + if( vips_region_prepare( ir, &s ) ) + return( -1 ); + +#ifdef DEBUG_VERBOSE + printf( "vips_morph_gen_vector: preparing %dx%d@%dx%d pixels\n", + s.width, s.height, s.left, s.top ); +#endif /*DEBUG_VERBOSE*/ + + for( j = 0; j < morph->n_pass; j++ ) + vips_executor_set_program( &executor[j], + morph->pass[j].vector, sz ); + + VIPS_GATE_START( "vips_morph_gen_vector: work" ); + + for( y = 0; y < r->height; y++ ) { + for( j = 0; j < morph->n_pass; j++ ) { + void *d; + + /* The last pass goes to the output image, + * intermediate passes go to t2. + */ + if( j == morph->n_pass - 1 ) + d = VIPS_REGION_ADDR( or, r->left, r->top + y ); + else + d = seq->t2; + + vips_executor_set_scanline( &executor[j], + ir, r->left, r->top + y ); + vips_executor_set_array( &executor[j], + morph->pass[j].r, seq->t1 ); + vips_executor_set_destination( &executor[j], d ); + vips_executor_run( &executor[j] ); + + VIPS_SWAP( void *, seq->t1, seq->t2 ); + } + } + + VIPS_GATE_STOP( "vips_morph_gen_vector: work" ); + + VIPS_COUNT_PIXELS( or, "vips_morph_gen_vector" ); + + return( 0 ); +} + static int vips_morph_build( VipsObject *object ) { VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object ); VipsMorphology *morphology = (VipsMorphology *) object; VipsMorph *morph = (VipsMorph *) object; - VipsImage **t = (VipsImage **) vips_object_local_array( object, 2 ); + VipsImage **t = (VipsImage **) vips_object_local_array( object, 5 ); - INTMASK *imsk; VipsImage *in; - - g_object_set( morph, "out", vips_image_new(), NULL ); + VipsImage *M; + VipsGenerateFn generate; + double *coeff; + int i; if( VIPS_OBJECT_CLASS( vips_morph_parent_class )->build( object ) ) return( -1 ); - in = morphology->in; + in = morphology->in; + /* Unpack for processing. + */ if( vips_image_decode( in, &t[0] ) ) return( -1 ); in = t[0]; if( vips_check_matrix( class->nickname, morph->mask, &t[1] ) ) return( -1 ); - morph->M = t[1]; + morph->M = M = t[1]; + morph->n_point = M->Xsize * M->Ysize; - if( !(imsk = im_vips2imask( morph->M, class->nickname )) || - !im_local_imask( morph->out, imsk ) ) + if( vips_embed( in, &t[2], + M->Xsize / 2, M->Ysize / 2, + in->Xsize + M->Xsize - 1, in->Ysize + M->Ysize - 1, + "extend", VIPS_EXTEND_COPY, + NULL ) ) + return( -1 ); + in = t[2]; + + /* Make sure we are uchar. + */ + if( vips_cast( in, &t[3], VIPS_FORMAT_UCHAR, NULL ) ) + return( -1 ); + in = t[3]; + + /* Make an int version of our mask. + */ + if( vips__image_intize( M, &t[4] ) ) return( -1 ); + M = t[4]; - switch( morph->morph ) { - case VIPS_OPERATION_MORPHOLOGY_DILATE: - if( im_dilate( in, morph->out, imsk ) ) - return( -1 ); - break; + coeff = VIPS_MATRIX( M, 0, 0 ); + if( !(morph->coeff = VIPS_ARRAY( object, morph->n_point, int )) ) + return( -1 ); - case VIPS_OPERATION_MORPHOLOGY_ERODE: - if( im_erode( in, morph->out, imsk ) ) - return( -1 ); - break; - - default: - g_assert_not_reached(); + for( i = 0; i < morph->n_point; i++ ) { + if( coeff[i] != 0 && + coeff[i] != 128 && + coeff[i] != 255 ) { + vips_error( class->nickname, + _( "bad mask element (%f " + "should be 0, 128 or 255)" ), + coeff[i] ); + return( -1 ); + } + morph->coeff[i] = coeff[i]; } - vips_reorder_margin_hint( morph->out, - morph->M->Xsize * morph->M->Ysize ); + /* Default to the C path. + */ + generate = morph->morph == VIPS_OPERATION_MORPHOLOGY_DILATE + ? vips_dilate_gen : vips_erode_gen; + + /* Generate code for this mask / image, if possible. + */ + if( vips_vector_isenabled() ) { + if( !vips_morph_compile( morph ) ) { + generate = vips_morph_gen_vector; + g_info( "morph: using vector path" ); + } + else + vips_morph_compile_free( morph ); + } + + g_object_set( morph, "out", vips_image_new(), NULL ); + if( vips_image_pipelinev( morph->out, + VIPS_DEMAND_STYLE_SMALLTILE, in, NULL ) ) + return( -1 ); + + /* Prepare output. Consider a 7x7 mask and a 7x7 image --- the output + * would be 1x1. + */ + morph->out->Xsize -= M->Xsize - 1; + morph->out->Ysize -= M->Ysize - 1; + + if( vips_image_generate( morph->out, + vips_morph_start, generate, vips_morph_stop, in, morph ) ) + return( -1 ); + + morph->out->Xoffset = -M->Xsize / 2; + morph->out->Yoffset = -M->Ysize / 2; + + vips_reorder_margin_hint( morph->out, morph->n_point ); return( 0 ); } @@ -136,6 +802,8 @@ vips_morph_class_init( VipsMorphClass *class ) gobject_class->set_property = vips_object_set_property; gobject_class->get_property = vips_object_get_property; + gobject_class->dispose = vips_morph_dispose; + object_class->nickname = "morph"; object_class->description = _( "morphology operation" ); object_class->build = vips_morph_build; @@ -166,6 +834,7 @@ static void vips_morph_init( VipsMorph *morph ) { morph->morph = VIPS_OPERATION_MORPHOLOGY_ERODE; + morph->coeff = NULL; } /** @@ -205,7 +874,7 @@ vips_morph_init( VipsMorph *morph ) * for analogues of the usual set difference and set union operations. * * Operations are performed using the processor's vector unit, - * if possible. Disable this with --vips-novector or IM_NOVECTOR. + * if possible. Disable this with --vips-novector or VIPS_NOVECTOR. * * Returns: 0 on success, -1 on error */ diff --git a/libvips/morphology/morphology.c b/libvips/morphology/morphology.c index fa055362..4ff8ae8e 100644 --- a/libvips/morphology/morphology.c +++ b/libvips/morphology/morphology.c @@ -132,11 +132,11 @@ vips_morphology_init( VipsMorphology *morphology ) void vips_morphology_operation_init( void ) { - extern int vips_morph_get_type( void ); - extern int vips_rank_get_type( void ); - extern int vips_countlines_get_type( void ); - extern int vips_labelregions_get_type( void ); - extern int vips_fill_nearest_get_type( void ); + extern GType vips_morph_get_type( void ); + extern GType vips_rank_get_type( void ); + extern GType vips_countlines_get_type( void ); + extern GType vips_labelregions_get_type( void ); + extern GType vips_fill_nearest_get_type( void ); vips_morph_get_type(); vips_rank_get_type(); diff --git a/libvips/morphology/nearest.c b/libvips/morphology/nearest.c index e7c542c6..6c252f88 100644 --- a/libvips/morphology/nearest.c +++ b/libvips/morphology/nearest.c @@ -201,7 +201,8 @@ vips_fill_nearest_grow_seed( VipsFillNearest *nearest, Seed *seed ) circle.nearest_pixel = vips_fill_nearest_pixel_clip; vips__draw_circle_direct( nearest->distance, - seed->x, seed->y, seed->r, vips_fill_nearest_scanline, &circle ); + seed->x, seed->y, seed->r, + vips_fill_nearest_scanline, &circle ); /* Update the action_mask for this seed. Next time, we can skip any * octants where we failed to act this time. diff --git a/libvips/morphology/rank.c b/libvips/morphology/rank.c index e9aa7661..351a0ce3 100644 --- a/libvips/morphology/rank.c +++ b/libvips/morphology/rank.c @@ -99,7 +99,7 @@ vips_rank_stop( void *vseq, void *a, void *b ) { VipsRankSequence *seq = (VipsRankSequence *) vseq; - VIPS_FREEF( g_object_unref, seq->ir ); + VIPS_UNREF( seq->ir ); return( 0 ); } diff --git a/libvips/mosaicing/Makefile.am b/libvips/mosaicing/Makefile.am index 35f23916..d7da3b87 100644 --- a/libvips/mosaicing/Makefile.am +++ b/libvips/mosaicing/Makefile.am @@ -6,6 +6,7 @@ libmosaicing_la_SOURCES = \ mosaic.c \ match.c \ mosaic1.c \ + matrixinvert.c \ global_balance.c \ im_avgdxdy.c \ im_chkpair.c \ diff --git a/libvips/mosaicing/global_balance.c b/libvips/mosaicing/global_balance.c index 9b6b64fe..190db7e2 100644 --- a/libvips/mosaicing/global_balance.c +++ b/libvips/mosaicing/global_balance.c @@ -58,6 +58,8 @@ * 12/7/12 * - always allocate local to an output descriptor ... stops ref cycles * with the new base class + * 18/6/20 kleisauke + * - convert to vips8 */ /* @@ -108,7 +110,6 @@ #include #include -#include #include #include "pmosaicing.h" @@ -146,7 +147,7 @@ break_items( char *line, char **out ) out[i] = line = p + 1; if( !(p = strchr( line, '>' )) ) { - im_error( "break_files", "%s", _( "no matching '>'" ) ); + vips_error( "break_files", "%s", _( "no matching '>'" ) ); return( -1 ); } @@ -155,7 +156,7 @@ break_items( char *line, char **out ) } if( i == MAX_ITEMS ) { - im_error( "break_files", "%s", _( "too many items" ) ); + vips_error( "break_files", "%s", _( "too many items" ) ); return( -1 ); } @@ -164,24 +165,35 @@ break_items( char *line, char **out ) /* Try to open a file. If full path fails, try the current directory. */ -IMAGE * -im__global_open_image( SymbolTable *st, char *name ) +VipsImage * +vips__global_open_image( SymbolTable *st, char *name ) { - IMAGE *im; + char *basename; + VipsImage *image; - if( (im = im_open_local( st->im, name, "r" )) || - (im = im_open_local( st->im, im_skip_dir( name ), "r" )) ) - return( im ); + if( !(image = vips_image_new_from_file( name, NULL ))) { + /* TODO(kleisauke): Is this behavior the same as im_skip_dir? + * i.e. could we open a filename which came + * from a win32 (`\\`) on a *nix machine? */ + basename = g_path_get_basename( name ); - return( NULL ); + if( !(image = vips_image_new_from_file( basename, NULL ))) { + g_free( basename ); + return( NULL ); + } + + g_free( basename ); + } + + vips_object_local( st->im, image ); + + return( image ); } -static int -junk_node( JoinNode *node ) +static void +junk_node( VipsImage *image, JoinNode *node ) { - IM_FREEF( g_slist_free, node->overlaps ); - - return( 0 ); + VIPS_FREEF( g_slist_free, node->overlaps ); } /* Hash from a filename to an index into symbol_table. @@ -204,12 +216,13 @@ hash( char *n ) static JoinNode * build_node( SymbolTable *st, char *name ) { - JoinNode *node = IM_NEW( st->im, JoinNode ); + JoinNode *node = VIPS_NEW( st->im, JoinNode ); int n = hash( name ); /* Fill fields. */ - if( !node || !(node->name = im_strdup( st->im, name )) ) + if( !node || !(node->name = + vips_strdup( VIPS_OBJECT( st->im ), name )) ) return( NULL ); node->type = JOIN_LEAF; @@ -224,13 +237,12 @@ build_node( SymbolTable *st, char *name ) node->im = NULL; node->index = 0; - if( im_add_close_callback( st->im, - (im_callback_fn) junk_node, node, NULL ) ) - return( NULL ); + g_signal_connect( st->im, "close", + G_CALLBACK( junk_node ), node ); /* Try to open. */ - if( (node->im = im__global_open_image( st, name )) ) { + if( (node->im = vips__global_open_image( st, name )) ) { /* There is a file there - set width and height. */ node->cumtrn.oarea.width = node->im->Xsize; @@ -239,7 +251,7 @@ build_node( SymbolTable *st, char *name ) else { /* Clear the error buffer to lessen confusion. */ - im_error_clear(); + vips_error_clear(); } st->table[n] = g_slist_prepend( st->table[n], node ); @@ -250,9 +262,9 @@ build_node( SymbolTable *st, char *name ) /* Make a new overlap struct. */ static OverlapInfo * -build_overlap( JoinNode *node, JoinNode *other, Rect *overlap ) +build_overlap( JoinNode *node, JoinNode *other, VipsRect *overlap ) { - OverlapInfo *lap = IM_NEW( node->st->im, OverlapInfo ); + OverlapInfo *lap = VIPS_NEW( node->st->im, OverlapInfo ); if( !lap ) return( NULL ); @@ -278,27 +290,24 @@ overlap_destroy( OverlapInfo *lap ) node->st->novl--; } -static int -junk_table( SymbolTable *st ) -{ +static void +junk_table( VipsImage *image, SymbolTable *st ) { int i; for( i = 0; i < st->sz; i++ ) - IM_FREEF( g_slist_free, st->table[i] ); - - return( 0 ); + VIPS_FREEF( g_slist_free, st->table[i] ); } /* Build a new symbol table. */ SymbolTable * -im__build_symtab( IMAGE *out, int sz ) +vips__build_symtab( VipsImage *out, int sz ) { - SymbolTable *st = IM_NEW( out, SymbolTable ); + SymbolTable *st = VIPS_NEW( out, SymbolTable ); int i; if( !st || - !(st->table = IM_ARRAY( out, sz, GSList * )) ) + !(st->table = VIPS_ARRAY( out, sz, GSList * )) ) return( NULL ); st->sz = sz; st->im = out; @@ -309,9 +318,8 @@ im__build_symtab( IMAGE *out, int sz ) st->leaf = NULL; st->fac = NULL; - if( im_add_close_callback( out, - (im_callback_fn) junk_table, st, NULL ) ) - return( NULL ); + g_signal_connect( out, "close", + G_CALLBACK( junk_table ), st ); for( i = 0; i < sz; i++ ) st->table[i] = NULL; @@ -322,7 +330,7 @@ im__build_symtab( IMAGE *out, int sz ) /* Does this node have this file name? */ static JoinNode * -test_name( JoinNode *node, char *name ) +test_name( JoinNode *node, char *name, void *b ) { if( strcmp( node->name, name ) == 0 ) return( node ); @@ -335,8 +343,8 @@ test_name( JoinNode *node, char *name ) static JoinNode * find_node( SymbolTable *st, char *name ) { - return( im_slist_map2( st->table[hash( name )], - (VSListMap2Fn) test_name, name, NULL ) ); + return( vips_slist_map2( st->table[hash( name )], + (VipsSListMap2Fn) test_name, name, NULL ) ); } /* Given a name: return either the existing node for that name, or a new node @@ -357,13 +365,13 @@ add_node( SymbolTable *st, char *name ) /* Map a user function over the whole of the symbol table. */ void * -im__map_table( SymbolTable *st, VSListMap2Fn fn, void *a, void *b ) +vips__map_table( SymbolTable *st, VipsSListMap2Fn fn, void *a, void *b ) { int i; void *r; for( i = 0; i < st->sz; i++ ) - if( (r = im_slist_map2( st->table[i], fn, a, b )) ) + if( (r = vips_slist_map2( st->table[i], fn, a, b )) ) return( r ); return( NULL ); @@ -372,7 +380,7 @@ im__map_table( SymbolTable *st, VSListMap2Fn fn, void *a, void *b ) /* Set the dirty field on a join. */ static void * -set_dirty( JoinNode *node, int state ) +set_dirty( JoinNode *node, int state, void *b ) { node->dirty = state; @@ -384,7 +392,7 @@ set_dirty( JoinNode *node, int state ) static void clean_table( SymbolTable *st ) { - (void) im__map_table( st, + (void) vips__map_table( st, (VipsSListMap2Fn) set_dirty, (void *) 0, NULL ); } @@ -394,7 +402,7 @@ clean_table( SymbolTable *st ) static void calc_geometry( JoinNode *node ) { - Rect um; + VipsRect um; switch( node->type ) { case JOIN_LR: @@ -403,7 +411,7 @@ calc_geometry( JoinNode *node ) case JOIN_TBROTSCALE: /* Join two areas. */ - im_rect_unionrect( &node->arg1->cumtrn.oarea, + vips_rect_unionrect( &node->arg1->cumtrn.oarea, &node->arg2->cumtrn.oarea, &um ); node->cumtrn.iarea.left = 0; node->cumtrn.iarea.top = 0; @@ -431,24 +439,24 @@ calc_geometry( JoinNode *node ) break; default: - error_exit( "internal error #98356" ); + vips_error_exit( "internal error #98356" ); /*NOTREACHED*/ } } -/* Propogate a transform down a tree. If dirty is set, we've been here before, +/* Propagate a transform down a tree. If dirty is set, we've been here before, * so there is a doubling up of this node. If this is a leaf, then we have the * same leaf twice (which, in fact, we can cope with); if this is a node, we * have circularity. */ static int -propogate_transform( JoinNode *node, VipsTransformation *trn ) +propagate_transform( JoinNode *node, VipsTransformation *trn ) { if( !node ) return( 0 ); if( node->dirty && node->arg1 && node->arg2 ) { - im_error( "im_global_balance", + vips_error( "vips_global_balance", "%s", _( "circularity detected" ) ); return( -1 ); } @@ -456,8 +464,8 @@ propogate_transform( JoinNode *node, VipsTransformation *trn ) /* Transform our children. */ - if( propogate_transform( node->arg1, trn ) || - propogate_transform( node->arg2, trn ) ) + if( propagate_transform( node->arg1, trn ) || + propagate_transform( node->arg2, trn ) ) return( -1 ); /* Transform us, and recalculate our position and size. @@ -469,7 +477,7 @@ propogate_transform( JoinNode *node, VipsTransformation *trn ) } /* Ah ha! A leaf is actually made up of two smaller files with an lr or a tb - * merge. Turn a leaf node into a join node. Propogate the transform down + * merge. Turn a leaf node into a join node. Propagate the transform down * arg2's side of the tree. */ static int @@ -482,7 +490,7 @@ make_join( SymbolTable *st, JoinType type, /* Check output is ok. */ if( out->type != JOIN_LEAF ) { - im_error( "im_global_balance", + vips_error( "vips_global_balance", _( "image \"%s\" used twice as output" ), out->name ); return( -1 ); } @@ -506,11 +514,11 @@ make_join( SymbolTable *st, JoinType type, out->thistrn.odx = dx; out->thistrn.ody = dy; - /* Clean the table and propogate the transform down the RHS of the + /* Clean the table and propagate the transform down the RHS of the * graph. */ clean_table( st ); - if( propogate_transform( arg2, &out->thistrn ) ) + if( propagate_transform( arg2, &out->thistrn ) ) return( -1 ); /* Find the position and size of our output. @@ -528,7 +536,7 @@ make_join( SymbolTable *st, JoinType type, trn.odx = -out->cumtrn.oarea.left; trn.ody = -out->cumtrn.oarea.top; clean_table( st ); - if( propogate_transform( out, &trn ) ) + if( propagate_transform( out, &trn ) ) return( -1 ); return( 0 ); @@ -542,7 +550,7 @@ make_copy( SymbolTable *st, JoinNode *before, JoinNode *after ) /* Check output is ok. */ if( after->type != JOIN_LEAF ) { - im_error( "im_global_balance", + vips_error( "vips_global_balance", _( "image \"%s\" used twice as output" ), after->name ); return( -1 ); } @@ -573,10 +581,10 @@ process_line( SymbolTable *st, const char *text ) /* We destroy line during the parse. */ - im_strncpy( line, text, 1024 ); + vips_strncpy( line, text, 1024 ); - if( im_isprefix( "#LRJOIN ", line ) || - im_isprefix( "#TBJOIN ", line ) ) { + if( vips_isprefix( "#LRJOIN ", line ) || + vips_isprefix( "#TBJOIN ", line ) ) { /* Yes: magic join command. Break into tokens. Format is eg. #LRJOIN [] @@ -591,7 +599,7 @@ process_line( SymbolTable *st, const char *text ) if( (nitems = break_items( line, item )) < 0 ) return( -1 ); if( nitems != 5 && nitems != 6 ) { - im_error( "global_balance", + vips_error( "global_balance", "%s", _( "bad number of args in join line" ) ); return( -1 ); } @@ -606,7 +614,7 @@ process_line( SymbolTable *st, const char *text ) mwidth = atoi( item[5] ); else mwidth = -1; - if( im_isprefix( "#LRJOIN ", line ) ) + if( vips_isprefix( "#LRJOIN ", line ) ) type = JOIN_LR; else type = JOIN_TB; @@ -615,8 +623,8 @@ process_line( SymbolTable *st, const char *text ) join, 1.0, 0.0, dx, dy, mwidth ) ) return( -1 ); } - else if( im_isprefix( "#LRROTSCALE ", line ) || - im_isprefix( "#TBROTSCALE ", line ) ) { + else if( vips_isprefix( "#LRROTSCALE ", line ) || + vips_isprefix( "#TBROTSCALE ", line ) ) { /* Rot + scale. Format is eg. #LRROTSCALE \ @@ -633,7 +641,7 @@ process_line( SymbolTable *st, const char *text ) if( (nitems = break_items( line, item )) < 0 ) return( -1 ); if( nitems != 7 && nitems != 8 ) { - im_error( "global_balance", + vips_error( "global_balance", "%s", _( "bad number of args in join1 line" ) ); return( -1 ); } @@ -650,7 +658,7 @@ process_line( SymbolTable *st, const char *text ) mwidth = atoi( item[7] ); else mwidth = -1; - if( im_isprefix( "#LRROTSCALE ", line ) ) + if( vips_isprefix( "#LRROTSCALE ", line ) ) type = JOIN_LRROTSCALE; else type = JOIN_TBROTSCALE; @@ -659,8 +667,8 @@ process_line( SymbolTable *st, const char *text ) join, a, b, dx, dy, mwidth ) ) return( -1 ); } - else if( im_isprefix( "copy ", line ) ) { - /* im_copy() call ... make a JOIN_CP node. + else if( vips_isprefix( "copy ", line ) ) { + /* vips_copy() call ... make a JOIN_CP node. */ char *item[MAX_ITEMS]; int nitems; @@ -669,7 +677,7 @@ process_line( SymbolTable *st, const char *text ) if( (nitems = break_items( line, item )) < 0 ) return( -1 ); if( nitems != 2 ) { - im_error( "global_balance", + vips_error( "global_balance", "%s", _( "bad number of args in copy line" ) ); return( -1 ); } @@ -686,7 +694,7 @@ process_line( SymbolTable *st, const char *text ) /* Set the dirty flag on any nodes we reference. */ static void * -set_referenced( JoinNode *node ) +set_referenced( JoinNode *node, void *a, void *b ) { if( node->arg1 ) node->arg1->dirty = 1; @@ -699,7 +707,7 @@ set_referenced( JoinNode *node ) /* Is this a root node? Should be clean. */ static void * -is_root( JoinNode *node ) +is_root( JoinNode *node, void *a, void *b ) { if( !node->dirty ) return( (void *) node ); @@ -717,17 +725,17 @@ find_root( SymbolTable *st ) /* Clean the table, then scan it, setting all pointed-to nodes dirty. */ clean_table( st ); - im__map_table( st, (VipsSListMap2Fn) set_referenced, NULL, NULL ); + vips__map_table( st, (VipsSListMap2Fn) set_referenced, NULL, NULL ); /* Look for the first clean symbol. */ - root = (JoinNode *) im__map_table( st, + root = (JoinNode *) vips__map_table( st, (VipsSListMap2Fn) is_root, NULL, NULL ); /* No root? Hot dang! */ if( !root ) { - im_error( "im_global_balance", + vips_error( "vips_global_balance", "%s", _( "mosaic root not found in desc file\n" "is this really a mosaiced image?" ) ); return( NULL ); @@ -737,8 +745,8 @@ find_root( SymbolTable *st ) * more than one root. */ root->dirty = 1; - if( im__map_table( st, (VipsSListMap2Fn) is_root, NULL, NULL ) ) { - im_error( "im_global_balance", + if( vips__map_table( st, (VipsSListMap2Fn) is_root, NULL, NULL ) ) { + vips_error( "vips_global_balance", "%s", _( "more than one root" ) ); return( NULL ); } @@ -749,16 +757,16 @@ find_root( SymbolTable *st ) /* Walk history_list and parse each line. */ int -im__parse_desc( SymbolTable *st, IMAGE *in ) +vips__parse_desc( SymbolTable *st, VipsImage *in ) { GSList *p; for( p = in->history_list; p; p = p->next ) { GValue *value = (GValue *) p->data; - g_assert( G_VALUE_TYPE( value ) == IM_TYPE_REF_STRING ); + g_assert( G_VALUE_TYPE( value ) == VIPS_TYPE_REF_STRING ); - if( process_line( st, im_ref_string_get( value ) ) ) + if( process_line( st, vips_value_get_ref_string( value, NULL ) ) ) return( -1 ); } @@ -773,7 +781,7 @@ im__parse_desc( SymbolTable *st, IMAGE *in ) /* Count and index all leaf images. */ static void * -count_leaves( JoinNode *node ) +count_leaves( JoinNode *node, void *a, void *b ) { if( node->type == JOIN_LEAF ) { node->index = node->st->nim; @@ -789,11 +797,13 @@ count_leaves( JoinNode *node ) static void print_node( JoinNode *node ) { + char *basename = g_path_get_basename( node->name ); printf( "%s, position %dx%d, size %dx%d, index %d\n", - im_skip_dir( node->name ), + basename, node->cumtrn.oarea.left, node->cumtrn.oarea.top, node->cumtrn.oarea.width, node->cumtrn.oarea.height, node->index ); + g_free( basename ); } #endif /*DEBUG*/ @@ -801,7 +811,7 @@ print_node( JoinNode *node ) /* Print a leaf. */ static void * -print_leaf( JoinNode *node ) +print_leaf( JoinNode *node, void *a, void *b ) { if( node->type == JOIN_LEAF ) print_node( node ); @@ -813,7 +823,7 @@ print_leaf( JoinNode *node ) /* Count all join nodes. */ static void * -count_joins( JoinNode *node ) +count_joins( JoinNode *node, void *a, void *b ) { if( node->type == JOIN_TB || node->type == JOIN_LR || @@ -850,7 +860,7 @@ JoinType2char( JoinType type ) case JOIN_LEAF: return( "JOIN_LEAF" ); default: - error_exit( "internal error #9275" ); + vips_error_exit( "internal error #9275" ); /*NOTEACHED*/ return( NULL ); @@ -864,6 +874,8 @@ JoinType2char( JoinType type ) static void * print_joins( JoinNode *node, int indent ) { + char *basename = g_path_get_basename( node->name ); + switch( node->type ) { case JOIN_TB: case JOIN_LR: @@ -872,29 +884,31 @@ print_joins( JoinNode *node, int indent ) spc( indent ); printf( "%s to make %s, size %dx%d, pos. %dx%d, of:\n", JoinType2char( node->type ), - im_skip_dir( node->name ), + basename, node->cumtrn.oarea.width, node->cumtrn.oarea.height, node->cumtrn.oarea.left, node->cumtrn.oarea.top ); spc( indent ); printf( "reference:\n" ); - print_joins( node->arg1, indent+2 ); + print_joins( node->arg1, indent + 2 ); spc( indent ); printf( "secondary:\n" ); - print_joins( node->arg2, indent+2 ); + print_joins( node->arg2, indent + 2 ); break; case JOIN_CP: spc( indent ); - printf( "copy to make %s of:\n", im_skip_dir( node->name ) ); - print_joins( node->arg1, indent+2 ); + printf( "copy to make %s of:\n", basename ); + print_joins( node->arg1, indent + 2 ); break; case JOIN_LEAF: spc( indent ); - printf( "input image %s\n", im_skip_dir( node->name ) ); + printf( "input image %s\n", basename ); break; } + g_free( basename ); + return( NULL ); } #endif /*DEBUG*/ @@ -903,14 +917,20 @@ print_joins( JoinNode *node, int indent ) /* Print an overlap. */ static void * -print_overlap( OverlapInfo *lap ) +print_overlap( OverlapInfo *lap, void *a, void *b ) { + char *basename_node = g_path_get_basename( lap->node->name ); + char *basename_other = g_path_get_basename( lap->other->name ); + printf( "-> %s overlaps with %s; (this, other) = (%.4G, %.4G)\n", - im_skip_dir( lap->node->name ), - im_skip_dir( lap->other->name ), - lap->nstats->coeff[4], - lap->ostats->coeff[4] ); + basename_node, + basename_other, + *VIPS_MATRIX( lap->nstats, 4, 0 ), + *VIPS_MATRIX( lap->ostats, 4, 0 ) ); + g_free( basename_node ); + g_free( basename_other ); + return( NULL ); } #endif /*DEBUG*/ @@ -919,12 +939,16 @@ print_overlap( OverlapInfo *lap ) /* Print the overlaps on a leaf. */ static void * -print_overlaps( JoinNode *node ) +print_overlaps( JoinNode *node, void *a, void *b ) { + char *basename; + if( node->type == JOIN_LEAF && g_slist_length( node->overlaps ) > 0 ) { - printf( "overlap of %s with:\n", im_skip_dir( node->name ) ); - im_slist_map2( node->overlaps, - (VSListMap2Fn) print_overlap, NULL, NULL ); + basename = g_path_get_basename( node->name ); + printf( "overlap of %s with:\n", basename ); + g_free( basename ); + vips_slist_map2( node->overlaps, + (VipsSListMap2Fn) print_overlap, NULL, NULL ); } return( NULL ); @@ -937,8 +961,9 @@ print_overlaps( JoinNode *node ) static void * print_overlap_error( OverlapInfo *lap, double *fac, double *total ) { - double na = lap->nstats->coeff[4]; - double oa = lap->ostats->coeff[4]; + char *basename_other = g_path_get_basename( lap->other->name ); + double na = *VIPS_MATRIX( lap->nstats, 4, 0 ); + double oa = *VIPS_MATRIX( lap->ostats, 4, 0 ); double err; if( fac ) { @@ -949,8 +974,10 @@ print_overlap_error( OverlapInfo *lap, double *fac, double *total ) err = na - oa; printf( "-> file %s, error = %g\n", - im_skip_dir( lap->other->name ), err ); - *total += err*err; + basename_other, err ); + *total += err * err; + + g_free( basename_other ); return( NULL ); } @@ -962,11 +989,15 @@ print_overlap_error( OverlapInfo *lap, double *fac, double *total ) static void * print_overlap_errors( JoinNode *node, double *fac, double *total ) { + char *basename; + if( node->type == JOIN_LEAF && g_slist_length( node->overlaps ) > 0 ) { - printf( "overlap of %s (index %d) with:\n", - im_skip_dir( node->name ), node->index ); - im_slist_map2( node->overlaps, - (VSListMap2Fn) print_overlap_error, fac, total ); + basename = g_path_get_basename( node->name ); + printf( "overlap of %s (index %d) with:\n", basename, + node->index ); + g_free( basename ); + vips_slist_map2( node->overlaps, + (VipsSListMap2Fn) print_overlap_error, fac, total ); } return( NULL ); @@ -976,29 +1007,29 @@ print_overlap_errors( JoinNode *node, double *fac, double *total ) /* Extract a rect. */ static int -extract_rect( IMAGE *in, IMAGE *out, Rect *r ) +extract_rect( VipsImage *in, VipsImage **out, VipsRect *r ) { - return( im_extract_area( in, out, - r->left, r->top, r->width, r->height ) ); + return( vips_extract_area( in, out, + r->left, r->top, r->width, r->height, NULL ) ); } /* Two images overlap in an area ... make a mask the size of the area, which * has 255 for every pixel where both images are non-zero. */ static int -make_overlap_mask( IMAGE *mem, IMAGE *ref, IMAGE *sec, IMAGE *mask, - Rect *rarea, Rect *sarea ) +make_overlap_mask( VipsImage *mem, VipsImage *ref, VipsImage *sec, VipsImage **mask, + VipsRect *rarea, VipsRect *sarea ) { - IMAGE *t[6]; + VipsImage **t = (VipsImage **) + vips_object_local_array( VIPS_OBJECT( mem ), 6 ); - if( im_open_local_array( mem, t, 6, "mytemps", "p" ) || - extract_rect( ref, t[0], rarea ) || - extract_rect( sec, t[1], sarea ) || - im_extract_band( t[0], t[2], 0 ) || - im_extract_band( t[1], t[3], 0 ) || - im_notequalconst( t[2], t[4], 0.0 ) || - im_notequalconst( t[3], t[5], 0.0 ) || - im_andimage( t[4], t[5], mask ) ) + if( extract_rect( ref, &t[0], rarea ) || + extract_rect( sec, &t[1], sarea ) || + vips_extract_band( t[0], &t[2], 0, NULL ) || + vips_extract_band( t[1], &t[3], 0, NULL ) || + vips_notequal_const1( t[2], &t[4], 0.0, NULL ) || + vips_notequal_const1( t[3], &t[5], 0.0, NULL ) || + vips_andimage( t[4], t[5], mask, NULL ) ) return( -1 ); return( 0 ); @@ -1007,39 +1038,42 @@ make_overlap_mask( IMAGE *mem, IMAGE *ref, IMAGE *sec, IMAGE *mask, /* Find the number of non-zero pixels in a mask image. */ static int -count_nonzero( IMAGE *in, gint64 *count ) +count_nonzero( VipsImage *in, gint64 *count ) { double avg; - if( im_avg( in, &avg ) ) + if( vips_avg( in, &avg, NULL ) ) return( -1 ); *count = (avg * VIPS_IMAGE_N_PELS( in )) / 255.0; - + return( 0 ); } /* Find stats on an area of an IMAGE ... consider only pixels for which the * mask is true. */ -static DOUBLEMASK * -find_image_stats( IMAGE *mem, IMAGE *in, IMAGE *mask, Rect *area ) +static VipsImage * +find_image_stats( VipsImage *mem, VipsImage *in, VipsImage *mask, VipsRect *area ) { - DOUBLEMASK *stats; - IMAGE *t[4]; + VipsImage **t = (VipsImage **) + vips_object_local_array( VIPS_OBJECT( mem ), 4 ); + + VipsImage *stats; gint64 count; /* Extract area, build black image, mask out pixels we want. */ - if( im_open_local_array( mem, t, 4, "find_image_stats", "p" ) || - extract_rect( in, t[0], area ) || - im_black( t[1], t[0]->Xsize, t[0]->Ysize, t[0]->Bands ) || - im_clip2fmt( t[1], t[2], t[0]->BandFmt ) || - im_ifthenelse( mask, t[0], t[2], t[3] ) ) + if( extract_rect( in, &t[0], area ) || + vips_black( &t[1], t[0]->Xsize, t[0]->Ysize, + "bands", t[0]->Bands, + NULL ) || + vips_cast( t[1], &t[2], t[0]->BandFmt, NULL ) || + vips_ifthenelse( mask, t[0], t[2], &t[3], NULL ) ) return( NULL ); /* Get stats from masked image. */ - if( !(stats = im_local_dmask( mem, im_stats( t[3] ) )) ) + if( vips_stats( t[3], &stats, NULL ) ) return( NULL ); /* Number of non-zero pixels in mask. @@ -1049,16 +1083,16 @@ find_image_stats( IMAGE *mem, IMAGE *in, IMAGE *mask, Rect *area ) /* And scale masked average to match. */ - stats->coeff[4] *= (double) count / VIPS_IMAGE_N_PELS( mask ); + *VIPS_MATRIX( stats, 4, 0 ) *= (double) count / VIPS_IMAGE_N_PELS( mask ); /* Yuk! Zap the deviation column with the pixel count. Used later to * determine if this is likely to be a significant overlap. */ - stats->coeff[5] = count; + *VIPS_MATRIX( stats, 5, 0 ) = count; #ifdef DEBUG if( count == 0 ) - im_warn( "global_balance", "%s", _( "empty overlap!" ) ); + g_warning( "global_balance %s", _( "empty overlap!" ) ); #endif /*DEBUG*/ return( stats ); @@ -1069,9 +1103,11 @@ find_image_stats( IMAGE *mem, IMAGE *in, IMAGE *mask, Rect *area ) static int find_overlap_stats( OverlapInfo *lap ) { - IMAGE *mem = lap->node->st->im; - IMAGE *t1 = im_open_local( mem, "find_overlap_stats:1", "p" ); - Rect rarea, sarea; + VipsImage *mem = lap->node->st->im; + VipsImage **t = (VipsImage **) + vips_object_local_array( VIPS_OBJECT( mem ), 1 ); + + VipsRect rarea, sarea; /* Translate the overlap area into the coordinate scheme for the main * node. @@ -1090,16 +1126,16 @@ find_overlap_stats( OverlapInfo *lap ) /* Make a mask for the overlap. */ if( make_overlap_mask( mem, - lap->node->trnim, lap->other->trnim, t1, &rarea, &sarea ) ) + lap->node->trnim, lap->other->trnim, &t[0], &rarea, &sarea ) ) return( -1 ); /* Find stats for that area. */ if( !(lap->nstats = find_image_stats( mem, - lap->node->trnim, t1, &rarea )) ) + lap->node->trnim, t[0], &rarea )) ) return( -1 ); if( !(lap->ostats = find_image_stats( mem, - lap->other->trnim, t1, &sarea )) ) + lap->other->trnim, t[0], &sarea )) ) return( -1 ); return( 0 ); @@ -1108,7 +1144,7 @@ find_overlap_stats( OverlapInfo *lap ) /* Sub-fn. of below. */ static void * -overlap_eq( OverlapInfo *this, JoinNode *node ) +overlap_eq( OverlapInfo *this, JoinNode *node, void *b ) { if( this->other == node ) return( this ); @@ -1119,9 +1155,9 @@ overlap_eq( OverlapInfo *this, JoinNode *node ) /* Is this an overlapping leaf? If yes, add to overlap list. */ static void * -test_overlap( JoinNode *other, JoinNode *node ) +test_overlap( JoinNode *other, JoinNode *node, void *b ) { - Rect overlap; + VipsRect overlap; OverlapInfo *lap; /* Is other a suitable leaf to overlap with node? @@ -1131,9 +1167,9 @@ test_overlap( JoinNode *other, JoinNode *node ) /* Is there an overlap? */ - im_rect_intersectrect( &node->cumtrn.oarea, &other->cumtrn.oarea, + vips_rect_intersectrect( &node->cumtrn.oarea, &other->cumtrn.oarea, &overlap ); - if( im_rect_isempty( &overlap ) ) + if( vips_rect_isempty( &overlap ) ) return( NULL ); /* Is this a trivial overlap? Ignore it if it is. @@ -1146,8 +1182,8 @@ test_overlap( JoinNode *other, JoinNode *node ) /* Have we already added this overlap the other way around? ie. is * node on other's overlap list? */ - if( im_slist_map2( other->overlaps, - (VSListMap2Fn) overlap_eq, node, NULL ) ) + if( vips_slist_map2( other->overlaps, + (VipsSListMap2Fn) overlap_eq, node, NULL ) ) return( NULL ); /* A new overlap - add to overlap list. @@ -1164,13 +1200,13 @@ test_overlap( JoinNode *other, JoinNode *node ) /* If the pixel count either masked overlap is trivial, ignore this * overlap. */ - if( lap->nstats->coeff[5] < TRIVIAL || - lap->ostats->coeff[5] < TRIVIAL ) { + if( *VIPS_MATRIX( lap->nstats, 5, 0 ) < TRIVIAL || + *VIPS_MATRIX( lap->ostats, 5, 0 ) < TRIVIAL ) { #ifdef DEBUG printf( "trivial overlap ... junking\n" ); printf( "nstats count = %g, ostats count = %g\n", - lap->nstats->coeff[5], lap->ostats->coeff[5] ); - print_overlap( lap ); + *VIPS_MATRIX( lap->nstats, 5, 0 ), *VIPS_MATRIX( lap->ostats, 5, 0 ) ); + print_overlap( lap, NULL, NULL ); #endif /*DEBUG*/ overlap_destroy( lap ); } @@ -1183,20 +1219,20 @@ test_overlap( JoinNode *other, JoinNode *node ) * not. */ static void * -find_overlaps( JoinNode *node, SymbolTable *st ) +find_overlaps( JoinNode *node, SymbolTable *st, void *b ) { if( node->type == JOIN_LEAF ) { /* Check for image. */ if( !node->im ) { - im_error( "im_global_balance", + vips_error( "vips_global_balance", _( "unable to open \"%s\"" ), node->name ); return( node ); } if( !node->trnim ) - error_exit( "global_balance: sanity failure #9834" ); + vips_error_exit( "global_balance: sanity failure #9834" ); - return( im__map_table( st, + return( vips__map_table( st, (VipsSListMap2Fn) test_overlap, node, NULL ) ); } @@ -1208,40 +1244,37 @@ find_overlaps( JoinNode *node, SymbolTable *st ) typedef struct { SymbolTable *st; /* Main table */ JoinNode *leaf; /* Leaf to be 1.000 */ - DOUBLEMASK *K; /* LHS */ - DOUBLEMASK *M; /* RHS */ + VipsImage *K; /* LHS */ + VipsImage *M; /* RHS */ int row; /* Current row */ } MatrixBundle; -/* Add a new row for the nominated overlap to the matricies. +/* Add a new row for the nominated overlap to the matrices. */ static void * add_nominated( OverlapInfo *ovl, MatrixBundle *bun, double *gamma ) { - double *Kp = bun->K->coeff + bun->row; - double *Mp = bun->M->coeff + bun->row*bun->M->xsize; - double ns = pow( ovl->nstats->coeff[4], 1/(*gamma) ); - double os = pow( ovl->ostats->coeff[4], 1/(*gamma) ); + double ns = pow( *VIPS_MATRIX( ovl->nstats, 4, 0 ), 1.0 / (*gamma) ); + double os = pow( *VIPS_MATRIX( ovl->ostats, 4, 0 ), 1.0 / (*gamma) ); - Kp[0] = ns; - Mp[ovl->other->index - 1] = os; + *VIPS_MATRIX( bun->K, 0, bun->row ) = ns; + *VIPS_MATRIX( bun->M, ovl->other->index - 1, bun->row ) = os; bun->row++; return( NULL ); } -/* Add a new row for an ordinary overlap to the matricies. +/* Add a new row for an ordinary overlap to the matrices. */ static void * add_other( OverlapInfo *ovl, MatrixBundle *bun, double *gamma ) { - double *Mp = bun->M->coeff + bun->row*bun->M->xsize; - double ns = -pow( ovl->nstats->coeff[4], 1/(*gamma) ); - double os = pow( ovl->ostats->coeff[4], 1/(*gamma) ); + double ns = -pow( *VIPS_MATRIX( ovl->nstats, 4, 0 ), 1.0 / (*gamma) ); + double os = pow( *VIPS_MATRIX( ovl->ostats, 4, 0 ), 1.0 / (*gamma) ); - Mp[ovl->node->index - 1] = ns; - Mp[ovl->other->index - 1] = os; + *VIPS_MATRIX( bun->M, ovl->node->index - 1, bun->row ) = ns; + *VIPS_MATRIX( bun->M, ovl->other->index - 1, bun->row ) = os; bun->row++; @@ -1254,11 +1287,11 @@ static void * add_row( JoinNode *node, MatrixBundle *bun, double *gamma ) { if( node == bun->leaf ) - im_slist_map2( node->overlaps, - (VSListMap2Fn) add_nominated, bun, gamma ); + vips_slist_map2( node->overlaps, + (VipsSListMap2Fn) add_nominated, bun, gamma ); else - im_slist_map2( node->overlaps, - (VSListMap2Fn) add_other, bun, gamma ); + vips_slist_map2( node->overlaps, + (VipsSListMap2Fn) add_other, bun, gamma ); return( NULL ); } @@ -1266,7 +1299,7 @@ add_row( JoinNode *node, MatrixBundle *bun, double *gamma ) /* Fill K and M. leaf is image selected to have factor of 1.000. */ static void -fill_matricies( SymbolTable *st, double gamma, DOUBLEMASK *K, DOUBLEMASK *M ) +fill_matrices( SymbolTable *st, double gamma, VipsImage *K, VipsImage *M ) { MatrixBundle bun; @@ -1276,15 +1309,15 @@ fill_matricies( SymbolTable *st, double gamma, DOUBLEMASK *K, DOUBLEMASK *M ) bun.M = M; bun.row = 0; - /* Build matricies. + /* Build matrices. */ - im__map_table( st, (VipsSListMap2Fn) add_row, &bun, &gamma ); + vips__map_table( st, (VipsSListMap2Fn) add_row, &bun, &gamma ); } /* Used to select the leaf whose coefficient we set to 1. */ static void * -choose_leaf( JoinNode *node ) +choose_leaf( JoinNode *node, void *a, void *b ) { if( node->type == JOIN_LEAF ) return( node ); @@ -1294,26 +1327,30 @@ choose_leaf( JoinNode *node ) /* Make an image from a node. */ -static IMAGE * +static VipsImage * make_mos_image( SymbolTable *st, JoinNode *node, transform_fn tfn, void *a ) { - IMAGE *im1, *im2, *out; + VipsImage *im1, *im2, *out; switch( node->type ) { case JOIN_LR: case JOIN_TB: if( !(im1 = make_mos_image( st, node->arg1, tfn, a )) || - !(im2 = make_mos_image( st, node->arg2, tfn, a )) || - !(out = im_open_local( st->im, node->name, "p" )) ) + !(im2 = make_mos_image( st, node->arg2, tfn, a )) ) return( NULL ); + out = vips_image_new(); + vips_object_local( st->im, out ); + + vips_image_set_string( out, "mosaic-name", node->name ); + if( node->type == JOIN_LR ) { - if( im_lrmerge( im1, im2, out, + if( vips_lrmerge( im1, im2, out, -node->dx, -node->dy, node->mwidth ) ) return( NULL ); } else { - if( im_tbmerge( im1, im2, out, + if( vips_tbmerge( im1, im2, out, -node->dx, -node->dy, node->mwidth ) ) return( NULL ); } @@ -1323,18 +1360,22 @@ make_mos_image( SymbolTable *st, JoinNode *node, transform_fn tfn, void *a ) case JOIN_LRROTSCALE: case JOIN_TBROTSCALE: if( !(im1 = make_mos_image( st, node->arg1, tfn, a )) || - !(im2 = make_mos_image( st, node->arg2, tfn, a )) || - !(out = im_open_local( st->im, node->name, "p" )) ) + !(im2 = make_mos_image( st, node->arg2, tfn, a )) ) return( NULL ); + out = vips_image_new(); + vips_object_local( st->im, out ); + + vips_image_set_string( out, "mosaic-name", node->name ); + if( node->type == JOIN_LRROTSCALE ) { - if( im__lrmerge1( im1, im2, out, + if( vips__lrmerge1( im1, im2, out, node->a, node->b, node->dx, node->dy, node->mwidth ) ) return( NULL ); } else { - if( im__tbmerge1( im1, im2, out, + if( vips__tbmerge1( im1, im2, out, node->a, node->b, node->dx, node->dy, node->mwidth ) ) return( NULL ); @@ -1358,7 +1399,7 @@ make_mos_image( SymbolTable *st, JoinNode *node, transform_fn tfn, void *a ) break; default: - error_exit( "internal error #982369824375987" ); + vips_error_exit( "internal error #982369824375987" ); /*NOTEACHED*/ return( NULL ); } @@ -1369,10 +1410,10 @@ make_mos_image( SymbolTable *st, JoinNode *node, transform_fn tfn, void *a ) /* Re-build mosaic. */ int -im__build_mosaic( SymbolTable *st, IMAGE *out, transform_fn tfn, void *a ) +vips__build_mosaic( SymbolTable *st, VipsImage *out, transform_fn tfn, void *a ) { JoinNode *root = st->root; - IMAGE *im1, *im2; + VipsImage *im1, *im2; switch( root->type ) { case JOIN_LR: @@ -1382,12 +1423,12 @@ im__build_mosaic( SymbolTable *st, IMAGE *out, transform_fn tfn, void *a ) return( -1 ); if( root->type == JOIN_LR ) { - if( im_lrmerge( im1, im2, out, + if( vips_lrmerge( im1, im2, out, -root->dx, -root->dy, root->mwidth ) ) return( -1 ); } else { - if( im_tbmerge( im1, im2, out, + if( vips_tbmerge( im1, im2, out, -root->dx, -root->dy, root->mwidth ) ) return( -1 ); } @@ -1401,13 +1442,13 @@ im__build_mosaic( SymbolTable *st, IMAGE *out, transform_fn tfn, void *a ) return( -1 ); if( root->type == JOIN_LRROTSCALE ) { - if( im__lrmerge1( im1, im2, out, + if( vips__lrmerge1( im1, im2, out, root->a, root->b, root->dx, root->dy, root->mwidth ) ) return( -1 ); } else { - if( im__tbmerge1( im1, im2, out, + if( vips__tbmerge1( im1, im2, out, root->a, root->b, root->dx, root->dy, root->mwidth ) ) return( -1 ); @@ -1418,7 +1459,8 @@ im__build_mosaic( SymbolTable *st, IMAGE *out, transform_fn tfn, void *a ) case JOIN_LEAF: /* Trivial case! Just one file in our mosaic. */ - if( !(im1 = tfn( root, a )) || im_copy( im1, out ) ) + if( !(im1 = tfn( root, a )) || + vips_image_write( im1, out ) ) return( -1 ); break; @@ -1426,65 +1468,128 @@ im__build_mosaic( SymbolTable *st, IMAGE *out, transform_fn tfn, void *a ) case JOIN_CP: /* Very trivial case. */ - if( !(im1 = make_mos_image( st, root->arg1, tfn, a )) ) - return( -1 ); - if( im_copy( im1, out ) ) + if( !(im1 = make_mos_image( st, root->arg1, tfn, a )) || + vips_image_write( im1, out ) ) return( -1 ); break; default: - error_exit( "internal error #982369824375987" ); + vips_error_exit( "internal error #982369824375987" ); /*NOTEACHED*/ } return( 0 ); } +static int +vips__matrixtranspose( VipsImage *in, VipsImage **out ) +{ + int yc, xc; + + /* Allocate output matrix. + */ + if( !(*out = vips_image_new_matrix( in->Ysize, in->Xsize )) ) + return( -1 ); + + /* Transpose. + */ + for( yc = 0; yc < (*out)->Ysize; ++yc ) + for( xc = 0; xc < (*out)->Xsize; ++xc ) + *VIPS_MATRIX( *out, xc, yc ) = *VIPS_MATRIX( in, yc, xc ); + + return( 0 ); +} + +static int +vips__matrixmultiply( VipsImage *in1, VipsImage *in2, VipsImage **out ) +{ + int xc, yc, col; + double sum; + double *mat, *a, *b; + double *s1, *s2; + + /* Check matrix sizes. + */ + if( in1->Xsize != in2->Ysize ) { + vips_error( "vips__matrixmultiply", "%s", _( "bad sizes" ) ); + return( -1 ); + } + + /* Allocate output matrix. + */ + if( !(*out = vips_image_new_matrix( in2->Xsize, in1->Ysize )) ) + return( -1 ); + + /* Multiply. + */ + mat = VIPS_MATRIX( *out, 0, 0 ); + s1 = VIPS_MATRIX( in1, 0, 0 ); + + for( yc = 0; yc < in1->Ysize; yc++ ) { + s2 = VIPS_MATRIX( in2, 0, 0 ); + + for( col = 0; col < in2->Xsize; col++ ) { + /* Get ready to sweep a row. + */ + a = s1; + b = s2; + + for( sum = 0.0, xc = 0; xc < in1->Xsize; xc++ ) { + sum += *a++ * *b; + b += in2->Xsize; + } + + *mat++ = sum; + s2++; + } + + s1 += in1->Xsize; + } + + return( 0 ); +} + /* Find correction factors. */ static int find_factors( SymbolTable *st, double gamma ) { - DOUBLEMASK *K; - DOUBLEMASK *M; - DOUBLEMASK *m1, *m2, *m3, *m4, *m5; + VipsImage **t = (VipsImage **) + vips_object_local_array( VIPS_OBJECT( st->im ), 7 ); + double total; double avg; int i; - /* Make output matricies. + /* Make output matrices. */ - if( !(K = im_local_dmask( st->im, - im_create_dmask( "K", 1, st->novl ) )) || - !(M = im_local_dmask( st->im, - im_create_dmask( "M", st->nim-1, st->novl ) )) ) + if( !(t[0] = vips_image_new_matrix( 1, st->novl )) || + !(t[1] = vips_image_new_matrix( st->nim - 1, st->novl )) ) return( -1 ); - fill_matricies( st, gamma, K, M ); + + fill_matrices( st, gamma, t[0], t[1] ); + #ifdef DEBUG - im_write_dmask( K ); - im_write_dmask( M ); + vips_image_write_to_file( t[0], "K.mat", NULL ); + vips_image_write_to_file( t[1], "M.mat", NULL ); #endif /*DEBUG*/ /* Calculate LMS. */ - if( !(m1 = im_local_dmask( st->im, im_mattrn( M, "lms:1" ) )) ) - return( -1 ); - if( !(m2 = im_local_dmask( st->im, im_matmul( m1, M, "lms:2" ) )) ) - return( -1 ); - if( !(m3 = im_local_dmask( st->im, im_matinv( m2, "lms:3" ) )) ) - return( -1 ); - if( !(m4 = im_local_dmask( st->im, im_matmul( m3, m1, "lms:4" ) )) ) - return( -1 ); - if( !(m5 = im_local_dmask( st->im, im_matmul( m4, K, "lms:5" ) )) ) + if( vips__matrixtranspose( t[1], &t[2] ) || + vips__matrixmultiply( t[2], t[1], &t[3] ) || + vips_matrixinvert( t[3], &t[4], NULL ) || + vips__matrixmultiply( t[4], t[2], &t[5] ) || + vips__matrixmultiply( t[5], t[0], &t[6] ) ) return( -1 ); /* Make array of correction factors. */ - if( !(st->fac = IM_ARRAY( st->im, st->nim, double )) ) + if( !(st->fac = VIPS_ARRAY( st->im, st->nim, double )) ) return( -1 ); - for( i = 0; i < m5->ysize; i++ ) - st->fac[i + 1] = m5->coeff[i]; + for( i = 0; i < t[6]->Ysize; i++ ) + st->fac[i + 1] = *VIPS_MATRIX( t[6], 0, i ); st->fac[0] = 1.0; /* Find average balance factor, normalise to that average. @@ -1499,18 +1604,18 @@ find_factors( SymbolTable *st, double gamma ) #ifdef DEBUG /* Diagnostics! */ - printf( "debugging output for im_global_balance():\n" ); + printf( "debugging output for vips_global_balance():\n" ); for( i = 0; i < st->nim; i++ ) printf( "balance factor %d = %g\n", i, st->fac[i] ); total = 0.0; printf( "Overlap errors:\n" ); - im__map_table( st, + vips__map_table( st, (VipsSListMap2Fn) print_overlap_errors, NULL, &total ); printf( "RMS error = %g\n", sqrt( total / st->novl ) ); total = 0.0; printf( "Overlap errors after adjustment:\n" ); - im__map_table( st, + vips__map_table( st, (VipsSListMap2Fn) print_overlap_errors, st->fac, &total ); printf( "RMS error = %g\n", sqrt( total / st->novl ) ); #endif /*DEBUG*/ @@ -1518,34 +1623,78 @@ find_factors( SymbolTable *st, double gamma ) return( 0 ); } +/* TODO(kleisauke): Copied from im__affinei */ +/* Shared with vips_mosaic1(), so not static. */ +int +vips__affinei( VipsImage *in, VipsImage *out, VipsTransformation *trn ) +{ + VipsImage **t = (VipsImage **) + vips_object_local_array( VIPS_OBJECT( out ), 2 ); + VipsArea *oarea; + gboolean repack; + + oarea = VIPS_AREA( vips_array_int_newv( 4, + trn->oarea.left, trn->oarea.top, + trn->oarea.width, trn->oarea.height ) ); + + /* vips7 affine would repack labq and im_benchmark() depends upon + * this. + */ + repack = in->Coding == VIPS_CODING_LABQ; + + if( vips_affine( in, &t[0], + trn->a, trn->b, trn->c, trn->d, + "oarea", oarea, + "odx", trn->odx, + "ody", trn->ody, + NULL ) ) { + vips_area_unref( oarea ); + return( -1 ); + } + vips_area_unref( oarea ); + in = t[0]; + + if( repack ) { + if (vips_colourspace( in, &t[1], + VIPS_INTERPRETATION_LABQ, NULL ) ) + return ( -1 ); + in = t[1]; + } + + if( vips_image_write( in, out ) ) + return( -1 ); + + return( 0 ); +} + /* Look for all leaves, make sure we have a transformed version of each. */ static void * -generate_trn_leaves( JoinNode *node, SymbolTable *st ) +generate_trn_leaves( JoinNode *node, SymbolTable *st, void *b ) { if( node->type == JOIN_LEAF ) { /* Check for image. */ if( !node->im ) { - im_error( "im_global_balance", + vips_error( "vips_global_balance", _( "unable to open \"%s\"" ), node->name ); return( node ); } if( node->trnim ) - error_exit( "global_balance: sanity failure #765" ); - + vips_error_exit( "global_balance: sanity failure #765" ); + /* Special case: if this is an untransformed leaf (there will * always be at least one), then skip the affine. */ if( vips__transform_isidentity( &node->cumtrn ) ) node->trnim = node->im; - else - if( !(node->trnim = - im_open_local( node->st->im, - "trnleaf:1", "p" )) || - vips__affine( node->im, node->trnim, - &node->cumtrn ) ) + else { + node->trnim = vips_image_new(); + vips_object_local( node->st->im, node->trnim ); + + if ( vips__affinei( node->im, node->trnim, &node->cumtrn ) ) return( node ); + } } return( NULL ); @@ -1554,18 +1703,18 @@ generate_trn_leaves( JoinNode *node, SymbolTable *st ) /* Analyse mosaic. */ static int -analyse_mosaic( SymbolTable *st, IMAGE *in ) +analyse_mosaic( SymbolTable *st, VipsImage *in ) { /* Parse Hist on in. */ - if( im__parse_desc( st, in ) ) + if( vips__parse_desc( st, in ) ) return( -1 ); /* Print parsed data. */ #ifdef DEBUG printf( "Input files:\n" ); - im__map_table( st, (VipsSListMap2Fn) print_leaf, NULL, NULL ); + vips__map_table( st, (VipsSListMap2Fn) print_leaf, NULL, NULL ); printf( "\nOutput file:\n" ); print_node( st->root ); printf( "\nJoin commands:\n" ); @@ -1574,24 +1723,24 @@ analyse_mosaic( SymbolTable *st, IMAGE *in ) /* Generate transformed leaves. */ - if( im__map_table( st, + if( vips__map_table( st, (VipsSListMap2Fn) generate_trn_leaves, st, NULL ) ) return( -1 ); /* Find overlaps. */ - if( im__map_table( st, (VipsSListMap2Fn) find_overlaps, st, NULL ) ) + if( vips__map_table( st, (VipsSListMap2Fn) find_overlaps, st, NULL ) ) return( -1 ); /* Scan table, counting and indexing input images and joins. */ - im__map_table( st, (VipsSListMap2Fn) count_leaves, NULL, NULL ); - im__map_table( st, (VipsSListMap2Fn) count_joins, NULL, NULL ); + vips__map_table( st, (VipsSListMap2Fn) count_leaves, NULL, NULL ); + vips__map_table( st, (VipsSListMap2Fn) count_joins, NULL, NULL ); /* Select leaf to be 1.000. * This must be index == 0, unless you change stuff above! */ - st->leaf = im__map_table( st, + st->leaf = vips__map_table( st, (VipsSListMap2Fn) choose_leaf, NULL, NULL ); /* And print overlaps. @@ -1600,7 +1749,7 @@ analyse_mosaic( SymbolTable *st, IMAGE *in ) printf( "\nLeaf to be 1.000:\n" ); print_node( st->leaf ); printf( "\nOverlaps:\n" ); - im__map_table( st, (VipsSListMap2Fn) print_overlaps, NULL, NULL ); + vips__map_table( st, (VipsSListMap2Fn) print_overlaps, NULL, NULL ); printf( "\n%d input files, %d unique overlaps, %d joins\n", st->nim, st->novl, st->njoin ); #endif /*DEBUG*/ @@ -1611,104 +1760,92 @@ analyse_mosaic( SymbolTable *st, IMAGE *in ) /* Scale im by fac --- if it's uchar/ushort, use a lut. If we can use a lut, * transform in linear space. If we can't, don't bother for efficiency. */ -static IMAGE * +static VipsImage * transform( JoinNode *node, double *gamma ) { SymbolTable *st = node->st; - IMAGE *in = node->im; + VipsImage *in = node->im; double fac = st->fac[node->index]; + VipsImage **t = (VipsImage **) + vips_object_local_array( VIPS_OBJECT( st->im ), 5 ); - IMAGE *out = im_open_local( st->im, node->name, "p" ); - - IMAGE *t1 = im_open_local( out, "transform:1", "p" ); - IMAGE *t2 = im_open_local( out, "transform:2", "p" ); - IMAGE *t3 = im_open_local( out, "transform:3", "p" ); - IMAGE *t4 = im_open_local( out, "transform:4", "p" ); - IMAGE *t5 = im_open_local( out, "transform:5", "p" ); - - if( !out || !t1 || !t2 || !t3 || !t4 || !t5 ) - return( NULL ); + VipsImage *out; if( fac == 1.0 ) { /* Easy! */ out = in; } - else if( in->BandFmt == IM_BANDFMT_UCHAR ) { - if( im_identity( t1, 1 ) || - im_powtra( t1, t2, 1.0 / (*gamma) ) || - im_lintra( fac, t2, 0.0, t3 ) || - im_powtra( t3, t4, *gamma ) || - im_clip2fmt( t4, t5, IM_BANDFMT_UCHAR ) || - im_maplut( in, out, t5 ) ) - return( NULL ); - } - else if( in->BandFmt == IM_BANDFMT_USHORT ) { - if( im_identity_ushort( t1, 1, 65535 ) || - im_powtra( t1, t2, 1.0 / (*gamma) ) || - im_lintra( fac, t2, 0.0, t3 ) || - im_powtra( t3, t4, *gamma ) || - im_clip2fmt( t4, t5, IM_BANDFMT_USHORT ) || - im_maplut( in, out, t5 ) ) + /* TODO(kleisauke): Could we call vips_gamma instead? */ + else if( in->BandFmt == VIPS_FORMAT_UCHAR || + in->BandFmt == VIPS_FORMAT_USHORT ) { + if( vips_identity( &t[0], + "bands", 1, + "ushort", in->BandFmt == VIPS_FORMAT_USHORT, + //"size", 65535, + NULL ) || + vips_pow_const1( t[0], &t[1], + 1.0 / (*gamma), NULL ) || + vips_linear1( t[1], &t[2], fac, 0.0, NULL ) || + vips_pow_const1( t[2], &t[3], *gamma, NULL ) || + vips_cast( t[3], &t[4], in->BandFmt, NULL ) || + vips_maplut( in, &out, t[4], NULL ) ) return( NULL ); } else { - /* Just im_lintra it. + /* Just vips_linear1 it. */ - if( im_lintra( fac, in, 0.0, t1 ) || - im_clip2fmt( t1, out, in->BandFmt ) ) + if( vips_linear1( in, &t[0], fac, 0.0, NULL ) || + vips_cast( t[0], &out, in->BandFmt, NULL ) ) return( NULL ); } + vips_image_set_string( out, "mosaic-name", node->name ); + return( out ); } /* As above, but output as float, not matched to input. */ -static IMAGE * +static VipsImage * transformf( JoinNode *node, double *gamma ) { SymbolTable *st = node->st; - IMAGE *in = node->im; + VipsImage *in = node->im; double fac = node->st->fac[node->index]; + VipsImage **t = (VipsImage **) + vips_object_local_array( VIPS_OBJECT( st->im ), 4 ); - IMAGE *out = im_open_local( st->im, node->name, "p" ); - IMAGE *t1 = im_open_local( out, "transform:1", "p" ); - IMAGE *t2 = im_open_local( out, "transform:2", "p" ); - IMAGE *t3 = im_open_local( out, "transform:3", "p" ); - IMAGE *t4 = im_open_local( out, "transform:4", "p" ); - - if( !out || !t1 || !t2 || !t3 || !t4 ) - return( NULL ); + VipsImage *out; if( fac == 1.0 ) { /* Easy! */ out = in; } - else if( in->BandFmt == IM_BANDFMT_UCHAR ) { - if( im_identity( t1, 1 ) || - im_powtra( t1, t2, 1/(*gamma) ) || - im_lintra( fac, t2, 0.0, t3 ) || - im_powtra( t3, t4, *gamma ) || - im_maplut( in, out, t4 ) ) - return( NULL ); - } - else if( in->BandFmt == IM_BANDFMT_USHORT ) { - if( im_identity_ushort( t1, 1, 65535 ) || - im_powtra( t1, t2, 1/(*gamma) ) || - im_lintra( fac, t2, 0.0, t3 ) || - im_powtra( t3, t4, *gamma ) || - im_maplut( in, out, t4 ) ) + else if( in->BandFmt == VIPS_FORMAT_UCHAR || + in->BandFmt == VIPS_FORMAT_USHORT ) { + if( vips_identity( &t[0], + "bands", 1, + "ushort", in->BandFmt == VIPS_FORMAT_USHORT, + //"size", 65535, + NULL ) || + vips_pow_const1( t[0], &t[1], + 1.0 / (*gamma), NULL ) || + vips_linear1( t[1], &t[2], fac, 0.0, NULL ) || + vips_pow_const1( t[2], &t[3], *gamma, NULL ) || + vips_maplut( in, &out, t[3], NULL ) ) return( NULL ); } else { - /* Just im_lintra it. + /* Just vips_linear1 it. */ - if( im_lintra( fac, in, 0.0, out ) ) + if( vips_linear1( in, &out, fac, 0.0, NULL ) ) return( NULL ); } + vips_image_set_string( out, "mosaic-name", node->name ); + return( out ); } @@ -1741,14 +1878,14 @@ vips_globalbalance_build( VipsObject *object ) build( object ) ) return( -1 ); - if( !(st = im__build_symtab( globalbalance->out, SYM_TAB_SIZE )) || + if( !(st = vips__build_symtab( globalbalance->out, SYM_TAB_SIZE )) || analyse_mosaic( st, globalbalance->in ) || find_factors( st, globalbalance->gamma ) ) return( -1 ); trn = globalbalance->int_output ? (transform_fn) transform : (transform_fn) transformf; - if( im__build_mosaic( st, globalbalance->out, + if( vips__build_mosaic( st, globalbalance->out, trn, &globalbalance->gamma ) ) return( -1 ); diff --git a/libvips/mosaicing/global_balance.h b/libvips/mosaicing/global_balance.h index ebe1394a..94a4f890 100644 --- a/libvips/mosaicing/global_balance.h +++ b/libvips/mosaicing/global_balance.h @@ -1,4 +1,4 @@ -/* Header for the .desc file parser in im_global_balance() +/* Header for the .desc file parser in vips_global_balance() * * 1/11/01 JC * - cut from global_balance.c @@ -42,16 +42,16 @@ typedef struct _SymbolTable SymbolTable; /* Type of a transform function. */ -typedef IMAGE *(*transform_fn)( JoinNode *, void * ); +typedef VipsImage *(*transform_fn)( JoinNode *, void * ); /* Join type. */ enum _JoinType { - JOIN_LR, /* im_lrmerge join */ - JOIN_TB, /* im_tbmerge join */ + JOIN_LR, /* vips_lrmerge join */ + JOIN_TB, /* vips_tbmerge join */ JOIN_LRROTSCALE, /* 1st oder lrmerge */ JOIN_TBROTSCALE, /* 1st oder tbmerge */ - JOIN_CP, /* im_copy operation */ + JOIN_CP, /* vips_copy operation */ JOIN_LEAF /* Base file */ }; @@ -61,9 +61,9 @@ enum _JoinType { struct _OverlapInfo { JoinNode *node; /* The base node - we are on this list */ JoinNode *other; /* Node we overlap with */ - Rect overlap; /* The overlap area */ - DOUBLEMASK *nstats; /* Node's stats for overlap area */ - DOUBLEMASK *ostats; /* Other's stats for overlap area */ + VipsRect overlap; /* The overlap area */ + VipsImage *nstats; /* Node's stats for overlap area */ + VipsImage *ostats; /* Other's stats for overlap area */ }; /* Struct for a join node. @@ -93,11 +93,11 @@ struct _JoinNode { VipsTransformation thistrn; /* Transformation for arg2 */ /* Special for leaves: all the join_nodes we overlap with, the - * IMAGE for that file, and the index. + * VipsImage for that file, and the index. */ GSList *overlaps; - IMAGE *im; - IMAGE *trnim; /* Transformed image .. used in 2nd pass */ + VipsImage *im; + VipsImage *trnim; /* Transformed image .. used in 2nd pass */ int index; }; @@ -108,7 +108,7 @@ struct _JoinNode { struct _SymbolTable { GSList **table; /* Ptr to base of hash table */ int sz; /* Size of hash table */ - IMAGE *im; /* Malloc relative to this */ + VipsImage *im; /* Malloc relative to this */ int novl; /* Number of unique overlaps */ int nim; /* Number of leaf images */ @@ -119,9 +119,9 @@ struct _SymbolTable { double *fac; /* Correction factors */ }; -IMAGE *im__global_open_image( SymbolTable *st, char *name ); -SymbolTable *im__build_symtab( IMAGE *out, int sz ); -int im__parse_desc( SymbolTable *st, IMAGE *in ); -void *im__map_table( SymbolTable *st, VSListMap2Fn fn, void *a, void *b ); -int im__build_mosaic( SymbolTable *st, - IMAGE *out, transform_fn tfn, void * ); +VipsImage *vips__global_open_image( SymbolTable *st, char *name ); +SymbolTable *vips__build_symtab( VipsImage *out, int sz ); +int vips__parse_desc( SymbolTable *st, VipsImage *in ); +void *vips__map_table( SymbolTable *st, VipsSListMap2Fn fn, void *a, void *b ); +int vips__build_mosaic( SymbolTable *st, + VipsImage *out, transform_fn tfn, void *a ); diff --git a/libvips/mosaicing/im_avgdxdy.c b/libvips/mosaicing/im_avgdxdy.c index b2ff9026..6490c2a1 100644 --- a/libvips/mosaicing/im_avgdxdy.c +++ b/libvips/mosaicing/im_avgdxdy.c @@ -1,10 +1,10 @@ /* @(#) Function which averages the difference x_secondary[] - x_reference[] * @(#) and y_secondary[] - y_reference[] of the structure points; - * @(#) The rounded integer result is returnd into dx, dy - * @(#) No IMAGES are involved in this function. + * @(#) The rounded integer result is returned into dx, dy + * @(#) No images are involved in this function. * @(#) - * @(#) int im_avgdxdy( points, dx, dy ) - * @(#) TIE_POINTS *points; + * @(#) int vips_avgdxdy( points, dx, dy ) + * @(#) TiePoints *points; * @(#) int *dx, *dy; * @(#) * @(#) Returns 0 on sucess and -1 on error. @@ -52,18 +52,17 @@ #include #include -#include #include "pmosaicing.h" int -im__avgdxdy( TIE_POINTS *points, int *dx, int *dy ) +vips__avgdxdy( TiePoints *points, int *dx, int *dy ) { int sumdx, sumdy; int i; if( points->nopoints == 0 ) { - im_error( "im_avgdxdy", "%s", _( "no points to average" ) ); + vips_error( "vips_avgdxdy", "%s", _( "no points to average" ) ); return( -1 ); } @@ -76,8 +75,8 @@ im__avgdxdy( TIE_POINTS *points, int *dx, int *dy ) sumdy += points->y_secondary[i] - points->y_reference[i]; } - *dx = IM_RINT( (double) sumdx / (double) points->nopoints ); - *dy = IM_RINT( (double) sumdy / (double) points->nopoints ); + *dx = VIPS_RINT( (double) sumdx / (double) points->nopoints ); + *dy = VIPS_RINT( (double) sumdy / (double) points->nopoints ); return( 0 ); } diff --git a/libvips/mosaicing/im_chkpair.c b/libvips/mosaicing/im_chkpair.c index 90d09035..5472f5d1 100644 --- a/libvips/mosaicing/im_chkpair.c +++ b/libvips/mosaicing/im_chkpair.c @@ -15,6 +15,8 @@ * - order of args changed to help C++ API * 24/1/11 * - gtk-doc + * 18/6/20 kleisauke + * - convert to vips8 */ /* @@ -54,12 +56,11 @@ #include #include -#include #include "pmosaicing.h" /** - * im_correl: + * vips_correl: * @ref: reference image * @sec: secondary image * @xref: position in reference image @@ -82,32 +83,26 @@ * * Only the first band of each image is correlated. @ref and @sec may be * very large --- the function extracts and generates just the - * parts needed. Correlation is done with im_spcor(); the position of - * the maximum is found with im_maxpos(). + * parts needed. Correlation is done with vips_spcor(); the position of + * the maximum is found with vips_max(). * - * See also: im_match_linear(), im_match_linear_search(), im_lrmosaic(). + * See also: vips_match(), vips_lrmosaic(). * * Returns: 0 on success, -1 on error */ int -im_correl( IMAGE *ref, IMAGE *sec, +vips_correl( VipsImage *ref, VipsImage *sec, int xref, int yref, int xsec, int ysec, int hwindowsize, int hsearchsize, double *correlation, int *x, int *y ) { - IMAGE *surface = im_open( "surface", "t" ); - IMAGE *t1, *t2, *t3, *t4; + VipsImage *surface = vips_image_new(); + VipsImage **t = (VipsImage **) + vips_object_local_array( VIPS_OBJECT( surface ), 4 ); - Rect refr, secr; - Rect winr, srhr; - Rect wincr, srhcr; - - if( !surface || - !(t1 = im_open_local( surface, "correlate:1", "p" )) || - !(t2 = im_open_local( surface, "correlate:1", "p" )) || - !(t3 = im_open_local( surface, "correlate:1", "p" )) || - !(t4 = im_open_local( surface, "correlate:1", "p" )) ) - return( -1 ); + VipsRect refr, secr; + VipsRect winr, srhr; + VipsRect wincr, srhcr; /* Find position of window and search area, and clip against image * size. @@ -118,9 +113,9 @@ im_correl( IMAGE *ref, IMAGE *sec, refr.height = ref->Ysize; winr.left = xref - hwindowsize; winr.top = yref - hwindowsize; - winr.width = hwindowsize*2 + 1; - winr.height = hwindowsize*2 + 1; - im_rect_intersectrect( &refr, &winr, &wincr ); + winr.width = hwindowsize * 2 + 1; + winr.height = hwindowsize * 2 + 1; + vips_rect_intersectrect( &refr, &winr, &wincr ); secr.left = 0; secr.top = 0; @@ -128,52 +123,52 @@ im_correl( IMAGE *ref, IMAGE *sec, secr.height = sec->Ysize; srhr.left = xsec - hsearchsize; srhr.top = ysec - hsearchsize; - srhr.width = hsearchsize*2 + 1; - srhr.height = hsearchsize*2 + 1; - im_rect_intersectrect( &secr, &srhr, &srhcr ); + srhr.width = hsearchsize * 2 + 1; + srhr.height = hsearchsize * 2 + 1; + vips_rect_intersectrect( &secr, &srhr, &srhcr ); /* Extract window and search area. */ - if( im_extract_area( ref, t1, - wincr.left, wincr.top, wincr.width, wincr.height ) || - im_extract_area( sec, t2, - srhcr.left, srhcr.top, srhcr.width, srhcr.height ) ) { - im_close( surface ); + if( vips_extract_area( ref, &t[0], + wincr.left, wincr.top, wincr.width, wincr.height, NULL ) || + vips_extract_area( sec, &t[1], + srhcr.left, srhcr.top, srhcr.width, srhcr.height, NULL ) ) { + g_object_unref( surface ); return( -1 ); } - /* Make sure we have just one band. From im_*mosaic() we will, but - * from im_match_linear_search() etc. we may not. + /* Make sure we have just one band. From vips_*mosaic() we will, but + * from vips_match() etc. we may not. */ - if( t1->Bands != 1 ) { - if( im_extract_band( t1, t3, 0 ) ) { - im_close( surface ); + if( t[0]->Bands != 1 ) { + if( vips_extract_band( t[0], &t[2], 0, NULL ) ) { + g_object_unref( surface ); return( -1 ); } - t1 = t3; + t[0] = t[2]; } - if( t2->Bands != 1 ) { - if( im_extract_band( t2, t4, 0 ) ) { - im_close( surface ); + if( t[1]->Bands != 1 ) { + if( vips_extract_band( t[1], &t[3], 0, NULL ) ) { + g_object_unref( surface ); return( -1 ); } - t2 = t4; + t[1] = t[3]; } /* Search! */ - if( im_spcor( t2, t1, surface ) ) { - im_close( surface ); + if( vips_spcor( t[1], t[0], &surface, NULL ) ) { + g_object_unref( surface ); return( -1 ); } /* Find maximum of correlation surface. */ - if( im_maxpos( surface, x, y, correlation ) ) { - im_close( surface ); + if( vips_max( surface, correlation, "x", x, "y", y, NULL ) ) { + g_object_unref( surface ); return( -1 ); } - im_close( surface ); + g_object_unref( surface ); /* Translate back to position within sec. */ @@ -184,7 +179,7 @@ im_correl( IMAGE *ref, IMAGE *sec, } int -im__chkpair( IMAGE *ref, IMAGE *sec, TIE_POINTS *points ) +vips__chkpair( VipsImage *ref, VipsImage *sec, TiePoints *points ) { int i; int x, y; @@ -195,22 +190,22 @@ im__chkpair( IMAGE *ref, IMAGE *sec, TIE_POINTS *points ) /* Check images. */ - if( im_incheck( ref ) || im_incheck( sec ) ) + if( vips_image_wio_input( ref ) || vips_image_wio_input( sec ) ) return( -1 ); if( ref->Bands != sec->Bands || ref->BandFmt != sec->BandFmt || ref->Coding != sec->Coding ) { - im_error( "im_chkpair", "%s", _( "inputs incompatible" ) ); + vips_error( "vips_chkpair", "%s", _( "inputs incompatible" ) ); return( -1 ); } - if( ref->Bands != 1 || ref->BandFmt != IM_BANDFMT_UCHAR ) { - im_error( "im_chkpair", "%s", _( "help!" ) ); + if( ref->Bands != 1 || ref->BandFmt != VIPS_FORMAT_UCHAR ) { + vips_error( "vips_chkpair", "%s", _( "help!" ) ); return( -1 ); } for( i = 0; i < points->nopoints; i++ ) { /* Find correlation point. */ - if( im_correl( ref, sec, + if( vips_correl( ref, sec, points->x_reference[i], points->y_reference[i], points->x_reference[i], points->y_reference[i], hcor, harea, diff --git a/libvips/mosaicing/im_clinear.c b/libvips/mosaicing/im_clinear.c index b567e88b..18338bae 100644 --- a/libvips/mosaicing/im_clinear.c +++ b/libvips/mosaicing/im_clinear.c @@ -1,14 +1,14 @@ /* @(#) Function which calculates the coefficients between corresponding * @(#) points from reference and secondary images (probably from the scanner), - * @(#) previously calculated using the functions im_calcon() and im_chpair() - * @(#) It is assummed that a selection of the best(?) possible points has + * @(#) previously calculated using the functions vips__{lr,bt}calcon() and vips_chpair() + * @(#) It is assumed that a selection of the best(?) possible points has * @(#) been already carried out and that those nopoints points are in arrays * @(#) x1, y1 and x2, y2 - * @(#) No IMAGES are involved in this function and the calculated parameters - * @(#) are returned in scale angle deltax and deltay of the TIE_POINTS struct. + * @(#) No images are involved in this function and the calculated parameters + * @(#) are returned in scale angle deltax and deltay of the TiePoints struct. * @(#) - * @(#) int im_clinear( points ) - * @(#) TIE_POINTS *points; + * @(#) int vips_clinear( points ) + * @(#) TiePoints *points; * @(#) * @(#) Returns 0 on sucess and -1 on error. * @@ -19,6 +19,8 @@ * Modified on : 18/04/1991 * 24/1/97 JC * - tiny mem leak fixed + * 18/6/20 kleisauke + * - convert to vips8 */ /* @@ -57,19 +59,18 @@ #include #include -#include #include #include "pmosaicing.h" int -im__clinear( TIE_POINTS *points ) +vips__clinear( TiePoints *points ) { - double **mat; /* matrix mar[4][4] */ - double *g; /* vector g[1][4] */ + VipsImage *mat, *matinv; + double *g; double value; - double sx1=0.0, sx1x1=0.0, sy1=0.0, sy1y1=0.0, sx1y1 = 0.0; - double sx2x1=0.0, sx2y1=0.0, sx2=0.0, sy2=0.0, sy2y1=0.0, sy2x1=0.0; + double sx1 = 0.0, sx1x1 = 0.0, sy1 = 0.0, sy1y1 = 0.0, sx1y1 = 0.0; + double sx2x1 = 0.0, sx2y1 = 0.0, sx2 = 0.0, sy2 = 0.0, sy2y1 = 0.0, sy2x1 = 0.0; int i, j; int elms; @@ -86,10 +87,10 @@ im__clinear( TIE_POINTS *points ) dev = &points->deviation[0]; elms = points->nopoints; - if( !(mat = im_dmat_alloc( 0, 3, 0, 3 )) ) + if( !(mat = vips_image_new_matrix( 4, 4 )) ) return( -1 ); - if( !(g = im_dvector( 0, 3 )) ) { - im_free_dmat( mat, 0, 3, 0, 3 ); + if( !(g = VIPS_ARRAY( NULL, 4, double )) ) { + g_object_unref( mat ); return( -1 ); } @@ -107,48 +108,52 @@ im__clinear( TIE_POINTS *points ) sy2 += ysec[i]; } - mat[0][0] = sx1x1 + sy1y1; - mat[0][1] = 0; - mat[0][2] = sx1; - mat[0][3] = sy1; + *VIPS_MATRIX( mat, 0, 0 ) = sx1x1 + sy1y1; + *VIPS_MATRIX( mat, 1, 0 ) = 0; + *VIPS_MATRIX( mat, 2, 0 ) = sx1; + *VIPS_MATRIX( mat, 3, 0 ) = sy1; - mat[1][0] = 0; - mat[1][1] = sx1x1 + sy1y1; - mat[1][2] = -sy1; - mat[1][3] = sx1; + *VIPS_MATRIX( mat, 0, 1 ) = 0; + *VIPS_MATRIX( mat, 1, 1 ) = sx1x1 + sy1y1; + *VIPS_MATRIX( mat, 2, 1 ) = -sy1; + *VIPS_MATRIX( mat, 3, 1 ) = sx1; - mat[2][0] = sx1; - mat[2][1] = -sy1; - mat[2][2] = (double)elms; - mat[2][3] = 0.0; + *VIPS_MATRIX( mat, 0, 2 ) = sx1; + *VIPS_MATRIX( mat, 1, 2 ) = -sy1; + *VIPS_MATRIX( mat, 2, 2 ) = (double) elms; + *VIPS_MATRIX( mat, 3, 2 ) = 0.0; - mat[3][0] = sy1; - mat[3][1] = sx1; - mat[3][2] = 0.0; - mat[3][3] = (double)elms; + *VIPS_MATRIX( mat, 0, 3 ) = sy1; + *VIPS_MATRIX( mat, 1, 3 ) = sx1; + *VIPS_MATRIX( mat, 2, 3 ) = 0.0; + *VIPS_MATRIX( mat, 3, 3 ) = (double) elms; g[0] = sx2x1 + sy2y1; g[1] = -sx2y1 + sy2x1; g[2] = sx2; g[3] = sy2; - if( im_invmat( mat, 4 ) ) { - im_free_dmat( mat, 0, 3, 0, 3 ); - im_free_dvector( g, 0, 3 ); - im_error( "im_clinear", "%s", _( "im_invmat failed" ) ); + if( vips_matrixinvert( mat, &matinv, NULL ) ) { + g_object_unref( mat ); + g_free( g ); + vips_error( "vips_clinear", "%s", _( "vips_invmat failed" ) ); return( -1 ); } - + scale = 0.0; angle = 0.0; xdelta = 0.0; ydelta = 0.0; for( j = 0; j < 4; j++ ) { - scale += mat[0][j] * g[j]; - angle += mat[1][j] * g[j]; - xdelta += mat[2][j] * g[j]; - ydelta += mat[3][j] * g[j]; + scale += *VIPS_MATRIX( matinv, j, 0 ) * g[j]; + angle += *VIPS_MATRIX( matinv, j, 1 ) * g[j]; + xdelta += *VIPS_MATRIX( matinv, j, 2 ) * g[j]; + ydelta += *VIPS_MATRIX( matinv, j, 3 ) * g[j]; } + g_object_unref( mat ); + g_object_unref( matinv ); + g_free( g ); + /* find the deviation of each point for the estimated variables * if it greater than 1 then the solution is not good enough * but this is handled by the main program @@ -160,7 +165,7 @@ im__clinear( TIE_POINTS *points ) dy[i] = ysec[i] - ((angle * xref[i]) + (scale * yref[i]) + ydelta); - value = sqrt( dx[i]*dx[i] + dy[i]*dy[i] ); + value = sqrt( dx[i] * dx[i] + dy[i] * dy[i] ); dev[i] = value; } @@ -169,8 +174,5 @@ im__clinear( TIE_POINTS *points ) points->l_deltax = xdelta; points->l_deltay = ydelta; - im_free_dmat( mat, 0, 3, 0, 3 ); - im_free_dvector( g, 0, 3 ); - return( 0 ); } diff --git a/libvips/mosaicing/im_improve.c b/libvips/mosaicing/im_improve.c index 8b36e085..f30fbd9f 100644 --- a/libvips/mosaicing/im_improve.c +++ b/libvips/mosaicing/im_improve.c @@ -1,14 +1,14 @@ /* @(#) Function which improves the selection of tiepoints carried out by - * @(#) im_clinear() until no points have deviation greater than 1 pixel + * @(#) vips_clinear() until no points have deviation greater than 1 pixel * @(#) No reference or secondary images are involved - * @(#) Function im_improve assumes that im_clinear has been applied on points - * @(#) No IMAGES are involved in this function and the result is + * @(#) Function vips__improve assumes that vips_clinear has been applied on points + * @(#) No images are involved in this function and the result is * @(#) returned in outpoints which is declared as a pointer in the * @(#) calling routine. Space for outpoints should be allocated in the calling * @(#) routine * @(#) - * @(#) int im_improve( inpoints, outpoints ) - * @(#) TIE_POINTS *inpoints, *outpoints; + * @(#) int vips__improve( inpoints, outpoints ) + * @(#) TiePoints *inpoints, *outpoints; * @(#) * @(#) Returns 0 on sucess and -1 on error. * @@ -56,25 +56,24 @@ #include #include -#include #include "pmosaicing.h" static void -copypoints( TIE_POINTS *pnew, TIE_POINTS *pold ) +copypoints( TiePoints *pnew, TiePoints *pold ) { - int i; + int i; - pnew->reference = pold->reference; - pnew->secondary = pold->secondary; + pnew->reference = pold->reference; + pnew->secondary = pold->secondary; - pnew->deltax = pold->deltax; - pnew->deltay = pold->deltay; - pnew->nopoints = pold->nopoints; - pnew->halfcorsize = pold->halfcorsize; + pnew->deltax = pold->deltax; + pnew->deltay = pold->deltay; + pnew->nopoints = pold->nopoints; + pnew->halfcorsize = pold->halfcorsize; pnew->halfareasize = pold->halfareasize; - for( i = 0; i < pold->nopoints; i++ ) { + for( i = 0; i < pold->nopoints; i++ ) { pnew->x_reference[i] = pold->x_reference[i]; pnew->y_reference[i] = pold->y_reference[i]; pnew->x_secondary[i] = pold->x_secondary[i]; @@ -95,10 +94,10 @@ copypoints( TIE_POINTS *pnew, TIE_POINTS *pold ) /* exclude all points with deviation greater or equal to 1.0 pixel */ static int -copydevpoints( TIE_POINTS *pnew, TIE_POINTS *pold ) +copydevpoints( TiePoints *pnew, TiePoints *pold ) { - int i; - int j; + int i; + int j; double thresh_dev,max_dev, min_dev; double *corr; @@ -108,19 +107,19 @@ copydevpoints( TIE_POINTS *pnew, TIE_POINTS *pold ) for( i = 0; i < pold->nopoints; i++ ) if( corr[i] > 0.01 ) { - if( pold->deviation[i]/corr[i] < min_dev ) + if( pold->deviation[i] / corr[i] < min_dev ) min_dev = pold->deviation[i]/corr[i] ; - if( pold->deviation[i]/corr[i] > max_dev ) + if( pold->deviation[i] / corr[i] > max_dev ) max_dev = pold->deviation[i]/corr[i]; - } + } - thresh_dev = min_dev + (max_dev - min_dev)*0.3; + thresh_dev = min_dev + (max_dev - min_dev) * 0.3; if( thresh_dev <= 1.0 ) thresh_dev = 1.0; - for( i = 0, j = 0; i < pold->nopoints; i++ ) + for( i = 0, j = 0; i < pold->nopoints; i++ ) if( pold->correlation[i] > 0.01 ) - if( pold->deviation[i]/corr[i] <= thresh_dev ) { + if( pold->deviation[i] / corr[i] <= thresh_dev ) { pnew->x_reference[j] = pold->x_reference[i]; pnew->y_reference[j] = pold->y_reference[i]; pnew->x_secondary[j] = pold->x_secondary[i]; @@ -132,9 +131,9 @@ copydevpoints( TIE_POINTS *pnew, TIE_POINTS *pold ) pnew->dy[j] = pold->dy[i]; j++; } - pnew->nopoints = j; + pnew->nopoints = j; - for( i = j; i < IM_MAXPOINTS; i++ ) { + for( i = j; i < VIPS_MAXPOINTS; i++ ) { pnew->x_reference[i] = 0; pnew->y_reference[i] = 0; pnew->x_secondary[i] = 0; @@ -155,11 +154,11 @@ copydevpoints( TIE_POINTS *pnew, TIE_POINTS *pold ) } int -im__improve( TIE_POINTS *inpoints, TIE_POINTS *outpoints ) +vips__improve( TiePoints *inpoints, TiePoints *outpoints ) { - TIE_POINTS points1, points2; - TIE_POINTS *p = &points1; - TIE_POINTS *q = &points2; + TiePoints points1, points2; + TiePoints *p = &points1; + TiePoints *q = &points2; /* p has the current state - make a new state, q, with only those * points which have a small deviation. @@ -173,12 +172,12 @@ im__improve( TIE_POINTS *inpoints, TIE_POINTS *outpoints ) /* Fit the model to the new set of points. */ - if( im__clinear( q ) ) + if( vips__clinear( q ) ) return( -1 ); /* And loop. */ - IM_SWAP( void *, p, q ); + VIPS_SWAP( void *, p, q ); } /* q has the output - copy to outpoints. diff --git a/libvips/mosaicing/im_initialize.c b/libvips/mosaicing/im_initialize.c index 98dbd188..a2db5c7e 100644 --- a/libvips/mosaicing/im_initialize.c +++ b/libvips/mosaicing/im_initialize.c @@ -34,15 +34,14 @@ #include #include -#include #include "pmosaicing.h" int -im__initialize( TIE_POINTS *points ) +vips__initialize( TiePoints *points ) { - if( im__clinear( points ) ) { - /* im_clinear failed! Set some sensible fallback values. + if( vips__clinear( points ) ) { + /* vips_clinear failed! Set some sensible fallback values. */ int i, j; double xdelta, ydelta, max_cor; @@ -76,7 +75,7 @@ im__initialize( TIE_POINTS *points ) } if( j == 0 ) { - vips_error( "im_initialize", "no tie points" ); + vips_error( "vips_initialize", "no tie points" ); return( -1 ); } diff --git a/libvips/mosaicing/im_lrcalcon.c b/libvips/mosaicing/im_lrcalcon.c index fe2af8fd..74f80591 100644 --- a/libvips/mosaicing/im_lrcalcon.c +++ b/libvips/mosaicing/im_lrcalcon.c @@ -20,10 +20,10 @@ * @(#) The calculation of the contrast is carried out based on bandno only. * @(#) The variable bandno should be between 1 and ref->Bands * @(#) - * @(#) int im_lrcalcon( ref, sec, bandno, points ) - * @(#) IMAGE *ref, *sec; + * @(#) int vips_lrcalcon( ref, sec, bandno, points ) + * @(#) VipsImage *ref, *sec; * @(#) int bandno; - * @(#) TIE_POINTS *points; see mosaic.h + * @(#) TiePoints *points; see mosaic.h * @(#) * @(#) Returns 0 on sucess and -1 on error. * @(#) @@ -47,6 +47,8 @@ * - ooops, < 0 should have been <= 0 * 10/3/03 JC * - better error message for overlap too small + * 18/6/20 kleisauke + * - convert to vips8 */ /* @@ -86,7 +88,6 @@ #include #include -#include #include "pmosaicing.h" @@ -101,9 +102,9 @@ typedef struct { * One-band uchar only. */ static int -all_black( IMAGE *im, int xpos, int ypos, int winsize ) +all_black( VipsImage *im, int xpos, int ypos, int winsize ) { - const int hwinsize = (winsize - 1)/2; + const int hwinsize = (winsize - 1) / 2; const int left = xpos - hwinsize; const int top = ypos - hwinsize; const int ls = im->Xsize; @@ -132,9 +133,9 @@ all_black( IMAGE *im, int xpos, int ypos, int winsize ) * One band uchar only, */ static int -calculate_contrast( IMAGE *im, int xpos, int ypos, int winsize ) +calculate_contrast( VipsImage *im, int xpos, int ypos, int winsize ) { - const int hwinsize = (winsize - 1)/2; + const int hwinsize = (winsize - 1) / 2; const int left = xpos - hwinsize; const int top = ypos - hwinsize; const int ls = im->Xsize; @@ -144,10 +145,10 @@ calculate_contrast( IMAGE *im, int xpos, int ypos, int winsize ) int total; line = im->data + top*ls + left; - for( total = 0, y = 0; y < winsize-1; y++ ) { + for( total = 0, y = 0; y < winsize - 1; y++ ) { p = line; - for( x = 0; x < winsize-1; x++ ) { + for( x = 0; x < winsize - 1; x++ ) { const int lrd = (int) p[0] - p[1]; const int tbd = (int) p[0] - p[ls]; @@ -175,7 +176,7 @@ pos_compare( const void *vl, const void *vr ) /* Search an area for the n best contrast areas. */ int -im__find_best_contrast( IMAGE *im, +vips__find_best_contrast( VipsImage *im, int xpos, int ypos, int xsize, int ysize, int xarray[], int yarray[], int cont[], int nbest, int hcorsize ) @@ -201,7 +202,7 @@ im__find_best_contrast( IMAGE *im, int x, y, i; if( nacross <= 0 || ndown <= 0 ) { - im_error( "im__lrcalcon", "%s", + vips_error( "vips__lrcalcon", "%s", _( "overlap too small for your search size" ) ); return( -1 ); } @@ -209,7 +210,7 @@ im__find_best_contrast( IMAGE *im, /* Malloc space for 3 int arrays, to keep the int coordinates and * the contrast. */ - if( !(pc = IM_ARRAY( NULL, nacross * ndown, PosCont )) ) + if( !(pc = VIPS_ARRAY( NULL, nacross * ndown, PosCont )) ) return( -1 ); /* Find contrast for each area. @@ -240,10 +241,10 @@ im__find_best_contrast( IMAGE *im, /* Found enough tie-points? */ if( elms < nbest ) { - im_error( "im_mosaic", + vips_error( "vips_mosaic", _( "found %d tie-points, need at least %d" ), elms, nbest ); - im_free( pc ); + g_free( pc ); return( -1 ); } @@ -258,13 +259,13 @@ im__find_best_contrast( IMAGE *im, yarray[i] = pc[i].y; cont[i] = pc[i].cont; } - im_free( pc ); + g_free( pc ); return( 0 ); } int -im__lrcalcon( IMAGE *ref, TIE_POINTS *points ) +vips__lrcalcon( VipsImage *ref, TiePoints *points ) { /* Geometry: border we must leave around each area. */ @@ -279,14 +280,14 @@ im__lrcalcon( IMAGE *ref, TIE_POINTS *points ) const int len = points->nopoints / AREAS; int i; - Rect area; + VipsRect area; /* Make sure we can read image. */ - if( im_incheck( ref ) ) + if( vips_image_wio_input( ref ) ) return( -1 ); - if( ref->Bands != 1 || ref->BandFmt != IM_BANDFMT_UCHAR ) { - im_error( "im__lrcalcon", "%s", _( "not 1-band uchar image" ) ); + if( ref->Bands != 1 || ref->BandFmt != VIPS_FORMAT_UCHAR ) { + vips_error( "vips__lrcalcon", "%s", _( "not 1-band uchar image" ) ); return( -1 ); } @@ -297,14 +298,14 @@ im__lrcalcon( IMAGE *ref, TIE_POINTS *points ) area.width = ref->Xsize; area.left = 0; area.top = 0; - im_rect_marginadjust( &area, -border ); + vips_rect_marginadjust( &area, -border ); area.width--; area.height--; /* Loop over areas, finding points. */ for( i = 0; area.top < ref->Ysize; area.top += aheight, i++ ) - if( im__find_best_contrast( ref, + if( vips__find_best_contrast( ref, area.left, area.top, area.width, area.height, points->x_reference + i*len, points->y_reference + i*len, diff --git a/libvips/mosaicing/im_lrmerge.c b/libvips/mosaicing/im_lrmerge.c index 6512e2ae..4bbe31f2 100644 --- a/libvips/mosaicing/im_lrmerge.c +++ b/libvips/mosaicing/im_lrmerge.c @@ -83,6 +83,8 @@ * - match formats and bands automatically * 22/5/14 * - wrap as a class + * 18/6/20 kleisauke + * - convert to vips8 */ /* @@ -123,12 +125,11 @@ #include #include -/* +/* Define for debug output. #define DEBUG */ #include -#include #include #include #include @@ -137,40 +138,40 @@ /* Blend luts. Shared between all lr and tb blends. */ -double *im__coef1 = NULL; -double *im__coef2 = NULL; -int *im__icoef1 = NULL; -int *im__icoef2 = NULL; +double *vips__coef1 = NULL; +double *vips__coef2 = NULL; +int *vips__icoef1 = NULL; +int *vips__icoef2 = NULL; /* Create a lut for the merging area. Always BLEND_SIZE entries, we * scale later when we index it. */ int -im__make_blend_luts( void ) +vips__make_blend_luts( void ) { int x; /* Already done? */ - if( im__coef1 && im__coef2 ) + if( vips__coef1 && vips__coef2 ) return( 0 ); /* Allocate and fill. */ - im__coef1 = IM_ARRAY( NULL, BLEND_SIZE, double ); - im__coef2 = IM_ARRAY( NULL, BLEND_SIZE, double ); - im__icoef1 = IM_ARRAY( NULL, BLEND_SIZE, int ); - im__icoef2 = IM_ARRAY( NULL, BLEND_SIZE, int ); - if( !im__coef1 || !im__coef2 || !im__icoef1 || !im__icoef2 ) + vips__coef1 = VIPS_ARRAY( NULL, BLEND_SIZE, double ); + vips__coef2 = VIPS_ARRAY( NULL, BLEND_SIZE, double ); + vips__icoef1 = VIPS_ARRAY( NULL, BLEND_SIZE, int ); + vips__icoef2 = VIPS_ARRAY( NULL, BLEND_SIZE, int ); + if( !vips__coef1 || !vips__coef2 || !vips__icoef1 || !vips__icoef2 ) return( -1 ); for( x = 0; x < BLEND_SIZE; x++ ) { double a = VIPS_PI * x / (BLEND_SIZE - 1.0); - im__coef1[x] = (cos( a ) + 1.0) / 2.0; - im__coef2[x] = 1.0 - im__coef1[x]; - im__icoef1[x] = im__coef1[x] * BLEND_SCALE; - im__icoef2[x] = im__coef2[x] * BLEND_SCALE; + vips__coef1[x] = (cos( a ) + 1.0) / 2.0; + vips__coef2[x] = 1.0 - vips__coef1[x]; + vips__icoef1[x] = vips__coef1[x] * BLEND_SCALE; + vips__icoef2[x] = vips__coef2[x] * BLEND_SCALE; } return( 0 ); @@ -179,10 +180,10 @@ im__make_blend_luts( void ) /* Return the position of the first non-zero pel from the left. */ static int -find_first( REGION *ir, int *pos, int x, int y, int w ) +find_first( VipsRegion *ir, int *pos, int x, int y, int w ) { - VipsPel *pr = IM_REGION_ADDR( ir, x, y ); - IMAGE *im = ir->im; + VipsPel *pr = VIPS_REGION_ADDR( ir, x, y ); + VipsImage *im = ir->im; int ne = w * im->Bands; int i; @@ -202,19 +203,19 @@ find_first( REGION *ir, int *pos, int x, int y, int w ) } switch( im->BandFmt ) { - case IM_BANDFMT_UCHAR: lsearch( unsigned char ); break; - case IM_BANDFMT_CHAR: lsearch( signed char ); break; - case IM_BANDFMT_USHORT: lsearch( unsigned short ); break; - case IM_BANDFMT_SHORT: lsearch( signed short ); break; - case IM_BANDFMT_UINT: lsearch( unsigned int ); break; - case IM_BANDFMT_INT: lsearch( signed int ); break; - case IM_BANDFMT_FLOAT: lsearch( float ); break; - case IM_BANDFMT_DOUBLE: lsearch( double ); break; - case IM_BANDFMT_COMPLEX:lsearch( float ); break; - case IM_BANDFMT_DPCOMPLEX:lsearch( double ); break; + case VIPS_FORMAT_UCHAR: lsearch( unsigned char ); break; + case VIPS_FORMAT_CHAR: lsearch( signed char ); break; + case VIPS_FORMAT_USHORT: lsearch( unsigned short ); break; + case VIPS_FORMAT_SHORT: lsearch( signed short ); break; + case VIPS_FORMAT_UINT: lsearch( unsigned int ); break; + case VIPS_FORMAT_INT: lsearch( signed int ); break; + case VIPS_FORMAT_FLOAT: lsearch( float ); break; + case VIPS_FORMAT_DOUBLE: lsearch( double ); break; + case VIPS_FORMAT_COMPLEX: lsearch( float ); break; + case VIPS_FORMAT_DPCOMPLEX: lsearch( double ); break; default: - im_error( "im_lrmerge", "%s", _( "internal error" ) ); + vips_error( "vips_lrmerge", "%s", _( "internal error" ) ); return( -1 ); } @@ -228,10 +229,10 @@ find_first( REGION *ir, int *pos, int x, int y, int w ) /* Return the position of the first non-zero pel from the right. */ static int -find_last( REGION *ir, int *pos, int x, int y, int w ) +find_last( VipsRegion *ir, int *pos, int x, int y, int w ) { - VipsPel *pr = IM_REGION_ADDR( ir, x, y ); - IMAGE *im = ir->im; + VipsPel *pr = VIPS_REGION_ADDR( ir, x, y ); + VipsImage *im = ir->im; int ne = w * im->Bands; int i; @@ -251,19 +252,19 @@ find_last( REGION *ir, int *pos, int x, int y, int w ) } switch( im->BandFmt ) { - case IM_BANDFMT_UCHAR: rsearch( unsigned char ); break; - case IM_BANDFMT_CHAR: rsearch( signed char ); break; - case IM_BANDFMT_USHORT: rsearch( unsigned short ); break; - case IM_BANDFMT_SHORT: rsearch( signed short ); break; - case IM_BANDFMT_UINT: rsearch( unsigned int ); break; - case IM_BANDFMT_INT: rsearch( signed int ); break; - case IM_BANDFMT_FLOAT: rsearch( float ); break; - case IM_BANDFMT_DOUBLE: rsearch( double ); break; - case IM_BANDFMT_COMPLEX:rsearch( float ); break; - case IM_BANDFMT_DPCOMPLEX:rsearch( double ); break; + case VIPS_FORMAT_UCHAR: rsearch( unsigned char ); break; + case VIPS_FORMAT_CHAR: rsearch( signed char ); break; + case VIPS_FORMAT_USHORT: rsearch( unsigned short ); break; + case VIPS_FORMAT_SHORT: rsearch( signed short ); break; + case VIPS_FORMAT_UINT: rsearch( unsigned int ); break; + case VIPS_FORMAT_INT: rsearch( signed int ); break; + case VIPS_FORMAT_FLOAT: rsearch( float ); break; + case VIPS_FORMAT_DOUBLE: rsearch( double ); break; + case VIPS_FORMAT_COMPLEX: rsearch( float ); break; + case VIPS_FORMAT_DPCOMPLEX: rsearch( double ); break; default: - im_error( "im_lrmerge", "%s", _( "internal error" ) ); + vips_error( "vipslrmerge", "%s", _( "internal error" ) ); return( -1 ); } @@ -277,11 +278,11 @@ find_last( REGION *ir, int *pos, int x, int y, int w ) /* Make sure we have first/last for this area. */ static int -make_firstlast( MergeInfo *inf, Overlapping *ovlap, Rect *oreg ) +make_firstlast( MergeInfo *inf, Overlapping *ovlap, VipsRect *oreg ) { - REGION *rir = inf->rir; - REGION *sir = inf->sir; - Rect rr, sr; + VipsRegion *rir = inf->rir; + VipsRegion *sir = inf->sir; + VipsRect rr, sr; int y, yr, ys; int missing; @@ -294,7 +295,7 @@ make_firstlast( MergeInfo *inf, Overlapping *ovlap, Rect *oreg ) /* Do we already have first/last for this area? Bail out if we do. */ missing = 0; - for( y = oreg->top; y < IM_RECT_BOTTOM( oreg ); y++ ) { + for( y = oreg->top; y < VIPS_RECT_BOTTOM( oreg ); y++ ) { const int j = y - ovlap->overlap.top; const int first = ovlap->first[j]; @@ -329,7 +330,7 @@ make_firstlast( MergeInfo *inf, Overlapping *ovlap, Rect *oreg ) sr.top -= ovlap->sarea.top; #ifdef DEBUG - printf( "im__lrmerge: making first/last for areas:\n" ); + printf( "vips__lrmerge: making first/last for areas:\n" ); printf( "ref: left = %d, top = %d, width = %d, height = %d\n", rr.left, rr.top, rr.width, rr.height ); printf( "sec: left = %d, top = %d, width = %d, height = %d\n", @@ -338,7 +339,8 @@ make_firstlast( MergeInfo *inf, Overlapping *ovlap, Rect *oreg ) /* Make pixels. */ - if( im_prepare( rir, &rr ) || im_prepare( sir, &sr ) ) { + if( vips_region_prepare( rir, &rr ) || + vips_region_prepare( sir, &sr ) ) { g_mutex_unlock( ovlap->fl_lock ); return( -1 ); } @@ -346,7 +348,7 @@ make_firstlast( MergeInfo *inf, Overlapping *ovlap, Rect *oreg ) /* Make first/last cache. */ for( y = oreg->top, yr = rr.top, ys = sr.top; - y < IM_RECT_BOTTOM( oreg ); y++, yr++, ys++ ) { + y < VIPS_RECT_BOTTOM( oreg ); y++, yr++, ys++ ) { const int j = y - ovlap->overlap.top; int *first = &ovlap->first[j]; int *last = &ovlap->last[j]; @@ -406,8 +408,8 @@ make_firstlast( MergeInfo *inf, Overlapping *ovlap, Rect *oreg ) TYPE *ts = (TYPE *) (IN2); \ TYPE *tq = (TYPE *) (OUT); \ const int cb = (B); \ - const int left = IM_CLIP( 0, first - oreg->left, oreg->width ); \ - const int right = IM_CLIP( left, last - oreg->left, oreg->width ); \ + const int left = VIPS_CLIP( 0, first - oreg->left, oreg->width ); \ + const int right = VIPS_CLIP( left, last - oreg->left, oreg->width ); \ int ref_zero; \ int sec_zero; \ int x, b; \ @@ -437,8 +439,8 @@ make_firstlast( MergeInfo *inf, Overlapping *ovlap, Rect *oreg ) if( !ref_zero && !sec_zero ) { \ int inx = ((x + oreg->left - first) << \ BLEND_SHIFT) / bwidth; \ - int c1 = im__icoef1[inx]; \ - int c2 = im__icoef2[inx]; \ + int c1 = vips__icoef1[inx]; \ + int c2 = vips__icoef2[inx]; \ \ for( b = 0; b < cb; b++, i++ ) \ tq[i] = c1 * tr[i] / BLEND_SCALE + \ @@ -473,8 +475,8 @@ make_firstlast( MergeInfo *inf, Overlapping *ovlap, Rect *oreg ) TYPE *ts = (TYPE *) (IN2); \ TYPE *tq = (TYPE *) (OUT); \ const int cb = (B); \ - const int left = IM_CLIP( 0, first - oreg->left, oreg->width ); \ - const int right = IM_CLIP( left, last - oreg->left, oreg->width ); \ + const int left = VIPS_CLIP( 0, first - oreg->left, oreg->width ); \ + const int right = VIPS_CLIP( left, last - oreg->left, oreg->width ); \ int ref_zero; \ int sec_zero; \ int x, b; \ @@ -504,8 +506,8 @@ make_firstlast( MergeInfo *inf, Overlapping *ovlap, Rect *oreg ) if( !ref_zero && !sec_zero ) { \ int inx = ((x + oreg->left - first) << \ BLEND_SHIFT) / bwidth; \ - double c1 = im__coef1[inx]; \ - double c2 = im__coef2[inx]; \ + double c1 = vips__coef1[inx]; \ + double c2 = vips__coef2[inx]; \ \ for( b = 0; b < cb; b++, i++ ) \ tq[i] = c1 * tr[i] + c2 * ts[i]; \ @@ -535,13 +537,13 @@ make_firstlast( MergeInfo *inf, Overlapping *ovlap, Rect *oreg ) /* Left-right blend function for non-labpack images. */ static int -lr_blend( REGION *or, MergeInfo *inf, Overlapping *ovlap, Rect *oreg ) +lr_blend( VipsRegion *or, MergeInfo *inf, Overlapping *ovlap, VipsRect *oreg ) { - REGION *rir = inf->rir; - REGION *sir = inf->sir; - IMAGE *im = or->im; + VipsRegion *rir = inf->rir; + VipsRegion *sir = inf->sir; + VipsImage *im = or->im; - Rect prr, psr; + VipsRect prr, psr; int y, yr, ys; /* Make sure we have a complete first/last set for this area. @@ -563,18 +565,17 @@ lr_blend( REGION *or, MergeInfo *inf, Overlapping *ovlap, Rect *oreg ) /* Make pixels. */ - if( im_prepare( rir, &prr ) ) - return( -1 ); - if( im_prepare( sir, &psr ) ) + if( vips_region_prepare( rir, &prr ) || + vips_region_prepare( sir, &psr ) ) return( -1 ); /* Loop down overlap area. */ for( y = oreg->top, yr = prr.top, ys = psr.top; - y < IM_RECT_BOTTOM( oreg ); y++, yr++, ys++ ) { - VipsPel *pr = IM_REGION_ADDR( rir, prr.left, yr ); - VipsPel *ps = IM_REGION_ADDR( sir, psr.left, ys ); - VipsPel *q = IM_REGION_ADDR( or, oreg->left, y ); + y < VIPS_RECT_BOTTOM( oreg ); y++, yr++, ys++ ) { + VipsPel *pr = VIPS_REGION_ADDR( rir, prr.left, yr ); + VipsPel *ps = VIPS_REGION_ADDR( sir, psr.left, ys ); + VipsPel *q = VIPS_REGION_ADDR( or, oreg->left, y ); const int j = y - ovlap->overlap.top; const int first = ovlap->first[j]; @@ -582,29 +583,29 @@ lr_blend( REGION *or, MergeInfo *inf, Overlapping *ovlap, Rect *oreg ) const int bwidth = last - first; switch( im->BandFmt ) { - case IM_BANDFMT_UCHAR: + case VIPS_FORMAT_UCHAR: iblend( unsigned char, im->Bands, pr, ps, q ); break; - case IM_BANDFMT_CHAR: + case VIPS_FORMAT_CHAR: iblend( signed char, im->Bands, pr, ps, q ); break; - case IM_BANDFMT_USHORT: + case VIPS_FORMAT_USHORT: iblend( unsigned short, im->Bands, pr, ps, q ); break; - case IM_BANDFMT_SHORT: + case VIPS_FORMAT_SHORT: iblend( signed short, im->Bands, pr, ps, q ); break; - case IM_BANDFMT_UINT: + case VIPS_FORMAT_UINT: iblend( unsigned int, im->Bands, pr, ps, q ); break; - case IM_BANDFMT_INT: + case VIPS_FORMAT_INT: iblend( signed int, im->Bands, pr, ps, q ); break; - case IM_BANDFMT_FLOAT: + case VIPS_FORMAT_FLOAT: fblend( float, im->Bands, pr, ps, q ); break; - case IM_BANDFMT_DOUBLE: + case VIPS_FORMAT_DOUBLE: fblend( double, im->Bands, pr, ps, q ); break; - case IM_BANDFMT_COMPLEX: - fblend( float, im->Bands*2, pr, ps, q ); break; - case IM_BANDFMT_DPCOMPLEX: - fblend( double, im->Bands*2, pr, ps, q ); break; + case VIPS_FORMAT_COMPLEX: + fblend( float, im->Bands * 2, pr, ps, q ); break; + case VIPS_FORMAT_DPCOMPLEX: + fblend( double, im->Bands * 2, pr, ps, q ); break; default: - im_error( "im_lrmerge", "%s", _( "internal error" ) ); + vips_error( "vips_lrmerge", "%s", _( "internal error" ) ); return( -1 ); } } @@ -612,14 +613,14 @@ lr_blend( REGION *or, MergeInfo *inf, Overlapping *ovlap, Rect *oreg ) return( 0 ); } -/* Left-right blend function for IM_CODING_LABQ images. +/* Left-right blend function for VIPS_CODING_LABQ images. */ static int -lr_blend_labpack( REGION *or, MergeInfo *inf, Overlapping *ovlap, Rect *oreg ) +lr_blend_labpack( VipsRegion *or, MergeInfo *inf, Overlapping *ovlap, VipsRect *oreg ) { - REGION *rir = inf->rir; - REGION *sir = inf->sir; - Rect prr, psr; + VipsRegion *rir = inf->rir; + VipsRegion *sir = inf->sir; + VipsRect prr, psr; int y, yr, ys; /* Make sure we have a complete first/last set for this area. This @@ -642,18 +643,17 @@ lr_blend_labpack( REGION *or, MergeInfo *inf, Overlapping *ovlap, Rect *oreg ) /* Make pixels. */ - if( im_prepare( rir, &prr ) ) - return( -1 ); - if( im_prepare( sir, &psr ) ) + if( vips_region_prepare( rir, &prr ) || + vips_region_prepare( sir, &psr ) ) return( -1 ); /* Loop down overlap area. */ for( y = oreg->top, yr = prr.top, ys = psr.top; - y < IM_RECT_BOTTOM( oreg ); y++, yr++, ys++ ) { - VipsPel *pr = IM_REGION_ADDR( rir, prr.left, yr ); - VipsPel *ps = IM_REGION_ADDR( sir, psr.left, ys ); - VipsPel *q = IM_REGION_ADDR( or, oreg->left, y ); + y < VIPS_RECT_BOTTOM( oreg ); y++, yr++, ys++ ) { + VipsPel *pr = VIPS_REGION_ADDR( rir, prr.left, yr ); + VipsPel *ps = VIPS_REGION_ADDR( sir, psr.left, ys ); + VipsPel *q = VIPS_REGION_ADDR( or, oreg->left, y ); const int j = y - ovlap->overlap.top; const int first = ovlap->first[j]; @@ -681,35 +681,62 @@ lr_blend_labpack( REGION *or, MergeInfo *inf, Overlapping *ovlap, Rect *oreg ) return( 0 ); } -static void * -lock_free( GMutex *lock ) +static void +lock_free( VipsImage *image, GMutex *lock ) { - vips_g_mutex_free( lock ); - - return( NULL ); + VIPS_FREEF( vips_g_mutex_free, lock ); } /* Build basic per-call state and do some geometry calculations. Shared with - * im_tbmerge, so not static. + * vips_tbmerge, so not static. */ Overlapping * -im__build_mergestate( const char *domain, - IMAGE *ref, IMAGE *sec, IMAGE *out, int dx, int dy, int mwidth ) +vips__build_mergestate( const char *domain, + VipsImage *ref, VipsImage *sec, VipsImage *out, int dx, int dy, int mwidth ) { - IMAGE **vec; + VipsImage **t = (VipsImage **) + vips_object_local_array( VIPS_OBJECT( out ), 4 ); + + VipsImage **arry; Overlapping *ovlap; int x; - if( !(vec = im__insert_base( domain, ref, sec, out )) || - !(ovlap = IM_NEW( out, Overlapping )) ) + /* TODO(kleisauke): Copied from vips_insert, perhaps we + * need a separate function for this? + * (just like im__insert_base) */ + if( vips_image_pio_input( ref ) || + vips_image_pio_input( sec ) || + vips_check_bands_1orn( domain, + ref, sec ) || + vips_check_coding_known( domain, ref ) || + vips_check_coding_same( domain, + ref, sec ) ) return( NULL ); + + /* Cast our input images up to a common format and bands. + */ + if( vips__formatalike( ref, sec, &t[0], &t[1] ) || + vips__bandalike( domain, t[0], t[1], &t[2], &t[3] ) ) + return( NULL ); + + if( !(arry = vips_allocate_input_array( out, + t[2], t[3], NULL )) ) + return( NULL ); + + if( vips_image_pipeline_array( out, + VIPS_DEMAND_STYLE_SMALLTILE, arry ) ) + return( NULL ); + if( mwidth < -1 ) { - im_error( domain, "%s", _( "mwidth must be -1 or >= 0" ) ); + vips_error( domain, "%s", _( "mwidth must be -1 or >= 0" ) ); return( NULL ); } - ovlap->ref = vec[0]; - ovlap->sec = vec[1]; + if( !(ovlap = VIPS_NEW( out, Overlapping )) ) + return( NULL ); + + ovlap->ref = arry[0]; + ovlap->sec = arry[1]; ovlap->out = out; ovlap->dx = dx; ovlap->dy = dy; @@ -731,15 +758,15 @@ im__build_mergestate( const char *domain, /* Compute overlap. */ - im_rect_intersectrect( &ovlap->rarea, &ovlap->sarea, &ovlap->overlap ); - if( im_rect_isempty( &ovlap->overlap ) ) { - im_error( domain, "%s", _( "no overlap" ) ); + vips_rect_intersectrect( &ovlap->rarea, &ovlap->sarea, &ovlap->overlap ); + if( vips_rect_isempty( &ovlap->overlap ) ) { + vips_error( domain, "%s", _( "no overlap" ) ); return( NULL ); } /* Find position and size of output image. */ - im_rect_unionrect( &ovlap->rarea, &ovlap->sarea, &ovlap->oarea ); + vips_rect_unionrect( &ovlap->rarea, &ovlap->sarea, &ovlap->oarea ); /* Now: translate everything, so that the output image, not the left * image, is at (0,0). @@ -755,28 +782,26 @@ im__build_mergestate( const char *domain, /* Make sure blend luts are built. */ - im__make_blend_luts(); + vips__make_blend_luts(); /* Size of first/last cache. Could be either of these ... just pick * the larger. */ - ovlap->flsize = IM_MAX( ovlap->overlap.width, ovlap->overlap.height ); + ovlap->flsize = VIPS_MAX( ovlap->overlap.width, ovlap->overlap.height ); /* Build first/last cache. */ - ovlap->first = IM_ARRAY( out, ovlap->flsize, int ); - ovlap->last = IM_ARRAY( out, ovlap->flsize, int ); + ovlap->first = VIPS_ARRAY( out, ovlap->flsize, int ); + ovlap->last = VIPS_ARRAY( out, ovlap->flsize, int ); if( !ovlap->first || !ovlap->last ) return( NULL ); for( x = 0; x < ovlap->flsize; x++ ) ovlap->first[x] = -1; ovlap->fl_lock = vips_g_mutex_new(); - if( im_add_close_callback( out, - (im_callback_fn) lock_free, ovlap->fl_lock, NULL ) ) { - vips_g_mutex_free( ovlap->fl_lock ); - return( NULL ); - } + + g_signal_connect( out, "close", + G_CALLBACK( lock_free ), ovlap->fl_lock ); return( ovlap ); } @@ -784,27 +809,27 @@ im__build_mergestate( const char *domain, /* Build per-call state. */ static Overlapping * -build_lrstate( IMAGE *ref, IMAGE *sec, IMAGE *out, int dx, int dy, int mwidth ) +build_lrstate( VipsImage *ref, VipsImage *sec, VipsImage *out, int dx, int dy, int mwidth ) { Overlapping *ovlap; - if( !(ovlap = im__build_mergestate( "im_lrmerge", + if( !(ovlap = vips__build_mergestate( "vips_lrmerge", ref, sec, out, dx, dy, mwidth )) ) return( NULL ); /* Select blender. */ switch( ovlap->ref->Coding ) { - case IM_CODING_LABQ: + case VIPS_CODING_LABQ: ovlap->blend = lr_blend_labpack; break; - case IM_CODING_NONE: + case VIPS_CODING_NONE: ovlap->blend = lr_blend; break; default: - im_error( "im_lrmerge", "%s", _( "unknown coding type" ) ); + vips_error( "vips_lrmerge", "%s", _( "unknown coding type" ) ); return( NULL ); } @@ -819,9 +844,9 @@ build_lrstate( IMAGE *ref, IMAGE *sec, IMAGE *out, int dx, int dy, int mwidth ) /* Is there too much overlap? ie. right edge of ref image is greater * than right edge of sec image, or left > left. */ - if( IM_RECT_RIGHT( &ovlap->rarea ) > IM_RECT_RIGHT( &ovlap->sarea ) || + if( VIPS_RECT_RIGHT( &ovlap->rarea ) > VIPS_RECT_RIGHT( &ovlap->sarea ) || ovlap->rarea.left > ovlap->sarea.left ) { - im_error( "im_lrmerge", "%s", _( "too much overlap" ) ); + vips_error( "vips_lrmerge", "%s", _( "too much overlap" ) ); return( NULL ); } @@ -836,12 +861,12 @@ build_lrstate( IMAGE *ref, IMAGE *sec, IMAGE *out, int dx, int dy, int mwidth ) * or the sec images. Attach output to the appropriate part of the input image. * area is the position that ir->im occupies in the output image. * - * Shared with im_tbmerge(), so not static. + * Shared with vips_tbmerge(), so not static. */ int -im__attach_input( REGION *or, REGION *ir, Rect *area ) +vips__attach_input( VipsRegion *or, VipsRegion *ir, VipsRect *area ) { - Rect r = or->valid; + VipsRect r = or->valid; /* Translate to source coordinate space. */ @@ -850,12 +875,12 @@ im__attach_input( REGION *or, REGION *ir, Rect *area ) /* Demand input. */ - if( im_prepare( ir, &r ) ) + if( vips_region_prepare( ir, &r ) ) return( -1 ); /* Attach or to ir. */ - if( im_region_region( or, ir, &or->valid, r.left, r.top ) ) + if( vips_region_region( or, ir, &or->valid, r.left, r.top ) ) return( -1 ); return( 0 ); @@ -865,12 +890,12 @@ im__attach_input( REGION *or, REGION *ir, Rect *area ) * above, but just do a sub-area of the output, and make sure we copy rather * than just pointer-fiddling. reg is the sub-area of or->valid we should do. * - * Shared with im_tbmerge(), so not static. + * Shared with vips_tbmerge(), so not static. */ int -im__copy_input( REGION *or, REGION *ir, Rect *area, Rect *reg ) +vips__copy_input( VipsRegion *or, VipsRegion *ir, VipsRect *area, VipsRect *reg ) { - Rect r = *reg; + VipsRect r = *reg; /* Translate to source coordinate space. */ @@ -879,37 +904,38 @@ im__copy_input( REGION *or, REGION *ir, Rect *area, Rect *reg ) /* Paint this area of ir into or. */ - if( im_prepare_to( ir, or, &r, reg->left, reg->top ) ) + if( vips_region_prepare_to( ir, or, &r, reg->left, reg->top ) ) return( -1 ); return( 0 ); } -/* Generate function for merge. This is shared between im_lrmerge() and - * im_tbmerge(). +/* Generate function for merge. This is shared between vips_lrmerge() and + * vips_tbmerge(). */ int -im__merge_gen( REGION *or, void *seq, void *a, void *b ) +vips__merge_gen( VipsRegion *or, void *seq, void *a, void *b, + gboolean *stop ) { MergeInfo *inf = (MergeInfo *) seq; Overlapping *ovlap = (Overlapping *) a; - Rect *r = &or->valid; - Rect rreg, sreg, oreg; + VipsRect *r = &or->valid; + VipsRect rreg, sreg, oreg; /* Find intersection with overlap, ref and sec parts. */ - im_rect_intersectrect( r, &ovlap->rpart, &rreg ); - im_rect_intersectrect( r, &ovlap->spart, &sreg ); + vips_rect_intersectrect( r, &ovlap->rpart, &rreg ); + vips_rect_intersectrect( r, &ovlap->spart, &sreg ); /* Do easy cases first: can we satisfy this demand with pixels just * from ref, or just from sec. */ - if( im_rect_equalsrect( r, &rreg ) ) { - if( im__attach_input( or, inf->rir, &ovlap->rarea ) ) + if( vips_rect_equalsrect( r, &rreg ) ) { + if( vips__attach_input( or, inf->rir, &ovlap->rarea ) ) return( -1 ); } - else if( im_rect_equalsrect( r, &sreg ) ) { - if( im__attach_input( or, inf->sir, &ovlap->sarea ) ) + else if( vips_rect_equalsrect( r, &sreg ) ) { + if( vips__attach_input( or, inf->sir, &ovlap->sarea ) ) return( -1 ); } else { @@ -923,17 +949,17 @@ im__merge_gen( REGION *or, void *seq, void *a, void *b ) /* Need intersections with whole of left & right, and overlap * too. */ - im_rect_intersectrect( r, &ovlap->rarea, &rreg ); - im_rect_intersectrect( r, &ovlap->sarea, &sreg ); - im_rect_intersectrect( r, &ovlap->overlap, &oreg ); + vips_rect_intersectrect( r, &ovlap->rarea, &rreg ); + vips_rect_intersectrect( r, &ovlap->sarea, &sreg ); + vips_rect_intersectrect( r, &ovlap->overlap, &oreg ); - im_region_black( or ); - if( !im_rect_isempty( &rreg ) ) - if( im__copy_input( or, + vips_region_black( or ); + if( !vips_rect_isempty( &rreg ) ) + if( vips__copy_input( or, inf->rir, &ovlap->rarea, &rreg ) ) return( -1 ); - if( !im_rect_isempty( &sreg ) ) - if( im__copy_input( or, + if( !vips_rect_isempty( &sreg ) ) + if( vips__copy_input( or, inf->sir, &ovlap->sarea, &sreg ) ) return( -1 ); @@ -947,7 +973,7 @@ im__merge_gen( REGION *or, void *seq, void *a, void *b ) /* Now blat in the blended area. */ - if( !im_rect_isempty( &oreg ) ) + if( !vips_rect_isempty( &oreg ) ) if( ovlap->blend( or, inf, ovlap, &oreg ) ) return( -1 ); } @@ -955,48 +981,33 @@ im__merge_gen( REGION *or, void *seq, void *a, void *b ) return( 0 ); } -/* Stop function. Shared with im_tbmerge(). Free explicitly to reduce mem +/* Stop function. Shared with vips_tbmerge(). Free explicitly to reduce mem * requirements quickly for large mosaics. */ int -im__stop_merge( void *seq, void *a, void *b ) +vips__stop_merge( void *seq, void *a, void *b ) { MergeInfo *inf = (MergeInfo *) seq; - if( inf->rir ) { - im_region_free( inf->rir ); - inf->rir = NULL; - } - if( inf->sir ) { - im_region_free( inf->sir ); - inf->sir = NULL; - } - if( inf->from1 ) { - im_free( inf->from1 ); - inf->from1 = NULL; - } - if( inf->from2 ) { - im_free( inf->from2 ); - inf->from2 = NULL; - } - if( inf->merge ) { - im_free( inf->merge ); - inf->merge = NULL; - } - im_free( inf ); + VIPS_UNREF( inf->rir ); + VIPS_UNREF( inf->sir ); + VIPS_FREE( inf->from1 ); + VIPS_FREE( inf->from2 ); + VIPS_FREE( inf->merge ); + g_free( inf ); return( 0 ); } -/* Start function. Shared with im_tbmerge(). +/* Start function. Shared with vips_tbmerge(). */ void * -im__start_merge( IMAGE *out, void *a, void *b ) +vips__start_merge( VipsImage *out, void *a, void *b ) { Overlapping *ovlap = (Overlapping *) a; MergeInfo *inf; - if( !(inf = IM_NEW( NULL, MergeInfo )) ) + if( !(inf = VIPS_NEW( NULL, MergeInfo )) ) return( NULL ); inf->rir = NULL; @@ -1005,24 +1016,24 @@ im__start_merge( IMAGE *out, void *a, void *b ) inf->from2 = NULL; inf->merge = NULL; - /* If this is going to be a IM_CODING_LABQ, we need IM_CODING_LABQ + /* If this is going to be a VIPS_CODING_LABQ, we need VIPS_CODING_LABQ * blend buffers. */ - if( out->Coding == IM_CODING_LABQ ) { - inf->from1 = IM_ARRAY( NULL, ovlap->blsize * 3, float ); - inf->from2 = IM_ARRAY( NULL, ovlap->blsize * 3, float ); - inf->merge = IM_ARRAY( NULL, ovlap->blsize * 3, float ); + if( out->Coding == VIPS_CODING_LABQ ) { + inf->from1 = VIPS_ARRAY( NULL, ovlap->blsize * 3, float ); + inf->from2 = VIPS_ARRAY( NULL, ovlap->blsize * 3, float ); + inf->merge = VIPS_ARRAY( NULL, ovlap->blsize * 3, float ); if( !inf->from1 || !inf->from2 || !inf->merge ) { - im__stop_merge( inf, NULL, NULL ); + vips__stop_merge( inf, NULL, NULL ); return( NULL ); } } - inf->rir = im_region_create( ovlap->ref ); - inf->sir = im_region_create( ovlap->sec ); + inf->rir = vips_region_new( ovlap->ref ); + inf->sir = vips_region_new( ovlap->sec ); if( !inf->rir || !inf->sir ) { - im__stop_merge( inf, NULL, NULL ); + vips__stop_merge( inf, NULL, NULL ); return( NULL ); } @@ -1030,12 +1041,12 @@ im__start_merge( IMAGE *out, void *a, void *b ) } int -im__lrmerge( IMAGE *ref, IMAGE *sec, IMAGE *out, int dx, int dy, int mwidth ) +vips__lrmerge( VipsImage *ref, VipsImage *sec, VipsImage *out, int dx, int dy, int mwidth ) { Overlapping *ovlap; #ifdef DEBUG - printf( "im__lrmerge %s %s %s %d %d %d\n", + printf( "vips__lrmerge %s %s %s %d %d %d\n", ref->filename, sec->filename, out->filename, dx, dy, mwidth ); printf( "ref is %d x %d pixels\n", ref->Xsize, ref->Ysize ); @@ -1044,12 +1055,14 @@ im__lrmerge( IMAGE *ref, IMAGE *sec, IMAGE *out, int dx, int dy, int mwidth ) if( dx > 0 || dx < 1 - ref->Xsize ) { #ifdef DEBUG - printf( "im__lrmerge: no overlap, using insert\n" ); + printf( "vips__lrmerge: no overlap, using insert\n" ); #endif /* No overlap, use insert instead. */ - if( im_insert( ref, sec, out, -dx, -dy ) ) + if( vips_insert( ref, sec, &out, -dx, -dy, + "expand", TRUE, + NULL ) ) return( -1 ); out->Xoffset = -dx; out->Yoffset = -dy; @@ -1060,25 +1073,24 @@ im__lrmerge( IMAGE *ref, IMAGE *sec, IMAGE *out, int dx, int dy, int mwidth ) if( !(ovlap = build_lrstate( ref, sec, out, dx, dy, mwidth )) ) return( -1 ); - if( im_cp_descv( out, ovlap->ref, ovlap->sec, NULL ) ) + if( vips_image_pipelinev( out, + VIPS_DEMAND_STYLE_THINSTRIP, ovlap->ref, ovlap->sec, NULL ) ) return( -1 ); + out->Xsize = ovlap->oarea.width; out->Ysize = ovlap->oarea.height; out->Xoffset = -dx; out->Yoffset = -dy; - if( im_demand_hint( out, IM_THINSTRIP, ovlap->ref, ovlap->sec, NULL ) ) - return( -1 ); - - if( im_generate( out, - im__start_merge, im__merge_gen, im__stop_merge, ovlap, NULL ) ) + if( vips_image_generate( out, + vips__start_merge, vips__merge_gen, vips__stop_merge, ovlap, NULL ) ) return( -1 ); return ( 0 ); } const char * -im__get_mosaic_name( VipsImage *image ) +vips__get_mosaic_name( VipsImage *image ) { const char *name; @@ -1093,30 +1105,40 @@ im__get_mosaic_name( VipsImage *image ) } void -im__add_mosaic_name( VipsImage *image ) +vips__add_mosaic_name( VipsImage *image ) { - static int serial = 0; + static int global_serial = 0; + + /* TODO(kleisauke): Could we call vips_image_temp_name instead? */ + /* Old glibs named this differently. + */ + int serial = +#if GLIB_CHECK_VERSION( 2, 30, 0 ) + g_atomic_int_add( &global_serial, 1 ); +#else + g_atomic_int_exchange_and_add( &global_serial, 1 ); +#endif char name[256]; /* We must override any inherited name, so don't test for doesn't * exist before setting. */ - vips_snprintf( name, 256, "mosaic-temp-%d", serial++ ); + vips_snprintf( name, 256, "mosaic-temp-%d", serial ); vips_image_set_string( image, "mosaic-name", name ); } int -im_lrmerge( IMAGE *ref, IMAGE *sec, IMAGE *out, int dx, int dy, int mwidth ) +vips_lrmerge( VipsImage *ref, VipsImage *sec, VipsImage *out, int dx, int dy, int mwidth ) { - if( im__lrmerge( ref, sec, out, dx, dy, mwidth ) ) + if( vips__lrmerge( ref, sec, out, dx, dy, mwidth ) ) return( -1 ); - im__add_mosaic_name( out ); - if( im_histlin( out, "#LRJOIN <%s> <%s> <%s> <%d> <%d> <%d>", - im__get_mosaic_name( ref ), - im__get_mosaic_name( sec ), - im__get_mosaic_name( out ), + vips__add_mosaic_name( out ); + if( vips_image_history_printf( out, "#LRJOIN <%s> <%s> <%s> <%d> <%d> <%d>", + vips__get_mosaic_name( ref ), + vips__get_mosaic_name( sec ), + vips__get_mosaic_name( out ), -dx, -dy, mwidth ) ) return( -1 ); diff --git a/libvips/mosaicing/im_lrmosaic.c b/libvips/mosaicing/im_lrmosaic.c index 3f425d3a..82cc9ef6 100644 --- a/libvips/mosaicing/im_lrmosaic.c +++ b/libvips/mosaicing/im_lrmosaic.c @@ -25,6 +25,8 @@ * - remove balance stuff * - any mix of types and bands * - cleanups + * 18/6/20 kleisauke + * - convert to vips8 */ /* @@ -54,6 +56,10 @@ */ +/* Define for debug output. +#define DEBUG + */ + #ifdef HAVE_CONFIG_H #include #endif /*HAVE_CONFIG_H*/ @@ -63,13 +69,12 @@ #include #include -#include #include "pmosaicing.h" #ifdef DEBUG static void -im__print_mdebug( TIE_POINTS *points ) +vips__print_mdebug( TiePoints *points ) { int i; double adx = 0.0; @@ -93,26 +98,27 @@ im__print_mdebug( TIE_POINTS *points ) #endif /*DEBUG*/ int -im__find_lroverlap( IMAGE *ref_in, IMAGE *sec_in, IMAGE *out, +vips__find_lroverlap( VipsImage *ref_in, VipsImage *sec_in, VipsImage *out, int bandno_in, int xref, int yref, int xsec, int ysec, int halfcorrelation, int halfarea, int *dx0, int *dy0, double *scale1, double *angle1, double *dx1, double *dy1 ) { - Rect left, right, overlap; - IMAGE *ref, *sec; - IMAGE *t[6]; - TIE_POINTS points, *p_points; - TIE_POINTS newpoints, *p_newpoints; - int dx, dy; + VipsImage **t = (VipsImage **) + vips_object_local_array( VIPS_OBJECT( out ), 6 ); + + VipsRect left, right, overlap; + TiePoints points, *p_points; + TiePoints newpoints, *p_newpoints; int i; + int dx, dy; /* Test cor and area. */ if( halfcorrelation < 0 || halfarea < 0 || halfarea < halfcorrelation ) { - im_error( "im_lrmosaic", "%s", _( "bad area parameters" ) ); + vips_error( "vips_lrmosaic", "%s", _( "bad area parameters" ) ); return( -1 ); } @@ -129,54 +135,49 @@ im__find_lroverlap( IMAGE *ref_in, IMAGE *sec_in, IMAGE *out, /* Find overlap. */ - im_rect_intersectrect( &left, &right, &overlap ); + vips_rect_intersectrect( &left, &right, &overlap ); if( overlap.width < 2 * halfarea + 1 || overlap.height < 2 * halfarea + 1 ) { - im_error( "im_lrmosaic", + vips_error( "vips_lrmosaic", "%s", _( "overlap too small for search" ) ); return( -1 ); } /* Extract overlaps as 8-bit, 1 band. */ - if( !(ref = im_open_local( out, "temp_one", "t" )) || - !(sec = im_open_local( out, "temp_two", "t" )) || - im_open_local_array( out, t, 6, "im_lrmosaic", "p" ) || - im_extract_area( ref_in, t[0], + if( vips_extract_area( ref_in, &t[0], overlap.left, overlap.top, - overlap.width, overlap.height ) || - im_extract_area( sec_in, t[1], + overlap.width, overlap.height, NULL ) || + vips_extract_area( sec_in, &t[1], overlap.left - right.left, overlap.top - right.top, - overlap.width, overlap.height ) ) + overlap.width, overlap.height, NULL ) ) return( -1 ); - if( ref_in->Coding == IM_CODING_LABQ ) { - if( im_LabQ2Lab( t[0], t[2] ) || - im_LabQ2Lab( t[1], t[3] ) || - im_Lab2disp( t[2], t[4], im_col_displays( 1 ) ) || - im_Lab2disp( t[3], t[5], im_col_displays( 1 ) ) || - im_extract_band( t[4], ref, 1 ) || - im_extract_band( t[5], sec, 1 ) ) + if( ref_in->Coding == VIPS_CODING_LABQ ) { + if( vips_LabQ2sRGB( t[0], &t[2], NULL ) || + vips_LabQ2sRGB( t[1], &t[3], NULL ) || + vips_extract_band( t[2], &t[4], 1, NULL ) || + vips_extract_band( t[3], &t[5], 1, NULL ) ) return( -1 ); } - else if( ref_in->Coding == IM_CODING_NONE ) { - if( im_extract_band( t[0], t[2], bandno_in ) || - im_extract_band( t[1], t[3], bandno_in ) || - im_scale( t[2], ref ) || - im_scale( t[3], sec ) ) + else if( ref_in->Coding == VIPS_CODING_NONE ) { + if( vips_extract_band( t[0], &t[2], bandno_in, NULL ) || + vips_extract_band( t[1], &t[3], bandno_in, NULL ) || + vips_scale( t[2], &t[4], NULL ) || + vips_scale( t[3], &t[5], NULL ) ) return( -1 ); } else { - im_error( "im_lrmosaic", "%s", _( "unknown Coding type" ) ); + vips_error( "vips_lrmosaic", "%s", _( "unknown Coding type" ) ); return( -1 ); } - /* Initialise and fill TIE_POINTS + /* Initialise and fill TiePoints */ p_points = &points; p_newpoints = &newpoints; p_points->reference = ref_in->filename; p_points->secondary = sec_in->filename; - p_points->nopoints = IM_MAXPOINTS; + p_points->nopoints = VIPS_MAXPOINTS; p_points->deltax = 0; p_points->deltay = 0; p_points->halfcorsize = halfcorrelation; @@ -184,7 +185,7 @@ im__find_lroverlap( IMAGE *ref_in, IMAGE *sec_in, IMAGE *out, /* Initialise the structure */ - for( i = 0; i < IM_MAXPOINTS; i++ ) { + for( i = 0; i < VIPS_MAXPOINTS; i++ ) { p_points->x_reference[i] = 0; p_points->y_reference[i] = 0; p_points->x_secondary[i] = 0; @@ -199,29 +200,29 @@ im__find_lroverlap( IMAGE *ref_in, IMAGE *sec_in, IMAGE *out, /* Search ref for possible tie-points. Sets: p_points->contrast, * p_points->x,y_reference. */ - if( im__lrcalcon( ref, p_points ) ) + if( vips__lrcalcon( t[4], p_points ) ) return( -1 ); /* For each candidate point, correlate against corresponding part of * sec. Sets x,y_secondary and fills correlation and dx, dy. */ - if( im__chkpair( ref, sec, p_points ) ) + if( vips__chkpair( t[4], t[5], p_points ) ) return( -1 ); - /* First call to im_clinear(). + /* First call to vips_clinear(). */ - if( im__initialize( p_points ) ) + if( vips__initialize( p_points ) ) return( -1 ); /* Improve the selection of tiepoints until all abs(deviations) are * < 1.0 by deleting all wrong points. */ - if( im__improve( p_points, p_newpoints ) ) + if( vips__improve( p_points, p_newpoints ) ) return( -1 ); /* Average remaining offsets. */ - if( im__avgdxdy( p_newpoints, &dx, &dy ) ) + if( vips__avgdxdy( p_newpoints, &dx, &dy ) ) return( -1 ); /* Offset with overlap position. @@ -240,7 +241,7 @@ im__find_lroverlap( IMAGE *ref_in, IMAGE *sec_in, IMAGE *out, } int -im_lrmosaic( IMAGE *ref, IMAGE *sec, IMAGE *out, +vips_lrmosaic( VipsImage *ref, VipsImage *sec, VipsImage *out, int bandno, int xref, int yref, int xsec, int ysec, int hwindowsize, int hsearchsize, @@ -249,27 +250,26 @@ im_lrmosaic( IMAGE *ref, IMAGE *sec, IMAGE *out, { int dx0, dy0; double scale1, angle1, dx1, dy1; - IMAGE *dummy; + VipsImage *dummy; /* Correct overlap. dummy is just a placeholder used to ensure that * memory used by the analysis phase is freed as soon as possible. */ - if( !(dummy = im_open( "placeholder:1", "p" )) ) - return( -1 ); - if( im__find_lroverlap( ref, sec, dummy, + dummy = vips_image_new(); + if( vips__find_lroverlap( ref, sec, dummy, bandno, xref, yref, xsec, ysec, hwindowsize, hsearchsize, &dx0, &dy0, &scale1, &angle1, &dx1, &dy1 ) ) { - im_close( dummy ); + g_object_unref( dummy ); return( -1 ); } - im_close( dummy ); + g_object_unref( dummy ); /* Merge left right. */ - if( im_lrmerge( ref, sec, out, dx0, dy0, mwidth ) ) + if( vips_lrmerge( ref, sec, out, dx0, dy0, mwidth ) ) return( -1 ); return( 0 ); diff --git a/libvips/mosaicing/im_remosaic.c b/libvips/mosaicing/im_remosaic.c index 2c6a42a4..e74d9ed5 100644 --- a/libvips/mosaicing/im_remosaic.c +++ b/libvips/mosaicing/im_remosaic.c @@ -6,6 +6,8 @@ * - detect size change * 10/4/06 * - spot file-not-found + * 18/6/20 kleisauke + * - convert to vips8 */ /* @@ -51,7 +53,6 @@ #include #include -#include #include #include "pmosaicing.h" @@ -74,18 +75,18 @@ typedef VipsOperationClass VipsRemosaicClass; G_DEFINE_TYPE( VipsRemosaic, vips_remosaic, VIPS_TYPE_OPERATION ); -static IMAGE * +static VipsImage * remosaic_fn( JoinNode *node, VipsRemosaic *remosaic ) { SymbolTable *st = node->st; - IMAGE *im = node->im; + VipsImage *im = node->im; - IMAGE *out; + VipsImage *out; char filename[FILENAME_MAX]; char *p; if( !im ) { - im_error( "im_remosaic", _( "file \"%s\" not found" ), + vips_error( "vips_remosaic", _( "file \"%s\" not found" ), node->name ); return( NULL ); } @@ -93,26 +94,26 @@ remosaic_fn( JoinNode *node, VipsRemosaic *remosaic ) /* Remove substring remosaic->old_str from in->filename, replace with * remosaic->new_str. */ - im_strncpy( filename, im->filename, FILENAME_MAX ); - if( (p = im_strrstr( filename, remosaic->old_str )) ) { + vips_strncpy( filename, im->filename, FILENAME_MAX ); + if( (p = vips_strrstr( filename, remosaic->old_str )) ) { int offset = p - &filename[0]; - im_strncpy( p, remosaic->new_str, FILENAME_MAX - offset ); - im_strncpy( p + remosaic->new_len, + vips_strncpy( p, remosaic->new_str, FILENAME_MAX - offset ); + vips_strncpy( p + remosaic->new_len, im->filename + offset + remosaic->old_len, FILENAME_MAX - offset - remosaic->new_len ); } #ifdef DEBUG - printf( "im_remosaic: filename \"%s\" -> \"%s\"\n", + printf( "vips_remosaic: filename \"%s\" -> \"%s\"\n", im->filename, filename ); #endif /*DEBUG*/ - if( !(out = im__global_open_image( st, filename )) ) + if( !(out = vips__global_open_image( st, filename )) ) return( NULL ); if( out->Xsize != im->Xsize || out->Ysize != im->Ysize ) { - im_error( "im_remosaic", + vips_error( "vips_remosaic", _( "substitute image \"%s\" is not " "the same size as \"%s\"" ), filename, im->filename ); @@ -135,13 +136,13 @@ vips_remosaic_build( VipsObject *object ) build( object ) ) return( -1 ); - if( !(st = im__build_symtab( remosaic->out, SYM_TAB_SIZE )) || - im__parse_desc( st, remosaic->in ) ) + if( !(st = vips__build_symtab( remosaic->out, SYM_TAB_SIZE )) || + vips__parse_desc( st, remosaic->in ) ) return( -1 ); remosaic->old_len = strlen( remosaic->old_str ); remosaic->new_len = strlen( remosaic->new_str ); - if( im__build_mosaic( st, remosaic->out, + if( vips__build_mosaic( st, remosaic->out, (transform_fn) remosaic_fn, remosaic ) ) return( -1 ); @@ -158,7 +159,7 @@ vips_remosaic_class_init( VipsRemosaicClass *class ) gobject_class->get_property = vips_object_get_property; object_class->nickname = "remosaic"; - object_class->description = _( "global balance an image mosaic" ); + object_class->description = _( "rebuild an mosaiced image" ); object_class->build = vips_remosaic_build; VIPS_ARG_IMAGE( class, "in", 1, @@ -206,7 +207,7 @@ vips_remosaic_init( VipsRemosaic *remosaic ) * mosaiced image @in and rebuilds it, substituting images. * * Unlike vips_globalbalance(), images are substituted based on their file‐ - * names. The rightmost occurence of the string @old_str is swapped + * names. The rightmost occurrence of the string @old_str is swapped * for @new_str, that file is opened, and that image substituted for * the old image. * diff --git a/libvips/mosaicing/im_tbcalcon.c b/libvips/mosaicing/im_tbcalcon.c index 6b68e9cd..a4910337 100644 --- a/libvips/mosaicing/im_tbcalcon.c +++ b/libvips/mosaicing/im_tbcalcon.c @@ -14,13 +14,13 @@ * @(#) Input image should are either memory mapped or in a buffer. * @(#) To make the calculation faster set FACTOR to 1, 2 or 3 * @(#) Calculations are based on bandno only. - * @(#) The function uses functions im_calculate_contrast() - * @(#) which is in im_lrcalcon() + * @(#) The function uses functions vips__find_best_contrast() + * @(#) which is in vips_lrcalcon() * @(#) - * @(#) int im_tbcalcon( ref, sec, bandno, points ) - * @(#) IMAGE *ref, *sec; + * @(#) int vips_tbcalcon( ref, sec, bandno, points ) + * @(#) VipsImage *ref, *sec; * @(#) int bandno; - * @(#) TIE_POINTS *points; see mosaic.h + * @(#) TiePoints *points; see mosaic.h * @(#) * @(#) Returns 0 on sucess and -1 on error. * @@ -35,6 +35,8 @@ * 12/7/95 JC * - reworked * - what a lot of horrible old code there was too + * 18/6/20 kleisauke + * - convert to vips8 */ /* @@ -73,13 +75,12 @@ #include #include -#include #include #include "pmosaicing.h" int -im__tbcalcon( IMAGE *ref, TIE_POINTS *points ) +vips__tbcalcon( VipsImage *ref, TiePoints *points ) { /* Geometry: border we must leave around each area. */ @@ -94,14 +95,14 @@ im__tbcalcon( IMAGE *ref, TIE_POINTS *points ) const int len = points->nopoints / AREAS; int i; - Rect area; + VipsRect area; /* Make sure we can read image. */ - if( im_incheck( ref ) ) + if( vips_image_wio_input( ref ) ) return( -1 ); - if( ref->Bands != 1 || ref->BandFmt != IM_BANDFMT_UCHAR ) { - im_error( "im__tbcalcon", "%s", _( "help!" ) ); + if( ref->Bands != 1 || ref->BandFmt != VIPS_FORMAT_UCHAR ) { + vips_error( "vips__tbcalcon", "%s", _( "help!" ) ); return( -1 ); } @@ -111,22 +112,22 @@ im__tbcalcon( IMAGE *ref, TIE_POINTS *points ) area.height = ref->Ysize; area.left = 0; area.top = 0; - im_rect_marginadjust( &area, -border ); + vips_rect_marginadjust( &area, -border ); area.width--; area.height--; if( area.width < 0 || area.height < 0 ) { - im_error( "im__tbcalcon", "%s", _( "overlap too small" ) ); + vips_error( "vips__tbcalcon", "%s", _( "overlap too small" ) ); return( -1 ); } /* Loop over areas, finding points. */ for( i = 0; area.left < ref->Xsize; area.left += awidth, i++ ) - if( im__find_best_contrast( ref, + if( vips__find_best_contrast( ref, area.left, area.top, area.width, area.height, - points->x_reference + i*len, - points->y_reference + i*len, - points->contrast + i*len, + points->x_reference + i * len, + points->y_reference + i * len, + points->contrast + i * len, len, points->halfcorsize ) ) return( -1 ); diff --git a/libvips/mosaicing/im_tbmerge.c b/libvips/mosaicing/im_tbmerge.c index f3e27b5d..8600f25e 100644 --- a/libvips/mosaicing/im_tbmerge.c +++ b/libvips/mosaicing/im_tbmerge.c @@ -4,8 +4,8 @@ * Usage: * * int - * im_tbmerge( ref, sec, out, dx, dy ) - * IMAGE *ref, *sec, *out; + * vips_tbmerge( ref, sec, out, dx, dy ) + * VipsImage *ref, *sec, *out; * int dx, dy; * * Returns 0 on success and -1 on error @@ -71,6 +71,8 @@ * 24/1/11 * - gtk-doc * - match formats and bands automatically + * 18/6/20 kleisauke + * - convert to vips8 */ /* @@ -110,7 +112,6 @@ #include #include -#include #include #include #include @@ -120,11 +121,11 @@ /* Return the position of the first non-zero pel from the top. */ static int -find_top( REGION *ir, int *pos, int x, int y, int h ) +find_top( VipsRegion *ir, int *pos, int x, int y, int h ) { - VipsPel *pr = IM_REGION_ADDR( ir, x, y ); - IMAGE *im = ir->im; - int ls = IM_REGION_LSKIP( ir ) / IM_IMAGE_SIZEOF_ELEMENT( im ); + VipsPel *pr = VIPS_REGION_ADDR( ir, x, y ); + VipsImage *im = ir->im; + int ls = VIPS_REGION_LSKIP( ir ) / VIPS_IMAGE_SIZEOF_ELEMENT( im ); int b = im->Bands; int i, j; @@ -150,19 +151,19 @@ find_top( REGION *ir, int *pos, int x, int y, int h ) } switch( im->BandFmt ) { - case IM_BANDFMT_UCHAR: tsearch( unsigned char ); break; - case IM_BANDFMT_CHAR: tsearch( signed char ); break; - case IM_BANDFMT_USHORT: tsearch( unsigned short ); break; - case IM_BANDFMT_SHORT: tsearch( signed short ); break; - case IM_BANDFMT_UINT: tsearch( unsigned int ); break; - case IM_BANDFMT_INT: tsearch( signed int ); break; - case IM_BANDFMT_FLOAT: tsearch( float ); break; - case IM_BANDFMT_DOUBLE: tsearch( double ); break; - case IM_BANDFMT_COMPLEX:tsearch( float ); break; - case IM_BANDFMT_DPCOMPLEX:tsearch( double ); break; + case VIPS_FORMAT_UCHAR: tsearch( unsigned char ); break; + case VIPS_FORMAT_CHAR: tsearch( signed char ); break; + case VIPS_FORMAT_USHORT: tsearch( unsigned short ); break; + case VIPS_FORMAT_SHORT: tsearch( signed short ); break; + case VIPS_FORMAT_UINT: tsearch( unsigned int ); break; + case VIPS_FORMAT_INT: tsearch( signed int ); break; + case VIPS_FORMAT_FLOAT: tsearch( float ); break; + case VIPS_FORMAT_DOUBLE: tsearch( double ); break; + case VIPS_FORMAT_COMPLEX: tsearch( float ); break; + case VIPS_FORMAT_DPCOMPLEX: tsearch( double ); break; default: - im_error( "im_tbmerge", "%s", _( "internal error" ) ); + vips_error( "vips_tbmerge", "%s", _( "internal error" ) ); return( -1 ); } @@ -174,11 +175,11 @@ find_top( REGION *ir, int *pos, int x, int y, int h ) /* Return the position of the first non-zero pel from the bottom. */ static int -find_bot( REGION *ir, int *pos, int x, int y, int h ) +find_bot( VipsRegion *ir, int *pos, int x, int y, int h ) { - VipsPel *pr = IM_REGION_ADDR( ir, x, y ); - IMAGE *im = ir->im; - int ls = IM_REGION_LSKIP( ir ) / IM_IMAGE_SIZEOF_ELEMENT( ir->im ); + VipsPel *pr = VIPS_REGION_ADDR( ir, x, y ); + VipsImage *im = ir->im; + int ls = VIPS_REGION_LSKIP( ir ) / VIPS_IMAGE_SIZEOF_ELEMENT( ir->im ); int b = im->Bands; int i, j; @@ -204,19 +205,19 @@ find_bot( REGION *ir, int *pos, int x, int y, int h ) } switch( im->BandFmt ) { - case IM_BANDFMT_UCHAR: rsearch( unsigned char ); break; - case IM_BANDFMT_CHAR: rsearch( signed char ); break; - case IM_BANDFMT_USHORT: rsearch( unsigned short ); break; - case IM_BANDFMT_SHORT: rsearch( signed short ); break; - case IM_BANDFMT_UINT: rsearch( unsigned int ); break; - case IM_BANDFMT_INT: rsearch( signed int ); break; - case IM_BANDFMT_FLOAT: rsearch( float ); break; - case IM_BANDFMT_DOUBLE: rsearch( double ); break; - case IM_BANDFMT_COMPLEX:rsearch( float ); break; - case IM_BANDFMT_DPCOMPLEX:rsearch( double ); break; + case VIPS_FORMAT_UCHAR: rsearch( unsigned char ); break; + case VIPS_FORMAT_CHAR: rsearch( signed char ); break; + case VIPS_FORMAT_USHORT: rsearch( unsigned short ); break; + case VIPS_FORMAT_SHORT: rsearch( signed short ); break; + case VIPS_FORMAT_UINT: rsearch( unsigned int ); break; + case VIPS_FORMAT_INT: rsearch( signed int ); break; + case VIPS_FORMAT_FLOAT: rsearch( float ); break; + case VIPS_FORMAT_DOUBLE: rsearch( double ); break; + case VIPS_FORMAT_COMPLEX: rsearch( float ); break; + case VIPS_FORMAT_DPCOMPLEX: rsearch( double ); break; default: - im_error( "im_tbmerge", "%s", _( "internal error" ) ); + vips_error( "vips_tbmerge", "%s", _( "internal error" ) ); return( -1 ); } @@ -228,11 +229,11 @@ find_bot( REGION *ir, int *pos, int x, int y, int h ) /* Make first/last for oreg. */ static int -make_firstlast( MergeInfo *inf, Overlapping *ovlap, Rect *oreg ) +make_firstlast( MergeInfo *inf, Overlapping *ovlap, VipsRect *oreg ) { - REGION *rir = inf->rir; - REGION *sir = inf->sir; - Rect rr, sr; + VipsRegion *rir = inf->rir; + VipsRegion *sir = inf->sir; + VipsRect rr, sr; int x; int missing; @@ -245,7 +246,7 @@ make_firstlast( MergeInfo *inf, Overlapping *ovlap, Rect *oreg ) /* Do we already have first/last for this area? Bail out if we do. */ missing = 0; - for( x = oreg->left; x < IM_RECT_RIGHT( oreg ); x++ ) { + for( x = oreg->left; x < VIPS_RECT_RIGHT( oreg ); x++ ) { const int j = x - ovlap->overlap.left; const int first = ovlap->first[j]; @@ -282,7 +283,8 @@ make_firstlast( MergeInfo *inf, Overlapping *ovlap, Rect *oreg ) /* Make pixels. */ - if( im_prepare( rir, &rr ) || im_prepare( sir, &sr ) ) { + if( vips_region_prepare( rir, &rr ) || + vips_region_prepare( sir, &sr ) ) { g_mutex_unlock( ovlap->fl_lock ); return( -1 ); } @@ -387,12 +389,12 @@ make_firstlast( MergeInfo *inf, Overlapping *ovlap, Rect *oreg ) const int bheight = last[x] - first[x]; \ const int inx = ((y - first[x]) << \ BLEND_SHIFT) / bheight; \ - int c1 = im__icoef1[inx]; \ - int c2 = im__icoef2[inx]; \ + int c1 = vips__icoef1[inx]; \ + int c2 = vips__icoef2[inx]; \ \ for( b = 0; b < cb; b++, i++ ) \ - tq[i] = c1*tr[i] / BLEND_SCALE + \ - c2*ts[i] / BLEND_SCALE; \ + tq[i] = c1 * tr[i] / BLEND_SCALE + \ + c2 * ts[i] / BLEND_SCALE; \ } \ else if( !ref_zero ) \ for( b = 0; b < cb; b++, i++ ) \ @@ -447,8 +449,8 @@ make_firstlast( MergeInfo *inf, Overlapping *ovlap, Rect *oreg ) const int bheight = last[x] - first[x]; \ const int inx = ((y - first[x]) << \ BLEND_SHIFT) / bheight; \ - double c1 = im__coef1[inx]; \ - double c2 = im__coef2[inx]; \ + double c1 = vips__coef1[inx]; \ + double c2 = vips__coef2[inx]; \ \ for( b = 0; b < cb; b++, i++ ) \ tq[i] = c1 * tr[i] + c2 * ts[i]; \ @@ -466,13 +468,13 @@ make_firstlast( MergeInfo *inf, Overlapping *ovlap, Rect *oreg ) /* Top-bottom blend function for non-labpack images. */ static int -tb_blend( REGION *or, MergeInfo *inf, Overlapping *ovlap, Rect *oreg ) +tb_blend( VipsRegion *or, MergeInfo *inf, Overlapping *ovlap, VipsRect *oreg ) { - REGION *rir = inf->rir; - REGION *sir = inf->sir; - IMAGE *im = or->im; + VipsRegion *rir = inf->rir; + VipsRegion *sir = inf->sir; + VipsImage *im = or->im; - Rect prr, psr; + VipsRect prr, psr; int y, yr, ys; /* Make sure we have a complete first/last set for this area. @@ -494,47 +496,46 @@ tb_blend( REGION *or, MergeInfo *inf, Overlapping *ovlap, Rect *oreg ) /* Make pixels. */ - if( im_prepare( rir, &prr ) ) - return( -1 ); - if( im_prepare( sir, &psr ) ) + if( vips_region_prepare( rir, &prr ) || + vips_region_prepare( sir, &psr ) ) return( -1 ); /* Loop down overlap area. */ for( y = oreg->top, yr = prr.top, ys = psr.top; - y < IM_RECT_BOTTOM( oreg ); y++, yr++, ys++ ) { - VipsPel *pr = IM_REGION_ADDR( rir, prr.left, yr ); - VipsPel *ps = IM_REGION_ADDR( sir, psr.left, ys ); - VipsPel *q = IM_REGION_ADDR( or, oreg->left, y ); + y < VIPS_RECT_BOTTOM( oreg ); y++, yr++, ys++ ) { + VipsPel *pr = VIPS_REGION_ADDR( rir, prr.left, yr ); + VipsPel *ps = VIPS_REGION_ADDR( sir, psr.left, ys ); + VipsPel *q = VIPS_REGION_ADDR( or, oreg->left, y ); const int j = oreg->left - ovlap->overlap.left; const int *first = ovlap->first + j; const int *last = ovlap->last + j; switch( im->BandFmt ) { - case IM_BANDFMT_UCHAR: + case VIPS_FORMAT_UCHAR: iblend( unsigned char, im->Bands, pr, ps, q ); break; - case IM_BANDFMT_CHAR: + case VIPS_FORMAT_CHAR: iblend( signed char, im->Bands, pr, ps, q ); break; - case IM_BANDFMT_USHORT: + case VIPS_FORMAT_USHORT: iblend( unsigned short, im->Bands, pr, ps, q ); break; - case IM_BANDFMT_SHORT: + case VIPS_FORMAT_SHORT: iblend( signed short, im->Bands, pr, ps, q ); break; - case IM_BANDFMT_UINT: + case VIPS_FORMAT_UINT: iblend( unsigned int, im->Bands, pr, ps, q ); break; - case IM_BANDFMT_INT: + case VIPS_FORMAT_INT: iblend( signed int, im->Bands, pr, ps, q ); break; - case IM_BANDFMT_FLOAT: + case VIPS_FORMAT_FLOAT: fblend( float, im->Bands, pr, ps, q ); break; - case IM_BANDFMT_DOUBLE: + case VIPS_FORMAT_DOUBLE: fblend( double, im->Bands, pr, ps, q ); break; - case IM_BANDFMT_COMPLEX: - fblend( float, im->Bands*2, pr, ps, q ); break; - case IM_BANDFMT_DPCOMPLEX: - fblend( double, im->Bands*2, pr, ps, q ); break; + case VIPS_FORMAT_COMPLEX: + fblend( float, im->Bands * 2, pr, ps, q ); break; + case VIPS_FORMAT_DPCOMPLEX: + fblend( double, im->Bands * 2, pr, ps, q ); break; default: - im_error( "im_tbmerge", "%s", _( "internal error" ) ); + vips_error( "vips_tbmerge", "%s", _( "internal error" ) ); return( -1 ); } } @@ -542,14 +543,14 @@ tb_blend( REGION *or, MergeInfo *inf, Overlapping *ovlap, Rect *oreg ) return( 0 ); } -/* Top-bottom blend function for IM_CODING_LABQ images. +/* Top-bottom blend function for VIPS_CODING_LABQ images. */ static int -tb_blend_labpack( REGION *or, MergeInfo *inf, Overlapping *ovlap, Rect *oreg ) +tb_blend_labpack( VipsRegion *or, MergeInfo *inf, Overlapping *ovlap, VipsRect *oreg ) { - REGION *rir = inf->rir; - REGION *sir = inf->sir; - Rect prr, psr; + VipsRegion *rir = inf->rir; + VipsRegion *sir = inf->sir; + VipsRect prr, psr; int y, yr, ys; /* Make sure we have a complete first/last set for this area. This @@ -572,18 +573,17 @@ tb_blend_labpack( REGION *or, MergeInfo *inf, Overlapping *ovlap, Rect *oreg ) /* Make pixels. */ - if( im_prepare( rir, &prr ) ) - return( -1 ); - if( im_prepare( sir, &psr ) ) + if( vips_region_prepare( rir, &prr ) || + vips_region_prepare( sir, &psr ) ) return( -1 ); /* Loop down overlap area. */ for( y = oreg->top, yr = prr.top, ys = psr.top; - y < IM_RECT_BOTTOM( oreg ); y++, yr++, ys++ ) { - VipsPel *pr = IM_REGION_ADDR( rir, prr.left, yr ); - VipsPel *ps = IM_REGION_ADDR( sir, psr.left, ys ); - VipsPel *q = IM_REGION_ADDR( or, oreg->left, y ); + y < VIPS_RECT_BOTTOM( oreg ); y++, yr++, ys++ ) { + VipsPel *pr = VIPS_REGION_ADDR( rir, prr.left, yr ); + VipsPel *ps = VIPS_REGION_ADDR( sir, psr.left, ys ); + VipsPel *q = VIPS_REGION_ADDR( or, oreg->left, y ); const int j = oreg->left - ovlap->overlap.left; const int *first = ovlap->first + j; @@ -613,27 +613,27 @@ tb_blend_labpack( REGION *or, MergeInfo *inf, Overlapping *ovlap, Rect *oreg ) /* Build per-call state. */ static Overlapping * -build_tbstate( IMAGE *ref, IMAGE *sec, IMAGE *out, int dx, int dy, int mwidth ) +build_tbstate( VipsImage *ref, VipsImage *sec, VipsImage *out, int dx, int dy, int mwidth ) { Overlapping *ovlap; - if( !(ovlap = im__build_mergestate( "im_tbmerge", + if( !(ovlap = vips__build_mergestate( "vips_tbmerge", ref, sec, out, dx, dy, mwidth )) ) return( NULL ); /* Select blender. */ switch( ovlap->ref->Coding ) { - case IM_CODING_LABQ: + case VIPS_CODING_LABQ: ovlap->blend = tb_blend_labpack; break; - case IM_CODING_NONE: + case VIPS_CODING_NONE: ovlap->blend = tb_blend; break; default: - im_error( "im_tbmerge", "%s", _( "unknown coding type" ) ); + vips_error( "vips_tbmerge", "%s", _( "unknown coding type" ) ); return( NULL ); } @@ -649,9 +649,9 @@ build_tbstate( IMAGE *ref, IMAGE *sec, IMAGE *out, int dx, int dy, int mwidth ) * than bottom edge of sec image, or top edge of ref > top edge of * sec. */ - if( IM_RECT_BOTTOM( &ovlap->rarea ) > IM_RECT_BOTTOM( &ovlap->sarea ) || + if( VIPS_RECT_BOTTOM( &ovlap->rarea ) > VIPS_RECT_BOTTOM( &ovlap->sarea ) || ovlap->rarea.top > ovlap->sarea.top ) { - im_error( "im_tbmerge", "%s", _( "too much overlap" ) ); + vips_error( "vips_tbmerge", "%s", _( "too much overlap" ) ); return( NULL ); } @@ -663,14 +663,16 @@ build_tbstate( IMAGE *ref, IMAGE *sec, IMAGE *out, int dx, int dy, int mwidth ) } int -im__tbmerge( IMAGE *ref, IMAGE *sec, IMAGE *out, int dx, int dy, int mwidth ) +vips__tbmerge( VipsImage *ref, VipsImage *sec, VipsImage *out, int dx, int dy, int mwidth ) { Overlapping *ovlap; if( dy > 0 || dy < 1 - ref->Ysize ) { /* No overlap, use insert instead. */ - if( im_insert( ref, sec, out, -dx, -dy ) ) + if( vips_insert( ref, sec, &out, -dx, -dy, + "expand", TRUE, + NULL ) ) return( -1 ); out->Xoffset = -dx; out->Yoffset = -dy; @@ -681,34 +683,33 @@ im__tbmerge( IMAGE *ref, IMAGE *sec, IMAGE *out, int dx, int dy, int mwidth ) if( !(ovlap = build_tbstate( ref, sec, out, dx, dy, mwidth )) ) return( -1 ); - if( im_cp_descv( out, ref, sec, NULL ) ) + if( vips_image_pipelinev( out, + VIPS_DEMAND_STYLE_THINSTRIP, ovlap->ref, ovlap->sec, NULL ) ) return( -1 ); + out->Xsize = ovlap->oarea.width; out->Ysize = ovlap->oarea.height; out->Xoffset = -dx; out->Yoffset = -dy; - if( im_demand_hint( out, IM_THINSTRIP, ref, sec, NULL ) ) - return( -1 ); - - if( im_generate( out, - im__start_merge, im__merge_gen, im__stop_merge, ovlap, NULL ) ) + if( vips_image_generate( out, + vips__start_merge, vips__merge_gen, vips__stop_merge, ovlap, NULL ) ) return( -1 ); return ( 0 ); } int -im_tbmerge( IMAGE *ref, IMAGE *sec, IMAGE *out, int dx, int dy, int mwidth ) +vips_tbmerge( VipsImage *ref, VipsImage *sec, VipsImage *out, int dx, int dy, int mwidth ) { - if( im__tbmerge( ref, sec, out, dx, dy, mwidth ) ) + if( vips__tbmerge( ref, sec, out, dx, dy, mwidth ) ) return( -1 ); - im__add_mosaic_name( out ); - if( im_histlin( out, "#TBJOIN <%s> <%s> <%s> <%d> <%d> <%d>", - im__get_mosaic_name( ref ), - im__get_mosaic_name( sec ), - im__get_mosaic_name( out ), + vips__add_mosaic_name( out ); + if( vips_image_history_printf( out, "#TBJOIN <%s> <%s> <%s> <%d> <%d> <%d>", + vips__get_mosaic_name( ref ), + vips__get_mosaic_name( sec ), + vips__get_mosaic_name( out ), -dx, -dy, mwidth ) ) return( -1 ); diff --git a/libvips/mosaicing/im_tbmosaic.c b/libvips/mosaicing/im_tbmosaic.c index 0c70b0be..61d8364b 100644 --- a/libvips/mosaicing/im_tbmosaic.c +++ b/libvips/mosaicing/im_tbmosaic.c @@ -25,6 +25,8 @@ * - remove balance stuff * - any mix of types and bands * - cleanups + * 18/6/20 kleisauke + * - convert to vips8 */ /* @@ -63,24 +65,24 @@ #include #include -#include #include #include "pmosaicing.h" int -im__find_tboverlap( IMAGE *ref_in, IMAGE *sec_in, IMAGE *out, +vips__find_tboverlap( VipsImage *ref_in, VipsImage *sec_in, VipsImage *out, int bandno_in, int xref, int yref, int xsec, int ysec, int halfcorrelation, int halfarea, int *dx0, int *dy0, double *scale1, double *angle1, double *dx1, double *dy1 ) { - Rect top, bottom, overlap; - IMAGE *ref, *sec; - IMAGE *t[6]; - TIE_POINTS points, *p_points; /* defined in mosaic.h */ - TIE_POINTS newpoints, *p_newpoints; + VipsImage **t = (VipsImage **) + vips_object_local_array( VIPS_OBJECT( out ), 6 ); + + VipsRect top, bottom, overlap; + TiePoints points, *p_points; /* defined in mosaic.h */ + TiePoints newpoints, *p_newpoints; int i; int dx, dy; @@ -88,7 +90,7 @@ im__find_tboverlap( IMAGE *ref_in, IMAGE *sec_in, IMAGE *out, */ if( halfcorrelation < 0 || halfarea < 0 || halfarea < halfcorrelation ) { - im_error( "im_tbmosaic", "%s", _( "bad area parameters" ) ); + vips_error( "vips_tbmosaic", "%s", _( "bad area parameters" ) ); return( -1 ); } @@ -105,54 +107,49 @@ im__find_tboverlap( IMAGE *ref_in, IMAGE *sec_in, IMAGE *out, /* Find overlap. */ - im_rect_intersectrect( &top, &bottom, &overlap ); + vips_rect_intersectrect( &top, &bottom, &overlap ); if( overlap.width < 2 * halfarea + 1 || overlap.height < 2 * halfarea + 1 ) { - im_error( "im_tbmosaic", "%s", + vips_error( "vips_tbmosaic", "%s", _( "overlap too small for search" ) ); return( -1 ); } /* Extract overlaps as 8-bit, 1 band. */ - if( !(ref = im_open_local( out, "temp_one", "t" )) || - !(sec = im_open_local( out, "temp_two", "t" )) || - im_open_local_array( out, t, 6, "im_tbmosaic", "p" ) || - im_extract_area( ref_in, t[0], + if( vips_extract_area( ref_in, &t[0], overlap.left, overlap.top, - overlap.width, overlap.height ) || - im_extract_area( sec_in, t[1], + overlap.width, overlap.height, NULL ) || + vips_extract_area( sec_in, &t[1], overlap.left - bottom.left, overlap.top - bottom.top, - overlap.width, overlap.height ) ) + overlap.width, overlap.height, NULL ) ) return( -1 ); - if( ref_in->Coding == IM_CODING_LABQ ) { - if( im_LabQ2Lab( t[0], t[2] ) || - im_LabQ2Lab( t[1], t[3] ) || - im_Lab2disp( t[2], t[4], im_col_displays( 1 ) ) || - im_Lab2disp( t[3], t[5], im_col_displays( 1 ) ) || - im_extract_band( t[4], ref, 1 ) || - im_extract_band( t[5], sec, 1 ) ) + if( ref_in->Coding == VIPS_CODING_LABQ ) { + if( vips_LabQ2sRGB( t[0], &t[2], NULL ) || + vips_LabQ2sRGB( t[1], &t[3], NULL ) || + vips_extract_band( t[2], &t[4], 1, NULL ) || + vips_extract_band( t[3], &t[5], 1, NULL ) ) return( -1 ); } - else if( ref_in->Coding == IM_CODING_NONE ) { - if( im_extract_band( t[0], t[2], bandno_in ) || - im_extract_band( t[1], t[3], bandno_in ) || - im_scale( t[2], ref ) || - im_scale( t[3], sec ) ) + else if( ref_in->Coding == VIPS_CODING_NONE ) { + if( vips_extract_band( t[0], &t[2], bandno_in, NULL ) || + vips_extract_band( t[1], &t[3], bandno_in, NULL ) || + vips_scale( t[2], &t[4], NULL ) || + vips_scale( t[3], &t[5], NULL ) ) return( -1 ); } else { - im_error( "im_tbmosaic", "%s", _( "unknown Coding type" ) ); + vips_error( "vips_tbmosaic", "%s", _( "unknown Coding type" ) ); return( -1 ); } - /* Initialise and fill TIE_POINTS + /* Initialise and fill TiePoints */ p_points = &points; p_newpoints = &newpoints; p_points->reference = ref_in->filename; p_points->secondary = sec_in->filename; - p_points->nopoints = IM_MAXPOINTS; + p_points->nopoints = VIPS_MAXPOINTS; p_points->deltax = 0; p_points->deltay = 0; p_points->halfcorsize = halfcorrelation; @@ -160,7 +157,7 @@ im__find_tboverlap( IMAGE *ref_in, IMAGE *sec_in, IMAGE *out, /* Initialise the structure */ - for( i = 0; i < IM_MAXPOINTS; i++ ) { + for( i = 0; i < VIPS_MAXPOINTS; i++ ) { p_points->x_reference[i] = 0; p_points->y_reference[i] = 0; p_points->x_secondary[i] = 0; @@ -175,29 +172,29 @@ im__find_tboverlap( IMAGE *ref_in, IMAGE *sec_in, IMAGE *out, /* Search ref for possible tie-points. Sets: p_points->contrast, * p_points->x,y_reference. */ - if( im__tbcalcon( ref, p_points ) ) + if( vips__tbcalcon( t[4], p_points ) ) return( -1 ); /* For each candidate point, correlate against corresponding part of * sec. Sets x,y_secondary and fills correlation and dx, dy. */ - if( im__chkpair( ref, sec, p_points ) ) + if( vips__chkpair( t[4], t[5], p_points ) ) return( -1 ); - /* First call to im_clinear(). + /* First call to vips_clinear(). */ - if( im__initialize( p_points ) ) + if( vips__initialize( p_points ) ) return( -1 ); /* Improve the selection of tiepoints until all abs(deviations) are * < 1.0 by deleting all wrong points. */ - if( im__improve( p_points, p_newpoints ) ) + if( vips__improve( p_points, p_newpoints ) ) return( -1 ); /* Average remaining offsets. */ - if( im__avgdxdy( p_newpoints, &dx, &dy ) ) + if( vips__avgdxdy( p_newpoints, &dx, &dy ) ) return( -1 ); /* Offset with overlap position. @@ -216,7 +213,7 @@ im__find_tboverlap( IMAGE *ref_in, IMAGE *sec_in, IMAGE *out, } int -im_tbmosaic( IMAGE *ref, IMAGE *sec, IMAGE *out, +vips_tbmosaic( VipsImage *ref, VipsImage *sec, VipsImage *out, int bandno, int xref, int yref, int xsec, int ysec, int hwindowsize, int hsearchsize, @@ -225,27 +222,26 @@ im_tbmosaic( IMAGE *ref, IMAGE *sec, IMAGE *out, { int dx0, dy0; double scale1, angle1, dx1, dy1; - IMAGE *dummy; + VipsImage *dummy; /* Correct overlap. dummy is just a placeholder used to ensure that * memory used by the analysis phase is freed as soon as possible. */ - if( !(dummy = im_open( "placeholder:1", "p" )) ) - return( -1 ); - if( im__find_tboverlap( ref, sec, dummy, + dummy = vips_image_new(); + if( vips__find_tboverlap( ref, sec, dummy, bandno, xref, yref, xsec, ysec, hwindowsize, hsearchsize, &dx0, &dy0, &scale1, &angle1, &dx1, &dy1 ) ) { - im_close( dummy ); + g_object_unref( dummy ); return( -1 ); } - im_close( dummy ); + g_object_unref( dummy ); /* Merge top-bottom. */ - if( im_tbmerge( ref, sec, out, dx0, dy0, mwidth ) ) + if( vips_tbmerge( ref, sec, out, dx0, dy0, mwidth ) ) return( -1 ); return( 0 ); diff --git a/libvips/mosaicing/match.c b/libvips/mosaicing/match.c index d9b6c305..dfad8866 100644 --- a/libvips/mosaicing/match.c +++ b/libvips/mosaicing/match.c @@ -37,7 +37,6 @@ #include #include -#include #include "pmosaicing.h" @@ -45,48 +44,39 @@ * image with. */ int -im__coeff( int xr1, int yr1, int xs1, int ys1, +vips__coeff( int xr1, int yr1, int xs1, int ys1, int xr2, int yr2, int xs2, int ys2, double *a, double *b, double *dx, double *dy ) -{ - DOUBLEMASK *in, *out; +{ + VipsImage **t = VIPS_ARRAY( NULL, 2, VipsImage * ); - if( !(in = im_create_dmask( "in", 4, 4 )) ) - return( -1 ); - - in->coeff[0] = (double)xs1; - in->coeff[1] = (double)-ys1; - in->coeff[2] = 1.0; - in->coeff[3] = 0.0; - in->coeff[4] = (double)ys1; - in->coeff[5] = (double)xs1; - in->coeff[6] = 0.0; - in->coeff[7] = 1.0; - in->coeff[8] = (double)xs2; - in->coeff[9] = (double)-ys2; - in->coeff[10] = 1.0; - in->coeff[11] = 0.0; - in->coeff[12] = (double)ys2; - in->coeff[13] = (double)xs2; - in->coeff[14] = 0.0; - in->coeff[15] = 1.0; - - if( !(out = im_matinv( in, "out" )) ) { - im_free_dmask( in ); + if( !(t[0] = vips_image_new_matrixv( 4, 4, + (double)xs1, (double)-ys1, 1.0, 0.0, + (double)ys1, (double)xs1, 0.0, 1.0, + (double)xs2, (double)-ys2, 1.0, 0.0, + (double)ys2, (double)xs2, 0.0, 1.0 )) ) { + g_free( t ); return( -1 ); } - *a = out->coeff[0]*xr1 + out->coeff[1]*yr1 + - out->coeff[2]*xr2 + out->coeff[3]*yr2; - *b = out->coeff[4]*xr1 + out->coeff[5]*yr1 + - out->coeff[6]*xr2 + out->coeff[7]*yr2; - *dx= out->coeff[8]*xr1 + out->coeff[9]*yr1 + - out->coeff[10]*xr2 + out->coeff[11]*yr2; - *dy= out->coeff[12]*xr1 + out->coeff[13]*yr1 + - out->coeff[14]*xr2 + out->coeff[15]*yr2; + if( vips_matrixinvert( t[0], &t[1], NULL ) ) { + g_object_unref( t[0] ); + g_free( t ); + return( -1 ); + } - im_free_dmask( in ); - im_free_dmask( out ); + *a = *VIPS_MATRIX( t[1], 0, 0 ) * xr1 + *VIPS_MATRIX( t[1], 0, 1 ) * yr1 + + *VIPS_MATRIX( t[1], 0, 2 ) * xr2 + *VIPS_MATRIX( t[1], 0, 3 ) * yr2; + *b = *VIPS_MATRIX( t[1], 1, 0 ) * xr1 + *VIPS_MATRIX( t[1], 1, 1 ) * yr1 + + *VIPS_MATRIX( t[1], 1, 2 ) * xr2 + *VIPS_MATRIX( t[1], 1, 3 ) * yr2; + *dx= *VIPS_MATRIX( t[1], 2, 0 ) * xr1 + *VIPS_MATRIX( t[1], 2, 1 ) * yr1 + + *VIPS_MATRIX( t[1], 2, 2 ) * xr2 + *VIPS_MATRIX( t[1], 2, 3 ) * yr2; + *dy= *VIPS_MATRIX( t[1], 3, 0 ) * xr1 + *VIPS_MATRIX( t[1], 3, 1 ) * yr1 + + *VIPS_MATRIX( t[1], 3, 2 ) * xr2 + *VIPS_MATRIX( t[1], 3, 3 ) * yr2; + + g_object_unref( t[0] ); + g_object_unref( t[1] ); + g_free( t ); return( 0 ); } @@ -137,7 +127,7 @@ vips_match_build( VipsObject *object ) int xs, ys; double cor; - if( im_correl( match->ref, match->sec, + if( vips_correl( match->ref, match->sec, match->xr1, match->yr1, match->xs1, match->ys1, match->hwindow, match->harea, &cor, &xs, &ys ) ) @@ -145,7 +135,7 @@ vips_match_build( VipsObject *object ) match->xs1 = xs; match->ys1 = ys; - if( im_correl( match->ref, match->sec, + if( vips_correl( match->ref, match->sec, match->xr2, match->yr2, match->xs2, match->ys2, match->hwindow, match->harea, &cor, &xs, &ys ) ) @@ -157,7 +147,7 @@ vips_match_build( VipsObject *object ) /* Solve to get scale + rot + disp to obtain match. */ - if( im__coeff( match->xr1, match->yr1, match->xs1, match->ys1, + if( vips__coeff( match->xr1, match->yr1, match->xs1, match->ys1, match->xr2, match->yr2, match->xs2, match->ys2, &a, &b, &dx, &dy ) ) return( -1 ); diff --git a/libvips/mosaicing/matrixinvert.c b/libvips/mosaicing/matrixinvert.c new file mode 100644 index 00000000..04e2d37b --- /dev/null +++ b/libvips/mosaicing/matrixinvert.c @@ -0,0 +1,491 @@ +/* solve and invert matrices + * + * 19/4/20 kleisauke + * - from im_matinv + */ + +/* + + 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 + +#include + +/* Our state. + */ +typedef struct _VipsMatrixinvert { + VipsOperation parent_instance; + + VipsImage *in; + VipsImage *out; + + /* .. and cast to a matrix. + */ + VipsImage *mat; + + /* The LU decomposed matrix. + */ + VipsImage *lu; +} VipsMatrixinvert; + +typedef VipsOperationClass VipsMatrixinvertClass; + +G_DEFINE_TYPE( VipsMatrixinvert, vips_matrixinvert, VIPS_TYPE_OPERATION ); + +static void +vips_matrixinvert_dispose( GObject *gobject ) +{ + VipsMatrixinvert *matrix = (VipsMatrixinvert *) gobject; + + VIPS_UNREF( matrix->mat ); + VIPS_UNREF( matrix->lu ); + + G_OBJECT_CLASS( vips_matrixinvert_parent_class )->dispose( gobject ); +} + +/* DBL_MIN is smallest *normalized* double precision float + */ +#define TOO_SMALL (2.0 * DBL_MIN) + +/* Save a bit of typing. + */ +#define ME( m, i, j ) (*VIPS_MATRIX( (m), (i), (j) )) + +/** + * lu_decomp: + * @mat: matrix to decompose + * + * This function takes any square NxN #VipsImage. + * It returns a #VipsImage which is (N+1)xN. + * + * It calculates the PLU decomposition, storing the upper and diagonal parts + * of U, together with the lower parts of L, as an NxN matrix in the first + * N rows of the new matrix. The diagonal parts of L are all set to unity + * and are not stored. + * + * The final row of the new #VipsImage has only integer entries, which + * represent the row-wise permutations made by the permutation matrix P. + * + * The scale and offset members of the input #VipsImage are ignored. + * + * See: + * + * PRESS, W. et al, 1992. Numerical Recipies in C; The Art of Scientific + * Computing, 2nd ed. Cambridge: Cambridge University Press, pp. 43-50. + * + * Returns: the decomposed matrix on success, or NULL on error. + */ +static VipsImage * +lu_decomp( VipsImage *mat ) +{ + int i, j, k; + double *row_scale; + VipsImage *lu; + + if ( !(row_scale = VIPS_ARRAY( NULL, mat->Xsize, double )) ) { + return( NULL ); + } + + if( !(lu = vips_image_new_matrix( mat->Xsize, mat->Xsize + 1 )) ) { + g_free( row_scale ); + return( NULL ); + } + + /* copy all coefficients and then perform decomposition in-place */ + memcpy( VIPS_MATRIX( lu, 0, 0), VIPS_MATRIX( mat, 0, 0), + mat->Xsize * mat->Xsize * sizeof( double ) ); + + for( i = 0; i < mat->Xsize; ++i ) { + row_scale[i] = 0.0; + + for( j = 0; j < mat->Xsize; ++j ) { + double abs_val = fabs( ME( lu, i, j ) ); + + /* find largest in each ROW */ + if( abs_val > row_scale[i] ) + row_scale[i] = abs_val; + } + + if( !row_scale[i] ) { + vips_error( "matrixinvert", "singular matrix" ); + g_object_unref( lu ); + g_free( row_scale ); + return( NULL ); + } + + /* fill array with scaling factors for each ROW */ + row_scale[i] = 1.0 / row_scale[i]; + } + + for( j = 0; j < mat->Xsize; ++j ) { /* loop over COLs */ + double max = -1.0; + int i_of_max; + + /* not needed, but stops a compiler warning */ + i_of_max = 0; + + /* loop over ROWS in upper-half, except diagonal */ + for( i = 0; i < j; ++i ) + for( k = 0; k < i; ++k ) + ME( lu, i, j ) -= ME( lu, i, k ) * ME( lu, k, j ); + + /* loop over ROWS in diagonal and lower-half */ + for( i = j; i < mat->Xsize; ++i ) { + double abs_val; + + for( k = 0; k < j; ++k ) + ME( lu, i, j ) -= ME( lu, i, k ) * ME( lu, k, j ); + + /* find largest element in each COLUMN scaled so that */ + /* largest in each ROW is 1.0 */ + abs_val = row_scale[i] * fabs( ME( lu, i, j ) ); + + if( abs_val > max ) { + max = abs_val; + i_of_max = i; + } + } + + if( fabs( ME( lu, i_of_max, j ) ) < TOO_SMALL ) { + /* divisor is near zero */ + vips_error( "matrixinvert", "singular or near-singular matrix" ); + g_object_unref( lu ); + g_free( row_scale ); + return( NULL ); + } + + if( i_of_max != j ) { + /* swap ROWS */ + for( k = 0; k < mat->Xsize; ++k ) { + double temp = ME( lu, j, k ); + ME( lu, j, k ) = ME( lu, i_of_max, k ); + ME( lu, i_of_max, k ) = temp; + } + + row_scale[i_of_max] = row_scale[j]; + /* no need to copy this scale back up - we won't use it */ + } + + /* record permutation */ + ME( lu, j, mat->Xsize ) = i_of_max; + + /* divide by best (largest scaled) pivot found */ + for( i = j + 1; i < mat->Xsize; ++i ) + ME( lu, i, j ) /= ME( lu, j, j ); + } + g_free( row_scale ); + + return( lu ); +} + +/** + * lu_solve: + * @lu: matrix to solve + * @vec: name for output matrix + * + * Solve the system of linear equations Ax=b, where matrix A has already + * been decomposed into LU form in VipsImage *lu. Input vector b is in + * vec and is overwritten with vector x. + * + * See: + * + * PRESS, W. et al, 1992. Numerical Recipies in C; The Art of Scientific + * Computing, 2nd ed. Cambridge: Cambridge University Press, pp. 43-50. + * + * See also: im_mattrn(), im_matinv(). + * + * Returns: 0 on success, -1 on error + */ +static int +lu_solve( VipsImage *lu, double *vec ) +{ + int i, j; + + if( lu->Xsize + 1 != lu->Ysize ) { + vips_error( "matrixinvert", "not an LU decomposed matrix" ); + return( -1 ); + } + + for( i = 0; i < lu->Xsize; ++i ) { + int i_perm = ME( lu, i, lu->Xsize ); + + if( i_perm != i ) { + double temp = vec[i]; + vec[i] = vec[i_perm]; + vec[i_perm] = temp; + } + for( j = 0; j < i; ++j ) + vec[i] -= ME( lu, i, j ) * vec[j]; + } + + for( i = lu->Xsize - 1; i >= 0; --i ) { + + for( j = i + 1; j < lu->Xsize; ++j ) + vec[i] -= ME( lu, i, j ) * vec[j]; + + vec[i] /= ME( lu, i, i ); + } + + return( 0 ); +} + +static int +vips_matrixinvert_solve( VipsMatrixinvert *matrix ) +{ + VipsImage *out = matrix->out; + + int i, j; + double *vec; + + if( !(matrix->lu = lu_decomp( matrix->mat ) ) ) + return( -1 ); + + if( !(vec = VIPS_ARRAY( matrix, matrix->lu->Xsize, double )) ) + return( -1 ); + + for( j = 0; j < matrix->lu->Xsize; ++j ) { + for( i = 0; i < matrix->lu->Xsize; ++i ) + vec[i] = 0.0; + + vec[j] = 1.0; + + if( lu_solve( matrix->lu, vec ) ) + return( -1 ); + + for( i = 0; i < matrix->lu->Xsize; ++i ) + ME( out, i, j ) = vec[i]; + } + + return( 0 ); +} + +static int +vips_matrixinvert_direct( VipsMatrixinvert *matrix ) +{ + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( matrix ); + VipsImage *in = matrix->mat; + VipsImage *out = matrix->out; + + switch( matrix->mat->Xsize ) { + case 1: +{ + double det = ME( in, 0, 0 ); + + if( fabs( det ) < TOO_SMALL ) { + /* divisor is near zero */ + vips_error( class->nickname, + "%s", _( "singular or near-singular matrix" ) ); + return( -1 ); + } + + ME( out, 0, 0 ) = 1.0 / det; +} + break; + + case 2: +{ + double det = ME( in, 0, 0 ) * ME( in, 1, 1 ) - + ME( in, 0, 1 ) * ME( in, 1, 0 ); + + double tmp; + + if( fabs( det ) < TOO_SMALL ) { + /* divisor is near zero */ + vips_error( class->nickname, + "%s", _( "singular or near-singular matrix" ) ); + return( -1 ); + } + + tmp = 1.0 / det; + ME( out, 0, 0 ) = tmp * ME( in, 1, 1 ); + ME( out, 0, 1 ) = -tmp * ME( in, 0, 1 ); + ME( out, 1, 0 ) = -tmp * ME( in, 1, 0 ); + ME( out, 1, 1 ) = tmp * ME( in, 0, 0 ); +} + break; + + case 3: +{ + double det; + double tmp; + + det = ME( in, 0, 0 ) * ( ME( in, 1, 1 ) * + ME( in, 2, 2 ) - ME( in, 1, 2 ) * ME( in, 2, 1 ) ); + det -= ME( in, 0, 1 ) * ( ME( in, 1, 0 ) * + ME( in, 2, 2 ) - ME( in, 1, 2 ) * ME( in, 2, 0) ); + det += ME( in, 0, 2) * ( ME( in, 1, 0 ) * + ME( in, 2, 1 ) - ME( in, 1, 1 ) * ME( in, 2, 0 ) ); + + if( fabs( det ) < TOO_SMALL ) { + /* divisor is near zero */ + vips_error( class->nickname, + "%s", _( "singular or near-singular matrix" ) ); + return( -1 ); + } + + tmp = 1.0 / det; + + ME( out, 0, 0 ) = tmp * ( ME( in, 1, 1 ) * ME( in, 2, 2 ) - + ME( in, 1, 2 ) * ME( in, 2, 1 ) ); + ME( out, 1, 0 ) = tmp * ( ME( in, 1, 2 ) * ME( in, 2, 0 ) - + ME( in, 1, 0 ) * ME( in, 2, 2 ) ); + ME( out, 2, 0 ) = tmp * ( ME( in, 1, 0 ) * ME( in, 2, 1 ) - + ME( in, 1, 1 ) * ME( in, 2, 0 ) ); + + ME( out, 0, 1 ) = tmp * ( ME( in, 0, 2 ) * ME( in, 2, 1 ) - + ME( in, 0, 1 ) * ME( in, 2, 2 ) ); + ME( out, 1, 1 ) = tmp * ( ME( in, 0, 0 ) * ME( in, 2, 2 ) - + ME( in, 0, 2 ) * ME( in, 2, 0 ) ); + ME( out, 2, 1 ) = tmp * ( ME( in, 0, 1 ) * ME( in, 2, 0 ) - + ME( in, 0, 0 ) * ME( in, 2, 1 ) ); + + ME( out, 0, 2 ) = tmp * ( ME( in, 0, 1 ) * ME( in, 1, 2 ) - + ME( in, 0, 2 ) * ME( in, 1, 1 ) ); + ME( out, 1, 2 ) = tmp * ( ME( in, 0, 2 ) * ME( in, 1, 0 ) - + ME( in, 0, 0 ) * ME( in, 1, 2 ) ); + ME( out, 2, 2 ) = tmp * ( ME( in, 0, 0 ) * ME( in, 1, 1 ) - + ME( in, 0, 1 ) * ME( in, 1, 0 ) ); +} + break; + + /* TODO(kleisauke): + * We sometimes use 4x4 matrices, could we also make a + * direct version for those? For e.g.: + * https://stackoverflow.com/a/1148405/10952119 */ + default: + g_assert( 0 ); + return( -1 ); + } + + return( 0 ); +} + +static int +vips_matrixinvert_build( VipsObject *object ) +{ + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object ); + VipsMatrixinvert *matrix = (VipsMatrixinvert *) object; + + if( VIPS_OBJECT_CLASS( vips_matrixinvert_parent_class )-> + build( object ) ) + return( -1 ); + + if( vips_check_matrix( class->nickname, matrix->in, &matrix->mat ) ) + return( -1 ); + + if( matrix->mat->Xsize != matrix->mat->Ysize ) { + vips_error( class->nickname, "%s", _( "non-square matrix" ) ); + return( -1 ); + } + + g_object_set( matrix, + "out", vips_image_new_matrix( matrix->mat->Xsize, + matrix->mat->Ysize ), + NULL ); + + /* Direct path for < 4x4 matrices + */ + if( matrix->mat->Xsize >= 4 ) { + if( vips_matrixinvert_solve( matrix ) ) + return( -1 ); + } + else { + if( vips_matrixinvert_direct( matrix ) ) + return( -1 ); + } + + return( 0 ); +} + +static void +vips_matrixinvert_class_init( VipsMatrixinvertClass *class ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( class ); + VipsObjectClass *vobject_class = VIPS_OBJECT_CLASS( class ); + + gobject_class->dispose = vips_matrixinvert_dispose; + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + vobject_class->nickname = "matrixinvert"; + vobject_class->description = _( "invert an matrix" ); + vobject_class->build = vips_matrixinvert_build; + + VIPS_ARG_IMAGE( class, "in", 0, + _( "Input" ), + _( "An square matrix" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsMatrixinvert, in ) ); + + VIPS_ARG_IMAGE( class, "out", 1, + _( "Output" ), + _( "Output matrix" ), + VIPS_ARGUMENT_REQUIRED_OUTPUT, + G_STRUCT_OFFSET( VipsMatrixinvert, out ) ); +} + +static void +vips_matrixinvert_init( VipsMatrixinvert *matrix ) +{ +} + +/** + * vips_matrixinvert: (method) + * @m: matrix to invert + * @out: (out): output matrix + * @...: %NULL-terminated list of optional named arguments + * + * This operation calculates the inverse of the matrix represented in @m. + * The scale and offset members of the input matrix are ignored. + * + * See also: vips_matrixload(). + * + * Returns: 0 on success, -1 on error + */ +int +vips_matrixinvert( VipsImage *m, VipsImage **out, ... ) +{ + va_list ap; + int result; + + va_start( ap, out ); + result = vips_call_split( "matrixinvert", ap, m, out ); + va_end( ap ); + + return( result ); +} diff --git a/libvips/mosaicing/merge.c b/libvips/mosaicing/merge.c index d309b8e0..24d2cd47 100644 --- a/libvips/mosaicing/merge.c +++ b/libvips/mosaicing/merge.c @@ -33,11 +33,6 @@ */ -/* This is a simple wrapper over the old vips7 functions. At some point we - * should rewrite this as a pure vips8 class and redo the vips7 functions as - * wrappers over this. - */ - #ifdef HAVE_CONFIG_H #include #endif /*HAVE_CONFIG_H*/ @@ -46,7 +41,6 @@ #include #include -#include typedef struct { VipsOperation parent_instance; @@ -77,13 +71,13 @@ vips_merge_build( VipsObject *object ) switch( merge->direction ) { case VIPS_DIRECTION_HORIZONTAL: - if( im_lrmerge( merge->ref, merge->sec, merge->out, + if( vips_lrmerge( merge->ref, merge->sec, merge->out, merge->dx, merge->dy, merge->mblend ) ) return( -1 ); break; case VIPS_DIRECTION_VERTICAL: - if( im_tbmerge( merge->ref, merge->sec, merge->out, + if( vips_tbmerge( merge->ref, merge->sec, merge->out, merge->dx, merge->dy, merge->mblend ) ) return( -1 ); break; diff --git a/libvips/mosaicing/mosaic.c b/libvips/mosaicing/mosaic.c index b14bef9f..d2d83d0e 100644 --- a/libvips/mosaicing/mosaic.c +++ b/libvips/mosaicing/mosaic.c @@ -4,6 +4,8 @@ * - from vips_mosaic() * 4/9/18 * - add docs for transform output + * 18/6/20 kleisauke + * - convert to vips8 */ /* @@ -33,11 +35,6 @@ */ -/* This is a simple wrapper over the old vips7 functions. At some point we - * should rewrite this as a pure vips8 class and redo the vips7 functions as - * wrappers over this. - */ - #ifdef HAVE_CONFIG_H #include #endif /*HAVE_CONFIG_H*/ @@ -46,7 +43,6 @@ #include #include -#include #include "pmosaicing.h" @@ -103,7 +99,7 @@ vips_mosaic_build( VipsObject *object ) switch( mosaic->direction ) { case VIPS_DIRECTION_HORIZONTAL: - if( im__find_lroverlap( mosaic->ref, mosaic->sec, x, + if( vips__find_lroverlap( mosaic->ref, mosaic->sec, x, mosaic->bandno, mosaic->xref, mosaic->yref, mosaic->xsec, mosaic->ysec, mosaic->hwindow, mosaic->harea, @@ -117,7 +113,7 @@ vips_mosaic_build( VipsObject *object ) break; case VIPS_DIRECTION_VERTICAL: - if( im__find_tboverlap( mosaic->ref, mosaic->sec, x, + if( vips__find_tboverlap( mosaic->ref, mosaic->sec, x, mosaic->bandno, mosaic->xref, mosaic->yref, mosaic->xsec, mosaic->ysec, mosaic->hwindow, mosaic->harea, diff --git a/libvips/mosaicing/mosaic1.c b/libvips/mosaicing/mosaic1.c index 80ccda25..39aad6ce 100644 --- a/libvips/mosaicing/mosaic1.c +++ b/libvips/mosaicing/mosaic1.c @@ -15,6 +15,8 @@ * - works for LABQ as well * 25/1/11 * - gtk-doc + * 18/6/20 kleisauke + * - convert to vips8 */ /* @@ -53,13 +55,12 @@ #include #include -#include #include #include #include "pmosaicing.h" -/* +/* Define for debug output. #define DEBUG */ @@ -67,10 +68,10 @@ #define OLD */ -/* Like im_similarity(), but return the transform we generated. +/* Like vips_similarity(), but return the transform we generated. */ static int -apply_similarity( VipsTransformation *trn, IMAGE *in, IMAGE *out, +apply_similarity( VipsTransformation *trn, VipsImage *in, VipsImage *out, double a, double b, double dx, double dy ) { trn->iarea.left = 0; @@ -89,7 +90,7 @@ apply_similarity( VipsTransformation *trn, IMAGE *in, IMAGE *out, if( vips__transform_calc_inverse( trn ) ) return( -1 ); - if( vips__affine( in, out, trn ) ) + if( vips__affinei( in, out, trn ) ) return( -1 ); return( 0 ); @@ -97,40 +98,41 @@ apply_similarity( VipsTransformation *trn, IMAGE *in, IMAGE *out, /* A join function ... either left-right or top-bottom rotscalemerge. */ -typedef int (*joinfn)( IMAGE *, IMAGE *, IMAGE *, +typedef int (*joinfn)( VipsImage *, VipsImage *, VipsImage *, double, double, double, double, int ); /* similarity+lrmerge. */ int -im__lrmerge1( IMAGE *ref, IMAGE *sec, IMAGE *out, +vips__lrmerge1( VipsImage *ref, VipsImage *sec, VipsImage *out, double a, double b, double dx, double dy, int mwidth ) { VipsTransformation trn; - IMAGE *t1 = im_open_local( out, "im_lrmosaic1:1", "p" ); + VipsImage **t = (VipsImage **) + vips_object_local_array( VIPS_OBJECT( out ), 1 ); VipsBuf buf; char text[1024]; /* Scale, rotate and displace sec. */ - if( !t1 || apply_similarity( &trn, sec, t1, a, b, dx, dy ) ) + if( apply_similarity( &trn, sec, t[0], a, b, dx, dy ) ) return( -1 ); /* And join to ref. */ - if( im__lrmerge( ref, t1, out, + if( vips__lrmerge( ref, t[0], out, -trn.oarea.left, -trn.oarea.top, mwidth ) ) return( -1 ); /* Note parameters in history file ... for global balance to pick up * later. */ - im__add_mosaic_name( out ); + vips__add_mosaic_name( out ); vips_buf_init_static( &buf, text, 1024 ); vips_buf_appendf( &buf, "#LRROTSCALE <%s> <%s> <%s> <", - im__get_mosaic_name( ref ), - im__get_mosaic_name( sec ), - im__get_mosaic_name( out ) ); + vips__get_mosaic_name( ref ), + vips__get_mosaic_name( sec ), + vips__get_mosaic_name( out ) ); vips_buf_appendg( &buf, a ); vips_buf_appendf( &buf, "> <" ); vips_buf_appendg( &buf, b ); @@ -139,7 +141,7 @@ im__lrmerge1( IMAGE *ref, IMAGE *sec, IMAGE *out, vips_buf_appendf( &buf, "> <" ); vips_buf_appendg( &buf, dy ); vips_buf_appendf( &buf, "> <%d>", mwidth ); - if( im_histlin( out, "%s", vips_buf_all( &buf ) ) ) + if( vips_image_history_printf( out, "%s", vips_buf_all( &buf ) ) ) return( -1 ); return( 0 ); @@ -148,34 +150,35 @@ im__lrmerge1( IMAGE *ref, IMAGE *sec, IMAGE *out, /* similarity+tbmerge. */ int -im__tbmerge1( IMAGE *ref, IMAGE *sec, IMAGE *out, +vips__tbmerge1( VipsImage *ref, VipsImage *sec, VipsImage *out, double a, double b, double dx, double dy, int mwidth ) { VipsTransformation trn; - IMAGE *t1 = im_open_local( out, "im_lrmosaic1:2", "p" ); + VipsImage **t = (VipsImage **) + vips_object_local_array( VIPS_OBJECT( out ), 1 ); VipsBuf buf; char text[1024]; /* Scale, rotate and displace sec. */ - if( !t1 || apply_similarity( &trn, sec, t1, a, b, dx, dy ) ) + if( apply_similarity( &trn, sec, t[0], a, b, dx, dy ) ) return( -1 ); /* And join to ref. */ - if( im__tbmerge( ref, t1, out, + if( vips__tbmerge( ref, t[0], out, -trn.oarea.left, -trn.oarea.top, mwidth ) ) return( -1 ); /* Note parameters in history file ... for global balance to pick up * later. */ - im__add_mosaic_name( out ); + vips__add_mosaic_name( out ); vips_buf_init_static( &buf, text, 1024 ); vips_buf_appendf( &buf, "#TBROTSCALE <%s> <%s> <%s> <", - im__get_mosaic_name( ref ), - im__get_mosaic_name( sec ), - im__get_mosaic_name( out ) ); + vips__get_mosaic_name( ref ), + vips__get_mosaic_name( sec ), + vips__get_mosaic_name( out ) ); vips_buf_appendg( &buf, a ); vips_buf_appendf( &buf, "> <" ); vips_buf_appendg( &buf, b ); @@ -184,7 +187,7 @@ im__tbmerge1( IMAGE *ref, IMAGE *sec, IMAGE *out, vips_buf_appendf( &buf, "> <" ); vips_buf_appendg( &buf, dy ); vips_buf_appendf( &buf, "> <%d>", mwidth ); - if( im_histlin( out, "%s", vips_buf_all( &buf ) ) ) + if( vips_image_history_printf( out, "%s", vips_buf_all( &buf ) ) ) return( -1 ); return( 0 ); @@ -193,7 +196,7 @@ im__tbmerge1( IMAGE *ref, IMAGE *sec, IMAGE *out, /* Join two images, using a pair of tie-points as parameters. */ static int -rotjoin( IMAGE *ref, IMAGE *sec, IMAGE *out, joinfn jfn, +rotjoin( VipsImage *ref, VipsImage *sec, VipsImage *out, joinfn jfn, int xr1, int yr1, int xs1, int ys1, int xr2, int yr2, int xs2, int ys2, int mwidth ) @@ -202,7 +205,7 @@ rotjoin( IMAGE *ref, IMAGE *sec, IMAGE *out, joinfn jfn, /* Solve to get scale + rot + disp. */ - if( im__coeff( xr1, yr1, xs1, ys1, xr2, yr2, xs2, ys2, + if( vips__coeff( xr1, yr1, xs1, ys1, xr2, yr2, xs2, ys2, &a, &b, &dx, &dy ) ) return( -1 ); @@ -217,7 +220,7 @@ rotjoin( IMAGE *ref, IMAGE *sec, IMAGE *out, joinfn jfn, /* Like rotjoin, but do a search to refine the tie-points. */ static int -rotjoin_search( IMAGE *ref, IMAGE *sec, IMAGE *out, joinfn jfn, +rotjoin_search( VipsImage *ref, VipsImage *sec, VipsImage *out, joinfn jfn, int bandno, int xr1, int yr1, int xs1, int ys1, int xr2, int yr2, int xs2, int ys2, @@ -237,21 +240,19 @@ rotjoin_search( IMAGE *ref, IMAGE *sec, IMAGE *out, joinfn jfn, /* Temps. */ - IMAGE *t[3]; - - if( im_open_local_array( out, t, 3, "rotjoin_search", "p" ) ) - return( -1 ); + VipsImage **t = (VipsImage **) + vips_object_local_array( VIPS_OBJECT( out ), 3 ); /* Unpack LABQ to LABS for correlation. */ - if( ref->Coding == IM_CODING_LABQ ) { - if( im_LabQ2LabS( ref, t[0] ) ) + if( ref->Coding == VIPS_CODING_LABQ ) { + if( vips_LabQ2LabS( ref, &t[0], NULL ) ) return( -1 ); } else t[0] = ref; - if( sec->Coding == IM_CODING_LABQ ) { - if( im_LabQ2LabS( sec, t[1] ) ) + if( sec->Coding == VIPS_CODING_LABQ ) { + if( vips_LabQ2LabS( sec, &t[1], NULL ) ) return( -1 ); } else @@ -259,7 +260,7 @@ rotjoin_search( IMAGE *ref, IMAGE *sec, IMAGE *out, joinfn jfn, /* Solve to get scale + rot + disp. */ - if( im__coeff( xr1, yr1, xs1, ys1, xr2, yr2, xs2, ys2, + if( vips__coeff( xr1, yr1, xs1, ys1, xr2, yr2, xs2, ys2, &a, &b, &dx, &dy ) || apply_similarity( &trn, t[1], t[2], a, b, dx, dy ) ) return( -1 ); @@ -273,11 +274,11 @@ rotjoin_search( IMAGE *ref, IMAGE *sec, IMAGE *out, joinfn jfn, * vips__transform_set_area() has set, and move the sec tie-points * accordingly. */ - if( im_correl( t[0], t[2], xr1, yr1, + if( vips_correl( t[0], t[2], xr1, yr1, xs3 - trn.oarea.left, ys3 - trn.oarea.top, halfcorrelation, halfarea, &cor1, &xs5, &ys5 ) ) return( -1 ); - if( im_correl( t[0], t[2], xr2, yr2, + if( vips_correl( t[0], t[2], xr2, yr2, xs4 - trn.oarea.left, ys4 - trn.oarea.top, halfcorrelation, halfarea, &cor2, &xs6, &ys6 ) ) return( -1 ); @@ -305,7 +306,7 @@ rotjoin_search( IMAGE *ref, IMAGE *sec, IMAGE *out, joinfn jfn, /* Recalc the transform using the refined points. */ - if( im__coeff( xr1, yr1, xs7, ys7, xr2, yr2, xs8, ys8, + if( vips__coeff( xr1, yr1, xs7, ys7, xr2, yr2, xs8, ys8, &a, &b, &dx, &dy ) ) return( -1 ); @@ -318,11 +319,11 @@ rotjoin_search( IMAGE *ref, IMAGE *sec, IMAGE *out, joinfn jfn, } #ifdef OLD -/* 1st order mosaic using im__find_lroverlap() ... does not work too well :( - * Look at im__find_lroverlap() for problem? +/* 1st order mosaic using vips__find_lroverlap() ... does not work too well :( + * Look at vips__find_lroverlap() for problem? */ static int -old_lrmosaic1( IMAGE *ref, IMAGE *sec, IMAGE *out, +old_lrmosaic1( VipsImage *ref, VipsImage *sec, VipsImage *out, int bandno, int xr1, int yr1, int xs1, int ys1, int xr2, int yr2, int xs2, int ys2, @@ -340,35 +341,31 @@ old_lrmosaic1( IMAGE *ref, IMAGE *sec, IMAGE *out, /* Temps. */ - IMAGE *t1 = im_open_local( out, "im_lrmosaic1:1", "p" ); - IMAGE *t2 = im_open_local( out, "im_lrmosaic1:2", "p" ); - IMAGE *dummy; - - if( !t1 || !t2 ) - return( -1 ); + VipsImage **t = (VipsImage **) + vips_object_local_array( VIPS_OBJECT( out ), 2 ); + VipsImage *dummy; /* Solve to get scale + rot + disp. */ - if( im__coeff( xr1, yr1, xs1, ys1, xr2, yr2, xs2, ys2, + if( vips__coeff( xr1, yr1, xs1, ys1, xr2, yr2, xs2, ys2, &a, &b, &dx, &dy ) || - apply_similarity( &trn1, sec, t1, a, b, dx, dy ) ) + apply_similarity( &trn1, sec, t[0], a, b, dx, dy ) ) return( -1 ); /* Correct tie-points. dummy is just a placeholder used to ensure that * memory used by the analysis phase is freed as soon as possible. */ - if( !(dummy = im_open( "placeholder:1", "p" )) ) - return( -1 ); - if( im__find_lroverlap( ref, t1, dummy, + dummy = vips_image_new(); + if( vips__find_lroverlap( ref, t[0], dummy, bandno, -trn1.area.left, -trn1.area.top, 0, 0, halfcorrelation, halfarea, &dx0, &dy0, &a1, &b1, &dx1, &dy1 ) ) { - im_close( dummy ); + g_object_unref( dummy ); return( -1 ); } - im_close( dummy ); + g_object_unref( dummy ); /* Now combine the two transformations to get a corrected transform. */ @@ -386,7 +383,7 @@ old_lrmosaic1( IMAGE *ref, IMAGE *sec, IMAGE *out, /* Scale and rotate final. */ - if( apply_similarity( &trn2, sec, t2, af, bf, dxf, dyf ) ) + if( apply_similarity( &trn2, sec, t[1], af, bf, dxf, dyf ) ) return( -1 ); printf( "disp: trn1 left = %d, top = %d\n", @@ -396,7 +393,7 @@ old_lrmosaic1( IMAGE *ref, IMAGE *sec, IMAGE *out, /* And join to ref. */ - if( im_lrmerge( ref, t2, out, + if( vips_lrmerge( ref, t[1], out, -trn2.area.left, -trn2.area.top, mwidth ) ) return( -1 ); @@ -448,7 +445,7 @@ vips_mosaic1_build( VipsObject *object ) mosaic1->interpolate = vips_interpolate_new( "bilinear" ); jfn = mosaic1->direction == VIPS_DIRECTION_HORIZONTAL ? - im__lrmerge1 : im__tbmerge1; + vips__lrmerge1 : vips__tbmerge1; if( mosaic1->search ) { if( rotjoin_search( mosaic1->ref, mosaic1->sec, mosaic1->out, diff --git a/libvips/mosaicing/mosaicing.c b/libvips/mosaicing/mosaicing.c index 0e03430b..5689cb7e 100644 --- a/libvips/mosaicing/mosaicing.c +++ b/libvips/mosaicing/mosaicing.c @@ -29,7 +29,7 @@ */ -/* +/* Define for debug output. #define DEBUG */ @@ -52,14 +52,14 @@ * @include: vips/vips.h * * These functions are useful for joining many small images together to make - * one large image. They can cope with unstable contrast and arbitary sub-image + * one large image. They can cope with unstable contrast and arbitrary sub-image * layout, but will not do any geometric correction. Geometric errors should * be removed before using these functions. * * The mosaicing functions can be grouped into layers: * - * The lowest level functions are im_correl() and vips_merge(). - * im_correl() + * The lowest level functions are vips_correl() and vips_merge(). + * vips_correl() * searches a large image for a small sub-image, returning * the position of the best sub-image match. vips_merge() * joins two images together @@ -69,7 +69,7 @@ * search function plus the two low-level merge operations to join two images * given just an approximate overlap as a start point. * - * The functions im_lrmosaic1() and im_tbmosaic1() are + * The functions vips_lrmosaic1() and vips_tbmosaic1() are * first-order * analogues of the basic mosaic functions: they take two approximate * tie-points and use @@ -82,7 +82,7 @@ * measures image contrast differences along the seams, finds a set of * correction factors which will minimise these differences, and reassembles * the mosaic. - * im_remosaic() uses the + * vips_remosaic() uses the * same * techniques, but will reassemble the image from a different set of source * images. @@ -95,15 +95,17 @@ void vips_mosaicing_operation_init( void ) { - extern int vips_merge_get_type( void ); - extern int vips_mosaic_get_type( void ); - extern int vips_mosaic1_get_type( void ); - extern int vips_match_get_type( void ); - extern int vips_globalbalance_get_type( void ); + extern GType vips_merge_get_type( void ); + extern GType vips_mosaic_get_type( void ); + extern GType vips_mosaic1_get_type( void ); + extern GType vips_match_get_type( void ); + extern GType vips_globalbalance_get_type( void ); + extern GType vips_matrixinvert_get_type( void ); vips_merge_get_type(); vips_mosaic_get_type(); vips_mosaic1_get_type(); + vips_matrixinvert_get_type(); vips_match_get_type(); vips_globalbalance_get_type(); } diff --git a/libvips/mosaicing/pmosaicing.h b/libvips/mosaicing/pmosaicing.h index 64f24acf..adaf8457 100644 --- a/libvips/mosaicing/pmosaicing.h +++ b/libvips/mosaicing/pmosaicing.h @@ -1,7 +1,7 @@ /* Local definitions used by the mosaicing program - * If IM_MAXPOINTS change please ensure that it is still a multiple of + * If VIPS_MAXPOINTS change please ensure that it is still a multiple of * AREAS or else AREAS must change as well. Initial setup is for - * IM_MAXPOINTS = 60, AREAS = 3. + * VIPS_MAXPOINTS = 60, AREAS = 3. * * Copyright: 1990, 1991 N. Dessipris * Author: Nicos Dessipris @@ -36,6 +36,9 @@ */ +/* TODO(kleisauke): This import is needed for vips__affinei */ +#include + /* Number of entries in blend table. As a power of two as well, for >>ing. */ #define BLEND_SHIFT (10) @@ -54,9 +57,9 @@ typedef int (*VipsBlendFn)( VipsRegion *or, /* Keep state for each call in one of these. */ typedef struct _Overlapping { - IMAGE *ref; /* Arguments */ - IMAGE *sec; - IMAGE *out; + VipsImage *ref; /* Arguments */ + VipsImage *sec; + VipsImage *out; int dx, dy; int mwidth; @@ -64,18 +67,18 @@ typedef struct _Overlapping { * that the output image is always positioned at (0,0) - ie. all these * coordinates are in output image space. */ - Rect rarea; - Rect sarea; - Rect overlap; - Rect oarea; + VipsRect rarea; + VipsRect sarea; + VipsRect overlap; + VipsRect oarea; int blsize; /* Max blend length */ int flsize; /* first/last cache size */ /* Sections of ref and sec which we use in output, excluding * overlap area. */ - Rect rpart; - Rect spart; + VipsRect rpart; + VipsRect spart; /* Overlap start/end cache */ @@ -90,101 +93,108 @@ typedef struct _Overlapping { /* Keep per-thread state here. */ typedef struct _MergeInfo { - REGION *rir; /* Two input regions */ - REGION *sir; + VipsRegion *rir; /* Two input regions */ + VipsRegion *sir; - float *from1; /* IM_CODING_LABQ buffers */ + float *from1; /* VIPS_CODING_LABQ buffers */ float *from2; float *merge; } MergeInfo; /* Functions shared between lr and tb. */ -extern double *im__coef1; -extern double *im__coef2; -extern int *im__icoef1; -extern int *im__icoef2; -int im__make_blend_luts( void ); +extern double *vips__coef1; +extern double *vips__coef2; +extern int *vips__icoef1; +extern int *vips__icoef2; +int vips__make_blend_luts( void ); -void im__add_mosaic_name( VipsImage *image ); -const char *im__get_mosaic_name( VipsImage *image ); +void vips__add_mosaic_name( VipsImage *image ); +const char *vips__get_mosaic_name( VipsImage *image ); -int im__attach_input( REGION *or, REGION *ir, Rect *area ); -int im__copy_input( REGION *or, REGION *ir, Rect *area, Rect *reg ); -Overlapping *im__build_mergestate( const char *domain, - IMAGE *ref, IMAGE *sec, IMAGE *out, int dx, int dy, int mwidth ); -void *im__start_merge( IMAGE *out, void *, void * ); -int im__merge_gen( REGION *or, void *seq, void *a, void * ); -int im__stop_merge( void *seq, void *, void * ); -int im__lrmerge( IMAGE *ref, IMAGE *sec, IMAGE *out, +int vips__affinei( VipsImage *in, VipsImage *out, VipsTransformation *trn ); + +int vips__attach_input( VipsRegion *or, VipsRegion *ir, VipsRect *area ); +int vips__copy_input( VipsRegion *or, VipsRegion *ir, VipsRect *area, VipsRect *reg ); +Overlapping *vips__build_mergestate( const char *domain, + VipsImage *ref, VipsImage *sec, VipsImage *out, int dx, int dy, int mwidth ); +void *vips__start_merge( VipsImage *out, void *, void * ); +int vips__merge_gen( VipsRegion *or, void *seq, void *a, void *, + gboolean *stop ); +int vips__stop_merge( void *seq, void *, void * ); +int vips__lrmerge( VipsImage *ref, VipsImage *sec, VipsImage *out, int dx, int dy, int mwidth ); -int im__tbmerge( IMAGE *ref, IMAGE *sec, IMAGE *out, +int vips__tbmerge( VipsImage *ref, VipsImage *sec, VipsImage *out, int dx, int dy, int mwidth ); -int im__lrmerge1( IMAGE *ref, IMAGE *sec, IMAGE *out, +int vips__lrmerge1( VipsImage *ref, VipsImage *sec, VipsImage *out, double a, double b, double dx, double dy, int mwidth ); -int im__tbmerge1( IMAGE *ref, IMAGE *sec, IMAGE *out, +int vips__tbmerge1( VipsImage *ref, VipsImage *sec, VipsImage *out, double a, double b, double dx, double dy, int mwidth ); -#define IM_MAXPOINTS (60) /* IM_MAXPOINTS % AREAS must be zero */ +#define VIPS_MAXPOINTS (60) /* VIPS_MAXPOINTS % AREAS must be zero */ #define AREAS (3) typedef struct { - char *reference; /* filename of reference */ - char *secondary; /* filename of secondary */ - int deltax; /* initial estimate of displacement */ - int deltay; /* initial estimate of displacement */ - int nopoints; /* must be multiple of AREAS and <= IM_MAXPOINTS */ - int halfcorsize; /* recommended 5 */ - int halfareasize; /* recommended 8 */ + char *reference; /* filename of reference */ + char *secondary; /* filename of secondary */ + int deltax; /* initial estimate of displacement */ + int deltay; /* initial estimate of displacement */ + int nopoints; /* must be multiple of AREAS and <= VIPS_MAXPOINTS */ + int halfcorsize; /* recommended 5 */ + int halfareasize; /* recommended 8 */ - /* x, y_reference and contrast found by im_calcon() + /* x, y_reference and contrast found by vips_calcon() */ - int x_reference[IM_MAXPOINTS], y_reference[IM_MAXPOINTS]; - int contrast[IM_MAXPOINTS]; + int x_reference[VIPS_MAXPOINTS], y_reference[VIPS_MAXPOINTS]; + int contrast[VIPS_MAXPOINTS]; - /* x, y_secondary and correlation set by im_chkpair() + /* x, y_secondary and correlation set by vips_chkpair() */ - int x_secondary[IM_MAXPOINTS], y_secondary[IM_MAXPOINTS]; + int x_secondary[VIPS_MAXPOINTS], y_secondary[VIPS_MAXPOINTS]; /* returns the corrected best correlation * as detected in 2*halfareasize+1 * centered at point (x2, y2) and using * correlation area 2*halfareasize+1 */ - double correlation[IM_MAXPOINTS]; + double correlation[VIPS_MAXPOINTS]; - /* Coefficients calculated by im_clinear() + /* Coefficients calculated by vips_clinear() */ double l_scale, l_angle, l_deltax, l_deltay; - /* used by im_clinear() + /* used by vips_clinear() */ - double dx[IM_MAXPOINTS], dy[IM_MAXPOINTS]; - double deviation[IM_MAXPOINTS]; -} TIE_POINTS; + double dx[VIPS_MAXPOINTS], dy[VIPS_MAXPOINTS]; + double deviation[VIPS_MAXPOINTS]; +} TiePoints; -int im__chkpair( IMAGE *, IMAGE *, TIE_POINTS *point ); -int im__initialize( TIE_POINTS *points ); -int im__improve( TIE_POINTS *inpoints, TIE_POINTS *outpoints ); -int im__avgdxdy( TIE_POINTS *points, int *dx, int *dy ); -int im__lrcalcon( IMAGE *ref, TIE_POINTS *points ); -int im__tbcalcon( IMAGE *ref, TIE_POINTS *points ); -int im__coeff( int xr1, int yr1, int xs1, int ys1, +int vips__chkpair( VipsImage *, VipsImage *, TiePoints *point ); +int vips__initialize( TiePoints *points ); +int vips__improve( TiePoints *inpoints, TiePoints *outpoints ); +int vips__avgdxdy( TiePoints *points, int *dx, int *dy ); +int vips__lrcalcon( VipsImage *ref, TiePoints *points ); +int vips__tbcalcon( VipsImage *ref, TiePoints *points ); +int vips__coeff( int xr1, int yr1, int xs1, int ys1, int xr2, int yr2, int xs2, int ys2, double *a, double *b, double *dx, double *dy ); -int im__clinear( TIE_POINTS *points ); -int im__find_lroverlap( IMAGE *ref_in, IMAGE *sec_in, IMAGE *out, +int vips__clinear( TiePoints *points ); +int vips__find_lroverlap( VipsImage *ref_in, VipsImage *sec_in, VipsImage *out, int bandno_in, int xref, int yref, int xsec, int ysec, int halfcorrelation, int halfarea, int *dx0, int *dy0, double *scale1, double *angle1, double *dx1, double *dy1 ); -int im__find_tboverlap( IMAGE *ref_in, IMAGE *sec_in, IMAGE *out, +int vips__find_tboverlap( VipsImage *ref_in, VipsImage *sec_in, VipsImage *out, int bandno_in, int xref, int yref, int xsec, int ysec, int halfcorrelation, int halfarea, int *dx0, int *dy0, double *scale1, double *angle1, double *dx1, double *dy1 ); +int vips__find_best_contrast( VipsImage *image, + int xpos, int ypos, int xsize, int ysize, + int xarray[], int yarray[], int cont[], + int nbest, int hcorsize ); diff --git a/libvips/resample/affine.c b/libvips/resample/affine.c index cbf6903a..b75cd73d 100644 --- a/libvips/resample/affine.c +++ b/libvips/resample/affine.c @@ -88,6 +88,8 @@ * - add "background" parameter * - better clipping means we have no jaggies on edges * - premultiply alpha + * 18/5/20 + * - add "premultiplied" flag */ /* @@ -166,6 +168,10 @@ typedef struct _VipsAffine { */ VipsPel *ink; + /* True if the input is already premultiplied (and we don't need to). + */ + gboolean premultiplied; + } VipsAffine; typedef VipsResampleClass VipsAffineClass; @@ -524,11 +530,13 @@ vips_affine_build( VipsObject *object ) affine->trn.idx -= 1; affine->trn.idy -= 1; - /* If there's an alpha, we have to premultiply before resampling. See + /* If there's an alpha and we've not premultiplied, we have to + * premultiply before resampling. See * https://github.com/libvips/libvips/issues/291 */ have_premultiplied = FALSE; - if( vips_image_hasalpha( in ) ) { + if( vips_image_hasalpha( in ) && + !affine->premultiplied ) { if( vips_premultiply( in, &t[3], NULL ) ) return( -1 ); have_premultiplied = TRUE; @@ -680,6 +688,13 @@ vips_affine_class_init( VipsAffineClass *class ) G_STRUCT_OFFSET( VipsAffine, background ), VIPS_TYPE_ARRAY_DOUBLE ); + VIPS_ARG_BOOL( class, "premultiplied", 117, + _( "Premultiplied" ), + _( "Images have premultiplied alpha" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsAffine, premultiplied ), + FALSE ); + } static void @@ -709,6 +724,7 @@ vips_affine_init( VipsAffine *affine ) * * @ody: %gdouble, output vertical offset * * @extend: #VipsExtend how to generate new pixels * * @background: #VipsArrayDouble colour for new pixels + * * @premultiplied: %gboolean, images are already premultiplied * * This operator performs an affine transform on an image using @interpolate. * @@ -737,6 +753,10 @@ vips_affine_init( VipsAffine *affine ) * * @idx, @idy, @odx, @ody default to zero. * + * Image are normally treated as unpremultiplied, so this operation can be used + * directly on PNG images. If your images have been through vips_premultiply(), + * set @premultiplied. + * * This operation does not change xres or yres. The image resolution needs to * be updated by the application. * diff --git a/libvips/resample/bicubic.cpp b/libvips/resample/bicubic.cpp index 97e4a403..7388ab06 100644 --- a/libvips/resample/bicubic.cpp +++ b/libvips/resample/bicubic.cpp @@ -79,16 +79,15 @@ typedef VipsInterpolate VipsInterpolateBicubic; typedef VipsInterpolateClass VipsInterpolateBicubicClass; /* Precalculated interpolation matrices. int (used for pel - * sizes up to short), and double (for all others). We go to - * scale + 1 so we can round-to-nearest safely. + * sizes up to short), and double (for all others). */ /* We could keep a large set of 2d 4x4 matricies, but this actually * works out slower since for many resizes the thing will no longer * fit in L1. */ -static int vips_bicubic_matrixi[VIPS_TRANSFORM_SCALE + 1][4]; -static double vips_bicubic_matrixf[VIPS_TRANSFORM_SCALE + 1][4]; +static int vips_bicubic_matrixi[VIPS_TRANSFORM_SCALE][4]; +static double vips_bicubic_matrixf[VIPS_TRANSFORM_SCALE][4]; /* We need C linkage for this. */ @@ -498,19 +497,13 @@ static void vips_interpolate_bicubic_interpolate( VipsInterpolate *interpolate, void *out, VipsRegion *in, double x, double y ) { - /* Find the mask index. We round-to-nearest, so we need to generate - * indexes in 0 to VIPS_TRANSFORM_SCALE, 2^n + 1 values. We multiply - * by 2 more than we need to, add one, mask, then shift down again to - * get the extra range. + /* Find the mask index. */ - const int sx = x * VIPS_TRANSFORM_SCALE * 2; - const int sy = y * VIPS_TRANSFORM_SCALE * 2; + const int sx = x * VIPS_TRANSFORM_SCALE; + const int sy = y * VIPS_TRANSFORM_SCALE; - const int six = sx & (VIPS_TRANSFORM_SCALE * 2 - 1); - const int siy = sy & (VIPS_TRANSFORM_SCALE * 2 - 1); - - const int tx = (six + 1) >> 1; - const int ty = (siy + 1) >> 1; + const int tx = sx & (VIPS_TRANSFORM_SCALE - 1); + const int ty = sy & (VIPS_TRANSFORM_SCALE - 1); /* We know x/y are always positive, so we can just (int) them. */ @@ -643,7 +636,7 @@ vips_interpolate_bicubic_class_init( VipsInterpolateBicubicClass *iclass ) /* Build the tables of pre-computed coefficients. */ - for( int x = 0; x < VIPS_TRANSFORM_SCALE + 1; x++ ) { + for( int x = 0; x < VIPS_TRANSFORM_SCALE; x++ ) { calculate_coefficients_catmull( vips_bicubic_matrixf[x], (float) x / VIPS_TRANSFORM_SCALE ); diff --git a/libvips/resample/quadratic.c b/libvips/resample/quadratic.c index 06363f1a..bb6c0c4e 100644 --- a/libvips/resample/quadratic.c +++ b/libvips/resample/quadratic.c @@ -241,7 +241,6 @@ vips_quadratic_build( VipsObject *object ) VipsResample *resample = VIPS_RESAMPLE( object ); VipsQuadratic *quadratic = (VipsQuadratic *) object; - VipsInterpolate *interpolate; int window_size; int window_offset; VipsImage *in; @@ -294,12 +293,11 @@ vips_quadratic_build( VipsObject *object ) return( -1 ); } - if( !vips_object_argument_isset( object, "interpolator" ) ) + if( !quadratic->interpolate ) quadratic->interpolate = vips_interpolate_new( "bilinear" ); - interpolate = quadratic->interpolate; - window_size = vips_interpolate_get_window_size( interpolate ); - window_offset = vips_interpolate_get_window_offset( interpolate ); + window_size = vips_interpolate_get_window_size( quadratic->interpolate ); + window_offset = vips_interpolate_get_window_offset( quadratic->interpolate ); /* Enlarge the input image. */ diff --git a/libvips/resample/reduce.c b/libvips/resample/reduce.c index b71ea36d..c5e1bed4 100644 --- a/libvips/resample/reduce.c +++ b/libvips/resample/reduce.c @@ -6,6 +6,8 @@ * - rename xshrink -> hshrink for greater consistency * 9/9/16 * - add @centre option + * 6/6/20 kleisauke + * - deprecate @centre option, it's now always on */ /* @@ -78,14 +80,10 @@ * Optional arguments: * * * @kernel: #VipsKernel to use to interpolate (default: lanczos3) - * * @centre: %gboolean use centre rather than corner sampling convention * * Reduce @in vertically by a float factor. The pixels in @out are * interpolated with a 1D mask generated by @kernel. * - * Set @centre to use centre rather than corner sampling convention. Centre - * convention can be useful to match the behaviour of other systems. - * * This is a very low-level operation: see vips_resize() for a more * convenient way to resize images. * @@ -107,14 +105,10 @@ * Optional arguments: * * * @kernel: #VipsKernel to use to interpolate (default: lanczos3) - * * @centre: %gboolean use centre rather than corner sampling convention * * Reduce @in horizontally by a float factor. The pixels in @out are * interpolated with a 1D mask generated by @kernel. * - * Set @centre to use centre rather than corner sampling convention. Centre - * convention can be useful to match the behaviour of other systems. - * * This is a very low-level operation: see vips_resize() for a more * convenient way to resize images. * @@ -136,7 +130,7 @@ typedef struct _VipsReduce { */ VipsKernel kernel; - /* Use centre rather than corner sampling convention. + /* Deprecated. */ gboolean centre; @@ -152,18 +146,16 @@ vips_reduce_build( VipsObject *object ) VipsResample *resample = VIPS_RESAMPLE( object ); VipsReduce *reduce = (VipsReduce *) object; VipsImage **t = (VipsImage **) - vips_object_local_array( object, 3 ); + vips_object_local_array( object, 2 ); if( VIPS_OBJECT_CLASS( vips_reduce_parent_class )->build( object ) ) return( -1 ); if( vips_reducev( resample->in, &t[0], reduce->vshrink, "kernel", reduce->kernel, - "centre", reduce->centre, NULL ) || vips_reduceh( t[0], &t[1], reduce->hshrink, "kernel", reduce->kernel, - "centre", reduce->centre, NULL ) || vips_image_write( t[1], resample->out ) ) return( -1 ); @@ -210,13 +202,6 @@ vips_reduce_class_init( VipsReduceClass *class ) G_STRUCT_OFFSET( VipsReduce, kernel ), VIPS_TYPE_KERNEL, VIPS_KERNEL_LANCZOS3 ); - VIPS_ARG_BOOL( class, "centre", 7, - _( "Centre" ), - _( "Use centre sampling convention" ), - VIPS_ARGUMENT_OPTIONAL_INPUT, - G_STRUCT_OFFSET( VipsReduce, centre ), - FALSE ); - /* The old names .. now use h and v everywhere. */ VIPS_ARG_DOUBLE( class, "xshrink", 8, @@ -233,6 +218,15 @@ vips_reduce_class_init( VipsReduceClass *class ) G_STRUCT_OFFSET( VipsReduce, vshrink ), 1.0, 1000000.0, 1.0 ); + /* We used to let people pick centre or corner, but it's automatic now. + */ + VIPS_ARG_BOOL( class, "centre", 7, + _( "Centre" ), + _( "Use centre sampling convention" ), + VIPS_ARGUMENT_OPTIONAL_INPUT | VIPS_ARGUMENT_DEPRECATED, + G_STRUCT_OFFSET( VipsReduce, centre ), + FALSE ); + } static void @@ -252,14 +246,10 @@ vips_reduce_init( VipsReduce *reduce ) * Optional arguments: * * * @kernel: #VipsKernel to use to interpolate (default: lanczos3) - * * @centre: %gboolean use centre rather than corner sampling convention * * Reduce @in by a pair of factors with a pair of 1D kernels. This * will not work well for shrink factors greater than three. * - * Set @centre to use centre rather than corner sampling convention. Centre - * convention can be useful to match the behaviour of other systems. - * * This is a very low-level operation: see vips_resize() for a more * convenient way to resize images. * diff --git a/libvips/resample/reduceh.cpp b/libvips/resample/reduceh.cpp index 91c9c613..cc829c01 100644 --- a/libvips/resample/reduceh.cpp +++ b/libvips/resample/reduceh.cpp @@ -8,6 +8,10 @@ * - rename xshrink as hshrink for consistency * 9/9/16 * - add @centre option + * 6/6/20 kleisauke + * - deprecate @centre option, it's now always on + * - fix pixel shift + * - remove unnecessary round-to-nearest behaviour */ /* @@ -66,20 +70,23 @@ typedef struct _VipsReduceh { */ VipsKernel kernel; - /* Use centre rather than corner sampling convention. - */ - gboolean centre; - /* Number of points in kernel. */ int n_point; - /* Precalculated interpolation matrices. int (used for pel - * sizes up to short), and double (for all others). We go to - * scale + 1 so we can round-to-nearest safely. + /* Horizontal displacement. */ - int *matrixi[VIPS_TRANSFORM_SCALE + 1]; - double *matrixf[VIPS_TRANSFORM_SCALE + 1]; + double hoffset; + + /* Precalculated interpolation matrices. int (used for pel + * sizes up to short), and double (for all others). + */ + int *matrixi[VIPS_TRANSFORM_SCALE]; + double *matrixf[VIPS_TRANSFORM_SCALE]; + + /* Deprecated. + */ + gboolean centre; } VipsReduceh; @@ -101,19 +108,19 @@ vips_reduce_get_points( VipsKernel kernel, double shrink ) return( 1 ); case VIPS_KERNEL_LINEAR: - return( rint( 2 * shrink ) + 1 ); + return( 2 * rint( shrink ) + 1 ); case VIPS_KERNEL_CUBIC: case VIPS_KERNEL_MITCHELL: - return( rint( 4 * shrink ) + 1 ); + return( 2 * rint( 2 * shrink ) + 1 ); case VIPS_KERNEL_LANCZOS2: /* Needs to be in sync with calculate_coefficients_lanczos(). */ - return( rint( 2 * 2 * shrink ) + 1 ); + return( 2 * rint( 2 * shrink ) + 1 ); case VIPS_KERNEL_LANCZOS3: - return( rint( 2 * 3 * shrink ) + 1 ); + return( 2 * rint( 3 * shrink ) + 1 ); default: g_assert_not_reached(); @@ -194,13 +201,11 @@ reduceh_signed_int_tab( VipsReduceh *reduceh, for( int z = 0; z < bands; z++ ) { int sum; - sum = reduce_sum( in, bands, cx, n ); + sum = reduce_sum( in + z, bands, cx, n ); sum = signed_fixed_round( sum ); sum = VIPS_CLIP( min_value, sum, max_value ); out[z] = sum; - - in += 1; } } @@ -216,10 +221,8 @@ reduceh_float_tab( VipsReduceh *reduceh, const T* restrict in = (T *) pin; const int n = reduceh->n_point; - for( int z = 0; z < bands; z++ ) { - out[z] = reduce_sum( in, bands, cx, n ); - in += 1; - } + for( int z = 0; z < bands; z++ ) + out[z] = reduce_sum( in + z, bands, cx, n ); } /* 32-bit int output needs a double intermediate. @@ -238,10 +241,8 @@ reduceh_unsigned_int32_tab( VipsReduceh *reduceh, for( int z = 0; z < bands; z++ ) { double sum; - sum = reduce_sum( in, bands, cx, n ); - out[z] = VIPS_CLIP( 0, sum, max_value ); - - in += 1; + sum = reduce_sum( in + z, bands, cx, n ); + out[z] = VIPS_CLIP( 0, sum, max_value ); } } @@ -258,11 +259,9 @@ reduceh_signed_int32_tab( VipsReduceh *reduceh, for( int z = 0; z < bands; z++ ) { double sum; - sum = reduce_sum( in, bands, cx, n ); + sum = reduce_sum( in + z, bands, cx, n ); sum = VIPS_CLIP( min_value, sum, max_value ); out[z] = sum; - - in += 1; } } @@ -282,11 +281,8 @@ reduceh_notab( VipsReduceh *reduceh, vips_reduce_make_mask( cx, reduceh->kernel, reduceh->hshrink, x ); - for( int z = 0; z < bands; z++ ) { - out[z] = reduce_sum( in, bands, cx, n ); - - in += 1; - } + for( int z = 0; z < bands; z++ ) + out[z] = reduce_sum( in + z, bands, cx, n ); } /* Tried a vector path (see reducev) but it was slower. The vectors for @@ -315,18 +311,16 @@ vips_reduceh_gen( VipsRegion *out_region, void *seq, r->width, r->height, r->left, r->top ); #endif /*DEBUG*/ - s.left = r->left * reduceh->hshrink; + s.left = r->left * reduceh->hshrink - reduceh->hoffset; s.top = r->top; s.width = r->width * reduceh->hshrink + reduceh->n_point; s.height = r->height; - if( reduceh->centre ) - s.width += 1; if( vips_region_prepare( ir, &s ) ) return( -1 ); VIPS_GATE_START( "vips_reduceh_gen: work" ); - for( int y = 0; y < r->height; y ++ ) { + for( int y = 0; y < r->height; y++ ) { VipsPel *p0; VipsPel *q; @@ -334,9 +328,8 @@ vips_reduceh_gen( VipsRegion *out_region, void *seq, q = VIPS_REGION_ADDR( out_region, r->left, r->top + y ); - X = r->left * reduceh->hshrink; - if( reduceh->centre ) - X += 0.5; + X = (r->left + 0.5) * reduceh->hshrink - 0.5 - + reduceh->hoffset; /* We want p0 to be the start (ie. x == 0) of the input * scanline we are reading from. We can then calculate the p we @@ -351,11 +344,10 @@ vips_reduceh_gen( VipsRegion *out_region, void *seq, ir->valid.left * ps; for( int x = 0; x < r->width; x++ ) { - int ix = (int) X; + const int ix = (int) X; VipsPel *p = p0 + ix * ps; - const int sx = X * VIPS_TRANSFORM_SCALE * 2; - const int six = sx & (VIPS_TRANSFORM_SCALE * 2 - 1); - const int tx = (six + 1) >> 1; + const int sx = X * VIPS_TRANSFORM_SCALE; + const int tx = sx & (VIPS_TRANSFORM_SCALE - 1); const int *cxi = reduceh->matrixi[tx]; const double *cxf = reduceh->matrixf[tx]; @@ -441,7 +433,7 @@ vips_reduceh_build( VipsObject *object ) vips_object_local_array( object, 2 ); VipsImage *in; - int width; + double width, extra_pixels; if( VIPS_OBJECT_CLASS( vips_reduceh_parent_class )->build( object ) ) return( -1 ); @@ -457,8 +449,6 @@ vips_reduceh_build( VipsObject *object ) if( reduceh->hshrink == 1 ) return( vips_image_write( in, resample->out ) ); - /* Build the tables of pre-computed coefficients. - */ reduceh->n_point = vips_reduce_get_points( reduceh->kernel, reduceh->hshrink ); g_info( "reduceh: %d point mask", reduceh->n_point ); @@ -467,7 +457,29 @@ vips_reduceh_build( VipsObject *object ) "%s", _( "reduce factor too large" ) ); return( -1 ); } - for( int x = 0; x < VIPS_TRANSFORM_SCALE + 1; x++ ) { + + /* Output size. We need to always round to nearest, so round(), not + * rint(). + */ + width = VIPS_ROUND_UINT( + (double) resample->in->Xsize / reduceh->hshrink ); + + /* How many pixels we are inventing in the input, -ve for + * discarding. + */ + extra_pixels = + width * reduceh->hshrink - resample->in->Xsize; + + /* If we are rounding down, we are not using some input + * pixels. We need to move the origin *inside* the input image + * by half that distance so that we discard pixels equally + * from left and right. + */ + reduceh->hoffset = (1 + extra_pixels) / 2.0; + + /* Build the tables of pre-computed coefficients. + */ + for( int x = 0; x < VIPS_TRANSFORM_SCALE; x++ ) { reduceh->matrixf[x] = VIPS_ARRAY( object, reduceh->n_point, double ); reduceh->matrixi[x] = @@ -485,7 +497,7 @@ vips_reduceh_build( VipsObject *object ) VIPS_INTERPOLATE_SCALE; #ifdef DEBUG - printf( "vips_reduceh_build: mask %d\n ", x ); + printf( "vips_reduceh_build: mask %d\n ", x ); for( int i = 0; i < reduceh->n_point; i++ ) printf( "%d ", reduceh->matrixi[x][i] ); printf( "\n" ); @@ -499,15 +511,10 @@ vips_reduceh_build( VipsObject *object ) in = t[0]; /* Add new pixels around the input so we can interpolate at the edges. - * In centre mode, we read 0.5 pixels more to the right, so we must - * enlarge a little further. */ - width = in->Xsize + reduceh->n_point - 1; - if( reduceh->centre ) - width += 1; if( vips_embed( in, &t[1], reduceh->n_point / 2 - 1, 0, - width, in->Ysize, + in->Xsize + reduceh->n_point, in->Ysize, "extend", VIPS_EXTEND_COPY, (void *) NULL ) ) return( -1 ); @@ -524,8 +531,7 @@ vips_reduceh_build( VipsObject *object ) * example, vipsthumbnail knows the true reduce factor (including the * fractional part), we just see the integer part here. */ - resample->out->Xsize = VIPS_ROUND_UINT( - resample->in->Xsize / reduceh->hshrink ); + resample->out->Xsize = width; if( resample->out->Xsize <= 0 ) { vips_error( object_class->nickname, "%s", _( "image has shrunk to nothing" ) ); @@ -574,20 +580,13 @@ vips_reduceh_class_init( VipsReducehClass *reduceh_class ) G_STRUCT_OFFSET( VipsReduceh, hshrink ), 1, 1000000, 1 ); - VIPS_ARG_ENUM( reduceh_class, "kernel", 3, + VIPS_ARG_ENUM( reduceh_class, "kernel", 4, _( "Kernel" ), _( "Resampling kernel" ), VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET( VipsReduceh, kernel ), VIPS_TYPE_KERNEL, VIPS_KERNEL_LANCZOS3 ); - VIPS_ARG_BOOL( reduceh_class, "centre", 7, - _( "Centre" ), - _( "Use centre sampling convention" ), - VIPS_ARGUMENT_OPTIONAL_INPUT, - G_STRUCT_OFFSET( VipsReduceh, centre ), - FALSE ); - /* Old name. */ VIPS_ARG_DOUBLE( reduceh_class, "xshrink", 3, @@ -597,6 +596,15 @@ vips_reduceh_class_init( VipsReducehClass *reduceh_class ) G_STRUCT_OFFSET( VipsReduceh, hshrink ), 1, 1000000, 1 ); + /* We used to let people pick centre or corner, but it's automatic now. + */ + VIPS_ARG_BOOL( reduceh_class, "centre", 7, + _( "Centre" ), + _( "Use centre sampling convention" ), + VIPS_ARGUMENT_OPTIONAL_INPUT | VIPS_ARGUMENT_DEPRECATED, + G_STRUCT_OFFSET( VipsReduceh, centre ), + FALSE ); + } static void diff --git a/libvips/resample/reducev.cpp b/libvips/resample/reducev.cpp index a64a052e..396069f2 100644 --- a/libvips/resample/reducev.cpp +++ b/libvips/resample/reducev.cpp @@ -17,6 +17,11 @@ * - add @centre option * 7/3/17 * - add a seq line cache + * 6/6/20 kleisauke + * - deprecate @centre option, it's now always on + * - fix pixel shift + * - speed up the mask construction for uchar/ushort images + * - remove unnecessary round-to-nearest behaviour */ /* @@ -104,30 +109,33 @@ typedef struct _VipsReducev { */ VipsKernel kernel; - /* Use centre rather than corner sampling convention. - */ - gboolean centre; - /* Number of points in kernel. */ int n_point; - /* Precalculated interpolation matrices. int (used for pel - * sizes up to short), and double (for all others). We go to - * scale + 1 so we can round-to-nearest safely. + /* Vertical displacement. */ - int *matrixi[VIPS_TRANSFORM_SCALE + 1]; - double *matrixf[VIPS_TRANSFORM_SCALE + 1]; + double voffset; + + /* Precalculated interpolation matrices. int (used for pel + * sizes up to short), and double (for all others). + */ + int *matrixi[VIPS_TRANSFORM_SCALE]; + double *matrixf[VIPS_TRANSFORM_SCALE]; /* And another set for orc: we want 2.6 precision. */ - int *matrixo[VIPS_TRANSFORM_SCALE + 1]; + int *matrixo[VIPS_TRANSFORM_SCALE]; /* The passes we generate for this mask. */ int n_pass; Pass pass[MAX_PASS]; + /* Deprecated. + */ + gboolean centre; + } VipsReducev; typedef VipsResampleClass VipsReducevClass; @@ -146,7 +154,7 @@ vips_reducev_finalize( GObject *gobject ) for( int i = 0; i < reducev->n_pass; i++ ) VIPS_FREEF( vips_vector_free, reducev->pass[i].vector ); reducev->n_pass = 0; - for( int i = 0; i < VIPS_TRANSFORM_SCALE + 1; i++ ) { + for( int i = 0; i < VIPS_TRANSFORM_SCALE; i++ ) { VIPS_FREE( reducev->matrixf[i] ); VIPS_FREE( reducev->matrixi[i] ); VIPS_FREE( reducev->matrixo[i] ); @@ -531,25 +539,24 @@ vips_reducev_gen( VipsRegion *out_region, void *vseq, #endif /*DEBUG*/ s.left = r->left; - s.top = r->top * reducev->vshrink; + s.top = r->top * reducev->vshrink - reducev->voffset; s.width = r->width; s.height = r->height * reducev->vshrink + reducev->n_point; - if( reducev->centre ) - s.height += 1; if( vips_region_prepare( ir, &s ) ) return( -1 ); VIPS_GATE_START( "vips_reducev_gen: work" ); - for( int y = 0; y < r->height; y ++ ) { + double Y = (r->top + 0.5) * reducev->vshrink - 0.5 - + reducev->voffset; + + for( int y = 0; y < r->height; y++ ) { VipsPel *q = VIPS_REGION_ADDR( out_region, r->left, r->top + y ); - const double Y = (r->top + y) * reducev->vshrink + - (reducev->centre ? 0.5 : 0.0); - VipsPel *p = VIPS_REGION_ADDR( ir, r->left, (int) Y ); - const int sy = Y * VIPS_TRANSFORM_SCALE * 2; - const int siy = sy & (VIPS_TRANSFORM_SCALE * 2 - 1); - const int ty = (siy + 1) >> 1; + const int py = (int) Y; + VipsPel *p = VIPS_REGION_ADDR( ir, r->left, py ); + const int sy = Y * VIPS_TRANSFORM_SCALE; + const int ty = sy & (VIPS_TRANSFORM_SCALE - 1); const int *cyi = reducev->matrixi[ty]; const double *cyf = reducev->matrixf[ty]; const int lskip = VIPS_REGION_LSKIP( ir ); @@ -606,13 +613,15 @@ vips_reducev_gen( VipsRegion *out_region, void *vseq, case VIPS_FORMAT_DPCOMPLEX: case VIPS_FORMAT_DOUBLE: reducev_notab( reducev, - q, p, ne, lskip, Y - (int) Y ); + q, p, ne, lskip, Y - py ); break; default: g_assert_not_reached(); break; } + + Y += reducev->vshrink; } VIPS_GATE_STOP( "vips_reducev_gen: work" ); @@ -644,11 +653,9 @@ vips_reducev_vector_gen( VipsRegion *out_region, void *vseq, #endif /*DEBUG_PIXELS*/ s.left = r->left; - s.top = r->top * reducev->vshrink; + s.top = r->top * reducev->vshrink - reducev->voffset; s.width = r->width; s.height = r->height * reducev->vshrink + reducev->n_point; - if( reducev->centre ) - s.height += 1; if( vips_region_prepare( ir, &s ) ) return( -1 ); @@ -663,15 +670,15 @@ vips_reducev_vector_gen( VipsRegion *out_region, void *vseq, VIPS_GATE_START( "vips_reducev_vector_gen: work" ); - for( int y = 0; y < r->height; y ++ ) { + double Y = (r->top + 0.5) * reducev->vshrink - 0.5 - + reducev->voffset; + + for( int y = 0; y < r->height; y++ ) { VipsPel *q = VIPS_REGION_ADDR( out_region, r->left, r->top + y ); - const double Y = (r->top + y) * reducev->vshrink + - (reducev->centre ? 0.5 : 0.0); - const int py = (int) Y; - const int sy = Y * VIPS_TRANSFORM_SCALE * 2; - const int siy = sy & (VIPS_TRANSFORM_SCALE * 2 - 1); - const int ty = (siy + 1) >> 1; + const int py = (int) Y; + const int sy = Y * VIPS_TRANSFORM_SCALE; + const int ty = sy & (VIPS_TRANSFORM_SCALE - 1); const int *cyo = reducev->matrixo[ty]; #ifdef DEBUG_PIXELS @@ -682,7 +689,7 @@ vips_reducev_vector_gen( VipsRegion *out_region, void *vseq, printf( "first column of pixel values:\n" ); for( int i = 0; i < reducev->n_point; i++ ) printf( "\t%d - %d\n", i, - *VIPS_REGION_ADDR( ir, r->left, r->top + y + i ) ); + *VIPS_REGION_ADDR( ir, r->left, py ) ); #endif /*DEBUG_PIXELS*/ /* We run our n passes to generate this scanline. @@ -709,6 +716,8 @@ vips_reducev_vector_gen( VipsRegion *out_region, void *vseq, printf( "pixel result:\n" ); printf( "\t%d\n", *q ); #endif /*DEBUG_PIXELS*/ + + Y += reducev->vshrink; } VIPS_GATE_STOP( "vips_reducev_vector_gen: work" ); @@ -726,45 +735,11 @@ vips_reducev_raw( VipsReducev *reducev, VipsImage *in, VipsImage **out ) VipsGenerateFn generate; - /* Build masks. - */ - for( int y = 0; y < VIPS_TRANSFORM_SCALE + 1; y++ ) { - reducev->matrixf[y] = - VIPS_ARRAY( NULL, reducev->n_point, double ); - if( !reducev->matrixf[y] ) - return( -1 ); - - vips_reduce_make_mask( reducev->matrixf[y], - reducev->kernel, reducev->vshrink, - (float) y / VIPS_TRANSFORM_SCALE ); - -#ifdef DEBUG - printf( "%6.2g", (double) y / VIPS_TRANSFORM_SCALE ); - for( int i = 0; i < reducev->n_point; i++ ) - printf( ", %6.2g", reducev->matrixf[y][i] ); - printf( "\n" ); -#endif /*DEBUG*/ - } - - /* uchar and ushort need an int version of the masks. - */ - if( VIPS_IMAGE_SIZEOF_ELEMENT( in ) <= 2 ) - for( int y = 0; y < VIPS_TRANSFORM_SCALE + 1; y++ ) { - reducev->matrixi[y] = - VIPS_ARRAY( NULL, reducev->n_point, int ); - if( !reducev->matrixi[y] ) - return( -1 ); - - vips_vector_to_fixed_point( - reducev->matrixf[y], reducev->matrixi[y], - reducev->n_point, VIPS_INTERPOLATE_SCALE ); - } - - /* And we need an 2.6 version if we will use the vector path. + /* We need an 2.6 version if we will use the vector path. */ if( in->BandFmt == VIPS_FORMAT_UCHAR && vips_vector_isenabled() ) - for( int y = 0; y < VIPS_TRANSFORM_SCALE + 1; y++ ) { + for( int y = 0; y < VIPS_TRANSFORM_SCALE; y++ ) { reducev->matrixo[y] = VIPS_ARRAY( NULL, reducev->n_point, int ); if( !reducev->matrixo[y] ) @@ -787,7 +762,7 @@ vips_reducev_raw( VipsReducev *reducev, VipsImage *in, VipsImage **out ) *out = vips_image_new(); if( vips_image_pipelinev( *out, - VIPS_DEMAND_STYLE_FATSTRIP, in, (void *) NULL ) ) + VIPS_DEMAND_STYLE_THINSTRIP, in, (void *) NULL ) ) return( -1 ); /* Size output. We need to always round to nearest, so round(), not @@ -830,7 +805,7 @@ vips_reducev_build( VipsObject *object ) VipsImage **t = (VipsImage **) vips_object_local_array( object, 4 ); VipsImage *in; - int height; + double height, extra_pixels; if( VIPS_OBJECT_CLASS( vips_reducev_parent_class )->build( object ) ) return( -1 ); @@ -855,6 +830,52 @@ vips_reducev_build( VipsObject *object ) return( -1 ); } + /* Output size. We need to always round to nearest, so round(), not + * rint(). + */ + height = VIPS_ROUND_UINT( + (double) resample->in->Ysize / reducev->vshrink ); + + /* How many pixels we are inventing in the input, -ve for + * discarding. + */ + extra_pixels = + height * reducev->vshrink - resample->in->Ysize; + + /* If we are rounding down, we are not using some input + * pixels. We need to move the origin *inside* the input image + * by half that distance so that we discard pixels equally + * from left and right. + */ + reducev->voffset = (1 + extra_pixels) / 2.0; + + /* Build the tables of pre-computed coefficients. + */ + for( int y = 0; y < VIPS_TRANSFORM_SCALE; y++ ) { + reducev->matrixf[y] = + VIPS_ARRAY( NULL, reducev->n_point, double ); + reducev->matrixi[y] = + VIPS_ARRAY( NULL, reducev->n_point, int ); + if( !reducev->matrixf[y] || + !reducev->matrixi[y] ) + return( -1 ); + + vips_reduce_make_mask( reducev->matrixf[y], + reducev->kernel, reducev->vshrink, + (float) y / VIPS_TRANSFORM_SCALE ); + + for( int i = 0; i < reducev->n_point; i++ ) + reducev->matrixi[y][i] = reducev->matrixf[y][i] * + VIPS_INTERPOLATE_SCALE; + +#ifdef DEBUG + printf( "vips_reducev_build: mask %d\n ", y ); + for( int i = 0; i < reducev->n_point; i++ ) + printf( "%d ", reducev->matrixi[y][i] ); + printf( "\n" ); +#endif /*DEBUG*/ + } + /* Unpack for processing. */ if( vips_image_decode( in, &t[0] ) ) @@ -863,12 +884,9 @@ vips_reducev_build( VipsObject *object ) /* Add new pixels around the input so we can interpolate at the edges. */ - height = in->Ysize + reducev->n_point - 1; - if( reducev->centre ) - height += 1; if( vips_embed( in, &t[1], 0, reducev->n_point / 2 - 1, - in->Xsize, height, + in->Xsize, in->Ysize + reducev->n_point, "extend", VIPS_EXTEND_COPY, (void *) NULL ) ) return( -1 ); @@ -939,13 +957,6 @@ vips_reducev_class_init( VipsReducevClass *reducev_class ) G_STRUCT_OFFSET( VipsReducev, kernel ), VIPS_TYPE_KERNEL, VIPS_KERNEL_LANCZOS3 ); - VIPS_ARG_BOOL( reducev_class, "centre", 7, - _( "Centre" ), - _( "Use centre sampling convention" ), - VIPS_ARGUMENT_OPTIONAL_INPUT, - G_STRUCT_OFFSET( VipsReducev, centre ), - FALSE ); - /* Old name. */ VIPS_ARG_DOUBLE( reducev_class, "yshrink", 3, @@ -955,6 +966,15 @@ vips_reducev_class_init( VipsReducevClass *reducev_class ) G_STRUCT_OFFSET( VipsReducev, vshrink ), 1, 1000000, 1 ); + /* We used to let people pick centre or corner, but it's automatic now. + */ + VIPS_ARG_BOOL( reducev_class, "centre", 7, + _( "Centre" ), + _( "Use centre sampling convention" ), + VIPS_ARGUMENT_OPTIONAL_INPUT | VIPS_ARGUMENT_DEPRECATED, + G_STRUCT_OFFSET( VipsReducev, centre ), + FALSE ); + } static void diff --git a/libvips/resample/resample.c b/libvips/resample/resample.c index 1a48c9c8..c5ba7f7a 100644 --- a/libvips/resample/resample.c +++ b/libvips/resample/resample.c @@ -68,7 +68,7 @@ * * vips_reduce() is like vips_affine(), but it can only shrink images, it can't * enlarge, rotate, or skew. It's very fast and uses an adaptive kernel for - * interpolation. Again, it will give poor results for large size reductions. + * interpolation. * * vips_shrink() is a fast block shrinker. It can quickly reduce images by * large integer factors. It will give poor results for small size reductions: @@ -157,6 +157,7 @@ vips_resample_operation_init( void ) extern GType vips_thumbnail_file_get_type( void ); extern GType vips_thumbnail_buffer_get_type( void ); extern GType vips_thumbnail_image_get_type( void ); + extern GType vips_thumbnail_source_get_type( void ); extern GType vips_mapim_get_type( void ); extern GType vips_shrink_get_type( void ); extern GType vips_shrinkh_get_type( void ); @@ -173,6 +174,7 @@ vips_resample_operation_init( void ) vips_thumbnail_file_get_type(); vips_thumbnail_buffer_get_type(); vips_thumbnail_image_get_type(); + vips_thumbnail_source_get_type(); vips_mapim_get_type(); vips_shrink_get_type(); vips_shrinkh_get_type(); diff --git a/libvips/resample/resize.c b/libvips/resample/resize.c index e99ca551..4d185f62 100644 --- a/libvips/resample/resize.c +++ b/libvips/resample/resize.c @@ -33,6 +33,8 @@ * affine nearest interpolator is always centre * 7/7/19 [lovell] * - don't let either axis drop below 1px + * 12/7/20 + * - much better handling of "nearest" */ /* @@ -105,45 +107,6 @@ typedef VipsResampleClass VipsResizeClass; G_DEFINE_TYPE( VipsResize, vips_resize, VIPS_TYPE_RESAMPLE ); -/* How much of a scale should be by an integer shrink factor? - * - * This depends on the scale and the kernel we will use for residual resizing. - * For upsizing and nearest-neighbour downsize, we want no shrinking. - * - * The others are adaptive: the size of the kernel changes with the shrink - * factor. We will get the best quality (but be the slowest) if we let - * reduce do all the work. Leave it the final 200 - 300% to do as a compromise - * for efficiency. - * - * FIXME: this is rather ugly. Kernel should be a class and this info should be - * stored in there. - */ -static int -vips_resize_int_shrink( VipsResize *resize, double scale ) -{ - int shrink; - - if( scale > 1.0 ) - shrink = 1; - else - switch( resize->kernel ) { - case VIPS_KERNEL_NEAREST: - shrink = 1; - break; - - case VIPS_KERNEL_LINEAR: - case VIPS_KERNEL_CUBIC: - case VIPS_KERNEL_MITCHELL: - case VIPS_KERNEL_LANCZOS2: - case VIPS_KERNEL_LANCZOS3: - default: - shrink = VIPS_MAX( 1, VIPS_FLOOR( 1.0 / (scale * 2) ) ); - break; - } - - return( shrink ); -} - /* Suggest a VipsInterpolate which corresponds to a VipsKernel. We use * this to pick a thing for affine(). */ @@ -192,27 +155,49 @@ vips_resize_build( VipsObject *object ) else vscale = resize->scale; - /* The int part of our scale. + /* The int part of our scale. Leave the final 200 - 300% to reduce. */ - int_hshrink = vips_resize_int_shrink( resize, hscale ); - int_vshrink = vips_resize_int_shrink( resize, vscale ); + int_hshrink = VIPS_MAX( 1, VIPS_FLOOR( 1.0 / (hscale * 2) ) ); + int_vshrink = VIPS_MAX( 1, VIPS_FLOOR( 1.0 / (vscale * 2) ) ); - if( int_vshrink > 1 ) { - g_info( "shrinkv by %d", int_vshrink ); - if( vips_shrinkv( in, &t[0], int_vshrink, NULL ) ) - return( -1 ); - in = t[0]; + /* Unpack for processing. + */ + if( vips_image_decode( in, &t[5] ) ) + return( -1 ); + in = t[5]; - vscale *= int_vshrink; - } + if( resize->kernel == VIPS_KERNEL_NEAREST ) { + if( int_vshrink > 1 || + int_hshrink > 1 ) { + g_info( "subsample by %d, %d", + int_hshrink, int_vshrink ); + if( vips_subsample( in, &t[0], + int_hshrink, int_vshrink, NULL ) ) + return( -1 ); + in = t[0]; - if( int_hshrink > 1 ) { - g_info( "shrinkh by %d", int_hshrink ); - if( vips_shrinkh( in, &t[1], int_hshrink, NULL ) ) - return( -1 ); - in = t[1]; + hscale *= int_hshrink; + vscale *= int_vshrink; + } + } + else { + if( int_vshrink > 1 ) { + g_info( "shrinkv by %d", int_vshrink ); + if( vips_shrinkv( in, &t[0], int_vshrink, NULL ) ) + return( -1 ); + in = t[0]; - hscale *= int_hshrink; + vscale *= int_vshrink; + } + + if( int_hshrink > 1 ) { + g_info( "shrinkh by %d", int_hshrink ); + if( vips_shrinkh( in, &t[1], int_hshrink, NULL ) ) + return( -1 ); + in = t[1]; + + hscale *= int_hshrink; + } } /* Don't let either axis drop below 1 px. @@ -226,7 +211,6 @@ vips_resize_build( VipsObject *object ) g_info( "residual reducev by %g", vscale ); if( vips_reducev( in, &t[2], 1.0 / vscale, "kernel", resize->kernel, - "centre", TRUE, NULL ) ) return( -1 ); in = t[2]; @@ -237,7 +221,6 @@ vips_resize_build( VipsObject *object ) hscale ); if( vips_reduceh( in, &t[3], 1.0 / hscale, "kernel", resize->kernel, - "centre", TRUE, NULL ) ) return( -1 ); in = t[3]; @@ -283,6 +266,7 @@ vips_resize_build( VipsObject *object ) "idx", id, "idy", id, "extend", VIPS_EXTEND_COPY, + "premultiplied", TRUE, NULL ) ) return( -1 ); in = t[4]; @@ -294,6 +278,7 @@ vips_resize_build( VipsObject *object ) "idx", id, "idy", id, "extend", VIPS_EXTEND_COPY, + "premultiplied", TRUE, NULL ) ) return( -1 ); in = t[4]; @@ -305,6 +290,7 @@ vips_resize_build( VipsObject *object ) "idx", id, "idy", id, "extend", VIPS_EXTEND_COPY, + "premultiplied", TRUE, NULL ) ) return( -1 ); in = t[4]; @@ -438,7 +424,10 @@ vips_resize_init( VipsResize *resize ) * This operation does not change xres or yres. The image resolution needs to * be updated by the application. * - * See also: vips_shrink(), vips_reduce(). + * This operation does not premultiply alpha. If your image has an alpha + * channel, you should use vips_premultiply() on it first. + * + * See also: vips_premultiply(), vips_shrink(), vips_reduce(). * * Returns: 0 on success, -1 on error */ diff --git a/libvips/resample/shrinkh.c b/libvips/resample/shrinkh.c index d11e9345..cfa421ba 100644 --- a/libvips/resample/shrinkh.c +++ b/libvips/resample/shrinkh.c @@ -252,12 +252,6 @@ vips_shrinkh_build( VipsObject *object ) if( shrink->hshrink == 1 ) return( vips_image_write( in, resample->out ) ); - /* Unpack for processing. - */ - if( vips_image_decode( in, &t[0] ) ) - return( -1 ); - in = t[0]; - /* We need new pixels at the right so that we don't have small chunks * to average down the right edge. */ diff --git a/libvips/resample/shrinkv.c b/libvips/resample/shrinkv.c index 9a523982..d6a016a7 100644 --- a/libvips/resample/shrinkv.c +++ b/libvips/resample/shrinkv.c @@ -124,6 +124,8 @@ vips_shrinkv_stop( void *vseq, void *a, void *b ) VipsShrinkvSequence *seq = (VipsShrinkvSequence *) vseq; VIPS_FREEF( g_object_unref, seq->ir ); + VIPS_FREE( seq->sum ); + VIPS_FREE( seq ); return( 0 ); } @@ -137,14 +139,14 @@ vips_shrinkv_start( VipsImage *out, void *a, void *b ) VipsShrinkv *shrink = (VipsShrinkv *) b; VipsShrinkvSequence *seq; - if( !(seq = VIPS_NEW( out, VipsShrinkvSequence )) ) + if( !(seq = VIPS_NEW( NULL, VipsShrinkvSequence )) ) return( NULL ); seq->ir = vips_region_new( in ); /* Big enough for the largest intermediate .. a whole scanline. */ - seq->sum = VIPS_ARRAY( out, shrink->sizeof_line_buffer, VipsPel ); + seq->sum = VIPS_ARRAY( NULL, shrink->sizeof_line_buffer, VipsPel ); return( (void *) seq ); } @@ -345,12 +347,6 @@ vips_shrinkv_build( VipsObject *object ) if( shrink->vshrink == 1 ) return( vips_image_write( in, resample->out ) ); - /* Unpack for processing. - */ - if( vips_image_decode( in, &t[0] ) ) - return( -1 ); - in = t[0]; - /* Make the height a multiple of the shrink factor so we don't need to * average half pixels. */ diff --git a/libvips/resample/templates.h b/libvips/resample/templates.h index d7558bb2..d60144ba 100644 --- a/libvips/resample/templates.h +++ b/libvips/resample/templates.h @@ -321,14 +321,15 @@ calculate_coefficients_triangle( double *c, { /* Needs to be in sync with vips_reduce_get_points(). */ - const int n_points = rint( 2 * shrink ) + 1; + const int n_points = 2 * rint( shrink ) + 1; + const double half = x + n_points / 2.0 - 1; int i; double sum; sum = 0; for( i = 0; i < n_points; i++ ) { - double xp = (i - (shrink - 0.5) - x) / shrink; + const double xp = (i - half) / shrink; double l; @@ -358,14 +359,15 @@ calculate_coefficients_cubic( double *c, { /* Needs to be in sync with vips_reduce_get_points(). */ - const int n_points = rint( 4 * shrink ) + 1; + const int n_points = 2 * rint( 2 * shrink ) + 1; + const double half = x + n_points / 2.0 - 1; int i; double sum; sum = 0; for( i = 0; i < n_points; i++ ) { - const double xp = (i - (2 * shrink - 1) - x) / shrink; + const double xp = (i - half) / shrink; const double axp = VIPS_FABS( xp ); const double axp2 = axp * axp; const double axp3 = axp2 * axp; @@ -406,14 +408,15 @@ calculate_coefficients_lanczos( double *c, { /* Needs to be in sync with vips_reduce_get_points(). */ - const int n_points = rint( 2 * a * shrink ) + 1; + const int n_points = 2 * rint( a * shrink ) + 1; + const double half = x + n_points / 2.0 - 1; int i; double sum; sum = 0; for( i = 0; i < n_points; i++ ) { - double xp = (i - (n_points - 2) / 2 - x) / shrink; + const double xp = (i - half) / shrink; double l; diff --git a/libvips/resample/thumbnail.c b/libvips/resample/thumbnail.c index 93b0e4cb..acae217a 100644 --- a/libvips/resample/thumbnail.c +++ b/libvips/resample/thumbnail.c @@ -24,6 +24,12 @@ * - support multi-page (animated) images * 27/8/19 kleisauke * - prevent over-pre-shrink in thumbnail + * 30/9/19 + * - smarter heif thumbnail selection + * 12/10/19 + * - add thumbnail_source + * 2/6/20 + * - add subifd pyr support */ /* @@ -110,9 +116,11 @@ typedef struct _VipsThumbnail { int input_width; int input_height; int page_height; - VipsAngle angle; /* From vips_autorot_get_angle() */ + int orientation; /* From vips_image_get_orientation() */ + gboolean swap; /* If we must swap width / height */ int n_pages; /* Pages in file */ int n_loaded_pages; /* Pages we've loaded from file */ + int n_subifds; /* Number of subifds */ /* For openslide, we need to read out the size of each level too. * @@ -127,6 +135,10 @@ typedef struct _VipsThumbnail { int heif_thumbnail_width; int heif_thumbnail_height; + /* For TIFF sources, open subifds rather than pages to get pyr layers. + */ + gboolean subifd_pyramid; + } VipsThumbnail; typedef struct _VipsThumbnailClass { @@ -192,9 +204,11 @@ vips_thumbnail_read_header( VipsThumbnail *thumbnail, VipsImage *image ) { thumbnail->input_width = image->Xsize; thumbnail->input_height = image->Ysize; - thumbnail->angle = vips_autorot_get_angle( image ); + thumbnail->orientation = vips_image_get_orientation( image ); + thumbnail->swap = vips_image_get_orientation_swap( image ); thumbnail->page_height = vips_image_get_page_height( image ); thumbnail->n_pages = vips_image_get_n_pages( image ); + thumbnail->n_subifds = vips_image_get_n_subifds( image ); /* VIPS_META_N_PAGES is the number of pages in the document, * not the number we've read out into this image. We calculate @@ -231,16 +245,29 @@ vips_thumbnail_read_header( VipsThumbnail *thumbnail, VipsImage *image ) } } -/* This may not be a pyr tiff, so no error if we can't find the layers. - * We just look for two or more pages following roughly /2 shrinks. +/* Detect a TIFF pyramid made of pages following a roughly /2 shrink. + * + * This may not be a pyr tiff, so no error if we can't find the layers. */ static void -vips_thumbnail_get_tiff_pyramid( VipsThumbnail *thumbnail ) +vips_thumbnail_get_tiff_pyramid_page( VipsThumbnail *thumbnail ) { VipsThumbnailClass *class = VIPS_THUMBNAIL_GET_CLASS( thumbnail ); int i; - for( i = 0; i < thumbnail->n_pages; i++ ) { +#ifdef DEBUG + printf( "vips_thumbnail_get_tiff_pyramid_page:\n" ); +#endif /*DEBUG*/ + + /* Single-page docs can't be pyramids. + */ + if( thumbnail->n_loaded_pages < 2 ) + return; + + /* Use n_loaded_pages not n_pages since we support thumbnailing a page + * or range of pages from a many-page tiff. + */ + for( i = 0; i < thumbnail->n_loaded_pages; i++ ) { VipsImage *page; int level_width; int level_height; @@ -272,10 +299,66 @@ vips_thumbnail_get_tiff_pyramid( VipsThumbnail *thumbnail ) /* Now set level_count. This signals that we've found a pyramid. */ #ifdef DEBUG - printf( "vips_thumbnail_get_tiff_pyramid: %d layer pyramid detected\n", - thumbnail->n_pages ); + printf( "vips_thumbnail_get_tiff_pyramid_page: " + "%d layer pyramid detected\n", + thumbnail->n_loaded_pages ); #endif /*DEBUG*/ - thumbnail->level_count = thumbnail->n_pages; + thumbnail->level_count = thumbnail->n_loaded_pages; +} + +/* Detect a TIFF pyramid made of subifds following a roughly /2 shrink. + * + * This may not be a pyr tiff, so no error if we can't find the layers. + */ +static void +vips_thumbnail_get_tiff_pyramid_subifd( VipsThumbnail *thumbnail ) +{ + VipsThumbnailClass *class = VIPS_THUMBNAIL_GET_CLASS( thumbnail ); + int i; + +#ifdef DEBUG + printf( "vips_thumbnail_get_tiff_pyramid_subifd:\n" ); +#endif /*DEBUG*/ + + for( i = 0; i < thumbnail->n_subifds; i++ ) { + VipsImage *page; + int level_width; + int level_height; + int expected_level_width; + int expected_level_height; + + if( !(page = class->open( thumbnail, i )) ) + return; + level_width = page->Xsize; + level_height = page->Ysize; + VIPS_UNREF( page ); + + /* The main image is size 1, subifd 0 is half that. + */ + expected_level_width = thumbnail->input_width / (2 << i); + expected_level_height = thumbnail->input_height / (2 << i); + + /* This won't be exact due to rounding etc. + */ + if( abs( level_width - expected_level_width ) > 5 || + level_width < 2 ) + return; + if( abs( level_height - expected_level_height ) > 5 || + level_height < 2 ) + return; + + thumbnail->level_width[i] = level_width; + thumbnail->level_height[i] = level_height; + } + + /* Now set level_count. This signals that we've found a pyramid. + */ +#ifdef DEBUG + printf( "vips_thumbnail_get_tiff_pyramid_subifd: " + "%d layer pyramid detected\n", + thumbnail->n_subifds ); +#endif /*DEBUG*/ + thumbnail->level_count = thumbnail->n_subifds; } static int @@ -309,11 +392,10 @@ static void vips_thumbnail_calculate_shrink( VipsThumbnail *thumbnail, int input_width, int input_height, double *hshrink, double *vshrink ) { - /* If we will be rotating, swap the target width and height. + /* If we will be rotating, swap the target width and height. */ gboolean rotate = - (thumbnail->angle == VIPS_ANGLE_D90 || - thumbnail->angle == VIPS_ANGLE_D270) && + thumbnail->swap && thumbnail->auto_rotate; int target_width = rotate ? thumbnail->height : thumbnail->width; @@ -379,9 +461,7 @@ vips_thumbnail_calculate_common_shrink( VipsThumbnail *thumbnail, /* We don't want to shrink so much that we send an axis to 0. */ - if( shrink > thumbnail->input_width || - shrink > thumbnail->input_height ) - shrink = 1.0; + shrink = VIPS_MIN( shrink, VIPS_MIN( width, height ) ); return( shrink ); } @@ -457,11 +537,21 @@ vips_thumbnail_open( VipsThumbnail *thumbnail ) g_info( "input size is %d x %d", thumbnail->input_width, thumbnail->input_height ); - /* For tiff, we need a separate ->open() for each page to - * get all the pyramid levels. + /* For tiff, scan the image and try to spot page-based and ifd-based + * pyramids. */ - if( vips_isprefix( "VipsForeignLoadTiff", thumbnail->loader ) ) - vips_thumbnail_get_tiff_pyramid( thumbnail ); + if( vips_isprefix( "VipsForeignLoadTiff", thumbnail->loader ) ) { + /* Test for a subifd pyr first, since we can do that from just + * one page. + */ + thumbnail->subifd_pyramid = TRUE; + vips_thumbnail_get_tiff_pyramid_subifd( thumbnail ); + + if( thumbnail->level_count == 0 ) { + thumbnail->subifd_pyramid = FALSE; + vips_thumbnail_get_tiff_pyramid_page( thumbnail ); + } + } /* For heif, we need to fetch the thumbnail size, in case we can use * that as the source. @@ -480,26 +570,34 @@ vips_thumbnail_open( VipsThumbnail *thumbnail ) thumbnail->input_width, thumbnail->input_height ); else if( vips_isprefix( "VipsForeignLoadTiff", thumbnail->loader ) || vips_isprefix( "VipsForeignLoadOpenslide", - thumbnail->loader ) ) - factor = vips_thumbnail_find_pyrlevel( thumbnail, - thumbnail->input_width, thumbnail->input_height ); + thumbnail->loader ) ) { + if( thumbnail->level_count > 0 ) + factor = vips_thumbnail_find_pyrlevel( thumbnail, + thumbnail->input_width, + thumbnail->input_height ); + } else if( vips_isprefix( "VipsForeignLoadPdf", thumbnail->loader ) || vips_isprefix( "VipsForeignLoadWebp", thumbnail->loader ) || vips_isprefix( "VipsForeignLoadSvg", thumbnail->loader ) ) factor = vips_thumbnail_calculate_common_shrink( thumbnail, thumbnail->input_width, thumbnail->page_height ); - if( vips_isprefix( "VipsForeignLoadHeif", thumbnail->loader ) ) { + else if( vips_isprefix( "VipsForeignLoadHeif", thumbnail->loader ) ) { /* 'factor' is a gboolean which enables thumbnail load instead * of image load. * - * Use the thumbnail if it's larger than our target. + * Use the thumbnail if, by using it, we could get a factor > + * 1.0, ie. we would not need to expand the thumbnail. + * + * Don't use >= since factor can be clipped to 1.0 under some + * resizing modes. */ - if( thumbnail->heif_thumbnail_width >= thumbnail->width && - thumbnail->heif_thumbnail_height >= thumbnail->height ) - factor = 1.0; - else - factor = 0.0; + double shrink_factor = vips_thumbnail_calculate_common_shrink( + thumbnail, + thumbnail->heif_thumbnail_width, + thumbnail->heif_thumbnail_height ); + + factor = shrink_factor > 1.0 ? 1 : 0; } g_info( "loading with factor %g pre-shrink", factor ); @@ -516,15 +614,13 @@ static int vips_thumbnail_build( VipsObject *object ) { VipsThumbnail *thumbnail = VIPS_THUMBNAIL( object ); - VipsImage **t = (VipsImage **) vips_object_local_array( object, 13 ); - VipsInterpretation interpretation = thumbnail->linear ? - VIPS_INTERPRETATION_scRGB : VIPS_INTERPRETATION_sRGB; + VipsImage **t = (VipsImage **) vips_object_local_array( object, 15 ); VipsImage *in; int preshrunk_page_height; - int output_page_height; double hshrink; double vshrink; + VipsInterpretation interpretation; /* TRUE if we've done the import of an ICC transform and still need to * export. @@ -622,6 +718,12 @@ vips_thumbnail_build( VipsObject *object ) */ if( in->Type == VIPS_INTERPRETATION_CMYK ) have_imported = TRUE; + if( thumbnail->linear ) + interpretation = VIPS_INTERPRETATION_scRGB; + else if( in->Bands < 3 ) + interpretation = VIPS_INTERPRETATION_B_W; + else + interpretation = VIPS_INTERPRETATION_sRGB; g_info( "converting to processing space %s", vips_enum_nick( VIPS_TYPE_INTERPRETATION, interpretation ) ); if( vips_colourspace( in, &t[2], interpretation, NULL ) ) @@ -669,9 +771,20 @@ vips_thumbnail_build( VipsObject *object ) return( -1 ); in = t[4]; - output_page_height = VIPS_RINT( preshrunk_page_height / vshrink ); - vips_image_set_int( in, - VIPS_META_PAGE_HEIGHT, output_page_height ); + /* Only set page-height if we have more than one page, or this could + * accidentally turn into an animated image later. + */ + if( thumbnail->n_loaded_pages > 1 ) { + int output_page_height = + VIPS_RINT( preshrunk_page_height / vshrink ); + + if( vips_copy( in, &t[13], NULL ) ) + return( -1 ); + in = t[13]; + + vips_image_set_int( in, + VIPS_META_PAGE_HEIGHT, output_page_height ); + } if( have_premultiplied ) { g_info( "unpremultiplying alpha" ); @@ -725,20 +838,12 @@ vips_thumbnail_build( VipsObject *object ) } if( thumbnail->auto_rotate && - thumbnail->angle != VIPS_ANGLE_D0 ) { - VipsAngle angle = vips_autorot_get_angle( in ); - - g_info( "rotating by %s", - vips_enum_nick( VIPS_TYPE_ANGLE, angle ) ); - - /* Need to copy to memory, we have to stay seq. - */ - if( !(t[9] = vips_image_copy_memory( in )) || - vips_rot( t[9], &t[10], angle, NULL ) ) - return( -1 ); - in = t[10]; - - vips_autorot_remove_angle( in ); + thumbnail->orientation != 1 ) { + g_info( "rotating by EXIF orientation %d", + thumbnail->orientation ); + if( vips_autorot( in, &t[14], NULL ) ) + return( -1 ); + in = t[14]; } /* Crop after rotate so we don't need to rotate the crop box. @@ -944,10 +1049,24 @@ vips_thumbnail_file_open( VipsThumbnail *thumbnail, double factor ) NULL ) ); } else if( vips_isprefix( "VipsForeignLoadTiff", thumbnail->loader ) ) { - return( vips_image_new_from_file( file->filename, - "access", VIPS_ACCESS_SEQUENTIAL, - "page", (int) factor, - NULL ) ); + /* We support three modes: subifd pyramids, page-based + * pyramids, and simple multi-page TIFFs (no pyramid). + */ + if( thumbnail->subifd_pyramid ) + return( vips_image_new_from_file( file->filename, + "access", VIPS_ACCESS_SEQUENTIAL, + "subifd", (int) factor, + NULL ) ); + else if( thumbnail->level_count > 0 ) + return( vips_image_new_from_file( file->filename, + "access", VIPS_ACCESS_SEQUENTIAL, + "page", (int) factor, + NULL ) ); + else + return( vips_image_new_from_file( file->filename, + "access", VIPS_ACCESS_SEQUENTIAL, + NULL ) ); + } else if( vips_isprefix( "VipsForeignLoadHeif", thumbnail->loader ) ) { return( vips_image_new_from_file( file->filename, @@ -1220,8 +1339,11 @@ vips_thumbnail_buffer_init( VipsThumbnailBuffer *buffer ) * * @import_profile: %gchararray, fallback import ICC profile * * @export_profile: %gchararray, export ICC profile * * @intent: #VipsIntent, rendering intent + * * @option_string: %gchararray, extra loader options * - * Exacty as vips_thumbnail(), but read from a memory buffer. + * Exacty as vips_thumbnail(), but read from a memory buffer. One extra + * optional argument, @option_string, lets you pass options to the underlying + * loader. * * See also: vips_thumbnail(). * @@ -1247,6 +1369,178 @@ vips_thumbnail_buffer( void *buf, size_t len, VipsImage **out, int width, ... ) return( result ); } +typedef struct _VipsThumbnailSource { + VipsThumbnail parent_object; + + VipsSource *source; + char *option_string; +} VipsThumbnailSource; + +typedef VipsThumbnailClass VipsThumbnailSourceClass; + +G_DEFINE_TYPE( VipsThumbnailSource, vips_thumbnail_source, + vips_thumbnail_get_type() ); + +/* Get the info from a source. + */ +static int +vips_thumbnail_source_get_info( VipsThumbnail *thumbnail ) +{ + VipsThumbnailSource *source = (VipsThumbnailSource *) thumbnail; + + VipsImage *image; + + g_info( "thumbnailing source" ); + + if( !(thumbnail->loader = vips_foreign_find_load_source( + source->source )) || + !(image = vips_image_new_from_source( source->source, + source->option_string, NULL )) ) + return( -1 ); + + vips_thumbnail_read_header( thumbnail, image ); + + g_object_unref( image ); + + return( 0 ); +} + +/* Open an image, scaling as appropriate. + */ +static VipsImage * +vips_thumbnail_source_open( VipsThumbnail *thumbnail, double factor ) +{ + VipsThumbnailSource *source = (VipsThumbnailSource *) thumbnail; + + if( vips_isprefix( "VipsForeignLoadJpeg", thumbnail->loader ) ) { + return( vips_image_new_from_source( + source->source, + source->option_string, + "access", VIPS_ACCESS_SEQUENTIAL, + "shrink", (int) factor, + NULL ) ); + } + else if( vips_isprefix( "VipsForeignLoadOpenslide", + thumbnail->loader ) ) { + return( vips_image_new_from_source( + source->source, + source->option_string, + "access", VIPS_ACCESS_SEQUENTIAL, + "level", (int) factor, + NULL ) ); + } + else if( vips_isprefix( "VipsForeignLoadPdf", thumbnail->loader ) || + vips_isprefix( "VipsForeignLoadSvg", thumbnail->loader ) || + vips_isprefix( "VipsForeignLoadWebp", thumbnail->loader ) ) { + return( vips_image_new_from_source( + source->source, + source->option_string, + "access", VIPS_ACCESS_SEQUENTIAL, + "scale", 1.0 / factor, + NULL ) ); + } + else if( vips_isprefix( "VipsForeignLoadTiff", thumbnail->loader ) ) { + return( vips_image_new_from_source( + source->source, + source->option_string, + "access", VIPS_ACCESS_SEQUENTIAL, + "page", (int) factor, + NULL ) ); + } + else if( vips_isprefix( "VipsForeignLoadHeif", thumbnail->loader ) ) { + return( vips_image_new_from_source( + source->source, + source->option_string, + "access", VIPS_ACCESS_SEQUENTIAL, + "thumbnail", (int) factor, + NULL ) ); + } + else { + return( vips_image_new_from_source( + source->source, + source->option_string, + "access", VIPS_ACCESS_SEQUENTIAL, + NULL ) ); + } +} + +static void +vips_thumbnail_source_class_init( VipsThumbnailClass *class ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( class ); + VipsObjectClass *vobject_class = VIPS_OBJECT_CLASS( class ); + VipsThumbnailClass *thumbnail_class = VIPS_THUMBNAIL_CLASS( class ); + + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + vobject_class->nickname = "thumbnail_source"; + vobject_class->description = _( "generate thumbnail from source" ); + + thumbnail_class->get_info = vips_thumbnail_source_get_info; + thumbnail_class->open = vips_thumbnail_source_open; + + VIPS_ARG_OBJECT( class, "source", 1, + _( "Source" ), + _( "Source to load from" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsThumbnailSource, source ), + VIPS_TYPE_SOURCE ); + + VIPS_ARG_STRING( class, "option_string", 20, + _( "Extra options" ), + _( "Options that are passed on to the underlying loader" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsThumbnailSource, option_string ), + "" ); + +} + +static void +vips_thumbnail_source_init( VipsThumbnailSource *source ) +{ +} + +/** + * vips_thumbnail_source: + * @source: source to thumbnail + * @out: (out): output image + * @width: target width in pixels + * @...: %NULL-terminated list of optional named arguments + * + * Optional arguments: + * + * * @height: %gint, target height in pixels + * * @size: #VipsSize, upsize, downsize, both or force + * * @no_rotate: %gboolean, don't rotate upright using orientation tag + * * @crop: #VipsInteresting, shrink and crop to fill target + * * @linear: %gboolean, perform shrink in linear light + * * @import_profile: %gchararray, fallback import ICC profile + * * @export_profile: %gchararray, export ICC profile + * * @intent: #VipsIntent, rendering intent + * * @option_string: %gchararray, extra loader options + * + * Exacty as vips_thumbnail(), but read from a source. One extra + * optional argument, @option_string, lets you pass options to the underlying + * loader. + * + * See also: vips_thumbnail(). + * + * Returns: 0 on success, -1 on error. + */ +int +vips_thumbnail_source( VipsSource *source, VipsImage **out, int width, ... ) +{ + va_list ap; + int result; + + va_start( ap, width ); + result = vips_call_split( "thumbnail_source", ap, source, out, width ); + va_end( ap ); + + return( result ); +} + typedef struct _VipsThumbnailImage { VipsThumbnail parent_object; @@ -1333,7 +1627,12 @@ vips_thumbnail_image_init( VipsThumbnailImage *image ) * * @export_profile: %gchararray, export ICC profile * * @intent: #VipsIntent, rendering intent * - * Exacty as vips_thumbnail(), but read from an existing image. + * Exacty as vips_thumbnail(), but read from an existing image. + * + * This operation + * is not able to exploit shrink-on-load features of image load libraries, so + * it can be much slower than `vips_thumbnail()` and produce poorer quality + * output. Only use it if you really have to. * * See also: vips_thumbnail(). * diff --git a/libvips/resample/transform.c b/libvips/resample/transform.c index 5d6af3dc..59ca9715 100644 --- a/libvips/resample/transform.c +++ b/libvips/resample/transform.c @@ -43,26 +43,30 @@ #include #include +/* DBL_MIN is smallest *normalized* double precision float + */ +#define TOO_SMALL (2.0 * DBL_MIN) + /* Calculate the inverse transformation. */ int vips__transform_calc_inverse( VipsTransformation *trn ) { - DOUBLEMASK *msk, *msk2; + double det = trn->a * trn->d - trn->b * trn->c; - if( !(msk = im_create_dmaskv( "boink", 2, 2, - trn->a, trn->b, trn->c, trn->d )) ) - return( -1 ); - if( !(msk2 = im_matinv( msk, "boink2" )) ) { - (void) im_free_dmask( msk ); + if( fabs( det ) < TOO_SMALL ) { + /* divisor is near zero */ + vips_error( "vips__transform_calc_inverse", + "%s", _( "singular or near-singular matrix" ) ); return( -1 ); } - trn->ia = msk2->coeff[0]; - trn->ib = msk2->coeff[1]; - trn->ic = msk2->coeff[2]; - trn->id = msk2->coeff[3]; - (void) im_free_dmask( msk ); - (void) im_free_dmask( msk2 ); + + double tmp = 1.0 / det; + + trn->ia = tmp * trn->d; + trn->ib = -tmp * trn->b; + trn->ic = -tmp * trn->c; + trn->id = tmp * trn->a; return( 0 ); } diff --git a/po/POTFILES.in b/po/POTFILES.in index 756d0e31..2b1843eb 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -1,417 +1,393 @@ -libvips/arithmetic/hough_circle.c -libvips/arithmetic/boolean.c -libvips/arithmetic/complex.c -libvips/arithmetic/hough_line.c -libvips/arithmetic/profile.c -libvips/arithmetic/sign.c -libvips/arithmetic/hough.c -libvips/arithmetic/getpoint.c -libvips/arithmetic/remainder.c -libvips/arithmetic/math.c -libvips/arithmetic/sum.c -libvips/arithmetic/hist_find_ndim.c -libvips/arithmetic/subtract.c -libvips/arithmetic/statistic.c -libvips/arithmetic/unary.c -libvips/arithmetic/abs.c -libvips/arithmetic/round.c -libvips/arithmetic/measure.c -libvips/arithmetic/linear.c -libvips/arithmetic/relational.c -libvips/arithmetic/multiply.c -libvips/arithmetic/deviate.c -libvips/arithmetic/unaryconst.c -libvips/arithmetic/min.c -libvips/arithmetic/add.c -libvips/arithmetic/nary.c -libvips/arithmetic/stats.c -libvips/arithmetic/binary.c -libvips/arithmetic/project.c -libvips/arithmetic/hist_find.c -libvips/arithmetic/arithmetic.c -libvips/arithmetic/divide.c -libvips/arithmetic/invert.c -libvips/arithmetic/max.c -libvips/arithmetic/find_trim.c -libvips/arithmetic/math2.c -libvips/arithmetic/avg.c -libvips/arithmetic/hist_find_indexed.c -libvips/colour/dE76.c -libvips/colour/scRGB2XYZ.c -libvips/colour/LabQ2LabS.c -libvips/colour/LabS2LabQ.c -libvips/colour/UCS2LCh.c -libvips/colour/profiles.c -libvips/colour/LCh2Lab.c -libvips/colour/Yxy2XYZ.c -libvips/colour/XYZ2Lab.c -libvips/colour/scRGB2BW.c -libvips/colour/sRGB2HSV.c -libvips/colour/LabQ2Lab.c -libvips/colour/LabQ2sRGB.c -libvips/colour/Lab2XYZ.c -libvips/colour/CMYK2XYZ.c -libvips/colour/XYZ2Yxy.c -libvips/colour/XYZ2scRGB.c -libvips/colour/colour.c -libvips/colour/profile_load.c -libvips/colour/Lab2LCh.c -libvips/colour/rad2float.c -libvips/colour/XYZ2CMYK.c -libvips/colour/Lab2LabQ.c -libvips/colour/dECMC.c -libvips/colour/colourspace.c -libvips/colour/scRGB2sRGB.c -libvips/colour/sRGB2scRGB.c -libvips/colour/LCh2UCS.c -libvips/colour/icc_transform.c -libvips/colour/dE00.c -libvips/colour/float2rad.c -libvips/colour/HSV2sRGB.c -libvips/colour/Lab2LabS.c -libvips/colour/LabS2Lab.c -libvips/conversion/bandjoin.c -libvips/conversion/wrap.c -libvips/conversion/arrayjoin.c -libvips/conversion/premultiply.c -libvips/conversion/scale.c -libvips/conversion/flatten.c -libvips/conversion/conversion.c -libvips/conversion/rot.c -libvips/conversion/sequential.c -libvips/conversion/gamma.c -libvips/conversion/msb.c -libvips/conversion/autorot.c -libvips/conversion/smartcrop.c -libvips/conversion/bandmean.c -libvips/conversion/copy.c -libvips/conversion/tilecache.c -libvips/conversion/extract.c -libvips/conversion/bandbool.c -libvips/conversion/grid.c -libvips/conversion/transpose3d.c -libvips/conversion/unpremultiply.c -libvips/conversion/bandrank.c -libvips/conversion/ifthenelse.c -libvips/conversion/join.c -libvips/conversion/falsecolour.c -libvips/conversion/cache.c -libvips/conversion/embed.c -libvips/conversion/insert.c -libvips/conversion/replicate.c -libvips/conversion/rot45.c -libvips/conversion/byteswap.c -libvips/conversion/bandunfold.c -libvips/conversion/cast.c -libvips/conversion/flip.c -libvips/conversion/zoom.c -libvips/conversion/bandfold.c -libvips/conversion/subsample.c -libvips/conversion/recomb.c -libvips/conversion/bandary.c -libvips/convolution/conva.c -libvips/convolution/correlation.c -libvips/convolution/gaussblur.c -libvips/convolution/conv.c -libvips/convolution/convi.c -libvips/convolution/convsep.c -libvips/convolution/sharpen.c -libvips/convolution/convolution.c -libvips/convolution/fastcor.c -libvips/convolution/canny.c -libvips/convolution/convf.c -libvips/convolution/spcor.c -libvips/convolution/compass.c -libvips/convolution/convasep.c -libvips/convolution/sobel.c -libvips/create/perlin.c -libvips/create/worley.c -libvips/create/zone.c -libvips/create/mask_ideal_band.c -libvips/create/gaussmat.c -libvips/create/grey.c -libvips/create/buildlut.c -libvips/create/create.c -libvips/create/fractsurf.c -libvips/create/black.c -libvips/create/xyz.c -libvips/create/invertlut.c -libvips/create/mask.c -libvips/create/mask_butterworth.c -libvips/create/mask_ideal_ring.c -libvips/create/point.c -libvips/create/tonelut.c -libvips/create/mask_ideal.c -libvips/create/text.c -libvips/create/mask_butterworth_ring.c -libvips/create/mask_gaussian.c -libvips/create/gaussnoise.c -libvips/create/mask_butterworth_band.c -libvips/create/sines.c -libvips/create/mask_gaussian_ring.c -libvips/create/eye.c -libvips/create/logmat.c -libvips/create/identity.c -libvips/create/mask_gaussian_band.c -libvips/create/mask_fractal.c -libvips/draw/drawink.c -libvips/draw/draw_line.c -libvips/draw/draw_circle.c -libvips/draw/draw_flood.c -libvips/draw/draw_rect.c -libvips/draw/draw_smudge.c -libvips/draw/draw_image.c -libvips/draw/draw_mask.c -libvips/draw/draw.c -libvips/foreign/vipssave.c -libvips/foreign/dzsave.c -libvips/foreign/csv.c -libvips/foreign/niftiload.c -libvips/foreign/magick.c -libvips/foreign/ppmload.c -libvips/foreign/openslideload.c -libvips/foreign/tiffload.c -libvips/foreign/tiff2vips.c -libvips/foreign/radsave.c -libvips/foreign/analyze2vips.c -libvips/foreign/fits.c -libvips/foreign/csvsave.c -libvips/foreign/analyzeload.c -libvips/foreign/ppmsave.c -libvips/foreign/radload.c -libvips/foreign/pdfload.c -libvips/foreign/fitssave.c -libvips/foreign/rawload.c -libvips/foreign/heifload.c -libvips/foreign/jpeg2vips.c -libvips/foreign/vips2jpeg.c -libvips/foreign/pdfiumload.c -libvips/foreign/webpsave.c -libvips/foreign/magick7load.c -libvips/foreign/csvload.c -libvips/foreign/heifsave.c -libvips/foreign/radiance.c -libvips/foreign/pngload.c -libvips/foreign/openslide2vips.c -libvips/foreign/matrixsave.c -libvips/foreign/tiffsave.c -libvips/foreign/magickload.c -libvips/foreign/ppm.c -libvips/foreign/jpegsave.c -libvips/foreign/webpload.c -libvips/foreign/gifload.c -libvips/foreign/pngsave.c -libvips/foreign/exif.c -libvips/foreign/magick2vips.c -libvips/foreign/openexr2vips.c -libvips/foreign/matload.c -libvips/foreign/vips2webp.c -libvips/foreign/openexrload.c -libvips/foreign/rawsave.c -libvips/foreign/fitsload.c -libvips/foreign/niftisave.c -libvips/foreign/tiff.c -libvips/foreign/quantise.c -libvips/foreign/webp2vips.c -libvips/foreign/vips2tiff.c -libvips/foreign/cairo.c -libvips/foreign/magicksave.c -libvips/foreign/svgload.c -libvips/foreign/jpegload.c -libvips/foreign/vipsload.c -libvips/foreign/matlab.c -libvips/foreign/foreign.c -libvips/foreign/vipspng.c -libvips/foreign/matrixload.c -libvips/freqfilt/freqfilt.c -libvips/freqfilt/invfft.c -libvips/freqfilt/freqmult.c -libvips/freqfilt/spectrum.c -libvips/freqfilt/fwfft.c -libvips/freqfilt/phasecor.c -libvips/histogram/hist_norm.c -libvips/histogram/hist_cum.c -libvips/histogram/histogram.c -libvips/histogram/hist_match.c -libvips/histogram/hist_entropy.c -libvips/histogram/hist_plot.c -libvips/histogram/stdif.c -libvips/histogram/percent.c -libvips/histogram/hist_ismonotonic.c -libvips/histogram/hist_equal.c -libvips/histogram/maplut.c -libvips/histogram/hist_unary.c -libvips/histogram/hist_local.c -libvips/introspect.c -libvips/iofuncs/sinkscreen.c -libvips/iofuncs/vipsmarshal.c -libvips/iofuncs/init.c -libvips/iofuncs/dbuf.c -libvips/iofuncs/buffer.c -libvips/iofuncs/operation.c -libvips/iofuncs/sinkmemory.c -libvips/iofuncs/window.c -libvips/iofuncs/reorder.c -libvips/iofuncs/base64.c -libvips/iofuncs/memory.c -libvips/iofuncs/sinkdisc.c -libvips/iofuncs/region.c -libvips/iofuncs/rect.c -libvips/iofuncs/util.c -libvips/iofuncs/vips.c -libvips/iofuncs/cache.c -libvips/iofuncs/type.c -libvips/iofuncs/semaphore.c -libvips/iofuncs/gate.c -libvips/iofuncs/mapfile.c -libvips/iofuncs/sink.c -libvips/iofuncs/enumtypes.c -libvips/iofuncs/vector.c -libvips/iofuncs/header.c -libvips/iofuncs/error.c -libvips/iofuncs/generate.c -libvips/iofuncs/image.c -libvips/iofuncs/threadpool.c -libvips/iofuncs/buf.c -libvips/iofuncs/system.c -libvips/iofuncs/object.c -libvips/morphology/labelregions.c -libvips/morphology/morph.c -libvips/morphology/nearest.c -libvips/morphology/morphology.c -libvips/morphology/rank.c -libvips/morphology/hitmiss.c -libvips/morphology/countlines.c -libvips/mosaicing/global_balance.c -libvips/mosaicing/im_lrmosaic.c -libvips/mosaicing/im_initialize.c -libvips/mosaicing/im_remosaic.c -libvips/mosaicing/im_improve.c -libvips/mosaicing/im_lrcalcon.c -libvips/mosaicing/mosaic1.c -libvips/mosaicing/im_chkpair.c -libvips/mosaicing/im_tbcalcon.c -libvips/mosaicing/mosaicing.c -libvips/mosaicing/mosaic.c -libvips/mosaicing/im_avgdxdy.c -libvips/mosaicing/im_clinear.c -libvips/mosaicing/merge.c -libvips/mosaicing/im_tbmosaic.c -libvips/mosaicing/match.c -libvips/mosaicing/im_lrmerge.c -libvips/mosaicing/im_tbmerge.c -libvips/resample/shrinkh.c -libvips/resample/shrinkv.c -libvips/resample/reduce.c -libvips/resample/transform.c -libvips/resample/resize.c -libvips/resample/mapim.c -libvips/resample/thumbnail.c -libvips/resample/resample.c -libvips/resample/affine.c -libvips/resample/quadratic.c -libvips/resample/interpolate.c -libvips/resample/similarity.c -libvips/resample/shrink.c -tools/vips.c -tools/vipsedit.c -tools/vipsheader.c -tools/vipsthumbnail.c +cplusplus/include/vips/VConnection8.h cplusplus/include/vips/VError8.h -cplusplus/include/vips/vips-operators.h cplusplus/include/vips/VImage8.h cplusplus/include/vips/VInterpolate8.h -libvips/arithmetic/binary.h -libvips/arithmetic/unary.h -libvips/arithmetic/parithmetic.h -libvips/arithmetic/hough.h -libvips/arithmetic/nary.h -libvips/arithmetic/statistic.h -libvips/arithmetic/unaryconst.h -libvips/colour/profiles.h -libvips/colour/pcolour.h -libvips/conversion/bandary.h -libvips/conversion/pconversion.h -libvips/convolution/correlation.h -libvips/convolution/pconvolution.h -libvips/create/pmask.h -libvips/create/pcreate.h -libvips/create/point.h -libvips/draw/pdraw.h -libvips/draw/drawink.h -libvips/foreign/tiff.h -libvips/foreign/pforeign.h -libvips/foreign/dbh.h -libvips/foreign/magick.h -libvips/foreign/jpeg.h -libvips/freqfilt/pfreqfilt.h -libvips/histogram/phistogram.h -libvips/histogram/hist_unary.h -libvips/include/vips/freqfilt.h +cplusplus/include/vips/vips-operators.h libvips/include/vips/arithmetic.h -libvips/include/vips/buf.h -libvips/include/vips/histogram.h -libvips/include/vips/intl.h -libvips/include/vips/threadpool.h -libvips/include/vips/operation.h -libvips/include/vips/video.h -libvips/include/vips/memory.h -libvips/include/vips/conversion.h -libvips/include/vips/private.h -libvips/include/vips/interpolate.h -libvips/include/vips/internal.h libvips/include/vips/basic.h -libvips/include/vips/region.h -libvips/include/vips/foreign.h -libvips/include/vips/gate.h -libvips/include/vips/almostdeprecated.h -libvips/include/vips/dispatch.h -libvips/include/vips/image.h -libvips/include/vips/mosaicing.h -libvips/include/vips/vector.h +libvips/include/vips/buf.h libvips/include/vips/cimg_funcs.h -libvips/include/vips/dbuf.h -libvips/include/vips/error.h -libvips/include/vips/type.h -libvips/include/vips/create.h -libvips/include/vips/generate.h -libvips/include/vips/format.h -libvips/include/vips/util.h -libvips/include/vips/convolution.h -libvips/include/vips/thread.h -libvips/include/vips/resample.h -libvips/include/vips/object.h -libvips/include/vips/vips.h -libvips/include/vips/inlines.h -libvips/include/vips/transform.h -libvips/include/vips/draw.h -libvips/include/vips/semaphore.h -libvips/include/vips/vips7compat.h -libvips/include/vips/header.h -libvips/include/vips/soname.h -libvips/include/vips/rect.h libvips/include/vips/colour.h -libvips/include/vips/mask.h +libvips/include/vips/connection.h +libvips/include/vips/conversion.h +libvips/include/vips/convolution.h +libvips/include/vips/create.h +libvips/include/vips/dbuf.h libvips/include/vips/debug.h -libvips/include/vips/morphology.h +libvips/include/vips/dispatch.h +libvips/include/vips/draw.h libvips/include/vips/enumtypes.h -libvips/include/vips/deprecated.h +libvips/include/vips/error.h +libvips/include/vips/foreign.h +libvips/include/vips/format.h +libvips/include/vips/freqfilt.h +libvips/include/vips/gate.h +libvips/include/vips/generate.h +libvips/include/vips/header.h +libvips/include/vips/histogram.h +libvips/include/vips/image.h +libvips/include/vips/inlines.h +libvips/include/vips/internal.h +libvips/include/vips/interpolate.h +libvips/include/vips/intl.h +libvips/include/vips/mask.h +libvips/include/vips/memory.h +libvips/include/vips/morphology.h +libvips/include/vips/mosaicing.h +libvips/include/vips/object.h +libvips/include/vips/operation.h +libvips/include/vips/private.h +libvips/include/vips/rect.h +libvips/include/vips/region.h +libvips/include/vips/resample.h +libvips/include/vips/sbuf.h +libvips/include/vips/semaphore.h +libvips/include/vips/soname.h +libvips/include/vips/thread.h +libvips/include/vips/threadpool.h +libvips/include/vips/transform.h +libvips/include/vips/type.h +libvips/include/vips/util.h +libvips/include/vips/vector.h libvips/include/vips/version.h -libvips/iofuncs/sink.h -libvips/iofuncs/vipsmarshal.h -libvips/morphology/pmorphology.h -libvips/mosaicing/pmosaicing.h -libvips/mosaicing/global_balance.h -libvips/resample/presample.h -libvips/resample/templates.h +libvips/include/vips/video.h +libvips/include/vips/vips7compat.h +libvips/include/vips/vips.h +cplusplus/VConnection.cpp cplusplus/VError.cpp cplusplus/VImage.cpp cplusplus/VInterpolate.cpp cplusplus/vips-operators.cpp +libvips/introspect.c +libvips/arithmetic/abs.c +libvips/arithmetic/add.c +libvips/arithmetic/arithmetic.c +libvips/arithmetic/avg.c +libvips/arithmetic/binary.c +libvips/arithmetic/boolean.c +libvips/arithmetic/complex.c +libvips/arithmetic/deviate.c +libvips/arithmetic/divide.c +libvips/arithmetic/find_trim.c +libvips/arithmetic/getpoint.c +libvips/arithmetic/hist_find.c +libvips/arithmetic/hist_find_indexed.c +libvips/arithmetic/hist_find_ndim.c +libvips/arithmetic/hough.c +libvips/arithmetic/hough_circle.c +libvips/arithmetic/hough_line.c +libvips/arithmetic/invert.c +libvips/arithmetic/linear.c +libvips/arithmetic/math2.c +libvips/arithmetic/math.c +libvips/arithmetic/max.c +libvips/arithmetic/measure.c +libvips/arithmetic/min.c +libvips/arithmetic/multiply.c +libvips/arithmetic/nary.c +libvips/arithmetic/profile.c +libvips/arithmetic/project.c +libvips/arithmetic/relational.c +libvips/arithmetic/remainder.c +libvips/arithmetic/round.c +libvips/arithmetic/sign.c +libvips/arithmetic/statistic.c +libvips/arithmetic/stats.c +libvips/arithmetic/subtract.c +libvips/arithmetic/sum.c +libvips/arithmetic/unary.c +libvips/arithmetic/unaryconst.c +libvips/colour/CMYK2XYZ.c +libvips/colour/colour.c +libvips/colour/colourspace.c +libvips/colour/dE00.c +libvips/colour/dE76.c +libvips/colour/dECMC.c +libvips/colour/float2rad.c +libvips/colour/HSV2sRGB.c +libvips/colour/icc_transform.c +libvips/colour/Lab2LabQ.c +libvips/colour/Lab2LabS.c +libvips/colour/Lab2LCh.c +libvips/colour/Lab2XYZ.c +libvips/colour/LabQ2Lab.c +libvips/colour/LabQ2LabS.c +libvips/colour/LabQ2sRGB.c +libvips/colour/LabS2Lab.c +libvips/colour/LabS2LabQ.c +libvips/colour/LCh2Lab.c +libvips/colour/LCh2UCS.c +libvips/colour/profile_load.c +libvips/colour/profiles.c +libvips/colour/rad2float.c +libvips/colour/scRGB2BW.c +libvips/colour/scRGB2sRGB.c +libvips/colour/scRGB2XYZ.c +libvips/colour/sRGB2HSV.c +libvips/colour/sRGB2scRGB.c +libvips/colour/UCS2LCh.c +libvips/colour/XYZ2CMYK.c +libvips/colour/XYZ2Lab.c +libvips/colour/XYZ2scRGB.c +libvips/colour/XYZ2Yxy.c +libvips/colour/Yxy2XYZ.c +libvips/conversion/arrayjoin.c +libvips/conversion/autorot.c +libvips/conversion/bandary.c +libvips/conversion/bandbool.c +libvips/conversion/bandfold.c +libvips/conversion/bandjoin.c +libvips/conversion/bandmean.c +libvips/conversion/bandrank.c +libvips/conversion/bandunfold.c +libvips/conversion/byteswap.c +libvips/conversion/cache.c +libvips/conversion/cast.c libvips/conversion/composite.cpp -libvips/resample/reduceh.cpp -libvips/resample/vsqbs.cpp -libvips/resample/lbb.cpp -libvips/resample/nohalo.cpp -libvips/resample/reducev.cpp +libvips/conversion/conversion.c +libvips/conversion/copy.c +libvips/conversion/embed.c +libvips/conversion/extract.c +libvips/conversion/falsecolour.c +libvips/conversion/flatten.c +libvips/conversion/flip.c +libvips/conversion/gamma.c +libvips/conversion/grid.c +libvips/conversion/ifthenelse.c +libvips/conversion/insert.c +libvips/conversion/join.c +libvips/conversion/msb.c +libvips/conversion/premultiply.c +libvips/conversion/recomb.c +libvips/conversion/replicate.c +libvips/conversion/rot45.c +libvips/conversion/rot.c +libvips/conversion/scale.c +libvips/conversion/sequential.c +libvips/conversion/smartcrop.c +libvips/conversion/subsample.c +libvips/conversion/switch.c +libvips/conversion/tilecache.c +libvips/conversion/transpose3d.c +libvips/conversion/unpremultiply.c +libvips/conversion/wrap.c +libvips/conversion/zoom.c +libvips/convolution/canny.c +libvips/convolution/compass.c +libvips/convolution/conva.c +libvips/convolution/convasep.c +libvips/convolution/conv.c +libvips/convolution/convf.c +libvips/convolution/convi.c +libvips/convolution/convolution.c +libvips/convolution/convsep.c +libvips/convolution/correlation.c +libvips/convolution/fastcor.c +libvips/convolution/gaussblur.c +libvips/convolution/sharpen.c +libvips/convolution/sobel.c +libvips/convolution/spcor.c +libvips/create/black.c +libvips/create/buildlut.c +libvips/create/create.c +libvips/create/eye.c +libvips/create/fractsurf.c +libvips/create/gaussmat.c +libvips/create/gaussnoise.c +libvips/create/grey.c +libvips/create/identity.c +libvips/create/invertlut.c +libvips/create/logmat.c +libvips/create/mask_butterworth_band.c +libvips/create/mask_butterworth.c +libvips/create/mask_butterworth_ring.c +libvips/create/mask.c +libvips/create/mask_fractal.c +libvips/create/mask_gaussian_band.c +libvips/create/mask_gaussian.c +libvips/create/mask_gaussian_ring.c +libvips/create/mask_ideal_band.c +libvips/create/mask_ideal.c +libvips/create/mask_ideal_ring.c +libvips/create/perlin.c +libvips/create/point.c +libvips/create/sines.c +libvips/create/text.c +libvips/create/tonelut.c +libvips/create/worley.c +libvips/create/xyz.c +libvips/create/zone.c +libvips/draw/draw.c +libvips/draw/draw_circle.c +libvips/draw/draw_flood.c +libvips/draw/draw_image.c +libvips/draw/drawink.c +libvips/draw/draw_line.c +libvips/draw/draw_mask.c +libvips/draw/draw_rect.c +libvips/draw/draw_smudge.c +libvips/foreign/analyze2vips.c +libvips/foreign/analyzeload.c +libvips/foreign/cairo.c +libvips/foreign/csvload.c +libvips/foreign/csvsave.c +libvips/foreign/dzsave.c +libvips/foreign/exif.c +libvips/foreign/fits.c +libvips/foreign/fitsload.c +libvips/foreign/fitssave.c +libvips/foreign/foreign.c +libvips/foreign/gifload.c +libvips/foreign/heifload.c +libvips/foreign/heifsave.c +libvips/foreign/jpeg2vips.c +libvips/foreign/jpegload.c +libvips/foreign/jpegsave.c +libvips/foreign/magick2vips.c +libvips/foreign/magick7load.c +libvips/foreign/magick.c +libvips/foreign/magickload.c +libvips/foreign/magicksave.c +libvips/foreign/matlab.c +libvips/foreign/matload.c +libvips/foreign/matrixload.c +libvips/foreign/matrixsave.c +libvips/foreign/niftiload.c +libvips/foreign/niftisave.c +libvips/foreign/openexr2vips.c +libvips/foreign/openexrload.c +libvips/foreign/openslide2vips.c +libvips/foreign/openslideload.c +libvips/foreign/pdfiumload.c +libvips/foreign/pdfload.c +libvips/foreign/pngload.c +libvips/foreign/pngsave.c +libvips/foreign/ppmload.c +libvips/foreign/ppmsave.c +libvips/foreign/quantise.c +libvips/foreign/radiance.c +libvips/foreign/radload.c +libvips/foreign/radsave.c +libvips/foreign/rawload.c +libvips/foreign/rawsave.c +libvips/foreign/spngload.c +libvips/foreign/svgload.c +libvips/foreign/tiff2vips.c +libvips/foreign/tiff.c +libvips/foreign/tiffload.c +libvips/foreign/tiffsave.c +libvips/foreign/vips2jpeg.c +libvips/foreign/vips2tiff.c +libvips/foreign/vips2webp.c +libvips/foreign/vipsload.c +libvips/foreign/vipspng.c +libvips/foreign/vipssave.c +libvips/foreign/webp2vips.c +libvips/foreign/webpload.c +libvips/foreign/webpsave.c +libvips/freqfilt/freqfilt.c +libvips/freqfilt/freqmult.c +libvips/freqfilt/fwfft.c +libvips/freqfilt/invfft.c +libvips/freqfilt/phasecor.c +libvips/freqfilt/spectrum.c +libvips/histogram/case.c +libvips/histogram/hist_cum.c +libvips/histogram/hist_entropy.c +libvips/histogram/hist_equal.c +libvips/histogram/hist_ismonotonic.c +libvips/histogram/hist_local.c +libvips/histogram/hist_match.c +libvips/histogram/hist_norm.c +libvips/histogram/histogram.c +libvips/histogram/hist_plot.c +libvips/histogram/hist_unary.c +libvips/histogram/maplut.c +libvips/histogram/percent.c +libvips/histogram/stdif.c +libvips/iofuncs/buf.c +libvips/iofuncs/buffer.c +libvips/iofuncs/cache.c +libvips/iofuncs/connection.c +libvips/iofuncs/dbuf.c +libvips/iofuncs/enumtypes.c +libvips/iofuncs/error.c +libvips/iofuncs/gate.c +libvips/iofuncs/generate.c +libvips/iofuncs/ginputsource.c +libvips/iofuncs/header.c +libvips/iofuncs/image.c +libvips/iofuncs/init.c +libvips/iofuncs/mapfile.c +libvips/iofuncs/memory.c +libvips/iofuncs/object.c +libvips/iofuncs/operation.c +libvips/iofuncs/rect.c +libvips/iofuncs/region.c +libvips/iofuncs/reorder.c +libvips/iofuncs/sbuf.c +libvips/iofuncs/semaphore.c +libvips/iofuncs/sink.c +libvips/iofuncs/sinkdisc.c +libvips/iofuncs/sinkmemory.c +libvips/iofuncs/sinkscreen.c +libvips/iofuncs/source.c +libvips/iofuncs/sourcecustom.c +libvips/iofuncs/system.c +libvips/iofuncs/target.c +libvips/iofuncs/targetcustom.c +libvips/iofuncs/threadpool.c +libvips/iofuncs/type.c +libvips/iofuncs/util.c +libvips/iofuncs/vector.c +libvips/iofuncs/vips.c +libvips/iofuncs/vipsmarshal.c +libvips/iofuncs/window.c +libvips/morphology/countlines.c +libvips/morphology/labelregions.c +libvips/morphology/morph.c +libvips/morphology/morphology.c +libvips/morphology/nearest.c +libvips/morphology/rank.c +libvips/mosaicing/global_balance.c +libvips/mosaicing/im_avgdxdy.c +libvips/mosaicing/im_chkpair.c +libvips/mosaicing/im_clinear.c +libvips/mosaicing/im_improve.c +libvips/mosaicing/im_initialize.c +libvips/mosaicing/im_lrcalcon.c +libvips/mosaicing/im_lrmerge.c +libvips/mosaicing/im_lrmosaic.c +libvips/mosaicing/im_remosaic.c +libvips/mosaicing/im_tbcalcon.c +libvips/mosaicing/im_tbmerge.c +libvips/mosaicing/im_tbmosaic.c +libvips/mosaicing/match.c +libvips/mosaicing/matrixinvert.c +libvips/mosaicing/merge.c +libvips/mosaicing/mosaic1.c +libvips/mosaicing/mosaic.c +libvips/mosaicing/mosaicing.c +libvips/resample/affine.c libvips/resample/bicubic.cpp +libvips/resample/interpolate.c +libvips/resample/lbb.cpp +libvips/resample/mapim.c +libvips/resample/nohalo.cpp +libvips/resample/quadratic.c +libvips/resample/reduce.c +libvips/resample/reduceh.cpp +libvips/resample/reducev.cpp +libvips/resample/resample.c +libvips/resample/resize.c +libvips/resample/shrink.c +libvips/resample/shrinkh.c +libvips/resample/shrinkv.c +libvips/resample/similarity.c +libvips/resample/thumbnail.c +libvips/resample/transform.c +libvips/resample/vsqbs.cpp +tools/vips.c +tools/vipsedit.c +tools/vipsheader.c +tools/vipsthumbnail.c diff --git a/po/README b/po/README index d2403fb8..38f77405 100644 --- a/po/README +++ b/po/README @@ -21,15 +21,22 @@ tips ---- cd vips-8.x -find */* -name "*.c" > po/POTFILES.in -find */* -name "*.cc" >> po/POTFILES.in -find */* -name "*.h" >> po/POTFILES.in -find */* -name "*.cxx" >> po/POTFILES.in -find */* -name "*.cpp" >> po/POTFILES.in - - regenerate the list of files to search for strings ... don't forget to - remove autogenerated files, fuzzer sources, deprecated stuff etc. +find cplusplus/include libvips/include \ + -name '*deprecated*' -prune -o \ + -name '*.h' \ + -printf '%h\0%d\0%p\n' | \ + sort -t '\0' -n | \ + awk -F '\0' '{print $3}' > po/POTFILES.in +find libvips tools cplusplus \ + -path libvips/deprecated -prune -o \ + -name dummy.c -prune -o \ + -path cplusplus/examples -prune -o \ + \( -name '*.c' -o -name '*.cpp' \) \ + -printf '%h\0%d\0%p\n' | \ + sort -t '\0' -n | \ + awk -F '\0' '{print $3}' >> po/POTFILES.in +cd vips-8.x/po intltool-update --pot make a new vips8.x.pot translation template from the sources diff --git a/po/de.po b/po/de.po index 6e2fcdb9..a42e64f1 100644 --- a/po/de.po +++ b/po/de.po @@ -2619,12 +2619,6 @@ msgstr "»mmap %s« zur gleichen Adresse nicht möglich" msgid "too little data" msgstr "zu wenige Daten" -#. We shouldn't really be used for large amounts of data. -#. -#: libvips/iofuncs/base64.c:176 libvips/iofuncs/base64.c:241 -msgid "too much data" -msgstr "zu viele Daten" - #: libvips/iofuncs/object.c:148 #, c-format msgid "parameter %s not set" diff --git a/po/en_GB.po b/po/en_GB.po index 97d8c11c..31e5de02 100644 --- a/po/en_GB.po +++ b/po/en_GB.po @@ -5318,18 +5318,6 @@ msgstr "" msgid "TB" msgstr "" -#. We shouldn't really be used for large amounts of data, plus -#. * we are using int offsets. -#. * -#. * A large ICC profile can be 1MB, so allow 10MB of b64. -#. -#. We shouldn't really be used for large amounts of data, plus -#. * we are using an int for offset. -#. -#: ../libvips/iofuncs/base64.c:177 ../libvips/iofuncs/base64.c:244 -msgid "too much data" -msgstr "" - #: ../libvips/morphology/morph.c:139 msgid "morphology operation" msgstr "" diff --git a/suppressions/lsan.supp b/suppressions/lsan.supp index b7a2dd7d..65a43ab9 100644 --- a/suppressions/lsan.supp +++ b/suppressions/lsan.supp @@ -1,4 +1,5 @@ leak:python2.7 +leak:python3 leak:libfontconfig.so leak:libglib-2.0.so leak:libIlmImf-2_2.so diff --git a/test/.gitignore b/test/.gitignore new file mode 100644 index 00000000..9baa9b25 --- /dev/null +++ b/test/.gitignore @@ -0,0 +1,2 @@ +test_descriptors +test_connections diff --git a/test/Makefile.am b/test/Makefile.am index 006665d5..17d8cb51 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -1,19 +1,39 @@ # don't run test_thumbnail.sh by default, it takes ages TESTS = \ + test_connections.sh \ + test_descriptors.sh \ test_cli.sh \ test_formats.sh \ test_seq.sh \ + test_stall.sh \ test_threading.sh SUBDIRS = \ test-suite +noinst_PROGRAMS = \ + test_descriptors \ + test_connections + +test_descriptors_SOURCES = \ + test_descriptors.c + +test_connections_SOURCES = \ + test_connections.c + +AM_CPPFLAGS = -I${top_srcdir}/libvips/include @VIPS_CFLAGS@ @VIPS_INCLUDES@ +AM_LDFLAGS = @LDFLAGS@ +LDADD = @VIPS_CFLAGS@ ${top_builddir}/libvips/libvips.la @VIPS_LIBS@ + EXTRA_DIST = \ variables.sh.in \ test_cli.sh \ + test_descriptors.sh \ + test_connections.sh \ test_formats.sh \ test_seq.sh \ test_thumbnail.sh \ + test_stall.sh \ test_threading.sh clean-local: diff --git a/test/test-suite/Makefile.am b/test/test-suite/Makefile.am index 690361f7..aca7359c 100644 --- a/test/test-suite/Makefile.am +++ b/test/test-suite/Makefile.am @@ -15,6 +15,7 @@ EXTRA_DIST = \ test_histogram.py \ test_iofuncs.py \ test_morphology.py \ - test_resample.py + test_resample.py \ + test_connection.py diff --git a/test/test-suite/helpers/helpers.py b/test/test-suite/helpers/helpers.py index 00d8caf6..e5645713 100644 --- a/test/test-suite/helpers/helpers.py +++ b/test/test-suite/helpers/helpers.py @@ -8,11 +8,15 @@ import pytest import pyvips IMAGES = os.path.join(os.path.dirname(__file__), os.pardir, 'images') -JPEG_FILE = os.path.join(IMAGES, "йцук.jpg") +JPEG_FILE = os.path.join(IMAGES, "sample.jpg") +TRUNCATED_FILE = os.path.join(IMAGES, "truncated.jpg") SRGB_FILE = os.path.join(IMAGES, "sRGB.icm") MATLAB_FILE = os.path.join(IMAGES, "sample.mat") PNG_FILE = os.path.join(IMAGES, "sample.png") TIF_FILE = os.path.join(IMAGES, "sample.tif") +TIF1_FILE = os.path.join(IMAGES, "1bit.tif") +TIF2_FILE = os.path.join(IMAGES, "2bit.tif") +TIF4_FILE = os.path.join(IMAGES, "4bit.tif") OME_FILE = os.path.join(IMAGES, "multi-channel-z-series.ome.tif") ANALYZE_FILE = os.path.join(IMAGES, "t00740_tr1_segm.hdr") GIF_FILE = os.path.join(IMAGES, "cramps.gif") @@ -26,11 +30,27 @@ SVG_FILE = os.path.join(IMAGES, "logo.svg") SVGZ_FILE = os.path.join(IMAGES, "logo.svgz") SVG_GZ_FILE = os.path.join(IMAGES, "logo.svg.gz") GIF_ANIM_FILE = os.path.join(IMAGES, "cogs.gif") +GIF_ANIM_EXPECTED_PNG_FILE = os.path.join(IMAGES, "cogs.png") +GIF_ANIM_DISPOSE_BACKGROUND_FILE = os.path.join(IMAGES, "dispose-background.gif") +GIF_ANIM_DISPOSE_BACKGROUND_EXPECTED_PNG_FILE = os.path.join(IMAGES, "dispose-background.png") +GIF_ANIM_DISPOSE_PREVIOUS_FILE = os.path.join(IMAGES, "dispose-previous.gif") +GIF_ANIM_DISPOSE_PREVIOUS_EXPECTED_PNG_FILE = os.path.join(IMAGES, "dispose-previous.png") DICOM_FILE = os.path.join(IMAGES, "dicom_test_image.dcm") BMP_FILE = os.path.join(IMAGES, "MARBLES.BMP") NIFTI_FILE = os.path.join(IMAGES, "avg152T1_LR_nifti.nii.gz") ICO_FILE = os.path.join(IMAGES, "favicon.ico") -HEIC_FILE = os.path.join(IMAGES, "Example1.heic") +HEIC_FILE = os.path.join(IMAGES, "heic-orientation-6.heic") +MOSAIC_FILES = [os.path.join(IMAGES, "cd1.1.jpg"), os.path.join(IMAGES, "cd1.2.jpg"), + os.path.join(IMAGES, "cd2.1.jpg"), os.path.join(IMAGES, "cd2.2.jpg"), + os.path.join(IMAGES, "cd3.1.jpg"), os.path.join(IMAGES, "cd3.2.jpg"), + os.path.join(IMAGES, "cd4.1.jpg"), os.path.join(IMAGES, "cd4.2.jpg")] +MOSAIC_MARKS = [[489, 140], [66, 141], + [453, 40], [15, 43], + [500, 122], [65, 121], + [495, 58], [40, 57]] +MOSAIC_VERTICAL_MARKS = [[388, 44], [364, 346], + [384, 17], [385, 629], + [527, 42], [503, 959]] unsigned_formats = [pyvips.BandFormat.UCHAR, pyvips.BandFormat.USHORT, diff --git a/test/test-suite/images/1bit.tif b/test/test-suite/images/1bit.tif new file mode 100644 index 00000000..c95a1ab2 Binary files /dev/null and b/test/test-suite/images/1bit.tif differ diff --git a/test/test-suite/images/2bit.tif b/test/test-suite/images/2bit.tif new file mode 100644 index 00000000..0c06fe4a Binary files /dev/null and b/test/test-suite/images/2bit.tif differ diff --git a/test/test-suite/images/4bit.tif b/test/test-suite/images/4bit.tif new file mode 100644 index 00000000..52d79d7f Binary files /dev/null and b/test/test-suite/images/4bit.tif differ diff --git a/test/test-suite/images/cd1.1.jpg b/test/test-suite/images/cd1.1.jpg new file mode 100644 index 00000000..73e12953 Binary files /dev/null and b/test/test-suite/images/cd1.1.jpg differ diff --git a/test/test-suite/images/cd1.2.jpg b/test/test-suite/images/cd1.2.jpg new file mode 100644 index 00000000..d88f3ca3 Binary files /dev/null and b/test/test-suite/images/cd1.2.jpg differ diff --git a/test/test-suite/images/cd2.1.jpg b/test/test-suite/images/cd2.1.jpg new file mode 100644 index 00000000..91002c97 Binary files /dev/null and b/test/test-suite/images/cd2.1.jpg differ diff --git a/test/test-suite/images/cd2.2.jpg b/test/test-suite/images/cd2.2.jpg new file mode 100644 index 00000000..6438fff8 Binary files /dev/null and b/test/test-suite/images/cd2.2.jpg differ diff --git a/test/test-suite/images/cd3.1.jpg b/test/test-suite/images/cd3.1.jpg new file mode 100644 index 00000000..84dba311 Binary files /dev/null and b/test/test-suite/images/cd3.1.jpg differ diff --git a/test/test-suite/images/cd3.2.jpg b/test/test-suite/images/cd3.2.jpg new file mode 100644 index 00000000..68a0a2cf Binary files /dev/null and b/test/test-suite/images/cd3.2.jpg differ diff --git a/test/test-suite/images/cd4.1.jpg b/test/test-suite/images/cd4.1.jpg new file mode 100644 index 00000000..5e52b0d4 Binary files /dev/null and b/test/test-suite/images/cd4.1.jpg differ diff --git a/test/test-suite/images/cd4.2.jpg b/test/test-suite/images/cd4.2.jpg new file mode 100644 index 00000000..82faa76d Binary files /dev/null and b/test/test-suite/images/cd4.2.jpg differ diff --git a/test/test-suite/images/cogs.png b/test/test-suite/images/cogs.png new file mode 100644 index 00000000..2256a6a8 Binary files /dev/null and b/test/test-suite/images/cogs.png differ diff --git a/test/test-suite/images/dispose-background.gif b/test/test-suite/images/dispose-background.gif new file mode 100644 index 00000000..dfdac192 Binary files /dev/null and b/test/test-suite/images/dispose-background.gif differ diff --git a/test/test-suite/images/dispose-background.png b/test/test-suite/images/dispose-background.png new file mode 100644 index 00000000..0e29eb52 Binary files /dev/null and b/test/test-suite/images/dispose-background.png differ diff --git a/test/test-suite/images/dispose-previous.gif b/test/test-suite/images/dispose-previous.gif new file mode 100644 index 00000000..c4b5b0e6 Binary files /dev/null and b/test/test-suite/images/dispose-previous.gif differ diff --git a/test/test-suite/images/dispose-previous.png b/test/test-suite/images/dispose-previous.png new file mode 100644 index 00000000..c2231022 Binary files /dev/null and b/test/test-suite/images/dispose-previous.png differ diff --git a/test/test-suite/images/heic-orientation-6.heic b/test/test-suite/images/heic-orientation-6.heic new file mode 100644 index 00000000..b5c44cc3 Binary files /dev/null and b/test/test-suite/images/heic-orientation-6.heic differ diff --git a/test/test-suite/images/rotation/0.png b/test/test-suite/images/rotation/0.png new file mode 100644 index 00000000..4399557f Binary files /dev/null and b/test/test-suite/images/rotation/0.png differ diff --git a/test/test-suite/images/rotation/1.jpg b/test/test-suite/images/rotation/1.jpg new file mode 100644 index 00000000..0b48be31 Binary files /dev/null and b/test/test-suite/images/rotation/1.jpg differ diff --git a/test/test-suite/images/rotation/2.jpg b/test/test-suite/images/rotation/2.jpg new file mode 100644 index 00000000..ca66e666 Binary files /dev/null and b/test/test-suite/images/rotation/2.jpg differ diff --git a/test/test-suite/images/rotation/3.jpg b/test/test-suite/images/rotation/3.jpg new file mode 100644 index 00000000..5adaf698 Binary files /dev/null and b/test/test-suite/images/rotation/3.jpg differ diff --git a/test/test-suite/images/rotation/4.jpg b/test/test-suite/images/rotation/4.jpg new file mode 100644 index 00000000..395942a4 Binary files /dev/null and b/test/test-suite/images/rotation/4.jpg differ diff --git a/test/test-suite/images/rotation/5.jpg b/test/test-suite/images/rotation/5.jpg new file mode 100644 index 00000000..d0932af0 Binary files /dev/null and b/test/test-suite/images/rotation/5.jpg differ diff --git a/test/test-suite/images/rotation/6.jpg b/test/test-suite/images/rotation/6.jpg new file mode 100644 index 00000000..d0119693 Binary files /dev/null and b/test/test-suite/images/rotation/6.jpg differ diff --git a/test/test-suite/images/rotation/7.jpg b/test/test-suite/images/rotation/7.jpg new file mode 100644 index 00000000..d5a8b82f Binary files /dev/null and b/test/test-suite/images/rotation/7.jpg differ diff --git a/test/test-suite/images/rotation/8.jpg b/test/test-suite/images/rotation/8.jpg new file mode 100644 index 00000000..962e6665 Binary files /dev/null and b/test/test-suite/images/rotation/8.jpg differ diff --git a/test/test-suite/images/sample.hdr b/test/test-suite/images/sample.hdr new file mode 100644 index 00000000..ae27a6ff --- /dev/null +++ b/test/test-suite/images/sample.hdr @@ -0,0 +1,91 @@ +#?RADIANCE +FORMAT=32-bit_rle_rgbe +EXPOSURE=1.000000e+00 +COLORCORR= 1.000000 1.000000 1.000000 +SOFTWARE=vips 8.9.0-Mon Oct 28 17:00:41 UTC 2019 +PIXASPECT=1.000000 +PRIMARIES= 0.6400 0.3300 0.2900 0.6000 0.1500 0.0600 0.3333 0.3333 + +-Y 442 +X 290 +"䋕濰䎑Ⴭ더ƻڀӀռӃƲ튓Ѯ܃ݰ敄􅍏"兊怃Z[TXVXWVV]d_WW``X\mueWTRbhaXSTZSb\Y\_XXaea[]Y__`^XXadceeg`Y`\_\XV]a[Vvu]bacWPZaU`e]UVW{rfhWcd`X~yegdWyXe[VWWR~thW^^\XXW[h[RX`dbb]_WYbbcbZX\]YX`ffbddc[S`aac`YUQTTVVTSTQ"RTWab^X[\cmdWYZW[\WSXWcwXQT++(HQT)**)*,.0-)*.-+/:?5-)(^[NV43-%(PNK+0*T^/*'*,$#)01,/04231-/871-)+--421+)&-./1SNNQHEJI;J../1*(IGCZ[Y1.*6:IR[b66,&JPVF6ETR&%F@CA>PNJDNS15\MOT>IL,2+('KHFD@;A`92-)DQTVQL-5*!%% NRX~_pL/'13-&*-Z]bTP*-+&-././.52WZf^MV14003/++.0++051/-*%!CPRW2001-('&FT-)*-,+*'"M*(,0//,/39<3+JV3,%'(((KGL)*9K8L,1"Ž䃂뀌ꈔ̸̢̀삀|񎗕ߺ轨󉑒􅍏"䃌̀VVUTXXW[be_TV[U[zrWR]hn]SZXTVYXXVVTV]d__db`ad`WUU`fehg]Zd^^^WUT[UUYY[]Ul`icafdXXSaia}_ddbY\jhbU{x|d`XVUyƚR~X]dcZWYWUWXbc_WZfhdeacXYba]aXX`eZU[cdcb_^YURXcigd[XTUWZUSTTS"SXaccc`\dnjdZY][YZYVRSW^gU*-N@KP%&))*../,(Q-)#.GX=0U+]SGM.7<0LC@H+,+),-++,) !(//.047674-).54+)+*-60**((*+*/SKEFRQUTUP*,T-*)LC&L\Y134L?Sf\d:7-.SYYUUXi_-)KML7.CP'%&),.48);;&./-.42464+>F/1)(*(*2,''%)O%'VQE1=CUYZPK%JB&*)O7!BT^.44W?MbW\;62.XWGR2_eddWZYEJ/+WOEHM//,VMQ\OJQ//+*)NL`MH(1357-Mgf\MDF%#"('/7N\4b_>/.-+),.,.3764-OJ,43/..,+LQl8XU2121/&%*0+((/1..-($%'))032.-,)//g9630LHTWV"ON),231/2/(&/4-*&""#%&'QNHJ())%EFB"ܷ텇Ʒ׍ƬƼ풆覀;ϼ惒Ų𴘣劕񇒘ǵ⊔"ւԀxe\qW[\b|WZ^lXYXnyZVT[e`X]iW{[e^XYVVWVPch{y_XmTeedca]]a`^bXZVSa]UV[|W[Sr_phic|b]g]Xccabd^beg_cXw_\ed\WOU]ded`^[WRSWaf`^ZUiuhY"W_e`\W_YPOTY\WTSTZZT[jfOP=$+H*-00QH=BJ8(X4G]B<7GMEP-0,BNL>8MUFA6BR.4856/)PQRP)2=:2UL=Kb~IG?dIINKQ"OED'.24.-RNEM2.KM)(%%MNKLA=M5:5M@J~"ß޽ׂᵲʩϝ龀寧텃⫠ʴҸ焎й༴ƩܽӾ扊Ժ􁉉Ę㹞Է݁"툅Һք̀flxpYXbdY~Tuu~{adVVVRVUcttrndl`R|dgi}XkYVz{~QadstxaVpyk`_w~stU^^]TydkyxnxRYX_\UVT{]e\yRTpu|vYaghZXrxXyqqxUV"STUVXXRRSy~VS|UfUykiS;7DMVX*=9a]P/-0,NBKSEAEGR+NOVQC@MYMTQGPLO]Jz78(',O&')NL@Gq=DN>6/,(A9MQHKSKEI]F6=+4JKIB6!C[j4)L87@G*19;/A?FIFH+??1@?ADAGI[\QABM()"&E &+--'N(*NNcW>DQ*'JEHGIB48-;7E,/0Z@3D,QNZM=;@E=C8:JJJ@)-Wl6RNMS\ALN,7@KGKJJMVD:GZ?CLE7C&6LG@EJP>>JSKJADIHOR',*#&GM]]dr\4(:LNHKLJXZKCLMPC?JV1`K>DKG:FNTBO./FALF/%>W86[=-G6Q*&GY>9BJ]ZTYMX]P>@AF.9-64CEEDF,.K::N/,"*)%,/,)#F(*%CWP:7CHACHJEHG7-AYX;7=~~"逗ڤ󍽃Ǯ齗셎ɺۄ;Ĺȶмèͱ̽ꂂѶƾ܇÷茌ҭڰكԦՀ탅˾ց"򃋃ӾĹkuZvqWi^Vjjic\Upks~cksV_hhWSUurn[hjyW||WcL[]Us[}XXa~VggXTUX^c\][d_Yxdf`^UjrXcbuul^}bmnqwU_Zai`U_R^_"^a_k`RRRUPyVUhf7?de-?^F2N%:4M58011,L?ERWRGN+LGNXUH:6AO]^IC21;7@N*3;6'%(U?22:.4mFF05NNC;F*KMUGE@:IGBFQ-JJD(&Jh8-VR^ZVhb>IHPMQHEZfMHW.DWONPmJ@I;7R]4*/YQELOYNNRT*4+ !R*1[48fF>MTJLVXT47aHHHLGKP274,@BTI18IIEN-746><`TG?JTTADNNIA;62;GOeX2;DJXWU057*(*dG87'';VW8+HH>CN+RPL>@IKSP@J0-MVW=.GZ0*-/,.XSECHJCMUR=BS2ROn2T0/+/nXINS@W3124RC?CR]UPU*1;/ 'V.0/10(PJT`YTde,15.$'QQPMZ352/NSa[F>>>R,/*&'MLEBT-/3,8.G_P=5N47X>DKMY2657:860SEDIIFFITQKGBCBHQc2=LJO%%E,(/1GFL??>EVQIP(/*(LY@3,&)HaiE'5K<>JOY2ZH7AYR-[SS/(J\ZG($?@+!0?FReh9!6G89RNTeUC7QhNI.^0/%EC=%Dm.102,eY6MZ//ICXVXQJKGFQ//3522T=:SK@R2=iROL>?AGMO,RJGCH;E&+.)MPMIKFR/1.00*)+-,RKMZ0+F;H>FHLRY*().01TU)!N]j7Y<6DHRQM13)RGB<3H+,041(),Q43UV+-*+'%"$$&%@+@9+(OGQW+,0",&##$%$FI)-*(MJFHZ/*%%'''N(RV[[RDI"򆗛εہǷᾭ̀盂м߲ҳˠׅϲѲɀֱϧ񃄌⅁̩숊ۊܾ׾񊒓ᶯ򊔐􄑜"너񾵾]W[ebPyshsWzVRTWWWX_cTXajzxboUX_JnXWWTujXrzguhv]~y}[bQnXTSUxw}y~^oTlqTSUTTSUQVUnOoW[Yd_Y\Yha^Z_^[RuY]WZTPPVXWV\[XTV\f"_RPSURRSTVXURRSUUSPPR~&(Vc/2/(37Tei9I6OOF @T*?8GOG>?(&'&''I/"801-,EMXG;:?EQKA80(;C)U0Z(:F>9PDPbWIEUqO>S---'E=;#5fN0'6`6\XJ/T_>:I;.3,PVaH(EEBD@'))&*/0)LJNO-'8-9$9FSUX,(#)(M@G)(.btD3 ?U.D;MRFEE''*'')R<3;Y5GB$KTPC>SYVPH>?>CC%'-W0NIEGFFAS1XMJX7XJW,MIJEFI>9G@<6.U74VG8`fNMOC+/)$*3]A','GEF2*CM^DEI%FHJL%'$%)OHOW'&/"+!$&&%$#%'+NDE79R,+)+00%$,J;DN]^-~~ +"􂆍巔牑Чن爎݀܄ۿǥۅꆓ݊Ĺź嵰پ汘Žɶ㉈Ѽ񄆇"θ၈^bcc]RWY^^[fZWDOSTWWVXxg[tW^UT}vVX_`WZbZZ\`[a\dfyYfYWdc[WbYW_]XWaVQ~{QTwdXX\hX\vYzWWTUVWZXWOUVSQUYWWWTRP"RUTTSTSRQSo~YOSqWW.2/--N!9J//..0PA>;9)$G@KYXL*/)$,45/5,-JBO5*&*3L?=CMCGGGM[nZO=2E-/M7;U`01-./-,$R`QLFIL&)&%H@?JMFBJS++()-LBN[*$""#%%('%%$"D#C75HLEKQKKUBI/&*K92BX10"놐̠؊Ȫ̹˶ṻݸ˭ذۣ넀惂٥θ˼މ눏膑󆋈"价׎ܴX``_V_d`a\s^^]eVTXWXZXyml`aZVh[{s`\~ZQr[qrXu_s\Xd]aka[`e\W`^T\]XTWXTTSVTVSUVWUnetpZVQ_~W]VW][X]XVOQUTRUWUX_WURQ"SUTSRSRQRR}}crWQXVwwT-30.+I90L20,-*QFFCIE3GYbLH(I62@(('(,0D73@EZd('$%TAD[^PUIK0!EU>?WqSjyUHFGMY3,RA8YQ8M*%HPOMC:Ve0J78_/L;Z;G3H-+DL/),44566-.\N_4,#.2'@>GUNVQ>?#)+&O.[LPd+0`)(*/UBL\W+QFFJKMHIMX\]SR\0(D79GOB2$-FMPWHBQTNRIDJL,-('-*IS`+%""#%&++*'"##@3+6JVPNND@M:;)%,)K>>PY-~"ރ侘惁厙񵮜ՆͭۘԅĽՈֵܲЩ቎أտ䆌ܫӼ̸쏓玖틍񆉂"ŭϸT_eaU^c`_Y~`btVRX][WSumbcu]\^[Tu~Vl|_rteZTzyYSW{pdfiWn_Wblebae_VXZSZ`]USXYURVWY[VUVTYZWTTuptcex~U`lk[]ea_]YVX^TVQZ\WXX"RTTSSRQQPpU[[+110+G51M10/1+J14DNOBWc^MJ)&=:E+)'%(U:(&?GX1--)(W;;ZeFB>=(96L28GXMq?O7BDKL1'QB?\H,J('X.WXC9k4/PU_@/=1H9ZQKIQEAJ&-75598/0h]`.*$)2*P?BPN]MC&$()''0aUQ.+.+'(P,SMTXIK''IJRULNWYZ,+P[,PHA?NVN:44S[\WFL_ZG:89J\RBLT-aVS:FG(*)SX^^\ONMKSQG<(+(+/VNH"(''+,*%$"E=)'DT^VXVNHDA5A&),WK<=HN~ +~"䄑׃߱Ѓˀ絜ǽɯہ϶۱؄킍񆍆߿划偂ڭ̸܁߂􁁀"Я肊㄀`mc^e^VpMXoVZTTQMRX[ZV]RGgRxbh~uswx\ZaVgu`rj~yV]Pw`]YWenTzcZT\ejg^[ZSUXUY\^`SUYXUVV[[YTRXVVSOOuruv}zsZRWX`yXdfcc^U]eXVXXWVVUUW"SRSTTRQqrWVTQUWWTV03/UG87L4435WJ$/=T,1.KJQS(($&)*'$?@2!!Y;JP^[+NM;.?pAcHC=cP=)+(FFQE@Ho/)(.E#(367;60+VX`,-')-01FBELKK*YL),(&/eSM)'''"$PWQPTMCGE$ILQSOOQSJ%##%,_M@=DG@HDJNNQGALW/OLGJGEF= :_v|FBPaUB!8=B\,,/0,.I/Rb91GIP7?XD48u2IP_SJ)''15260.VFAS-2'&>\5L@DJHH_TIF+*)*\NL&&'(%%MOQPNOIK>AHLNOQSUYNE:@W^PA:8'@BIK@AB>BNOR)`URF99?N(+./0DD=TENE&)-,8@.@M46OIG@:;B;(B[0,/^+%LJGKZUS'cK=A"MN*,+)'&SNFGHHW-)'$%T_RHHGJ&&LHAJ+~~~"㺽ұۀɲ½ͩ耆Ɓȡ鎅˷реžûڹڇꀆѻǹ"ǹѴ샃ν℀gmSW`\zamsTZ\XVSSLe>{P^\VT]_TQVSfzlQZm{xrakdn{ztuYNqSR_ePsq}]dWbToo_WVXVVYWa^Y]ZWTWyTWQQTSQlt}}~{rtbquib]pY~w~TR\_cRVVgiVQ~d{^\O|~"QTVSQ}pZ[WVpfrUUR1:JLGGL.`342PF<#2>H3EGFSB?L\H8+7I>M(&/3*3@D7OV02JEL1;.J>+0d.,55+**)00.1021WG?GK,Q;&J^I?;=RRIL.bC833;DQ*'+-P\4FmH#&2216VH]aR?-9CGHG0(@]+(NNH#IH@EJLHGXP?;"?EU+)+*'NMJUSDIV)&$%SN?;GPT('LICI(~~~~"ܸ슐̿޽ƴή復ݽ򌐒Ͼîúい߲ľɾնꂄ憐߻徨ʾĻ"ཽǁKAS|[^`\][{ch]bljUlSX[\TORTfrvSy}hNc{qYZYtzpo}tkz~{wstqT[QfcnY[`TY]c_WUXV]WVZYYTzg}tuRRWRyttvulupzw~~~umccZUbtrgmqgYWwSRWY_m^no\_z~"qoSQPcfTURQ|cXfUV#!#8N-1;:50YL6Nv<<67<;$"&EN*-# &*A&080WO7 3D:.62EU^7?KUC7@B1NYbHKL84FC5CLQOIO->I=(.$GRI+44A[1XZ/5-NJH@Z+/;91/.+2024460NIJA?QW:@GEBDIF@NNL@EO(*ZZaaKBQ) =EVCEG;G;E?GJUplw{S<@CIID95<;37GC./EWLCFJK7("3?BE(&&::DISYF22BB+0UOLLQ?@-&>IJH80G1)ELJJB>EB<=LD:DEC;"49D=E)-)NJIOI=@H&&%%J;-);IWX+VJFH(~~"ؽƃ͐ݽǧೣѻڻѼƽѰéھؿȦӶĴ"؂ٿ㽡؂\oUgd][`qiff\w_b][UOSXTTTgsh]lw]jd^{sTUVQUVyt\]VUVSVWW__YY\[XX`ixsnyTzkV[bkvxsddcPWmfZXiVgtvk"skitQUt^UzVUS{jbXTX<^KA-269:\GJM:I:B=853`I7,.HS,+$$)#:D>ZZ;'APJB>/5D_IO\X90DQL]rkON_YHOC*3N]]UQ.EP+()#+6oL=>HJgf^-.+)('U,)+578b]Wsm89900Yb^ZLO`6,BGDEFB@?BIQNKMKLUZnoVFJ:T\IDC01;;TISooH?5EF=:>H651BN(%!::51.A[d1MRGO4=?942QKJ@>AA*,$&+*K'1BN\JG\QRJI(#8XM04\@@U\LK5aNM_10X?$+ObjQSGP+2.(&3/1/][^Jfh`/*.-)(,2+)07:.YJinq:8*.4=?tOEHDMHKMMNE<-39SQQIDAB_ġrXE.dfYQSobXH4CDCDB=3ZGEA<4*(@OI#*-,O46>I[TD&Yw8SI'',33-.3/-0^bLG6oa3.00*&/54/,1YedC]`Qm͎ޮmvު{cɾzRABABHIMZHFSd691)%+adQYju4+LIKYMOUR)*WF1BJ?>hbMNe[Hda>>\_069VH;14ID;>ITUQHKBL01,R"LRbWFI$GGS\K22=K%$''%'*121.1.P@9B%~~~~~~~"ĭܳǵӯܫʛˈɓ惄ܿ{kmmįө芏ဇ́󀆌讌憖"Ŀ̶鄊犀_fkx~dd]eWY_oxweRW]sY``Syofvw`a\nclN[c{X[dgWQX^YVYwjVf`a[WTUmSWtUVadgkixzsʀt{yĄlĪVUthxZ_]a`b^UX_hdU\]d^X[WXZ^[\wVjb^Sb[T"SST[`ZUTXYUWZ[WJ82B=C564/NI \>;/F %,.11?F1*gRM4Eegd5-1-.%+gFoK(v-4A3YX'*58B|^4vIRKFAO[?.HUftxfy|wݻɆؐUrĬ}\J 9IJTq-))0594)MMVZW0472TJAINRheSO-1W>M\LJ793161IRT=I-5531E>*1@OPMP\2.SLRGF.81("MQWUJFD5;DXK9AHR&'+/+''',-,-.+J89"~~~"ه䱏ղǀñܷͭºۯ󉉇ʖĽƵlk˪ȿȪԊյ테ɾӽ"ЀvuZ^GL]ahqv|dZky`TepuY``RraanicSX[VvlnSXb_ohIWV_RU\VSQ~ioposc]YWZXRme[vb^b`TWUZatֻv{ʠӿxj{Z\]^^^XSXW_c\a]fa`YZZaddeX|_\x°{S[VR"SSSvgoSRV]^WUVVWWX]VSHB:=M8-"+53787H[dXH)%!'4'"5@ADMU+(#$I?5.6G?&1(*-+IOZWWZALQEA:2GP3),'%?3Qj49&:"(*,-Ya5?RKC;?SW`6/-*.&)JujIPyi&,bz1-15>rD%7:9EAEAMZ,+Y-/,&##$(,..)?;$~"䅚ĤΤ󌖉ƩȾʺᆚ毑ȡ¿eo|{zllȻ灈й迱牊ڭ̭̰"ë򄍋\X}ethLjhecfxjPSoujp{Qv\QZgdoW]Uu~y|ql{{YKUy|wS\VRbZUdk^]YT[]hPa~SVUYbk_br_Ynuy}jxwu}̶yW\XUT[W_\V_\Xa`ahYucvnma_RVTQ"SUcXShXb[XXVWVU\W-1JIADJA%7?;]H;$()'I>@??EICa턉{Ʊиrol_adf_-,'+,)>D?AAI&'')SQMOTMHANSQhcOEJ?7:M_5-YXRO/31/93N4&3=B2?MT^RE%6P]b0.*&"%%C3>D1-,43DBN\EB)--J61&"3RFKa=XVRIHJTs?S@5<<-7BA+/*'8#%'D8N]>+1@Q<8F((\A(*29D'IDHIVhP*31.-PGBP\y-/7@9ZNFNSTr?Ag[?6WL3FS160*F#0'-!DeyO;.%#6R((\eJ5+0'HdJ]j:0PRMMNKSKDSpR29EBLIn}}ԁ٭繪~j^VHHH?=;IQPJ&OQORPJW-QI;@GHGJ^9P:@>E=DMNVWOB8]Z,0RWNJ?M]`D3,0'C`m033+"'%G4.478CARX12+>8If/CB~~"ʬϵ󀌐᮲䄏ײà˻Ȩu|tjز翮Øр"ȼ乵~x|rlx}}dbuy[e^TTY[X[XY\dfchdTw]eZQ]PrxgbwmRXu}V`gbim_swyr~ux̾zÏᵤVUa{xtYWdiXVR]eaa]R"PP{\Ykjq}lo\~PKGOJJU\Y^V^pihaPCCGO_plS=6INFBEMD:FO57RLJUVO.40*+//0lo4,,287UD6JX]h<74Z74*\IMa36/)H2>,KCRx:/IG9'=Ya,.eZEZW7CD27A7[[\UOOOYUI\lH1BHCOWӴŋᴭqbUOOIF?>LU[SPWQQVREI*/\F?FFFMc5I9CE?CQeg53bUNQP,5LO//QW]UPLKJ6%49322,"(&KB7Hjk@&+9/6AC@2,8833=K(F:)6QWQV~"ʷǺ½î򎛒ѮաꏌИݲ۰}ᠴlھؽظމĿ䊑"ػƸuyxt|[~sw|zvntZd[SX^cd`XYWdqjv_j]YXeng_PdPWnjbV[bjkhbjoqso[s}{鹳ϯTUv~QSSzj[h_de`[u]dcg`fhibbZQ"QTSz~g_v}L:?RXNFMVLEKfXTJSEJJN^3ZHA?=<>?BGF>JUFGs~HvbwyAA=:@@2_RdRF[bxgolbLDMMHFpڃsu݁ϸ§waUWWZ-)IB6JT^RQJIRZ('+edK>FAHMefD;>03?T75550N>PS@Z\386^^RHQ]c4)-140/.&"'MLLM[63E;CL2)8EIE8F@B=6>IIRJEEN\S + +"̶ửꍛ ၅ǐ嘑㸗򆩖߇Ǜ跰ꂄﴘnä脄۶ɷ޷䌔ڽ"޿ķ҅ڀgbopcjnzzumnjkbkzwxmr|yvn]Xlu}\dURXXUVcxj`pnocw[ZedWS[l`veTUzngZWTVfb[gppgjwtqlv޾}UTi_pVQPwlh`sdfb~x{f2dWiihgb_b[SQSSpeuWWZW6&,DUG*#6:=?H=96<4:FKRUOG97-6GF@=KHNT>.*:329M13(HG('-_X@*/N:PN,&9?0Gi8155.: '[83WG/727JPXg:3..)*[[:vs_QqcetAa55;?:cRAVNHkzrkiZZQTNN\|sž耫}gVX`a`+*L>$3RYNLGH[2)'QMNADGBHM\\HC?4Da533W@8@IOi513057SCHP[1.00-/3/%"$FDDT^30G=OW;4;IPPNTUVO=C)(-.PLRVK"ܲƪмϊڼ隙劂ȍм𑐀⇹؞Ƃ׸x|Իֺ̿IJ﹠¼̻މʲ򔛔ֹ憒"﷬׵ϸdebujGMgzhUWhm_Va|q]\_ysTQJRW\eacO~}t`T_TOd~hYnEI]\v^SjpkUPSRuqorm^VifYjsys~߱ўtSK[x~zXOmrl[u\ba~~}kkbbcWzaida_^]ZS"qbqu}STV&#,=MH$!8;,%)7?7)2IKMTKC2+':JLMGJGDG0-!! #P71$94A7G>6*IKF\y6!$44#=TY_?4W/#O48S>!'Md@?Qh@;-)&&FAZdC@GGYab1D+751RD@PSHQibaZ`^w`KYU\w{DzѤեh[_jdYCNL>(2EME@:@^7S$E?<>RLGJMQYMTW$";X433S=P/'./3-%I8'&ERbUAALT@81'8GHJMGHC7+-@:"%/7Xx2D3/2783""6JJK<%)9;9;DN^oR7)#(@TeD,"D&;=3EXv@ZH'HF55M,DhANRA&1I4nYAHU`T8qEF?BNKKMOKTFCK958/R4-UU=:47Gb741.20H(%5P541//.-*L"F*&?FE@?AICGECSklJAFDFMNJEOYSRWT)~~~~~~~~~"εԧ꺧ֳŷҶģ׻̼޻~׽|~տĵýă爔軴킎"Ÿű퀂}|WJpcW^nrld|cZfcWTlu}b^Zb^}xnobRSxt\`uo`khfeKDFDgu|uy}RO}faqry~Xay{~zjeqrxmк{st{y{cZQj~~hPK]\iia]X~wWaf`XWWT"_Jaur|w{TVWTnKFLH,$CI+.u@G<5>IMTR5,66I_0OLD(G9*;YkG4..BV\RKF7@F?*EJF[VC;75'+BVB,;LMO6:28=A?(&'#7OL6??FhLAL''BJ>Qc\ZV@O25AHgU9.HW\[@AKN@7IN@>`nNxܚʜ٦żsthyyg_VHDOVLM=GHNLHLIL?DG9;GOKRTP><;?7,4v$R8LG^N?03CO5><63.K2+?J384.,.3/T"C$0E?7/6;?BGF9@Y\D6CMN_1-OL(KIGI]veE268$O]jcJL_Y9IHG\^XLGKSMQgM:5LI,M@AQdV>.,5,L{hT^B?QIE^)(R\M7R2@[GGEG^L*IUNLRXQ@D?H?3EK0OrH>lmOB?AFYE@2)+9OWJ9%2@8;AKWT;9GLMKIHKM~~~~~~~~~~ "Ϳڲޏ׃恉յɮ̷̽݀ΪŀՁ}|ڴ}z}zҸ̾傉μݹ䃉Մ"븖tMkvhd_PVVR}hWgom|[^wXVS`Xw^gxtV^eWUaTffgghdqdtzshq`hn¬¹ouoicdj{xwrtrpi\i~~UTbgSU{~|bXzeou}^dRnsTTY"kj}rg|kr|w>8DHEDKSUG4 7LSA93S'KMLB9JK%&&C3>O^YNYgaPRD"%==;MPWs`E..<9IKD@C^cg-""MOX739058?OC-5Mb2)0:0C:@P7336:\K_jV^m;90F4FRFHMiAu_VLF[VbO\}߯ĉcpoaMXqxl~ukd[S`y{jYH?MORYUae51P[`ONPCLQFBBCCW78YIOJ')H?==3>Z/&C7'7@>N28,`>;%>CN,QIGK3";9XCB@>:+6DLPG;09B;BFHORLHKMLHD@:< ~~~ "ھ攷߀򀊈䳯ڲķՂíۆ뀆ﷵヒӪ΁~裉~~mʷԾݼξ"۸igUxhXOUURUXSry^irUVwa^uzvph}NR[a\uuttiH@CjZVZ`U[_cnqeW]ofk[acT[׶hqՀ۩nrume`cpxoqhnmkkhes^ZWUUxVYxxY"djyynwwFHWTEC3*L+U<<@?\ea/B"((%E@KQ&(*G38QZVJLQVNZW>&7C$":D\SF1*>AE@30-CA>) (-b>FJLOJJ[">TH=B*++ICPU)-.E;8IULBEKIHXTG4FZ."*5CGC7DEA:792,'&+;&%MleF647?49-=05G(23(E?CCC+/128gLJG@DF63N)A*,Y2K`jgjjbeT(UZkbhϫZQQSLHZ]aWjmngXWYX]ubf^XQFGMQTTcr2,[VT\U12?9GAJWb^QKOBKd`R*LLW-J;6;#,OEUUPYKCF,;=-#BGGMZSJS"AdC:4=>>@H,-PA10;CRYY*(),NGD9DTWS)~~~~~~~"󰞍ĮλҰ潕ϻʲ½ʴڿŐ瀏ܻ¯Ŀ̥򁇌ֿ׼߽݁񻮿㊕"ŷܺŹU\d{iOmBfdO\|VUURX~r|h_^~zOCF`Ru~burP[aZUUYT^Y_tZZ}^Wbquqjㆇ}ƣ|qi`_dZ`X_]aUVY_abbc[pvhy}tlv\n\W}sSWUXd[S"e{ruVV|UVWVVW)-8aC<$3!=XcT]N2"*9>J+13)NPPQ1ZGmUD3, 6OYFKMH8EVC!%.> :;HH33EHN9@P=BD$,/'%(/G>TSS+\WEGB+?MO7$@ai_]ROFIb`NLHHLO'NIHP\T9BN+-RHH;.5.#HLNOSSEA"kHj/06O^TE=6?DFIQSLKI7?N(*QJC8E2,DCM,.XOK>"HRD2513@JP&#YWCAF'QTPO')))%:;O[RSh~~~~~"郋ةꋉ̩໲㶸畑œȩꅃ~聈ҽͶ翼䄐ŹͲ郏ִ"肃쀁̯8U_}vb\cdVWVVV[WRROT[^Wm`WW]Wxmf|~cYQRa^[ZTU^{\cuiv}fX\ދ|}`WT[VYZZ[Z`vfdrfnq|wnvdvzVvbXY^TOUimXQqSZTykoryhbTTPQTSWW[XT,7JCI75CAPYc_nj[IVTO),-,,2.$#$?BCSSTZ[OJ')*,]:'CZ4okRScYQY^UE/71>8R=7E9AI[XJ1+SH+QP#71+-Y%(3^F-AFLr;_Vkc]PO+ZW:9[pi`e93M_aH?FDl}|ầbOFJFIG|HROarlQXcSGKNP[gsg]O>AVfFPPVb]ZE"?w:[0bQ;7L,+9>=J*OKLB-:HL))G>KQMNFCL,0-SD9"9:;HF-(8I(#=_x_DB%()M&'$''$LDAGCED~~~~~~"ͬ񃕙ఢӵߍ꼽цǠ߀􁅐ⲧݞ؂ަἴֻも񈏊꺬"éϿYbwsjvUUTTUWURSRPRSYT\]XwnVWR[aWtvahrfh[X[^ZY^v|ab[iemyokggfqщz谚yjWY[\[VX^fakumhmrvawso\t\bYgU{_`XWRR~\T{RQWYZ{"|znSTWXVTTW|l`/:TIN9$4KXbZ^`SMU_TG(,**,-'$$%&**/WN\dQ@$,,*WC7M[`65ZV_[[Zdt,,3/JInDD9ZB08ai43*&*23.4NK.LJDHN@7:DR`96Uo>9ghjVpKI:?DFFtlOH_aYGYX`i񖔎}ݰepzFHFFIHMVXQ]dQCAHESe9hSJIM;XBN+/.Pdj4&joFK\OA0E11%!JI&KIHJA$"1Ii\KHM+RK%%#(*(.aJ5460~~~~~"񅊉Ϯȼሎΰጌɜ򇏘绳ڿ䃁ɀ耄ߝϡՉєӲ֎ֳ"Ұź]V`Rlmr|XSSSTVXTRTV[[\VZXxc`cdkkW^Y\adZ]ruy^hx_Z_\_Zhrve]noba]WԆz媓h|hWZ[][_fbh|voxc`ptzvff~vV^tq{v^_ZXWRU]Vu^UvWVWWY"vvr]`fxUUUUTSTZ_Xv203):*E*OGKV3]LEOI@7K)(')*('')(-.,(AEGD9J-*EIEFR[MFFJIEN_Wd<0*-YMD\9?h2H*"M_n`OJ0681?YL?;CEF|ko<=0KXhjxߒ}ꮒo>_}IKECHW\VfhWLBBMd29dJNeeXIM,.REErZ/EN3CNK:/E21*'%''-&GIDC2=Sj2-RYUPSVYT(-0\PP"LYO28CBGK>2)_]?v8:zux|?:6#[j~Ɗ|tⷕptVLHBGVZ]ftfXPKFMd8:gYnwjYPOR/0LBEeT5DGESVKB3L3-+-1.57I>??EE3>[DN7+PQ[000+#&,+'K"LUO8;KMPS@("&+1ES..LFOP&LNP*2314dY~~~~~~"ЩŰ̂ϵɟĴ܃湛׃ג֯鐊򉝣θɚ㐑Ùڀ"ӵռӅ􇎈値bQjmyhkoq{SWSUVWTOQOrvTS|bv~xp~}se_ljgZoj]nuefkxT~jybY[fgbZgwi_mv[Ykeb}験m΢{Ʋ\Zblqto_^kvZ{d|pgiixfR[q}XpprkTZp~_X`_TR"^E`uX[UVSY`XM7(HDMZPIbz@-;CP_P+("#%'V+*(JDD>58AJO-*1#9H=:9FOY[GIDPOH85(3;<"/23@C<>>EU[Z/FO;<(145/29?3.5CIT.W]TIL@NlMJF>D:CROVK(/@N8NCISX*/0%$WTQI"GPOCKUTMSI1J#(;H(+(MKQ*&IFE@Q360KG~~~~~"جҼɷ􂃊򋊻ǫٰ݈給õ߀닉´ıˌ؄๴ВǸ"Ź悉ԀwpVzaYnQPTTSQTZ~{VTqSqsWPjtcba~xi`hhmf`_lt{cZu|wuuu\OUW\bkhYavwy\|ƙej|#|}귣l鰫_Y\zejlvu_mzziezzmahQir^HQv[[Z`vynohYcmWWYSplr|lIa\XX\MG=4:QRX\,NNSC10>gxmM$!""!$Q36J:9ALOPPOO.)%0G4%!,8.CUWO65EIA40/7=E6(+BITIB8IU_cPA9=<:/13KJ7>HU3:@1:9;LIN-0(FWUPL"OUN6.4AG1)7>9HTGRO\BHUF&DW_V95D<5AF0/G`GDPO2GWmM3@FB8]L?GKD0.Dm[NIGSHpF9?HWVgCIPF-"-2XL0'WK2LO3(:DKJ8:RD=KWTeaV=;KJ/ASUJ5;\K?SX;1IbPEEA-Jw{W54ELMXxXd_T:WB^WKUMZADIK[n}|ٙpcDF>BLG?GpcYRM`OIQLC;IFM@CN="H=Th^A4AO/)M]WNFQOC>URBFVNC?493HJ7I=7Oj;-$%%LMHI"&(+H3&+,-;AHB3:KG=BD>FJ@>@B>@BGKKO +~~~~~~~"ꅈ֫™⸸ǸЭθ胑贜ɾ𱏎УƯׂŢ˹继ߛ"ٰǫ틐YZ]a^ZWQW^lvg\\]YVTYaXSQji[b]R]Rmu{[^_g]w`gf`bk`{fjnoyhvڔqZWT[[Vyvwop~n{_js`WZqTV_zpOOR_qrr_PUoQp{\b`jl)'),,*'(ZZ0ZM8IO@742-N++Y^20,++JTY:7J+,)%A@B2*eu=G^K?^\D:?CF+-^rRTS[hb[VXVWi.2-HA5Z>36fZHIHVVL@E.T8s_@9MWdUXm:tVNWfxb^^gDRSJUo{rlkHNCCB?;JEG|bRJJRQIOEOUl\\X`P~cYK@ay^SD:IWK?HKNXXSFF-"$3OYbwY7=A-0PNN:E8e>$!IB=@"%3HGH;90!+=QU+C9:;GTM041Z@/2EJHNTT~~~~"񂇀뇊ԾݱŬĶȞ㏐胂볊۪ݵʀÞϹ¯Ͽ"Ȭ߂䲵򂍕؀S]d`VZ]W\y`UWSMX`[VNUY[UlWjtbdsUehh{dinhX\hpm^VUV]db}xxhypΪ~aWWWUW`Rm|xxybdmU}}]p_e\SWcrm"Pc^}mwUR~U]fedtK&)).+EoA?>lVV-&MQOQOO06`L=9JYWHJa^g;rMIRhrONxuRPSWmr}qf~[?FB=::uJIczeQJKTLEPR\p|ngcfIHR>LzdbWB:ED@NUXXb\RPC5!2L[570B./I4YU2=:gAB?KI?E"':td1AAC1:G[1.TF@INO%*116dC6OTIH@F~~~~"т軰Љ¹ѿ鴓հ׼슅ȧ凒찚ϟ؂ܗ򻸻z~zɘֵǯν½󇄷"̹|Uc_RslqZnSXytvrOQTYT{emyXZc}E{ecxcJX|hfXKtl]cVueV[^rUq|sj]j^[ms{p_ZWTYWy}~xxɼY]UTVWYYR\ary]"QUt{bTZVSTVUUYim79(,/(?%7>CV7UNKky16cE43A:FOppOFZcYK!&)+)E9CD.1OQHM:/1RN5?B9U0-edS23/\Q]69S= Ns>nW\_63%BD7EP+25M>_3>OLF=RY26W1/:_T:HgNIS]vzvv||VkHH=;;txOwvm`LMbVLY]swihOex]Sh{umXE-CQGDTO@\sWSBC::88DDA6+EI+:FA94PQGC8#";"*9A?]jK5!GulODUo<0"24+9NOU5N+OnHFD>5Nh3*S(!(FN9IcGCMQ^uuw\Ut}|Ɏ{rXDDnI;dOI]b\^l|wpei~dLW;dXEGD?AKT2/*'$##+H=MYC0NS*'5(h5KNA?IHXSYiWO07W~~ώzvcWzDHxFN[i[ZNO_ccchy}rmmxkLJWvXUBIN@LO\3I?%&'%**C?885:>#D@>EC>I*,9cMN=8Ap"6.301.#%'(0-GG>>MNRSMIMM4." =CNZ~~~~~~~~~~~"չӸϱԾςܣ˿Ŧ͉𬒜஍|yѲĻɳʹ޺"Ц՜π\_b_UZ[^wpzztuWLy]RPSr}vs]]dQWbfZ[kX_b[`fRfckkbWgx}k_huX^~geuRb|dICK]}ןiSMUWXLKR`yqrfkwZ_cXzpwzSUU"zZXYZXUQQRRVUc\}aNt.243/QLM[6rp55YNJ;/]s.A38%&j^/2\7)0`oNBD0/5=,3AlAU?DPTIOD&,L87IUKA8127B,/URR;JZI5[D]TW[O;..E~lܘz|z}cABHDEu@GLXk}aLNOWef`doldmztoB7GRUbPEMHRae4H:J&&%HD;AEGGAE#B<;@NEC&'QX[iV@qi"U4/)+.&&,,20LF93EUXF?IIE":QJ!.8D~~~~~~~~~~~~~"󀊄ܼȶⵯ޿ߨΆLjϻܪ⾧񬩩୓ʽp־Ƴ˳"ٳŝ_ac^VSR[^fkhZX[aơtqXPxvgti`TeV]dZqcz\Wh~~zoitffq_c{TCORnĤkYWVXUMMgtzaet|xq{phgdabVmjzTTTXZST_"|ZUTVa]TRbdulep|||/320-*LS-49>?<212T]eAFSY`:cDRROCPL\82UMjhB7eNFF^mc>G1*0V59hvRKXUFAJ\\[a]H991Πyqr`YE@?Dm=Gum|Sec?BLNOH\^VW[[@>b}ej|x^?;A7@VV>@9FW_eMIW/'#92 -ERTJN(MLIJ(KD$9=O150/901iTc2WVNH`BFeVY`fZ*11RS(>3@GW^R6/3V[M&K]s7796`<8SgtRMD073`MAO^Wfv1,[[UWYA4Lp@5HE5Ade]3@mK;ML@64X4=::-$*OG6>OMY7hP?C@}~yeeUQO~|xA;l>EtusPReiUGMYX[ee[OTEG>^pIKhoiSBBJ;6BXSN?8EHIHLi5)#9734HRTHI((RPS(IFJ=7N*--046"5-B;(2/%ABF4;*GIO5<2(HEG@.9a_+-LAD~~~~~ "Įрʸө񽯹읟责ںඹ򁊂څ怒ʾ炌è˂ϵ닐ؾچ"߁мˇрfe`XTUPU][RzT^_dY^cXTXnviw{Zedbb|lp`hmspnbbxVm_PS^\rjftbXYZjjqh_TN~XiXmoHMGVmVr[Ybuy][}{Y`ZXpxaa|rva^VXUVZWWTZ`"[TZVVuzeVcgh_43/.-.(GINV.50-VV@E)/1ZXbAYUPXd-,2/U-,=0BIOVN533bUI0[k?<5,POL?VbgW_^9=6cFDCJF[779:A>?`A)Lr;2)D+:;xG:PcAF=6na7Ui8>=>431,I4@RL19^IcyNbug[QMxm||{yUZetd]fim<7\VNR|j4i[asl4\KMYZ6]k`,JKD8>LHZ60MLEAORKGAO-)LMRO&',PA'+,-(5:"2D??L0/)MNOIEDIO*>D7+KBGECFe854,F@ ~~~"􄍉ۧʸȹªö݁농ʬĺȨ󂅍݁쀬î̴ָ݁ѳȺֵׂꄊ"󇔙鉍Հhe[XUzTUQeZhWh`|TRmz~sZkeZaXU|mkr_xdqrhbb^hY_ek_bpiYbvf_hrldjWe{NjGFJPMJIr}eWVd{Xrfmkfþbjxsed`WYd}lhqultbbUYXV^]ZSY"^^gid\Vca`\75/*.aH6@PNQ-/*A6((LcZ-`\UMIF=GVMG'LL\.<7FD;KI63L9?6\KU25.)@LH9FQSf~>=A7`ILMB?PZ4;=-TB=1Q:;K0'$2HH;?B>eIFx?VQ?Fa3MGP3:61pSTR?M70)J96LeT3]wJYSQ=}]Z)isNF>AXM8IVR,6,EB=3430Ig}|vrz\H?}~{ylFXkw}]aqybT;JZoD@@AAA94`G;7fV5/+$CJHII7D18-O-,?F@O%AGBHJ*LK(&)**2ON"ODB:CUTHL+MBBKSF*/?VI8+C@%R176aXE5~"ۿσ̨¹ȣ㼱鑖Җ쪖޻侭Çş°ʧdz߁澠ѻ䅏뷔׵􅇔"žʴcdQ{|X\pw|pW\jRWTdblqp`IPv~qikWdaYWv^`i{he{z[_n^yxj{pkx]l袓xΎőxnOWzuHBHKNHJQM[knXRF]RfrrrpT{nomZgi~k^iXecXMWzXVstUW`\Ysa"bVary]SUY[_f]vrA9*==?8G-1RPfZCEMH=LR>DQJ<03!1JX^/N?141-53!$IZ?>dC?4R6<\9.H.:B;B>5RDJeP28]2WkTR[`o,LD/BXWGMX_N;7@NF(IoOtWW95i]>YtvT;D+0GF331;@96932Umyuz}wunaNB={exO?YntrSTjb[RHDgIE?9C?qgW?+@bVGTPGAGFJK8G/57iF"%NXQHK.+DEFD@Ka~HOO3!!'+2/KF?4~~~"҇撣ɷ݀ôɱոԋ谈Ż–ݫÿȿߏܾ́׾ͼï늅"ʰۃ﹪^smmzSt}_f\_jukWWu{}tTXtijg[WY\sU]}nxXJr\xmbyhf`k}onm`W^kbegcuoUusCJLOIFIPV\y}{rMMFŮUjx{qeup_qdRmdz[Vab}nUy^V"`cynfsQZ_Zbhsbtc<\1/?CX@H,@LdMG989)8EN:Jh>;;\=Cc<5K2,h6A9#4Tg4#2gN2GOFV`dYU<:$3NV;85?IVV='..kl`MCPTX.KABDGJHC2?:DW/#RgR";@EEOPOGI14MA64.9GTOphO"%&"(5Y5+=C~~~~~~~"蜃ũ눈޷ѴDŽ甛ū޾̕ժø퀕ͺռԸ܊۳ƹ㾳Ӷ"ȿõνӋ篩Ā}Ypos{Vtc|`ajo{X\rWZhXRZ^kfh~ohvPm[ps|ter{jUYSUz[Rd[fgp~iftĉgq{RJQOHGPPT]qOVhqsxd\gYWeyXdZU]g^|{ozsu{y}"xrxnfU]`h\``tX:-,U`nFJ/?=5Ji98fvCoY;0Jhf>?RY)-YUNRWLHNNEJPLTc1/;s<<_:5\?38$-K@#5-(;FS<3>SaD4:8AaR=*,(*Jyi?P`L2#<0:FWcC>dL\Mq;8IT=EP54599==53DfcbnpXZAd{u|}?XrpLHcQqBPHv>CNQHAKXbR5OQq>h8aiQ@:IXUC*/,JJYMGX]JKB-5MVTQA629;>SIFDE>"2799F\]H?KSJGCDA:COMM9"8(++07\,/YN~~~~~~~~~~"ą۬Ї˺عƕҰíĄ퇮ᕢì읧߀νָ󅆊ïϹ쉕է󽭕Ķ"̹慗еsz`hlld{mw^m`a]Z^\eUwU\||T}{}k_oa\xezlMPzwogompxawt}W^NLMPPOQR`skN~\_gn|~Zaff_azwqbWc{iZeXgswYMOe\rim~"^~{sjs}z[qw[Xi~Mf5B7PY;4H_.1I?KJXJK[rM?!>06UgD-%$=5.6??T};#@D#F}gejHOA<8D2A@Wk[Emm[SM:CGNG+FD>50/5=>A2HPl\imxieCU9V{~foPPZnfDPGsSLKPKNOLXJ;@>39bT\QDV]L;/LecQ+0/E1?IRg[=?,!18JRMFAI>-/7DLOO"=g278W_D7FIMTX]bRJD7>F>?Sc_Z>*JJME09OiSA7=MQGWIRBQX\ZPFBB/)/3:=FBHUhajrjeRC:OCwyoauRRUfN]NGWOPVXYemr3QR\DAoe`iIBROC.D22X/55_@5@U3'=9*!=<*)8FIJaXC,?JLS^"3D;?9GNLMW-Z_[VSOA,$*HYGMXLh;2>D4 ~~~~~~~~~"􎑗Ȳ׉Ѿ㳔︌ᯡſ쀒Dz厍ǫչԴʯȯㅂν󎤨뙪ロؾဋ쌉މк؄ꀁ"׮ȷ̤퀧{^bllWhdbu}_\Z_a\ypUx[_}v~q|`rma~dnpg]ry~XbmXaZVioî_W}udPTVST[uyxllbcRyut]^vyj}x{kmzwsW_[Xd^yvZT}TQ{Jh[eaST"Z]\zZ^{^Zu}}c_jytyMC2AV37?B0H?KP7/U6U9CSPRER/-R'',0SMBLE94``E2#=]WH..OWS@7B@AERPaNQy@E?@ZOe<<932OPk|bhl@B<5`0dDPe4t@cV@DQd1/GH]fYacSV[Ndqd74O?@?0%6:>QFO[alxeZX=4:PK|wunNRajnS{_ZKLN\ezud5;uvPMa`gl]H<;HS?'.*+Z_<7TB@U0(MK>&1K?B%.B,02YOUP&'P"353>6?MTb2/FJMMFIJ73/Oik^JR5,2JB:T ~~~~~ ~~~"Հڲպ޾حϷéؼ셐ÞԹ඿յ􋜨òѺ멃Ʒቌήñ΀ݏƹن򄆀"ҥĽާ΃YSYZglpw}jxiYX`XvgzkF\}z|yowx`jpmhjhfufoWo|Wb[awUuzgIN]\hv}}ttqfc_SJ}wqm{t~}llxǩdiaz}|zxTWopWSmTNUjXZWU"QivX}xfmKFPlW*G>G-3/8AY=.C[N5MS?4B\beVT,,F;C,0QA6AA))CHA1"2HJEBARX[QGO?2F[JO\ng7[fPJoAA=:=`=_tpqAC>1ei0&LLZ-S\af`MR_64PIhra^[BFLXb[m7G1AG5!)=G\`aabz]YV@4BSUJyy}mi{wxybi]j|t[X^TIMdfH3;9[VQO4B_eV@;GUMP.)XSRAE`NBQ3-M^eJE4%e:=HO)/1ZX,I=>"NT0@-FIGFOZVIWzGk1#9XJJ0~~~"ɤȾʳŽʳ׸߾į̳ȶ̶¶൛⾽ȟ̸Ӽ͸ͻɲæɡ¸ͱפι"ۅїγ副ӹbXY]VSU^kifd]rf[XX[Vuyto~VSWdxbk}pazglf}rflbzw}jda{Zqgs~t`{laJKY]{y~uksqhe[Jytvw{|xi^qudc~il`}^aOQYx}|w}ulXbTbtUh"sSZR[R`{skefffjoy\HUm0)IX34'$1aA#0UO3654@`8m7.,,*C7=IVPI6:B734;FB*+2;CKLNOUM?LPT\L78LiRQ>@6DUe=AAOn(9ITRwuN]kq+2MC4JIH\aP?`b53JHjXHF8KSQ9>QJeP4974"%:D_iicdvZRWUKJRUH|wvpht}t}xRr|~zXi]MTmleWZL52N]PUh^@QA;3YlkfC9DBWU^XQNg>1)RhP1M2aT:,?ZV4+9=Lk7\-/+*M>7AECLRF=$OJBDTT66@I8:BGKJB4:NrvL94=F?4(',<[6;?%,:BGSgB?FVS:7=0ODKNDF:[f60*OS@85A:I\I@X]TN././/':FWabae~pPJXfNNJKzmtqwntgpx~mP\|gbr|fra\hjZLLI/IKg[&CdOACHA>";AJ2@JN(1=9',31,0M+F?KkFgKRA!9FTT~~~~~~~~~~"廤ꈏ񫉉󅃄ӹ⃌邇⼥ߌۡͤ댊؈Մ٭ϼιúĵī삝񜩑肂Ѵνރ"Ý𔤣큻`__gjZ|jabhOUapUTUX]UVYSdwfnrmuekee[kr[][`w}ivs][uwbUweai^sq_cqh]KQXXWMJEg}~s`\glfg]x\gj|y~}llt{npx{e}\lyXWZ^u[\s~vveh~_ju|ws_\kcaaMOdaNalWa}RV"al|eSM_}Y[QsdX^ryk3326;1FCF?BS18]0"/AB_\?H,+-THBMURWXJ?&-.S\0-$JOT<8=KNU>+5O8:U@:>FB5/.>J^93*`h`\452:EBE7HLoV/,EUGB@58TMSe52[QN\D*Q;;SNFkqYED>ATQ7ZH[J=LKN78,.<2) %?SRC2+6.#;KI:668$,c"RME/'*FE1=N+CEH6%#2^>O,9@A5~ +~~~~~~~~~~~~~~~~~"ϝ쐑뇏ĪسԁУڃ이܏λļĶ®ʶDzΟն"ҵ뙻˷X^eg_fgmlfcK^wY`\SY[[ZZp]^WdeXf[ivgw|nYVjzbdZapPM\ka]PWIev^\hh^Si][kpxsx~wmji`Ƴhoqx\begf]r{slksgew~sccg_ru]LEP]yjRVWRY{[j^"pkprb]eEG`qkd|r03124P@;-/4X55Q%9QEQ[CN034dZSY_WZ_TMJ'?G_/+((+\SLJSYF7*>Q*$B65?PWTIEOMI0Q:2boGPS]RLGK5ipa~VBHaK==;;6;;P88fK1@^VfxsOC0(*>DH?EBJZ\_cl_KERP?>oZWbag]naZv~p_b[R[nla`bVO;OA=K8]t\HS^dVPAF@A>F_hJ;E>./5/CIA7"!$(/BO<46*"1UM9BKT4=5"_af`C>H_S>>=O;D1@GA:KH( ?;L@G0;4~~~~~~~~~~~"ְߠɱ㎐Dzك竅ɹʺǮǛ߼ʹƹѸⲅijҾЯĶþ܎贿筓°յ⼱ū၌"ù¨ɯ֒얄`]kʅu\[]Vpze`dg^wmYm]YYZ\]\ioTSrUg{[e|y\gȭgO]]WauteiԭVmrcP[Mfvg]]fdW=]Y`srvu{u{~tnhnh{{lee_Y[kuqx~{[IVi_H[scWdjR]fT"~{`hrguzg_hzn_OltSV**YO;&935J30?!&ANJ^9i6451UQUW]WUffU;,,J-()*,/2UKSVF;;E%#@9/DVcWVPRRD5LQOC3EbqkwdP]gGAUvtYMLkiOKS@/4%@38XNBEajly>p^:v{l87JME@AFJZejaXOJJOP?4qk^ghwvrnwvfcz|_ZYVIHUeijq}e[RpgmikRrD{lkdhU`fZBMPqyqSA><%!+7WQGQJ5!8?RF-3EMFf'4<~~~~"ѵۻ댍鸏≐䶐˛˩ǭº߸ȟDZ˜鍭Ӽɭ˹ҳdzܬގ߹üɴǰ㬌我"ӵpWwwnmnriigiaVYAFbVWWVSdbWTefzghzzutk~|}|jFJ^b{qleqb`t|xt^aSm~rfaahra}dZgsuwtx{}}zye|npdv|per`_bae[{\V`~r_siUghndjV"|{jl`[{wWHc|hy~kM[JOOPWF."9_IIh_C256@Ki?@;70&D?DNRLNagT+ B&'*+)L]SKKI=>PM&&H?kKZhN;7GOD2CXTJ9CeqsvZ{\ROM[qjVZ]ulWO:=L<++0LIGW^D/=Z`C!9HV7>7TKALe5*"H=:,-F\p4,K;AIOVRDA"2FIAM5CG?G=/~~~~~~~"ҺŔӅѽϸ񅆅ĵĩґՖ찗ġ녚ȹ˳ºƵˀîƣȵʵ΀哐ѵΎ"սo|kWenjnvpfpbejiXSuVcvSQR|vZ`TXT`yfshY\s{\blMcubVq}tlspZSt}rwv`\o|j^lvuyzg_|wqa`hgvlOxfgpxzyczrznop{ypjX~okxupt{l^b_WZxqt[hepfpe_ijh"|lqubcfoi^qeppvn|V@A;;IR-+H9XuI7?H@6II%/L<@R]WIB4T^d++C^U97|F9HI>:6CLZaQQ{HS;F>T_8#))-572B/32/?4M^Y"KK, 1BE0"$79.@SQ*&BCLXjlE'Be="G\f1/.)-7I$ +~~~~~~~~~~~"ޡńǓͿ䬞މɥ뺭貋蕭웳ナĚdswyҾѽ𶼽򟝇츐õؽΚ呚ߴ"ƭٳ̩ÀԡWPIVpxQja\c[WYYSLLjZZtGFNnpZhwwhjlxmOvvLpzdq~me]frww}vsiQp}sachhirdQrju}͇~tJS]wjVWotuy``{~{}ckzk^l}vposXlbwsZdjXR{XaiKXbxgcIDE]gpp"~|s`lm{lfkngjp`]c\}^b81#!-ZplfaB%E>c=4\Y^eRIahVf8-&).O(!'MVh*(D:GE4+:JPLXF:JcdE5lgB9LJ;(DX6Sd_I0M=MJI:.Tk=Q06C|R.)=KHOUkzqO=TY=HN((+0P-hWU.'!"FC;WZVH1T+$@M~~~~~~~~~~~~~~ ~~~~~~~~~~~ ~~~~~~"Ձ۶ڛ߶輕ٱɻ뇃ֶ°܅怘񊠰hngU|۴ɲž΂ޗʤڈʹֈϽߌ"ۊ꾱֛Xv|WwzVSasy~}wZabmjX_XXWVUw[}j]e[w~YX{lw˻]|ov|pow}稅φ{c`jpvy_qedix½}|SRKtYeceknKZmZez}oYx[c{wk_~rte_]\YndXOYaWj"zgVfqofm]URHCUquwyofvTSaT$8K8>IELnb9 $)+7DWqvu];JS@KWM',1X8=U-+$! $'FXiZauv}~ROSSP[SWWV_wٺqk[MJHEKTBWvwxwa}f~{yqwD9/TLwX>27MTLK>J=N*5gRPI``We_7@>94FAc[VjkBUqPK.HPMNQE94;3)K3P'G-?=64;A7ARM"?;=oNJGs<8d_[I1!%+&%1A58IF39`_ !/7~~~~~~~~~~~~~~~~~~~~"􅐏NJϻʳٰȧӱ·ǍvomLuƹۻ¿郃Ǻख़ˑ܍߻Ӊܫر㯎"뾒ԐѱߢzRo{njx|j[gmcslrXabbq^dYn^|}UVTrQ[S{_ʼiU~ĺkwq̶Չz]zi\ghm\~kgpz˽OKJgarpln|jdX}`foh}ySMXu\kxcbzvyfvsPlvV^vrknkS`"lw`\wvuehgXbtw{lMHhaMu;"XME936IB'2=8B5C[lnFEJ?[l))-3^A+L0/%CFDTQG>ON1+D_^U=$H-L[&HIHD0/DIENUT+VPUMS`iVZ]g_ICT[5"+@"]qhMQH7g>^=FdJ=E9FC27>9>?)"4YS.(RG~~~~~~~~~~~~ ~~~~~~~~~"Ⲑ̺峻첥ȾĀ­ʫ޾ʖ§͉lvo{qSތþͧøǍƤ߁獌龰žȨ"ь֜弯ϡ؟ydux\Zzqcuz~{]d`WXuV]Xzr~aQcUU{Vo{fӵlinr|{rhfp{Q}h^cejw[m{{{{{gwsi}xMUMPaaXU|wmXhusomss]Kgojrp_osxZP^zs_cchk|mLl"zaEgcy}}lcfoqMUz]WsG87KF/3C?3$.5Hd\<:HPgs]SI>Wb,-./.B'?31(E>?O[HIeV99LY[R@&)V2'JG=A.H:HHLS\aYRSNJW`OSTUO=pbE&\Z[vloLHNIST[a򇜯ƽ_RifNHLJKUEdxxezvtzxsnn~wz}z~yus}sQD6sdKWpH>MmdH= #QSB:BOMMc0<-:>! 1CE@267<@KK8<4"TmpDE<$R>\#:nPEXDBF43:DU@#+;BC-,[A~~~~~~~~ ~~~~~~~"곣Úи≊䬥仯ۃٶՂ۾ۿխь¢荩bZicegdf{|WȚĽøڿȩø߇׾ۯĽDz"šǺѪꠀwnxj^]g}fntabRT|pTVwrV^aVU}s[Sqfũwvku{traű^[hintxܡucW_kog_uvw|wo|w}pc[hZSdhWpTTMztws]hovz_Zuc\Uulik_}\S\|[NOSW_aR"`~eggK\mzJsqad`RPdwdVp`imQP<@M@7LM>*+0/=cpjRMVZ44(F*N2hOF$aQ;F-!'$-AO0!:RH=1B`>~~~~~~~~~}}~~~~~~~~"Ý󦏫߅߄ ۴̄볚Ԥ۶ǿ췟ꁂqhhfdbefchdf|{Y{ȕ쿰ҷȽպည١̼¬–¯ڼ˝"优뤮ƄWtYkxWoXeoeoubdaWRS\SU}hX\_lrURxYVynm}hu{jitjljadigddipmqnuzc^_fpq]|rlxwqy|X}]Z|dsqjs`}uux~rglvsZ\sq\X{bJ?NNSg"{b_Hr|d{rh]_kq^rYtUh0T7)EVPS,UE7.8_5YB:/8Z>;1\a^43OH*0SJI++N;+Kh.1U:Iq7E/=ZiNLR.&B:4FJ;;Ql`UWfhQOQN~AMUMLsbUgv_<@PHH@D@GQNXms˾dOONKKRCUqyzubovgZex~y3Yz|FJ_S[R5FMbUjn05?GBPd~}r^GGGDVMNS{QIbiaW_Z9*.1>H96EPRI;?KfQ0("&OT/-FB"LAIQVHC@M"4HSl`7-31W;:3cg]WXWV177lY+)+MQ[m30O9R:1@:KdkXT)*&>BH\daa\kdSO:MBDLLP\df[K=eMVj{TFRdILQca^mjx\MLOKKIFeh||tYn}yjl^xz}~~rMFk{_dgL.PI4DIb8=NPOTjt}gT??E9VGH_}\APflnhQP6:FK*c9Y113:VXPOZSLO-OWLBJ0//1/D.(EbjTE.2179>~~~~~~~~}}~~~}~~"ڽӈ޽̓Ģ䕮ﺤȤrkijispaQX\\^ea\Zjg\]~twS~|hzNR|†Ǹؿ{{x~xpާа帬ù􋋊޵մب"ˎѵ燅zs^hc^aifhVVQUVWWjv]k`X[OWp{sx{{kffkdkdb`c`\[impxgbVyǂXilge_dkeYOli}mye{}na[eioqwsnn}r}||xvknzly|vX[]WURV`_j_XWhr|}yh"bkffe\czWhebgjdbxwpY_kszGJKRKM[YEBP275297Q4WH<6)0V?CGPQROlFpk^aFIbg_itx{{ǒPIIBJMMSCsvm]x}ldftnVU^ry}nڱPSrYm`X_haYf[[_`Y[br[C4;'*2.)"+47YGHIPXcopt<952`c_NMK>1>CGE?=OHOaRD"M/1121..gC,S8.N-/1,-VMARi`@0B.7<>C~~~~~~~~~}~~~"Ǯỳ׺ۻ񍏉޷Ƨ㲕뷊랗ìzolomivl[HRVPkiSVjSKGu]CPTmrovkRM\HKoʪ͹ƺʸous|{ij{ؑ̇冉޵"۸炋mwyiRe|ui`UXUR|omsyZaV[|dЙсzmZWbZhd[[VWYFWUByKY{p|aS[olk_Za`^Qtjyyžtv~~f]Zeotgkjqhpsy{ps{ot{ukxljVWZWRUc]TTUWX[jbTQV[_~cZ^^XJ\Xt"fc`aacYldkdehfijdv\mnGT\T>Bfh@3BfCxT_c&6bfS6'BKQIB*"E[:LKJE>5L]>?;VunE,?9^E&5T>KPXjaG^]nvWV[^jrtƍ峎թKLKCPRIEojoj~mfjtsVcsz}}ùͳ̰adOe``KRPQam>bPPC51FT344-'+78-(&)/6ehmk98,(.3YVZ.E956/&/'4":USL"c60-./00X65Oi4.-0,*-2eSLaSBMS2>FJ>~~~~~~~~~~~}}~~~}~"÷ۼξẩӳİڷ̅״䘀፶Ψʄ鑪|xohrqkdfl@tJJ`cI]=89BDLdn_dq`99DBAK蒜௶ıg{pwg[hs?IKYxӆϰ􉒌ͧ"⊎ټكဝiaxumwibm{oT[VP{j}hM^rwtkVgYtfq}RObJ{bTUE@8>K[}mPYPKMW|dhWS]_\Sblvqz}~{bdn~yy|wlҋczxxd`wBuPO^㉗ntgsXUPZcbhbYVU\mWMHRNZ"[`\^`dfqE_cd^[Y`syaf|jLSR>*Ch7PC>Ut=6>?JkgF'%I[`YP@4KY>CK1C,+&!AC<8DBD@8*!->=GaVy<)IT=-CCSBRaidR[rKTYZht}ӝҲQNFFJJLG~frs}~zzadyuvyzղقo_Z\X`^uIn`YL=79Qeok1*NWfcW$?-8ccfg25/&##QV_0F/'%#EM%*&1SV<"I31+1413rG$2GW0/01-.`ZP:A?Hf=?JLHx ~~~~~~~~~~~~~}~~~}~}}~~~"ſȫȦɥִ։ŤDzԗ懴̶­✫uyssksuhzZP9_LH1[QeJQ>/A{;\Q?@ETkpaK(#>O\3cU?>E7NS(?/-((*M:16AF<5')A;7AYHuAJL5;+SFO?T_ed`iqclzrԿ襚½YNIKIHJScn{s~w|ids~~rrw{󼶺gae]p__kUH48Pcf[+PEDDLVI/8gXfk-/..))/220$>9@0TXAK/@5WE"5'--32.+0I(8F@)'-LGCH<54/"4P=deKRW~~~~~}~}~~~}~~~~"⭢鄌ƯѰ쐔幪ùݹ˩Ϯ~{wu}zrvNE9C|q:;:q38Eshc`zK09;::5=@AEKAF˖ʲ쿲ꁀ뻞ܶ򏞋"񏑌âȭ˩ΫWVT\b~ho{bugqEN}eX^baVV`zxtqmq_]UShzǽSF<:ulkwz?A~~q??D>?dhJMLIGCp_X[]YWVhkvnnxyrnox~|tilpvxfY}̒ibhkmh^r~@D?DMOJJISz~x[[n]a_bijeZVV]\Up|ZvU~"\_`YXT]Uop_mqnulPLRV%#&IGY,.]D69PRDIS6H@\O6 #FT\YXH5''M[:G1/+-1XG;GTH>@@CTNHQHFB55PLIH<GP]YJQ8)'580@=AP;-.*7Wbjn]釆o䐐󇏈ԓLKCGHPE{eWcx|daszsu~uwu]qgzyOj`]SN`ykVPTgPM/1/kpo30996vC:--04)IJM*/TJYZQEG@H/-SW"V1-(Z[V048`UQNN.6V0F;TBaR_fP4&/617~~~~~}}}~~}~~~}~}~~~ "էȫ醐뽱کꁏp}|xtqsoKQn8,ol58r9422_q`n7665873>GAyR`^o\H@<;?82B씝ƼͿts_SXPSc`^i>y497<3;7=:?=?a膔B󀑐םVSTUXTt[O{XZi\mXNQcmgl^o]]Z]a[URV_mmrY[ksgZhfhvpd[TorXlTu89{v@Bz<C3?"EMQZ.3B@WD3%!Ho?CAABzCBB@BBFEEJNOMTWVnbeaWNNPFRZOOylzxv{{|zvw~ww|~}yȧaJ[jvyu~DCIPOHAEKQRWNȆde\\acbWdbiw~eba^^Z]UVWYX~"`b`XcbYZbfcdUpbnpnQgnu|'&%'PE5(HJ%:9JP+UGDPafR":c;8Y^6!92CDFK~~~~~~~}}~}}~}}~}}}~~~~}~~"ӎ‰Ϲ҃݁鉥xrvvk7?84224245399707<98987576D~|NvUFFE==97EH¿ǻĤ}b~peCV\bzlpyutw>=<=?<;>>;?MN^ܮ񁆊½"ȒٯܥWUUQhL{VyTpU}cnamXwgqXYUU[WV]\|gcUVdv~r}kOu[ysߏԚufw=>9?A@DEFEEGEDHVN;%$"IB(,%"&+VA>E2+BPE9QaTA'5>FD.EVbS{Ԍ쮗Ļåɥꨀ}j}k[kkeo}rqt{|}vֵܳ܃¾`HnpY7)-6Ta2/.-<=1T?4BZ50146320050-ULOP*0O767;89:B=<<37356?xuhw[traaDGFB976:?j˼˼ƿuƿaE7ACBACH˲҈¾鄒ɿ惌û"Һ˩YWVQjxu`TTsLfXz`~qwqv~[UWTTU{wsRkqTTwk酲ϼwښ@qh<;?CCEDFLNKKIMKIIQLOTNSNJEIyaaLwfqXkl\QJIKHNuqzgcpz{vuwvyo{rnozۭoMMI[U_npks]al{B>FGaeZRMSRNKKPadnwYX]gg]V`Xef^VVUUZdXb"yk}}wW^cTkPUkV}e^-/-%G>%ZuBO0T,-N31HN%JUwf`QdR@*@G5pHRTUUQʋÄdzƸցpcopvjiyujp~uuxy||~z|Dz뀄珡ľbcTK38>:K`c.+/=<3-R>B8M,"Rc651,'&+EBAL.5N:9L/6R52"60GiK=A^]SUW-.1*?A)K7V+/T>GGfA2~~~~~~~~~}}~}~~~}~~}~}~~~~"ęᒖåȧ񄉂¨펌絕̽t}}|uc:?49432898798<;88:788;?;64453:8:rlULUFGTEIFB@68B7E¿ƼлտL=9@;>BD}Wdul56m8lrz<=HJF=:8@===ADr㊒恁̧ꕔ܁"ڑſ₌ިݽ㛀WTUT\RYy[aVST~R[joXokwwgfdie{WYTyrwheXfX{zxiͳtFnA;>>GFJJHHLONMNNJMQNMOKKMNLGKIG{qWILNZoShr_VHHMIOzyxnjmlobldqplovzu~{jNQOTSXamɇedyt;<{@qsEIY`]SPNSQPOSTxy}c`[Z_lo[WizibUqZ}W"~|~l]TZ`YzÖutu[X.0.$<@3!3J(+),/V>!(\FC/B?/7F63Q;?@p\E011SPFHIT...-KM92(6Uh]M=31"FQH&~~~~~~}~}~}~}~~}~}}}~~~~"ݸڄ༥㌌뻧؃₈扤h{~yx]uhCY86:3354;@@;:899:<:989<74468577:pfHIFN[iKEHDD=7696?b˾̾uF;ABB?>}YIGq;7:5op~s;<=?@ADJGKMKOSSPQNNNPPOPKKNKKMNMIKJHylUMGLsbRevaTJEIKRXkyyy|tvmzqtijv|s|eQV\Z[WWʺw\Pt>;>>~DHP_WRRQRSWWQZRbyt__]]ZkskVm]Qz`x"bf[W`ff][qypks,35.$KPPCDP_))KSYLH7/W9BKE`RAJ<*A375cP@<8=IN,/-XOLSYbfV/24/*3_B:GFxAGCCSшˣ̩ʊÆĬŷܳwiYSk~x{rz~zmsty|~|~恙󀆐hF6Rh0,(H&&?jQ5RL:7C/;1O*bF3=RPFA6:;PV]F="/;DP66,&:UgdVK^cX9zP4~~~~}~}~}~}}}~~~}~}~}}~~~~~"舎׼󵯷Ħсۼꄅ㮛􊦙srp}cGuvU7:89;9<8:999?<:9=:846:734075777hhI=LVaJFFFIC;57;=:CįĴD;<=C?A>WvEg_54gsrr8;9=>8862:AECA@KMIτ㖁٢⌏⨄"郑շо܋ҚRYSUSPs~wslwojtV^WXagmg[q|iOx[owTBJLMRQQLNMKKUSLKOLJEJMLMQMMIOMGyoVJSO`UW\dneWLHLOLKsxwt}vyxve`g{|r{|vqwVYWVYXYh\t;@{AFHPSOLKMQU[WRS\Z^o]_daZ`^yrvbcTwUlW"Uah^UdjheeZd|gfvqLY11*ONNUTV,+'?CFJ\QDV_@pYymMF>*T68*UnN*;JNJH?Kioe4PD"ELLO/54,'JBAM45107:`M\[RRQRj<7bSEB~~~}~}~}~}}~}}~}}~~}~}}~~~"ȹČɢ؆ΰ޴Ģ鑦xorq|zkFwKH<999<278>;:889;;><=8876650479614hjdOQhL?CEGJF=3698BN㱹ǼD??DBB?@?>@??MEA:cbepr;:<J,N76K(&'WM0+@L\,JS*-D|gfINP3>N<>rnH0;ET^23)'ZL8,>@R+C6CUj^-!cNqPRWm޲꾶Ҹȸƿݷg\\qki}}~vo}m{ywqlrhvc톈ĸɿvL4N'KB?!=PAEE,Y?7:FL]80XmG*GYT*0.1:9760-"*WM7Q130-WOD@69>:::899=;;;75:8764;98;89gptakpzGEPQF@43;549θ·{T=1BBB>?;>9;<<@BNJKQOLIPPRWci`beiko{dms~xm^]~qU^b`icbd`_"bzbigWSbi[Zcbf~'07N-L44J(%(XH26N)'" (*IOKE;_Ob?6HRMY]=008O`857RYC+&8T-I4>Ppi?-YH^T]yǠþĥ·þĹҤ򨀵choheSq{mf^xl{og{񟡄Ǽ3MIKPVI-9RT_s]F>A8BPc:7XRGF``M&/512-,-,)".`I8Ja62,*N;;V`R+12+?6=QRKR47/4jQI~~~~~}~}~~}}~~~}}~"旾ųކΤ됑УߍŠսݥ{tpRIJcK>0;8:=<<:697;:<5683<774896458@;98<79798KS;4DRDEFE?5:<:;6૵οtcH77>>C>>:<:8:834/9:9<_7=;9A<79::5899:FFBCGHCLҲ˘ѻ鈔쓙ƿŷυ̮ζ_VRURNhk][XSRoczhWVm|lhfiwk_hdVrkx{uslYUʇQUZfNH?LNMMMPSNONOLNKNOEMLJGKLKLMNTPNNQFB<@AUTFLknmlcWKPOMNL^v|yz}|pwqzzrhuvq`PKKPR^UTTUQOTVRNKSQNKw>?BDOMJLKIJOOQTcdabikefv}prvyoeSWb{Ydgdd_^\Zg"dwqzdjXv\lbly|_C5CP+(>;N)'(UT:;Q)%%%'H=4B\mNJQAZV.,>D65-6HSAW/DKVH=38R/L03GXLE>,3i©ſƾnmjdWPh}e]ze~shy凅ľp5KP]mf]MCIDhG>*3@MLYf]MPMXVFL+031-(&*+("0VH7ALj74/J@AJP?E(*(KLNRQBGUdLL/7 ~~~~~~~}~~~~}~~"硽ްڊ츌댦ɸɬt|q_xjF:::96=:97879:?>=:;689897977379:63999789CODCBCCDM@35::;5I`Dz׿lS>58@<@@?<<66;7;47?9;A.55:<=79?=<8:73;AEC@?FF>EJꏮ߈Ϡδ"ɼ÷ҬoWUURO~nZ[YUR{[p]Rk_Vqvwfyk`b}ukrxpjށntwRCEINJNJIHJJNNNLKLNFHJIKKKGLKMORMGKGB>DIPWSkrsrefVILPOJGMUn~uzzsphW{o|nruwustqpnXKJIOMX`ZbUNMPRUMLTPST>92>EMLNRNMLMOQUXjo`VYZSjyx``ys]maSs\bccfi[VVb"[jdfjbamqj_svvcZt/9HN,*C=K)'*\ZF8F)(,+%?- GUwE-LCgpf5=I57 ~~~~~~~~}}~~~~~ }~~~~~~"ׄ󄆀Яکكͳоӯkswb~mm?<<4<=989989899:;>:;;897857:98469574;<@>><:7=C=;;@;9B89577738;9<789:?J[F=DLIGGUauǓ向ː΀ۀ"򠍑̬ƅ³ʀ^mTSVSN|gZXWn\{iuca]]m^g`hnqvz{p_iQGIELOLJLKHKJMONKMIIKIIEFEJLKNLLOMMKOLNJNJHJTnwrqk_XLOQXPGIJ|sxvzjceruh]{eijxsejrjoĺFIHORZXWUTNPWTRJSRQWID8>DLLNNLQLJLQSlydaca_gosxߕsfhaaQVqogvSYXZ[][U"gMOfexxm]yiw(7BI*(GAM)&)\RB-?T-*(J<(#GK\HXT@=wDOiVS^8d\C5 9@OSCV]JABDSLFQ\NPƀź¹h_nvtfsrfg|rsbwy{qvx𖣕ȿoPZZ666fU1--B@UN=AKLPglONXQ7%) #'$@LH!"I7%$=:EQTQPNWNJ<-A.D>7699:97668:<=ELC[IA52:C@36:ʸǾ̓ڝq284698@9<42:?A89:;07:84889859;9<@GD>>CGCACXhӈBݶ޸ֶνúMg}UVUSpXXZ|fkWpXjĴS[eegpUo^vvTnLPSLFKNLLKKIJLKLMNNSLLLJJDFGLTMLKLKMNOOMNOMJIJOWqwm|fZOJQ_VHMLbquwunom}fsdcicktppspnNPJMQR^TWSMPSRTRU[STPL@HOQKMMOONONOSWlod[\b_]eims|cfhaUw|xbkuYX^iaVWVY[U"~YSVY`jy%5ES,*LIS*&L_XE;AT-)&KDAAX<<*E\]>`;BTtj/49=BmE/)hpTkV\.GIUURJ=J]P\ȯ¶ĺ¼ȷȾhytqqf|m|~usu|tüþVj4-,)+VTO"B89.-87BFFHBAGG=6JODDZ\RCGJKFBI?+>~~~~~~}~}~}}}~"鍍ۈׄ墄Įּ݃꽪}}wum=E<86>:23;<:88997:85;?:>;979<5:;:8356877579869:;4>CVJTID56;B=:73ǻ߸pa1788862<;;>7<56<:;;:C878788:9:=79;;?ECBAEA?CEDYϳ劑"ۺ؇츲whlYXVtelzswZYdnaXwWVY`YU\dno`sPwe`y|IA>KJICDJPPMLLNNLKKJMMINMMKKKLMPPNFHKKJLKKMKLQ]gzx|jaRPR\UPMFsygqrzpx{kyotv{lnthfPONNNKLVTRVOSMNSTYWRULKMLLMQPSYNLPRT]cdbgYV^a`qtikdgXRdfWSWZYWV'Rxgkg_v]hb\>?LV*%#L]WF:00==7G,*PTEAJU79-*`hA2BBM5.+5:?B;K7-VHAfbX,DLX\UF?OTRaյýȭ;qy|hd]o}yxyempfqķ˸sFa0,&,-0_aY`TNCRMcYIM@?N35-*-/54,)ZdP?"<:J\84@JSUUG92:8Ѣɹ¿rnh47:59689<9>F3.5/7=;>B<:97;@A<:8797:@FEBCCHDBAABӘ´ҁ"ɸӷro~ZZ\VZXqahg][lxj\|[`^SPSWjclkn|~|y^B?>EHDCGKSNMNRQRMJJNLGELLKMOKNQNOMICIMILMKMQRNNMPYhlw{eXMNTPJNSqijkgpqw~wvvvqnihQOOILGLPQJTeRKNKT^UTUPNLKQQRQQPOPORUdmcdjgZURWb򜻞jndda\|{WaXX\^WTRS"xu[[ic^W@;>J'"!"MQ>" %@,C-+LC:8DU@B4.^^G:E:@W" ,4<7\D7^KBWKGC@9AGLNDGRZcyˤȽõƿ¼crtq\]jorqmmpifZc뛖϶nrS12.157YKYYIJZgQSUHI=8A)124220-**[`\V"NLUnDRBS[^6`URJ?CQWXV27-(NKH*SUWSV~~}}~}~}~~~"zŭ得ҋzsu{`}@084887579?;9:46676:Pf<31267<8??6AC@>;7863666456769764>DCDBBDB@?@A_cʂӾ$숇⪆ʸ݀꿭´fv~UWUlN\l]h^XVa]WYkztg]svh{ptgfksv{퀗F>ECGGB@GJQONPMNOQORq|KELKKMQLRQJUWOIKKMLMNNQONKIPSbspzln_PJPJNKSdgaftiyutvyueeaWwNHNMJILOPNWTEHmWRUPLDJOININMPPONUedabf`TLHR[ln\hb[pXtdp[m|\V"da]i[c}T_XWf6:,3E!!K?.##G/E-'>.# 0AxEF@\MC9J>6H>2HZ@iHFMca`:0*57@AF@=KVm卹۽ƿ|xtoml|gelh{~q]Va⛠ƸݠϾ°¾z:/312HV0-=80;4nO:B:IJ@GLT`Z2/\KSUTNWp"7-KfG%9IUTg78e/+DgD==<4966;;72.137FGHEBBAFLMMJINKKMOV}PFMKHMILMLNVTMKKKLMNMKKMONIMTdqjlkg`TLIMGLOPajgcc``hmonor~jtrjNtQMNJMBIOMQUZFGjWQQSHLKKTTNLJLMPUfkhY``]ZPNRXesridvp\Xg~b`Vi"c^pi~zohyqRrX`og7J9(3DFNQ:&#8gWmp=?;39XdL-1/DQOOS^XWXXYVVOOJOl";2ELR55C?AZqAo=3JSPWaeY'JF.&6@D^>~~}}~}}~}~"趭դħ÷Ҵ|qmrBu75255468757569;863575:::2345577317=C:3534=B5,4655463@?7463@::78898677745638:@BBC>AEDCB?<@Cx}ʜ펀덶ɹƳ"ðƣ巕|yQmRZrtpwc^uc^~r}z|kYs{Risy`az}~Lo8BBEFECADIQNKLMKIHLKNZ`PGKKHILLHHORSNJJHJQTIBKNNNLOS^iixjm[SJMOMQLOS]΁hkjopm_aYMNOROONNPT^ZQNQQ\QQRPLMLJNNLJMMMQQXc^b]\]YTRRPTpskr^_Nnwy"|uk~nmv\OYq{qpyGaB06H&ZV8%-6CJN;)$CD4>BXiSGJ/o?ESN64GIGAW|Ѱ˄򊦪ȿ̻òźžûӦ]nRr{lvȯ½WbYFwgK@;sbrc:*JR=PWTVKJF?;@CMGHOIR"n[BDVKKH??=@B@??BEBUjϬͺ׶˘"˵ۮThHRy{uoY[xr{dHi]RPgyiw~ٚ5>DD?AFDB?FKKKLKKJLHJMLKOOLKMNNMMOQMOOMKIFKPYMCJQNOORThHpm^jd\WONPOMNQ]Z]]YakSJTP\b[KNOONTKRSSRTROPTPMOMJMMKPNVSli^dVZZRPQTXkwyvy{ns|e\jwfqjdhm~vQUqwjZShE2@L'TP5#);IKE?CD3'BZVGN^bZKALf]ULII 3QPSX5taRI4=IYfY@5?MOOgϗǽ˽լ^J\ɾ񕮱õJG8FYTOMBuhTL`vaL;X[PJEKSEKQHLQOJ,@"TL55AJNTVCDOP>AGIGBGLQHHG9#'69,-5Q~}}~}~~}~~}~~~"Ųق٭ҳձМ׿ɷ~zbo8178;68641/3588>::5=779@=489859>EI{|ĜkoosdrYa;HDJmu~/:DHA836798?1.89;@979?;:667388=89<::>CBF\¬򅊎╕"ȟֲξ󀜬yTVRqNP}zhcqxoRXdj^pk}xz|yՃC=@CE=@DC@BGLOJLLLGMJLMKLGJLMQQLMILPNONMGLNKSaHCHLMOTYT`xba^Y^YPQPNKRNPPf蔅qCQOPX_fQHGILMHQMNTSNSRQPTRSMNQMPNQOPQQZ^ncVZYRLJMV^oҁesZRoUap"iiru`Ym{ebw~|tXViH.A))+P9#'CTO>=LD:0>FQXYZXLBJLXX\[VT'Jee1253`Y\LRXX[VJ:;NRbüþʰüݯw棲öý»p[r7OINWUGuvt=,@f^QLSQTTba[]o1M$>"ULH63[bf]YF;23(1EGDIKLGIF0@-55<>0E~}}~}}}|||}|}~~~~~"ٶ΂翕״ѭ̣򄎁ʟ~|sl{tG7D;34?x:;@76434548;:8:?>8;>>7422324=56488521078LT2/;677587<@EEB|{428A70739qONWQTh`ML~OOQDETEzw{>VH5428:9;7987><:669:8555467;:8;>?A>?C7;=8@;BC@MͶ"뱻ЩϪècxpRSP[Rzup}zvu}cYas^n]x}zw{faK@@@KD@CBCCGMPOMLLLMPRNNKJIKLQJJILLKMJIPPkuICNNNQPSU`iga`߸OHMUNLMDInnxysqefp}qta^pzxkTCFDMPJPRSTSROPKJOSPJJMMNNRTQSUTSTYeTXWPSV``fwz:fKqɭ[aV{[g^Z[l^hx{ebocvvZSVA0<')*N@+)BefKGKIB8=CMPC?B=AJHOX[dkU+'\lUV4:0MamB8KI=KXE?TbǿƼҸº³ʶ螵̖ݶҧżkTRRGIPWSPAkfUR'AgiVQPWVPQQS\73'.B"[[W;4,30,*D?94$)>HGTKJG?<9::2MP>-~}~}}}~}}}|}||}~~~~"׺ꅅέϭ뺏픨Մ}tacuE$<986===8ro5878::6906<<=:9885-/0459:856939<548>SN856876;;=9=CBA|98>=87-40:eXHM\]OQXMM9=@=DRFS[cG>FdHB74399=:;;:;887;:9;664;:779::;=Aqx>;==>;A@BB@bƃǑıЁ􁂄"޲ljصĵڄϓ~RQQmRcwgmv}]bio{w|mvqwl_[Okndsq|rkADAEAFHJ>{EIMPQRMJFHLNRPMHJKGIGLOPOMGFKGOTLKNRwtNJORPOQQSQWaX[SMSQLNGJFJznvypqjZg]^`ajokrtȻWxxYNAGJOOPQWVUPNLMOOPWQKFONJLQTSSV]WXY[\UVWb]`qzqikELfVXZ^]]ed"zajeZXUUvp}lpZ^]TNGIF(*-\I=/7`fC=04?=9IRF'-/7CFDLULS]sHNBD9MQQKGI5GL&9ET^QQSS,)$(.Z1,'9="HKH0.(.1*(GM##DFFGNYI:;+2AZdTI24J+~~~~~}}~}}}~}}}|}~}}~~~}}~~~"ձֱҊҷƳznjls;2=;8;;:<;u59<==?<963079:6856742324698=779<9977:9997876=;:9;>B9:32,32bZd]]\KWXPBI@?XE?Gk_fBDG`MK:10/9:9677789::86:8975.57:C@kp=DBAIOPPQPONLHKNSONILPNIGHLKLKLHLORNLKNSSQOMTRNOOMLOTTVWhi`VVUKLIIEKIsouhpia_iZTn`Wcp`ck_HFMMSRSWZTOKKMNORWTNNOONMHMJP]XYc^[UVVWYSjܜ{[~qr\evWWZZ`if"dldXVXVV[Uycwh_Zg^_,RQRW--.YF:64>?0)-ADFCGH:(''>SJCDHDDDfjZ.=CB9PN4_i>=DICbkb㘬膜􄝨¶¹ƽȽ䬝ÀѱϾĽՂvsWcDN1HJ<9JG=i;9CLWLQSOJ*(),T0,)B7"HLIB@+..,-/*%&(QI@<;:96..6:7735560/358968;9:97:954546769@:99;7>;=;AAB:045202rf]m\LUUkPR_OOPRNXVc>@;?>;BZ@02/57:;;998867;3:98744/4.5=9CAtqiFQE@>CJQROPPONIHMQLKMMNRMHGKMJGJKKOOOMKIOPLJILQQLNQMONOKMJUZfgaNBHIFKHw}sjmh|hkf[cc_rovge\XPMWdREOPSRTUWWTNJHKOLXVQPNOONGILKXYdriUOJRVQiynƾgrijqUUXXVZbZ"W^dmdXV_`\`Xcrq]c^QSWV\Z,-.0+B:A8"=!-BJRPJ4+88+.9RUD2-0?FBUJSM?/1:.GVIRLMGCxwÏ½̾ǽ󎟰¿Ʊ˪ݑ˽Ķ򑦟­_ddd;WJOIIGRHI=CAPVORYU*-/.,X,,)ND").TPS.0-0164/.+TE3=>/0',xDSyA]''J>~~~~~~}~}}~}|}~}~  +~"ӾߑΩ̴Ȥ޶jpkfeZwP?517:;;;<=7878>A=:9960537/0307810,4769;:?;99768737565975639<4668;7<>788C9:2:;::82669979nnmz}FMD@=?>@Bh⻴ހ"񆓕ᯜҸξHU[\TSS[`ytWX|lbqi[srew|uonmYBB>@CDE>EDHRWROPPOILJI?EMLPTNKGKLILMKQQOOMHIMMMJMMPOLNKMOHIGDGLVYbcNJLHHGFL~nbf_ac\Z`Q^ksXDORTRIJ[XENPRTZ[Y`[MNNVORPWXSRVSPMMKJVOluWOLQPUatø}yUVV[\R]_Zj_eXWbba\RqgvmegtZ[_01-'(+D8DG-B/?UaZLF5.@KB@@HYL5#-HJM]`MLQG15F?3NUYNOKQR͍듅˺ʽ¼ƵȿꍟȽСƊҵ縶uwdn`\[LGHFFDLF/=D\)OQ\++24*QS11XPR"14-KO[2*WS-42.*'B/0:2:?B600MaK0,@I~~~~}}~}}}||}~}~"킆׾Ͽ⸡ڂ߮kokhfaE==<:499:99;:768;>=?>:8855422823998688569<=@:;566966224?;;68896>:5==:7=H46533277yfXVLSCPLTMNWRYf13366802EC=<7>;:=@?;979;>9<5>><865698789dm}{FB::7>DC\w섀׹􁍕"؁ѯԋ҂RZV~h^V__cWqlZXXop{koxqnkkXVA@D>ACEEB@?=DJKRTRPOQSNILJGNMORPNNLLJLNNPUQTOMHJNPGGMVQQPTPONUOIOONUY_KKGFHFJMnl]eVbaaWSakmrISLLLOJM`UQSQSTUVX]VRQQRVSUQVTSTUPMMJKNNo\ONKRT_ibb]_VUWTW]]RWW"cb~Udgc_\{VZcY]mxV]PLR.3,E@GB;8>59Y+377,IJPNLGOM]eR=8>FH,`bT,1_H@GDǘǽļùϭʾ斮żѸïBh333'E>=B&MXJFH`+GGS++.0*OX0-RMI"Q/-K>D_VJE+32-+*A63?>$&]G5"(.59G')~~~}~}}}|}~}~"Ѹҷ厗ƴۼ򼘉׫oopgfcbm4>?:u9;>88:3/=498:686168A@A98448784kr|unG=:=@ACAdǎހ绵􇆄ֿ"҇窪쑒Шtqv`dc_W~[[X\^^apdserxuhjnjC<@=z>DLKJ>=AEGKSNJKRTXPMJSRJOPOSONKEKPJLLPTQRMHPPKHLNSWQOQSPTXTPROQTXlZLFMJNGHQwVXaMOWTVM_qq\RLPLPQJEOZUVWNU\V[SSRQKQOILLQTaUQMILLOHpSMPSUYQqqgcebWT]XUWY]^TXVV"\fryfhaba[W_ffmk]^G9I_aXIDHD<,0CSr46;;5QMQPCK]141GCKOKP++UX7=oYHACMN\edSK>ijͿ˾Ǻ¿ʷ֐ᇙؚżµ굯²rPF775+&?;E',)PJFW(AJY/*,0+P41,GND"@)/H17LXBFd64/0*'B<@76:?><551121<:9BE=9C91/?77:::66;375683=?:<>>=A>;=;:9;?HEFB>-067651bZV?@G?BC=IQ^c/tT@=;=9:=877=?=<=;;52/8B8:9577576=;;2775:9ufkmboG999?@CHEv쉊³"|dw~v`ile]vllZ[XV\ctyvЀzmlka\q>>;9;AAHGAAGQMSNOV]aXOKDIIAKNOSRMHJKICPFGSVULIRLOMNRPVTNNLNPUVRQOPQVkwtogWLMNLLNJhIR_USSKY\_zCl[UNONQTOOQ[^Z\YUWPPNUcMNSPOOKLNVTRKNKJRRoPJJSVZSUwktc`_^YVSV]]aaWVY_W"^ezbfdca\\XVUVYcjiZXbc:)B^`fcZQF>2?\iB@BBCuLB<96Q430-HO\]VMJFAP5;aTSPRSQXVZWXW̔௩»̿䉂ʑӇǼ׭≠D6d70,,PNML'CGJ@W+*W..(,1.,/4,FPY"I).VB=IWH_7410.+'$E*(%&*.2.\UML+31~~}}~}~}~}~}~"ͨ퍔߰ˆԾзrmmkfcZA/6<993598527x99@@84>>@9131330945:>99<56538948656<98668;:8==7<8::;;B<9CBEcVdY12;=866^aAI>EFICDD[\[;[~YH58::9991=?>89:9-,4:?87-367<91;679=sl^e_xG=99?AB:@Kצ򄅆"ѹڀ򊑑ipjxenld_}obZ_]URWznzvyrljj\S@=@>@E*-/0-/-,+.'EKP=D.0+/3+EN24~}~}~}~}~}~"년ҵɾqhnjd`\8*qsxuq;;8/53?<<<=949<;5411264732<>957687678566539895/5368:9866723B==>@@PCRHj;aK6852d[KG@EHNDBOZ3ъX=6:;:82:<>=;:<83897:;=;;<3.27@=:?>84;jcamr}CD>=<8=@6:@N􅋎"܇鼪򌒓退p]WtY]YS_`cWN{UV|zbq~{{s|{z|upmjjl\D8}}y?@BCHHPKQPQPQVZSKIHJIMKPONTOIDJIJKLKKLNPNINMOLFMLNNNJMMMNKIQOQSXkfugceMNHD|j`SU\bUJ^`vnTFKPQQMQSUSQQQPPTTTUTZXUXPHHMX[TRPNN[ҠorZSPMRUOZkXȞ^^_USVagi_XVV"]dbX]fdc`_`_XVSuo|[WW^Y?2J]YfvzbEBPLKV4<@8YU;+,'$J2/OHDE98+AMRE:1HIBMVVFDLEGVjǼ˾ƷȼȺЀȦ®酜WTd243USOGIIOQP.[1'+,-,,*'>N\YX"23-+F;H.1-(&+0-()%EA>7.7Z.&(41LJOU~}~~~}~}~~~}~}~"Աը薠臂̤ȼ~mgjiec6297owv;;75:>;9;<;;8983634211/0.0;:8226675161400154496455799435878:@<=>?elxbQUk@U:843_KHJ>@G]GLQK8HuWE/;6<94:<=A;8<:1?=<8?:;985428=A?9<:7;~d`e}vBC;6:;<>8CNNĩ굯ܺ"錙ꄆ庫戏р{~^itmiI_VSgMPT^v}{ux~vz}uiehjg69B>v|w99@FNPNNSSQTTXULKFJIDDGKLNTNKFHIFKLKMFIHLNPKGLJKLILNNMMMOLNOVSSWhv|wpizSOHC|qkXWbu\JcYKnsmDPMSROQSSSRQRRQ[TTNTUWTSQMCMT[[PPQPSiiTLMPSTNXcW]VVVdi^WVU"\ecX`iaYX]\\QSzqWVVZU9;`zspvwYJ[dM7:O[UBEBGMJ>%O.+>;3 $3>KQP?8FJDPXVPNSQOQトƶſξƽ˽Ĩƥţ…ͷ宩VWl58/MHH@9ARQFSSQN&170-'&C>PKM"372-E@O02+ '2-F<''JD;7HS)$$.1VOIO~~~}~}}}~~}~}~~}~}~"臌嬏Ÿꀵȶ΁ۻнtgflfZR}o4556uxu=>:>7:9><::688963431/3332695.276776664520.2467760771466>77347=;AmYPZn6L<,92hd\HIPD:5748utx;B?<79:EJPIKNUTQSRTSQKGKMJFKKLNROLGIKKKLNMKHNNNKHFJMMMHMNKLOMOJMNQRTZw}|x|OrSBPKwikb\cf\c]ErzMHRQTRRRMNOQOQQSSMQNT]QPWNMJRUXRNPKKà^fWRKNQSWVXci{]VSi]mpmXTXTU"bdWdn\SQ^XVRXSPTT>Vi>?@@yZAVrZ7/BNF?C9KFs\?@F)M<23-7CNJAGDFCOlD3]M*1+-NT"]55.TH[32$)5,?5&()QNLa[C#$(RSMGB~~}}~}}}~~}~}~~}~}~ "ԢҾάշһ{ojgjb[vOk144iqty|A9;4889=8748:=:932,41603464335:7996788777658;9573445.5<95116;>F}i7=W<555988^cWVFFMCFKN]];E`V`~99=ECDI>:;9;8334:745:945@A:828997889:wuur=@@;7?;=??@GXԀ怂㤈͸"ꍔǂ潮y]_]alekOgUvWrevqt}xplmg\Kss9AA{y{~HIOKMNQVQQPRSSMIJPMOLLGOQOMLKLOKNOLLOSSRQMIOTQNRMIKLNMMMOPTez}wKDJHKPUe^d\^`bdtJXzq_\WZ_XUURPQRWQINQXTPOTTRQXWQQLQXPNOQ_cWOMTPRVSO`iq]Z\u`kko{[`a]Uci[QZVSS|a8:79B>]?$7a/D>IMGKP?2ah90JMQD7;ELNCHS\[]VPR[eeWRVZ]؏˷ͨ¢ϔռվR{4.-PLLL@<@CHHFC=MmbSKX42/.S"W_\3bQ^3,&<8J]bL?#&%TWN^TCJ'-TK:;C~~}}~~~}~~}~~}~}}"ߥDŽȃ޴ukjhhfWKBj4734gs7;BB;33:::94569;<9143115222/3111257:789<:99;::87740657567:=9577:@Iu\-B66=E9523299453okP^sA@@:8;@?C?>DMdͿʪĦ򋙗"퐓Ŋߴ鼥cakfZ]eanK~SQwhXr||trojgR^@h9=?@w@CINNJLRTSRLNPRW[PDLTQNPJHMNNKLMOMNNPSTTRPRPPONOQNPPROOSNKMMTYwtW|ro>HSJNP`UFl`YQe\bLo+pnSkZd~`TOOHQXPQKNQTSPQRUQRYhSPJIJPOLOO}edZOMQTSXUS^iq`_d;{zfpTuiuau^edaVghZW[TWywX\|{?C>5:?;U07@-(MOIHYcMJn?4HEOV[HKQZZ]Z^TYV_`]]btڅڄ񂅉ƷɴŔڤ·ǭǀ岈ֵ½Ʒľl4*+_^UOM86JV-K9+:@UDJLX20--'"TEIRb[a62,F<@GIG?(''+L8>A9F+2J954=~~~}~}}~~}~~~}~}~"ޭہҳኀ֦݁slkdd`@B97;9rsv98;=78><;9;8749BZB-9<3514232/1-*00878889>98;<8985666:@7;;?C86667?HgYyRB::84447:5_XKJJAGHSPX:LLiO89BUB<5>/398;==>987+,7;9:9:55969:>:69rj`Ym@?=@;99EBA7BS[ղކк쇑"쏑ۼі܅؀b_YWcazYLqWT`ep\vtzZYS|sqmljӝJ=8962>?YE63:65565:21/17:>>8:;93:<<<:702:=@9;==>;7880=DJf}n2577836;9=:46647:86=985346=B?:85`KQlA@E@@??9>DFA?HRN߅:ڀ߻ۉ霘ӳ螙Һ开dbTbtT\W]XbkzipzSQppljif\>{{;?ACIITPMNWQONKHFRWyaJGQNNNLJJGHGKNLLQRPQKKMMIPRSSRSONPOSOOPRROKNSOT`qKINONJR\]yZTXttfpx:}JLUOYQQRJQSTZQSQQQVNQMIJRPQPOKFPWVRTPinwgaVRMPU]UZ^fX~`rXmr^ihhi_Y"mhYgh_k`WSmvZ[T=:(1Bk:RSOIS.+&HOW2/V]sqV-*;TT:;HISY(KH%LUTQOMWXY[hȼ܄Ү£ġݮ̈́𦯧ŽäȈJ^]28GL[CMaL +~~~}}~~}}}~}~}}~}~}~}~~"Ч潰ڌȹ۲Ƹиjbmkacx=bzr99lnt>9<678;C?><;964=8:86rh553.41//340116:;79;454175582569=::839658>:22>:8/,2.6::@C?95878;8;B:9aL\t?D@:;:;A@99FM^ĢꮁԵ"ߜπ²h]gdVyzmYavgovujiihlrJtvp;?|xCFQNOORUTQPNLGHPMPLENNLGJCCDIMKKKMNNMNMGHJKNIIONOKNPOOMOTTQOGPPJX[fzZNFGJIJPP`YVKqZMT\hgs~DnHMMSPTOSPJS^SMNQPPGIPKMPQXa\SPRLGLOR_TXeh\RQKHORLNX`o͝tRmtd]d`db\Z"nj]`esXngbYUOSZZVbmt~vM.'KTEHNUqB7(DCD?71?QRE.(;SY>9?CAA<5678:<016ss428B=gje4/19586658766755:?695799<><9AB:/279=C?A575/:8789;55ibQDBKUJKTu=X[>4/78;;54=1:;92698644.36@<=9<;;997:9<=39m`aj|ADG>59;>?BEAPL!GORWOIHGT^qxk΁胊䀅ĴƽíئҸ唺βºϼwP]YEpr}G6,@JSJ>532F:N`0(+24."I57N\84Gjj0BI@M;O1,+1-Q[T('"K=EH:4~~~~}}~}}~}}}~}~}~}~}~~}}~}}~~~~~~~ ~~"́ጻ乒ȵdžyrejhc`Ryde457559<==899=;566796443r\_dshfb32;5668764865822631768>@4057=<935989?7.059>;9>:42:6YHE=FD9WbaD>A914762-667>?=42597/5367=;;9;>=:<==<<;9:kWwnrw@>=677=CKONJNQS\^SOKLKLQROLIHIOHJLONMKMJHKGKOMKLJLVZNIJKUIVPILOPTZNDJMMOONOMKKQP`MH]_djk~d\YRJHKOMHQNP^^TNKLPNEOPRRTQRSSSRPOSTPUWʻXUSHIMQSPU[p򬰎ptzvux{e~\W}xogsYUSXXVWWTYSYgkxF@9UaRPXE05IV-$AGEOaihSBd@"BUC5HSTYK IYfh_RSWV]wx郈ÿºӝ½óȝϯٻv]ZVItwN?48B?@B>/.cjSS-%Dclc"N;BOTfM,OR3QO(GRX0)%*,*-,&%%H)>W<>~~~}~}~}}~}~}~~~~~~~~~~"h켩Ӂ⨀ǙӂômkifkgWmTyvi766758=D96::;<>=<8675523623dmiffiglaj2239;8:<9:56210779::785080>8886;6@A=9307:<<=:<9999<:IQ^;7741940-77;?~:47878606568:<6:>989<Docjmlo==9<319=?@DFOfɭͶ6ӹӼ횯󆃂ֻܾ䊍シX]d}fv|yu^f]{asWVSRUUVvnqsqmmeXNm|BA??>BITKHKNU]eZPGGKIIHMPLJHIFEHJJKNTVONJMMLILIKNSSUNELNdRMJGOSaeWPIJPROPSNOPRUYeMHZ^bk˿USSPHJGIGOPRUŶQLLILNNOURPRRWQPSQOMRRVNNWnZUPRGDNSSSW`nưpht]e"^^fqrcaqw\\ZWUSOze.:Ce}]TnT;W`GB:&=DIX:Aw]9?EBH(XLQ*+&#'+3iZRV[XSQjڂ󃅄ղѺۑʾƹ¸¼ދ񔣳ԻyT^_TOJ`MMN^MC=J>2ErCG]-NH]3",/`iLPC&=C=037;F=;=976998iP@A>GRXxH>=:779835673=D@55:6799599:<7569973=<@;==sYTh[_r;9<@;68;ABADMYƽӻ"乇ɑW^j{gsvenp[^p~td~~crUSRSTzemusqnjlVx|=@?@?@CLPJKJKTVTQPJIKHIJKORLOJHHHKGDMQNPELNMOKIHNKOROKOOS]PKKMLQWZTGJPT^QPVQOPRPMdOU[_k~mWUTPLMMLNNQR]m^NOOHKOSTWTSWRPNPPOIPQQNPQ歓YUURQJLQXWW[mqƢX_~h"ffdg^d``q[~f``YZYVRRd{`3AL^m^QiZ1=F<9.!2ABMkB:C]LjQI/JLF#%'P()Y[SVX^QEN쉞ŹǼĶñᯗꂗƿ쮞񅊏rTUT/VMTTS]kWI>P[E&P`CpA`DH]"--05/MA"X8=KX[/\DA6-G0-0,%%CNN6Nk/~~~~~~}}}~}~}~}~}~~~~"һ棻߻qskkjc\Y\pfpxvul4697868859:;>9:512366510YaS[je^d^a753689=88;?><786268658875442DE<9>;889?366:>@<9676:??9lG>D?FU5;A==<6;;669759;B<66=:7>AG>JH095>777=@C658868:<>:;89_6".+-+,E9=d^Yi1-''XNTMR166,"2+E_SGAG~~~~~~~~~~~}~}~}~~}}}~}~~}~~}~ +"̱ڌ픢덌˺dpmlke^xfoxsrqno76622488:=B957451).35831`ZXbT`bffbi427679:<=<:87589;=<<@679<8<98;9;9:<::7:39888787:@B<<6^>A=JS7wyfw;487::78;2678<>9492453<=9:@<:6554169:6.bgQ\eHVNDG~<9327:9=AEDINI̶ݠɸӐ"꽫뽑T\mz}]|`eYuwgpVRNtxz\nplmmi}s{||yxEKRMMNONPTRKJLHJLKJIMSLKJKOIHKLOSRONMKLMNPPQUHHMNO\YRPORSVUQOINNWROPQQNQZZSWJ|ZYSie԰pXSOTVQPSJTRSUTNLRMMRIT[TRRMLJKLINQVWRJutsrsVSKHKPPRU`a_hxz}uscpvalh"f`ca]|RPfzV]]WS9N\[IGZ[7PoTUR;@c@'471EEcIQ:eFKN'&'RTkPS[GIM֒Ǻ½ƴؽ±²µۗҽㆢDBG`WULO8:9=,2K2YA>OD%.[@"70-+&F30NOHNL%&CMDRf\.4.+%94EOHDBJ~~~~~~~~~~}~}~}~}~}}~~}~~}~"ñ݄ԃƹhjkkh_R|jozz<7863787518:8<@>5756753477:92WV`cc]`aide9=757:=>>;=<878758:;88778>@:68<6:>667639:6:76;<:?>A7276544595dhs|mXSOm|9@<77=;:;>EDGK[ϊ"ƹȢ憈 j|Z]_|Uwpd^ftr\URijjjkifcxtt;8=>?EMQMIORPU]VLMFILOTQMMOLH~|NTLIKMMNRPPPNNOMKLMOONKKJTc_MPMPSRSLMLMMNOOKPRNOUTVPL{ahp_sǰ^pZVU`ZPQXQQRMMKJNWYPPOU_TKOPVKIPMNORRRMSVPRNPPNLQ_]ejuvvoei{tmnvy_b^"a^d]Yx}sd}WVYXWUaAUswin]0=2\f[TJ0DCO#++04FHDPRAQR*+QOQV_[NGKD򙝡ᇅžŴƺβ޿ċ󁍋Რ񀐖MB@PZK9BF90*+6JVEAHMM73S?"<4,+'J=7BM^+),+,)DFMOLKV5~~~~~~~~~}~}~}~~}}}~}~"򃉉ਔիɯδmpljjhYxOoppzw6758:;997699:7;8776731731554]eaddgcbac]24:=979::=6A<;5:9759;<989:=@9/630;63039785533347936755[ZGY]?=oho9149=71288=1481587;;8689>HKQ6+FW"/0,+OG<<@=UbWI!,&cIP(&$(-*(R4gRB\O~~~~~~~~}~}~}~~~}}}~}~}}~~~"ʻᬇýpngfgheWgtjlrg36l78??58>9;67<;:653/44553//a_bff`bchcd4746989756.9667:7738;97884:985568;:74,57<44/1/:625279dNYQ>cC9`a?956874323785.4<7=8;374;;8:965:777<865965rlZQMKRas}E@78:5A>=9;AR[g馷ֵԲȼ"࿯Ʃ݌Ї¹΀cȩrUbť~kld\_oemikkg`Upo|~@?wAHVVJMSORMLQPNKJIGKKJGCCH}su}}DJHIIJLKIKCNNLMMHMMOONNOMKTQOOONMPQPOGLFHILJLGKJIOORT}hTseXPJKPPNKKRUUQKPTOSQSHNRaaTRPMNTPNGORSPQORfVMOOJVTSUOUsuƒꏌx|t"UZZZ~ufvnbWRZ_VN^noxC~bFFQYu}S4Td53QIMNJJSRF绾˼˼ȷùƽشׁ݄ϺߕWNPYP`xoF.2IWPMKLS[@4EQ"(+,+PK@Lz~~}~~}~}~}~~}}~}~}}~~ ~~"޾ܮůǽpmkifg`ID}{xoqp6m479A=::9:97898872+1598<<10OYh`]]elcee1099:7858<<679:;=7734479;89731657<:0429=5401/.97/3697oP_R>PmA7967567:;84434735:75;237688826888:=>99736889uqeQTOV_hx>;879:BJZTPRONNLMNJMNJDHJLLKIFJ~}syFGKHIHJJMPOONNQONLMKMNMOPNJMNPSQKNLQLCJLNMFJKFOPSQ~aKmseZTQJKMQSOJPPQTQQWRPTSPQPRVXMKPPORWWMMRPQSVXVPNMNPTWSTVZnuwʍh_Wx&WSW^ZrpfjTYgyf{]}qkQZVKZtp^[SWnjB:_~MK\9;`L@KWLQY0/SNLCBPb\Oԗļǽ¿ӳ잞ׄôn]XptJ?/?;IRVaTUWfZ.TV"$%+/gTIWYJ==>,6#H887NKNE:L5?KQJ;=9~~}~}~}~}~}~}~~~"ՔľͿȴspolhgdYh7stno9;844:3:8<770856679=:.5371@m>41159872446206549Bp\bADLowq:7532426665.568466:652/478474878;99985.5;7=?>EQUUPPOPTQOLJIKIGKJIHIH}={BHQIEIJKKNmSDMORNOGMNOOKJMPKNIJGc^QMKMOMQMOQNEHPNOUZXV^TNIKOOPNPSPSUUOQPQPPQQQPTRQINMOTSTQPOJOVSYf`NeVKMNQWUWVSXlpqɋchmn^STSV\ZXUU]Wzcco\U]ic|mwffa?1BS\VXY`9wJ=V:72mE88=;86/34149>8950587;T802417:8543-70-.47EtGbN8Zjll886138;72043456178/796468448485:97988639<9x8bODFVT[kyx>968;@>A?85?GN{;睫󈆈ħ胆˄ѽEi[mJH`fkv`RPmmf_~ilmpkjf[Ynw{<=;?==@FMUKINOKMLINJKJKMKFy{x}|ECMOKJNJMNRSDKSONPLNONNNMIMMILMNSvTJJKJORMLMNKQLMNPUiukU~_WQHLPROJNSQQRSKPMHPQSUTQNPONJEORRVSQPPTTRbNSMLNQYV^\QQ]emxedfhdQUVSTZ`cX}p{b\_ZrJc`ewmK<, #&JYVRVPKsHqDB/&)PG@A<-$9NYVFAHLRQBETSY䜫ӫ⁈úñĹɽĸƱ݋끕ǽ񐮾iDHaFD?;KPLNDALJ~~~~}}}~}~}~}~}~}~}~~~~~"ϩȜ羧‹Ǿiknkh_Dcur879pn78<<>>8269;;705431308353999>=58?D>744575-135/9:;9565297865;;867884+215468:@jO]>9ni:`97557665:42759749<989=6663<302==;75:;647:7x8\bYRQRXwn>668:zA?ELQOJGKNNMLJMLKJKHJGIIOSx~suLRMKLLLPW[KFMLJMNLNOPLOMLMLNLJMJNLHLMPNKLONJPNNMPTVdkblaWQPRNJLQPPPMSSPQOOQR[VSPIONNDGNRSSTROPQQM]VMMNOR^`aVV[diuc`gedciXUT^ZSSVah"_wfytoxtY_cg\Y|{q[\yd1%&5We_^N>3)6Vk6+&UQ@7DE;EYaZND:>GQTNRZB㒁̿ȾƮžȀ¾ݾƸŮñ݁ƭ昫z|qGN^@CB@<=-GFM+U)-0,*-5;"3N73I@883$FY24:1C2TP2@I=+"9JYjSBDC~~ ~~~}}}~~~}}~}~}~}~}~}~}~~~~"Ѷ߃ͣۻѺafhmhb_vahj8stttk7::><9668<:353/0/2068657nfXSU`jhiffcX9A=34<::859=<81512-2338:<9638785476547215;<1343568A>I~`e^;tllkl79689?77578;:4387<<<>8=76?9647C?67:C9-66runa`LUYX]l~<=625;:=GK?>@Dg"Ѽîΰ¼dht}ffvkURSgtSt]jllfc`zz|?@FMTRKGKMPMHLLJKIHDHHHJO~{x~Q^VIFMLLKIJILMJNJLIKJMROMMMNSQPHHLKKJMLKLOSOQRNMNOY^seTRRSQROPPSQQRQSTNTTSYU\QNPNQJCQUSSRWSKPNfbNIKPPP[`RVW_}bgdc_f\VVXWV\`^WQVbn"kar`hfgp_ez|64:ELVew==]FDC=HJ0,/og9>OKKacVNG:=P.ZQOSQ»ºƾûƿųηȀtDKUpACC=90*),+-../.(-7A"=9XNDFCO8*Ty;,3C42XjIOboYMDIOTKCDA~~}~}~}~}~}~}}~~~~~"ϳ㱁¾۽ѿfegig`MoG8145liiheq98756511+5965441njaW]Wbffbdf4=?:7:7233944088332277;;79563848647732:47?95.+06<>H>L`oGDststn578:<;875:881764864735544?877?::38=D:596l[\QZRKYX[zvy:53369:;D>?@EITb􃅆"н⿢IjxVhnm`~Y_vI_~hllhgaWpNI??A}|xvCKTSOLILPOPM5DHJHDAHJywl{zDOXMFJJGFHMGIFMRMMNMPPQMHMLMMQKMN:LNRINTQSQNMNQQYiƒxsijQPQRUUPQQWQQKTZTSPMNNPNMKQNOMMMR]YURTOUNJLMNPRUQUTQ^u~dffh][UV]XXXWVXUYbklj_yjkssfbhXf_iaq`gu127AJ4|FJF8KLQF>U/Q\:c-%BEMdZEU\ECWa`]ZYb敜޽ၣȻýŮՂŹoBSQeBFF:61-.++,,(*&-8@"><7XFIST4+4C=*.4# DRR.?Ft^L>7B?8660218;23<7629545gqi`Te5qocfee:AEC5573=C286741454:9>9528<:9616568<75,1248@DLH{ES[9uzvwr<57:<:16.67@:73:749986447:?86>:62:;C;652gbXRYWi\^]yuv:74124:<>@A@CHSZyű¨牓ʩ߀}_limkf}Wa~blZruslknlfZeSI@?Ayo>?CJUSTLFDLKLKNPLJHCIHHEIMz?z͹NPSTJILISXGLMPMLRQNOLONLIMMLNOMPNNPLGMRPRQQNLP]ixXtwƷXOQTZYLNLQQXSRTZPKPQSQLLOQUOLPMQTVSXUSQLPNKKJKVTSSTSU^ty|bdhb_[U`\XWQQVWXej"mojWvwrqouiapmjj|ryBCBJ1@@ELHd6@LKMR*=BaU*.[WVSa]^]^kꏀżƻ׀厬ĶĽǻÝ܏Ⅱϸ󛕍κ¿뙶¾dC[R`GFD=6[,-,,,(#&)0;@"AC;.IL]X<.8TZK3/$/@HP6EJIDiP=9AMKL~~}~}~}~~~}~}~"Ёݺӹƴиqjeef^PZM96;qhpnji99@@?9;86887895426645536jjkgaRQcgefadf{z5>BA;59;HB389;74818;<<848744:44457455768694658@WHzsZQMnpsw427/9914488:8869548083585<<93066719?<>9:qYOXWUZXZV`jo87:689879=G@BHPWi"Ӫϻᇏ¸uWkulebr\OuZcor|~dljihg[mTJACw{~}z@EPTSNPKINOLLKGJG@AHKGFMyv~|z{uưPSUQLFLObTBGMTNKQNQRPQONTNDCOMMNPSOPLNNQOOKNQPXk}TOSMUWLKLPOQQQPRJHNHROMPLQUQNIKOUOQVZ]TSONPMNPPONQXSVdwzdgiV_XXZTRW\gk"iljZswzx_ljhҬ:IN0>E:6AyP/7Ze[PE7AMD-#,C@L]9G696CQZWQU^䊣Ղ釧ʻϹðѹ£͟ӱڰ󓕔ܺۿꗹøfXQ\ECG{ZO(..15-G*-3;?"CF6'OMFFJ>)EeTE9,8HFDkJBFRMLV~~~~}~~~}~}~~}~}~"Mֺ̰̽ͪefcdc[GIz6q{vqrnhu87;>=944697:7620422234kjj^]Yjg6]hm]u|v>CED6:6<=:68;86346<:8833/679;49>4443/4*766<:31ASlnk~NG|ufl:/26877.54:;7:8;7658640882:>82558738:B>:6nfYVQNWQX\SZgn6769:@>:9@@>>BITiͿүƱɸ儑׷Rn`eZkqi|zbZ_xwmdmkiieT`@||yz{AFPVTMIKLMGKJKIEHEGJGDFIKz}t=p~ĩW^\UGIGOPKCHPQMGJNUPMNLNOQLOUNOSMKLMLMDOMGLOP[^st~usbSRSRQQJOMQPKQOQNLLONQOQPHPTQLMLPSQRTe[USOOLMORPPRXVUX_n{fgUZXRTW^ip"ngbVUxX~|q|^njb9]iST=%?I];IDCED@vVc,3MUZT?9T藥؊ŭŶƁӼñد刑玚¶吶|huU@LpBEyTMYZ\350&*,4>F"H?7$(K9>T*.1LGI2(8FMaBKC@|d>BQOLLZ~~~~~}}~}~}~}~~}~}~"ϲǒ깏qebffbV{Cuofiguwor8:9@C;98999=:9543/)*4631-4jg]X^`hje`klmsD9;;=<=39537665776664.243>835897352525:9585=AJ_ڌYoln673.72552966978::98796:8>:49::7;@D>=?A>?GIVc"ݾ񶤬ۻJc||h`snnmoqkg_[xuu|sl~@CJWaQLKLKFHJMKJID>?GJHGDI~{yjTVc\RNLKLOFKKKLMLKNMHJNMION.GTSMJMMLKONQNNMJGORYpo}ZWTPUPQOOVRPOLPpOQSPXWWSPQRUNOQWTOTTZbTULMJMOPVSUX^X[`pv\ZTZYWW^inia^t{[wypjcwS5bkelQ?IF41QRd7JJHIA>MOB@2cypMfaUQB6򁍣ڻ񅍍˾ˀƴƸؠ¼ቲzXIEfGqSPffe543,$,9FL"IB3C>@:8J.?-9>:.&?SNZzE@}wT6AIAHP+~~~~}~}~~~}|}~}~"ޅڶלifgff`Qm?uk`hhsuqv>===>>89<;<;92000//25332/\iY2/_cgfeaaoeq|68BHy99@<677354544633232358299?98:347151869100=:dW}Tku79:6431*8-32688734?6688:<;=45776<;892:5::7`qh_V\XTROVh]q785;:A<AHKP\l麢􅋋"۽΃ƪuVWhmUYYnimpmkf\Wy}{|wCIRVTOJKNKFEHJKKIEBFGGHGD~DC}{}~}SU``JFLLIKMIJJKKNQLNNLLMLJFONRMKOMORNRJLJMLPUaswꃦ`WUPPQPIRLQQPOOPNKRLNPQVZU]SOPSLOQRWRSNSTTLLIMLSSTVWWW_iwt|uyW{tW[WW`fe"ca^{Vvin||o}ʵ~oTTRCTg\EKY].-NVi@?,LJJNOU`f,-JF>dpZyXXSSZaۋ􁓬ºĿɂƻĹþ̀ɷȹŸꙻ󈙦ڽ˿ʴxF8Cp=^VEG3JOZ300/>DE"?>3KFLMAJ.I'0392!5NDRs}zqN=;8:8411100044552cd`Zjs`ejldsjq{>?@x849;>697168341,2/.464486@97/6<8956735865.9DFN[_7_Yjp=;8918266:<9:66631=@77389:538137527:=87B9.1lk^[TYLNNjiri56;:=<9<>;>842255311145652_ZXf{@n4`]fktuxzG?<59:=LW>778825442+658-55566713=4466<675:65:AHIECs[=_tz?=:67::6@;>:854724::119<85452326<37;:89:70\ccY.X[OK]]knp<8999?CFBA>?AHPN[pÖ㷴ɿꂉ"ϼɥlXbc[t^e`lkrje^KAnxzvFNUSSTQSUPSKFHIJKIGECFECBCqjzL=;";gU+27;oSQMOEA<<,DM1B{umd{j13,&,~~~}~}~}~}~}~}~}~"ɳ¼ٳݿkjnjgj<6pplhlqigi;@C<:8::;:;94234446583351`XFJbhibdbkfhl:;=|@GB;1778FOG83564037;466;475040221744766347668;BHFBCR8\nt>963<:;=>::99788349469<;8957599867<:899973giqV\YZR[]_bm7337:9=@@BC@ADGIKVoƤ透"怍ީx~v_suh`]rh|ziglkgxQEvqrpv}}MY]NJJORTPSRIHJIJIHFFBCC?v_f|~}yzs~|MUYfueTHLIHU\VJGLONLKKNMOMNKSPLQMNONPKKMMMJJONOST\of^bn^`URQWTSRRRVUURPPMPQLNRRRQPKTRQSQNPWUTVUROGz{wOMMQSRWXTX`]`egkjl{xSgRkWfid^["biYrwd`Sd]7FkZ-!EVFOVy;GKOP^YR_87[IIb[Ec@Mcukdbvİȴк¾;ڦ㊘͠ᐦ}UV'JFUNII8!:ZJP8@?>A;"pXHOh894cd^eVABA2LS2@xtj`i|93)P^~~~}~}~~~}~}~}~"Ȫꛗʃニ⽓ตuhhiffN8iqmnhefqw9?F?<;:58<>?5785470552578656638=13541686743386<@HKCC>]:[s=62;85<:<@=>;257358677997764/8:823?<89884`emdfeMUHP\[a5237668>BECEBCKKHDO]w惏"Շƀ܅۲ڀmgxxkc\\r\f_bhp_VywdhhikjhXJ~rlpnoo~ER_`LHKKNNPTQNLIJKGHEEB@C~_Wrsp}}|zPV[`iXURNFILJNGJMLJMGHJJOQOMNOPMNTLMNOMNNMNLMNPPTZmla_UbgUOVTSWPQUWZVOQQNPRPPPRRMJLPKPUQIKYWTVTRKx{rNMLLMNQV[]^_WYcda_ozvRV~ZbdZW"aTdW\VWV{z^W4-?@7HKG()fP;>;ARbdY5?>Z@2EB60LjkndGTøöʺĻ仭ŚچwS.*NDJHIWOFIMFS:>?u:4"g7)4MU)8Yno51SKUH1/>;[a@?AVrviXIOL~~~~~~~~~}~}~}~}~"츨􎛝Űͷ슨okghf`K;q|qdcdnqp8=HM;464=>;85403574666735lP@G`e^_]fgvlealt@B?AD=<;77;<<85988895:7<:3533346456223874-.3=866:>BBDg^Ymw=:85/32=7:12;97705736676:65788694576:;:>36kgicX][ROXY]b3.426<:AB@BE?HIHIFNXp܆ɿ"݄ڀгģπ~j{ݥXl`qhtq}~_|Rq|^qiikihaxMItjsw{|GYkfMHLINNNPNMKIEHHGDCDBGsUWuwhu|^h`[VVTRJHMLIKHKKJIJFLMTUJIHIKLKHMNKNMNNMILMRNMMPVX[oyx]dZROTSZOQIGRRRTPQRPSQQSWOJOSPLRQPPRUTU_NKz}wqKJOLLOPV[Yb]R[`ckirygTR_["PbhnZoVVY[~gShNJ5-9?2E6,0FIU?=2DNHCAV:{V@$BGK5PaZWLp˖הŲǻǽĸŴʴ҂ʋtGQ-,FDHLS\RAF?Nh76c_V"OLB&:;-?fa0/TZ[N01F7E@-OcjqVKRK~~~~~~~~~~~~}~}~}}}~}~~~"zÉ򗞓㏃UffidcPh>5iqn]_[_hk7@PH=36699775.00856556444aN=R\Yaoihehli14:==89B=8;::7=;;<;6:;9976762977568447758988,04=5838>@E>Cohjtt>5/-436:;98789555427,447;663679616389:;7fhij`2WYTST_Z__0/04::BC?@@?Pc4-B>JPT4DML=88KB>LN*'F*,I;GG6+2FIQo`QQO~~~~~~~~~~}~}~}~}~}~}~~"ç֎ܺÄ^ige_\K;3gkjiTZW36;AEE=<997453//0.04686443dV@I]T__bk/13gf34>CswC=;=HC<9:@C;485496345996:9964988897673.:77554=G{|Zcg7<824404;557;96645136.54858:569656555=99:`ffji0`cYZX[\]d3.088:=?ACB@@@~EISVe¾һ"ϵѾ¬Jhxyg{ki}v^Xrrhc[ie[lpgrohpolf`R>Bwqpivy?GWjnfSPPSNJMKFFJJIHHICgQYifnpw{36@zyAFRb\VQQcWPMMPSLGKFDNMKJKNPNQOQMKNKLONMMKJLNGLNMMVaƻq]eYQPPKNUQQPPQPQPPLQZQVRQMTVLOQKNQQOOVSTQysyx<~TNKLOOPVVZhf[\_np{y~bY`Vk|utTySwmwPL9J2?X?B24>EG@+"9LFGG8-8QgV98JUKEBDBICGKQ]W˰偈"̔φǿw_X^q_h}}}wi_oOUvnfuntwpnjjjfY|L:?{up}?HWkfWOFKOQLKRKHJKMJGFEGJC|\XYkgasr96:8;xN^^[PN\[PNOMRiLHDHIQJLLHMM2KNPPLHKHQMNPMMPKLJMMOTUdfwXQZOMROKQMOOPKQMPJOQRlMNQRNQQJJORRPUVUorwzx|FJLNOQTYY][g_gimtux뮇U^eU»x]jS\UB48ANDnFOH==>;B<,I^Q=WG%/IXMA>MM~JNя􀆣ƾļѦĀŽŵثۂ¸񙴫܃iCIEEGHCW^MRS,14-LLb"xvPn]pbACO<8?=D,FbKCS_Ybjbc5c25im17?n=DB:2221773DYC=9178648488^766321248848;54445558??>s{qG^cq{:68<:79:665776798<455237:997656:8?699diadkad^`ZaeZ\U_l11:69;>9>@G|INSGI쁉熈޾Ѐw{ism}rs~znieZ`zakyj\bsvy~mkjfdWUF|tqzvsuuz?FUohTNONLOOPPKJHIIJHEEIPQz]S_skpzux?r8=~FQeǟUf_SLMNLOQOUfUSPK=MFLNMMNOPMLKMMKMLJNMMPQNQWYe[wNOQTSQSQOPQTRQQQLQQURLMORSeOPURTLTZrq{{~yKNQNRQQLTUVZ_osui}T[fVpqΖmgVbVLTTJIdnORH84@AC@8ShQ-6+!*GRF7?IB:?LRºǴ¶̅ͯǀͼ̴Ϭ׽򝩘jEGFIB@HQ\PV\.393\IX"xIKiI}zIZfL@;DM,MCPYYWZ_kmg6eTQ*~~~~~~~~}~} ~}~~}}~~~}}~}~}~}}}~~~~"oʘıumiece]u7=i]^ouolbe52=DE9700344464<301430028f_FIWX]gpjd^hf7mtf?>EE:9786789763102-446665:36/066>;;8866166688tpS\afeh=7``bhaS\\e20379:?79;;>AxzCHKM`/Ṯ뺐χ刍d~wsuk|ogru}j|~x{xsmnmjiieZF?ull}}ttqwAESmpRMHJMLMPSJJEFHJHFFJTqNN`hnx|}u?VlSf`YQNMMPPPNQQQ`aOMILKKLNMMLMNOLMKMMLGNMOJMOKRTXdƫ_h}zHGOSQR[QOPRTRQRQMQRRTSPQUPRTSQSQURPzyxHDLKMOQONGNRSVUhqstznYTz|"puq}n]\XVXWaieUh8VVMO^]N<1B[T@5Wi^GcB<*HIK@LRKHILTi􅛝¿ļνǽ򪪴䈏Ƹ晤Ż񏚕ze>HFHFFLPVNKONU98VA>"j?GJ}jTcIem.)')P/+EGPTYY`qy579fU.~~~~~~~~}~}~}}}~~}}~}~}~~}~}}}~ ~~"دİvkfd__PH/:fS^ospiiih5AG=57635815544430131002\UKN^Y_hqna]Wkkwono@st;A<::70498756?:=:79677889884086005677:6144396;488bnmE_mqp.:<8;?=>155468:>;:957927;:379=55176jlPQ_a2d:4`hifc^^_e20687>:705:@>}wBDMLJrꄁע"琯聈hztlt}ieZsztvlinmjggedTd@?vjs}xww~CTk_OLLLNNJNMIFJLHFHHFEEeSTdglr~|s~]R^[USNHLOMMNORMQONNKLLMMMKMLLOONNNMNMNLJMLKOMSSZ\cu?MQPSVURHNQQPQPPLKQPRVKLPPLSU^ROQWQzt=q?@JGNNNRNMJNRVSsr}shzfRQer"onkjnkce_UVWW[Yajlj`XZe3LWZPB=EG7=`U1,SomjIJCCDv\AC0I]0-+*Q01F>IX]/04<;:=<2/~~~~~~~}~}~}}~}~}~}~~}~}}}~~"荗øóĪ̭~rigieZ}7*9b\]npfilhj3=D>28:::79544413222341_HNNSW^dikgb`cksomgbhjn2@<7<9755868AJC<478567678:=67:966535776673444641:71g^avxj[76688:9<83338:9A;@.:77;:44579740..TTV]ac7mid`cadri_bo:2:74804435;?xxxCCOMRs̾"ւ累׍㻺~gpny{ec`sum{utpklknod[O?@idh{{}}DQ`VKOOPOLOLHFHIJHFIKHBj_\aelxxx|zy~|M[YQRONJGIIO\dWPINMILMMNOMMMPOORRNIJMNMNSLNONORRWUQrq~MNSSRRQVPNNORQNTRYIQQNOOJLORTQOPPMuq;ynr}uzPFMLJPJNMMPV[{x|nf˔USRVQx~"^dahj~ahbO\^cirrmfbKAAfxdND00B/0/G;L/J>GX\a02=@vxxAPTLVםƤ׶p]olkoywxmbjasxtoklimpfWrG?=jjvwz}|{}KWSKLNOMLMLIFJJIHIKHD@vgUXenq~w{|~zty~v}VZONMOHKILNRVSNJLNKLMNNMMLROLNNNKMMNSNQMNNRPOQ\WTuyq;BIKRQJIKQSTQSVMSSPRIPOPPHMPIQPQQQLqty|yz{>m~SLHHQORKPORVWrkqSUWYUP"Whqjįw~g`~W]ajqroli;*DGLJ[JJ0AG48AISL7/7E`dQR^iƷ⬡ۊŸĀƹоЕý`GWLHLRQKMO##$'''LF"T3WeEIFtY44S31B1FQICJWY`-/7?ABC?6~~~}~}~}~}~}~}~}}}~}}~ "䟶Ȥзtkhjgkm70rjurpjkgjjlh07=?;.45758776?:8115359<6277877;63659?y:nijfe6805578548<;:>;8573/-5<787547866652c\_S]pen1hed\1^c_\]Z^32:4565287:?zvq?EIKT𶉻"ƅĩߎʱ`xq|ztugosgon}ocaswvqmpnkjnlllC?lw{{~~}@RWMMQPQ[SLLIFFIIIJKE@mSPYfmlt~zz~~|vy~V_TMMLDCGLKOIGDMNOINQOKQOPONMJLNNKMOLMNOPLMONT^Svo;=AGIMPJENSSSVVVQRQMINPOPONMOPTSPNMkz=|~|=ny}MLRLNONJMMQWdxyooޞnSV[VYXTUR"TW`stiv^`{U[flnmlr\\4BKX\>C215.8`qSKD)&>MWNB.0;9<>NNauĸ˰ʇʀ¾ʿ̸ø̃þ^G_MLOPQRQ'%'"%)*-+",1G?7D]fIBU1D.4G.KUJHIKMQO&/69?B=<~~}~}~}~}~}~}~}~}~}}~"Ӝɳojhgc]\32lmrsg`h57oxx7<;5..3297328744/2314fUEIS[_ckineepnid^knmkjYP7CB:19A><;7EH?1315642887523666:7<36469;8<5:869>}lsgbd6376<7775689B<::6721,0;:6857961176=;gmtkgij1.dbfgc^bX_OWbc42089646>?>tsx~DCHQ`"́͊۹q}logmftyzyh[UZionmnjc^@?tjpz{}ECGPSMHKMJOKGGHFHJFHFDE|^LUchlv}zzuzP_`QKNMJKMOSYTIMKPOMLNMJLMOTONOKNJPNKMONNHMOOT[yp>A@CHTԅѾ厪ƀ킂ꑒü˸㭻Ŷ잩^eOOVSSJI(#&)(((*("*1E=0>fiE<[33+1J9KUGDB@AFHI'-4>@??~~~~~}~~}}}~}~}~}~~}~}~}~~"ϧٴځgfhhfdhQ605iroea]28u~|65?9459==;22776765406eSH@W\jq~ticZgmtfkhfihhXS2?A>8388980J[H2,318889:6406262:786337;:=:16368=}w|~dh7457<=:4=6789=4=814717887.85985/35975tyfdhm/.dZcbbj`bcH]gl4986996988>voeo?HHQ["ԥꇓuigjxsnoxhYglmkjonVD:5jxxy}BEDLWROQQNNKEGJJLOMJGDCqYQQjhs|xv|ryqs{N^cWQMLJLMQqdQHBLMRLKNOKKLROPIOMOOPNLONOLDMONRZv@?BHSTOKRMOPRVKRRPRUNQQRSLTQRPNMOPQQR½xrv79~y{KMKLNMLLHPYftrux\ŝb[[WWae]WWV"U\WUXf||kg[Y[VxzUaiqrtnbd?_cnXQ858?2P_^RRVO;269=87JGc<Ђ쭳ڄɸ׶ʿּѾ╲͍̼ˀ»ı􀂎쒋϶ŽĶڱ֋S[LQhvh522++/-*)($"(1,$%4IH<94M;)-'AHK:;97;DDC&,1<>?B~~~~}~~}}}~}~~}~}~~}~}~}~~"ȸjgdhea_J630ddpokh55ov47;999<9:<71541252123^EPKVdnkvmihgioojac3hv{ik8ABA;589482HR50258??<;7[7=88:;?6989<7:::6553246zyvaa6829:41,3<8894898?9>88<6=17077/033>11uidmw8ceS_b_bSdTO\]d9<564C9;?~rlwzENNY󇒐נľكucfu~g^cs}}dsnpkigorMB<3ou|}ABBJRRRSUNLNNKLKLLKIGFBjM\fk||z~}|w;yN]cWPJMMHNQj_F?GKMMLMMKLSQPPNQMPOTRJMNNLLOMNQTŲo;@AMSOPNOSOORNNNPWTZQPTPYQVOQOHNQQ^OP֘ypw:tw}}suxLLEKKKMLLOWjuvychllh[S_fcXTU"VZYYYclmhecYT[a[\uuR_jqrukII?3qmdW8?LVSbj^KDKI7'!#+/8P@=$KZ֑ҡ邎퇕ƼȿҼ܊׏Ȁ˽Ǻؾ݆ջսжɀU~bQSpBBA>4+/..*##"'/-&"0?A9;>3'*-)%JJ>=B>:AEE%-4:7<>~~~~~}~~}}}~}~}~~}~}~}~}~~~}~}~~"ʸDzriedecZ@74mpsolnk437|t9<<9689:;==HJ7.335799?9777888<787>7678842555/7;wvvof494553.,0<8787;;8:;>:8:@;5654:202631XqjhnwredN`^bo]c\Z]`0:=<344437<=}tgy}JRNYϋ"ۧ⊖wUV]ibbqtstzirusnidkyq@>@?Hr822578:8:9;:35377988989626254402=9qnwork59844//1:98:<>:49;>6679974625255460Wcl^jiljd0S[betY^\a`c05<8657669:=vtmwKLW"ޱāuTDVm~STW]XWV~vdrjtsmilp}ll9y{GJKFIJLKMOQWzwwckrsohVWWUVSVdundei_[\\X~sndlrXibUI<0!"/<32$'M,0,01VUO97RQCDS^P8D7WPrăµȾ퍩،Ľžɲ냄Ŀܕ¼ڼcjTOR\JIECE71-**NK"(PPLP0?LF?>>3,--+QN9?TM+$6;E+3/%M[~~~~~~}~}~}~}~}~}~~~}~~"B솎҂ۺڗqmgdfdb=2f}z:ynrw@>:689?:8;896453--1/23]XNJK\ilgcdjkh6sg3bgkoED=@B;:9::CCo558167989;@<79:74/6>8041.-32653/S``ln\iehhbbc`gi`^SS^a56<96476668vttryNKO踭灓犐qPMuSVSRTT\WS|~YVlotwqnyz\@>{CPOICDHOOPRQTPLIJJIIIJKIJEnYWdl{{nr~}x@}@b]UXbROMJKOXak_SHFLNJHKKLOVMQMNKJKKMLJLLKMKOKMLMKKODIHLQORSRRTUaYRRSOLIORPPPLNPSQSQPO{uzzyuw~}xIHJKKKNNQ|wvқjpc_STZ`hknj`^Zsupf|bXP@1&@]Y"$$')VJF041.OGLPGDI%+SI}Q؆ĺþºű罼쥷zPLMJWBA;@f.,TPPD"IKKMTW3:DA<;2/-UNR?ASWL74-=Z3/&PV~~~~}}}~}~}~}}~}~~}~}~~}~}~}~~~}}~"Ą̀ȣ|ogdmvnOV@ji|A<=uie9:677851:67723.02330+..01]UNRWZjjkgjngrmphgkh\o@?A<>BE=<889@GDIE61,/5:<:6:896677357825:2.14333945706h6quo09>066:5>79=:::67:975=;72-5.01256-.MXjhm_\deeedbjk_cOVO018678167=8:|ngruGINߪ懍"Ҷ|jWo^VSQQXhaU{pxzTXS~zvrltZTC~LKKLJDDDFGKRNRUOMJJIGGHHIIGDq\Xjr}mwX[WLLXdSQNJGQcvxbHEGMOLLKFLMMLMMKGLNSMMSMLLKKLKNKKONTSAHKILPSMTQSWSSTPRTTQPSPPQMQNNMMQTOOx~yzxpt|EGMJLPKKLQNPosunTWXRT"QWƿvv`[`vxYA2&/l7(%%%STHI0862]H<@<88"-2b^]jǂƱ͸¹е׾¸÷俽ҏ󅇅UTUC8Oa3.2]..VMKC">F"HNKJ0}u6@71+MHTFD]\\WM57S^/CBO ~~}}}~~~}}}~}~}~}}}~}~}~~~}}~"dچյڊ×whchnzlN|kfnsBA6dh5<63577668324/.12210,,/.ZUCEV1eikkkgikotv^fx}qv@C@C?><99D_cV84-,57:988:?:56783075787556743/3464/ie7>60983358?878:?5<;899749;71/3+/01451MO_pfhVaejfdhe\S_aQRS-01345333;:y~lkmbG:DFRڽƬிg`U~gthZXUR_ff^thvX[xolpxKx{pvRPEFG?>AGMPOOLNNMKJIFCEGJLHkSSpF|yy}Y\XUWZWOQPMJVnmgDDEJOMNNKJM&OMLIHJLMSNNOPQPKMONKMMOOQSbNBGGFMRRUQxUNRSUTRQOQOPNOUMJKNSWSxyzvwwzuqGHJLKLLLJPOsgj|֙k`h|R`ԪXV}Ugz;+):@G9*'%&QQQZ1498WMDA<01=.4d]d晢󈖈ʽ܅ɹΛƸͿ뀄®;ƴFUVN,8HQF*Wdg[OQS"HD=1GNLPrvw/zg0,IBTRRZY\djM9IN(<+?~~~}~~~}}~}~}~~}}~}~}~}~~~}}~"ۇȏ񂏖¥ɕlbbeofW?;jmkk8m]fj67o565664672011,2734--+.SCFN*24deiggjlpxco{x?@CDBBABA>:3UQS`[N@`\Z/NBQ-[SPQ[5UAKM&>&4~~~~~~}~}~~}~}~~~}~~}~}~~"ʴՎ鼟slgitzG;8k`dn78443t>;99765585450131.1/.W1/):K?C2895464300.35135Yc[mwdbebfcfaaJ``q_HR+,/02387357;AJGFFHIJMPNMNKJJEJMHEIF>[]\n?F=98w|TWY_Y_ca`e\XRNNONNMx|>XFJKJNLLKINOQOMRJNMKNNNKKGKLMNONOOLNLSRSQNGICFMQU[VSUOMOPPQSMQQPQKNONLOQPRQ{{kt}z{{~s|}GHKKMLLNMMMPTSWYfhorktUk"hmfY[~VflLQR3C5-%"""%-+&&*25,)J5 FjIVZ[SKWǎ۽풌ނЪŵÿƾĿڮêׂ􎎍|Vi[~:3ORU^knfl"nPB,@HR\PFNU*K'0~~~~~}~}~}~~~}~~}}}~~~}~~~}~~}~}~~~}~~~~~"ӯԋĨzihlnxv@4?raj896877@E>==8427974433472213bhoRGNFP3}kbe016jk5:FBBCAD@BCA@BGEC?:<>@FNUH:::7:97:=9965776:>87468886;889555>9>=75=7:58>98832778?=<=938/5021/,2-,fifcjmh1]jgw^.`]\\`aL$.-0342792957qvnqhryFKPLNTԆԸȯ֚Ӻ鋊ǀ^XUVTW`edXmɓladmyVF>zty?=:?+*K$%+0+QJ83]SntlXPf޹ɷ醋򁂙ϫƿŹ¾澺⃧ƲvRvrNkZ(IV(WdkbY"T<2+2LRNb?33?66BHUSC7:KSZLNW20S:G~~~~~}}}~}~}~~~}}~}~}~}~}~~~~"޷⃆·ŁǢϤ|qpkswU]*edpl88=E;<=?@E@:27;@;46647:4-41\eULFI@PdwgZ_h6lmfnsAGABCDED@EA@@C>:868:EE<6798:88?8<7676:6:;:;:544666;9<986/758;1><:898748787:079;><=B<63484300(X_Tdbdft{e_drfg/]`X]QQTO+5.1101575957:rpcllmptNRSKβȝ̩Դ"叙䃍zW][l}SVqk``pgjnbArq|ACGQLNLKIKKHHMT\SILMKNRKDIHm\TWR[lwndhy@y{w~Xsc\^gki_aYYXZYRLIMMOCDHIKIKLLRKOMROMMJPTRPNKMLLKMQLJINOLNHQZOQJFFHIMNPNRQSMRSQSTVaYSSUTNOKNOzzz|zw~v{9u|z~qy|DLEGJKKLNOOGMT~|wugnsz쥎l~~"v]UayNRbNrY^[UbXJGM1:9O1&&)F;69CGPKC@BCNi[Zr̈ͩýIJ㔱k~T]FJ?FKR_a_M">-$*&?QKW@:,;88JO\iK<@J]_QU2211X]~~}~}~}~}~}~}~}~ }}~~~~~"۶狅ҴʘttolwzT?)gin967=A>BCA<:A=98;<:64466653010]JHQBDR_mbaagjmqtov=BFA@@?>;~x;>Az;6884046815766<;>74995679<7068864656776<65565595::4.56.77=8894:8;:9@D@@CFA9:994768630456b`JRODZW_ja^afksyqetyCA>;;=BB8<>?=5:::669435536:899473989:<54538145897744<<65644g8>//6>367?<72>7:@:::<897=4331/-SSVhbcpwrgakmd\nb^\[RVKN427541257<757pphmknpksx=|ILS̵ڟ䶫"ﶇ²ufVVxUeozpqowxYAwwDGFFFHKLPVVY[c^RQOMHJILKJIJIJyX]XObSgnhjou|{~yoWacYWW\[VWWVTQPORNLMIJKKLKJEFKLM5INMOONKMNNQJMMOQOQLJRQMMNOQUVBBJQKPQXYSMPIOTRXUVUTMPOiQTuuw}ywywvshrknGIJILJMLKNMMMwx}y>k{¢~yz~WU|^~mlVUYX]`E;YV`szs[G)*OH>=>;::7884434478=>94668988855355337773677786943789=932=7088851>8<7;<;8:;74,/42000]_f^ddicafiljckd]_ZPKST23857,3489662qnllpn<89@Cy{}"m[ib~asXU14-Xb9|c)(NONBEP)NLP^hfK@Vw콿цƨ¾ɴʀĿŹŵĺúʼꦪݽ~ca`glV>CYi^WV"RB514:@J[gCFL:KHKb7]@?@>??A?>>:66244320724678878;=:;224686634=;<767:?8:577n>;;6483698;67;4728?698661456i4//nYc`b12ee]lhjT_PKUcVHSV-5:55236<:38:upqqrr==99mrwQTgyѴϱ"ҫۊٍℂ멲ԀSWYUV\a^^h`ymkm~wG>ww@CCFKRS_dbc^]Z_fRJFIHIJKJHGqZQio`Z_iojvtsvzy|}Yib[\eh`cbZUWVURLHLLMMLKLTNLJJMNLLOMINMLLNPNNLOTOQNJIMOLPLOO[ZSLGNMQRRTPRRMTSUVNSQNPNSVVVQL}~=;vzvzywYYT[`hgdkq>AI9JAPTF;9HJEBVirqgRPqrᑧòʻĿп󍓏ùź΀Ƚڹ聅ɷư뜲tWaY_maN2OhfZW"S=.-;@7B^ZEYCRDMQ./3bCZT0=O_0WNBOO~~~}}~}~}~}~~~}~~}~}~}~ ~~~"㉓궘޼ȳ{kkrwF5ip32438>==:<>>?BCEBB=87;247:356655:9878777:755<76926=9759:8=58679q<678<;7;9><5;96375<38764124/2-0`jgif6/1agbhkbMO@AVUbUYW.1423126769896npp78=<::7u@HNTblk|͢ھ"ʴć襗€X]^`effc`ifdtunrkkxG>w{8;x}w|}sVOHUbVgglp<@HKNKKNJJMLNM?;>@AABUpyuxu"whqgxkd`c|hs?R-).5;?CA?A8F=I@-8GHOGB]rna\RRzt½ñʹ¾󌉊Ķ̀н˾ƾ¿Մſ񕦳rWf^XelW7C]`^X"R:/6@DA@?98444642432eaVHVgmhc[?EX]efdjwtwmosvD?<>9:?@?@C@AA=98=87665644554835766582169;6555799:8=<;::676737763859779866788418:7321-/0-)`ebadp25/`dZ`]S=?=IYRX\j1145324476685e54pg9<<::t9;BUGN_ji맟İ"ɻ߽ێɿȻÀzQXV^ded_cç}oXrX[udnkjyrH{}w;DGJNW_dU^eQNIIHJKKIIF~ykST`fimcLXhjqurwzw^gaa\_ii[WZ[f`TMINPNNMKJJLMLFMMNMMMKONMMLMLMLIJLLKKOQNMONKMQSUPLIOQYXURNNPRUUTPSZTMLNOQSQPw|898y}vupdMMK\hTVeu;>DKNLNNPKLQLJE{AA@@ACIX|rt򥨦f|z"u`rzyjrheaXd-5?",,3<@?;;lY>67(CZ]-KF_9dEASgDz逇ŻӀǷƽø߀ÿ爏鐖po_Wa7eBAY\[b"Q74CBC99<2<=cKtK_6..gN9DQUOKSQKDJT~~~}}}~}~}~}~~~}~}~~}}~}~~~~"ׂ浡߁Ͻlifdklfoevu98:9@HBy~{kzq422523352`WLPJ^fptm@=K^_adhkorov|wIGD?>C@ABC@@D<:98857;295427764::8465:1964745696;57::>;:7458778;>:98;66:=8559:964//,*.,[djbcs530dfeb^D@@=N]XXX4/34414223557fm50hk759=wx63AFIJ\ůҾӐ̎Ȣ{z}|QVWZZY[baWnZzbkkmiRw?FMKPZVLJHKJLJIFk]^T_l|SK]iijmruv{y|fhf^]b]XY``YVSOKJLKMPHMKKILPOOMNPUMOKIPMLKLHLHLNNPONQMPQQRPNORU\WTOQPPRWTRQT]VMKLOPQRPw=:8y~|ugONMMel[RS8:ADHIJHJKJIMKDz@@ADDG^pjr|򮶦`#{mekmdcfiru,)&#))3tyq;&1,EecWHGa:hRNiz̊ŭľح℗ǃ䇊酒qfTZ2kZLXZZ]"QJFSPOHDC=3jqU3V@7,2R@OVeQ68ABAOZ~~}~}~}~}~~~}~}}~~}}~}}~}}~~"⎒ᵖpabjniVrsdw=<97=EAB}utlZen\1/041-43-GOJ^[bra7C]ddcqvzytyvvy~BJFD@B@@A>;E<<478852;7535466947;7;8846:79@616788;;348:977::;?<:::A7=;577>=@734,0./^dvddcih7pjkegZA=BDIYTFW1/043/5364595mh4cmp8499ux56@ADa쌁γÿӬƒ҃詙}}nlYXUUd\^~]jdiy`Xopj}BDFGKRSWLKJMLIHCBf\P\frgtsjI]nof]ruvy{~z}Oegk\YSSVWUZROGJKLKHONPNLKMMOJJMLNNSOQNMLNONRLJPLMNJIMOQNPTURKJKSSVVSQQYNQRRQMSVZNKOKQRQ~w~|=yz}zv]EGMTZeNM_8=AEEFIGKIIKJI|@AC>{DOhfkhZ\`"{Y\W`{mst{3-?/*'.psywQ4.7Uk/NB;Shc\bsžкͩ`ų灎ԀöԶ񺴿Ɗ͸ᅄęk5/S.6iZUW[k"[XXe^OJFHL;6X\Po:G*f]hb8cC675:LR~}~}~}~~~}~}~}~}}~}}}~}}~}~~"یݾǴsdfns_CO{lo8<<<;@FAAuoprsig[\11212/5a@;E\ck]}3@Qdlef7xttwzuwyyECA><=:<>8:887?@<<9751223ReybnbikjriojdR?EFCEMLJY.015394533882343aps778:u:45@@@CEP]_g̲l^]^"sp`gm\[_c^qfhlj:B0)L3+$GQnT5/Rp1I6/AU\^h׳ɓIEkؽ;̀ʾ춮ƍ񀃄g60S,1b[ZW^i"aYXa[NEEGPC9<2JbFTcAC26r<<:ZA:9:;>~}}}~}~}~~}~}~}~}}}~}~}~~~"΁ױﺪĸd_hqzi=?BBxtttyujiX_352523^NE>BXsxƂ4CX`lhj7:ylxwsqupAA>9=@>;:89387566:<>A;2676664677=:85.6887:;;;>97132464:wsv9@9<::D?8595=;92::<;=C<:899:5789=972:797541577:96689:66<;;976566.:8;yea6:?;=?>57748;>96489::67620.qqgjTcgmokfkmjjhV@H763mnl678wx:;9<>D}qŖ"ȿ뿢ؠ㸷UfaZsTSyliwselvGCnv|GOMNMQVRKLMJJiTa_ȇDYotoprv}vxzyxzyf[WUTSTQOKLLMLNNKNMOMOILJILMMLILLKKKJKIIPOOOMGOMPZWVPNOSRTRRWURMLLPLMROQTSPN{{}z{}~}|zhMUIPTR[LcDDACEHNNMMRVNJJABBCJPSQWle\]`ȸcfedwww\|~ \hijklh`)@&+J/,BjvNHa\:("3O[]iûí퍣[alͿ¼̀ǽĭؾ¾郙r|]7111-]ix`PW".0/2nVGHE?8FJLCD>OOCH.27?A@<7aWdUN~}}}~}~}}||}~~~}~}}}~}~}}}~~~}}~}}}~~~~~"뎑󹟗ȫҤy_bqxiQoI7479::;77<@=>6::;67878463468534440:;;668:77697:8156852779tc-3:9=<8<0732;:A779=<:;24500/7mim_cliiv|okloaaH?>GEPCJU147<303438=:58>9643379;2968>?;Ghe"ˢȷǀŹWR[qcgwckzv^^brl`i{fF7BGOmhCgejijryv5454/QIFgmm̿/qtq9=99567<75565365552:090369:9:977548698854374709820*488:<@96287:887;;<;:875:51/2okmcSebvoCynqro`Z@EDGI?KO083b..1135{u689965,498/6?87;:XbA241C<)CTY`isт󑞭ƻĮ􁆔բgccWguhG߃ȀƼ¸ö܂ưP631b`_ac^ZP"Web9LUTQA:;9616G:660QKHM ~~~~~~~}~}~}~}~}}}~}~}~~~}~}}~~~"˂s`doxqEG=o8<:;?@EMZ~ixYQ_fhv|sp42`PLeuKȭʍ0?Ycwpk6:6oqtrwzrl@=z<<<><13459:@7522456568:564B44<=9847564365676506546>6./)59:69=5169598?9>?=89845844/=zanS^dlgnwr\xxFC=GK@JT-1/1233648zn/988gq46:799=995>BDQWK޲ց룅ǻYd^qo]\ep}rZ`auxk_j}WU6pBGHLNQZdhxjvFHpOSiiP׉AVt}vr7<DAFKOMKINJJKEwAACBCENNMMSXcu{trj]"cxnprjwgailc_28aJ:2VeF*%4H?V'*/DA25;754WLJCG~~~~~~~~}~}~~}~~~}~~~}~~}~}~}}~}}~~~"󆝫㞴pZfvsIGyl6;;?BDGNaGVLYX`cvm5^ELetΓ2;68726939<7563669:8982552.7678;656255<43/44:7;8<<88>;<==:==>:818755414pgld[ehjknqrtzxGBH=;GIMPdd/2121465:q5858oq8:9677<:;9V$//-,(&(.;4GA%.I[^ialo|懤担㰜ۿs^\]copȀ÷û̌ރ~l7d]ahaY_4^"RO??fZUSULH@CI?/EE8qq:;=ADK^pUu^]`[an]EoZCB`zǚ2B_^hnnkwonmpsturm=A<<=;96273:4iZeimmecfebktu~Fk:?@QNRW423522356u~p87667:>:;899;:<;=DHGUt"žo``szxupihkg`YY_km|eiQNpsAHOQOW{_umtntyUoMMcesmHhzlzyxxy|yvabVPQOOSOLMNKKMKLHKMRLMOLKIMRKKKLLLSMPMNMMKRMKNNMMNJA??ALLNPRRRQSSSVY[^_UQNOVQTUwv}~OXkALN\_hnAACGHLMLMNJE@;=BAEILMSONPRdvqzeV"fmooy[hpf]b_N>+"0:;+)-,*)##+'$ #1Glmaqѻּǰs\ajoppû􁇌ĶƀϿøɼײ՟ۂƹǷmb6jccec[g5a"SRKVK7<=FNjX 0A<.6YhThn`dmqogWI~}}~~}~~}~~}~~}}~}~~~}~}}}~~~~"ȩ|llw~`l3pq;<=EMN^ubU[T\\Ya@KeMH^l•o7HY[ckpsvmmjilrtq9==qlsv::166877<9;369<;A93897976:8243349<655648=64mxo79=>;:?:99:>?>BF:4635078ncWkj}jhfgmfn~tK:HUTU`371342357ws9=;7t:;9:7:9=99;>DU^wǷƎ钴偀øxxzwpgii`[Y\eprzilGspAJPTT[}hpmppwqj|SllUP]mplǛx~skXw}{||{qw{Qa]OQNRNONLNMMFMSSKQLGLNOQKKMMLKNNMMNMILQMILNMMLCOZbjy>DBGKLNNOMLG@]kbqѽИりܹlofT`np`Xȳ򅑆枷Ż˹ŀҷDzӺצi큜₢ùwhZfla\b``lpp"WG^uK.Z?^g~QqN4?h=+[Qhm::elowxkaU~~}}~}~~}~~}~~~}~}}}~}~~}~}}}~}~}}~~~~~~~~~"Ӯη͆߱}wmmv}[C'x<<=>FXj^hmJya^Y[ZVS_14ruYBAH`شɾaBUY`[[nvwfbghmqqs;8>|p<:<=468886;268618=>9486899=7346:8778863;78297786ekne77>>;<>@488=::?A=576606;mahwomfkkunlmxud>AUW\p434213863vvc68:st<;79799<::>EHoj"戙ⷱրyzwrsy|rkaYOcux~jg|koho5m;GNPV^on[y_onotskl?n}Wjq;s>@lZ_~~~~}~}~~}~~~}~~~}}}~}~}~}~~}~}}}~~~}}~}}~~~~ ~~"Ʌ۞ujdhy@)568==FekahRwmYR\`-.cba^eaPDDXd|¥^\PZcdacijommrjnpo9=>FD?>>BB:8456167878;9898866889957757<84=8>77766753679qgjh4/488069;657528<<868507ws5iar]jfmnnmnmtkeA>VV`g0/2/36533it_6:97889.;678:;<@HG~ͧ"ꈙئˤ􈒆逃cduvggjr}|iYQLU_hoycdsgqT15?KSRWrauiikk58wy|}lNIQdop}dy}z}LbedUVUTSPKNMPOKNMMMORNMNKLMNPLKLJMLKLPMKRJMJMNLKLLKMLJ?GKOOOQOPMNONNRT[TKNOPWW{{|hN?F\e|=@HINPMPOOHE>@CC52;}8kkl`hdmwmqpoV{냏d@DIBBNKNMKMPTdqn"eoqnxyywtj7+#2- '76'%(&-"!$2C[43vܣدعbmXVgy½ぉ򁍍ʹľĀйʼýǻðʼ@\yPqꉖ폟ų΋dheWY]dh`ZW@"LiAD?>}eNSSP}LW[M^jfgkdzH@rVXc~}~}~~}~~~}~}}}~}}~}~~}~}}}~}}~~~~~"ݲptqeg||9Xa8?AAcNMvKD@jkgikqk^\bdXCJ[aqybMlxZ`gc``Zcmkpvjge24py|96<<5=@;877788:DB82;758643=9:369<458:9:<=8775536578qrna6343555:7534462/,//7564szq8molYfrqwlurv兏;FHFZ^./395466uzne38328:7885229==DHNKwŝͼ"ȳ܂ѧqeTc{l_mp^[o}zvVZl}nv@fyLPOVvTQsWQK~vv{~~|~mOUbepjtp^nzv||tqBFQNTTPTSLILMMNMOVSLHOJIPOKIOL PLJKNKKMPMKMMLOLLKLIyDBFKLHEJKLLKHNNLKPLNJOPXxxraln|U2R^dpv=ADLNMLPGD:=EDADIMLHKMLTfsdҗXyphrx~my}kϣ;4#!! 22 )-)9LG6:'Cepmˏ훷胅٪Ҵ`j_ftû牔ѻȿ̿źþŸޱD3nGHFFCwlp\WWONNZfWbyslufqEs`AI\~~~}}~~}~~}~~}}}~}~}}}~}~~}~}~}}~~"샽ۺ||mctz:8777599JJLJ~IMLHNMIMMMOOPRY|~}v{|`klkdf7Hbh|:=@EKKKLQHA@xw;AHJQMIMMLQX\UWrmtzn`̰{z{`hq>?CJe4CSQ)4Oej]R2E"2>N`jíٌﶖհ_rjt{ϼ怇څ¾¼Ǿŷ⚵þ8868pD}ξ򗛇xVojdcea/\PJL"F_DIKCBth_\ZQTZMF^`com[|sQ-86664547864789767759:997656777i654ov@9546;6658896>:8988<=|ttnyrbZehgpqt|L?AS[0./455459q|s651i548;<988;9::9CDI}߹"Ʈ궲dTd_lopлmmNiKR`|NP{Ryjo99;x~|wmjVKRcgzjr{R{toxvxFMMNJKNMOPOMMNTLHJMMJJJMKLNNKLLMNMKMMNMLMNOQQPNLKLMLLL>::QQMLLPLMMNOPJMMKMOPUYϵtvbinis8FPov;=@FHKMMQLE;z>He[0242598vvw~>863em678=9:>=:998=@Frո¦ǻ̳۴jr]~dNOdopj\jwemSg@LUsIWM{Y{nfq=<<<=A{ztn]MZcjo{[Houxq{RILLNMLOSMMPLINLLLGHMLHMLNOMHMMLLKLJLKMOPOORMMOKLKOKLJ@tz=ACJRMOMOONROPONNNMORRU]ٴ9w{fbofr9ERow=@GFKNMZTK=mr>DHNJLOOOMNNMLSԂs"zljūrvnlcet`dnAH6.#; Gb^^X;.61<=g݂ʂԏþ~b_fqmhΝлߛƼǽǀƽʾξⷻD086wMۺ̀ûWDjmhgca[ZZOP"NV]T]ZjwDB{ski_aJD<6G;Bo^[MPcy~~~}~~~}~}~~~~}~}}}~~~}~}}~}~}}~}~}}~}}~"ܿ˥ґzp\_s|E^:HWrGQBsib15745699d[]b[MK[br㜜v:cxeehhge\Yspodggp{|p,/2:=725=89=<=9605568;<=;;:788656466:;=:5579:88767:9886dm69;;546:8:89<619;B<;47C}vvwtvi_12oqtrxBEM^K21528678nrzFJ]ٽ׳"ڷňе|\WX_XtqsbPbcfhWx|bjGZ=N\RVDtdk7:;9;<<=zzzwkTMYfyǔ}XCu{oy}{}EJNSRHJNUKIMLMMNILKLMNMNMPQM KKLNMNKLNMJIMMMOPOMJAnq8=@GOSQPOONPSOMPLMLQQS]4<|yygh_nXpFXsv@AHGLKKNWXOBCCGPNKMQXWPLOHLVXvö\ml˶jp]Ā.*,56D /* Wnmn\4(95#?aRp֏؂ؑꉊ̽zXZfwqƬõľǹ¿ʾ¼مڷkF563aEGɀSll_bca_-ZQI"T\]XVNOaED~okkeZWeQT8A:]UY\s~~~~~}~~}}}~}~~~~}~}}~}~~} ~~|}~}}}~}~"ٽڟᵡ񭒿pcppCj?RmV_Fmd+/477m778oebaYU\`fq֡P2Umqkikrzoblnrjd5u}q6446=C502699>:;7335975:7:;9864775320;79:<9768=778692759rop899<:99;:889;959<;:96:xxuuvr[V34jmsoreٚۗÆBAS`[115493255x{?77C>7=9<89:A>?;<<>DMl"̿΃耭Y\|f[j|_`asqwjEgAVl_`Cih39;<:v?=<}|yrcQO`hl~|F8fu{~~NMLMOVYHDJOOINJMMMKKMLJNLNONMLLMNMKJLTNLLQNLLLMJMPMRJMKIwx>?CKVXRQPNMNPPNRMGLQRV|;=|y}zTj__y}OCLs~?BHHKIINOSIIUOHSPNIMR]SQOPJIS[p]]"spmnkͷdoiȀZPO04H;W_ ,Xnmzb4@`@fDJj]ˍށ镟𿃐¯hMkaoȔϼÿļĻ¶¿Ž܇¼Ⱥ適]61/[KNЂɼӒ¾\qkeccdh/2RK"Nd_YULEN[euyA@>{na\nhN/::aTeiq~~~~~}~~}}~}~~~}~~}~}}}~}}}~}~~} ~~~}~}}}~"꩔ܱ섕w`=d?OeKURukp,4:65e033`kjdVR\bq̨_cpmieijkgZdpsnj5]o535:>?@60.548?4:73665568936267:721344747898777:9766768577em879946>9989:>=6577821qzrsqb}hU005pvlv}KʅAKOfd43652325:=85A@6:8=999==>A=:?DLn"􊑂Ħ怦]ip[fu\{okDmCPdR`Zdu7?C?=s:@A~zr_SV_f{^ptwuzwqotLMJORUWXLCBKMNTKMMMNLLLMPQMNNNMNNM(LJKNRMLNOMKLMNMMMKMLMKH}|Wcl]]ODWm|@AEFGLLNURLHWVMTONKNORPQTPILU`nǶb_sxloŨdh]`XO-oiWlIq/ .EILV;"EhosîҊςɀƇŷ~\pkqvʴǼ¼Ā·¾ꌚĽϿƼ񇎁b;90._OLy΀̥Ƌľ¾oYtplfekm42QP"`h`XSZ^WU[X^r{v:@taV^oxlLU362Vgxqw~}}}~~ ~~}~~~~~}}~}~~~}~~~}~}}~}}~}~~~}~~}~}}}~"ìչ󇣝݄ɺar~sg;^:879:<<9477235u|wpvl[zee7/3orqx󍎏ж9GUgg;575152/9B7588558:86398=;9<>AwuhUKNf~Ņ\ztw{u<<;x{MX`PSROPPU\\NKBFKLMKNOONKMJLNOSRPOPNPOMMJLNMNONKKHONOMMJGILMK>8B@DGPUNPOOOPONNMHLNLMRw;6<~˿e_]L?]lGBDGHPNLR[LGNOMNONJHHKLSXUMQTZْy[c"ãi[_YˀS`H4BEIRhU2:~rq68<8879=>?74230138:8405457436799785-3557<8947:9342442868789>;=76;45?887;983279:snwth[Uiddsnkvkwwq҂=OUei475755466@142557::85;:3:H?A@EMI&̴ֶѼ쀀yafdp}oNvId|rGjFnQdZjNxmw;;=yu8DKWȪ"пϷꁐ߀yuYj\We}[_zpmGnVl`Wyjhmq;~ue7@vumhkvuxaVLorqqyz}AJOMTSTXUUQWXSKFEROMKJMNNORKPJIKMLLOOJMLHJKMMOLLNLGNPRLONMRSONNMJIOOQMMNLLQPVQMOSRQS][syz@:xjl[Bas{}ADEKJDKMZOMHJLLT\QLJGLKM-)*TU^h"]Z^[lnebɽ71DDM-)'+))498BIY=SolZcܥ떥ׅĸ]emdœłÿŀӿɽúȺۂnhmFA^@iޕ򶴍ƵǼdž콝L\ieS\_ge]Y^"^]^`]fkaXNGWbgdd`/a\Ve23PR09933moy~~ +~}}~~~~}~}~~}~~}~}}~}}}~}~~}~}~}}}~~}~"푋۝Ɠ߶z||pkbCn{opklmpmj3420688ri7:975jhk76=:64783366627:785/436668;:<985564550.35045;65775:167968556469;:7=;8=9::Hdge5973.ciqput@3,1227=?<99=;;5DK˫"ʶ²湀m^ja\x`\YowtmHlFz\wkLfmr{>=}wx}wxwjU|~cuRHugplu~}~@EIILORTY[VSRRTOMMPPKKLMNLPRMMKFMPSOOONNPLLKMOKJMMKKMMOKMKLQOLQLMLOOPMILNONPTRQORQORR\b|{?;gu\NAa}zEKKIIUNGIKLOVbUIGJLNJ%'()U_ٳ^ga"ca_³bfmkli($XqJ?85C89:9>ADJ2Jcj_oڌћпÏmmjV停ɾӄ吜¿ƀĹǸǽѺ灀_>pal;Y܍Ӷ撶ĸνȂʠIarg0;5]jjX]"]WV]_]_``^Z_iiX\[12bbk4c\\1367:9uj~~~~~ +~~}~~~~}~~}~~~~}~}}~}}}~}}}~}~~}~~}~}~ ~~}}~ "ۦڥů󁗻ᰟxkf`EN>AOnx9Qbd333oljbhhovlmsrxĭH/olnmmqllj37541778j;<:96cgm7:;9;A?;758165798743663779<8::;873445556876;9;<6673135:95658737896:=9;995:zinwsoY`^^eejkmnmu~⊪؁ARnfcg591Zhnohnu@8132/4;FE<::9775CEJPζ"悆관nmYXpqMx\oո}jknOSAWZp~Caen>>;yy}yzz~uoxwxáYcN:pkux{{~@DLJFMQVY__XWORRPRZWRMJMKONOOMMMOMPPSOOMINNOONLMMOQPNOPLMLNPJKOMLLMTRKKIMPOOOSPPSQPNPNT}~Zps:Ef{xHOIYPHIKLNSf]GEILPMI%*TVcr}}gl^"_``cchlgdd3@f\K[qF+C-HC>DCAL&-Qe`kɘ先־ùzfXJ͸݈݈ǀ½żǾƥкc8JWbN`ٿȻ¿ͳTVzj7@1AZaZV"\b][b`aa`ig_kpf_Z/24klg^`/156:?:r~~~~~~~}}}~~~}~~~~}~}~}}}~}~~~}~~~}~~}~"Ɗή֡nwhbeW~M6b{~AP^h58njjfce]hpqqtzv˟׊Fajnlprrqlai36432687;=<<:6778>:9;@C==676656588251678=;@=>>25;876766687<879<>:6567<55661547367::997z}f[knk^U\.eihnmmet{u׋>Qumnrkmbdci5_e??854115:FXB<884539@FGNdׯŪɽé􃇇̨wn{_}nRiab~·obamdtJ=l~Iacq>@xtwz|~tx{tnp|ŢvYItqpv~|xFNMLOOQPV\][WQQOPVSQS[`TQJKLONMINQLLFOSRUQRNPSMPTOPOPOOLNMMKLMLOMNKMOOMKKGLOPNONPNOQUQOQRB~|}qw`fcdpQB\|zQc\PLKKMOSmySJFILLFLUXXiÞffV~"``a]gomdc=VfXKZ6C1CPLGMNRg@L>ؤɸ˽½Ŷ縉rl47(7>@N\"che_aab_^b_TTioid_0254bUW^5;;=:n~~~~~~~~}}}~~}~~~~}~}~}~|}~}}~}}~"ѩʂ༦Ěή鈄xikspNjDU^l8moghggh4sxutsw֞oanyvpnsrqlt<=;:8968687666899:=:<<;75;;;:779787;:756794:786:69468685445786887:zq\Wlk^a]W_hoksnjfvzJ}ؗAOnekqlcUfp6341;75523338La;87767;?EFIWi׎ʰ"ʱѷȂ䶚wvbe[ujziyj`i|TmKeg{Azztwy|A|{}wV{Ɠ\qrw{|{}||O\UQOMHJQUZ_WUNKQSSQLTVPQPMJPNMJNPOMNTSPOROORTSQRPPPMOPNONMLLKJJLHKJKLNIKHNSNQNMKMNQPONOU~vv~uKlbVucGNttOORRYQMJGLMORv{JFGIHHMRWWaoten"v~a]\bnuaxACK0<*5+N?CFJMXUOV^VRU^izy㏧ԕöUU~lȷ뉝ÿ̀Ƚº¾֧상R9/.eKIij󊎄´ɽƾh^d6;][`G9R"iiicaffa[ZUSLO^^he,+/b`PMV5:B9XHgl~~~~~~~~~}}}~}~}~~~}~} ~~~}~}~"Ī톉д䷒n{}xrpppmaWWj{zoN_i2emdajkk6qssw}Նgavpulopqpmm?B>77445:BA>B=747=976;88=z|z<853975148=89;899:5;;>>>;957648987984/m?868587655635698627899wuddhg[a]-^gljsnhnwїe?]Tnnje\et;434634487388@\8787::;=?DGN_ܴ"Įޱކ⩅vham_c}w|wpijr}{hYZd}m`r\du:t~toyz}@uXĴ{Qxuw|pysvrrxR\WQPKFGLWcWTSOPQRPONRNLRPOKINOPMNQSKLOLOPTRVQPPOOOMNOMMIIKKJF?~HEFJKNLMNLNJJLNNMJNNORH}y||lbVxh?``~RMMNOIKGJKGOQ[eFHJIIJMQSVagp͓`qdi~"xsd]Z]npŀv778878=8688:wy>?:6:841/6?:;<<;6@?D?B@CCmbxyuryy|z}BKV]UUMMLLMS[\TNPQPNMPMJORPRTROOMMLHIROQPQRLTT[QROPLNQNKLKIKJJHC>|>=BMLJLNMMIMOMLMLMNLLTUQQ{z}~ca]ǧGLvIJKKKJKHOOMOPFJMJHHKLR\]elcs̜"p~ah^_ZtǀNYkW74::EUPC>GRTJPKgZPPVwࣝぇevf쀈؀¹׈ϺݻX774[Pxַ˹Ǻ݈_l>leG]U"Yepc[be_UUNBJvCM03/00fbSPWasxRF~~}~~}~~}~~~~~}~}~}}~}}~} ~~~}~ }~~~~~~"‚ʾĺɯuqx|{sifjxݑGI14oskd`i5:;sttyDj䙗Is`fswmnnpnpv=DF>=?;<86<;==8753779<689;986q~=p962229?;:<<@9;<><=6:98533464757734ux46;76485557751;5=8652-2bqt^Ycb_ej54nuvu~WߔubNygobX\jqut7-2579779346;8<<:899:?DC?Vi"޽ӭڋĮsyŮ{ϕz|wo]T\xcFS7>~vnu=BDIPmxCxitxx{zz||UbgXZXPNKJPRTSNNLLONMMILPSLJMXPLJLKMSRPPPTOPNPMOKO^MGHLKLGEB<:uzy?~~~~~}}}~~}~~~}~~~~~}~}}~}~}}}~}~~}~~~~}}~}~~"֑ԟศox~~vuqŒHF(1mpmk5558?B;s:;E݆Z2zifjqmplhpro8@DCBA><;::>:9;32467856777<:99:;@=8440-68588;;8;?>:987986568:45771557tv49<87787553954:<>8451.5fkpc\cac88:4ssvu}Vlmsyeikvu}744667664479:;:>;:95;AB@ER]w񳑟"ᵣ풕Υbqylgrb}c~ݏx{{nlrfR\1<}w8?BHORK;=JYq`L/jiyzuq~{z|yPhlheaYSNLLPPOQMMNONIHHLMOSNMQTVYTONMKHMNLOLOPZPSSPLJLMQPNNLJKHJHA;>=5yXf^\pfsMDJKKKJLNJLPMNMMNMPSUZZjzp^]toee\\g̰Ub|8:dKB>D2FBEC@HOI>@KNw=o|kաݪ탇Pn`N炐¹Ǽȱ旷ȼżۼခ댨I]725mTKųŸbiiG6INNP"\[bEJT_\7;maSSPGU`1.-/o\L@_+-<~~~~~~}}}~~}~}~~~}~}}~}~}~~~}}~}~~~~~~"ʈۼ…಑}}r{|xvxrbHQ)1j65;655;@Asg?EPUpprttysjhktu?CEEB@=:93:;8::364607755534>876;=::753358<:8;9:::;;8:9471:86:67875666nl48??;9746613679::81265mjmhjc_`gwn66pruw|=׎轔𪘉{~rwhferp>:56886425789=?>=;;87;AA@CO]r͙"̯̍櫃Ȭюʀdhdd`txȦsw}yvvh`_u9<=:@@FIOUU|BEMybWozzsz~{|{XknndZWTPHLPPRTPSRRKMMJMRMPUOOQQPNNLMMLMNQNLTPRVPLNLMKKOHOOHJJMG@>AA>}@FQRMNOLMNLMONMJJMGJTS~>;x}Rd]^ח}mn|RQNOSRIGHKLNMPTSOMMLLPRQS[dy|u|s"i]b{seie[ab^xaDCC//+)C=GbgZYejK:;+[qL^ܯՇ\9^̿އĽҴڋý߷HU947mmN䊥ǿŸ˿[^fV>;@O]"dYV8@CA>?=8;6:;766788;6886403357768:89;7/0487:99498;@<;>;77678889;875864hdf7:@::96535459;@96544;znnlikcehlkilppyx~Jĥ߭Zq|k22Yg8768?J?8247899;??B;;:9;?CACHZq밓"򱆑ҹߌ]^woludgllle]osfrrt~tqrIqB<=?@DDIJLwHT`kn_s{zzrpy~U^faVVVSRKMOQSRTMNMNQPONMIIIKMKLLNMMKOJOQPPONMKOMKLNMJMOHD?A=;|}}@JSLMMJKNQMLNOQLLMNRW~x}|}Q^_^{>^BDMMPQQphPEJKMPONSSS8MMLMRUURRcl{{udi}aaiii[j`SteHRB!8/:4;GI29PQ+FBD3MWF~ھۧۀ莢g7F_Ľɾꉏƽ똪ͺ̹񄫎T:87oadS틅񃖰IJ³`KYfT>0Xx"qUT-U~~~~~~~~~~~}}}~~~}~}~~~~}~}}}~}~~~~}~~}}~~"΂߽ܺͬΎs{~x|ww{vnPY143lpr;><B@?=><9:;:9438>;977;<666767577;8:?9840269<874779=::<>:89;5:<:988686fja56;C;=5755366:==:4245ksjfngnmdcgpg`fkpwy̯q}{o2mihp66:Tcu<37889:ABC<9:97>A?EHUlȏҫ"ª̛Ǖcvr^Xfmx󔋦ss}pr~ZMu?>;zDHJN~|K]I7itv~zxyx~~NV]ZYUUTQOONNMLQUPOOLMONOQQPTPMKOKMQMNNMMMOOMLGLNLLJKILLLMPMOMMLIG@>=r~@>GRMRKKJNNMJNQPPNOQR~~z}`~u^}ALNSelNIJLNNMTUUOLLMRXVTV^h}Ʉd"ႃ}}mȱpta][_|BLL3H553GSs`BPbQ.GBIjzԈӎгʧ˸އgk~]냔ŽǾ¹ſٴؤ[u;gfa\THQہȘȽļhO`llT8`"vYSCJLNHTiygXENirm7d\]{S&%<7<>;9868769886767979::99;74/33578865655;?8=A86986784699nnrf3384;>:=7877467;@;;6212co`a`_aoihavgbglqypeȾޘ\sfpo}88>f=<3D46?:899<==;99::=>8AGPo쉲א򄁙üŀk[oo~faX[y|{oяurs[JT`m9<=|DBFKO~y|H}CCskr{{yyvvwWaa]\YWSMOPNPSOPNJMMLMOQNONOTPNLNNNQNNMOPPONNNLMLHIKHKMHHLLMMKHKMI~{?AA8FMMTNOMNMMKOQOSQPPN}{~w^_K~~MOSrBSMRHILKIIOQQPMJLRVTRVRcyxqq"Ხmt|rfqrZ[bFBEdDS-+CZS19rkm*EEgƼ儕ܾ䉧jWp튢Ľ÷ʼýſ考ǍitAif~MR:"*JHLKIIEMNLKGMTVSTSVTrϗxe]ly{qrrdkqUNX{WSOPPFwBKNI;3>LCB/9f{}>P\N||ųdʼƹ耉鋪hBYͿƾþƀǽþ섄ඹhwm_\OLTR;|ޭƶcUFgQ3%"=<<:66=?9@:6887796::6378;:@?8:78<:99:689896!33.4109=75:5676556:7toV+2936621257{4657oppgcN/]_mkxc\fpmilnx޸”RlL@_ps:DVL82639;=5678508=?@I[^{Ȟˡ녝ɳ넋鼅|paXXlvy[\hnlfzʿvx{gaq{<9;BEKKKL~M^ԉHHqsryvyz|}~bfc\[^aUPLOTQSNLNMKMOMPMLNRNMIPSNPOMPNMKMMOMKPNKNLLIKMKKOQKLLIKOPQMCEMHKKJKLLLNSRPRTVSRNtuz{~{ףĪl-ENLA8RLNMbcbWZF1iwc~~}~~~}}~}~~~~}~~}~}}}~}~}~}~~~~~~~}}}"T瀃˛|{piVc`16888944==<8}VQ_hooknuvnejyG@@?:509:;=;9:7:8=9;=;57;=@><;:78:>=9:8:9:878543/1573639:624/2:89m,.3;8274-25;5498406rmd5644_]4492kXTYeekknxD֩jqhLAJcJm<9==0>KD24:=A98<<869;>ED?9AGKJaĸʼ葧ƶĸ뭐q\`qу\_^eab[xxeals;=<;>BBGPRMHy}Jo}Iou{}zz|}eca]\_bVRKOPPPONNLLJPMNOOORRPOOPOONMNONLMMPNLNNPOQOLLLMMPLONKFGEJMIHILLSOHJKJKJMMQVTRSXOPONNLME~n{}|Gε[mHFoYYTVzNJNISVKJHLNOJKNMKJMOT`ZRMRWdkp{F"x}ZŲ{~\uch[BFGA:AHI^`\`316tgYSj2.*L{٫Ķ郃°־fcwډοùÀƻ󁄁lkmM2gjebSNɡͽº~@I*AQF"SabYznN9"K]Oy|}NBJNN[wrdU*IAHN<~~}}}~}~~~}~}~}~}}}~}}~}~~~}~~~~"sɺŁŹ򌍗eHF_`5:zu89669@A?~{}RLyfhmqhjurqi8AGCBABC?>;26>=657679;>::<;999;;><::978:;799756-45:34;;865674267622498688238?98;9844e4246143133912QXYNofj5rz|Qڹr|nECOPh=866-79=98:<<948:;8:<>BEA<@CIUrރ кºĹж]}txŞifljd}YIVpw=>~>ABELTPIjzeFFsv|xv{}JUdc`_^a^a\OOQPJKMLLKKOOQRQRRMNNSPNONMMNMLMNMNLLNPRONNMKQNOTRMLKJIHEJKJMOLPPKKMLKMRKTXVTRRNNPPMJNMMKMFIvx}=O[ĵe^‹CL_UTWdPNKE?LDNNHJLIIHKLKHLPV_\SPTV]nwHUo"isƩs_g%@@A@DKUK\wMYTUa\B956=<87<:87995568:>A>8;;;999:989::89:<:876697;;;8?:=25657-448445966:636;:;=?::/612202101120432W]_Xbpfl7:~Y쁖Ħ[c_GGW_f:77:59636:::9644788<9>@DA@;AG\gŐ"Ѽƽn[mulopwuvz{}lwgVo}A>{}>BFGIORKfNFCswyw{{}~DPch^[_^^_[UONONOPUSMJMNPRQOOQRQPTSQOOONORPONMNOPQQOKKRNMNRPWQQJNMKKDKKMLMNSQNNMMMQPPVXTUMPINRMLMLMLKJLLKq|?AUt`lcqNMX[Y`mNLJIBIHLNLLMKKJGJJJLLUZ\UQMW[mx[O\"fy]~njgp[i33?KGA=LcEmC/1EC9Cn?I{`w˹ۙtu섟¹ĸļȾӂa<;9owpdlЛȎȻDzsTZKEZ>"0KN~jpp\Hb2X^GSc`O@DB;?MPLO/KEDG<~~}~~}}~}~~~~}~} ~~}}~}~~}~~~~~~~"«슩ßۄ`P[026756897:=Mf˫Jzelxwxtromk:CDHDGH@BA@A=;<737=:8::43.278;>=77;9667586:86:;;=:9:;=78<=:6559724575<797897889><;;77335/3,6/./313059gXdbchlq88Mqͷ귞֜rSFXR\<869865558865/:1879=:=@DAA=?CGJPNMdBrpw{x{{yK[amcgjY\XXWSOMJMTXTNMOMMJLLJMRTPSYRKLOLOPSPNOPQSPONMMLLLQPSRROMNMONKMMONRPTQPONQQOSRSSRROKMNSGNLLKLHLLQSux?APKjjfh͜k]]ghzOLHIHJJLMMMONKQGLKKLLSZ_USLSYjwscl"]lznt]NO]llo|wu*'44BB:0?AbF\&@U3;OYA|gfԺ苓ςƚϽóĿȵø·½ʶ򀎲|<=>BqYv͙Őȴ[uyY}H5"&5Q^[@@\EYIFQ;>^TM8@:5ANND>JD?7<@~ ~~~}}}~} ~~~~}~}~} +~~}}~~~~}~~~~~~"`ɪާވ|~ʲZSQ+334679<999{wP|Ş;xknwutpglr:@FFDCECBB=??<6=506:;85457::<98687789<58<:<<<689:9:=>:99=589:88778<;;;<<=?>::>B;;6634321310145454127:^accfkkntjŮK>JK^:;99:56657:;3,6097;:;;?F?@A@DWq*圍Լ⓮ʀ[xf`nrhwY]uku~~{ouQcd;D@>@BEIKNIQuvFw|zwpv{y{HTeikih_ZXRRUSJKGIQRROLPNKJKLJJOMOQRPLMOPPPSLNQPSSR2LNNLNNMONNMOIOPNMOQOQXVRTZWSUURQQTQRMPOOKKMQJHMONMLlMKxltGlhjlhtEQedvOQLKLKKKLMQVOFLFLKLIMQXeUTOT[mzy`GN[kbylsgz]PUkld\{yv?B-7<2)-F`Uey%/1=@IpIKxiS{ҵއҼ퇋b󅦵»¸ĺ̽õϢ@;>>=6=?=?>>B=<7;855562721554489463=c_cc`jlww}Y𾵥JAAM\w<7:96<658@=63447699;9:A<<;@FIILJG{Ttww|xywy||zJQci_ce\WTNR^olLILPUSQRTPMIMLMMMLONMOPNPTNNOLLMNPQO%NSNOPNNMMMLMJONNNPTSTTURZbWTTWSQRPSPONyMKOJIOONONOLLIIv|A|sjca^zvw:N\itSLMLKPIKMYWPKIJJINLNPT\RPQVbjuYbCR`Wji\i}jd|VJ[^\Xr~yv@8@#701+%\DIm)'28=OZq{z±熖G}a뇣Ǡžν¾¼ƽ¾ܫqC;??=@D~~~~~~}~}~~~}~}~}}~~}}}~~~}~"ʂ̨Ӻey}{pAZa487987:9963u}sϪˍTqxvtrtogflp:?AA@A@>98=A[m90233:9;@;95789886589758<;9:98:;87865975953665789589899<<:?=>;<>>==>?=;656455665665355476526XZaXcdqrvy㖕ĤKMHKax<<8779;57>D:3577836<@>@BB=@ES^ͱ"ѶᰟȔ⃎Ā^`xxgu`p]wwiÓg~}wpnOjm<@==@EJLLIC~~glotu{~{|{}GT^dhgbaVRUX}RJNQQQPRVONKNOPPNNOPNOOPTTNNOMLMNPQMIPOLMLMMKKMOQLNONMOSTQRRTSRSUSRQRQPPMMKNONLIJLNNRPLNMKEF{~toegfVi5MW\uQPLJJLJFM[fQJJLLOMNPUYZUTQSagshn|"XzggsXi{jZvzQTSRXcl}zLJ-+6//"(JD4@:=AMchzbSUέĿɐޅPc\H򆛤ЧĄÀƾ͹Ͽſ׿¸D@;CHEkmeP㛭ͶMgi]#$# AAdE=&787?I6(7KC9,e]dlDCt=8=@EF~~~~}}}~~}|}}}~}~~}~}~~}~~~~}~~}~"剂Պۡax|gPNYZa7;;9:=:<;txvNFg?<=?@==BSD65375647886467::778986759879;36875673874545785749:<;987;:=;7?;77:<;:62526776556517344030^YQXYh\]iswƲݧtU_VVo@;=8784596876455=>?ABEA@ENZ͡ـ䰭ˀ턠dWrejtdxxj{wsx|l|elF\oos==>@EKLRPrxL}ޢYl>t{wvz|S^`geh_c`VRTcxcPOORNONPPNLNQRWUPPQOLLONQPJLRMMMNOPNLMNKMLMMJILONQNQOONPORPIQQNMOQPPOKNLOPPNIGLONUQMLIJD}}|~Xvllfglc}BJhhvRQQJIKDFO[kRMLLLNNLOQUYYWVO[dpߧyVhxr\mRUk^rg}v XWikSVrrKZ1!+*57A8??R^UJ]NIUxO͑úʳ󄋉]__W~扚̀ʽȻ´ÿƼчbEA:;HGsldT`ۓβdheV:#"$MXTN8?+6@CK5T87CB;8;9435:@>7272:;?B.CHUaޘüɌȃ̧oaWZx_eygw}^}|]ejE[pqz?@ELMM{:;Kyʲg]uww}ty{uyWd`]\WW\XWRQNNPMLMMMNNNLMJEMSUSPOOLLMOQURKKKMNLHILKKNLLNNNOKLMNLPONLMMIMKONOOMLKONOPPNKNLOQONMHJMLNRQPNKFvfTVjmst=78:wy|ArݷLequwplkjden:?@A=;:<=A<:6753546;:78765599;96169;874;;<95379:65556469>=8345550427756825:<:987541755333668465984:<:6872UU_\]VZ\Xbuqy~M񍓈ӷ؝JJHYz88:8:964369<928;46399;>??ACHP[ό"ݚ酊ջ|suqT}mgmyqw}SNiLToqz<B4g_`zkTۊ̽`\PXde"MJ=LYJA6U16BHD@?3;CA=?>u853=@D@=99 ~~~~}}~~}~}~~}~}}}~}~~}~} ~~~}~~~}~}~"F΂ఘƒTB}YVe33kg237:89;xy޷Plonqn@idjs>A=>>><>A>=::67:878899798:7;<=7369878674:640548875-5469A@85546636526367164549356968<85886?<6:888;l_U^^W*$*-fwy;Mnܴ=?Xwq:=89;443:79958?=9688;<@?ADGMaЇׯўŹ  +ӀsQswZel~lrxmï׼{YJnPZv>=?@FNPPM|¡Ƭɖ_t||~W]UUW\^ZVTSQPOQTPOOPOMNQMLLOOPNLMMJLPPQNTQNIMMONNO)MOKMOTSMLNLLMLMLKOLJLLNMIMNGMMMLPIJLOMPUSOuRKKRQOLODBF@w|z?PQeVuzhl_=LzNNJLMIKKRLKNMPTSMGKJPT[VVZ]mu֑gvkh[s{|]Rfow|&16@0_d8C19:`gLH\PSTɋƲȱҼZh^ռƎžǽľƺú쀀v;C=91OFbn{򔛤˽ǻ_^TXe4"^T@@SYImC;5=?=@F?=>BHND9><5< ~~~}}~~~}}}~~}~~}}~}~~}~}~}}}~}}~~~~}~~"ׂӶאЂIAh]nrqxs348:477vp|±Lkuprijfgedsz@@>=C==@GA=9;:=<87325347::559=6537:53432874454567636567<>8645288:66654885501;57566755;<779423939:895:69meXX,XZ'*1rxwxKqݮҜ[=]mt8?979485897;27??<899;?EGMiwՊ"ڧۡހܴĜ_huaaWgYdyTjԐruͦpOJeaCFIPOQK}˱r[s}~yyRTRQTUTTRTSQQSWSOOLJKFJOPMJLMNJJIKLLMNNMPPNMMMNNMMNPMNNRTPNLLIMLLJKMKILMPRJIRJLLNOOJGMNINVRMLRMNLMPOSOQGDFDzwLWwz_Lwlffko`O}MQKILJLFNNIOMOQRNGKMPS[[Y``isڒ"ȓz]^RQjr}^i{TT; *7*9b:@A!1&-;F#JUTнϽ񍣭վsPeVW¸ǿ»¼ɲ˼􋈞mCR;3N)4k\tꅗý½ygiVO\Z"d}V@OcmO65@#!=?;>7G\F8AIMZHB5=~ ~}~~~~~~~}~~~}~}~~}~}~}}~~~} ~~~~}~~~~~"ʩݤ˿~Ceivql4/0475689vrK`quupjfigkos:97:{r6>C@<7:<>?97555354:74447588766521,58754556676445679772327655546777665064756574>=;;9/2357665523717qg`X.[]V^eqtzz}ܸ썀ƊUN\c7;7666878:8;4:;>;9:<<=?:;CJLfsٙ"Ԩ̰峋ӡk]Tr|_f{{qjç{Mim}<:BHKJMNKf^mzvt||HKJLKOMMOOPSYVOMLMMLPPRMKLKJGLMJGMPMNJMLMLKLNPMKMNOOOPRPOKLKMKIHIJJJIIOQMGLHLJLMNKOMMMUQQONPNLIIJNQLOF{wzsmfeuPjWV[|\mORLIKKKILNJNMOKOMHMPPQWXZ_dj{ޞ"ばchVtUdPW{_ZTfr07;$;@NRE=6.IC9SlS291=E/3458A+<0=NMTQB;38=~~}}~~~~}~}~~}~}}~}~}~}~~~~"Ƌู匿կ҃pB}deup887568757:xshz카{Lcortqmmhgc`oj0ffpj33;;;584:876555668:5263575864652/35::73264783/-56575741545751348;8757445665775:;>:8144.79895335336l^W,^`]gyyxœ}OT)67:66678779>;8779;?B?=>>>?GN\qל"ۼжј݂숷s]ycx럟σuL~ek}?ABCILLKLMpxң{l`ry|~|yww{vs?}}v?ABDJIJENLIJJKMPSTPKILJJJKOLKLONOQMMMLKIOQQMIIJOOONJONNQMNPJGJKKKJMNNHJKLKHLMKNMNNSPPNIONKMLLLMLLLFv|KqR`gq_^siHOQPHIHKLKLLNNLJMNLRVTRUVW\bk~מ{"kfR|Wq]YStuI820SFoo_lpJCL4JIHQSPbidܾ܇ɲŸөǞmfrρĿ¾üŽ¼ٵۂžμi{B=26#hK'7tqo⅘sw^nI"Yds[YSRd\QU>:?/2}yBG+=.q=FJ>5567~~~~~~~}~ }}}~~}}~~}~}~}}}~}~~~}}}~~~~ ~~}}~~~~}}~"mݼΧɪʃo{?wZ^ron6659:8879phxSqϩrQnv{yrojg1,hmjjprtrl67=;6vo54685688999;345787621.4556533<6747237566;7710-3443/468;<7668955567445777654267845424053b]_Q_dfkrwfsx䔎ɹןHO.;=855:376589<5157?<@@<1@=>AEKYuӠÅŘ˽Ԫبjw}wh`TxȄOhm|{?ADJOPPLIp}Tfjfcy}~{uls?;|~tu{usv<;DD?z??CJKLMOSQLMHJLMMOPQQOQLMLKLMWRQNOMNOOPKLNPMMHLMKIILJIJKLKKORLIJMOKKKLMOMIKLNLMLNOLKEML{}{{uasto]\dW{f|IQTOHHMHKKKMLQLJNLOP4WZSUTX[aj}ʠttaUY`sW}asH:F]H[VMc^G\0A*TOOTc^Go̶ا͛aVkĹɹлǾſĹûƶ˴ƼߺRnJ:.$$6"ORs¼Ͽķoq_JeD"o;Zlf^oh`GIEA:6@OKLI[D2flf@F<6/27~~~~~}~ }}}~~}}~~}~~}~}}~}~~~}}}~~~~~}}}~"ⶦЀ继ͅC@gdlsp9:8<=:88:uup~kMixutosoi_1isuqrssxnoqsmkoni54355467977876657682227<5;<=>@?CFIcmȲ"銛ʪ®ӡ͉jf͸bj}\vhۋˆVN|w}@BFNPQOKGzqxxsXZr~}~zxloAxqyytzy|x}sqtt;BIONLNPRSMKILNNPQNNNOQRLLMOUQONONNONSNLNNJLFJGIKJJGFBFKPONPLLIMOKKKLMIGLPJNNOMMOQMJN{u}|ywep|T]VFdn~׀wPSQLLNKGIKLKNJKGKOP4TVTTYX[eiws|g~ýinbidV}~Nq:o~uRI;;EGPD;A!)J[WNTT8KȲƭںtOWyĽôǽƴ·˽ƸϨ@ADKnC-<@V퇞õqo_]B}"5^Wiwe_nuf\4PKEHIPFJ>KD@[fd7=9C=<673667478745:>EJQMNMKLKMRMKIHPNPNONPVOJOOPNNRTuxxy?BVOcjlv^axnQTLUROGLLNLNJLJLNQSTUW_`_ojr~}vtxf]S^UX]rgbLUo9PVT8l>GF@?<6=L,?UXQPEXLËͳٻʿUYx܀ij¹̿ʀ»·̾ʿ퇌<"UILV>HQ-SR:Lºotd[RN"TKYlyhZ_q\K/(+_T>53)1JN-.=f-RFluplgijjjljcbkl530157760669:8555344551)/12445518436692296;55223045255561268;2385533545657965577MI782331210-VZX[ccegiyt}=CH̢듑ĕpam===CC=5756.;88:4668?=@CDPWo}"ѦѡыΟꈣ_ffhg{|^tmpezdVK=89=?AFLMMJHC|z{zd]wz{|}GXNz|x}}~yxyx{{<;@CHKJKIMLOPNLLJFJNNNLHMOOPMKLLOKKMKNIPPNIKMKKKILJC?;=BFOMMNMLMMNIHKKNSPJKSUaWHOOPNLQROIy~t|9AEYKocc]npQROSVOIOPPHNKKMMQPOSTW`]hnpxfڠ}vkec_igf}L}Ƹ^oLXp)>FF>DJ]D550146756:=><;97s}T{ѧMjpsqjnoltEtkmjhiecbh31hkk464279:54483257:;5326655013356315867542375765975435457863337854412265777;67;989C3e<350.62,02`b^]aefklyu?COc҆ꃈ^`vA>BAB<97283985958<0<:=EFS`dש"ٛ}i{nipzmXvj}xV\RA:8:?CILLKJHC>I`qXtw|{}Fxnwz|}{}?=@?@IMPMLOPOJKKKMJNLJDQZ3cQFOPLTTPPLw}4ACOllhNh^^f[gk䐈TQQQTONNJOJJGIPPQSHRRT_\n`{觥ד`[^_hcowsdlvsV}B8rJ>9hCBgZúǿ臕oe_hr"dU^gdPG)! *+.'8^Vdr\MQWa@E<~~ ~~~}~~~~}~~~~}~}~~}}}~~}~~}~}}}~~~~}}}~~~"ŧۋ̅ȓґjptIQG84588869>=;;<9sITאZfqu}noki8|tmlkihhce56m9p8855m78=;856425=<854597993145669456975233567757:5505756o645336:4112/55986:467:93Ip_Cr76.7.018eb[b`^oqx{vH>nےarB@BD@;:86;998889=C6699?AYR"جיʀvƏlamifuum]fXF;9@>DJLLJLLNORNLLNMKHLNKJMNONPKIHMNONONNMMMJIMNSOOKGB?=A@?GPPLNSPNHNMHMIIHLHJ]niXMTO\USNOx~hABglWmXgoseSQRSRONLHLLKJKPPU\NPUTWXnd"ݭ{]bbTZ^hfjt~hλ\zXEDzCG=K75-@KI>BCcW;vig`̽ӓᓥfi򀍍ýĶ鋏]976"a;epm@FH: ~~~~~~~}~~~~}}}~~}~}~~}~}~}~}~~}~}~~~~}}}~}~~~~~~~}}~~"߼½򂞯woTNI<5889879:;==>?BX҈Fgkqwuklinoxvjfihfdgirytpnospsq9;=624524:<7554798:626865797:776344458766653/2446pkkm21672/32056;=;819994msVGrvm847bb:9742;ADI"ʡꅐӷ΀so_Qeeub[m~ri]UE88;CHKLMMNHEJPU|pùPypv|}y}t}~~~~{psw}|~yCIMHFIKKMPRKJOOMLKMMKORNNOQMMMOMNMMLLKKMNMLHFF{y@CINNMONNPLRQMOIKKLGuRRXV|}|~{GOqce{`adbŪ۪MRSTRLIKJLKLLNUVTURPRQMRdtݲ^lyu^\apoch}fnjmmlǩPR[E8H7#0LG'?HPK>8EO-/XenƓ̼ܝĪܞzrğ򔟩¶À񐝫᷻테܅zL:O>YY`\57B;d]悊ľŭñqgddh"aR2{w]hRY8"!-8, "8nFGILA9y}WE02<~~}}~~}}~~~}~}~ }}~~}}}~~~}}~}~~~~}}}~}}}~~}~~"ʽʻ󛬯ws^}MB;898:;979:=?=F[bUţWEomrspojnsv|kkellfemu~}~sosynp887853534577965556887/5563597669755241522565643844lnom1354.267454:;;9358=gub2149q:=8nmspfdac`cgjxxzM蹹䧅>@DB<66=<=:;=?FD<3986136EMMJKMOKCT~xV{tsXRou{~~v|{~z|CEGHFFJHKKLMMKMQPMJLNHNPOKNQOOMNIILMOLMJJLLJMMLIEC}~ADKOMONMOSNPNKMJJMRLNLLRWVuwxT}ipZhqOOWXQKFKLLKMMN[WNKPLLKKLV^W"e`~dwwwxka_|rapdhijpnkkpgWTD%/A7/LX6FGOQK?<==GPknŠýؖ~ޥ󄒛¼Ļ܏ŬɂùyNM@S16EQQUKjh`iv~񁈓ƶϸeodb`"-0;8mD*J9(!%BI-!9uDACCB>>AD@PJ5@~}}}~~~~~}~}~}}}~}~~~}~~~}}~}}}~~}~~~~"ä񬍣Mbu~ocK<50469=8889=@AFh~|xϝi7jqstquq:uu|FO}moofbfikz?H91lj374::7303645564562556895535658567848431364534697566jkkhm317523965329=974269dmh\/2696987876][[T]gisyGWۉϟ~{>>CA=:6=7<:<==C>=33542445;ob\"߇î惈搿倊v[jjuk[pxwnVv{mpyloeJB:>BGMJJKLNHDP̊UC{uyANQ{{IPD;FGGNMGDCGGGIIKLMOPSNLLNLONNNLNNRPMHKLMMOQLLKKKQOMJE~zxCEMOLKNMOOKMMHHIINQJJKOQTTTVUQ|v{DKwɨfg\sPfMMWWQMFMLLINOIQLKILJGHJLOOk^k멳"dfeXXa`WqeecghjpecirnqLR@1FCQBVi@:H]fUPMHP_ъ^Rϟ|Œʺ킒Ļγ큏´ݺ遇ʈMMB=7GHAYgjdr􊉔þɴjiff"24G/tI&N'$!?gD"8^}BB?=BC=:BEvEFD~~}~~~~}~}~}}}~~}~}}~}~}~}~~}}~}~ +~}}~~~~"ʜݺ͘u̞v}lcM=5/4<>?::9;=>@KѠm?9<{sqtmlo77HmXyx{n4357?9;5.1428:=853/36647734544346660315432666229737377323641454h345358856;55738=97443625k^357g6766755cUZY^ejlx{I[怉񢮨H<;A><;8:=;<=>>A?=43146034cnceY"Ūſпؐmpbd\wZzmͅtuivmLE=DMMMKMLMIA>T՗UAED}?BVx\HGHGMHLIDFMMPPTNHFBA@BDKNMONLKJNMOLNLHLSPNKKQNINMLLKJJPRLJF@x;=BELPPLLSNMOJMMKKJLMOLLLLLRRSTWWUzztJG\f]c]QKNTQNKFJOLLOQLJLHGLJH1JEJKvabYqslf|{YwYcejlna[^ldgcF]aEBJQG7^LBNdiJYrSޗRܕ޵yܾ󀊏ź¿űꂂ셐ö瀇pCAU8J0I[rgUźߙLTeie"i;L)gHB??H&GNR/H8PDA:eM5.~~~~~}~~~~~}}~~~~~}~~}~}~}~}}~~~}~}~~}~}~~}}~ +~~~"ᯋ֦ʑxtqe\?77=@B=<::<><|A<93578?:352323::9642.hmk237612669445633-1224775310557897132442551f359247899984448955363422jd677jq=57<:7:^Uc5eiowy|}M`파̰ݵһ;8C=:877;?<==>?BB=1288020OXXSXU|"񼫾гٰǽζz]Xlcl\򘔁zlnzRIBFPQLLMLKIC~s񧇨bDHEz~~~}}AEQYPQVSMMMLSMILJKKMTSRLIF?uvy=BKPMLMMOMKNRONIKLKMPPNNMIKILNNIHKLTTLKHB{:BJGKNMLOTPJJLONMNMNMNKFKLMWTU_bWRNuNKzecw`po|IMVOLIGIOOMLMLMTNKJLLJDHFti`ZZP"̏f}tlYb|_X:XaE28T=>GLDzFSfJ[:LOڀʳч¸߉xiƷź½ɱ˿ꈆTcor7.>!1M(-SpvYLf鄎ļܢ}Zgg"pV.|HN>ARDEE061yF4qs6tUjaPIG?~}~~~~~}~~~~~}~~~}~}}~}}}~}~}}~~~}}~}}}~}~~~~}~}~~}}~~"ǣ˗ܟިٴuڮsG;=?ABA<<;<;:ઝ֑J;<:qglllkq;??p5::;877:9<:30156216743`cknthhk544644;4542.02035:6512356769624447654346669668<7423637747475964j45943r;5:=>:zj\674i9vyw=OyǴtvD;:4489?:;=CHNLE:598.54NIQORF^g"؜ڌƻr\c``\\Zyd;}jٳXMIJRSOKMLMLHzwu柊sMIF?~x~}|zEKJIOSWVSQQMNMJJLNMJKPQLIwt~{|KKKMLNTNQPOMKKKLLPNNLLJJJKKMKHLNSVOKJHECHMPOMLNMMKJMLNNMQMONTNHHHLLNXXWZaYQPJE{CETŕdibxRh衉`ONKKLNROMKO\bTONNKHDHEp]YUYM_"̠k\\{~Xx\kryv:S[P7*d5*),IGNHRBQ>0aǬȟöԿukl󍙙ǀǺcsv|553[393 P~wXyſǷ殪bZ"{m>8F>TEIYG?BFB7R998zy笜P;86looonox>A:776:76046778204563/55ia]jokkqie985633635422572467431246886441565433345574101686534557:87666956664641677=IJbԮʂrnC@976:6;9:>EQZYQE>?7b;qNKSWOIQu"ٕЋ‒Ӻ苕񖨶ξ|kg|obnڈ~ʭtRHHLMONOQQOOL~~ȉQH@:t{~EMJLPPSUWQQPKJLJLONOLHMMvu~|GIIJHMSOONMNMLJLKMMLIJGFHJJKLILMNNKJJKMKNLMMLJOMMLLLKJMMNMLNUOMMMLNLLQVc^YaY[KUSKGHDHMDjЛabhjmr×aCgmƇWRSOOPMSQMMQ^`WVRPKFIiYTXXONu"bgz]edZ}{`\]^gpQXWGDDLW@,3jiqOJJZB>{NbƗἙʸǺ쁨rvܷ򎧞żȀ䀊|?A88:;lA MHjn搫øѵNP"}UYKNC:TVLV'#()]JAI;J:h30GHF@@M~~~}~~}~~~}~}~}~}}~~}}}~}~}~~~~~"Խն؁ՀxsռfJICORPMGD?7:xU>7:rqpljgm:;997;;65866527537<753id63dfmsupif9956633213665657:74323996753239553334875972/.2455549<7:6:7577nlkl467:365>??:@<5<:536ovv:ÆϬ懱}mBDB?<6888<=AIR]]UFHx|_][\LLNcx"ձߎ埋faaljebbbk[yrx[LMHJKOPQQQPNLF?@=:;;HBLR ~~~~~}~~}~~}~}}~~}~}~ }}}~}~~~~}}~}~~~~~~~"˅Ժޑև]wݴwlv|h]^\H99@{ߣT>:8onmf`ijp<>;9:88585777;;4<98775317jqjlnjj5683433013767:75;=705437::25556644597954495221266896996ln;6447cqi437>JJG>9?:8>=@ILgrdsgYVHEGapq"ʿۑڐŻ[efvqfa_\[]xekbx`n`YVSMSUVROOMuJEA9wyx|LUUPQTVRQNOLJOPGLLOQSROKK}}z|>DHIKKNMLMPLKLJFMPOMPfPLKHFDILNOKJKJIGHIMNPNPTRNPNNNMOMHUMIJNNNOSQUUZbcYU^^{_a^kphdfTZNCut~o}N^idPKOMN8STY_VY_ljPPPb^pŸ[~g|tvfx܁wlk|y.4 5EOQ8#(1E8CC=ETOYfw؜fͺݒZohƺɺĹĪ傋yn=>7?C=;c#;IY`~q-C\P=;568:?:;8:>@89757332574hcejkkmld034604455766699432337=84654584436569339513756889:A6fh`4132o`kh447@vpow;8;>CBD@?gdlF搏ƴְBJK@>=??@>?GJcͫudZJNFDdeu"I[Ɏֳىݞ̛fOwTWo}eVXRƪfba|e|v[mhn^eldVMQP~ǀiJC?}~z{}vRURNRTQOOPMMQRHIJIPOONLJG~{yHLMQMNNMIFEGILONMMPLKLIGJKMNNKKJIHKMJJNMOSVQPONPSYMNLJHQOLQUUY_j[\^[Oardf_lq``ObN_nɦusˆLY_RLHINOSOPS`mzxݟ|eVKV`cv"bmYuZsqv^XqqXPZ>!-F+3DONA&&F6gwG4oeS񚈐VfhTac󗥥ĸɾľ؂ȹ~hq?@3-#8=TOJS^+mG:@?GAA:>Dml^>PֹI?<=@AFAERZvhbJQBT_qal"t骝ɀꡠ鿻֢p}[Sa~`U{kiYqNcvoNGwkoqeSKNTլuYKCAwz{}NVRNLMMOMMOQQNDFHJOOKKJGIJIHB:wy~LMONMNONKHFIOMPQLKKLJKMHIJKJLJKJIHKNKKIQQQSOOOMRV\RIKJHONHNT[WXVeZ\XXc}W]^njhfkzra|VUOnʻ~ƮUKHGJPPRRVZU{sohLU\fa"[tjwlThlcPxUzUjzx{x]DdH9!;[eYJ1!;a[vVCHk޹TZgfCEؐZdgӽ֓¹ɾay@AAIS>9? RS9h}[iƿȾ̿Vxο"^Z]$1(;AD>!87&5KZMAd4S[Y2=FB:.GH~ ~}}}~~~} ~~~~~~~~~}}~~~}~}~}}}~}~}}~}~}~~~ ~}}}~~~}~~~~ +~~~}~}}}~"ơȋ詍̈Դ䄍t㵓uV?65pmkjfnouyy=<:869=;65:9=<3754543400143777mjh.304535431415768752-03589832/3e/-.+.3683856:85641::;og2,//23frnh357@6nr;5y@F~q>`^pK󌒐ȶ۳C>>@CGIDVЁ񂇃pjYaDfo`i^"󢕪бݕyl`\oosybaᘯ{q{qxhn{ty}tZMQK{{ke_LjqQF@>~v{}NVUOKLLKJJLJLJDIJJMLLMKJKOIGD@~}DMLOPMMMLJIEJMMRSNKIIFHLJJGFDJIHHGJLOQJNMNNIINMOWTQOJKJIGMLOXP\SUYWinpklc\]WtRbkr㭍˨MGILNPPRVkwWZRVV{wuYefuwc"tTrfiwoii|ZlkfRMdzk83+"?YTS0A=YoneQTg{Œ{wjif\DCH_^e˸ݺ¼Ƹźۃ鈅—āXuABA??;59c)[!yamg՚ųµjjǷVRJ{𹬰"ko]8C"AB81ELC):9/0213Zpobtj3<76e65:FM_Rpmsr_l`"愠Ƭߎٗ͋ـn{|zaakouqtpsj{osyrhWLRSIHOHrЏ[LEBDwx;=~ELSSNMLKIKIHGEIKJJLNLMMKLOSJIH@CGJLNNJMOMKILIHNPRKJHDGNP@CGIHINNMILIMKHJNPOUPNPPRKLMKKLWTQTRSUVVVVеe}jvvs]gzz}NHGHQQTni\_WRUUXcnruvux•"mv\[SUcxsZye}Tl~wYe\{|RKFRD2ETbaAyp^[[ယźǻĴ⁌ӻ򆜙􉄁ɏIKcDJE?;YbfkjN@SHCYhuTcӷdb[WTRKoXʵ"w=:F=?F=47@=,BX3:1cV{`T[DH6>_g~~}~~~ ~~~~}~~~}~~}}}~}}}~}}~~}}}~}~}~}~~~}~~~~}~~~} +~}}}~~~~~~}}~}~~~}}}"о򰄓т䂎lyꗔP@EB?=upl33iqus89995688588976::7757:854457576ha1-+25542231-/22345>534848>|@DAi]nzƴˈMGALk߆đy|ztxEdo"wnӺ͸ʅ〄}z]VjdwxbpsҮ^\^yxok\KTYXUQSSXNRHRKAA}}=;{zELNMJLLJILKIEGPPKNLLNMMNMLKIHC}{CBEMLMONMOLHIKGGKNSJGEFEKOKIKKPLMKLOPOQNMOORNKIQPMORPRYUV_^~][`usdZJHTTpxSV^XYTRSPP|Vqt~St灓atg[]{plX]XW[M]OH\KJ-/GMK^mL$=K~MI|Ylom|wr_LD?@<3;JMFaVVιĽ݆Ĵℝʺ߆رfO*YzC?>TPVa|q;J_`Sp^X`yg{^`f]ZSRSNGwnbŻɍĕ"yJQ<1l~=?;9?FB:;JWT7A?wGAbV}Źɬ얦xVF_ឍ}rspR{"uIۦ߀ֆܥǁ_gYYryz{kx_ʷlsrt_Ycq|JvombUGUZYXZ_lnZQOeV>=zuvEMNMLIMMJLKLNJMMLOLLMOQMKJJJIGC}BBFNNMNPMNLGJKIEJHHHKGEBGHDDBBHHGGHPNNLLLONPMJMOPQTRLM~MPJNQOTTVVR?EXwlq{ք՝~TK`lUVZUUTTPJJIzu"ƃXa|uh[n}fst|SWYO]JAO?<:=^[VZT5?E`}jQRX_PPdwPvqxmTDA:988GSNFX\VX뇕ᄥϺƻȲ`BCZ7C]AIZseMUhbZc`chWY[YULIJEBBռ"mMjI5_kBHED?>:<;DG8oVIGIMie5262~~~~~~~~}~}~}~}~}~~~}}~~}~~~~~~} ~}}}~~~~}}~ ~~~~}}~"ؿÍི~̎PIYE9uurhppsywt;:89:776:743345454889;=2,48;98mg126955565472444/3051/,2345543/1*/2+/022476897863687mt889627oo2-e_4lp6415jhjl>yxuadyڨлҴv쁀ݹut"S݃òߧ釙eb|bif]ZnojlwzxLLIvmdWKKTYZY^z_VZz[>uy|z{y{~DINOPNLILKKLMLJHJKLMNPQLHLK=?GPOONNLKLJLLJCGEIGGEC@EFCEDCLHJMFGFIJJNOPNHMPNOLDSRSPKLB>NIHFKMÔ[9;>?bky}wv۞Uu9TUVTSTTQLIH|[ϲx`UWlwlar}mՒhU]Z^_UPLC+(JvAJJ2C4n}=B\]UZk^qadxRSL}npdSGCA>?@FTa;trn:IRVdicwq}KOTNpibRE=BIMJJLcF?gelkeħ퀓žº²ũſôzÃ[) ERE3?SDFQPQi[lav}TPPJBCII>k·"ځJ^`+7CFH4?@=;@>HO.cnF+,88l957<9872//34446855768724==;5fhjg1053651332ej32004423/21/4760/114369545:cO=8:7898mqc4rpgeih202hh7kon53dfc9961x{}u{ԎѼ򇅀ʈt"N@ꞻㅋߋVWʱWV}pleuLJLLFmaUOIIT^YaVCGj󠗾tsqQ<=~{~~vv}{{HQQNORIGKLJJLKKNOPONQTQNFFKQNNMJJGFHFCGJFEIEGFEFJKGIKHKJKPMKLRiPOSMLMPNC@@F?>s<:?Azv[rR9<9FR_oszrtFtZWUTWUNPTRRJzVlzzyvmt`l|}uW\zsT'NpqP;,6JNDMjWMKFALSIHskX@RwddcϺľίì¸ήx]}9. *;?BSD6G\bbnqqĎ~sPSTRROB?BFHEsMϷ"qxrGN*9TfFC=A>=;?EM.1N{WI6OId^9~}~~~}~}~}}~}}}~}~~~}}~}}}~~}}}~}~~~~}~~~~~~~~}}~"Ϻͷŷы­ʋ셏Ѧz˄oT<48rvwxvqponp;<9556777569336356654:;646;;=61jnh34522/2421000-01353531/0154174454976369HG7478:89sn/3nmgea]014243itjege48:99~ꂍ򸡗ƶ郤Tϝ"ecń񒛿ظ񻖟Ɍh}_a}~tyepxUzUULKIl]WSLKIMMVguvqftvdL??~{z~}GNOMMMKJKLOSLKMJKLNKKPOLMOQOME>EKOONILMJJIGGEEEGGGKIHFDBGHFMIGJJNNKFJM[VMMONPOPJNBACBDD~>?@BEswikbUE>jgtzfۚzjYVUTZYOPTXTQOQ`"枛^~gwjov_]q{gnep[qlV^H{]D:8FN@8AKL_oO?:nkcshG`rTWNPPlf[K?BEIECSwrPzqZa\c󅗢⇚ȵ̿퀋SE_FH{S F6)HDNL/QʆIYefh{EKROPMB>AEGMWIpP"ocSX.8%@IWlPJ=8=20PNWI3~}}}~~~}~}}}~}}~~}~}~}~~~~~~~~~~~~~}~~~~}}~"¯˭ԕ댑Ѥ𯅐Dž~X<6;txvsqpmqor;<662445677865732535879967=;7767mh1534243641.10-24533431-2124384;45656258;=4/38:886237>=pl^\a]31210cgpj5o8:9;@R߻u^`䆕ܸѴ"ӂxߧ֌𶦱Ȇ՞ɋ}{gVfvɽDžhgg~c\NLLnd`XSKJKHJMOQڪbLFE~|zyz|IQOOIIJKKLKOLKMJJMKKMLMNNLMLKF@@}EKLPNMMNKJIIGEGIIGBFEFFCKGJGKMLKFKLIMLIHNPMNLHINTQFBDEFCFFEGIWhj`Y|XveuzkZp{}uӍkhQZWRT\XURYYURRL|jZȬݕ _zcg_abVe\TX[zYZajiYZTqNP<:BUL6:EK_sLBp{^UbJfyjeOOSpjbUI?BFEA944vy`µ舗¸ƼmUgFKIeEZFDAKQ9EnЍNLRє7DGMHJEC=DEGKXK\PS"YLt??+**@O[^c8+;9+0:D"$B.+@CBEgK0~}~~~}~}}~}~}~}~}}~}}~ ~~~~ ~~~~~~~}~~}~~~~}}~"}£õ뗮֭ĵĩyY928lsztptrtmu>=;7774237884565646557:779;=57637k44940325841103212413057;;99542536678842477646348:;miaiqf10311dexb558;A>}}t꧰򇛁{ඣ ᡟ͡etXmRXw|yrmnqcpnzIHKJxph[SNKKGGHH뼏ȖhMDDzx~z{{}xLTUPKKJIJKJKILMKLLMLLKMMKLMPJIC<=yCHONKMKKJJJHCFGFEFIGHGDHGHHIKKIIKKLJKMMJKLJGJLLLKIKOPNFEFFHCEFHLK⟥mldpHdRqpcvvjU_TOV[[TWYVURNUr\^w|bt"wvLYrgkDNXPJNXcfW\kVR=[I27@ME8@>AFEGIQgyPMUqvaf\"BJBX48U=:L\QOV43>7027?NFl=PM;?C3-~~~}~}~}~}~}~~~}~ ~~~}~ +}~~~~~~}~"ϿηҼ蚲Ը}멸h9598sytjrstpu<<;7766335573367556868::<9<67567885583114783.2131221443553268<9888556434577445640653025?;mrpzl52324hls\34:BBjwzxbĿߍؼūqaaPeoDCSA<0%BPF?M>HjIϓ隅C6EEE>=?BFKJGCGQ~g[OWQcs"@HG7AVFCFNYQ:CJfE;pj_4?wm@H>8F@3~~~~~~}~}~}~}~~~}~~~~~~}~~~~~~}}~~}}}~~}}}~"bɘ޼਍뚳˶䱦}mѡZ94767vrltp9qs;=;87454544634576564489954688uq688.,158;543./.565659;955764445<7342///6742210154/TXk:?;nyrnn5305pnwk47y>CILKIGIHJKKLHHEHKLMKHIGIGGIKMMJC}{BHNLLMLMMKKIFGGHKMMJJKIGIJJJKJJLIFHIJKJKKKIGGGILKG|PTMHEAEFKNOD||ڪ|{eimzx|Ѹz—~[xOZ]XWW\^VWVUSOOLxaz}"}dstkelfT~ds\]ocPIIOWO:F8:FLG?>DQ`|TPUi~RYX]mqt~o[PNF@@@:>mc\wvfêƿǻ삊ʄԶޖ`P,2O;?i?OX=9IJ=:;EEA<<=CHJQJIXZqEHxQ=f_Z^z[h3d9::6loowok6@worffiV̨ȝʊۢߍį"ƐĨξ솟ūViVyl^TWoˈ|jeyxtnjkq}~p^UXSNKJKKM˂ÊgQCD=u{~{=~>>GKKJJKLJJHFHFFEJLLJKKLLIGELNNMF~FLNOLJLJCJGFIKKJFGIHJNPOJIIKJILJLOLKHIGDFHIIJ|uPRMSGMLKIIOʐqZeNXxYatoyжruqdOWZUTX]`YWW[POHHhme"`pnkYzwnySVS_ollHFKN+\T8!?CND7r?BOeSpQYcsW^YYejfgtoYJMG?=A??Fed]dtqkܭ´Ż¿Ͽ݂Իޒ^W"/iJLaPN8.2'-D?KBLwxkYc__z??EB=>:>FKMXI8.6v}xhX]wb"W[\i`05.(F.HUoSMRRE6]\Zq>e73GIH9+~}~~~~~~}~}~}}}~}}~}~}~~~}~~}~}~~}~~~~}~}~~~~ ~~~~~~~~}~}~"ϱ՞邜ԏܜχƾ䑰֭}[@9:nrqspop77::9586566557:<9968<;<=93139:<>968773.//243b`411473/122565.10/.ZbYRMF165i8207=?Asrrrg46:hnhgbcG׭燊ڢ{{NJоڷ"溠ʄé뚧ﴃ“θ˓ÂRSUVt\[Zԑs[t{zrifjr{~|um^TXSQKIKJI獟鶚kICCxyy}{z|>AFLNJJHKIFEHIIHFJLMJGOTNIHGMPROE>AAFLOQMMMJKHGGIIIIHFGHJROH@GLMJKHINMIEHGHGHJtrJOILDAJNPSCCDx|||kIH^zemwl`|yeT[YUS[bcZVWRLHEGq\`"^khiZib|gpV[^h|Z~xCJ%%(+SL;0,E75FQiaMrMJ`S\_XXbb^am{~~|p]OLE@=CC??f`_`lrr䤉􀆙ȱ鄋ⅈ󂃁Ԁӹ܉\FyiTUgf;)pNZGK]JHayo^슒EBBA=@=?DIMOE6/2lvtZTJQݛ"axogW.3+'!38|aY[LC>95]`eZ6:@9&WJ:4 ~~~}}~~~~~~~~}~}}~}~}~}~~~}~}~~~}~~~~~}}~~}}~ ~~~~~~~}~~"窕ጰõƼШrYL?9muwvrsrsq:<89:6677766;>=;;=BA=2201/5422kcY_XWTWNIX_cem:86uCG=d=lo9465tqhbbhϟҭ͈~hinsٿƪ"Ⱥ􍖣ҒŹĈ΀SRTTVXWVzbxnYh捂Ljds~uvtojrxuoidZVTQKHKKMȀwWGD}zvwy||}GPPPLGJKIHJJKMLMNNNOQVPONQRTWQB;@EHG?873dmhNOxzdMWsI?"+.'DRz@/I@C8Ret~S(=@C%B>GD~~~~~~}~}}~}}~} ~~~}~~~}~}}~}~~~~~~~}}~~~}}}~~~}}}~ ~~~~~}~~"İ鹠̬߉ٻʸ⎲˪weTz9xyywssqsqs==>;6555867:@><=?B@>@>=HQml"uì`ntfbXV~_^y^+#"&()(+..[QC7cVH>B_U|jwMbup_Vctx|~rg]_e^TJEHLGEAK^hagqvzꃋź݂ǷtfX*.fL.A<(DVJ@A:853888:<>>>?AA@C>>=@@@DD711-1344355011`iif65576110/.02653588740632/effkkbZdXMSSO[\`eyn8h>B:35579434kmfgvDZǾŷɸūpg]҆ĤϨ"ɏ\XUUUVWX\]Xhy|}Ӄyѳvhai`^agl`YVNQUoȹwHG|v}~yuwLVXVOLIFJLNOQTTUVYXWYVWTSSV[SB:?@CGLMLNNJMJDCCGKIGJEBEHJGCDEFKLHKGFB|uwyz}KKRNFJKHG@@AbuΙn~lntr]Ѷk~eⓣavglQR[Y^\ba\XQC?HQ"pΨYdjqsl^h^aY\~knMi/##'''(+--/cZJ4MCBBCY\WpoYszm[[honmeUTV_i[QLFHLOUkejqprz씀ǺѺ쁒ؽox;%6 #$.|zwpLN4E6O|}zQڂUI>:DEE>AB?:621fpPQbbP"Ee_M'221100M@4NQg/<>wE<2<>+I+>I~~~~~~~~}~}~}~}~}~~~~~}~}~~}~~~~}~~"繕¥巓ު϶ɻ섂ജpZo><}=8qtttuq;@?=:635788:=@?>?B@?@=:?DC==?8.,,01142352/.dbg213454334/.048974677423322e`^mpjXfhOQSU^X]`jmj556:7lqm77:5brh]qzDąâƽיȟ׊"ʼߺȄ٤㈦`WUWWVWVWWVUdncęfaǻzgWZX\bjugS]o{yg`|KK??z}~{JUWVOHFIJJLMQVVWXWUUVWVXVXWVRG;<@CDHNNMMKKIDBCDFIIJNIHIKMMKFDEHKKKHGE{z}~}~xJHINIGGICyg~}K[}dI{wkYiᲥYqtW]VX[]d^XRFBh"ε|lnviY^uaxbn`ZmcgVq4)#(*)++)'+/XH5!;CIELfo^Vkzwnfbd^ZUPSYep`LwhrQcl\`q75jei{璭Ü􉒛ظ񈌎޿bw~V73, ?=7DB2N/:.;~~~~~}}~~}~}}}~}~}}}~}~~~~~~}~~}~~~~~}~~"ʴǫʞ讗򍠩ރѶqa|D;z{;7nmuwurw?<:66146876?A<;.&(,-,+(&'(M$J@&EJJD@AMtte^Ibmzp]XYRIMV]ocdSdSetlds:ood\אꏎ¼¿߽鄏߶؅MpttlbV%"-BJRZMc|iIseMNJtõRCBA@<<@BBfL]n}bQ"JJPYTdcL264%"G?7I]UIKE@88r@UO7CL4~}~~ +~~~~}}~~}~}}}~}}}~}~}}~~}~}~~~~}}~~}~~~~~~}~~}}~}}~"ޜ҉ڭ̴zws75lpuxx{|=;556//6698=@?<::AB>96=B><>B;51565256:7640ib_01egd4789>|z|{zLSPNNIHJKLHMQQRPNTVRSPSVTSVhWG>@BBBKMOMMLGD@|IMQUVOLMIFJFHHFF?ww|{DBLPJHJFIE;?̞`a[OeibtꑍufwV[WWUN{"z~xU[`_}x{ptfYUcjm9-$%'*)((('%#$)WCOGID:;LpkTW8M`]ONWQJTYhttQ`hclsmehkou_Vـʾݦ¬Ÿ𛳻¿˔oo@qatas`#1y_oY26zZMJ"IJJJG`\F+++'BG?7GXMDGC=/7rymBB@^F0~~~~}~~}~}}}~~}}}~}~}~}~~}~}}~}~~~~}}~~}~~}|||}~~ }}}~~~}}~"Ìͯϸ繳ሜľϱֵIniqnlnqswyxx==6474457:9@56j_Qkƌͩלu~}󓿹ͩWXtro"ȶ˿ȿ՜dTQRSUVXYWTTRRTYg{p_onhQNdeV`kgdi{ըTws~{xwvu|IROKMKJIIIGJRUQNGSRPR>;<=HMMKJHF{LPRONJKMMIGIJHEEAAAAD{w|~DDNNJJFHLE9Bdca|fb@nnoz}Ȃyqz:ƒwljf}wvx]p^Y\ukavwr``S}ovc_1*$#$&&%%%&&&'+aT,9:DEOa|oOpURBESRIKQkzRHqnUXlpa\\ZegRͰꗴí󀌒Ļ߼惌û˽ꁀ󈔇ﰗ̉CB9+quhPY\HK9/~}~~~~~}~}}}~}}~}}~}~}}}~}}}~}~~}}~}~}~~~~~}}~~}~~~}}}~}~~}~~}~"ԛ֞ͼшͲ̼ɰɫ}F2konmhl8quvv<<735214569;?CA=8emkqntt85gppmspl7559521311mcbdmpm87h676li7=@<;4279p^^`]T+(..ZZU^^^ZVW\[YZ]ai765ni7:><36paSfȥ~ݠՌ׬ʲ_brh"ژ֍ݼ׽ɸRTRRTUVYXUTSQWmcisttb|sM_iib`ɗwfxĢT5huyv?CNOMJJHCIKLJFGLPLLIIJI}~x>>CD}{~{MMKBFJE@LǗgd\~JPzM@wyw{svt|~Θgws}f{i"vzmWYmbWKd}y\R|ys~]~U*&"#&(&%$$%')SS,S/10GQMGEcna\UaT6mJPTMu@zd?gyeZ\Y]S񅝬ñ䃗˹ǾҀـ녋޾ڃfBC6<.7rxB&t@:IY?=C=botmf|tkxwRvqI"SREFLhRK)#F:7<;@?B?5Xoo[B?LHcJ4~~~~~~~}~}~}~~}~}~~}~~~}}~}~}~~~}}~} +~~~~}}}~~~}~}|}}~}}~~}~"Ũȿ􇕷ຆ‒烔ϩfqP6rqri6ptpyuro9;454343687;?A>;l^cppwopebgmlonji46;535535qkfhmkjkkm7806m28:=;478<8b[_][Y+,/k`\]]WULQXZZ_aae:89vqpp;96:rlC܁}ᥒǔཔ\cyqt"觾ݘ벐ѦԿ渶€߀TXUTSTUVWUTSTlh~qTV`Wmаesbsրⷠp?kkxx>wwt|yKRLLKGFEGIJNSXTPy{?DOOMLKKH|xHHAGCHMMKFKIHE|{>AC~|x}zOKL}}FGLVad`wn-J^N`Yu̚ymfo|uiwz"چɹ}YV_g]PX}w[{PWtk)&$&()(%&&##OURMRD2CV`MBKeoxxl]O^}l6\sEOEu_}PUW8QylegfgST͗ꕱíԻƺϻ΀;懍|Sm4=>`=G&JomX828NI7MKk~ ~~~~~~~~~~}~}~}~}~}~}~~~}~~~}~} ~~~~}}}~~}~}~}|}~~~~}~}"Ʋܽޅ剒뿍Ȳ?:H8ghmt~}uvsqs<<:65688548<<;:>>46y}pk^^i57lcfjl6::46456imhfiilrooqjimj`7;;::88<:eaW^[^2^3ic]YWWSMOR]\fgdo<::9tpl28?=rrH݀恳茲aYix"粗庳Ø̽ŀ]\WRSSTUTRQZx|wah~oVVxpnu\b[Th@jnz||zyzy|FMOKIEDFGJLNQTUWUMOJH~z}BJPKIHMNvzHPOIHGFII|~BE~z|{z{|||NLJJ}tCJb@ENJd8H;?{d޲^|wb}y~fqxe{MNi"|PSk97:97258=:87==9=xie138876bhkq979643564liijeeukkkjecfhp<9=:8<=<6hW^_e5fdbbZTRWQQPSZZdhjx:778pl45;B|vwXeɾԓɆݧޛ~˪puy"̌⾿ɼí̀X^V^~{rbbnv[nmx؃t[SPiȦnrzdjkjxvvxz~~@EKRNIFGGEIILMQU_]UXHJNMNK|zBDLMIHMOJyy~{v{~SQNHFHKKF~{Bz}~}y{w}KKJI;:KUXlo}ˊns`xrutyvju䄞~pvj}deyј"yn{y{o\``wwZWeSH(')SLMLNQHDQ]XJH?5HZcjzfOcaYbhHB/Gjqfo|OmjXG>/9UaTdjh늡Ӽ߃񙦜ǾꁏЬMk*9)REjOmIH9E]\EKh]AfsH4:M_fއzsgpdH`㈎ƤybXb"MT=?/E:,$#@5<@AGNFGE=?97;653778788<DKPMJMNNzw}{wqx}MOOJILKKJG|s|~~vz{uMNKKICBCGPMa^lfվhssvuwozgƎqqd~zmxnٿ"xwmn]a]`gS~nirpO?NOQPHIMWYIAIT]WM;!;KKKRbgQGO]WUb^"AB{Hbn`׶OmfXrlR􏥰ĽٺõہغᑥѾʺúۡYeE% "Oo[I=DTdL|HO|EGZv11btt~kYbk~MXQPc¸zf"gWLE;L?4(*"#)&9:DACOJGKIE|klJOB;/~ ~~~~~~~}~}}}~}~}~}~}~~}~~}}~~~~}}}~~}~~~}~~~~}}}~"ޚ򦸹䏣燝̼Ǔžbe}rtfkx}|tn;=;8743454345::=@CA@BAtg4657>=58668764213<;?DFHEILOSzuw{xw=?EzxvCLOJGMJKNOJ}}}|~t|HJNJIHDDHQQ|刟fTVNZkftimzpʩxmFyoёqsn\͉~sշygW_faeWkn~vsuz_XUp4:7:B?AJZZD-ScFQZF!=GFGJKOP_bYS^}LPSC<~yvβYdPy|ht\摨Ш둣˼킇ǦҀ׶¹كb[B{eg]XYDK==>IMiKL{>5N^oOoqwU`_\TaynZ"`N;`QO4,/0).+L@FB@D?AB;63o8=A}769hnl6752.:7iikhida0/f_\Ra`n?;736889:52/2111f_Y]`NZ][Y_jqd3166:<666:;mvỢێ찋򔚂왕ʹȋޚѾ"ڍӈ۷һǝ|aXU]hzSlkUnVh[h|nu|s|kdbel}x]Ǿox}}zkdr{|LWZRKIIHFFHGJOPV_[`XVUOJIJNQA;=y@BGLLSIy{zzyx;=rwvRPLIKIINPJFBEEEA~qzxIIOJJIEFFKL}ފjILQky~Yltby}̟rd\so|uĐl"ui~fXifa`UhnmcXW]y[[x9"#]Y&0HI/ >;$8JM.@FHPVM\jadkj[NV]`hlR9jkg}^m^{O^Mڌʨ܎˼ҹϼƀҴݹ쬄nfUS;i^Ch[hcf@EN:ACJQgWA|mCHBBWhs{~c]iOo~ycL"ZVG[>H>i-52/0.IID9:<:;KQLFw^;EY|~~~~~}~~~~~~~}~ }~~~}~~~}}}~}~~}~}~}}~~~~~}~~~~}}}~}~~~}}~"ץŗܾއϙ͇ǯ񈒋ǺቂrrhqsxELt7>>97545635557:=?BE?>;:74m8:yt54ihkj68431:8mjnjhd0/0`WVY[ei9875368<><833242402/3*.][Mai7121679997466lmzRȼ񠷮޻辉ߋЛɲ궭ʬ掼"ӛӋȠ񇯽ϩadwPxjXaWx[aiwzxrnehybSZiidjxnϵ|visw~LO|GW_TLFIKIFIILOPSXX[URRSOHLLB?|{|y>?@HKSNx}zwx;:<}wuxwMMNNKJJMPLHGIIF@GDB=B=>{~oJFIJMKJIHGBBA}zNbwxj^f_vin|s{pgzz"}wqjk]^fhfh^|`YTop_ӀE#34FiD2AI!EJ@=> ..FGJLTSPiuq~VtQvPWpnWLGDNWY`_QvyD?x˂Ѻ멵׃ʾꀀ̀񀉁²鉪ľϩvn`lb1'eZ79*HEQYKIDnBQc=>JZU01ufBTN[~t~zsnikmNpm|viSV"qPSUKI>:jli7d0.1bRLZWagY4842029:999434451206//3\NUhlf479;:>9ro65osuʨț󯡠ŒފꨋΟ¹"סƚȂÀ}v]`q^bS[nZazj[e|gf[t^dвuwvuonǥýz}pr{~KUmKCV_VKFIHHIIKPPQRUWVVTVZVJGMKyxwqDBACzyMOONMOI@>Ʒy\jPhpxgzmXnt{qt}sooˡ|"dqdWolkfelphaTdaY}QI>5c*5+-1?E]hXUG;#1PLJGLRXd416|LtWqAHk|oZT\UZi[bqkH]zڑڏ˵ʲħxlhxtRI6*&'*@D\uW@Mc`L;:*1XA3~{~E>D?>jFF70/210.L@levwUeBRJ@lg=i~~~ ~~~~~~~~~~}~}~}~}}}~}~~~}~}~}}~~}~~~}~~~~}~~~~~~~~~~}~}}~~~"Ԗ؂ϷՅѹͮڵvt|iiryzE<7M:<=<:9:65458757:=?@>;>;:6450^_afjpkjjj756<=;ljih\[020ULNSU]T262211168795564423421.13*Q_llnqqv>;9oq6mq|超쀁롯ˤɚɑЂ׹ު󷱶ƾ"凒ҟƻ難ry}w^jpЭ]j`[pbaW€ee®rw|tș۾}{xzz}T>1IOT\RKILHEFJLOQPPRTUVU[]YMJKG~tszy>>HTVJ|vquzy;?Auov{{}xFNLKIGFJLMQLKIDDFGDDFCDEA}SNH>{wyŜ~FAJcws`Y}sbq؞lq͛IJyhpyآǐt~"~bwUbcoqrnqe`X^UZo__`nnTamAQr]@B9;9LRgu]1;73755203546:9;=>>=:7324111glppmjlp=76:9:igik]000-RQVWUUV^451530678:8755631/11-/`ovxpkpt88nsnjfjޒٰߑ郒ф¼㮂ˉوȔؒũǀ­ŰœˉtΈڿŶj]oek]Ît`sr_yz|ݬ뀓~Szu~G[9AVSTWPIINJGFGKMNMMNQRVYWURKKMJIF}{yy}~A@HTUQxtzz?<>>z{}wryGKIMLFLMOOKJFCEFFEGJFFGEHOHzʴÁyѼ߉lN`~k|jqWk[o]mgq|eưzzzh"Wyth{}rujdXvWYly~Wxt[Yp|qvehZ_IZbhzvzykYMORTew?9.LrT[\e4>[z{no}tJEnjkulcRWm߅ɿ񀭂禡ôڷɀɵ¼ȻU[AO'EY@agND)0:QN>D]o[8p{g_[TWQu[ji<6BdwuTcs:lsqVz_SZJ6"VoDNJO=YR,33/"639BFHI;[a`CD~DBD~~ +~~~~~~~~}~~~}~}~}~}~~} ~~~~~~~~ ~~~}}~~~}~~~~~~~~~ }}}~~}~~~"ѳDŽʩ򕟐򀆍ϳzgme[ggpq=DZ^dL;58975654423556::6;;?@72jf:;54oedjmgkns938<==dhha/.`^V[[VNQSUe1033269:;;68564201./0/,23vr:4_gl4mnrnmd_yyܾչ籲Ի쯂꼲ŻğڋُϞ"㼱اـΑϸyիlpuyn\hx}uvzZO`wurpm{tFLXW\QJOSNJMQMIHHKMLJJJHPSUTPHTSKI{v{|vxsxAFPZXLtv{z=;}|ptytFHNOKNNNMLHGDFEDCHHFFGFJGPOM~뽛榓e~|q|x[\^l}~ovdlpnrohŹjqw}}"з^c|zut[dkow{`~[Xpc{|[`e^QgyhrpNNVVkWMEhokGJg9>JaaR]sY?9OhzsnPFYlԾɾмĕ˹Ƶϣ{zzOnk&+/>BFROK20VFNLIAB;hij<@;lk`[gjkori5/6;;;g`_efX\g]ZWVOKUTW^/2326:;7756441.1114001/3853-0042gkprm^uͮȡշƠψ퀆ՄҠըԓզʁǐщ"즧퇓ΩŪӯ׀¢~iyg֋€f~t{p¬{w|j~lZDZ~}ztçʆwc|n{CDBFIF=BMOMJIPONMMLLLIKNLRWXUPW[S|~|zvuAEQ[WLyuw~y}ttxouHNPMONMLKIGEFCACEEEDHIFGJJKHMNPKz巬_sti_Ztew_vgiqTlnagķU]p}|qq"ίkkcgxtxbkuwd^|ZxL\s|oA-G;DnzJf>UEOeagYXnND@DXtJ]bO:y^^nid]UraQ{7KfɃù뀄藩¶êđϵwIC2EBQLdDR-Ni@GF\NQ%Ek;IS1=CUaNERll~hETnojP{hd[`ZO8"Tl?fH7/LLCZhL/PDCHKCAArfmFGBB~~~~~~~~~~~~}}}~}}}~~~}~}~}~}}~~~~~~~}~~}~~}~~~~~~}~"̟ꢝΉø󊞢澶ᛢॾ{Eltp69>A?nfb=C|kd\\[elml^/138785acabUY^[YV[VKUQRQY2143:631213/12423511324852121de/37pnXn鸯¹䔡䲧斔ˈۆ܇݁"̖僩Ш֪׼ө抴€Żhgwmrnqjrlulxmrӥŋ⤦s~Y}@A?|;;@LKIIJNPPNNJHLIINNT]VUPZd}w}{zyAFKQPIBvuvxz~|}{xxkqKLPMLIKKIGGDCEDAFIGFGGHILL?;@騈Vz`suq~opȊt{poumorpÛQR_p{th|`vkm_jwhawk\zx~vtxrhcTb6?NlXC\DCupC`nhVX\MfMQRJfNhfNnXXMKpjf̋zfVLMh؋ȷ뙩õʹ¿i}sqbgd?GHD\eMclU_{U^_U@Wk;4{c_OeRk|=cKisqzZloQmTRTY^_I":NJZn@L4G:8;=ZmI1TJ@DRA9766557;9:<9636@<>9=ELMIJKMMNMMKKMKIKOVYUTQJG^ICzx}zv>CECIPHCz}yz|yx|{wo}}rlq|KNPMIKLIIJJEGDCDD@EEGGIIJJKJ><>W٩]ft`pn{ؔÓz`hDzhfglWXY`"actpemYahxcex[tt>?@A>yuw|gfFNV_SOuJU][epWJKmcukabR@c]e~Qujx{px|tumZeᒙǹǭȸĸ򎨾¿þ녏kmSK{EaBxpaipl`6eeniRȠTggAEv{i|SxqjVvsnJbk_ELGPTR^P"E?E:vbX?@YK8>?<8.`r{pb022goqjh43469;85dffdhb^WOSOS+UNLPScn34=641431.17822131116;9848.0l545imh?cŸՙ؄vܦ߈ȥɗЫ"ɧ˚滖ϧٳ拤kjffmrkƱӢsy^{xxwmZ\pyyyļ~u=AAD@DLSUNIKLLLNOMLMMLNOSVUSROFHE@y}z;;BELRIDz|{w{yxqruFxnuyGIHIIGDBIIEFFEFDFFDDIMKLJJBD<@@?@??sYpOHL_~VvkTy\WQQ"IC6 v^.:?d^RHF;14b_P@??A:3cnphagh2eqslik65664353cidgf^SNR)),.YUTO\eSVjc]Ye_0,1652102224485688704434dgbT|ա݅ŧˢٰԹӓֹű"פ􅗝갳Ϝ놑ushakuqjghntrfȄdlەtztfmumszdvõqٚUvzADDDGNW\PKJMLMOSNHORNNMPUVUWOF@w~||y<@EKLIF@y~y|zuqou@DHF|rwv}y~{}xEEEDDIIDGJGIHLJMNHC=>=;@{jꚓbZxyrvl㡒Ŷn{yo~ktp^qdwxXd"h_Xaåzca{`{mw[V\gV[`ghJJGA??@FGDEFB:[wLADI[{{zvjTFHXn{rmrhXMLNC?S^Qczqw^m}JU݃腍žø񌑓ƴׇɾ0xg?aIdILFtTuod^ZRRS`s[PCY2ebvUht]UIUgoBGisRS`zOQ"FB7-s]@FYg05c^8@>Ca@?dL=>JJW%6>FC~~~}}}~}~}~}~}~}~~~} ~~~~}~~}}}~~~~~~~~~}}~~~~}~"ሌ۞֚úβྻé~{Wxosz=88>BDD;8426::;648;8:8:==>?:714<;<=~bקzmxnʌ{xktxီ|lwkqrΡv}["cPPel}a{g]g~odf_VQJwz>?BDDCBB=YlI@GKnzmu}}{ueYaiouuuvpdVNA# @FXpklrhnqod|J߿¤ж웣޽ǻރζ聟V:f~h|EyIoQcGEpwnhEJ7zZbHDwIDVQsvXqnhTp{Rjj_}6ePJ"Dp:Y_Z*Dil4cwaB\ea7@8JODBIQ9)3?A< ~~~}~}~}~}~}~~ }}~}}}~~~~~}}~~~~~~}}~~}}~~}~}}}~~~~~"򀐟̀ɸʶȦŵѶ|i|u_hs=97<@B=76135577546:9:978<@=;813?}fbiiemqkj67665:584cib_a_VOS**-0^PTZUXRNIHNT[^d\SY`[a55599433438553003jeUuM鄽żưϖѠƾ⨧鴿"¿ú崎ؾ׺xcltuwokxuqvjR;boU|s|^ƚobv]vp|b|xa[{uiɔɁunɑW"a}xfh~|iWdhgpwh^K\__ouqwDCDABCxQPxELDbplr||gOFR^`cn|xbM? HbFvk`Y^_;6Dfײӽ􅌘Ÿ߷޼źŞꁘYtd-]PYgpz{>h_b8X]sq_26J9LsS2NLD^[1GTRMXVTK^ZQ¤eZo\MRkrL"LrlbLGJ3@LMLfP6dq`6=U3BLDDIA35<>k~}}~~~}~}~}~}~}~~~}~~}}}~ ~~~}~~~~~}~~ ~}}~~}~~~~}~}"獛ǭŹ̴VZ188:9;<8674325777176687459><:9g4=v^dgfbhppo38677465gah_cd]TKK*+/1WPTVVURHAFQV\gd`ZYa]dic4;;888544242146orqCܩ흊ķ֘衫ԗ؆ʨ኱՞굃"㎍鋗ЭۢҎ̬^jqvwsodo}|wmu}paWm5=P\DVͦrrlZlsGHDGJLNMNPLONLKMPHJLNNMKKNQOLLNT}|y498>FJMIu|xyvx{HEDF||{vvtkluwzzs{y}@HJJKLHHF?@<9=AaՖrrry~q~q|anhMhҩjQ߄iwlkzVkpˬi"gwYW]VgdrZOLQTezh_Z02?Rj{ln|CAABFsOOF@@^Wryy[8:GD7=YgfO8".*  Iyrj^N@6<><9897~~~~}}}~}~~}~}~}~}}}~}}~~~}}~~}~~}~}~}~~}~~~ ~}}~~~}}}~~~~~"pҵ󌜤츚ȼ­ޥu_^T268788:8355346655355434035?@794186dbemon386:84543dfeke[XQM,./.*OOZ[NZKIJP[jiebUX`bd]T]hp3575443jd13gpv=Dzƪ–ա۫۲ˁʈח.ߤƢȌ鉂惛˿^oeuxvmǚwzkĹ˸q[QTnx\F99?B:M~|qP=BKW^{ksKLLJKKMNMOMMMLKNPJKNONOKLOSSJKHJRM{x|;=9=DILI@x}}yux|IHEFCy}yn|uvx}xx}~zpxFHIFFD@z;?YpØv\}ox{hpzW`v}wx]fcpvYUӕKjh{{X"WbfdsiZieyiÇ[Rkdai2 9ht_bmp>ECDFabGAAGJ|ww~lQKMB*"+;VbPC3! 6juZUlh@(tА򔣰Ⱦ²򅖝ĸ⊗IJĿǷs\o{byGXuc[aslI77WDPuN4)CeGDgwMWdnWS~@7fNyDevfL9O?Hc8PS"XB06439:E-AK=6O>MUL;Q^I;=?GD;:>~~}}~}~}~}~}}~~}}}~~}}~~}}}~~}~~}~} ~~}~~~~}}~ +~~}}~~}}~~ ~~~}}}~"Y犧Å屌ŝ֠ӿꭏ遈|jR18;988;<7544568433146<@8664;64.22`islm354865451/a_ed^^\Z\001+VQY]RRK?TRYcacaV[adg]RYij300/132egdglo:;F\曶ѩڮʛCмљӑՑ唗㝝áʥwqaapms}futqm|ŹievepaOUUV_pbkr|g}_p;>D\{|JPQMLMOQONLKLMMNPMMPMMNLLOSRJHJKSRPJJI{?>99niWIC>=OuwuZ8"#X}>V@WdHL9& 3x楡圶÷˿؃ĺɿºރށp:kf=8kqSX74OnhIqhSyaHa]b`~dl_Jy]^mDpxdLLKMiJAQnj"liZ=)548GJJPWAE@J_W_EkN{CA?Dq>J~~~~~~}}~}~}~}~}~}~~}}|}~~~}}}~}~~~~}}~}~~} ~~}}~~~~}}}~}}~~}}}~}~~"X޲ԉீ͌ٺለ玕ײsZ5:>;678::83546665763437;9796:852c`-4mfjf24664463ba`aecfbb[-,.,YTVXSGMCWYZ[]_abcafjdedqvv51+2c2llj7qo:;Fw٦ߟڎԩȶ|ﬥEⲶxɇý񌸻尿ʸىĬëÝugpzupmeqhkhæxx{ggaqojb}}rwwt~hdclyJaJNQLILMNNKGKLMNNNMMMKLJILLNOLJKJRTPLIK{{v:<>BGKNDyxyw{zyyHGIC}uqsux~E@9=wHD7,(Q?lCBB}P[RPJ4Vjimkiwy24m_ZdjfjECg=6VhJ,BrEDeETY]Y10v୴⚱¿޺ɾ훥ѽ뀖srAA=iCnrEOaRe>nbVRd~HywilSDOgzMNDy`JNFA>iW"Q;gP&1,2FRPKJ8<6AhFey~p]pgO@b[V|T~~~~}}~}}~~}~}~}~}~}}}~}}~}~~~~}}}~}}}~}}~~~}~~}~}~}}}~}~}}~~}}}~~}~"ٙDzïܛ贿􃐇f4:><67889<78566777534585446876359<<9d0+28gfj46562374dcddjddcfd/+.,.-.XTMUXZ\c]XThed^fiiieoz=hchf4mlook_uBO񖶴Ă؉؟윋臓̆冀팷ɲ"ǶՔҙtŚb}Ϳf}x[lh~xpsҔfpgj~FMQNHKMKKMJLLLMMLLKLKHIGJMNNMKGIQWTQLJNLwx<>?DDGME}~~x~zvxJEEAA>=z{}Mz|y>{Uq̀_vѴth^pvXwlhZu~pvfdWO]kks`bcx~iO"q~cZutv}UUVYg|vklwʼnfjDwYJHNXQC:a6Rllr{nw^aPK.ATbeopX;4I@>P:qlx@>kOQsltt?DqYg~}T=* Ǫ𑧭÷ûóؾ숗uPZK?KPIsy:vjbIR;ARxV8ob`neCP?eEMgiUy\Ş|A;Piu_OKEECE2XŊ"N-MS1-(-=HOQI7804BJBrePTtFpOkEIyV~~~~}}~}~}}}~}~}~}~}~~}}}~}}}~~~}~ +~}}~~~}~}}}~}~~}~}~"ץưѕ焃򻩤ϣۼf29<98989::74465564/137;7427875648>?:30/5:jefh2451483`^]dgfelc_0-0.401/XV\]\eri`\he`]chji^mrBAjhh4kkommdji@cݛ߀Љƹ˱ɇ࿇ƼǶ̏"螉ʨĢpn~rze^]i^n`RO]xy›נ~taz^jvCMQNKLLKKLKJKLIHIIGLLKKJKLQPNLKIOWVQMLLQPvvx:>CCHLBz~zx}{yJEECGCAB|~zQS|}A}Vw|Wzkzncpz\rVsh{xUYcxrbyk"Ʒsubyt]]gkyis|lzdyEAbDBIT`]TKDIQUbnsu~hqh=5-/9Lf2bR55 !2YaSN]^SS\s\]w{wMTyW?. +Hq½ƾíو¿ňrGsMSLG[WI~\AN\lp[K`}Jpd;GRs\J|=KB9l}gnsFessCKFN]pyKFI?=DLCVk"s_UXP7,&9?GYI=>kor?@kVMIDG)N_}YSvX~~~~~}}~}~}~}~~}}}~}~~}~}~}}~~~~}~~}~}}~}}~~~}}}~~}~"TǗ٩𺟏߀ۻդዚ˵󍑴d28<><;;877753212136;8547787758><8722585e`cfh624520a__hkhga]0.0-1/14\Y_Y`9=757sfWXajiibdnAAlfehsnroplwtBlݝɳ脌J΅񭐌Œϫǝ˽ڔ޻ԫȲcd\kz^fs_hXXPeo^oǕsjoq~{X5:DڠzENQQNLMJKLMKKJHHHIJLMKLNOMKKJNRRNNLLOPE{stvz>AHHA<}yt|}{{|IEECFDCERXTSRwy}}PRvy^z^`kkkstzma`mqɷr}XbaVo;dexwsipwrzwιx]yuesZkzw|e|_mrneifcH)"+9Nae^OLPS[bg`SrEI0PM4:Ic5cM6"(,"):POBHMBDIReznLIUvKORa`ih% #Ь≤¶񗰾䅉ƿźۓܻͷ7cKPPEJ_HAUc=EHPloc]nFZ=>EAevqDI<@>;QkCm;'~>jiYemonOADE;>JKIYr"||kUA<&6>CKG>>wyk7>NKI>?V./Jn_YkG~~~~~~~~~}}~}~}~}~}~~}~}~}~~}~~~~}}~~~~~~~}}}~~}~"˺儒řЬХ뉍ֺc29<<;;:67789676335613476547:;<88;=976003:8ifghif3453``dcfkml`[0--.-023.`g^148857{jXTcijd]Xn:95dfhulosxrnHmwڏٔݬȗҀ橊枔չȤզ׀"óŘƹb{^[ixuRvjb\voa_gx_]ptlpm|tW_`蚚HNOOMLMKKLNNJJIJLNPLMLMONNLMOOLMMJJNRF~trqsn=BE@tu|zy}~{wHGDDEHFEAHLRTRPz{|LJ>qxcpZVw|}vp[TYuc^ׅYWpy;^vmtpyy\`^wVo~~~xgqV^tu~bs`_ŽgzFJ:!<:PkTn]nFHNmY|a2,;ۃſ¿ƺ׀ƽ䚡ŠV@?s_^s|K`WşȃϮŏ´װ絹̳Ϙ"µԗπhrg][[dtxqrl\{[xqyzb]¼m_dk|wKJLLMMLKMNNLKJLLOQNMNKLOLMMMPONQPMLMLKNPE~xk[TYewxvt|}x}wv|DHFFGLHGDIKOQONyzHF~qw??w~C_a[aXPayip`yۓ̺b_ouo˨lvpdxyojgdm|yUUt"_Ymqx̞pzspgl]dVq}Rp}aoZq~1;@5/)K-]/(0NacUIHRM@;A7B4:=t77KG{e9FEGTqM@wnjmxCFMz\\OmqoukfY[YTJE5=KF?:"CE<)YF.sʝL[d~~~~~ ~}}}~}~}}}~}~~}}}~}~}}~}}}~~~}}~~}~~~~~}~~~}}}~~}||}}}"θگ쇠􇌇Բb09:;:;:699;865667;=<4145655766;9CE>996549;jfXRIHOZd`hd`cdlsneb..//,/.011h]Y245853cWU[[gc\4477nfj6mfbq|xͤܠի鎡џЈڣ̩Һ"ĽќСӥ}~~{qjXrUtgo~_}elkпĚFNMNNQSOLNQNKJJJMQPMKKLMNLLMLMRNT^VOMKMNNGw`RMS`q}yvtxwz}vqy?CDEEIGHHHLNORNJ}~GILKxy9w|{~ϡt[qփvUz]c~tRvt|}TyzZX^iytqom[OX|\"VJ]_jirrq`XWYU]X{{khX[Suv:576/>3-9FIFEC;016BEg_Hj:>;K0=aSbl~~~~~}}~}~}}}~}~}}}~}~~}~~}~}}~}~ ~~}}~~}~~}~~~}~~~}}||}}}"íԁμ򅎊ܫȸӃ԰uQg/4:;<>;168;:76669;:;3/28<77956=BNO=89<769=l^LOEFNX_hjaZ^bhhi0/.+.0-2/1/1naZ178764bWV\\hga23;;7jiimppt|Ϧ׮󂾇ǚ񖬥׍ݽ̔ɇƶݴɸɕ"∗Ȕ¦{ij[aabggưgꅏujcrXooiȷ_DLPOT][KHLRPKHIJLNLLIIKORLKPMNTWciVONNMOMHqZXNTbn|xprwxxtu7<==GG@FHJHJLPRROKCEONE|r|}r[o`|dwuwa\|Sxʚwzy~XJ~mYXTP]zxm\u{X"}{rZm{W`WUtqZ[eeyvvl]FCNTUSH8WMDI7P5>~=xtl\[WG6@KE@Ke;@Duqh_[VRLEUM:#/>M\CHGKд䕼»¿ζ¾݀􂇉Żيޯ裌|IK?DFNMLYncUejtgtdF{CMKa?}ibL\]wpw}rcQOPbXQ]um^SSstIH8aEG>"xoVDhGPvTF7@7vlevv;DobMS]\IOgq~~~~~}~}~}}}~}~}~~~}~}}~~~}~}~}~~~}~}~~~}~}~~}"끁􁊏﵇񒨼ٷ{tv^1.48:@N^815;;975476694039=8777:BGGC;687858@5XMN@HPT]heb`\`fhh..\T113111035f[2764545_S]Zlm5159>9844musn|楓ÙĽġρșՄϦ®԰ɪ≠ݏ"֔‹䇊̄ūksheñ]higijfXXZeggbüXWj}|t\HBTdl`li¶pADMOO[}PCJSQMJJIKKILKLMNPMLNOQ\a]WQOOKNOOPDp][MUaiuytxxzzxz:f>?K^MbWccYTf~~~~~}~~}}~}}~}~}~~}~}~~~}}}~}~}~~}}}~}~~}~~~}~~~~~~}}}~~}}}~}||}|"񷗃ؙ惉фs~4178;AuMI37;=;721788:7678:9:79=ED>;5566:45@5YSQCIOS\da_XYblfe_^UP.03..0372b[`854676oZcemohfc5=;9645oqnzz˔ƻξֹì򶐘ŐżӢ󡯲Ý"ֻƀՇ丛ȕfiw`ylmgcaZQ\heddcȿ`b_Xxn`TF9tFIEY[eºDCMPQ\Y`BHPQOMKKLLKMLNOLNQSNPT`XPPNOONRONPFwd\MT\eqzssyw}vyy{sqCDBADGINLONORSP}CIKGB<<{{y⭯wũ[qebwδXnz[^^UPUMNESxrY^"ySvgyXejobmt|tooDf47Z4fXF->MyifEXpFFEA>=nZRNE"T7BCB@?z}D?99mLrG- JCO̹ԒëϾĺڇ󍑔󃓎}̆FCGBWzmU{b`bA4GJ~SRs?Jzs}q{rs^TPQDKfsQnSprXB;63269<<865:;9989:644lss}ƕੲȶόқΈѣ٘廙"ǠƶƩfeWtcpelɿgea^[WVUUbri`^\ƹcd`qȲx>;7GukQ@FKQVsVEJPRPNLKJJLLJHJORTSMORWPLOLNOORSOLE>m`MPYZby}ux|vvy|xyBFE@@GMIIKJMRWRLCGLGB=<}|͢ǎ{÷[Ɗjhbmlhxkbg~v`bmx~_PXWLqicdn"[l^Y{o{suib^:V<=Sh5dJ#18Xr?@S"WNB}i^@Rq{qo3@FZCEOXD<5t[e|{l{GBs~~~}~}}~}~}~~}}~}}}~~}~~~}}~}}~}~}~~}}}~~}~~}|"•聊ᰔڵwzG`9988AGB;9<:55657:;;836::9<988<:731444358850]XJJLPT`d]beeiinld^_ef51TR.353he41158>;4diigdbg38zr4/1kpv~⮛ȗͅ爩ɮƞ︠Ǒ턝ĶŁş߳"Ś󬈋ޭÑcf`}hs{icldfhgd^WV]jcYUøc\W}r[N?p~nݣjeybTb{`[\lcmggej܂YUobkmilxU~LRT_ڜŏ"dzibddUӞbqn{\^:cGDGCJ\_=HI_?KM54Kizuqn;:;:5*#&0>?73fx{|D@;mbSAinl+ AK띺ú¾ɸĸŻվꈂ񃏎톊ݛ[MGYk;]JON\^QOKHBJiE/B3CJKZNF68c]ImuO@X>HE^vVIL>xJGKciPHE"}FsaPYDHU^J;5v=[rnyzpkD~i~~~~~~~~~}~}~~}}~}}~}~~}}~~~} ~~~~~}}~~~}}}~}}~}~~~}}}~~}~~~}}~~~}~~~}~}}"pذƻȱԨuutSU47449<<:8:8786558;:667988986679842420631]VHHLPPY[`T]aed5dcabda10YV[/410495578:884eaceb0.ntoe_14kw}ۨԿƦɘ뤷枾ޤƢҥ璱ʈڿ.߬ηDZ̿矤Ꚁmipi~vrlny|cgkjbXVW[XSd^qakhZIdcpjw@IJKMLKJKNMKKIIHJLMJILOOMMLKKHKPPMNNOPRNKE;h_PQUZ\lw|zq;v{}CB}zGMJHKQLKLOROJG{|CE{?@՟m]yu΄~cmkYԌsSPc|[a~cv`}ft[܁kK@zoyk"Zf\^gnk\{ryu[?:_ǵ|xtz"paVOORxrinYlmEBnXK"HozwpV9Tt4/[K(%IfwGG>4/'$')%A>>?CRYZ\jw~aKJPMEA8#SAB=Յ¿ƽ»¹ʿ©򄁂쀂rUeA:SSfkRGMMjh9Eh^I3xLggSSWM79I~Rg]|QPAXMA\xxT~LLzrrpjS]BVIL#":98zYdi_0,6]zACkwt^@<@B=zyUPRhv~~~}~}~~~}~}}~~}~}}~}}}~~}}}~}~~~~}~}~}}~}~~~~}~~}~~}"땯ͱͥżÿ켐ך~yz=vqgA>T6 "/-=O?EZCZ^vmum@=OPJB|Ly]b\So~~~~}~}~~~}}}~}}~~}~}}}~ +~~~~~~}~~}~~~~}~~~}~}~~}~}}}~~}}}~}"ꅌᯐÄ᳓ꐰĵ՗ VBc574376768:<85334389779767:78=>:85777853sreYLBGQMO[f^]SY_^ci`cjtfa//31ji1.07>@B:/2iiktma`5:@=hpl8CN۩့ݲ鿳蔤㑘Йǀ˥򭞼化ϯ׻"䋐ήy|րūhg~d`hnxdihhiaxc_SVRVTUwy̮zugb]Z]mTtENNMPNONNNPNMLLMMQQLJKKNPRMKNOPSQUTPQPL~jZT]c[_qzw|suxyzzr|w~AELJHJKNQSTPKM{KTYL|{CJLIPX޻azZdīvq}ah]pֲ^YWHBNaws{lz~[ts`k]"Ydtjvُz~~ccgtr[Zf|R\`n:4H:9w?}@>O]O^8::862H)6AF9_W@@R(&)\.-*RV\VE84@T_j||tG_B^lo72446qo{ܓµżܹž놏䀇{aw@fj`K0FJEAnh;IVYsWNKETW,7WiNiGS9hfozvKLAHRUuzea{ZR|prLQUD80"07DINglrnP=N^MG>CMCfch\d ~~~~~~~}~}~}}~}~}}}~~~}~~~~~}~~~}~~}~~}~~~}}}~~}"ɩߞ̋ڴެ䃐ͥfFg645469:89;>>766757986789:=;9=<;95656751mpbLNKJOOP[g\W]c\Yegekux`[\3203432d79<=8456bikj`d6==8hij5>D?GV⶜߈⑍҅ڨͷז–ٌㅒ"琅ȗֶÍlm`fddghb{PMdSk~SRPTYY~mqc\ilishtjqf}EKLMNOPNMMOPNONPNQQMJKMPQTQMONQRPSQNNMJ}YX\Z]Y`p~zy}yu~GKKKIJJNOSVSRQLuIQNDty{=FLJOcq}s\wwWk̖dqȲnxj|tYq[]bRzJ^iuzd[vlr"ajtiXUgwuncgy[|[YUZXOOPHnMQNcCM287/-48Z46'&3"$#/L("JZ0/-,[_ZKC==EFEZ>5fI13Ki;>:gYTS@AYא·Ľ½޷ĿǼ󀊖nGnR{tVC[OOQW"LsrrsyzwxFAun=b\KGFKTLq[a83~~~}~}~}~}~}}}~~~~}~~~~~}~}}~~~~~~}~~}~}}~}}}~~"ʹ˶Ι؃Ԓꀇϩϻpbq356779978;=>7:73667756:<;<<9:::;97779737raLTPNHLP^eZ+2^XUighkrtcc^/.2349rpr739978:/Yf^nk<>:mng66=?-N63%BQg8S@'S,# DQB>LQL'-/0-))MD<=9E]l]?DF60@\bjq`M<4^o䖼¸żÀ遍Ľν蝵򊐠MXD~=92Y2ZPX?#8XuVS7E_XYJWLCLHbsZTSg:6:7pjqv>@DSNGUJH/,RU{gvmg^6>"<=DEf`cW}~q`TQLa7U[XJGNPIC`b8~~~}~~~~}~}~~}~}}}~}~~~}}}~~~~~}~}~~~~~~~~~}~~}~}}}~"Ŷ̛ӁDžDZݑʮӿȵ}eo{468;875788;<9:60255686;@>;;:899689:<975qp`KMMOLMQcd[.2XW\edgmooeb[),16p:538<:8;627ft9A<9n62658=AEWrɾΧה̗󞍿횽񍨤뽴̈ں¥䦇暺װī +Ŝ׆jiqVvaorXV^d}sr|[ZZ[vov|y}z|}madlx|@FINOMKKJJMKGIKKLKJKMLOPPOPQRPKKMMML}ULR[WZcqtzBHu{wsyyxw~;CIJIKMQOQVXRORNKMJSJE{;6<>?DAPa{ZтxtXrnɵ]Zk功mxqtbSV\SZytjVPS4^bQKҖnprUt}~ehvt}fvŎ[\\W3T^\UX_Q@0!=B66;]4R:@Pbd6KPQjwļ۷ѱ灂zFn<:6]#_ZbKC>HZ^15K`[Npl]AcpY@gVB.;FB~wz@CFUfRVYOOPD@Huhpw}z{6"@D<7iscIVHUel{>Ch{yWADEFH5i=~~~}~}~~}~}~}}~}~}}~~~~~~~~~~~~}}}~~}~}~}}~"␵޻Ѡᕷͪ񧓎덤€ŰzbxE97;976798269b.49FQIFDERTTWT`VMHK~l}p"q>=4=xHKJ5@TlMCDZEQrC@BJJCDDF~~~~~~~~}~~}~}~~~}}~ }}}~~ ~~~~~}~~~}}~~}~ }}}~~}~~~}~~" ÷t[rF;89746:::6689=<<;8635:<9<;=;99:9>=;:::97lU>KLPIE^ih`ceWLZa]bgiqec]Z[Z*-.32358:855799=:>;;mgfgg128ww>V醍NjߠȔ侩ȫ۲晥ꔉ"£؂毳耎Yf|{qadgaadZj}qt]\ZYVu[Z[cpnryEAJNOLKMLMLOPNNNQQKIHMPQORQPPOMOPTSRTTTPL~XTXcagz|zkz{yz~uvuuFGEJIIOUVSRSVUTUPSQMrn};?AT{hUa`ksX|igorkmj^TYWJImU`jQPQMKK{lփObx"YXStay`lezjsvA9 (AK:8JZdl_ANDB7^\59/?4JK0EFvo61##AELA=AL++*,1b\TS[YM'.BDrG]sЍľȾž˾ֳ۹ѽꉩ|fJYw1y?48IDDI]U|ZNWHGe}Vs:GVelxb_NP7D7:GD~KC@BUEGQWc^UOIdXih~iNo_47=sfWwS;.R`}IHTp>jEBIJIP^PX~~~~~~~~~}}}~~~}~}~~~}}~~~}}}~~~}~~~~~~~~~}}~~}~~~}}}~~}~~"ĸ̶֪򇜑ੁꊒǾѵ{~v]vF977867<=<;76:<><<7636:9589:;989;=@959;588[AMROLOcec`bhZPW]\^bdfde^\Y_^2.30244;;7:;99<===;ndhim325syCWϟӷ챹ōҋŠȜƹż"؂ؖဥǫɣglv}lunRl`iYZQLdvwp}`b__^Z}^NAES`cwuJBGKNMLNNORRRQNMMNIIJMPQOQNNOSURQTUNQP`Y^`_jz{}|v}zz|~|xovJFIEEMS[WUZZTSSRRPJuv>@ATydnu|no{ǰpi{r}u۝}qhSWjzQEJM_o6RT\KHEbn׼ahRTWXXZk痠[lzy\vh{}xeX0=4@C1(7IdmT@/6]{}`WalmwņùƺDz׵ù񖩻jfE5GV9k{Hi^Fe\wGVUGH@?Enso]DF?^9.?|GKDE74L]^bcOKBKttTotEHuh1549rEQu`iQ^kvHPQQJJ@EKBCE[]2~~~~~}}~~}~~~~}~}~~~}}~ }}}~~~}}~~~~}}~~}}}~ ~}}}~~}}}~}~~~}}}~"ƛ㲷ۭߊ턈¸޲}w[x6686489=?@99:8::97879:769998<9;<;:61784::`EHSOIRdeaa`bZSS[[Zaiadc_fZein562/119;<<=;9=?@Ctjeplo66mt=DQ͜ۯ¹۹ުܱۘϦ۵;܇"冑ԬӛƼӁ\Ohþb^dXShcUtv}zzhgfdc^Z\mYV^gp{r>ELMKMMNOSRUSKLLJGJLNNOPRSQOSPOOOQOKPQKSR}^R^abkxx~}~ztv{rKJFDKPWWXXWSQTTRO{t{~@BJSqpy_~zWt°W^zqwkXqmbTIphWZJIJNZRzUysWZ"V[agvfpxؘkx^GV\fvrf]~anN^OSZB7k}sjah|tfM+ ,8&7S{~M6U*9y_gYKARt=97750-,ZTM=*AIK\^gjt}Žɸ㸸ŵŀ煎wE_yQLVF=amFN#H[L_YyrkYmjNJ6CRwTRqsdI?Z8RuCYVf?3aiUjMC>ABGFFBJJ;;>K,2~}~~~~}~}~}~~}}~}}}~}}~~~~}}~~~}~ ~}}}~~}}~~"ꌄڹψ쐗򃏣Ⱥȴ⺖}yazp777899=>>;?=8758698:;9;7;;;<9;<:7537:6;=ZAKOROTc+c^W^_ZWUY]W\b]a^_h\drgo52/.148?=<;79>@Aqmfo5suyBLj扖멍﯋Ŵ̛ŖгŽԲ좡罓ZTPbzotnhefŧbhulkwffcecd_]^r~INOONMLMNPWUOLKJFKLONLRQUROQOOPPQOKORNQNwWW[adjvw}nl{~}{JHHIMPSXUTSRTVTOyqz=@ABOYx[tԯYac^xywgqkTn\Pd|McMwxONEMIIk}UPT\"[X\exvࢭfra\\^WljP.)[cK%JZBCB=;>^EG3MMwlXD_VOr[=/ASmt992./12]SSTSSGN\ZX\Y/7~➷ǻž¾߸؀άہfL4~g]Gv`D"#(.BWK}6:7kI2kzMe^tmN`=6`vpEMthL{QEIE?JQWkcEKGL"I<52;>MEINoGEEEP0~~~~~}}~}~}~}}~~}}}~}}~~}~~~~}}~~~~}~~~~}}~~"Vߦん耉Ǹ·򄇇⯎ybn:9:<;:;@A85795569;8847<>85:=<859;?;>;X@OORX[ec]VZ_\TQ.0UMXTZ`W`]_mlk440/218tkgh5345u~Fޏ򇒠󼌙Hׅ̊뒕دծӳȂ߂QZQpUDhukosplimvrdzrueRPRWt¼bfcV`a_w|KNQTPLIIMPUXPNONIJMONLPOPQQMKNPQROPRVQPJsO[^bglst}wyB@leyz}JIIJLHOSWTTUQUXNzrs>?CDUrXXtق܈oY_em[ow|tr×qј՟PaZ[_lwYx^PKJTQK{~r[U"lj[WwDz‰yqZqjn^[(,'RI,'5]LcyDCCA>=CKE:umVBV|lqp^=MMfs$-E\wl..4ndW[gqujgiac\M'9ECÍĶ͵Ŀۀ惀î㸦}u-?緙SH5I"& 0IE^d?==9>|iTnqsaj[8s|NF@G@fao~~y[WSQSJAVjXYZ~"UL8.=u}u2'FWwg^wjcnOTcDAVPF3JBE/~~~~~}}~}~~}~}~}}~}~~~~}}}~~~~}~~~~}}}~~}~}}~~~~~}}}~~~~~"ֿϹɹԵþȢxfKo8:=??:878<=9436:6457<8768<=87:<;87:9;;>7Z?JONP`jgWWY\[TW12NNMMQVT\[Yaqso6122188@B?><=D>wic24457:@Jܪ󂷿ߔ飇ʒф㉫娬Ÿ"⁞“ؿTYPKZj~|a{хlmquqljfgeiw??LnzRwDKPURKGELRTQNNNQMLNNONQPNPQNKKOOOPQPRPOHpJTabanww|}vDAms{uyJIMLFMLSUV[WU\Kyr9>@CEIRZ{oKnxji_Wj[bbxoytfoĔwsz_fxnne[^ͲTEWPGLYfyxVYڙva~vŲ͞my_z)+&D(331(+0T|DA?A@>AEECEJpxpTdX/BA6/# Qfdlރ†µů؀熆êɷ֩PL=``ubN;:7.!'DLlKd<@>;Ib[ZҎawyT?fxFJE?JT`[˟^MNE@CNqP{"fTBe5ܞ=/K>U.Ec|g[gA`WW.A<@S~~~~}~}~~}~}}}~}}~~~}}~}~~~~~~~}~~~}~~~~~}}~~~}~~~"ÈƦпߪy|~xj{7:7<>:758<95535:;667:8899;<:8;9:;96877;4WAFNNNYecUUVW]\Y12RPLRTZTZYYXgo<87733669=B@BAA{sd/34556;AHѿÖח뙩͐ުڜ뱾֩ԝ"̼짢ϜǧVXROY]PX}nmllopiefimmkhXidw~;=FQQVjzlp{{oc\pw}k}AKLOMIHHMSQNMLLOPMMMOOPOKNPMILNPOONONMMGnRT_``kx}HE|~|x|RNNRLIKJLOUYa\U|v:=@B@BNUVj\ؕ\][_luu[xϫۚS{g|g]qWMg@MWUJNޅ"ruք{Éqsrp̉]xbo)*$:$.9.'i`EA@?==>BECEEBCoPV|mA"6'$) +"&6WkqjdfnmO;=AF/*UNW݈İʳۀҸҽ񃍥EAKnavee`PbJ!LSvEpGG;g[nqȷmkG3mZvIMB?=IdLTwxbbDEFMAFx^w"TFwY`ut^p86A>K/>mO?Nu]bvdV$47965678:99869:;::987858:9597363W?BNTPT]cZZ\\a`Y--UNVW\aZXV]Zb2==:845545:>>A@>A834;GjKlIKFC@<8;~??@DDC<48|gBF-38 + + +%+%2EWp{y]JB=>(-W.+Ļÿž׷ŀ뿸w5>ltOOsPgjRu_zN'@NKUhdGIwuwe~dFUUIY_NXCG=654?Ncm~nTj=B@SBETL~O"NPGubmjPMD>75CF6LMt`aClo<88AO~~~~~~~~~~}~}~~}~}}~}}~~~~~~ ~~~}~~~~~~~~~}}~~}~}}~~~~~~"ױۮȐޕ慒譊ꄁdkqh/26;<974:;965688:=<<:7:887886535:9888554T<@KPITch]^abdaY,)RSYXSZ[XO[\119=87645309;=>?=rggjrnliejGE꓾ɯݦɁˣջ١镈𻼾؁ޡܙ"ٺⷰ򣸁ʫRVVggcfsXoyync[_inorroni̺f]\Lwtuu{{uuBUqrvOS`~CIKLKKNMPNMMKIKKNPNNONOMNNNKILLLMONMLKJDjRUZ]Yix~|FDxyFGNQNOPMLJHMOSUVPtvyy_eXiorrU{t鞗砀w{{l^IIzt}_oVZ[XUTwT"|nٯkјqlh`]X`{gy_ #%KMG8I8458;9;2vNHEGB2Ual9>CA;:?;679778754359:;88864S>AJLFO^d[Zddlh],,`Z^cQRVVRT`124978:84527=8=A=pmmkuxplopLJɋȩފЪʦʇ´Ϭ􇆫εĽ"ċѵЩߋRU[e_f_siWrudop]iotvsookϴvr{b|}@<:=Pqvy|kv}~DHIKKMNNOMLLIIKKNONNMOQRNOPMMOLLNOOKLLIEr\Z^^W\n{|DF|}z|DFGLLPUSLMLOTQSUOv~|yekqԬdƖȬcսq\SwinġënuN^}ZNxs{y̓JZ_"|ż䤫ojtJģn~qV:#'RWYF%97;@?8+3C??AEmRI<=Z:BB<:A@ACs@:We@%!! + =izwwyumdTJZZxcU|׉¾ÿῷÂjHoWK?f~{hAWzIw[>?F2aF|z}{bl9Zt9"=h|?K6;;8.OTVl~wzQ4[e;["euV~3ceF./$bovH0`?*Bh~~~}~}~~}~} ~~~~~~~~~~~}~~}}~~~ ~~~~~"܃‘ݳی޳󂈊ͷʹǼnhx-17::7259:96469:=??=97599676324788;::953ZHAJGIM`bVV1akj^0/d]^`WURWWT\[23778=979<9=:>B>ulk79>qruWW¬ńʣߺؖᪧ¿̮⦴Ȧ}"̷ՋꜙܽͅဈRWXjk{gd^_\zluhZv\cccknmkegSF>z??=wzS|û}@FIJKKJMNKHHHILNNPOKJOQQNNONMNMMNOOMNMJG|bW_^YSl|{FDG|}}{EFJMQXUQRVRUWWXOpz?@EgnЉiqΡzs݈X`q~ʮ|afhnx[zÒwrmO\}"zqϳɃwx\X֡sQs1$'QRQ;!A?BGGE937n99oG/=5(@d|;7=?q@CFz~gLmkJ5ShyCIO3:>32G`Zu^h{owtmvpi77:sw}mvŒϊšβүˇ”鎑ӻѽמ"Ÿد؀ʭYSYTqGnhtilvTg}doomiiju_L@@CLRZngʲα}CGHLMNRNJKLNOQOMKNNMOQMLLMMNNPPOMMKJfX`bZRny|EH~~?CF~~~|AFMLOSSRTXW_f]^Ovw?>@r~~si݊l^t˶{twuEfywu^đaXJGX~v}w[i{^b/%)+SK+HNIBxYp}EGgEG>12=<5Vqgerxu{{{]J=0Rce"zd[WHA:Mafkvmf::6Svl{e^62JK1*>N~~~}}~}~~}~~~}~}}}~~~}~~~~~~~~~~~~~~~"k€ԯǐʠ麭ʸ֮ypyؤX\38;<<8776886576<:98724.K@DGHDS`d]+.`caZ3100.-X`^e\\1.1523996:;@CA=?=rpl537v@=C؂Ӏλ՜э֯3睜ƚĨ̌ʎ¾Ѷ΀T[zT`xtYxpTV^]hsrpnhxeTEKg}cΈ]ѻ|GILPSUQLNOKIJLPSPPLLNMPQNKKMKLKONNMMKKGiT]c[QgsHK}~JGBCFF}zzB@ELLMOOOTV\db\[Oy|?=>HGNցg֚jfwxrne`aޮ`{]ؤͶpbk^irϖ|d_Qf"]u\zzstifo~~{g2%(UQ="@L0_D>Rs]RK\A9JpaNHG1"%-NoBB@AEzfahjW:-*,PP(hThur/Pfvxpqx{ոļ¿ǮƀꐑӀ胃ف󆏋vR\qG‰BDf_nb::[wW:57< ?_xkO6QWf9^ѕ_R=:.'?Lhsxrdxx`]YXLeg"{a9-9_X^=`K9@>J786c|]]>DKC/5>F~~~~~~}}}~}~~}~}~}}}~~~}~~~}~~~~~}~~~~ ~~}}}~~~"ɏϴŹղ같ZS368=;njnl49>F@Eoυ䶜㷯ȂᘈꖭܨǼǻߙ"٭ӉڋÀTZe\Zii[p~n\sv]V[\wtsppmĩ}natnhky~eˤtJKLOPONMPRNJJMOPOQMMOPQONLLKKMMMNNMKGKLtX]b^Var~IJFzyGGEHJF~D@>IONMNNVX^[YVVMo|@DGLIV}ᇧ{jÿz_`{qxLNz`g{̲oXZT~mÈr"ȷa\Z]yrrhPlzrxݭz{e|1$&MC33Q$6Bn2CUHP4M4-6HhYLPI'0.7gBEC@@xrh\cQ/>PS3.4IWYS:9Ypmk:z~ǽ׺㕧ƿµﺷĀ搗ݶֺj}f\|CrpzuI4nquB,4:<K|bXpK=gFPnvqid¹[<.$8?SJg\_MerkNdƗ`Ru"`9*:4eAJr\JK><:-HaXojR_^G1>5;~~~~~~~~}}}~}~~~}}}~}~}~~~~~~~}~~}}~~~~~~~}~~~~"ցᄏΊżþśsWQ077=A=;86886777:<:897:778775798799730041PGJNMTbb_/--\XQ]04730^[XTYa2-*3127744:<<@=<<8e88ov}CCK~}₍䁇Ìϱũߜō٠ܘǎ"ם񥽠ֻkUXhtrtrzz_fuuRRYZcfskvpmrmȸciʻ{h]VaiXK`iΎj~INLOPLMOPQOLMNNOPOPROQPQPMNROOOLJKKI=VP[__etzGIHvqEFJIG~}EB@ILLJHLT]YWVRTOCk>AKK[iw킜jkkjkajUepmxztlYnts_ƴs^[VPj3ubhrw~xs]^bvwvqneh[Viȴr{i?)*XE%/@??EK{wTPVUC3**1:.4`xv<|ƩȮ吟ĻºÀ¾؆ٯn^ڸRV]|~?;m{Ohd`J9F52 ~~~~~}}}~}~~~}~}~}~~}}}~~~~~~~}~~}}}~~~~~~~~~"\蹩޺μغ߮ꐑշyuo\V068;<99::97:<:977:877:;8688899643386SGKMHIZc_^,/^[Q\24:6f`ZYT_a2/12367753;=;>>@=9j6:s{=>BO}Յ􋗟怌ۙل͒ʙA㼥á̃󙟂ΝҀ[`l^cdcRSx\bnqi`hjlnmjdjOBnko}GJJMLKLRTRMKKKLNPNQQOQPPOQSQMNQOOOLLNNOCYP[^[\o|FH{qGGMJ|wDDHIJJGGMR[YVURUUHt>FEJM]rjnpmmppasTh^nyg~scͫZ\\UWVuېeپfg"h]RpxիvMu›qs}oê46]_WUiXA>DMK<) %N|\%1[`&%SJF?Ya(+9MmA=7{~@EGJHCArpws_OF1BJ#&33VruŸꐜļþȽŀڳ򚚓񄔕aiy^^id{<;>=<:<:n7:*?KOAF?c14@{s{UXJ=lulx~vfL(A.:~~~~~~}}}~}~~}~}~}~~}}~~~ }~~~}}}~~~~ +~~~~~~"乶ɴ专٣퉈넋έ󁋣voZ\[468:888:<<879899::878:965;@<27;;888769>:XKKLGI!Yg^002a]Z^127kea.\Zc]0/31255779AA?}>8ij><>>@7ڭ̗䀍‘ÿӱՏثڬ͞šϹز񫩨ǥ⒅fjYiX^RiZRQd[finoqqmliƹ\jkQe||\xwGHJLJJLRUTMKLJJJMOQPOPPMLRZUKNSQOOMLKPUJgV[[TWj{HHH|vHJMC~uEFIGIJFINS[\WVTTTFx|IGGFLMəiojpsrnsyungaupǢ`h˶DžycXUett˙irzpsrl"x[vcxhogғjeu؏s@?-QOTk3,MK^aJ>=BfHLp4'%XTPB@=>B?>S-DWd7MbF^JKcdhM;H9QxwTioclT<:@HF@A:>nsAMEG?GD?"@30GJ3SJ977968;=:6:?;46:<<88777=;YKNMMR`fa02eeb`_/03lc/./1_.//0/-/37:tٮڤο孑ʛ£¶"腑귴Ρȱi`wjZ}lr`_TRsr_|epeospmieczq^xc]eTn{GHJKJKMORQLJJJLKLORNNQSPMRXRMOTRQNKIHKRMiTYWX`hs}FK{vHJMFDAADDFGEGHHKPS[[\VTROFp:DFIQomc_VoxqsvzmmwveYqyԔjjyr`ttzrux{{wyvxxq"_\i`wixhrҝխozjxfD\@1B@C,+H9RPUbny`}\b:<>L~Y~KHFADCB"c(@V.4.[?/)96DawyuoE7\lTE>Jie=~~~~}~}~~}~~~}}~}~}~}}~~}~~~}~~~~~~"ׯЂĻƩӣ楨ꐻիŀ닕ͷiyaXYk79;9659==:68;<96597::;855646569<97567;8]LLMORaid210fh_]10230/3140.0.2//05;B@DDB?A;;8bd:<@[pꏜˆ눎⸲շֵހ䭍ꈴլ̭"꩓͹^mmZ}hReczk|XzioonnkkiĽflc߰^nfkJLMKKJKONLILMOLOQRPMMNLNOPQPMJGIKSOqUUQVbjs|GJEwJIKKGGHEHGECFIHIIKOVTX_\TTROGnpBFN|žSZN{zxxmuyxlZkU`Xdseu}?ezgzhViyzywzryq_]ZqXd]fd|svmqB'2IV5aD=NOQsytx{eU_a`.`n]X-*APbKx}F>239g}ADEDCDEB{usm`fzj9ELBFѼ屪나¼ûȼüú¾㽱΄񅉐٩FO?m`gZXSpqZ9LgCJKH?.M=B"~S:>.+(TD,-"*1KOqwqY`o_4@QLHOdij\<~~~~~}~}~~~}~}}~ +~}}}~~}~~~~}~~~~~"Ų׿ġͯ۲ڇˮ}baRS[f68;9555:;99;<<64789:89943345434964346;>fMLROWgrc210d_Q^24/21/553100.132258DCAD=>>xzSHUOHpn涤냗üöûŽӄл凋T]aHuJdKQSfRJBHU[@=<4D@QQD)I@Zfu{xnjI_~mONVbTxfTggv}WKI65\vFRQAuNED9LHH=7477898:9775568506314466oOQYSP_eXX,/d`X`jck32101/1/1139;9>@C@@@7;;A=qmry=FGKIĭ򑵽Չн܎̱͚聲ڔ딟ڞ"ڲϝ҇̓Ҁg\lufhgǑzY^}\mĉubsuqjiotrqrokãÿgXifjMMQURLJNPNMOPPMNMMOOPRQONMMNMMPNNMLMNRVWifalz~EHryHGHIGEFEFGIKLNRRUV[VKLPXRxxQe^X`Ԯjwr`m[WTNcjio\q]N]Rdmtĥ^ppʰziW\syc]n_^]uscrr"oolei|h{v~xmgmXN4VX<5HE5@8|x<=hAF0:h9=yqVUV#0F\kRXkl@J[rADAACBA?@BDEC|lctvձг۾򗠊WxWU?ZI1H5#%-cR,ED?@=A4;@-!3=z@4/W\0RY8r@}ztVJT4QQ>:Kb?@=w,EjHC>z;1=TBRZ8039@A8g}{>GJNV訏踮íԗߜǯƉ禐砳ؕ􄘐띤"ߺȖ̖ժhomzi|sYdhĐ}[dhhgbmСo܈fomigluutrlgfjkgǿ=ksbd{MQWUPMOPLJNRROMLLMNQSNPONLLMOOMMLKKLObWee[n~FGEqnwJGHIFEGFHLMKNSVUWXUMCFOXUFyyN``anqyΗ|ilgbXyX\\]x~iobiYtrktqguvTwuweW`]]m\lzs{fq"sjhio~|rbwn@DRL*"?@151x>?hB,/[5>BDwA/:.RevEBFo_dS[uCB@A@ABCDCA?p_f<>@@k𙯷À󌒋ܹĻ䃏aHMeF/EtK9.)5'0.7kGG?2N=lFRG17/6,\L;YihiendX_C.Ac~~}~~~~~}~}~~~}~}}}~~~~}}~~~~~~}~}~~~~}~~~~}~~}}~~"ꫝʔǁڄ۲񧼞툛퐐꿣ԫJGrWYrqtw89=;885106<>8688778998;74368:75432525jcV[VJR^_]2-1c_VQZff326//1468:99=?B=@=834lg?FEB>@ADDi皙ŧ̹Ԑdzĵŀ߈~IAmZ?PYSG8J7DJ-7@_lUP;xxfYQV>M{q:;34Y5tDL?0\S8@[@Iw`FVIEwrwu9:=BCEdAh;"EtWMD>@>>=849;CC@rR1>KQZvƠӬڳØӡﮋγֱ۸՛쓹ೱὼ削Ώ"Ȣ巒j}ZssnqeiinkY]flqsp_eptsfc[uyiflolg`opmllmǛnmWUoXmHINQRPOQOLLKNTNLOQMNOPNLMMKIGNRNNMKIJJKy\[`U_~EBCBxnIIHEEHKLLMNQWVXW[WQPUUb`^fAS^]lfƟ~z~čӣ˽~lz~m{}fibxpzƜyUfkhoi"vhaɒifirj}/>Ub/L11HHITEh>>DyH:HM-8CHGFB6;bdGIFt964hsiBNqGFl]jDDA>;hZtCBBEEC|[v٨ετƾĺ̶žմذbAlkY_DG}`raHOd~vY~{vsTWDBlcH7;;8>@3H_?1RL>PL_DGGigob!D|rgdj73=?KM?Q"nTNQL@YFChkQL`fe4;x~ocA=?hRKRMm~~~~~~~~}~~}}}~~}}~}~}~~~~~~~~}~~"킂۬ܩӅދͼ̝묡ڋUL[X3228<:887521334:98;;989;96663358::765768:scXXUFGU]^0../_`V_hbZ.+0226567:;==???<98>>AEH_R7BLUcĐџ޽Ϸ讽ޮ׺ٲ쌤Ҽ"úϸԀ􃉊ޞﲡԀZW|}kmjjŗlr[_chmqp|zqxuhcfwgefhecmqonoi]VXTv[pHKJMRSQOMMKIJIJONMNONOQTROPPLGFLPPMNMLIHJsZYa[dvCDED{rIEEEFKJIKOQTVZ]`\VX^W\ciydIVbdwφ͜g{ñz샶}otygq_vhf{lSBhrXcly"nXqq^cbccŵniia|?OS_gNsYB@Bu>:X/2;EJJHH;:XXDHF@ZK9gsb?JdDEqlBB@@zhSeBCDCA@uQiŰݗƿýžԾºַ񓖝ûcocltLWKYX\Yllx|y[\In2-7GAA275njME>OYMXE5Q;=R< C}|}db`2/3:7;_J"VOMMOA2]2DFu2G].39>~}~_oE@B=tolQZ~~~~~~~ ~}~~}}}~}~}~~~~~~~~~~~}~~~"߃ߴڇ揘Q܍㱢͘[CAxYV123798898656597686<:9::;:6898587636657pf^XQGNU_a1-.1_]Z`dc[.-255633678:=?@A=:>@?>AFIKDILXb|եɚҠȖݦգ/רǩ򝳾೚ۈـYiokeho\chlnpn^U`r|vqntbżgdbaprnpiPIGsZmFIHLPQPPMNMJHJIKMKNMMQSTRNPQOJJLOOLMLLIHKq][eekvFFHG}tsJEEEGJJIMNOSWZZa^Ya[UVW_]YS\aku˟`~tioȮnyr^uravu_PTRPCmg_\kw}"|pzwzr[dhlf|nijcgT1RLRQA>NXfjS{XECCv:6W16>FJJE=K7+XCHEEwBGpxtMMlFBCFlSXrCEFDBEmĔþŹ񆓗ֿ惎q}cdqEeTXo]SOc|mLJ`2-<:C=6>MrbI=JUWC,1 ! "A;ruwrjw;;3358aN"Y\XPFCBI;@A@53>H-3:ExpCNFCE<>}}YI~~~~~}}}~}~}~~~~~~~}~"ကňŀ񹔞ޣীݯ؃ΨkZeVS44599::<98:75866559:9<>=:67:<987877656313nh[WPJQR]//-/ghb]abc/.0023324388;@A@?=;?@b,!F?GGKW$0tras~}CCEtc[nDEDFKy§Ӎƿ񀆘݂ez}u_Cjavafwv~arZl}nhw|xsS&1.,?C10TcwUAHV0VH&$39;0-2ojrxvHC<-'=DY;"PbdTDFSXEB3-0(",C+8==:o:ABDE<<>AiV}~}~}~~~~~~}~"ֻ̔涔琖שѿիŽٹ`O]VW25588:;><B<878<<:8676536610/hcUQWYXX[//10ddc]_`e0./.121044<:=?@>;:=AA=BEAAFGHIOSIѹ̲ƲԼݍџԻѤ"޲쏜Wdd¹fnYcknmnpl~Rs_xwhxWdçmofpzku^Y`[e?FJMMPORTVWSNKHLLLMPSQRQQPPTSPPOPOMIMMHJH}e]giio|CEIGz~HHFGHHHGIILKRVYUQNQWVQW\_`c^^bgc[ȤugɤŶllèrqbwboaaeĥhV~wcalilNE"d}^g~weoqg]`hlhnrrjhggW-NHNWSH>BSZTIU{h>?S,6@EIFAAvJ)C68CFGo3)tsphTPkAEFsb\o{zSzҀƺõ̀󂃔𔚓yO]\|rkD>oU^r}|suEw_gZAxqvmeXG@6*8@-%99haMLV85fPI"$G>7::9gnnr{LJ2"9=U%"8YcVEGU\QM<#") *D379:;<=@DHC?>B>;~~}~}~}~}~~~~~~}~}~~"섃ꂃޣ㶗򑕗뫜鏕պʦ҈UMYW\a34677:>>>=:788:9579;>?;989<=987441/3311bf]KK[d_]``a23_\`Z``f30../21269?=>?CABFJA=EHIQQYXXحǰŐБӶ󁅁շμ슉ϨλգՅ"ΦVSUXYXqdǻjtYalpnnqnhzmihW}Ngкgyog{{uxɜKRPW^\euCJMLLMQUVTSPNLNNLNQRPNORQRUUQONMOLHKIGJ~hZbiiq}GGz~~IIFGIIHHJKKQSUT\WUYb_blgdniprunv{~peø{tmh^f`~dijc[`Qh}Yr]Yu]vfx[gjddjdgppoljmS,(P-1^NEL./VDFi~{c?CV,7AFIFA?=lTFJh=?>]%24rzT+ Ez{DCm^djcZkzR䔱̀얢`xCDNSjD=}j]pzuj}}qNLg`CBusvk^-%!:?R9'.:@ZXFCc:632KF3!9DA>5pcrw@D+!54YK7"!T[W@EPURQT?$2-('6L-6>@=ACGF@?BAB~~~}~}~~}~}~~~~}~}~"݃ꁊŅ갱䍒яã¤dLT]WUY148978;?@;76::9;8867;<<::;=>;9755224321cgXIL`g^[_]`/0a_\\^Zc61./1231479zvn9;>@ABCCHIC?CHNfffelȱʯõԇթ麦ŢͶ޹"ҿ劕TVVSYY_[vdjʹqXakomprrqiidfnvmqL[lͳiˀ~CK]b_dq?HMQWYRPOPNNNKNONNMOQSTVVROLLNMLMJHI{bXgkbn{DF~~}zJIFFGHHHJMLNNU]_`[WZ`bfjkorw|~oiefvyofuxcJopѫnu\phde[fw_Ww{ctxXijfegegmlrsmoP,(([1XMP,0/RBDb<>sjP@w{Q/p}CohrmWp}nyvˆ˸€پ߉P_QFJfF?w<QEMB"P]X>FPSRP^_:;4$%3@H4BEB>=?CED@@?C ~~~~}~}~~}~}}}~~~~~~}~}~~~"߆􉏐װ󍗚Ô̯ㄜׂoF\aSRU_57667:?>;88;:8877417:9::8=?=7765543221`f]KH[cZZZ]]//0deb[T`g001334226;>vq58=>?AEDHGDADFRtwz{냊ɤȭމٿЛڑ۪򆒑ʞ㽁"톇ӱ܅WT`dcainke}Wbkppqsqon[rqlmyx̾u@Fy̸rr{va\I^f[`kvBGLNMPYYTQOOONKHJKJMMLQTTVWTLLMOOMLJIK{e[npdp~EFF}zIIGGHHHIMQRJMSX_^\X]cfedgrzy{䛥omkflnmμnvipgsgSmxwixpZab_ioUkR\o"m_YXTtiwVfmmilmnmjoormR.(KSQ@CS-/14_as?ABDj?>@;VVp>?{hMErV Vt{yo]cUTd]çûÀἱRljX]tDGF@B<:}uYkwmDNnt?EAwph;>9H0B%CK^;<\P-)NS87][/30.NB0;6A=5]]WR\\_V3Nj"4ai0FORVSM.-'G9#-EZ6BGF><B@ ~~~~~~}~}~~~}~}}~~~~}~~~~~~}~~~"뇁յٍŗÔֳиֆ͑IUWQPWc77987:?@=<79:865443798889>>=77556431//ahdRJR]XY\1/0/._g^W\`d002676638>@;<9;:<>AFFIGDFHTm}䃈Ϯ텢ޮ뢄ÜҤζ胎ʪߴ"Κ里ūWSxg^beeZ_immkVbkpssqqmeqzwa|źYyB:e¾l]_tQY[WZao?CKOLOVXTPJLQSMGGKKLLJORTWVSLLMPQLJIGJ{idqviqFEGHFGGFIINTTNNPRQTXZYW[^_X[sy}alsqlnrql÷stholb[MjrulW`ed_d[YjghZǓmon^UWZYVinb fjnorssjiecjjZ2-RL8+=P+,297o>CEEAlJ@L.5;>@A?BB=_Hc?=>>RC=R|m.+1\qmW{,+Rn莶άº÷ǀ׼焑ӅHVg`29?GJGE?>y|whoo\nzLgx?CA;jmVd0MN5lwnxmfM60RF-T[27510`OY?,:1S@P\_2j@2T"b@?<0-TURL*)))G6!Ip>EF=8DA~~~~~~}~}~}~}~~~~~~}~~~~~}~}~"cʢ򁍕Џʑ˜«cg_RUQRUe6885:@B@<9;:987676667;::97565991*/mumYPX`]]`100../]_XW_^,158:795:>@>w?;87==CBFEHJN[lv˿ϊڃɼ;˫ٸԞ䄉ѹĀ[Vic\ac`]]fmnophUZdimpsrpk^pxtax}J;Tylgiruy[phUVVU^xAHLKPWXSLGMTYRJGKLKKHLPQVSPNLLOPQPJEJ~kftylpEFHHFF}BCFFFDIKPTSNUROMTUWUWZ]Yay򋬣hrrdiktpķn}fmlϸÿ__{Ÿseb\beefQ\`]\o"^ttoeVUXXT}s\ojkjqsrpi``62WJ01GT,-2668>DHFB9WJ*28;=?@ADB5N9UA?>=PB9KxjX/9ZuJLkZvK47d`ȉ޽ȩ̻ſ퍞ҿte]_aTY37CDB>ABzy~tqRZu}@ECwepo3!0[A{[4?N&B[575/4[UMYQ==Q`6-59N<*l"FEED=1TOPH&&)*WH,'H6CEDAFF@=@=U^jx~~~~~}~}~}~}~}~ ~~~~~~~}}}~"ԪԆأ߆ټŢnBiSTUQOc6766:>?A?=<<<98::648775369==9675=?602iukYRYd^Z/012.03``XU[,.1479:;76<=rk;;76;=@AEHEIQ]YÐå􄕗ɫ兇䗉ͿŁض"င˿鎖򽵱]X{|X^ab_]_glnnojZTW_ehkosspj`ihul\Yzoh^zOCNZj~mjur`^f[GtTPUT[v>DINSVWUNJNW]QHILLMOKMPNQTSPMKNPUXQLL}j`iqdh?EFIGGHzDEEFHHFJLOPONRNLQTUUXa\_hjyՕ«a`]tym|sveioϪz|ξsmxUbXfhg`cRvosmhi"qrrojX~SVzkmkglljgea4/SK;BR.121/16<@CC@9/(,5=>>?BDC@5=AAEC@U%;@H@6O*=Zl|FD|mhbd9#*F荠˵ɿý憖փތfymjdTU10rph3=C?w||idysksv9=GhY18Fvya/+A"&P02/,8OF6MX$3>E52>\>""H@@@?5PGJD$'YQ]TE2;]BGEAEGA<@zGRCV ~~}~}~}~}}~~~~}~}~~"׹쏗߰䀊߹{RlWTZX\hot69>===>>;==99==846756479<;9755?FH@94neYQ[e_W/222./5fc[\_,001589<8j<:643;898=>AFIHIzmޗپ膈麴ǒֺ۲݂̾ǕՁӵ"ҵ嶷􌔌ƾ΀b_beccb^\agmkidWSV^dgjnrstnbswiw[mph\rIAJNvjme}իPYWOz\U\\ftzHRWUUSONNV\PHKONOPMLPOQTTQOKLNWhpcRFzk\dkbf@HGHDEHCGGFILIKLKMIGGPUUWZ`^g疲ƶ][fst_vqſnrrtpidfͿl^V^j\p\{yuҿWx|sqqpoY~}jjacfg`aŢc71QKHMW26760.28;=?@=1+/7=<=AFGC>6A+I~IExHM,P{IHB7L# VNjB?9mdbh̿o Jm򅔔ȳõӀ񍩷f|{qic00USRUT4<:@8QHC>HR\OSVMD;TwFF@AB@>@shR\~~~~}}~~~}~}~}~}~}~~~~~~~~~"ַE|saTWY_gqvo@B?}y=><<>:7:@>33889789879:77@Gc`K?n^WV]fc[0532..1gjece0./349:;8j><:8257:8=AAFHHJkܼ썊ܡ礴ɽǼ̤څ阍חȄ"Ŷٵ򌋈mlqzplneYY_hhe`UQTZbhknpruob|yklimmg[qv@˙hmd~ڜ\\f}Hp^_emvW\UOPQTXPJOTVONNNQQRSSQPOJJTcmSujbenmr@KHFDDFDDGIHLLOPLORRIJKPPU[[^^bgx¼c_ooU_h]dpnvxurgiZju^oKwS|fɜtXh"onopo^c_`bd]^ifcflB=VBFQ]5,-4;?C?1*.6:9>8_[UURsDxD7]zPI%NvH/)2?^8GH`epD/rG"DA=?@@EI;;~~~~~}~}~~~}}~}~}~}~~~~"؉ᤢ偌⚜파ߧãᛣv~4oq3.[afjmloADzsp==>B=6?><4/6;:=@CGGGL}|팏Є应Ӽݔɹޭ۩׼"ʽŹ셊djmp\_hcWQOPU\cinqrqmc}vvqVt{okjja`Jsypd~VM`x=C6cszX_PQUXOJS`fQLPQQQRSSSQOIJMQ]enjTkcdokj@JLFEGFDDFJIJNOPMOSYYNEGMQT[``afbȨfl}wUYWWPS}Š_]vr^bvntxtj`νcγ}~RBOevqΙWb"hfmjjeZa`c^_ehhlleA;J/;SgMohgzW'8V:?>7,%$-78;AB>=8.K>CiDF:DcIFDD=dB2FnZx?6bikz}AoЅƻųĀzk_goyv=@sG7CUW+,-S$"'Agix:&=GK>FRqDB?:89u_:7?FE<47;=;=?967:$1MhVyano'0Ru>;1(&)1899>?;:6-%;;X>D9HCe?GEB@<4S< #;e]E9dnnovgsöЬꊜezm_hisy5RELa[LXZTRIT_ae:ADE@@DDG? ~~}}}~~}~}~}}}~}~}~~~~~"̢쐃ݔ孇ڼԑ}06638Kjlafflvsqv@E?BEA??<;<54:=9<<7799<|:5464:HlXRcd_285kjie0c`^[,*0/235988;R48VVGPMNRSbqemm=><8;@BJw~~~~~}}~~}~}~}~~~}~}~~~~~ ~~~ ~~}~~"ȦɘҼؒm_Dhv96:Q|n]ccftpk;E`RBBCGN?8856:::<:99;8;=942115txp\R`cZ244njf.,^^`WV*0/0237:8;CCA?=8<===CCGLdؽ焏罀ە򶂷Ο"̾`uRRQPbaXRQYi|Ybmkhe_ZTptmnppoheb^tltkuȐPkvl`NMPiP[|p\Z^m{`OLLNPOQTRSQPMMNMNNNMIwrc[hmsAGJCBBEFJLKLLJOXbb[VQTSSTXYaeu餾gkh]bnpp`tyor`yps~SUpʲ]~PfZYanxuog^~_mokj`º`"cjlpqplkosfQPt6\|j<;<U"$*FKL~t}u\|E?üŀ߆nklwqqtu|=;82g9@CCtg8?BBlS5a~MLCjRU"3WRrk\O0mGE[&`nE KIKEDGD{K6Jh<*_H8:>|gtv}D"BDEF>9=@Hh+BO&(MJSS`wsskmrha`oye~~~~ +~~~}}}}~}~}~~~}}}~~}~ ~~~~~~~~~"؁åւ֢ڵl@o\aky<:CCB?=8;>=>@CHP_Ȕ腓٦äًɶО؈ЪՕ"Դװsv[\PSSTW^f^TYfzYamlgc_WSNb`qsnnqsoggn{JrxUƯYG{`gjPPRdpzR^|a][oxTLMOPOOOPRPPRPNKGvlof^jot>FKLDDDDFKJHKLMSY^a^XSUWUV[\bgugqqifksrd{yvq~gͻdgvoY_ldsfvv\yzrkazomqkb*dmkopspi~cdrQPvtB`<5FEWUJFH##%+3jBF[EGIN;XxBF/g=.-@N?K_bSN.AEF}{"CDBA;=AD[ED34L%)UMWO]xyzww|{ohivfN~~~~}}~}~}~}~~~}~~~~~"ꅍ烁…gEjp<8u~=ACHU_xȀֈᆉܹ㕺ᶓǸŹޔ¾"ԾåаZ]TTSSUXXX`ikZ]q|hZbkkfb]y^WO_V[hooppruoffnhPpvH_doۼmFyYU~NYppa{YhojURWKNPTQONNNOQSPNOPMMJHtvqktuu>EIKGFIF|FIKIJLOSX[]ZWUOYc]bhqwohƺhpsoqttiPwqq{WwaXhq[Sbfysm|wrm`nrsnfa̼"cmnnkrwjzoSvo{ia^L`74'ISU))***+/6=BBAAB@?@@@?X#$#-eFMtۢë맭ȸƼ򗻹À慠ኟ@gE>st=<@@BDGEx^I"9yJMrO;=KC:;CB.45?KOP9]+)GkS3 *)-&gmHD>?AAnKFOMZjJEDEHE"BDB@>DIGSFcdGU)WROZ\fEA>rvdM ~~~}~}~}}}~}~}~}}}~~~~~~~"򊑋ꉍ܎|bimpBDHHFI|ILKJKKPWX[_VTRM^h^ago°kmjgliffhlh]etbyirǑ[^sQ|dcajuul^aUcrfzpxur_fsqmgikjkŤ"aknmlkqjhjxryǽggfL`44,'M'++*,//1:?8Q5:]2.7=934<<96-E1+)I7:@EDCEGD>=<=>?=<<@IG?_GLO?OZyEIs>>.1GOqB/*-+39BR/&9e1+&Y/4/@uq@A<9:aCDMGEP\gqyzdAvc`4878<;9798;<9;;86468gafhdaci75856017ksm_Z2467789v@>BB<<==ACBCKRbvդ􈎑ǂ쑲ȡلڤ재º扡䰾"®󋙜yYZSXXVY[Y^a^d^\dotmjjfaX~crTlmonoqrsvshed]pptcP]?OSYaW`f斌hOKOOPQOONPPSTOOPQPMLIuvxpnu{?AFFIEEH}GIIIKKLVUWWQPRPRcd_chq˗glmniYudvffk[]O]QnYZyVhnpgqfmjis{wwr]^pqrknopsm"\gmppijihddhbxxilmemIX00,SQS-*',002;?oU=Ah8.2@NO?6775.K@FB(/-:;:>vGDD4BKb@HDocnΦƸҌȻ񳩸甄߀끈々чyjh~>>@A=5dja5LSEQ=4MeCW-'4djN<($(394(d9DRWkBJEA@JJEGI}ih">CCBDCGIC:9?{>`JGN^u}wFC@;wO~~~}}}~} +~~~~~}}}~}~}~}~}}~~~~~~~}}~"͵چǶ؆ΒN<:@9?LKkawnfsyKǒM?nd5978:9:86:68>>CCDCDJMYۮ뇘۔딢۞Աܖ㽼倕"ᴬ⊝䀀n|XUTVVVXW^Xdcc^W`llpomoomlnohb__]y_mp|ԥ[>7GJKS\n~^ˏbQNQNNNOQPPSRRPONPPONJE|{~vt{{GHKIDEEIIIKNWURKKPRWXceedir뗯eqpm^R]k|ho\VIY`g`WbWZ[w_^rbfyxpθxss_Vkgsmkjrut]"WekqoiiifbcijhjŴcmoiZfDBKVNMX^.)&)--.1cYQNVj4*Qz|c/+34/G=VR%(Z:>ACB?BD@?<8:wT)+Vmb:BIlaoqóӄׂ솥}|DŽрᆈ􈠹㻚fme9A@?:dbwwV!5NISDXD`hjX 0.9Y??=IMC"-DIABK=\U":AABCBEIB=>BBAFwdTTSSWVsx|>??60pL~~~}~~~~~}}~}~}~}~}~~~~~~ +~~~}~"¦ѩڬ󞦧ު胒𥓕ݗUF=>KY~do}DcՌI8i567988:98>A?<88876647gkokjjifkk43313734`[443337|~{?<;54:?>BCEDEHP`昩̲䆣ϑһ𤪢ЫָΧֆ"ܽ㍣ӀbZiXWTSUTSS|~}ċ\[_\Vuxmflqnlllkfehfa]WqaYhqu{pK@FGNi{Vbxv~TJMNOOLLPPQVTRQMOPQOOJG{z}xy||vGKMHFGFHLJJKLMWRQLIMSTVW]chekx·mnmicQlcy`j_dtWTvoq^uxiqgoq~٧tttammjhqowc"T`ejnigedbjlokhirgy}nyavqW~B/8MHDT0+&%'(&&PNDAHTd]E>f|h-&-0,91OIC$U:>@AA?>;FOgVebSڈ󯈟ڙhX_ƀŷڔ҇^gaUf=>AA;pqlT@0LFp[^raK+<I37:>:57:exFC<7fJGDMMLLtC=BG>?L>UH"1:=?@@BA@BB@B@B}=roR;6FSPR];A8'Ld~~~~~~} ~~~~~~}~}~}}~}}}~~~~~~~~" ӄݯȹԒ’å혁kCEVdsygvHtd98447999<;>CE@<9;96655nfkjikiihkhe67425562.555227~x=:997<>?@BDDEITg䅒յϑ˜볈쫉گ򍓒ഋW{TTTUTRNtkig~kVX][^moqnllkjdaddb_[lZj~avpyz𗊛LBHPtzwWtfkMLHILPMLPQV\WTQNQRPOMJyyxz~zz|IMJFFGHHJNLLJJMSQQPNQTUTTWZdemagimjdefbkͷg_àЦST^smov{U^druzזtt~l`eXhM"QX^chda_gkqlgdgdc]d[`yVieaK1EDVD)',/P5,jhL-W<>>>?=848A@759mJGzr?OjKZPVaz~}͆j^NHxҀ爖ӿbzujsc_R^7::;=:4H>&3:>=>DGlDfeHM?HOD/Aknn:6DpK "&+8@>=?=w??:798ceEoigGVME\s~y̹ǖjNuEJLrù؀ͽᇫ牢QX`hWYd7:@B?9o^NEROMG;Up~kWc}yx]G-: -?NH=0,:5)2<835=[ECinr9AB*7E77}>BcWXW]A73" !3@A95._i<=A?:~~~~~~~}~~~}~~~~~}~}~}}~~~~~~ ~~"ڹّ汎脌˕ʱފ瘣󇍇羫l^`\ێڝFF~t=;<=>CPbI937767875kdmjekqpjlkfb.386755/44437875<>;;7?CA@?A@CDGP񋋈ıקԛތ˱ӱͼ򆎐ӄɆ܉"튄؟廻fwXTRRRP~pyOU^c]Vd|gyjpmorqlc_cdcdebgkdpg{srt٠dY|q|kkonϹm[vAHQQNVpiSPSPMNNLIr}}wyzwv~EIMKKJMKLIIIJKLMPQOPNUWUTTW\`_dpsuiȵnmnoro­׌Z|e{ťwydt^nuoqtfboiu]\OVY_epyuiz"RV^ZRTzjccimmlkZzճH=C)4>IY/+(&$#>:BT_PRq|cA,+'.2/-J-DHS19T=A<<<:70/9>;:<=rqOI>lrvRK`MGe}^⑋玐hуulCEHy犚ȳۀN?4OYKds>;@C>8jbak\Y~S=WdtO0E||ntF@Z6NV=4407>F>8/22IO$/05?o)(?CDDF(6IgL3%0":$0<;/&:??[hwy<8=BCB>iA554ftkyXCY~~~~~~~~~~~~}~~~~~}}~}~~~~~~ ~~~~~~}~~~~"񋐐ݥ삋ݭ؆끏ɢ՝˩_dèٯRwy;=?ACYvZ<28:65565henhikm8hijd[+065754369636664=C>:>BFCB@@ADEGMm휡׌彔ܦȼε氛Ȼ虣൴"򈌊ǜ¿ӭ؅friwYWSRQOvivSWcf_es\nnoqpka]`cdeig[muvedlyϖéj{rߴgunhstFQROXxrVS_UNNOMIs}{t;t}EHLJJJKKMLJGHKMMQVTOQUZUUSV[_aeln\b{~nfob^dj|r\pʬuhdڞpS|nTzqqagr{]eJr{Qos"QORTOj|hnke`~kbWUZ]>AH0:L]0-)'&%A9BTb[]vpSDC%+110`XFYHH+-<6B><;<;2/5<<;?@pi?GB?=p`DMjRLqdІ]_y׾ÊDHFJu޾ִǽƿv{3$=?34WmA<;6ggh<=>WVE{e{M-BttWQz7)NJLh258-:28:V8)R(4C:IC<>pi^E?"E+*31D/>5FSfRfo|CEB:9UM:2dxgj839~ ~~~~~~}~~~}~}~}~~~~~~~}~}}~~~"ݐڃۛꄌג񒞢ڢӣŶp}̘Q_v~{87>??Omb>8><264655lkeiij624lf]/35466546765746:=A96=>DBA@ACDEIUͽԾˁʀ񶛌àα¸Ẵܶ䉐"񺱱ڹʿƌ^^`dhyUWSQQNmÀ][]\^]Yiqhpqplg`Z\cehlj^^muwk\kug{fji|ᝑgsncu̬kwzBHOQVnuUTc^MOMMHB|zu}{v<;3-2;=?BA;n;<9:;r[=IdPNn|iZnFJNvyӹڶHIHKlײ񄛷ڳȰѼہe#&5QLvpiM`if:DC^/N\{RJ``G>Z9aA?@tG!6=F2!33@DC>Cf8LZ<0#NHJ[C[\BaN>@xn"/2.gY3)7XWIM[FWjsGE;q^NN> ;NHa::;~~~~~~~~~}~~~~}~}~~~}}}~}~~~~~~}~}~ +~~"􇉆יˏզ抒ԗ뉙㤀á։{ׁ̆tF\t}y86<@>DZ^A9>;274579llijlj553g`\.3558877543784:@AGNNHJHHJJGNTWVOIMTVUTVbcg̈́J9}r=ytvSRcڀh_gfΎgŝvfnusq{{vpnXYTMk|ZP|Yzo}zYq|ywypxg_Qeff~km[uw`if6H[q**=JQ+*),S?2\gJ_i9mQET0/..+(SUD&F\t27Y;@?A?:5.19?BBC?;;98:;sY7HcFHxW~n_c{CZ:*BqFFHHIEFBbL0 B;7231A,^LLGOf6q3'K:QNFOFmJH;CBD"8:AS$%B@BMaH:998:778:nnhmnh46cb^.-048:774442775:>A=859=>;5//5>B?=up;;;>>o]JYqD3Zp\^HDBC>}óםTxGLѿ̿􈓙`=! (@j\<_szBEbGSrqphDFCmL2?SuCFE?B=5L@0%'MVG*2= #KVLLTlPc~E.AeL`iQ2QGF>@?B"?Jpw4'?VA-9BB-EjB9op]ROV6R>[6<~~~~~}~~~}~}~~}}}~}~}~~ ~~~~~~~~~~~~~"􃆁氊Ⱥ〉̿扆넉٣kӎ_ZcEw~~{@@@F`R<8:;>:98srlbikjjicda..026;773353666;?D><9<=::=BDEFFFu֣̯򱌊ׇⷭ∓ǹѵœ𡘍"ʤմ쁁μ޾խXqTWPS{dcw_]XSPRRxkrY`kmjhe^YY[be]UXcirm}rIJo`cjiɢ䁴yimY`pOOQOTqiOHKKKHHD~z}|yyGKOMKMIIHKLHJKKOV]UROPQPQSUX^`dpɍkm=}x~}|twԇďZZzgmmir|gmstkZ}iklyqohVT|aݶÂovsm~xwss"mzÏ`x]WIOal}Wghx*FSLPceLB*.)P)F20w?Lg|~p0-+&$&)I;3-3DWUM.9?=;<:4.,0:>:8kk<<;@?pkkpuK9Yrg`uIEvv@>>ݥ_}IM只ÿ󄒗T5(! $ @kmeWmos|tR-6NOWe}BEFBlE,?>9>8^YKA3< -9cM% $1 8ABFKivxuO1Tj416LHF@C"AY40?H73#78R]FXbkMjP#5S7@ ~ ~}~~~}~}~}~~~~~~~~~~"څ򾞋퉘޼ۦᇔ֕O\JG@zy@@CII?99;?>=<;;ADEEEGJOƜâ󇓏逊؎ݱ鮃г"Տ֧κϢaX]UUWTSVvi]}grndYTSSR}rvdb_`fhigcYTUX\^[Wafppöwdmbirrml½miXSUKMMRbaQHGGFCyw~|=zIHKROKIHIHJKIHKOOSXTRQPQPRUWX^``gtǗ}D@>~ލǠnjVbtp~chifd]_YUX]UcJxZVnmȵ{\`rswu"q{ŹenzirqX\idqmkx0PXEG02(I),')-M;:C~>Sp~DE9-)'%'+O;/-6La1-/7;;;:82+(.5887ikw:9@Ay}zqRFat:ipCApn?@?@EқlKK}󓟫ǹbhD< &##,BnWoqlslS19=;%WgZp?FBT$4k952065e;7eNZPY`k-B2$#"799i|CB|`c_:(\pENJED"CNCJNRE&6%0EhD;$*EHSzDLQ0Js>=???BFECDHUsż垆̼ǯ̸冒饗䀋ѡ惫VTUVVW[^Uuit\mlbXTtw^gidbgjjgaXSTVUVWZ^a`mtgeiucejnjbovvncl茗jsfsW{eORUXVMB@=}yz>>KLKKQQJGHIHIIHGJOSX\RPQQSSUWXZ^_co~ϫňihRvsuҬơrRezr~j^[U[`dqpkfrWrews~aVUfkqzkd{[}vv"tpbW__alZo`apuBUi[V2-O+04246+CCMKQag:A;0*PK)^YD4.7K032027<><82)'+1588hg987@B?D>nXM^l::<@@;uAB@=CSvoFIGn߉󀂀򁙭·{ZF4",37CTd}xjok_:2-#^V-;]@F5=Bmj,&%047@BEw=Az8jRE8C\5;5;XoBFGICDk>GG2F}E>"FBTDEB686]:C;EV:P0<1Ny[G7ET^`@?~~~}}~~~~~~}~}}}~}~~}~~~~~ ~~~~"Ӱܺ肏삃ԟ鄆ߍrjzʵӗion|df{AF[WL@p::vvplrurrq9:wqk74464745676678;|EIF<:;9>B@ADEDEIQa韍܊밉ĺǠÈ󁈑඼ˢϷՕ֎"ⳚȰߥYisgSxqpwgro[YxS\gfcejlhaXUV\]cticdtbensrmjmnngtڟnthʥwyivfkTovl_Ku::z~~y{~}<=~~FJKLKNKKJKJIJHFVh`PMPPSTTWX\^_dnŋ{Psl}v֢ׄuҭqbawt^Ÿ[goqqigkojedbsfwͭkvvna~nuSuXd"pcfiligmg^T`oqqwc[qpl\l|yspRBRk><94(DKSTRTXS^>HC2HCU3eUB9:D$)01/08?=83+(Ubnvvfa88iGH?EPB/?_5,"D>5/8>9E9= N5XF4A[E@379PN\nJC? ~~}~~~~}~~}}}~}~~}~~}}}~}}~~~~~~~~~"꼻݉ɚ쁇޾񓚕辗~̈Ջy|FOH|q?Ity^Eo69opierxr8tq;xpj83377966376469;x|GIC947:|??ADFGKQfꮧŠ۝ެͅ߄"씡灇⴮ӑ¹is~||qlw~`\`[~agb`ekg\`lhr_aottqigeiiyjmkʈhpFRKLYeLs8;9?CBA~fcSyJMIk삒ÖĹݑǾ¼𒢪lxcSeuQ(Fso|ztyqReL{O:;U;>87==fMBVim>EC@ADA??DDBIAMB:=:IֆthlAQUA/,"+/("ku@;;=>BDBO;#"$6:W:D^@D'#%CEM\~KJF ~~~}}}~~~}}~}~~}~}}~}}}~}}~}~}~~~~ +~~~~"ⅺďͰҀ働ϺƈؽǬ̥Ыα؄|{?>:9Ky\Co2665eepwvwswAHsj6436:999346547:vuCEC713:>>>ABEGMR䲡ታĭ썍ɛʡȿɟċҖʍ􂃍"숉󃃀»b}bkzx{ytglW_ffurZg`Z^hgX}{dtp\kqplgebhlȭؖfhldzno{@>=?X^Iz9=<9nnv|{}|HOHJJKKJJIGGIIKJJYcWJGMSXVRRRX^aj©kxto|Ā~kPuslrygƦ^l]akkpnmjhjddkusncqwyʢiq`LDM"mlnhuv~pUWP^wfnpgXkcdn~EKUNBELRhGD/JLd877]<+&!1-5.),123\OIL[n=xlef^JNkpmrgXQI]uy=A@=97"":UF?@CEFKL̶Ũ賮ғ͎ی"݉󎝜΀úddkx|bijctk{Wf`XX``Xh_na]rprvfoohcdcfdȩղݧhhkiWEw|BWzyNtz<8nhks{DLWIJKLLKHJJGGKMMJXRGJKMORPPQW^_cgjpڛziZZJtufilrzi}iťktpigiljfippwmgl\aoΒk[f"aXcjcnoUQXd|UZ}qtsy{ysÔ넫麈»󁀉Ԙ˺菥õnv?}s}lh__H{pPC;:7aSa>;x\2qjfhjc~mQQ{C=8?E@?CDBDEJCCm=*G\@:6?HQlS?(;?)Y"2(F?=DBbzX6S2++?C#mAgYK9S|DI?Q~~~~~}~}}~}}}~~}~~~}}~}}}~~~~~~~~~ ~~~~~"Եȳѣݶ슭칓֏ﲅruzw{K`_p~hnolhVTa~C?@wj:9:7:;978633366mqwB?565789<<@CCDIYȳٟ稆Թ畚ى㐭c"󅁆ہᛌäĽbgbXTSRW`egie}htd_YWYYW|wa{xiuhtsibbdd`ּȭmgjjħi|v_eco{{smberKFBKJMKKLIMLJGJLMJWRIMLIJJKQTZ_`hriiɲœ{dzlua||WMiVwcǺ~vll^acgkdlprvsh]{tse]gpȺjck"{_]]_blPQiZhSxjkuu{;|֠呿ɶɇƒƽ÷ow>;vy}{rhSzi^9GAQ4H\WcH1*6B1H^]*7Rw;uyKMV.3\s;4=E>:DGEBEFEC><*5B5),M3Gk{4,H;e"\2ps8=@9j`)W*!F )S+@=cH4;*1112.(*VQbr]Q_32_hlZD"0P:FpTO\uDC<97<@?>xxubYCEHGB>~}ѽČľotvA==z|ywuhek||TdbO1AJGZ@4PAGGKzb ;Xi8Fo=8Z FTf/t}71=D@CEEC:DEC3.(05Ls7.MX<\"IUOf{yeyiDP;9! @*1(JDEVV<0T+~~~~~~~}~~~~~}~~~}~~}~~~~~~~~~~~"뀉ԫdz󀐘߈ۥ́ɜм򎒕۲ˇ~{EUtfl`je_ILhǀnr::;=BDH]F64<<=;;>DFG:675g36>HE@GLoϤ򍒌ŞĵЦϣƁߨ򄕦֠ʽҹ"ḣ뫖beillfaa``^X{Udkd^\^a`[cc^ZXX_`]`b_]coTh~s~akrneaafhgcgsrmmmǥox}JYx}xsuqkdu{πJJKLNQ`~^LKPKJKLNRZ[LIMIFNTZ^denߠqunůjk̢rudXjriYaej֦uxv]iqroU{nZ^]U_szWVlq~"foq{nqlYPfaqn~D?=@?70/-.0-W\VTSMUZK6:PY/9@@@ACB;[HK,/0253,-/WV_YW14667r;W(9`NR\XHJ4@D?75:@>??{=ugVzHHFB=x͋޺򆠧spqxBA@lqxssrxrs@?zY=HLPS?;BX/CJ^p2!FVAYHW6|B9>P_EPMML@XnqkHLqTe2;<;=>CBDC5g67AGHFKRv䥢͂ރüГЮ̳ŏÅԫȦŋ䯿ɵלվ⼰ݜeelomjfcdf_WWtx^gbYf\Zbbba_ZY``Xaho~quxglqj^Z_gkkhghiÖͱvwxpmmhȻy~BM]plkdbqxcsˡ}KKNPXuJPMKNPPR_dKIMMLRYXbhiuꝗfpp|ȼixzrpvcfZkeWmw`ogujeZty[nsrc}\keknW_fk[us"X}nmhoa^Jom_QSlwsG@?>=82.-10/2je`SJV_Q43PYX4;@B=0O+121021-/3T>@R`,Tj>@FX~;:TbRicNCR4=A<2.4;;oߐē󎫦넘tmrnt?@AzSdvzz{zvnp=BDAlUTW[]KJkJA/GSG=5.5CV?-&"EOQCR8EB>8MG.5IW9CG)<;@2)#(":KHX~~~~~~}}}~}~~}~}~}~~~~~}~~~~}~~~~~~}~~"腏ð뺔焒䲜ݹ󏒖Ҍ汃ЬiAAITgg?25658;BGKGQZtƐ჋ޤ㈼Ƚ㎔™Ǘי۲Ԩ"隝ğƪhhkllljfdaVwxXcaebbdb[XXWwkڀ|xbl}mnqh\Y\dijfc`a`qțjvvsohΫcGHUo׺ϢmdY\ovorwMT]`BJPOMQRUoUFMPOQU]^gcl~adӄơtyvilҍzxqRwbxpm|rTm}f\~ynspfvix~ciƳp^f"}{ysi^Wbvsm]\\\O]SZsFA?>>>81//,`tyqo`S^`TC@TXU3;B}vxmI8U;84/-+(-4U2,CNLHVs[p>OgXEc]C@^8=?;1*+5<@??@A:m[ąID>>=>ꢩ̀աė˩_eqhfk9;rg^||zzmY^@CC?}kZ]_U>bbKXFJUc6_DS=8D>4&FcgM0+Rj\QDkEC?:\\Q6wI_g926W24"Y50tOD?IR@*'7<5&%[~~~~~~~}~}~~}~~~ ~~}}}~}~~~~~~~~~~~~"ρζパƽބ୅ñƑ䋦ԽɎn_S_y{{䨁e_VSZ^h⊻ɀnhoHf϶H/8===@EWY67566@<4/.ZjAC|ylZ[ZQE>LRS2@DA4+(/8==AvTfC@@??|xXu֊ôr·橆Y_]gkie`ch|c@DwBB?Jd`\XQVlitbPW]*2:9?0&:LF74bgeEClubyAABBsygdf`r\behXR3;"d. "D= &jNnl~nHnGXbxK""8DG908T~~~~~~~~}}~~~}}~~~~~"څҷꂊ䀎٭㮚ɀׇ񻉞򃆆ȉ뾒ʢΝߑǁgc[KXjmՀohtXÈ-8=?>BLp?<45:@ILSk}ñت؀޾ײ֨μӄó㙯"Ȱםõˀkqdnjeefda[lkȪw^vV`aYgcXSwaZo_|^_qogXWW]b_xcv}osۿ҆߹yVSe{tvtvi쯏?OQNLSWjJLJKOV^]oɟprؕhd][[q͓hfM^ΦtZU}ku}jqojlhcdeýdhŮYk"d~[O[ZUT}]wnt_BOf}EF@<><51/Y_|ECpUIHJ@/6HL+5?~qwoYUYl}@>/!AS]BQKM+2}|zxaDJ>~zYT2md7@>@s/*,256z~xqlPfs?AHIDqJr~~{psó`_ɳ䪱]vnaQKZXkvyymp[v6\7323hmsB{aWRbr&BKMR|Ėֶ‘ⵧ󾏓頑֥󰚲醕񀆍"煄Ęxeasagcdeca]flǧmvVZf}~Uyaf|\\lw`|nrd[\YZ^\x^v{߶겣zTWv|Ʃum߼vAMROMT[~ȀSNHGKW^^tȗΠޜy~i_~|mlW~cckfttfSm\ҕv}}֤vepsokjljjmiphkknnie__ah"cf{yV_p}fӝjNG@GZvD@<;9742P:OwDDzbE;DA.3JP*/qtwn^KOY_fZ"DJU`M\B;DRelWG<=GLF[jRGi?@1Xpr70,,,-fZF@Xa`cWel;898?CJHE?@};27:="A[).A589&!>pDOH]qwl121579>g~~}}~~~}~}~~}~~~~~~~~~}}}~}}~~~~~~~"ꯚﵪƸĬᵇ޷ڭ􀀌ӈӗ눇hpiWd~ϘlݢRi>>?E`ol:=CGJHdǛu㴝ٸؔꄐዖޙ雹ނ"ϩǷ́ҟ{kfaeghh`xjtlaeXWvjqǹǘę}lxgUd`\^Z~caXYaoozַԍk^q|}tз筡ÛQRQVspJNX^dn񺅴øbGUYT~kghܔkibmdgkZ}|ie֗cblqkmipnoponlffhjga_edik"mn`|t]elpg_WXDDWb||Ycu_@ajjWlCE;)GSdp6/,,,[G)@itog77:@RYC}js^e{rpDzb_}^Ť벜ၒޒ󉍕Ҥ}sdPztvna9 3;>NghH$@;GM5A:kAA]"?PMnVAZoYAK80HOIqAl\}eCB>;DB?<99:>EE@B@AB=:@I{n:@9M234CjxcAC[1-]]W[X^zxfNGKM_vn/144.XP>fPMf8Lko[RQ;*Gqo[\|BQ0NnN\:3]/]VD,50740117>A>:{sTz}czXgغtXuWjۯ熐t{҃sꊹQWK>Bh; )EFSs"Z: 0*9s8$'++MpqRvuEJlI %"GD=~~~}~} +~~~}}}~}~~}}~ }~~}~~~ +~~~~~}~~~~~~~~~~"Э狎л亳쎗֫áآڮٳ͐ⱍփވqA~uᄕ锟ZVit@Fwsq?FGKVhsq㊑ʹʞֻίȌ΋񃆆ڽ"끒ĸƃՃǖҀ]abd[nbcc`UemmcWS{dcqɦorS|]Wd`dfcbd`adda_ZrįsȪڮq~zw֋̄۸vQWnW]giuj~sqrɡRZq:2XP=9Z~lTGaj0=sJn3_0YRJ7*E341-./7@A;<@z]FxVXp~vPe~MerZu®֦Ѵo{˟닟}[|UeK??zcG .5442F9=ScxQGfdXS]a>FFXPOMJjJAZ~aQiP0XhLt@BDEGBs_Wkl7`L}"F'-!6WQWKB=F0HEU.z^s]/5/43+C3 ~~~~~~~~}}}~ ~~}}}~~~~}~~~}}}~~~~}}~~~~~~~~~~~~"ցϳޯ艒ڵ丞ۑӪӚݹ㔓֠͡ěnO˟|ʛy쒫aXdsFmz;BEJQev齴vڹݗnjќґ񊒋˼ջ"΍گ菙񍉂䀠dnlhc]YYZXVVobɫ[aa_|{flmi_U~ha[tfu^zq]dhpVddlhbbecceeca]Ŧw鼵~uz}{}PؙoS]bekuuoèeA|HCIKG{|qp}jd~isroXwW]~u}qshfc_}wwj}y"{c{y|cms[w|yOhwpj]]8512/,,041--UFDB;`uLMY..03d[SMN[UNQP]pwtw@A@>81TNMpe>C^m6.SREBbCoO=LT6&7HB[\X,VQH4.T742/008AB?=@iNsbtwzY:FLQ[>@\5693GnykE@29!@PKA,9~~~~~~~}}~ +~}}~~~~}~~~~}~~~~~~~ +~~~~~~~"ڰ䦵㆒ٰ􄑗䵜؀ӮՖɴ߼ɊҠ߳vyuϭvh`o|fFRM>3+QEzJLW-.-/_XSM@:205>[u}z|ACB>>;1L>?vLVN0wBmVTJCWwBwKmGWQPXECFDCKqbsokrm^_W}kygmofac^gm[MEca}S]"mVmtvwnd]dk~]bhf}uq}ٲjk[4.E^PH*-)A8?QW<%4Qh]M`l0+**WUXT@W"),0Opzz}AA>:=;.@/:F^k]1?SG^NJHSjyI5Nb\^kYMHKTWRNNJ3%Kg741029@@=<=<4WPS\vtvukUCƋՒwUIĀzsZҽ툪~tfψ_iYdtEBB~c>  - &7wb`=DC>=6A{A(Vxrnnv]JZi^P6 ?O_SDPD\U-*20"&88+JOaT+)"%"5*"); """$8e4202WLf6HKpedt>2~~ +~~~}}~~~~}}~~~}~~ +~}~~}}}~~~~~~~~~~~~}~~~~~"·߲Ӫш޶񄎔ě򄃂ݼ壏ޮݯ̭ÀǠаz|B\ůΓ8=}Mr͙̮ɲDŽ뷛ϔ՜Π唡αЭꐤΦ"˓烈ۨ흑⊕=Cdp[[Vlyaeb\Wnfmzbgjjf_cz`ZY]}xw_^d]w|{b}Wfeccbeihfdba\v}b͠tex…UiPU|NփeítmlxmqheQLDBK./-ZY[dePBa~NNo;1)&(RPUQ=GA`?=Kgwvw>>;9:7R32Rahl1.5C\JGKWjyrL?T`**1/L?H\]VTSN@1B0533238===<;82eshbkuqjeՐzlGzR۩orsut`Ë_p~>vwzjR@'(88*>  ;~pbqns4EpU&8=W^p_VXfB8[hX_L;YF[.-TnNA)!*=*-=e2AQQGQH".=7&*"B;-,d4../+9fHoXUYiOn-&~~~~~}~~}}}~~}~~~ +~}~~}}}~~~~~~~ ~~~~~}}~~~~~~"يƲ늜ǹ쑗낌췚 ޻稜ѥ߀וֹu|Mǀaڱs}GǶ3) DGNpViLla"$ +~~~~~~~}~~}}~~~~}~}~~}~~}}}~~}~~~~~~~~~}}~~~~}~~~~~~"ܐ捐Ɇ󴩹򏑅Ųݽ挑򁌖䳣ᷠ講劓Ӱ傉ƇמQw\Àpƭxhqą6>ghԯƧԬݨ֭̐ڶ欆ހ"Ӵ쒟ɚᅿݮ葀Nsea\[qccZpr]brdiombW|w_Yxcyrz`h\}QPQ\_^`cdgjkgdcbX`hWpc`zkKVoextqz»~fWof^q~N]wp]VNZskwtgy|qt~{xYMP"xvpj^Rnio{Ogncc`yy̢e5RfZ1E^0-6}uLsrRu]WSU,)(SOJH<>0B'%5Rky{;:;:4,D1;ewo50F&BZRPJMbspI53$6).,LEV[T'&)W[ZY..121258=?=;5%09N?xtNJǿzt|GF~wwxw~xV̳҅nkxyveRgYFDitw}yuhOE(BA^dSV|#E4B*12)&rBw_A1bqVDOF6PrGIU_oq[XOYQ\\Z6FSX,9@R*@3>".R`_I= &)*+0$9Auߔݺ߄ƷӴǭ،ɖݚӱѫ"̓β勂̅_oa`_`hy}rd[b[js`bngmplcYaabYnmtYg_QRRY]bddeijgdc]oyѴa`Wtu֨ʊnzTwxtuugtȷƨ{bepconf]Wm~wXqYc\x{Yp"WYqg^en]foRq^dlgUxqhv9N`g9K_.,5@}z}FKF@>xfRY1.,OIHC;4P86W6450<%-HWTUQMYtoLPK ;/3.RKMMJ$%)V]acd/0.--18??<==X'H}Ss}lꫮFGDzhguæx庰֖py[n帬KNeezzcFz\HD[u~t~IE\x[bH,CmIJ9/:B1-;ORU`oM(4[3`M3I/rR|~A XqzlV_ittxT^ivBGNlJPAS"8.s<^Z4+/:",1hC4#0GOM?WS|DS_-,3;A{rvAFHC>|]GT^/.QKHD>J;G+1CFOsA>:<@C?3L;M302`9$2LTQRSQb|nXbR3Ga51-N?6AJLQUW]fi0/,*+08??=@Cl5LmlnОEGIEtSSd\t՛ɳannth|⺟BXcgw9_@Q\]YmTxmV}dIZzofynVg?U{jB\aGL7Jb0b^YX;70S+UJ05:GW;86PZYy\EDh:`Vn:@xUOR.,.\W"D-AA4.B"&40@E245clA&*dLSHKkKdAO~~~~~}}}~}~~~~~}~~}~~~~~~~~~}}}~~~~~~~"᳒ドݤ冉ί끎މ񼢤⿪ŭޣל}߀򄏏〱ɦzyGnolꐎϐ񒃱Ħّ֥н˞֊͕ǫ¢ǻ̈́"劻৅ɘRvpW[^]\[er|ǪsSv]_wm_^ggxghiorph`_g`^g]a`SuW^ec`cdimjd\xɮّijebaꍓԦjخ}ftps^m|uyeɡ沕|~rڶzfLts~S|U_`QWjpɡmfrj^WTXXd_\V\gla[t]SvwopsvxcT[mh_O=9DR--,/5=su@LaSwH1AO//\RC>=0V.(,EFHlA@<>ADD>kUW0+,U:#,ISMNWg|n_]POX^23/&;0?NQTSU]df10,*,28??=?@kFkurrc|{~DEGHDs]ak]ρoQj}ytԶknapp~}q]@GVpwBWycNWSblIyrIau>}u{bsXjvGt{TXU<<:fNBxgc|HCDC@ysmfDթl洝\sa楚p|m?tbCM]yGM|hD;1zKehe@&=k8cvjcwGEgnt_l{FY@HVi<7Mc4Q,! dFnuVUs\?j5']`*&*5>>"13HP;\=!>7GLwm?>???@@=97R5%02.7Gs}>c`ZF`8Ob{{4/+)+VK;<MVZPNHMTM[:=424118@xeOABeWlJMOo]SrxdbxTgd\›z{uvԝͯk״Kw;?C|kInZRlwXYkhIV\soxAD;OiwvMBYrd\747:td1-65O"[GSO>=`U++4DH?OU*,_6CL@77aS2>:gXKPj|ghHLczjw|bbxSiMjPety|z̪|^p~xfXu69@EvRZtFVWf|DoX{llsY)'Cyftn[Qp3\I!8?wB>=8168ArJDKm]?B77;<4PeX[[SF><52KL8=6n54"G /$]I9 >Iof*:'+F<6AKBU~~~~~~~~~~~}~~~}~ ~~~}~ ~~~~ ~~~~"쉇Лnj҂ܨ蘹åދϽڶˆuƱEB_tvچƼˑӲЏԓοՇ쫌˃Dzޙƈ"ÄÚλֵ􇘀oYUSXVgk{azdghjcWY]\j]jmjkqrsrni_rd`^WZaecbfjf]ruUe[|^_]`n{n`[cauhe^__xЂæf`lytuie\Zadneȟحѽgzl}ztceZgt~~VmzQR{Iyszbhmd\Sj"6-OQ-38rTI<.Jlvg5>BA>>@A?=95cS9!6`p62.WU^9CDA?<:82I/ItcQ!*)KOf--15DOD<9aNb?8e_YQ^|hwRDKjE@:=F}dPzvrn{~ytӥv{xlelY|{77950SS.25nc=BB:dr7:=?AABEE@<60N3")Gi:873,O^FC>;cK]>4[_g\QSgL>FhEA?=;>}}ABAtv{yn{G~~yrŐ􅔸{xrmbbԼ̞758:77jOJVcSK]fwlV]m}tOV_gz|k$5Rcd>Kj@BA?^R6$SV?mD39?3383*HtH@L0jXHK9@qG`; (jcXHJWGUrr"B24(+U728%M\@4_??,&$>n\=5~~~ ~~~~~~~~}~~~~~}~~~~~}~~~~~}}}~~}~~"䎓𸤲Ћݲ속ݍ탆󇔚Β˨ʊߐ߸΅؋ƞԮ޳կΉݪ򯫝̓™׌й펇ɍŐꇈةѹ"˳ēޱ҇ԥz_q\]XWTQ|mifdf`UYa__`beggiotsqql[wY[[XVU]dc``ed`]iYmdj}VV\_^ahkgd]y~VXZppjid]eecglkdsߵyhdca`anϚ}m^llnqtv{|}]cZi_SJowJRdaiRhѳ"Ubt|a}teadhcgiiieh{~_N{E1>Y0,)+.-XEFidqVRGA?=<:/ML,12hkT.BSQj458<>AEHFA=6-;51(Le5683+GV;BA>:9:<6@.NBF>kmL+9jt0136=C@==lJK3-MUgfGYSfdE(iECA?98roEDA{ut{xv}G~z{wqY恵||uv}sZ˖{74654:]cz]SQWo~}?}sSRTXf|pX~FECDQS5NP\NgPn6/27Bh?;U!"LG^6"Nf=5F-2Mzzir]\m#R+C)9Nl:/Iwj8$P:F$-XjD2!~~~~~~~~~}}~~}~~~ ~~~}}}~~~~~~}}}~~"í󓚘ཚӆ肒㧄Ҵԭý۷ڙܒ񂂂򾵢~䢮ڍ򂆁ڙgⲶ挍밖غ蟧쉈ļŵʛۅ{"†żϽp`SXXVUS\ligba[X_]YuZ`dehntsppk[tW{X`bbeeb_ZePp~nʧxW[^bknjd_V[U}b_rrhd`Wec`ciu{̵ͣidbb\}猯nmjӪwvdf{ékimqxtZuʅqTa`zsusZqUVark¯ph"zriT{~xy|jbgfgd`ajN{CW1L+,++.-ZML`Oa5`E@?=@<\FC*/01hbHNH@X]/6;=>CGC?@;/=42(AKCL[YODQ7?=:98;>44"C::5q~jONfi/499=A@=>qJ<,,'OVW@B?g]0iDC@>93dezDGHGDvjkloft{}xnLix~}my76:94sf[gsfZ^qA?>KIHQpSr12PPWWhn2JQ#_q>;mB>SVj>2.X.4^QJPuppuKCn"IA;Kn?C'&JN?-!0?)&%!!=`[*":~~~~~~~~~~~}}}~~}~~~~~~~~~}}}~~~~~"پɃٶ숦͍́狢ɟѮϥńެŐד£džӈ摧ͷߍײܿ炀Ӧ˴ǐᓦ݀ٳ窡Ȱ΅"ŹȥkVXVUUkjahfd`\X`c\i`zWacdglqrswo[YatojuX_fiea^^O|syldU[adjnkb_UZaV}fcd]qqe`\V{jczmajbgea^~lnogɡy蒩Ӹ_\dcҤjTVàrxlnN}bezZQ\a}ŷikx"{qZaaXWl|oplnec[UNtSnSBW_EP,,+-/aZUR>_FsGEA@>>tYA@)231afR0H5EO/7;:;?B@=A5l=8\NU[)19;>AA=;2A9-1*LGA6@?R|i1iEC?>91WWk~qbmnh^n}wh5X_}HQTtsPv8oxBPXGoZim|xE\j#aUb>1/&N58U=S}zklBFD>";EG?BA+NE'#04:8##%&"( ?ob8-~~~~~~~~~}~ +~}}}~~~~~~}}}~ +~~~~}~~~~"ֆ۴˜䦎AᰀӅʸҎѽ˰󦛮łǕ獏̠臓肇퇂镮ژɘڥȡ֨"򱛋𼾵⍤쑐wpUXWXZYxwefba]SXgobq`zWkfehlpqrwo\n`sudZ^tW_hiea]g_XlhV^dejkd^_VZ_OzX~l[kla_\Xzkkʖo^ffYRxde`[xkoʸfloej`qx}{mmUQtimkjmx_Z|qrdn]UQxaɥ"c`yz]qVjϋkvtyoaYMwRJa`@K***.24fbWMJ_IFDB>vlXD!+8:4__J,GcAM4=<77;>>==8/H.3F="*HRMQb7:;:6:=]/$EEI65<8\EIN$+5:SNLQUoe.hFGCA<5YPZqznhc`{tZ\szpY\Gk~BHC`CDbfrYq:AC>nh`LQKcztbosgl>??;sW>56-@LSO^n>>mTaj33PM2-;BIfKk3DJMyktw~lM2Pag1,.&)fV2!T}b_t~\A;"T=9,;=JE"4/k~3)24**_ii=1'~~~~~~~~~}~~~}}~~}~~}}}~~}}~~~~~~~~"όǂ۬划򁄐ĵӴ򽤛尃ꇎˑݘ܍ԼÞڥ悆إ版ձ҂ӽڂ㘍݄ҒًȕמΑ蔠"βݩ˛踑֤ޑfTXW]b`hnieb\SVfobp_bcx]wnhhlppoqkZ|ubWWnX_hiea\vua]nlXV`dfif_^`ZXZdinqdli_^]YUzrpdyi|ðnɑcbhfW]_fmblql_lnPfxcaxcgVyw{xc^[R^L̩skwp[V"y{kemWVWapɈO`p}xffUp׸]Qre=G'(*166fkrhctCGFEB=uiTJ%+793YSA.+:FM7B>879<==;6/TOIG:$HTRWc7:>>;=@lC6@:baA9?<2KHL#)3=CB><4*EJ/0F3ZL^_Qm_+fFHFC>90WYmuRBGIYuM]|{~~umdAi>AE?k^k:tcj6<}qk]P=K^mjh`stfe=>=<:\[ahOUPVQZk??lPLX-H7YV;WS8=?.20Y{h{N|ry1UK0-)'R. \X|zUJJRP"PmI079,AA&%1p2 %.916HIu>9"~~~~~}~~ ~~~}}~~ +~}~~~}}~}}~~~}}}~~~~~~}}~~"ˁۅꀂɩܱßԛղ͙ޛ޺Ϋۉʩھ悔¡ċ}橀̎DŽ鬊ݟLJ"۷ճߏԥdrTVW^fagpojcZVRTY]YvebmoaysifjoopqfXV|reXXtQSX[`jjfa\xvkbfoogVU[cjke_``^XU[^gdlvqi`^]ZXTgXedf}y|dbc^DŽޢ[ucf̷eotpgcbmmtΖnV\ccWqjŽĨT[Nj\YTmayȻ`kuW"lrmarj_X[RY^RvzXrvkteU\pPEFG&(+063^l~k_sBGFD@;8\HH),/0-NA5-7FHM8D@;8:=??<4,+aQ@;&)HW*-7<:>@@?=nSHPN;U?B@:.PK"(3=DCA>5-(*13Q+;<=@=>A>1(*1XB8'*I-*,9=;>BC?9jXJPPIKgADC?3)I#)1:@AA?6-+-36^I0"36Ie}|KxLJB;9855lieG4>DYHBCAB>:svmkyZkS)W}@BB;jR>hk7b\ec\LJZ[kBAAA=<>BFF960OOF8=u?==56%$!+$+,+ %?L4+'~~~~~ ~~~~ +~~~~~~}}~~~"Ѩ̴˨ϹՐ̠Ӈԣ궖Նֳ̖ȳҥ҉¹Ĝʘܛ䭐끁􊋈ƨ"α̿򔊒ÕـoTW]aXxhn|w_VRmwyxqkebcbb_UT[Zq\TWnUTW[^ejjibwvnpZlnnng\X]gnigc`bc]^cW{u|lomph_^`][Wlyorrnkiaxyaa`\mmu̅ceoɵ˨mlklmsuuuniZWZv}nrje|{}v~nutlmkmGkgccb_a]e"fj]h`hmxMOT[m}xbU\ejStPbBOXSOT**/1)Ss}\OiIFE@62T@D*[UTXNG7N]hcJMJD>;98:90&'01L2!%I_,)179@EEA;kSEKHI[655530hnM6AP]wFB>>?A>vxtnwjI@@cADD?tP;EShu:8`^\]XH{ipyCDCB@?>=AHG700WUJ7K<96jY9I58->MMLB;?LFB/_OCfGA<9m7~O(j"a=F<]%&%QgW6*'$,%#+!%7L-/~~~~ +~}~~~"ĭǿ٭ﲘȴӢј뇇٭񂈉ĕꨇ󄍎Ϫ伻ݾǁޜɰޮ"潔Хй刃bpUTWYXuhfl|x`ST^aerph\XXXYZUU[`za_\jVTVXYbkjf_wxqxdqonlg_WX]]elida`ac^ZZUrfdnf]^a_[Vx]pjffhmoe^`]db^]_vdWnssimgceiswusme[[[lnunhgybz||ZvypllwIx`lVt^fkc^el͖"ylfk^m}fɝieaWtzZh\Z=@IT[.++.,GNefPKgGDIF;l\FF).\^_QD,GSRO8Y<>@>5122/-&%.4X4!F_,%&+2>EED@oQ?D?ImBC@DD:2-,038;;841-+-34\N&OMKMj[:7gDD=97883.bfH&2K`wA;8@ytqrtfLOlDACBBaSgfnkeGL5SU.^PEEYCDB@>@A>?EF822]SF8b;968aA=44=L7.$;S8(:dp=>>9;~vM6H"-;GRD1'+d>|S.)*0*9:)8<&@*1G4~ ~~~~}~~~"v犾Ѷ󌒕Ͼ䈋Ѩ򶠟刉򺟎ĝӘ佨ϫ켝ˣ̸Ǹ錊ߘҧĶ˂(Ѡހ׺䁋򀚔\WYVXYVgYwfkni^UVSvo|fTVWXWX^cpm__yVTSRT`lkf_ylqiqolgd_\_`afihb^^]^\Vh`ga\`b_^WtSgugg]\_a^^]ve`\[}weSVXbnciaZY^fkonrk`\X{ʙntolxPdhpry{rq}NXgyolfyDsxzSoS]dkqcxgc`"qcnj`boy_giqzDv[a\d^WK,J01-+,)FIWSC?[GCCC?~mKD'+-ceX=DAIFBL2?G\nb\/-+-)(,-ZA)!7V+&"':DDDAsW<7nLuDB?@?831359<<<721+(-lfSHN+\bkjQ4<`|A===>;4-W\D%?^rgU[wA@;=?lI&)56KlLk>CEB<2 8sHA?CN=7;D5%ZzD@>9tlW=:.4<1".4PE''" %G;BiG1\*11:8 %BTG3>$!8~~~~~~~~~~~~}~ ~~~~~"ϸ쐕踬Ԇ߫끄﷣늗ˢ倻˭αӥѧȁ˧Ӏšɱֱ灇ʎūǁߌ"۷А񍇂߷Ѡ€t[TVXViuknhd^UXTz~Wj~TUVWW[\Ytp_Xe\loh_u\kionkcbbabadghgc_[\\~yVV|c]uc^Z^bcbW|`tr~nl[\UVX_ab^Zafb]Yovg[UVY}_pU`j_VXUV[emtsd^XwnoponaqqӉjzi]}=9ƦtnkcSstkkckFMklrl^TRXZy"tZRajk}f_[qaC?}yZVTrJEJAa2+)*+QOZV@rPvFD?>=xOG')*Zb[@IB@?=DH!6S^a0-*,03.)\L- !D\VB3C6@A><:52126=@AA;3/,,giUGJM)*`j\C/9Y|C??AB=4+Q]K0ADN[E7Dj@>8:=@<:>U./SiDAC?x_:AeomFYv7,,.cnV<5IJ5EH<48426>@?>515kXBBV|EBBCA=4*O\[h~^LRB9GTq<<;<><:x|y:)TeABCyiL,1PX5rHN\.-.06`P_g[6EG=6@>44;BA90.kqZHLs>?AEGnA27UbtNR325D^@?=<:L.;JO>(V7.hFEA6f[==83)"QZc<&!. A2.*,5.1 '8;3FCI3 @2#*~~~~~~~ +~~~~~~ ~}~~~~~~~~~"񎌁⿩ɓ幯Ձʟٍ΄𸀷ℊڀҴ茀ȴ󌕋׈޷ڲդ놎ﶣ۽ݷկ؄"В뀅twWWSmyhlgbvbSXTRac^dUUVTtWUSYa`X\uWkmc]Jiiiecccddgkjfec`[ZXbSTQ]\]aa[V}_ea]ZXWkidmzoosVVWXb_WUYghjxv{lX]kul^Yqptqhak̗ibj]qrqjlsrqi}UMQar{k{exj|R"UCDOZvPSXYRfCLdsY\RNIVjYCJ)+,WTMAJarAB>?;;e63Wۏ@<83..,.5==<6XRVGQg<;{vrhV?Ia\BVn?CU*((+33`j6-8ABERX_I76=B:.-f^WOZ{=>@DLXI?AAa++FJ4)-?f">\|GU$"1B,=8GFPH%$+0+*!.?TO,@T6",n98~~~~~~~~  }}~~~~~~~~~~~~~~~"򍒆̭󋖏סʶ䖽قߦٯÏ􋔔ꆇрͬƭꮜԶ׵ѽ̀􀍣"뉆͘𔯗WZUlmikfaj~XT|rnkoprQRP{tVZ]`dpVekcZjwhjieaaeeadkkdba]ZZUUPadcd_VTmfYjqiaZWUogndi`aVVT]_TW\ahep|[`kpia]{qosrmg]|lmmckmxknqusfeo|Pb^s|rSbw"seE7NeiVVUVLQWXhVSSe}yuYTP`p[HM)+-[]N69\yBA>=~yXBHT,*QUY[[TSNEKI>@52H[Y''&NGD.=$2izKP_]S0>?85Q#'3u~@83/,///5<:0@P\MCJ763klnogWSq6_mm]Z-("H27f3139>=KnbhX;82.bTPNi>>@DIAo^p7?cqw?*8DU{{>?@CA6DR`:>?,wU9>S+MLRS+[ROIBJHCMD?OZOC$NN:61"""4HPXa3_^8A?71H!.A}<2,,/2236=EJ746^B3=K[X&%H@C]mncqDCD?;3MH'*&CR45]T+1o@>CEA95Z8BJOJU842hhlssjb6311:83.97)*3`_VJbz@@KTD@E;:5kH4;O_MkG2hQ&<3,`"^I  5e/!WD5KgmwQ82W_PDaDP[pNK~~~~~ +~}}~~~~~}}~~~~"脍ծי̼݃ÙΈ놕ٻ􁉇򀁆Ǐō󋖘Ý疝ݷ「"଍ǀ܀|s|UXXVbW_lgb^vhWW]YUScRdxp`\dobe^Xcg^Xphlf^^a^]bihwqVWUSW[_\\dih^SUUWZT\]\is{rrfVoqqjdZd`egeaabZTmgc`wmakm]VZ^bgkgaVulxonnqkklehbkZhĒbMn`qsso^UlwQVVcme[dyVdm"pzt_kj58MydrpVVTR\TOQZVS))*.wd;1O;BCDAxqKJ.O,MJKIMRPNA5?LPYUJ).)$%PO?)3+ (:fLH'*&FM,.+P.3;BAJNOA5TiN9,F.8I=3pEy?-6W"@3 ?K#4N70"6MNLfVAM)%QP$%YYOC~~~ ~}}~~~~~}~~~"帙…ċ҂ҷذ򐒉뮀䄕ҥ퇎񋐊ǫڜͰ؞􈙝݁얉֯‰۵焏ɪ"⫅Ë倍pg}\[YVrQRake`_Yp[VUyYZZ]a`YVS~`f~eW]qee_XWaib[Zmfljc_a_\^ffebwUURSW]\^[ZbcZRVU]`]jfywZjx`d^W^eic_cgid`_aaw}|~{~^kn`XY_cggc[θqqonqjgda]`H[e\Ver}]fapro]ksWadb`q"LNYUddgcnhpLN{Xbm^d[TXZXSQUaWF6Ebk.*++diN3/O9@CGBtc7>*O-)LGCJSVRA:G^2b.()--)()PI5!)?:#!9Re1-*/5:92/Q8$=ro[2312100/4=EmVcR>5>Q`VM&(&%'12215}}>;7U?G),'HQZTLM.3:HZqmtERFZILI:<7*,5;>x4.-,'(.8DVbGſ:=bfA>>=<749>?;2KP]yEBAA>?=967L!?Kf&#@EE@lHA@d?AC4IGL>[Q:/dg:,G08@:7ozwlR:KD" '3"'EfW]*'*+&&VV)L~ ~~~~~~~~~~}~~~"ֈⱐˁҹ˄̤們ĺ߄Ƶ؈ٳ汷萱ۗ⥃ㅳ󏙚岞۰ڌڌꅒʾҾ"̽Ŷܼ}heaaYVTk^ie`\~i[US[c\Y[^bb^YUnejTSi_gd^VV^__iwbikfa`]Y]fggjVRSTW_a\X\StSVS[b\hĽjYn_a[adfd]`fdbabp~~otx_osh`^ddliaWudxjlpqicba^^}sZkrdqqo_~vm_Xcged^"N|LY]\Z^UM5[Wzoq~VZWSSSSUZM5B6e;3/.(NQFj^M8?CFCoJG;3U,*OLKPX[VLO\450,)*.0-+*RPJ/&@D(4R44-(*Z-/1/SE6Ltd36773.,.8?DdFLCDISR,PL))(+13/.]kzz;1?/D*,(SglN;))18=z:76/')5G[SQPӯ'8NIrCAA??:8;B@;2KHJRlFC@><>=:52[Jzq<4C_03@UK:@@D6HAE]fTA@3|17H18@?<6hgzyYY8",!  ( *5+]d\N&).)'W+')~~~~~~~~~~~}}~ ~~~~~~"۴𱂓􊏍𶡱㋑ߦɵ݂⃅ɋЪυͣ˜Ӂ鳐ᇚП隱ГѠ̪󵤿"܂ۧrT__VSz[ca^X{Ooac^Y]`abc^UTX}ezqaT{ad^U[`^¾fkhb_][^gk^ήWWSTW]\YTX|SS\‡r`h]]rnYVҞ\d_a`d³[[^aaowq}k^jorlhchlpidX|h鉙ckpng`a^husprmn`{bntS[hqnmgiЛ"_N}~NQRKMv~XSVfahY]WXVSSRSVO<":_^:61.NLWS~u[=@BDAlE#ECZVTPQSVWSST\b230-,-141-)).[78D.!E;Y40+QQP*//)LH[yw=?>81-1:BC@iQTXJ^h..UMS*'+21.)Ypzv9[83I*)T]>X7M2638SmY>D90d}bRJ^??/,3:wux;;d,-8KaMEB68A4^DJC?=@<@@DZN+;C(B4=FEC<=sMI@" $'%'"# $/6dl[RM'.+'(&%)~~~~~ ~~~~~~}}}~~~~~~"Գۣιʧ愎ʧⰟ⺶Ì󍌀Լ󉗛Ȟ߉ѫۚ񿑻"޹㺺Ȕ\{fa_`[sl`df_Y^b_^`\WY`[puyzs[``[SYb\õdje`__abfh^b]WSRQTUVTSSsbqdU[XQU[Zaií^bc]]^]\hpign{uqcqonnolhpung[sjhpqlg`_spkms؀wsij]kvqawRZirtrolx"}mlRMvv_b\hcg\bkWXVTTXL/D@YSli`YOTk`yq^B@@:Q]VTSQNRTMMV_/031.-/120,*.66H'196>8(,F^2.RN'R.0,OERuDFC:3/29AC@jW]b`h51.SLO&$%,//*Zt|vo[FIW**UTxiBS4;3+cm1.(../69g_FVn:3148lflid[Z-5=EC?Ax{gC64AWAIA<>B?=EG@:0TPKLWbpHB><<>>nG;\Elm,7\a/=4KP]?<;B2K?6:47@4I,D7CHGEBDc_cBJT"5&0/,1+-)+"-+56,_tlYNQ//T($#)~~~~~ ~}}}~~~~~"óԕʹޣꊑڻ¶ɷ౬ð􏐇淟󇒑گ܀̍۸⃍쳷ڍ쎟г饋Ñߜ黒Ñ嚃"΂Ѳ缘qj}ydce]cfdVY`^[YXX]ggzi\s][US`]Ždh`^aacfhg`]b`eYTTPMNSWXWZ[VTT|xdvkVSUY^^^cif^e_X[`cdf_af^iqokp{}famxp_\y̾ntpnkbb̈́dibjj^b{uq^^Xr_~yrlRVdrusnlriv"Zv{bZ[abf[XYZvXWTL_POUE^`[YQWeTg\TDA@:e_XORWTWXRHKOIJVb132)M+./.,,/5<>cA8:JXJ6.AX0.*'LMV10TLQfy@@>94-/9@BCyblA>770STU)&$%(-/.0zoYT.1/,)IWuaNZ7>6*PKG&,1,,1325[P[c00./ZYml_SU,2:?=7`>Jj@X]C1>D>=DNRE96B8QI9<6$:8>>@JyP.9VR<;@sSNiADD=@CADAVAO}hP1O9>D9Tg?L71*<=]WKEM=?=Bd@+B1=GJHFC7T4+ C"ubm+'+%#*2* .549'%XxC7IP("DH~~~~~}}~~~ ~~~~~"шӨӶރǸԴ蹮򎗆ƚЂ꾞фׂめ򅒖扯Џ߻唠Ŀ׌Ê"ٰ钨h{neRUdۥjeT|t{RPOSannez~`g_ZUU_eYyr|`^adhhkhaXdYTVXXUZa]px~XheXW[ZWWXdkhaXWgbeb`d`bahjcnc`de__lpvt]oek͛ZlcimdfipYitUXel6^VV_jorkfdbYQVI`c^d\TQ|_vdHCJy_Fb^+*WZQAyHCavcKVK:75QH162UOmBA>?:649:7FCkc]Z_87776;?G|WO0]gA;C|YOlt6=9@C@@AV:OX79BBdNEEH +~~~~~}}~~~}~~~~~"уԩ򀎋۶׈ݳ⁃éܟΦ爃醋ʽ쌍޴؅Ǵ䂈օǦĭɄظ۠΍ڄ΃"ȹݏ~vdRTc֔muTWVkufQ`~RQQT_kml]mbS[]da~su{yz]`aeijgb\xb[VUUWZ[S[bamve^YTTZ]\Veq`^Wddgigc_]^cqdX^ba[__aZgomdѮ^v~i`bkmfilnljuzS^jjxoSUPW_djhkcd|zHo"_}aXkjVPn`^_\Uyvafe\uLCG}gEba))SXT@wtQ|]cLVF4A('&MQY^K>HISe[8&8HV+&&-6;756K" G/FHNUER0.*+/50LPdtu>qTKb;;jTA7GUXZe761)(-.0.F?]@G;_VINb62HCj>>@A>85427F@665/+3475h8;A@@R@;6"8O"%&,6.(*0/)@ 8UeP+.' 2DKL +~~~~~ +~~~~}~~~~~}~~~}~~~~"ӭݐ޺τ㽐䅇ƥퟰ񫎋ꂈϙ⇈҂у洞֮ι٢܉̳ā뙖˾⣊؄Nj肆ڃ"⫋Ѹʀ倎ڀvѱaqxzyRWXUmc`]\\WMVwTRSV_kpoajT|kkuySW_egmgjmVv\\_beihd`ra]oTUSSVYRv[\W{d]\WW]^Ssd÷Wafd^[XWXYW\ceeb`\^]^hmduehjfa]qcfkomowÅeTrUhmVnQSRU[bhhngbO"eljY[f\mXTRTjint{ZROoZOLqnR_RRTVT=^DGS`jg^bQ716DS)%'/6;737Y&0k|zNC4U/+*-6BFB9T<_vi1/)<-D*,,+-,)Rj?CCdA1JWU]em}s]LM_32/*C )0N:?NcdK5>I]WDP`{V@l<:720,0-.4961/-036=B=96Q39Y67:;;vla9GrV<=K_5\@!/>N.',HWURV\c40X]QorcCIEW-+,,cAIG~O#Q_5/)G?K+./,.-*/?a86:<=vcYj|YJsxQl>CD>st{reQ>RwG3g6hXDHLXjvdD&<>[LG@FX_YJ,3?EX1]VW7CDrGG:74=I0-1M[XQUbk94WQD]nvMdW1110X`?KwIJJuc:1*MHQ+,.-/00h?IFAv]27WdZ`7@OPEBV52-+'#)25icUTMI:6WhLAT^lED@=@810-,RU7=842-+39=>=A@sSJg<48=@=[IVbJ>wZ}|9<67uzvtuV37^38Fe_~uTLM]uw^PJ'cc=a"SG@3#.!<7-2)+>docC3*TH91^1".67@*~ ~~~~~~~~~~~~~~"Ͻ˱پᆎ򥾯ƽַل긅̆泔꾖؍Ý򇂉ūȿٲ΀ͮ蓳"ʼ󾥫ϑ̅­trz}kf~^rlzmiVUSotq{Xghpvp{fuxdkgȺode]X̋cWlk\U[__\Vfgǰ^bXfgqfkea]ZUV]cfw~gWXaghsqbZUSVipk^]UdqpnmnhdkhjnlXr}fhmib]|vʚ~mcX~mdgljmd̛h"vi|vrwqOMPMPWPU_QIXhc^aim^>PE?<@d\k[TSNK$!"LWRLVSOYYF8BT[^`WPU6A@e93-88;H?A=PWSJRkx=9aS>HSy=Y4650QSp^;5Q[b<5-NIO*-//00_`uECwP&Hb1\]hvkGC8J41++,-/0466cN9E7TZy-JXZ-:CBERF6.*)O-7>=;3"=3??>?B@@hXm>69<>8VETbQKL_n:8;>@{{qV%-U;FhtPBFUNB]9=.@IHBBGHQO\4:ACH@hZ-A"OPJ7;RIk^[`:9?GI@<#ORDu@G<~~~~ ~~~~~~~~~~~"ɱǽܹ뇦Ԁ߿蛾ɸފў𽏗넍Ϯ׀ۀĄȧざӦı򁀄۱䪻֭󐕖ߝ"ҽzƀgkendxnxtwSSyʷeoyVTR\Zvy]]xfӾ~kcg^XTͺsU_lj]T[bcaZ{TTpUZcYkkb_bWbg^pyjx[STWbjgkj[VUTekieWotnlid_Zkgjmj\t|^dnneVVXkt˵ywrdn[fhkgedb"zheMc}kNIPMOzvBMKObpi]bnn^FcPQ[I\C=4Xc2/QNP197bBD=>92AENCQVKHZy?vdOi@I|q7T6960(J^L+9VW`;4.NEI).2100ZWkrL "8P.YWenY{8-7+0+*04^Z16:6P4D6FJt0#G(,8CCDXP7-'(T.6:=H<.'RCB=>??@l^6<6:>?:YNdk\^_Ne|<9@EB{qeU*:_]yB$JIZWo+JIIKMMDNYf7:??GGcK4&".TR@!9PT?fh\5)1.I?''WmozPA/~~~~~ +~~ ~~~~~~~~~~~~~"Ɵ̧ԟñޥ߲ʐ慎ζȀ猏ցױՈ买ꁕ즐ŜΤۮ߮"׉亓ߌĻˊerqyxck~q^fR|ɸbvymve`SSymZMfmegӹzf_YViclhaTUY_`b]yW]wUYnbkd^XVdg_xR^WVUUW[dfbgtshYURT]a^ØwmzoqqlbaWhjlmjfyļlɽb\JMj~ɾƥsec{ohzvyiiigedy"V~z~QSlv[Y`TBIQMUfatEOdogYepmgYB?MP>BWH-2GSR&NMIMOKIBJK@69Qn=6WOEL.4iZPGX8(>FVIOSLRlGBuZDO9DZVKHEm:60*KRnrCN]M/`7.+&<@)/320/XSiuS,N/BRX_tsSu)D//-/.?L3888`DK9&Q.'#%)2>A>>C=7'(-/456u/$;uB=?AAAn^398>BB>fXhi[]g\cE=oiS!3S_HL#QFNKJH>AZm=;:=DGnG6="9-VG&*DTQ\U'A*.+@{v|zV 3~~~~~~~~~~~~~~~~~}~~~"ջοߜ܃̩ǀ«Ơܦ炃岄ľǜ׉ړ䅌Ƿɪ 񒖊̻ȩ􂈈婝룮Dž퍍跥픕"ԣրȟـiuhreUSkfhkxzy]pkhczPjlRUdjgeYy|nlz]^Uw^imhcWVXZ]a`z]kroolqZaĩ_lgg_]oWZXV`hc[_nlZTWb]ZwWDVfoqmeg__fmpkkb_fazоe__ZiL`ǘpWx]\yoihgddaM"TWVziSGZki[\]kJSllmERbbYXbhjuu]^ql`lLF2EW,(QXRJJKLJKB8@HA2D8A;84MAK^puspVJ+BK_(&RU_zHIC91RP@")[=@CDBA9a46:ADF@k^__,,/YWzH==7X4?Sgji9(.CH2CE.6Q)+PE?QFkp=:;;?DxgCS",/.I3/F]_Y?"Q31%"9DKc+'(XnDIGA1H2:<%Y>FFEE=858;BCC@4ZW*,,,PUJE~xDG?EE`#.2Vwq\7D[=&)F%$L//3/UKTWzq=<<<=>{d0)/".0.*BFMbebg^6"%044!1"G|}jtR.138~ +~~~~~~~~~~~~}~~ +~}}}~~~~~}"ƶՁӶ詁ԥυ԰ʅȎ􄋏ěԛϻ"Ĺ䃃ȱݐ󊒅{ڧ҄򄄆؏鿚䋙֬ٴɫץ֫Ԩmo|zsgld^p`j{hcilqsrwUdk]TVcdlzUZVUUXcjga^wdidey{YtYgh\TUX[ZY[Xxtw\NiY^ǭl^\^ZTfb\YZ\``ab_q^mx]pmkjedfgkniidXZX^gf|me_efkqo~^dʻr[`}ipYWafbbdf[hohfdbϥ{wS"YWTTQgk]bpd{ǷwKr]TLPXb]NLWeljwDC:m>E6D6GhlXQTPGG@4k=??A>40E[&BX;>:34@EpKJDC.,(),9GGC?T1$?V,.reLCKAd3H--'%G_>71*%)27=>8eB(957<8ABFG@<9:>B@<;4/*+-+ME\JDwiBEf4;JgxoD26J4,+T&&-03221bagu;BA?=>e7(("+-./N'Senmvw;0%'(.%)3gs~qE9 &$~~~~~~~~~~~~~~}~~~~~~~~"˷邋̷ܺډع쯃ݪ峄󄊋ݤͥӞ͵Ŵ܆͊񈐎ɪӝߡكጸމ̈́彷"ˤ껪ܮޭoorz}pauec_goWx[r_cddfqwxcle_fgew~X_VTU[gkif`~khðti\`{yUx`dXUXWUTXYW_dd˰~l}}zropqlhwmcZ_[gcZX[ahmcdli{[ourlmkieedfikjg`WZ\beaole`egihmDjŮƲkuWYbca`dc]duukba˩o"YWUWUUSswt{s̾{YW^EKORXTACV^YNbB97CG21$Bko\SRNFE<,Sj>>n1,1GU=*3OM?tF1(0COSK='HfXHTM30)E:?!"#'*268AD@vtZ`PYD^YQSPScJq5+" @G@9DldKDQMDI,,(SZl@7/*%+@?bB)3DPz@EIG?<769@B;71.,..+?6hJBn[o?<;;<^6'!GtLR7CSF?a+*1340.37|}u?GG?:@sC@"*-.-)'+mttz]K''.0/0DzvrL2'"6~~~~~~~~ ~~~~~~~~~~~~~~~~~"Ƭ򈓘ݺނݱݬ紑􇞢䅇қα繁ףѧک》ıȶ܃𛩍"֬ѸՉͱtvvuwy}fj^bgp[[`hZ_`fqxoz[iigrm[zxVaXUV[gjkmgpdsdfxyr}[]iZWWVSRWZZabcgxyskivYZOtf{|mrW[Xg_XY]bnrgfophv{klljec``fkkbWTWX`aWsheYTaZV}pIv­pzubtU_d```b`_}l^|"\XUWUSqus}YoӻqYVCGDGPUROEpDUWOJUps35@G78 .MYVTQLFDA606:eRX2;IL=,,F5AC@EE~t_`-*.+0,),7CDA?;g[Jk?F&:S6=HJIIKIC*HigHRM87-C4:#%&)-48:@BB?v^vRdO_TMMLQ`y>b)7eM[?&LWNPPOHG'))W^rD:1.*/AKB;<@=ZE3=bgFHID>70,0:=4.(&%14.A2kH@5-nv=72bS3+%Mȑ{g:;;H?[D>VZ/43/-*/39|ll\[=8}O)>",,)(%%Tg{upX-'3133(Gq_Q5#%a~~~~~~~ ~~~~~~~~~~~~~~~ ~~~~~~~}"䎽װ膑ۧၕՂتљ쏍¯˒ڸ䇆ҩĜԀ׀ֽد腎ˋഫǁ򏕖멶"ӠŨ鋳źٶ~~}||{}|]guZ^`f\vadmr|lgbftUXWYf|w`kud\XUV[dhilmrjxa`wl^]XWSQSW\ca`eɨpmthUYZXh]Utnu}WVag]WZ_`_Z^ajnlinaolif`^]\ikcYSQQQWifZVZ|^[Wgt~Qby`eddabd__pea"Y[WUTQoW]gy]˨oiQSPOUYVM2XCQRRUNLU,.5:7R5,6CLPNGADLH?:7cQX;CJQXE9&.12:IG=oj8/.X2-*-7AC@>;iXH~OR-:A[N\SH=qE>506lg`Z_O86/9jO#%,6B<8!2DV4/(*+0149[gqkAr{CCX1;"*,(&)+[hkyyl1(06H?||vh[L'";<~~~~~~~ +~~~~~~~~~~ ~~~~ ~~~~}}"ê􈏑׮ֵۃԯDŽѻ畏ٓެȍЭ̀޿ͬ􌕑쿉ɸՋ펒ᘣ"ɰة‖{|{{yci[]]^^wmlsRzpik|X[URYlsilV^UVV[dhijiqq~saxz{ca^vfcgaYSPPV^ec]_czVrlmlo}fXi~f`glswTVTTbd_\^c^UQ^db|vcpnjdZWW^nnbYTQPQ|mj`X[MUyƠd\mxvz~sKb_bddach_x}dbe"U[WUUQ~aPOY~hh`XUVVXM-IBRU`kTDO,/137paOJEGMMG ELKD<8gX`BFGYi]N)0-'.=@:uE!(FR((-6@A@>TlqaZVRP'')+ewFD?<50*!!cj57>r`^IA[KJD@?8/+/;=7/# "LPXP?B_?D"&+((,)WrtZczyh5 %>5@6NipR-JCS[ZNIA*"09i'"/;N19??>>;4,$&4>EE?|kA8bk40b[WO)(),35/*!ZQ:`D>;>Ct8=;\M7!tk&-(,-6F3-),0353T>Er>8w>;4)%"#*((&'XolT[lxw4!!@q[uyvfS0'#+0~~~~~ ~~~~~~~~~"Ҧ؆綠߀ᷝ煇С큂ϰ㔜񀀮ﲀȀ͙遄橣ᅎ߀ۻLJ㨖封ǵ"èѥա{x`efe_^``_aglorl~SO}ustaWey}VTRWcc[Xcrqlps\eXw`hmlabgg]c]oxa`cb~cmigmngZRQSXfnhe_WWqsghmi`x{fggaTUVVXcjjkidZUOjomgbXTTemnkeh~qc_bddekkj^\jbkǛUM[xnVw`a_cgjlZhdcfffeWP"PWWTQPlZ[]]נiReVn~vcTQS[`bgr428=710025:@CD@t\PQUV(%?:;??)#;;@?/6%U;@FID@?A>U=J]52F;PWY[VUN9)1<^E)DEM2>@??>82,'&0=CC~{=9WdQI+)TZlmOd?3HEVzFDA;8V6?i3,-/e`P,*(*2;A=?@8.*%EJdldcdUMH?mGA@?;512p>A@?;AYBGB{;fAD@=D=8@@9N:)nHq!$::'*>$&?_1+-/29<4OJq>@EE@;62*""&&&! Ytmok{vi,!PgǁYo{jmd*)-~~ +~~~~~~~~~~~~ \ No newline at end of file diff --git a/test/test-suite/images/sample.jpg b/test/test-suite/images/sample.jpg new file mode 100644 index 00000000..0b20daa1 Binary files /dev/null and b/test/test-suite/images/sample.jpg differ diff --git a/test/test-suite/images/truncated.jpg b/test/test-suite/images/truncated.jpg new file mode 100644 index 00000000..71ae0bb4 Binary files /dev/null and b/test/test-suite/images/truncated.jpg differ diff --git a/test/test-suite/images/йцук.jpg b/test/test-suite/images/йцук.jpg deleted file mode 100644 index 4ae2ad73..00000000 Binary files a/test/test-suite/images/йцук.jpg and /dev/null differ diff --git a/test/test-suite/test_arithmetic.py b/test/test-suite/test_arithmetic.py index b965991c..5d18e8e0 100644 --- a/test/test-suite/test_arithmetic.py +++ b/test/test-suite/test_arithmetic.py @@ -41,6 +41,12 @@ class TestArithmetic: cls.mono = cls.colour.extract_band(1) cls.all_images = [cls.mono, cls.colour] + @classmethod + def teardown_class(cls): + cls.colour = None + cls.mono = None + cls.all_images = None + # test all operator overloads we define def test_add(self): diff --git a/test/test-suite/test_colour.py b/test/test-suite/test_colour.py index 4e15fae8..4d4034cd 100644 --- a/test/test-suite/test_colour.py +++ b/test/test-suite/test_colour.py @@ -83,7 +83,6 @@ class TestColour: assert_almost_equal_objects(before, after, threshold=10) - # test results from Bruce Lindbloom's calculator: # http://www.brucelindbloom.com def test_dE00(self): @@ -170,8 +169,8 @@ class TestColour: im = test.colourspace("cmyk").colourspace("srgb") - before = test(582, 210) - after = im(582, 210) + before = test(150, 210) + after = im(150, 210) assert_almost_equal_objects(before, after, threshold=10) diff --git a/test/test-suite/test_connection.py b/test/test-suite/test_connection.py new file mode 100644 index 00000000..f088ad78 --- /dev/null +++ b/test/test-suite/test_connection.py @@ -0,0 +1,128 @@ +# vim: set fileencoding=utf-8 : + +import sys +import os +import shutil +import tempfile +import pytest + +import pyvips +from helpers import \ + JPEG_FILE, PNG_FILE, TIF_FILE, \ + temp_filename, assert_almost_equal_objects, have, skip_if_no + + +class TestConnection: + tempdir = None + + @classmethod + def setup_class(cls): + cls.tempdir = tempfile.mkdtemp() + cls.colour = pyvips.Image.jpegload(JPEG_FILE) + cls.mono = cls.colour.extract_band(1).copy() + # we remove the ICC profile: the RGB one will no longer be appropriate + cls.mono.remove("icc-profile-data") + cls.rad = cls.colour.float2rad().copy() + cls.rad.remove("icc-profile-data") + cls.cmyk = cls.colour.bandjoin(cls.mono) + cls.cmyk = cls.cmyk.copy(interpretation=pyvips.Interpretation.CMYK) + cls.cmyk.remove("icc-profile-data") + + @classmethod + def teardown_class(cls): + shutil.rmtree(cls.tempdir, ignore_errors=True) + cls.colour = None + cls.mono = None + cls.rad = None + cls.cmyk = None + + def test_source_new_from_file(self): + x = pyvips.Source.new_from_file(JPEG_FILE) + + assert x.filename() == JPEG_FILE + + @skip_if_no("jpegload_source") + def test_image_new_from_source_file(self): + x = pyvips.Source.new_from_file(JPEG_FILE) + y = pyvips.Image.new_from_source(x, "") + + assert y.width == 290 + assert y.height == 442 + + def test_target_new_to_file(self): + filename = temp_filename(self.tempdir, ".jpg") + x = pyvips.Target.new_to_file(filename) + + assert x.filename() == filename + + @skip_if_no("jpegload_source") + def test_image_write_to_target_file(self): + filename = temp_filename(self.tempdir, ".jpg") + x = pyvips.Target.new_to_file(filename) + self.colour.write_to_target(x, ".jpg") + with open(filename, 'rb') as f: + data = f.read() + data2 = self.colour.write_to_buffer(".jpg") + + assert data == data2 + + def test_source_new_memory(self): + data = self.colour.write_to_buffer(".jpg") + x = pyvips.Source.new_from_memory(data) + + assert x.filename() == None + + @skip_if_no("jpegload_source") + def test_image_new_from_source_memory(self): + data = self.colour.write_to_buffer(".jpg") + x = pyvips.Source.new_from_memory(data) + y = pyvips.Image.new_from_source(x, "") + + assert y.width == 290 + assert y.height == 442 + + def test_target_new_memory(self): + x = pyvips.Target.new_to_memory() + + assert x.filename() == None + + @skip_if_no("jpegload_source") + def test_image_write_to_target_memory(self): + x = pyvips.Target.new_to_memory() + self.colour.write_to_target(x, ".jpg") + y = self.colour.write_to_buffer(".jpg") + + assert x.get("blob") == y + + @skip_if_no("matrixload_source") + @skip_if_no("matrixsave_target") + def test_connection_matrix(self): + x = pyvips.Target.new_to_memory() + self.mono.matrixsave_target(x) + y = pyvips.Source.new_from_memory(x.get("blob")) + im = pyvips.Image.matrixload_source(y) + + assert (im - self.mono).abs().max() == 0 + + @skip_if_no("csvload_source") + @skip_if_no("csvsave_target") + def test_connection_csv(self): + x = pyvips.Target.new_to_memory() + self.mono.csvsave_target(x) + y = pyvips.Source.new_from_memory(x.get("blob")) + im = pyvips.Image.csvload_source(y) + + assert (im - self.mono).abs().max() == 0 + + @skip_if_no("ppmload_source") + @skip_if_no("ppmsave_target") + def test_connection_ppm(self): + x = pyvips.Target.new_to_memory() + self.mono.ppmsave_target(x) + y = pyvips.Source.new_from_memory(x.get("blob")) + im = pyvips.Image.ppmload_source(y) + + assert (im - self.mono).abs().max() == 0 + +if __name__ == '__main__': + pytest.main() diff --git a/test/test-suite/test_conversion.py b/test/test-suite/test_conversion.py index 0ee88574..94b29b90 100644 --- a/test/test-suite/test_conversion.py +++ b/test/test-suite/test_conversion.py @@ -1,17 +1,24 @@ # vim: set fileencoding=utf-8 : +import filecmp from functools import reduce + +import os import pytest +import tempfile +import shutil import pyvips -from helpers import JPEG_FILE, unsigned_formats, \ +from helpers import IMAGES, JPEG_FILE, unsigned_formats, \ signed_formats, float_formats, int_formats, \ noncomplex_formats, all_formats, max_value, \ sizeof_format, rot45_angles, rot45_angle_bonds, \ rot_angles, rot_angle_bonds, run_cmp, run_cmp2, \ - assert_almost_equal_objects + assert_almost_equal_objects, temp_filename class TestConversion: + tempdir = None + # run a function on an image, # 50,50 and 10,10 should have different values on the test image # don't loop over band elements @@ -37,6 +44,7 @@ class TestConversion: @classmethod def setup_class(cls): + cls.tempdir = tempfile.mkdtemp() im = pyvips.Image.mask_ideal(100, 100, 0.5, reject=True, optical=True) cls.colour = (im * [1, 2, 3] + [2, 3, 4]).copy(interpretation="srgb") @@ -44,6 +52,14 @@ class TestConversion: cls.all_images = [cls.mono, cls.colour] cls.image = pyvips.Image.jpegload(JPEG_FILE) + @classmethod + def teardown_class(cls): + shutil.rmtree(cls.tempdir, ignore_errors=True) + cls.colour = None + cls.mono = None + cls.image = None + cls.all_images = None + def test_band_and(self): def band_and(x): if isinstance(x, pyvips.Image): @@ -731,6 +747,39 @@ class TestConversion: diff = (after - im).abs().max() assert diff == 0 + def test_autorot(self): + rotation_images = os.path.join(IMAGES, 'rotation') + files = os.listdir(rotation_images) + files.sort() + + meta = { + 0: {'w': 290, 'h': 442}, + 1: {'w': 308, 'h': 410}, + 2: {'w': 308, 'h': 410}, + 3: {'w': 308, 'h': 410}, + 4: {'w': 308, 'h': 410}, + 5: {'w': 231, 'h': 308}, + 6: {'w': 231, 'h': 308}, + 7: {'w': 231, 'h': 308}, + 8: {'w': 231, 'h': 308}, + } + + i = 0 + for f in files: + if '.autorot.' not in f and not f.startswith('.'): + source_filename = os.path.join(rotation_images, f) + + actual_filename = temp_filename(self.tempdir, '.jpg') + + pyvips.Image.new_from_file(source_filename).autorot().write_to_file(actual_filename) + + actual = pyvips.Image.new_from_file(actual_filename) + + assert actual.width == meta[i]['w'] + assert actual.height == meta[i]['h'] + assert actual.get('orientation') if actual.get_typeof('orientation') else None is None + i = i + 1 + def test_scaleimage(self): for fmt in noncomplex_formats: test = self.colour.cast(fmt) diff --git a/test/test-suite/test_convolution.py b/test/test-suite/test_convolution.py index 1778c5c2..54fc0bb3 100644 --- a/test/test-suite/test_convolution.py +++ b/test/test-suite/test_convolution.py @@ -55,6 +55,17 @@ class TestConvolution: [-1, -2, -1]]) cls.all_masks = [cls.sharp, cls.blur, cls.line, cls.sobel] + @classmethod + def teardown_class(cls): + cls.colour = None + cls.mono = None + cls.all_images = None + cls.sharp = None + cls.blur = None + cls.line = None + cls.sobel = None + cls.all_masks = None + def test_conv(self): for im in self.all_images: for msk in self.all_masks: diff --git a/test/test-suite/test_create.py b/test/test-suite/test_create.py index aecac264..6dd9c35c 100644 --- a/test/test-suite/test_create.py +++ b/test/test-suite/test_create.py @@ -121,8 +121,8 @@ class TestCreate: sigma = im.deviate() mean = im.avg() - assert pytest.approx(sigma, 0.2) == 10 - assert pytest.approx(mean, 0.2) == 100 + assert sigma == pytest.approx(10, abs=0.4) + assert mean == pytest.approx(100, abs=0.4) def test_grey(self): im = pyvips.Image.grey(100, 90) @@ -197,11 +197,28 @@ class TestCreate: p = im(255, 0) assert_almost_equal_objects(p, [1, 1, 1]) p = im(0.2 * 255, 0) - assert pytest.approx(p[0], 0.1) == 0.1 + assert p[0] == pytest.approx(0.1, abs=0.1) p = im(0.3 * 255, 0) - assert pytest.approx(p[1], 0.1) == 0.1 + assert p[1] == pytest.approx(0.1, abs=0.1) p = im(0.1 * 255, 0) - assert pytest.approx(p[2], 0.1) == 0.1 + assert p[2] == pytest.approx(0.1, abs=0.1) + + def test_matrixinvert(self): + # 4x4 matrix to check if PLU decomposition works + mat = pyvips.Image.new_from_array([[4, 0, 0, 0], + [0, 0, 2, 0], + [0, 1, 2, 0], + [1, 0, 0, 1]]) + im = mat.matrixinvert() + assert im.width == 4 + assert im.height == 4 + assert im.bands == 1 + assert im.format == pyvips.BandFormat.DOUBLE + + p = im(0, 0) + assert p[0] == 0.25 + p = im(3, 3) + assert p[0] == 1.0 def test_logmat(self): im = pyvips.Image.logmat(1, 0.1) @@ -237,7 +254,7 @@ class TestCreate: assert im.height == 128 assert im.bands == 1 assert im.format == pyvips.BandFormat.FLOAT - assert pytest.approx(im.max(), 0.01) == 1 + assert im.max() == pytest.approx(1, abs=0.01) p = im(32, 32) assert p[0] == 1.0 @@ -275,7 +292,7 @@ class TestCreate: assert im.height == 128 assert im.bands == 1 assert im.format == pyvips.BandFormat.FLOAT - assert pytest.approx(im.min(), 0.01) == 0 + assert im.min() == pytest.approx(0, abs=0.01) p = im(0, 0) assert p[0] == 0.0 v, x, y = im.maxpos() @@ -288,7 +305,7 @@ class TestCreate: assert im.height == 128 assert im.bands == 1 assert im.format == pyvips.BandFormat.UCHAR - assert pytest.approx(im.min(), 0.01) == 0 + assert im.min() == pytest.approx(0, abs=0.01) p = im(64, 64) assert p[0] == 255 @@ -300,7 +317,7 @@ class TestCreate: assert im.bands == 1 assert im.format == pyvips.BandFormat.FLOAT p = im(45, 0) - assert pytest.approx(p[0], 0.0001) == 1.0 + assert p[0] == pytest.approx(1.0, abs=0.0001) v, x, y = im.minpos() assert x == 64 assert y == 64 @@ -318,7 +335,7 @@ class TestCreate: assert im.height == 128 assert im.bands == 1 assert im.format == pyvips.BandFormat.FLOAT - assert pytest.approx(im.max(), 0.01) == 1 + assert im.max() == pytest.approx(1, abs=0.01) p = im(32, 32) assert p[0] == 1.0 @@ -329,7 +346,7 @@ class TestCreate: assert im.height == 128 assert im.bands == 1 assert im.format == pyvips.BandFormat.FLOAT - assert pytest.approx(im.min(), 0.01) == 0 + assert im.min() == pytest.approx(0, abs=0.01) p = im(0, 0) assert p[0] == 0.0 @@ -341,7 +358,7 @@ class TestCreate: assert im.bands == 1 assert im.format == pyvips.BandFormat.FLOAT p = im(45, 0) - assert pytest.approx(p[0], 0.001) == 1.0 + assert p[0] == pytest.approx(1.0, abs=0.001) def test_mask_ideal_band(self): im = pyvips.Image.mask_ideal_band(128, 128, 0.5, 0.5, 0.7) @@ -349,7 +366,7 @@ class TestCreate: assert im.height == 128 assert im.bands == 1 assert im.format == pyvips.BandFormat.FLOAT - assert pytest.approx(im.max(), 0.01) == 1 + assert im.max() == pytest.approx(1, abs=0.01) p = im(32, 32) assert p[0] == 1.0 @@ -360,7 +377,7 @@ class TestCreate: assert im.height == 128 assert im.bands == 1 assert im.format == pyvips.BandFormat.FLOAT - assert pytest.approx(im.min(), 0.01) == 0 + assert im.min() == pytest.approx(0, abs=0.01) p = im(0, 0) assert p[0] == 0.0 @@ -372,7 +389,7 @@ class TestCreate: assert im.bands == 1 assert im.format == pyvips.BandFormat.FLOAT p = im(45, 0) - assert pytest.approx(p[0], 0.001) == 1.0 + assert p[0] == pytest.approx(1, abs=0.001) def test_sines(self): im = pyvips.Image.sines(128, 128) @@ -392,6 +409,12 @@ class TestCreate: assert im.max() == 255 assert im.min() == 0 + # test autofit + im = pyvips.Image.text("Hello, world!", width=500, height=500) + # quite a large threshold, since we need to work with a huge range of + # text rendering systems + assert abs(im.width - 500) < 50 + def test_tonelut(self): im = pyvips.Image.tonelut() assert im.bands == 1 diff --git a/test/test-suite/test_foreign.py b/test/test-suite/test_foreign.py index 6af9654a..182f8af8 100644 --- a/test/test-suite/test_foreign.py +++ b/test/test-suite/test_foreign.py @@ -1,5 +1,5 @@ # vim: set fileencoding=utf-8 : - +import filecmp import sys import os import shutil @@ -11,8 +11,13 @@ from helpers import \ JPEG_FILE, SRGB_FILE, MATLAB_FILE, PNG_FILE, TIF_FILE, OME_FILE, \ ANALYZE_FILE, GIF_FILE, WEBP_FILE, EXR_FILE, FITS_FILE, OPENSLIDE_FILE, \ PDF_FILE, SVG_FILE, SVGZ_FILE, SVG_GZ_FILE, GIF_ANIM_FILE, DICOM_FILE, \ - BMP_FILE, NIFTI_FILE, ICO_FILE, HEIC_FILE, \ - temp_filename, assert_almost_equal_objects, have, skip_if_no + BMP_FILE, NIFTI_FILE, ICO_FILE, HEIC_FILE, TRUNCATED_FILE, \ + GIF_ANIM_EXPECTED_PNG_FILE, GIF_ANIM_DISPOSE_BACKGROUND_FILE, \ + GIF_ANIM_DISPOSE_BACKGROUND_EXPECTED_PNG_FILE, \ + GIF_ANIM_DISPOSE_PREVIOUS_FILE, \ + GIF_ANIM_DISPOSE_PREVIOUS_EXPECTED_PNG_FILE, \ + temp_filename, assert_almost_equal_objects, have, skip_if_no, \ + TIF1_FILE, TIF2_FILE, TIF4_FILE class TestForeign: @@ -23,10 +28,10 @@ class TestForeign: cls.tempdir = tempfile.mkdtemp() cls.colour = pyvips.Image.jpegload(JPEG_FILE) - cls.mono = cls.colour.extract_band(1) + cls.mono = cls.colour.extract_band(1).copy() # we remove the ICC profile: the RGB one will no longer be appropriate cls.mono.remove("icc-profile-data") - cls.rad = cls.colour.float2rad() + cls.rad = cls.colour.float2rad().copy() cls.rad.remove("icc-profile-data") cls.cmyk = cls.colour.bandjoin(cls.mono) cls.cmyk = cls.cmyk.copy(interpretation=pyvips.Interpretation.CMYK) @@ -38,6 +43,11 @@ class TestForeign: @classmethod def teardown_class(cls): shutil.rmtree(cls.tempdir, ignore_errors=True) + cls.colour = None + cls.mono = None + cls.rad = None + cls.cmyk = None + cls.onebit = None # we have test files for formats which have a clear standard def file_loader(self, loader, test_file, validate): @@ -124,11 +134,13 @@ class TestForeign: def test_jpeg(self): def jpeg_valid(im): a = im(10, 10) - assert_almost_equal_objects(a, [6, 5, 3]) + # different versions of libjpeg decode have slightly different + # rounding + assert_almost_equal_objects(a, [141, 127, 90], threshold=3) profile = im.get("icc-profile-data") - assert len(profile) == 1352 - assert im.width == 1024 - assert im.height == 768 + assert len(profile) == 564 + assert im.width == 290 + assert im.height == 442 assert im.bands == 3 self.file_loader("jpegload", JPEG_FILE, jpeg_valid) @@ -160,6 +172,7 @@ class TestForeign: # can remove orientation, save, load again, orientation # has reset + x = x.copy() x.remove("orientation") filename = temp_filename(self.tempdir, '.jpg') @@ -183,6 +196,18 @@ class TestForeign: assert x1.width == x2.height assert x1.height == x2.width + # sets incorrect orientation, save, load again, orientation + # has reset to 1 + x = x.copy() + x.set("orientation", 256) + + filename = temp_filename(self.tempdir, '.jpg') + x.write_to_file(filename) + + x = pyvips.Image.new_from_file(filename) + y = x.get("orientation") + assert y == 1 + # can set, save and reload ASCII string fields x = pyvips.Image.new_from_file(JPEG_FILE) x = x.copy() @@ -233,6 +258,44 @@ class TestForeign: # format area at the end assert y.startswith("hello world") + @skip_if_no("jpegload") + def test_jpegsave(self): + im = pyvips.Image.new_from_file(JPEG_FILE) + + q10 = im.jpegsave_buffer(Q=10) + q10_subsample_auto = im.jpegsave_buffer(Q=10, subsample_mode="auto") + q10_subsample_on = im.jpegsave_buffer(Q=10, subsample_mode="on") + q10_subsample_off = im.jpegsave_buffer(Q=10, subsample_mode="off") + + q90 = im.jpegsave_buffer(Q=90) + q90_subsample_auto = im.jpegsave_buffer(Q=90, subsample_mode="auto") + q90_subsample_on = im.jpegsave_buffer(Q=90, subsample_mode="on") + q90_subsample_off = im.jpegsave_buffer(Q=90, subsample_mode="off") + + # higher Q should mean a bigger buffer + assert len(q90) > len(q10) + + assert len(q10_subsample_auto) == len(q10) + assert len(q10_subsample_on) == len(q10_subsample_auto) + assert len(q10_subsample_off) > len(q10) + + assert len(q90_subsample_auto) == len(q90) + assert len(q90_subsample_on) < len(q90) + assert len(q90_subsample_off) == len(q90_subsample_auto) + + @skip_if_no("jpegload") + def test_truncated(self): + # This should open (there's enough there for the header) + im = pyvips.Image.new_from_file(TRUNCATED_FILE) + # but this should fail with a warning, and knock TRUNCATED_FILE out of + # the cache + x = im.avg() + + # now we should open again, but it won't come from cache, it'll reload + im = pyvips.Image.new_from_file(TRUNCATED_FILE) + # and this should fail with a warning once more + x = im.avg() + @skip_if_no("pngload") def test_png(self): def png_valid(im): @@ -250,6 +313,22 @@ class TestForeign: self.save_load_file(".png", "[interlace]", self.colour, 0) self.save_load_file(".png", "[interlace]", self.mono, 0) + # size of a regular mono PNG + len_mono = len(self.mono.write_to_buffer(".png")) + + # 4-bit should be smaller + len_mono4 = len(self.mono.write_to_buffer(".png", bitdepth=4)) + assert( len_mono4 < len_mono ) + + len_mono2 = len(self.mono.write_to_buffer(".png", bitdepth=2)) + assert( len_mono2 < len_mono4 ) + + len_mono1 = len(self.mono.write_to_buffer(".png", bitdepth=1)) + assert( len_mono1 < len_mono2 ) + + # we can't test palette save since we can't be sure libimagequant is + # available and there's no easy test for its presence + @skip_if_no("tiffload") def test_tiff(self): def tiff_valid(im): @@ -261,6 +340,40 @@ class TestForeign: self.file_loader("tiffload", TIF_FILE, tiff_valid) self.buffer_loader("tiffload_buffer", TIF_FILE, tiff_valid) + + def tiff1_valid(im): + a = im(127, 0) + assert_almost_equal_objects(a, [0.0]) + a = im(128, 0) + assert_almost_equal_objects(a, [255.0]) + assert im.width == 256 + assert im.height == 4 + assert im.bands == 1 + + self.file_loader("tiffload", TIF1_FILE, tiff1_valid) + + def tiff2_valid(im): + a = im(127, 0) + assert_almost_equal_objects(a, [85.0]) + a = im(128, 0) + assert_almost_equal_objects(a, [170.0]) + assert im.width == 256 + assert im.height == 4 + assert im.bands == 1 + + self.file_loader("tiffload", TIF2_FILE, tiff2_valid) + + def tiff4_valid(im): + a = im(127, 0) + assert_almost_equal_objects(a, [119.0]) + a = im(128, 0) + assert_almost_equal_objects(a, [136.0]) + assert im.width == 256 + assert im.height == 4 + assert im.bands == 1 + + self.file_loader("tiffload", TIF4_FILE, tiff4_valid) + if pyvips.at_least_libvips(8, 5): self.save_load_buffer("tiffsave_buffer", "tiffload_buffer", @@ -270,22 +383,31 @@ class TestForeign: self.save_load("%s.tif", self.cmyk) self.save_load("%s.tif", self.onebit) - self.save_load_file(".tif", "[squash]", self.onebit, 0) + self.save_load_file(".tif", "[bitdepth=1]", self.onebit, 0) self.save_load_file(".tif", "[miniswhite]", self.onebit, 0) - self.save_load_file(".tif", "[squash,miniswhite]", self.onebit, 0) + self.save_load_file(".tif", "[bitdepth=1,miniswhite]", self.onebit, 0) self.save_load_file(".tif", "[profile={0}]".format(SRGB_FILE), self.colour, 0) self.save_load_file(".tif", "[tile]", self.colour, 0) self.save_load_file(".tif", "[tile,pyramid]", self.colour, 0) + self.save_load_file(".tif", "[tile,pyramid,subifd]", self.colour, 0) self.save_load_file(".tif", "[tile,pyramid,compression=jpeg]", self.colour, 80) + self.save_load_file(".tif", + "[tile,pyramid,subifd,compression=jpeg]", + self.colour, 80) self.save_load_file(".tif", "[bigtiff]", self.colour, 0) self.save_load_file(".tif", "[compression=jpeg]", self.colour, 80) self.save_load_file(".tif", "[tile,tile-width=256]", self.colour, 10) + im = pyvips.Image.new_from_file(TIF2_FILE) + self.save_load_file(".tif", "[bitdepth=2]", im, 0) + im = pyvips.Image.new_from_file(TIF4_FILE) + self.save_load_file(".tif", "[bitdepth=4]", im, 0) + filename = temp_filename(self.tempdir, '.tif') x = pyvips.Image.new_from_file(TIF_FILE) x = x.copy() @@ -303,6 +425,7 @@ class TestForeign: x = pyvips.Image.new_from_file(filename) y = x.get("orientation") assert y == 2 + x = x.copy() x.remove("orientation") filename = temp_filename(self.tempdir, '.tif') @@ -387,11 +510,14 @@ class TestForeign: assert a.height == b.height assert a.avg() == b.avg() - # region-shrink added in 8.7 x = pyvips.Image.new_from_file(TIF_FILE) buf = x.tiffsave_buffer(tile=True, pyramid=True, region_shrink="mean") buf = x.tiffsave_buffer(tile=True, pyramid=True, region_shrink="mode") buf = x.tiffsave_buffer(tile=True, pyramid=True, region_shrink="median") + buf = x.tiffsave_buffer(tile=True, pyramid=True, region_shrink="max") + buf = x.tiffsave_buffer(tile=True, pyramid=True, region_shrink="min") + buf = x.tiffsave_buffer(tile=True, pyramid=True, + region_shrink="nearest") @skip_if_no("magickload") def test_magickload(self): @@ -542,6 +668,7 @@ class TestForeign: if have("gifload"): x1 = pyvips.Image.new_from_file(GIF_ANIM_FILE, n=-1) w1 = x1.webpsave_buffer(Q=10) + x2 = pyvips.Image.new_from_buffer(w1, "", n=-1) assert x1.width == x2.width assert x1.height == x2.height @@ -670,6 +797,38 @@ class TestForeign: x2 = pyvips.Image.new_from_file(GIF_ANIM_FILE, page=1, n=-1) assert x2.height == 4 * x1.height + animation = pyvips.Image.new_from_file(GIF_ANIM_FILE, n=-1) + filename = temp_filename(self.tempdir, '.png') + animation.write_to_file(filename) + # Uncomment to see output file + # animation.write_to_file('cogs.png') + + assert filecmp.cmp(GIF_ANIM_EXPECTED_PNG_FILE, filename, shallow=False) + + @skip_if_no("gifload") + def test_gifload_animation_dispose_background(self): + animation = pyvips.Image.new_from_file(GIF_ANIM_DISPOSE_BACKGROUND_FILE, n=-1) + + filename = temp_filename(self.tempdir, '.png') + animation.write_to_file(filename) + + # Uncomment to see output file + # animation.write_to_file('dispose-background.png') + + assert filecmp.cmp(GIF_ANIM_DISPOSE_BACKGROUND_EXPECTED_PNG_FILE, filename, shallow=False) + + @skip_if_no("gifload") + def test_gifload_animation_dispose_previous(self): + animation = pyvips.Image.new_from_file(GIF_ANIM_DISPOSE_PREVIOUS_FILE, n=-1) + + filename = temp_filename(self.tempdir, '.png') + animation.write_to_file(filename) + + # Uncomment to see output file + # animation.write_to_file('dispose-previous.png') + + assert filecmp.cmp(GIF_ANIM_DISPOSE_PREVIOUS_EXPECTED_PNG_FILE, filename, shallow=False) + @skip_if_no("svgload") def test_svgload(self): def svg_valid(im): @@ -726,10 +885,10 @@ class TestForeign: self.colour.dzsave(filename, suffix=".png") # test horizontal overlap ... expect 256 step, overlap 1 - x = pyvips.Image.new_from_file(filename + "_files/10/0_0.png") + x = pyvips.Image.new_from_file(filename + "_files/9/0_0.png") assert x.width == 255 - y = pyvips.Image.new_from_file(filename + "_files/10/1_0.png") - assert y.width == 256 + y = pyvips.Image.new_from_file(filename + "_files/9/1_0.png") + assert y.width == 37 # the right two columns of x should equal the left two columns of y left = x.crop(x.width - 2, 0, 2, x.height) @@ -738,8 +897,8 @@ class TestForeign: # test vertical overlap assert x.height == 255 - y = pyvips.Image.new_from_file(filename + "_files/10/0_1.png") - assert y.height == 256 + y = pyvips.Image.new_from_file(filename + "_files/9/0_1.png") + assert y.height == 189 # the bottom two rows of x should equal the top two rows of y top = x.crop(0, x.height - 2, x.width, 2) @@ -751,19 +910,19 @@ class TestForeign: assert x.width == 1 assert x.height == 1 - # 10 should be the final layer - assert not os.path.isdir(filename + "_files/11") + # 9 should be the final layer + assert not os.path.isdir(filename + "_files/10") # default google layout filename = temp_filename(self.tempdir, '') self.colour.dzsave(filename, layout="google") # test bottom-right tile ... default is 256x256 tiles, overlap 0 - x = pyvips.Image.new_from_file(filename + "/2/2/3.jpg") + x = pyvips.Image.new_from_file(filename + "/1/1/1.jpg") assert x.width == 256 assert x.height == 256 - assert not os.path.exists(filename + "/2/2/4.jpg") - assert not os.path.exists(filename + "/3") + assert not os.path.exists(filename + "/1/1/2.jpg") + assert not os.path.exists(filename + "/2") x = pyvips.Image.new_from_file(filename + "/blank.png") assert x.width == 256 assert x.height == 256 @@ -773,8 +932,10 @@ class TestForeign: # overlap 1, 510x510 pixels, 256 pixel tiles, should be exactly 2x2 # tiles, though in fact the bottom and right edges will be white filename = temp_filename(self.tempdir, '') - self.colour.crop(0, 0, 510, 510).dzsave(filename, layout="google", - overlap=1, depth="one") + self.colour \ + .replicate(2, 2) \ + .crop(0, 0, 510, 510) \ + .dzsave(filename, layout="google", overlap=1, depth="one") x = pyvips.Image.new_from_file(filename + "/0/1/1.jpg") assert x.width == 256 @@ -784,8 +945,10 @@ class TestForeign: # with 511x511, it'll fit exactly into 2x2 -- we we actually generate # 3x3, since we output the overlaps filename = temp_filename(self.tempdir, '') - self.colour.crop(0, 0, 511, 511).dzsave(filename, layout="google", - overlap=1, depth="one") + self.colour \ + .replicate(2, 2) \ + .crop(0, 0, 511, 511) \ + .dzsave(filename, layout="google", overlap=1, depth="one") x = pyvips.Image.new_from_file(filename + "/0/2/2.jpg") assert x.width == 256 @@ -798,7 +961,7 @@ class TestForeign: # 256x256 tiles, no overlap assert os.path.exists(filename + "/ImageProperties.xml") - x = pyvips.Image.new_from_file(filename + "/TileGroup0/2-3-2.jpg") + x = pyvips.Image.new_from_file(filename + "/TileGroup0/1-0-0.jpg") assert x.width == 256 assert x.height == 256 @@ -819,23 +982,23 @@ class TestForeign: filename = temp_filename(self.tempdir, '') self.colour.dzsave(filename, suffix=".png") - x = pyvips.Image.new_from_file(filename + "_files/10/0_0.png") + x = pyvips.Image.new_from_file(filename + "_files/9/0_0.png") assert x.width == 255 # test overlap filename = temp_filename(self.tempdir, '') self.colour.dzsave(filename, overlap=200) - y = pyvips.Image.new_from_file(filename + "_files/10/1_1.jpeg") - assert y.width == 654 + y = pyvips.Image.new_from_file(filename + "_files/9/1_1.jpeg") + assert y.width == 236 # test tile-size filename = temp_filename(self.tempdir, '') self.colour.dzsave(filename, tile_size=512) - y = pyvips.Image.new_from_file(filename + "_files/10/0_0.jpeg") - assert y.width == 513 - assert y.height == 513 + y = pyvips.Image.new_from_file(filename + "_files/9/0_0.jpeg") + assert y.width == 290 + assert y.height == 442 # test save to memory buffer filename = temp_filename(self.tempdir, '.zip') @@ -862,9 +1025,9 @@ class TestForeign: a = im(10, 10) # different versions of HEIC decode have slightly different # rounding - assert_almost_equal_objects(a, [75.0, 86.0, 81.0], threshold=2) - assert im.width == 4032 - assert im.height == 3024 + assert_almost_equal_objects(a, [197.0, 181.0, 158.0], threshold=2) + assert im.width == 3024 + assert im.height == 4032 assert im.bands == 3 self.file_loader("heifload", HEIC_FILE, heif_valid) diff --git a/test/test-suite/test_histogram.py b/test/test-suite/test_histogram.py index a709dcfa..9c0ed1ed 100644 --- a/test/test-suite/test_histogram.py +++ b/test/test-suite/test_histogram.py @@ -96,7 +96,7 @@ class TestHistogram: ent = im.hist_find().hist_entropy() - assert pytest.approx(ent, 0.01) == 4.37 + assert pytest.approx(ent, 0.01) == 6.67 def test_stdif(self): im = pyvips.Image.new_from_file(JPEG_FILE) diff --git a/test/test-suite/test_iofuncs.py b/test/test-suite/test_iofuncs.py index 4d076f0e..2a02a48d 100644 --- a/test/test-suite/test_iofuncs.py +++ b/test/test-suite/test_iofuncs.py @@ -8,6 +8,8 @@ from helpers import assert_equal_objects class TestIofuncs: # test the vips7 filename splitter ... this is very fragile and annoying # code with lots of cases + + @pytest.mark.xfail(raises=AttributeError, reason="uses deprecated symbols") def test_split7(self): def split(path): filename7 = pyvips.path_filename7(path) diff --git a/test/test-suite/test_mosaicing.py b/test/test-suite/test_mosaicing.py new file mode 100644 index 00000000..f9e85be5 --- /dev/null +++ b/test/test-suite/test_mosaicing.py @@ -0,0 +1,43 @@ +# vim: set fileencoding=utf-8 : +import pytest + +import pyvips +from helpers import MOSAIC_FILES, MOSAIC_MARKS, MOSAIC_VERTICAL_MARKS + +class TestMosaicing: + def test_mosaic(self): + # ported from https://github.com/libvips/nip2/tree/master/share/nip2/data/examples/1_point_mosaic + + mosaiced_image = None + + for i in range(0, len(MOSAIC_FILES), 2): + files = MOSAIC_FILES[i:i + 2] + marks = MOSAIC_MARKS[i:i + 2] + + im = pyvips.Image.new_from_file(files[0]) + sec_im = pyvips.Image.new_from_file(files[1]) + horizontal_part = im.mosaic(sec_im, pyvips.Direction.HORIZONTAL, + marks[0][0], marks[0][1], marks[1][0], marks[1][1]) + + if mosaiced_image is None: + mosaiced_image = horizontal_part + else: + vertical_marks = MOSAIC_VERTICAL_MARKS[i - 2:i] + mosaiced_image = mosaiced_image.mosaic(horizontal_part, pyvips.Direction.VERTICAL, + vertical_marks[1][0], vertical_marks[1][1], + vertical_marks[0][0], vertical_marks[0][1]) + + mosaiced_image = mosaiced_image.globalbalance() + + # Uncomment to see output file + # mosaiced_image.write_to_file('1-pt-mosaic.jpg') + + # hard to test much more than this + assert mosaiced_image.width == 1005 + assert mosaiced_image.height == 1295 + assert mosaiced_image.interpretation == pyvips.Interpretation.B_W + assert mosaiced_image.format == pyvips.BandFormat.FLOAT + assert mosaiced_image.bands == 1 + +if __name__ == '__main__': + pytest.main() diff --git a/test/test-suite/test_resample.py b/test/test-suite/test_resample.py index 791a30c7..ceb2eecf 100644 --- a/test/test-suite/test_resample.py +++ b/test/test-suite/test_resample.py @@ -2,7 +2,7 @@ import pytest import pyvips -from helpers import JPEG_FILE, all_formats, have +from helpers import JPEG_FILE, OME_FILE, HEIC_FILE, all_formats, have # Run a function expecting a complex image on a two-band image @@ -103,8 +103,10 @@ class TestResample: def test_resize(self): im = pyvips.Image.new_from_file(JPEG_FILE) im2 = im.resize(0.25) - assert im2.width == round(im.width / 4.0) - assert im2.height == round(im.height / 4.0) + # in py3, round() does not round to nearest in the obvious way, so we + # have to do it by hand + assert im2.width == int(im.width / 4.0 + 0.5) + assert im2.height == int(im.height / 4.0 + 0.5) # test geometry rounding corner case im = pyvips.Image.black(100, 1) @@ -115,13 +117,15 @@ class TestResample: def test_shrink(self): im = pyvips.Image.new_from_file(JPEG_FILE) im2 = im.shrink(4, 4) - assert im2.width == round(im.width / 4.0) - assert im2.height == round(im.height / 4.0) + # in py3, round() does not round to nearest in the obvious way, so we + # have to do it by hand + assert im2.width == int(im.width / 4.0 + 0.5) + assert im2.height == int(im.height / 4.0 + 0.5) assert abs(im.avg() - im2.avg()) < 1 im2 = im.shrink(2.5, 2.5) - assert im2.width == round(im.width / 2.5) - assert im2.height == round(im.height / 2.5) + assert im2.width == int(im.width / 2.5 + 0.5) + assert im2.height == int(im.height / 2.5 + 0.5) assert abs(im.avg() - im2.avg()) < 1 @pytest.mark.skipif(not pyvips.at_least_libvips(8, 5), @@ -129,7 +133,7 @@ class TestResample: def test_thumbnail(self): im = pyvips.Image.thumbnail(JPEG_FILE, 100) - assert im.width == 100 + assert im.height == 100 assert im.bands == 3 assert im.bands == 3 @@ -138,9 +142,9 @@ class TestResample: assert abs(im_orig.avg() - im.avg()) < 1 # make sure we always get the right width - for width in range(1000, 1, -13): - im = pyvips.Image.thumbnail(JPEG_FILE, width) - assert im.width == width + for height in range(440, 1, -13): + im = pyvips.Image.thumbnail(JPEG_FILE, height) + assert im.height == height # should fit one of width or height im = pyvips.Image.thumbnail(JPEG_FILE, 100, height=300) @@ -162,6 +166,35 @@ class TestResample: im2 = pyvips.Image.thumbnail_buffer(buf, 100) assert abs(im1.avg() - im2.avg()) < 1 + # should be able to thumbnail many-page tiff + im = pyvips.Image.thumbnail(OME_FILE, 100) + assert im.width == 100 + assert im.height == 38 + + # should be able to thumbnail individual pages from many-page tiff + im1 = pyvips.Image.thumbnail(OME_FILE + "[page=0]", 100) + assert im1.width == 100 + assert im1.height == 38 + im2 = pyvips.Image.thumbnail(OME_FILE + "[page=1]", 100) + assert im2.width == 100 + assert im2.height == 38 + assert (im1 - im2).abs().max() != 0 + + # should be able to thumbnail entire many-page tiff as a toilet-roll + # image + im = pyvips.Image.thumbnail(OME_FILE + "[n=-1]", 100) + assert im.width == 100 + assert im.height == 570 + + if have("heifload"): + # this image is orientation 6 ... thumbnail should flip it + im = pyvips.Image.new_from_file(HEIC_FILE) + thumb = pyvips.Image.thumbnail(HEIC_FILE, 100) + + # thumb should be portrait + assert thumb.width < thumb.height + assert thumb.height == 100 + def test_similarity(self): im = pyvips.Image.new_from_file(JPEG_FILE) im2 = im.similarity(angle=90) @@ -196,7 +229,7 @@ class TestResample: # distorted, but the rest should not be too bad a = r.crop(50, 0, im.width - 50, im.height).gaussblur(2) b = im.crop(50, 0, im.width - 50, im.height).gaussblur(2) - assert (a - b).abs().max() < 20 + assert (a - b).abs().max() < 40 # this was a bug at one point, strangely, if executed with debug # enabled diff --git a/test/test_cli.sh b/test/test_cli.sh index 6e2d8a94..633445af 100755 --- a/test/test_cli.sh +++ b/test/test_cli.sh @@ -82,18 +82,17 @@ test_thumbnail() { exit 1 fi if [ $height -ne $correct_height ]; then - echo width is $height, not $correct_height + echo height is $height, not $correct_height exit 1 fi echo "ok" } -test_thumbnail 100 100 75 -test_thumbnail 100x100 100 75 -test_thumbnail x100 133 100 -test_thumbnail "100x100<" 1024 768 -test_thumbnail "2000<" 2000 1500 -test_thumbnail "100x100>" 100 75 -test_thumbnail "2000>" 1024 768 - +test_thumbnail 100 66 100 +test_thumbnail 100x100 66 100 +test_thumbnail x100 66 100 +test_thumbnail "100x100<" 290 442 +test_thumbnail "2000<" 1312 2000 +test_thumbnail "100x100>" 66 100 +test_thumbnail "2000>" 290 442 diff --git a/test/test_connections.c b/test/test_connections.c new file mode 100644 index 00000000..61366b06 --- /dev/null +++ b/test/test_connections.c @@ -0,0 +1,158 @@ +/* Test stream*u. + */ + +#include +#include +#include +#include +#include +#include + +typedef struct _MyInput { + const char *filename; + const unsigned char *contents; + size_t length; + size_t read_position; +} MyInput; + +typedef struct _MyOutput { + const char *filename; + int fd; +} MyOutput; + +static gint64 +read_cb( VipsSourceCustom *source_custom, + void *buffer, gint64 length, MyInput *my_input ) +{ + gint64 bytes_read = VIPS_MIN( length, + my_input->length - my_input->read_position ); + + /* + printf( "read_cb: buffer = 0x%p, length = %zd\n", buffer, length ); + */ + + memcpy( buffer, + my_input->contents + my_input->read_position, bytes_read ); + my_input->read_position += bytes_read; + + return( bytes_read ); +} + +static gint64 +seek_cb( VipsSourceCustom *source_custom, + gint64 offset, int whence, MyInput *my_input ) +{ + gint64 new_pos; + + /* + printf( "seek_cb: offset = %zd, whence = %d\n", offset, whence ); + */ + + switch( whence ) { + case SEEK_SET: + new_pos = offset; + break; + + case SEEK_CUR: + new_pos = my_input->read_position + offset; + break; + + case SEEK_END: + new_pos = my_input->length + offset; + break; + + default: + vips_error( "demo", "%s", "bad 'whence'" ); + return( -1 ); + } + + my_input->read_position = VIPS_CLIP( 0, new_pos, my_input->length ); + + return( my_input->read_position ); +} + +static gint64 +write_cb( VipsTargetCustom *target_custom, + const void *data, gint64 length, MyOutput *my_output ) +{ + gint64 bytes_written; + + /* + printf( "write_cb: data = 0x%p, length = %zd\n", data, length ); + */ + + bytes_written = write( my_output->fd, data, length ); + + return( bytes_written ); +} + +static void +finish_cb( VipsTargetCustom *target_custom, MyOutput *my_output ) +{ + /* + printf( "finish_cb:\n" ); + */ + + close( my_output->fd ); + my_output->fd = -1; +} + +int +main( int argc, char **argv ) +{ + MyInput my_input; + MyOutput my_output; + VipsSourceCustom *source_custom; + VipsTargetCustom *target_custom; + VipsImage *image; + + if( VIPS_INIT( NULL ) ) + return( -1 ); + + if( argc != 3 ) + vips_error_exit( "usage: %s in-file out-file.png", argv[0] ); + + my_input.filename = argv[1]; + my_input.contents = NULL; + my_input.length = 0; + my_input.read_position = 0; + + if( !g_file_get_contents( my_input.filename, + (char **) &my_input.contents, &my_input.length, NULL ) ) + vips_error_exit( "unable to load from %s", my_input.filename ); + + source_custom = vips_source_custom_new(); + g_signal_connect( source_custom, "seek", + G_CALLBACK( seek_cb ), &my_input ); + g_signal_connect( source_custom, "read", + G_CALLBACK( read_cb ), &my_input ); + + if( !(image = vips_image_new_from_source( + VIPS_SOURCE( source_custom ), "", + "access", VIPS_ACCESS_SEQUENTIAL, + NULL )) ) + vips_error_exit( NULL ); + + my_output.filename = argv[2]; + my_output.fd = -1; + + if( (my_output.fd = vips__open( my_output.filename, + O_WRONLY | O_CREAT | O_TRUNC, 0644 )) == -1 ) + vips_error_exit( "unable to save to %s", my_output.filename ); + + target_custom = vips_target_custom_new(); + g_signal_connect( target_custom, "write", + G_CALLBACK( write_cb ), &my_output ); + g_signal_connect( target_custom, "finish", + G_CALLBACK( finish_cb ), &my_output ); + + if( vips_image_write_to_target( image, ".png", + VIPS_TARGET( target_custom ), NULL ) ) + vips_error_exit( NULL ); + + VIPS_UNREF( image ); + VIPS_UNREF( source_custom ); + VIPS_UNREF( target_custom ); + + return( 0 ); +} diff --git a/test/test_connections.sh b/test/test_connections.sh new file mode 100755 index 00000000..de59c9a1 --- /dev/null +++ b/test/test_connections.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +# test load and save via custom connection + +# set -x +set -e + +. ./variables.sh + +if test_supported jpegload_source; then + ./test_connections $image $tmp/x.png + + # test max difference < 10 + test_difference $image $tmp/x.png 10 +fi diff --git a/test/test_descriptors.c b/test/test_descriptors.c new file mode 100644 index 00000000..64c3cda4 --- /dev/null +++ b/test/test_descriptors.c @@ -0,0 +1,107 @@ +/* Read an image and check that file handles are being closed on minimise. + * + * This will only work on linux: we signal success and do nothing if /dev/proc + * does not exist. + */ + +#include +#include + +#include + +/* Count the number of files in a directory, -1 for directory not found etc. + */ +static int +count_files( const char *dirname ) +{ + GDir *dir; + int n; + + if( !(dir = g_dir_open( dirname, 0, NULL )) ) + return( -1 ); + + for( n = 0; g_dir_read_name( dir ); n++ ) + ; + + g_dir_close( dir ); + + return( n ); +} + +int +main( int argc, char **argv ) +{ + VipsSource *source; + VipsImage *image, *x; + char fd_dir[256]; + int n_files; + double average; + + if( VIPS_INIT( argv[0] ) ) + vips_error_exit( "unable to start" ); + + if( argc != 2 ) + vips_error_exit( "usage: %s test-image", argv[0] ); + + vips_snprintf( fd_dir, 256, "/proc/%d/fd", getpid() ); + n_files = count_files( fd_dir ); + if( n_files == -1 ) + /* Probably not linux, silent success. + */ + return( 0 ); + + /* This is usually 4. stdout / stdin / stderr plus one more made for + * us by glib, I think, doing what I don't know. + */ + + /* Opening an image should read the header, then close the fd. + */ + printf( "** seq open ..\n" ); + if( !(source = vips_source_new_from_file( argv[1] )) ) + vips_error_exit( NULL ); + if( !(image = vips_image_new_from_source( source, "", + "access", VIPS_ACCESS_SEQUENTIAL, + NULL )) ) + vips_error_exit( NULL ); + if( count_files( fd_dir ) != n_files ) + vips_error_exit( "%s: fd not closed after header read", + argv[1] ); + + /* We should be able to read a chunk near the top, then have the fd + * closed again. + */ + printf( "** crop1, avg ..\n" ); + if( vips_crop( image, &x, 0, 0, image->Xsize, 10, NULL ) || + vips_avg( x, &average, NULL ) ) + vips_error_exit( NULL ); + g_object_unref( x ); + if( count_files( fd_dir ) != n_files ) + vips_error_exit( "%s: fd not closed after first read", + argv[1] ); + + /* We should be able to read again, a little further down, and have + * the input restarted and closed again. + */ + printf( "** crop2, avg ..\n" ); + if( vips_crop( image, &x, 0, 20, image->Xsize, 10, NULL ) || + vips_avg( x, &average, NULL ) ) + vips_error_exit( NULL ); + g_object_unref( x ); + if( count_files( fd_dir ) != n_files ) + vips_error_exit( "%s: fd not closed after second read", + argv[1] ); + + /* Clean up, and we should still just have three open. + */ + printf( "** unref ..\n" ); + g_object_unref( image ); + g_object_unref( source ); + printf( "** shutdown ..\n" ); + vips_shutdown(); + + if( count_files( fd_dir ) != n_files ) + vips_error_exit( "%s: fd not closed after shutdown", + argv[1] ); + + return( 0 ); +} diff --git a/test/test_descriptors.sh b/test/test_descriptors.sh new file mode 100755 index 00000000..8e0df7cc --- /dev/null +++ b/test/test_descriptors.sh @@ -0,0 +1,32 @@ +#!/bin/sh + +# test the various restartable loaders + +# webp and ppm use streams, but they mmap the input, so you can't close() the +# fd on minimise + +# set -x +set -e + +. ./variables.sh + +if test_supported jpegload_source; then + ./test_descriptors $image +fi + +if test_supported pngload_source; then + ./test_descriptors $test_images/sample.png +fi + +if test_supported tiffload_source; then + ./test_descriptors $test_images/sample.tif +fi + +if test_supported radload_source; then + ./test_descriptors $test_images/sample.hdr +fi + +if test_supported svgload_source; then + ./test_descriptors $test_images/logo.svg +fi + diff --git a/test/test_formats.sh b/test/test_formats.sh index 60a4cccc..4d7b8319 100755 --- a/test/test_formats.sh +++ b/test/test_formats.sh @@ -12,10 +12,6 @@ set -e poppler=$test_images/blankpage.pdf poppler_ref=$test_images/blankpage.pdf.png -# rsvg / svgload reference image -rsvg=$test_images/blankpage.svg -rsvg_ref=$test_images/blankpage.svg.png - # giflib / gifload reference image giflib=$test_images/trans-x.gif giflib_ref=$test_images/trans-x.png @@ -55,30 +51,6 @@ save_load() { fi } -# is a difference beyond a threshold? return 0 (meaning all ok) or 1 (meaning -# error, or outside threshold) -break_threshold() { - diff=$1 - threshold=$2 - return $(echo "$diff <= $threshold" | bc -l) -} - -# subtract, look for max difference less than a threshold -test_difference() { - before=$1 - after=$2 - threshold=$3 - - $vips subtract $before $after $tmp/difference.v - $vips abs $tmp/difference.v $tmp/abs.v - dif=$($vips max $tmp/abs.v) - - if break_threshold $dif $threshold; then - echo "save / load difference is $dif" - exit 1 - fi -} - # save to the named file in tmp, convert back to vips again, subtract, look # for max difference less than a threshold test_format() { @@ -171,20 +143,6 @@ test_saver() { echo "ok" } -# test for file format supported -test_supported() { - format=$1 - - if $vips $format > /dev/null 2>&1; then - result=0 - else - echo "support for $format not configured, skipping test" - result=1 - fi - - return $result -} - test_format $image v 0 if test_supported tiffload; then test_format $image tif 0 @@ -234,10 +192,7 @@ if test_supported pdfload; then test_loader $poppler_ref $poppler pdfload 0 fi -if test_supported svgload; then - # librsvg can give small differences on some platforms - test_loader $rsvg_ref $rsvg svgload 10 -fi +# don't test SVG --- the output varies too much between librsvg versions if test_supported gifload; then test_loader $giflib_ref $giflib gifload 0 diff --git a/test/test_stall.sh b/test/test_stall.sh new file mode 100755 index 00000000..c1050c3c --- /dev/null +++ b/test/test_stall.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +# set -x +set -e + +. ./variables.sh + +if test_supported tiffload; then + VIPS_STALL=1 $vips copy $image $tmp/x.tif + cat > $tmp/mask.con < /dev/null 2>&1; then + result=0 + else + echo "support for $format not configured, skipping test" + result=1 + fi + + return $result +} + +# is a difference beyond a threshold? return 0 (meaning all ok) or 1 (meaning +# error, or outside threshold) +break_threshold() { + diff=$1 + threshold=$2 + return $(echo "$diff <= $threshold" | bc -l) +} + +# subtract, look for max difference less than a threshold +test_difference() { + before=$1 + after=$2 + threshold=$3 + + $vips subtract $before $after $tmp/difference.v + $vips abs $tmp/difference.v $tmp/abs.v + dif=$($vips max $tmp/abs.v) + + if break_threshold $dif $threshold; then + echo "save / load difference is $dif" + exit 1 + fi +} diff --git a/tools/Makefile.am b/tools/Makefile.am index b8e2bb9e..2b2ace33 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -20,11 +20,11 @@ bin_SCRIPTS = \ batch_rubber_sheet \ batch_crop \ vipsprofile \ - vips-8.9 + vips-8.10 EXTRA_DIST = \ vipsprofile \ - vips-8.9 \ + vips-8.10 \ light_correct.in \ shrink_width.in \ batch_image_convert.in \ diff --git a/tools/vips-8.9 b/tools/vips-8.10 similarity index 100% rename from tools/vips-8.9 rename to tools/vips-8.10 diff --git a/tools/vips.c b/tools/vips.c index f1074502..6d0990be 100644 --- a/tools/vips.c +++ b/tools/vips.c @@ -42,6 +42,9 @@ * - parse options in two passes (thanks Haida) * 26/11/17 * - remove throw() decls, they are now deprecated everywhere + * 18/6/20 kleisauke + * - avoid using vips7 symbols + * - remove deprecated vips7 C++ generator */ /* @@ -92,9 +95,12 @@ #include #include -#include #include +#if VIPS_ENABLE_DEPRECATED +#include +#endif + #ifdef OS_WIN32 #define strcasecmp(a,b) _stricmp(a,b) #endif @@ -180,6 +186,7 @@ static GOptionEntry main_option[] = { { NULL } }; +#if VIPS_ENABLE_DEPRECATED typedef void *(*map_name_fn)( im_function * ); /* Loop over a package. @@ -241,13 +248,18 @@ list_function( im_function *func ) return( NULL ); } +#endif static int print_list( int argc, char **argv ) { +#if VIPS_ENABLE_DEPRECATED if( !argv[0] || strcmp( argv[0], "packages" ) == 0 ) im_map_packages( (VSListMap2Fn) list_package, NULL ); else if( strcmp( argv[0], "classes" ) == 0 ) +#else + if( !argv[0] || strcmp( argv[0], "classes" ) == 0 ) +#endif vips_type_map_all( g_type_from_name( "VipsObject" ), (VipsTypeMapFn) list_class, NULL ); else if( g_type_from_name( argv[0] ) && @@ -256,13 +268,18 @@ print_list( int argc, char **argv ) (VipsTypeMapFn) list_class, NULL ); } else { +#if VIPS_ENABLE_DEPRECATED if( map_name( argv[0], list_function ) ) vips_error_exit( "unknown package \"%s\"", argv[0] ); +#else + vips_error_exit( "unknown operation \"%s\"", argv[0] ); +#endif } return( 0 ); } +#if VIPS_ENABLE_DEPRECATED /* Print "ln -s" lines for this package. */ static void * @@ -301,6 +318,7 @@ has_print( im_function *fn ) return( 0 ); } +#endif static int isvips( const char *name ) @@ -313,6 +331,7 @@ isvips( const char *name ) return( vips_isprefix( "vips", name ) ); } +#if VIPS_ENABLE_DEPRECATED /* Print a usage string from an im_function descriptor. */ static void @@ -382,600 +401,7 @@ usage( im_function *fn ) fprintf( stderr, "\n" ); } - -/* Convert VIPS type name to C++ type name. NULL for type unsupported by C++ - * layer. - */ -static char * -vips2cpp( im_type_desc *ty ) -{ - int k; - - /* VIPS types. - */ - static char *vtypes[] = { - IM_TYPE_DOUBLE, - IM_TYPE_INT, - IM_TYPE_COMPLEX, - IM_TYPE_STRING, - IM_TYPE_IMAGE, - IM_TYPE_IMASK, - IM_TYPE_DMASK, - IM_TYPE_DISPLAY, - IM_TYPE_IMAGEVEC, - IM_TYPE_DOUBLEVEC, - IM_TYPE_INTVEC, - IM_TYPE_INTERPOLATE - }; - - /* Corresponding C++ types. - */ - static char *ctypes[] = { - "double", - "int", - "std::complex", - "char*", - "VImage", - "VIMask", - "VDMask", - "VDisplay", - "std::vector", - "std::vector", - "std::vector", - "char*" - }; - - for( k = 0; k < IM_NUMBER( vtypes ); k++ ) - if( strcmp( ty->type, vtypes[k] ) == 0 ) - return( ctypes[k] ); - - return( NULL ); -} - -/* Test a function definition for C++ suitability. - */ -static int -is_cppable( im_function *fn ) -{ - int j; - - /* Don't wrap im_remainderconst_vec(). - * - * This has been replaced by the saner name im_remainder_vec(). If we - * generate wrappers for both names we get a overloading clash. - */ - if( strcmp( fn->name, "im_remainderconst_vec" ) == 0 ) - return( 0 ); - - /* Check we know all the types. - */ - for( j = 0; j < fn->argc; j++ ) { - im_type_desc *ty = fn->argv[j].desc; - - if( !vips2cpp( ty ) ) - return( 0 ); - } - - /* We dont wrap output IMAGEVEC/DOUBLEVEC/INTVEC. - */ - for( j = 0; j < fn->argc; j++ ) { - im_type_desc *ty = fn->argv[j].desc; - - if( ty->flags & IM_TYPE_OUTPUT ) - if( strcmp( ty->type, IM_TYPE_IMAGEVEC ) == 0 || - strcmp( ty->type, IM_TYPE_DOUBLEVEC ) == 0 || - strcmp( ty->type, IM_TYPE_INTVEC ) == 0 ) - return( 0 ); - } - - /* Must be at least one image argument (input or output) ... since we - * get inserted in the VImage class. Other funcs get wrapped by hand. - */ - for( j = 0; j < fn->argc; j++ ) - if( strcmp( fn->argv[j].desc->type, IM_TYPE_IMAGE ) == 0 ) - break; - if( j == fn->argc ) - return( 0 ); - - return( -1 ); -} - -/* Search for the first output arg, and the first IMAGE input arg. - */ -static void -find_ioargs( im_function *fn, int *ia, int *oa ) -{ - int j; - - /* Look for first output arg - this will be the result of the - * function. - */ - *oa = -1; - for( j = 0; j < fn->argc; j++ ) { - im_type_desc *ty = fn->argv[j].desc; - - if( ty->flags & IM_TYPE_OUTPUT ) { - *oa = j; - break; - } - } - - /* Look for first input IMAGE arg. This will become the implicit - * "this" arg. - */ - *ia = -1; - for( j = 0; j < fn->argc; j++ ) { - im_type_desc *ty = fn->argv[j].desc; - - if( !(ty->flags & IM_TYPE_OUTPUT) && - strcmp( ty->type, IM_TYPE_IMAGE ) == 0 ) { - *ia = j; - break; - } - } -} - -static gboolean -drop_postfix( char *str, const char *postfix ) -{ - if( vips_ispostfix( str, postfix ) ) { - str[strlen( str ) - strlen( postfix )] = '\0'; - - return( TRUE ); - } - - return( FALSE ); -} - -/* Turn a VIPS name into a C++ name. Eg. im_lintra_vec becomes lin. - */ -static void -c2cpp_name( const char *in, char *out ) -{ - static const char *dont_drop[] = { - "_set", - }; - static const char *drop[] = { - "_vec", - "const", - "tra", - "set", - "_f" - }; - - int i; - gboolean changed; - - /* Copy, chopping off "im_" prefix. - */ - if( vips_isprefix( "im_", in ) ) - strcpy( out, in + 3 ); - else - strcpy( out, in ); - - /* Repeatedly drop postfixes while we can. Stop if we see a dont_drop - * postfix. - */ - do { - gboolean found; - - found = FALSE; - for( i = 0; i < IM_NUMBER( dont_drop ); i++ ) - if( vips_ispostfix( out, dont_drop[i] ) ) { - found = TRUE; - break; - } - if( found ) - break; - - changed = FALSE; - for( i = 0; i < IM_NUMBER( drop ); i++ ) - changed |= drop_postfix( out, drop[i] ); - } while( changed ); -} - -/* Print prototype for a function (ie. will be followed by code). - * - * Eg.: - * VImage VImage::lin( double a, double b ) - */ -static void * -print_cppproto( im_function *fn ) -{ - int j; - char name[4096]; - int oa, ia; - int flg; - - /* If it's not cppable, do nothing. - */ - if( !is_cppable( fn ) ) - return( NULL ); - - /* Make C++ name. - */ - c2cpp_name( fn->name, name ); - - /* Find input and output args. - */ - find_ioargs( fn, &ia, &oa ); - - /* Print output type. - */ - if( oa == -1 ) - printf( "void " ); - else - printf( "%s ", vips2cpp( fn->argv[oa].desc ) ); - - printf( "VImage::%s(", name ); - - /* Print arg list. - */ - flg = 0; - for( j = 0; j < fn->argc; j++ ) { - im_type_desc *ty = fn->argv[j].desc; - - /* Skip ia and oa. - */ - if( j == ia || j == oa ) - continue; - - /* Print arg type. - */ - if( flg ) - printf( ", %s", vips2cpp( ty ) ); - else { - printf( " %s", vips2cpp( ty ) ); - flg = 1; - } - - /* If it's an putput arg, print a "&" to make a reference - * argument. - */ - if( ty->flags & IM_TYPE_OUTPUT ) - printf( "&" ); - - /* Print arg name. - */ - printf( " %s", fn->argv[j].name ); - } - - /* End of arg list! - */ - if( flg ) - printf( " " ); - printf( ")\n" ); - - return( NULL ); -} - -/* Print cpp decl for a function. - * - * Eg. - * VImage lin( double, double ) - */ -static void * -print_cppdecl( im_function *fn ) -{ - int j; - char name[4096]; - int oa, ia; - int flg; - - /* If it's not cppable, do nothing. - */ - if( !is_cppable( fn ) ) - return( NULL ); - - /* Make C++ name. - */ - c2cpp_name( fn->name, name ); - - /* Find input and output args. - */ - find_ioargs( fn, &ia, &oa ); - if( ia == -1 ) - /* No input image, so make it a static in the class - * declaration. - */ - printf( "static " ); - - /* Print output type. - */ - if( oa == -1 ) - printf( "void " ); - else - printf( "%s ", vips2cpp( fn->argv[oa].desc ) ); - - /* Print function name and start arg list. - */ - printf( "%s(", name ); - - /* Print arg list. - */ - flg = 0; - for( j = 0; j < fn->argc; j++ ) { - im_type_desc *ty = fn->argv[j].desc; - - /* Skip ia and oa. - */ - if( j == ia || j == oa ) - continue; - - /* Print arg type. - */ - if( flg ) - printf( ", %s", vips2cpp( ty ) ); - else { - printf( " %s", vips2cpp( ty ) ); - flg = 1; - } - - /* If it's an putput arg, print a "&" to make a reference - * argument. - */ - if( ty->flags & IM_TYPE_OUTPUT ) - printf( "&" ); - - /* Print arg name. - * - * Prepend the member name to make the arg - * unique. This is important for SWIG since it needs to have - * unique names for %apply. - */ - printf( " %s_%s", name, fn->argv[j].name ); - } - - /* End of arg list! - */ - if( flg ) - printf( " " ); - - printf( ");\n" ); - - return( NULL ); -} - -static void -print_invec( int j, const char *arg, - const char *vips_name, const char *c_name, const char *extract ) -{ - printf( "\t((%s*) _vec.data(%d))->n = %s.size();\n", - vips_name, j, arg ); - printf( "\t((%s*) _vec.data(%d))->vec = new %s[%s.size()];\n", - vips_name, j, c_name, arg ); - printf( "\tfor( unsigned int i = 0; i < %s.size(); i++ )\n", - arg ); - printf( "\t\t((%s*) _vec.data(%d))->vec[i] = %s[i]%s;\n", - vips_name, j, arg, extract ); -} - -/* Print the definition for a function. - */ -static void * -print_cppdef( im_function *fn ) -{ - int j; - int ia, oa; - - /* If it's not cppable, do nothing. - */ - if( !is_cppable( fn ) ) - return( NULL ); - - find_ioargs( fn, &ia, &oa ); - - printf( "// %s: %s\n", fn->name, _( fn->desc ) ); - print_cppproto( fn ); - printf( "{\n" ); - - /* Declare the implicit input image. - */ - if( ia != -1 ) - printf( "\tVImage %s = *this;\n", fn->argv[ia].name ); - - /* Declare return value, if any. - */ - if( oa != -1 ) - printf( "\t%s %s;\n\n", - vips2cpp( fn->argv[oa].desc ), - fn->argv[oa].name ); - - /* Declare the arg vector. - */ - printf( "\tVargv _vec( \"%s\" );\n\n", fn->name ); - - /* Create the input args. - */ - for( j = 0; j < fn->argc; j++ ) { - im_type_desc *ty = fn->argv[j].desc; - - /* Images are special - have to init the vector, even - * for output args. Have to translate VImage. - */ - if( strcmp( ty->type, IM_TYPE_IMAGE ) == 0 ) { - printf( "\t_vec.data(%d) = %s.image();\n", - j, fn->argv[j].name ); - continue; - } - - /* For output masks, we have to set an input filename. Not - * freed, so constant string is OK. - */ - if( (ty->flags & IM_TYPE_OUTPUT) && - (strcmp( ty->type, IM_TYPE_IMASK ) == 0 || - strcmp( ty->type, IM_TYPE_DMASK ) == 0) ) { - printf( "\t((im_mask_object*) _vec.data(%d))->name = " - "(char*)\"noname\";\n", j ); - continue; - } - - /* Skip other output args. - */ - if( ty->flags & IM_TYPE_OUTPUT ) - continue; - - if( strcmp( ty->type, IM_TYPE_IMASK ) == 0 ) - /* Mask types are different - have to use - * im_mask_object. - */ - printf( "\t((im_mask_object*) " - "_vec.data(%d))->mask = %s.mask().iptr;\n", - j, fn->argv[j].name ); - else if( strcmp( ty->type, IM_TYPE_DMASK ) == 0 ) - printf( "\t((im_mask_object*) " - "_vec.data(%d))->mask = %s.mask().dptr;\n", - j, fn->argv[j].name ); - else if( strcmp( ty->type, IM_TYPE_DISPLAY ) == 0 ) - /* Display have to use VDisplay. - */ - printf( "\t_vec.data(%d) = %s.disp();\n", - j, fn->argv[j].name ); - else if( strcmp( ty->type, IM_TYPE_STRING ) == 0 ) - /* Zap input strings directly into _vec. - */ - printf( "\t_vec.data(%d) = (im_object) %s;\n", - j, fn->argv[j].name ); - else if( strcmp( ty->type, IM_TYPE_IMAGEVEC ) == 0 ) - print_invec( j, fn->argv[j].name, - "im_imagevec_object", "IMAGE *", ".image()" ); - else if( strcmp( ty->type, IM_TYPE_DOUBLEVEC ) == 0 ) - print_invec( j, fn->argv[j].name, - "im_doublevec_object", "double", "" ); - else if( strcmp( ty->type, IM_TYPE_INTVEC ) == 0 ) - print_invec( j, fn->argv[j].name, - "im_intvec_object", "int", "" ); - else if( strcmp( ty->type, IM_TYPE_INTERPOLATE ) == 0 ) { - printf( "\tif( vips__input_interpolate_init( " - "&_vec.data(%d), %s ) )\n", - j, fn->argv[j].name ); - printf( "\t\tverror();\n" ); - } - else - /* Just use vips2cpp(). - */ - printf( "\t*((%s*) _vec.data(%d)) = %s;\n", - vips2cpp( ty ), j, fn->argv[j].name ); - } - - /* Call function. - */ - printf( "\t_vec.call();\n" ); - - /* Extract output args. - */ - for( j = 0; j < fn->argc; j++ ) { - im_type_desc *ty = fn->argv[j].desc; - - /* Skip input args. - */ - if( !(ty->flags & IM_TYPE_OUTPUT) ) - continue; - - /* Skip images (done on input side, really). - */ - if( strcmp( ty->type, IM_TYPE_IMAGE ) == 0 ) - continue; - - if( strcmp( ty->type, IM_TYPE_IMASK ) == 0 || - strcmp( ty->type, IM_TYPE_DMASK ) == 0 ) - /* Mask types are different - have to use - * im_mask_object. - */ - printf( "\t%s.embed( (DOUBLEMASK *)((im_mask_object*)" - "_vec.data(%d))->mask );\n", - fn->argv[j].name, j ); - else if( strcmp( ty->type, IM_TYPE_STRING ) == 0 ) - /* Strings are grabbed out of the vec. - */ - printf( "\t%s = (char*) _vec.data(%d);\n", - fn->argv[j].name, j ); - else - /* Just use vips2cpp(). - */ - printf( "\t%s = *((%s*)_vec.data(%d));\n", - fn->argv[j].name, vips2cpp( ty ), j ); - } - - /* Note dependancies if out is an image and this function uses - * PIO. - */ - if( oa != -1 ) { - im_type_desc *ty = fn->argv[oa].desc; - - if( strcmp( ty->type, IM_TYPE_IMAGE ) == 0 && - (fn->flags & IM_FN_PIO) ) { - /* Loop for all input args again .. - */ - for( j = 0; j < fn->argc; j++ ) { - im_type_desc *ty2 = fn->argv[j].desc; - - /* Skip output args. - */ - if( ty2->flags & IM_TYPE_OUTPUT ) - continue; - - /* Input image. - */ - if( strcmp( ty2->type, IM_TYPE_IMAGE ) == 0 ) - printf( "\t%s._ref->addref( " - "%s._ref );\n", - fn->argv[oa].name, - fn->argv[j].name ); - else if( strcmp( ty2->type, IM_TYPE_IMAGEVEC ) - == 0 ) { - /* The out depends on every image in - * the input vector. - */ - printf( "\tfor( unsigned int i = 0; " - "i < %s.size(); i++ )\n", - fn->argv[j].name ); - printf( "\t\t%s._ref->addref( " - "%s[i]._ref );\n", - fn->argv[oa].name, - fn->argv[j].name ); - } - } - } - } - - /* Return result. - */ - if( oa != -1 ) - printf( "\n\treturn( %s );\n", fn->argv[oa].name ); - - printf( "}\n\n" ); - - return( NULL ); -} - -/* Print C++ decls for function, package or all. - */ -static int -print_cppdecls( int argc, char **argv ) -{ - printf( "// this file automatically generated from\n" - "// VIPS library %s\n", vips_version_string() ); - - if( map_name( argv[0], print_cppdecl ) ) - vips_error_exit( NULL ); - - return( 0 ); -} - -/* Print C++ bindings for function, package or all. - */ -static int -print_cppdefs( int argc, char **argv ) -{ - printf( "// this file automatically generated from\n" - "// VIPS library %s\n", vips_version_string() ); - - if( map_name( argv[0], print_cppdef ) ) - vips_error_exit( NULL ); - - return( 0 ); -} +#endif static int print_help( int argc, char **argv ) @@ -1000,14 +426,16 @@ static GOptionEntry empty_options[] = { }; static ActionEntry actions[] = { +#if VIPS_ENABLE_DEPRECATED { "list", N_( "list classes|packages|all|package-name|operation-name" ), +#else + { "list", N_( "list classes|all|operation-name" ), +#endif &empty_options[0], print_list }, - { "cpph", N_( "generate headers for C++ binding" ), - &empty_options[0], print_cppdecls }, - { "cppc", N_( "generate bodies for C++ binding" ), - &empty_options[0], print_cppdefs }, +#if VIPS_ENABLE_DEPRECATED { "links", N_( "generate links for vips/bin" ), &empty_options[0], print_links }, +#endif { "help", N_( "list possible actions" ), &empty_options[0], print_help }, }; @@ -1087,14 +515,16 @@ main( int argc, char **argv ) GOptionGroup *main_group; GOptionGroup *group; VipsOperation *operation; +#if VIPS_ENABLE_DEPRECATED im_function *fn; +#endif int i, j; gboolean handled; GError *error = NULL; if( VIPS_INIT( argv[0] ) ) - vips_error_exit( NULL ); + vips_error_exit( NULL ); textdomain( GETTEXT_PACKAGE ); setlocale( LC_ALL, "" ); @@ -1138,15 +568,11 @@ main( int argc, char **argv ) /* "vips" with no arguments does "vips --help". */ if( argc == 1 ) { -#ifdef HAVE_CONTEXT_GET_HELP char *help; help = g_option_context_get_help( context, TRUE, NULL ); printf( "%s", help ); g_free( help ); -#else /* !HAVE_CONTEXT_GET_HELP */ - printf( "help not available, your glib is too old\n" ); -#endif /* HAVE_CONTEXT_GET_HELP */ exit( 0 ); } @@ -1177,8 +603,18 @@ main( int argc, char **argv ) ; if( main_option_plugin ) { +#if VIPS_ENABLE_DEPRECATED if( !im_load_plugin( main_option_plugin ) ) - vips_error_exit( NULL ); + vips_error_exit( NULL ); +#else /*!VIPS_ENABLE_DEPRECATED*/ + GModule *module; + + module = g_module_open( main_option_plugin, G_MODULE_BIND_LAZY ); + if( !module ) { + vips_error_exit( _( "unable to load \"%s\" -- %s" ), + main_option_plugin, g_module_error() ); + } +#endif } if( main_option_version ) @@ -1236,6 +672,7 @@ main( int argc, char **argv ) break; } +#if VIPS_ENABLE_DEPRECATED /* Could be a vips7 im_function. We need to test for vips7 first, * since we don't want to use the vips7 compat wrappers in vips8 * unless we have to. They don't support all args types. @@ -1258,6 +695,7 @@ main( int argc, char **argv ) if( action && !handled ) vips_error_clear(); +#endif /* Could be a vips8 VipsOperation. */ diff --git a/tools/vipsedit.c b/tools/vipsedit.c index a5b01d72..ef4b48e8 100644 --- a/tools/vipsedit.c +++ b/tools/vipsedit.c @@ -1,21 +1,23 @@ /* modify vips file header! - useful for setting resolution, coding... -very dangerous! -no way of setting non-used codes in variables like newxres -so need flags to show new parameter has been set.. boring -Copyright K.Martinez 30/6/93 -29/7/93 JC - -format added - - ==0 added to strcmp! -17/11/94 JC - - new header fields added -21/10/04 - - more header updates - -22/8/05 - - less-stupid-ified -20/9/05 - - rewritten with glib option parser, ready for xml options to go in - + * very dangerous! + * + * no way of setting non-used codes in variables like newxres + * so need flags to show new parameter has been set.. boring + * Copyright K.Martinez 30/6/93 + * + * 29/7/93 JC + * - format added + * - ==0 added to strcmp! + * 17/11/94 JC + * - new header fields added + * 21/10/04 + * - more header updates + * 22/8/05 + * - less-stupid-ified + * 20/9/05 + * - rewritten with glib option parser, ready for xml options to go in + * 18/6/20 kleisauke + * - avoid using vips7 symbols */ /* @@ -59,7 +61,6 @@ Copyright K.Martinez 30/6/93 #include #include -#include #include #include @@ -119,8 +120,8 @@ parse_pint( char *arg, int *out ) /* Might as well set an upper limit. */ *out = atoi( arg ); - if( *out <= 0 || *out > 1000000 ) - error_exit( _( "'%s' is not a positive integer" ), arg ); + if( *out <= 0 || *out > 1000000 ) + vips_error_exit( _( "'%s' is not a positive integer" ), arg ); } int @@ -129,8 +130,8 @@ main( int argc, char **argv ) GOptionContext *context; GOptionGroup *main_group; GError *error = NULL; - IMAGE *im; - unsigned char header[IM_SIZEOF_HEADER]; + VipsImage *im; + unsigned char header[VIPS_SIZEOF_HEADER]; if( VIPS_INIT( argv[0] ) ) vips_error_exit( "%s", _( "unable to start VIPS" ) ); @@ -158,10 +159,7 @@ main( int argc, char **argv ) if( !g_option_context_parse( context, &argc, &argv, &error ) ) #endif /*HAVE_G_WIN32_GET_COMMAND_LINE*/ { - if( error ) { - fprintf( stderr, "%s\n", error->message ); - g_error_free( error ); - } + vips_g_error( &error ); exit( -1 ); } @@ -178,12 +176,14 @@ main( int argc, char **argv ) exit( -1 ); } - if( !(im = im_init( argv[1] )) || - (im->fd = im__open_image_file( im->filename )) == -1 ) - error_exit( _( "could not open image %s" ), argv[1] ); - if( read( im->fd, header, IM_SIZEOF_HEADER ) != IM_SIZEOF_HEADER || - im__read_header_bytes( im, header ) ) - error_exit( _( "could not read VIPS header for %s" ), + if( !(im = vips_image_new_from_file( argv[1], NULL )) ) + vips_error_exit( _( "could not open image %s" ), argv[1] ); + + vips__seek( im->fd, 0, SEEK_SET ); + if( read( im->fd, header, VIPS_SIZEOF_HEADER ) != + VIPS_SIZEOF_HEADER || + vips__read_header_bytes( im, header ) ) + vips_error_exit( _( "could not read VIPS header for %s" ), im->filename ); if( endian ) { @@ -191,8 +191,8 @@ main( int argc, char **argv ) im->magic = VIPS_MAGIC_INTEL; else if( strcmp( endian, "big" ) == 0 ) im->magic = VIPS_MAGIC_SPARC; - else - error_exit( _( "bad endian-ness %s, " + else + vips_error_exit( _( "bad endian-ness %s, " "should be 'big' or 'little'" ), endian ); } if( xsize ) @@ -202,26 +202,36 @@ main( int argc, char **argv ) if( bands ) parse_pint( bands, &im->Bands ); if( format ) { - VipsBandFormat f; + int f; + + if( (f = vips_enum_from_nick( argv[0], + VIPS_TYPE_BAND_FORMAT, format )) < 0 ) + vips_error_exit( _( "bad format %s" ), format ); - if( (f = im_char2BandFmt( format )) < 0 ) - error_exit( _( "bad format %s" ), format ); im->BandFmt = f; - im->Bbits = im_bits_of_fmt( f ); + + /* We don't use this, but make sure it's set in case any + * old binaries are expecting it. + */ + im->Bbits = vips_format_sizeof( f ) << 3; } if( interpretation ) { - VipsInterpretation i; + int i; - if( (i = im_char2Type( interpretation )) < 0 ) - error_exit( _( "bad interpretation %s" ), + if( (i = vips_enum_from_nick( argv[0], + VIPS_TYPE_INTERPRETATION, interpretation )) < 0 ) + vips_error_exit( _( "bad interpretation %s" ), interpretation ); + im->Type = i; } if( coding ) { - VipsCoding c; + int c; + + if( (c = vips_enum_from_nick( argv[0], + VIPS_TYPE_CODING, coding )) < 0 ) + vips_error_exit( _( "bad coding %s" ), coding ); - if( (c = im_char2Coding( coding )) < 0 ) - error_exit( _( "bad coding %s" ), coding ); im->Coding = c; } if( xres ) @@ -233,31 +243,31 @@ main( int argc, char **argv ) if( yoffset ) im->Yoffset = atoi( yoffset ); - if( lseek( im->fd, 0, SEEK_SET ) == (off_t) -1 ) - error_exit( _( "could not seek on %s" ), im->filename ); - if( im__write_header_bytes( im, header ) || - im__write( im->fd, header, IM_SIZEOF_HEADER ) ) - error_exit( _( "could not write to %s" ), im->filename ); + if( vips__seek( im->fd, 0, SEEK_SET ) == (off_t) -1 ) + vips_error_exit( _( "could not seek on %s" ), im->filename ); + if( vips__write_header_bytes( im, header ) || + vips__write( im->fd, header, VIPS_SIZEOF_HEADER ) ) + vips_error_exit( _( "could not write to %s" ), im->filename ); if( setext ) { char *xml; size_t size; - if( !(xml = im__file_read( stdin, "stdin", &size )) ) - error_exit( "%s", _( "could not get ext data" ) ); + if( !(xml = vips__file_read( stdin, "stdin", &size )) ) + vips_error_exit( "%s", _( "could not get ext data" ) ); /* Strip trailing whitespace ... we can get stray \n at the - * end, eg. "echo | editvips --setext fred.v". + * end, eg. "echo | vipsedit --setext fred.v". */ while( size > 0 && isspace( xml[size - 1] ) ) size -= 1; - if( im__write_extension_block( im, xml, size ) ) - error_exit( "%s", _( "could not set extension" ) ); - im_free( xml ); + if( vips__write_extension_block( im, xml, size ) ) + vips_error_exit( "%s", _( "could not set extension" ) ); + g_free( xml ); } - im_close( im ); + g_object_unref( im ); /* We don't free this on error exit, sadly. */ diff --git a/tools/vipsheader.c b/tools/vipsheader.c index f6114719..a984079a 100644 --- a/tools/vipsheader.c +++ b/tools/vipsheader.c @@ -38,6 +38,8 @@ * functions, so "header" is now obsolete * 27/2/13 * - convert to vips8 API + * 29/6/20 + * - allow "stdin" as a filename */ /* @@ -109,7 +111,8 @@ print_field_fn( VipsImage *image, const char *field, GValue *value, void *a ) char str[256]; VipsBuf buf = VIPS_BUF_STATIC( str ); - if( *many ) + if( *many && + image->filename ) printf( "%s: ", image->filename ); printf( "%s: ", field ); @@ -123,33 +126,35 @@ print_field_fn( VipsImage *image, const char *field, GValue *value, void *a ) /* Print header, or parts of header. */ static int -print_header( VipsImage *im, gboolean many ) +print_header( VipsImage *image, gboolean many ) { if( !main_option_field ) { - printf( "%s: ", im->filename ); + if( image->filename ) + printf( "%s: ", image->filename ); - vips_object_print_summary( VIPS_OBJECT( im ) ); + vips_object_print_summary( VIPS_OBJECT( image ) ); if( main_option_all ) - (void) vips_image_map( im, print_field_fn, &many ); + (void) vips_image_map( image, print_field_fn, &many ); } else if( strcmp( main_option_field, "getext" ) == 0 ) { - if( vips__has_extension_block( im ) ) { + if( vips__has_extension_block( image ) ) { void *buf; int size; - if( !(buf = vips__read_extension_block( im, &size )) ) + if( !(buf = + vips__read_extension_block( image, &size )) ) return( -1 ); printf( "%s", (char *) buf ); g_free( buf ); } } else if( strcmp( main_option_field, "Hist" ) == 0 ) - printf( "%s", vips_image_get_history( im ) ); + printf( "%s", vips_image_get_history( image ) ); else { char *str; - if( vips_image_get_as_string( im, main_option_field, &str ) ) + if( vips_image_get_as_string( image, main_option_field, &str ) ) return( -1 ); printf( "%s\n", str ); g_free( str ); @@ -205,21 +210,39 @@ main( int argc, char *argv[] ) result = 0; for( i = 1; argv[i]; i++ ) { - VipsImage *im; + VipsImage *image; + char filename[VIPS_PATH_MAX]; + char option_string[VIPS_PATH_MAX]; - if( !(im = vips_image_new_from_file( argv[i], NULL )) ) { + vips__filename_split8( argv[i], filename, option_string ); + if( strcmp( filename, "stdin" ) == 0 ) { + VipsSource *source; + + if( !(source = vips_source_new_from_descriptor( 0 )) ) + return( -1 ); + if( !(image = vips_image_new_from_source( source, + option_string, NULL )) ) { + VIPS_UNREF( source ); + return( -1 ); + } + VIPS_UNREF( source ); + } + else { + if( !(image = + vips_image_new_from_file( argv[i], NULL )) ) { + print_error(); + result = 1; + } + } + + if( image && + print_header( image, argv[2] != NULL ) ) { print_error(); result = 1; } - if( im && - print_header( im, argv[2] != NULL ) ) { - print_error(); - result = 1; - } - - if( im ) - g_object_unref( im ); + if( image ) + g_object_unref( image ); } /* We don't free this on error exit, sadly. diff --git a/tools/vipsthumbnail.c b/tools/vipsthumbnail.c index 961d447d..d917ff7f 100644 --- a/tools/vipsthumbnail.c +++ b/tools/vipsthumbnail.c @@ -96,6 +96,12 @@ * - add --intent * 23/10/17 * - --size Nx didn't work, argh ... thanks jrochkind + * 3/2/20 + * - add --no-rotate + * - add --import-profile / --export-profile names + * - back to -o for output + * 29/2/20 + * - deprecate --delete */ #ifdef HAVE_CONFIG_H @@ -124,39 +130,36 @@ static VipsSize size_restriction = VIPS_SIZE_BOTH; static char *output_format = "tn_%s.jpg"; static char *export_profile = NULL; static char *import_profile = NULL; -static gboolean delete_profile = FALSE; static gboolean linear_processing = FALSE; static gboolean crop_image = FALSE; +static gboolean no_rotate_image = FALSE; static char *smartcrop_image = NULL; -static gboolean rotate_image = FALSE; static char *thumbnail_intent = NULL; /* Deprecated and unused. */ +static gboolean delete_profile = FALSE; static gboolean nosharpen = FALSE; static gboolean nodelete_profile = FALSE; static gboolean verbose = FALSE; static char *convolution_mask = NULL; static char *interpolator = NULL; +static gboolean rotate_image = FALSE; static GOptionEntry options[] = { { "size", 's', 0, G_OPTION_ARG_STRING, &thumbnail_size, N_( "shrink to SIZE or to WIDTHxHEIGHT" ), N_( "SIZE" ) }, - { "output", 'o', G_OPTION_FLAG_HIDDEN, + { "output", 'o', 0, G_OPTION_ARG_STRING, &output_format, - N_( "set output to FORMAT" ), + N_( "output to FORMAT" ), N_( "FORMAT" ) }, - { "format", 'f', 0, - G_OPTION_ARG_STRING, &output_format, - N_( "set output format string to FORMAT" ), - N_( "FORMAT" ) }, - { "eprofile", 'e', 0, + { "export-profile", 'e', 0, G_OPTION_ARG_FILENAME, &export_profile, N_( "export with PROFILE" ), N_( "PROFILE" ) }, - { "iprofile", 'i', 0, + { "import-profile", 'i', 0, G_OPTION_ARG_FILENAME, &import_profile, N_( "import untagged images with PROFILE" ), N_( "PROFILE" ) }, @@ -171,13 +174,28 @@ static GOptionEntry options[] = { G_OPTION_ARG_STRING, &thumbnail_intent, N_( "ICC transform with INTENT" ), N_( "INTENT" ) }, - { "rotate", 't', 0, - G_OPTION_ARG_NONE, &rotate_image, - N_( "auto-rotate" ), NULL }, - { "delete", 'd', 0, + { "delete", 'd', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &delete_profile, - N_( "delete profile from exported image" ), NULL }, + N_( "(deprecated, does nothing)" ), NULL }, + { "no-rotate", 0, 0, + G_OPTION_ARG_NONE, &no_rotate_image, + N_( "don't auto-rotate" ), NULL }, + { "format", 'f', G_OPTION_FLAG_HIDDEN, + G_OPTION_ARG_STRING, &output_format, + N_( "set output format string to FORMAT" ), + N_( "FORMAT" ) }, + { "eprofile", 0, G_OPTION_FLAG_HIDDEN, + G_OPTION_ARG_FILENAME, &export_profile, + N_( "export with PROFILE" ), + N_( "PROFILE" ) }, + { "iprofile", 0, G_OPTION_FLAG_HIDDEN, + G_OPTION_ARG_FILENAME, &import_profile, + N_( "import untagged images with PROFILE" ), + N_( "PROFILE" ) }, + { "rotate", 't', G_OPTION_FLAG_HIDDEN, + G_OPTION_ARG_NONE, &rotate_image, + N_( "(deprecated, does nothing)" ), NULL }, { "crop", 'c', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &crop_image, N_( "(deprecated, crop exactly to SIZE)" ), NULL }, @@ -280,11 +298,11 @@ thumbnail_process( VipsObject *process, const char *filename ) if( vips_thumbnail( filename, &image, thumbnail_width, "height", thumbnail_height, "size", size_restriction, - "auto_rotate", rotate_image, + "no-rotate", no_rotate_image, "crop", interesting, "linear", linear_processing, - "import_profile", import_profile, - "export_profile", export_profile, + "import-profile", import_profile, + "export-profile", export_profile, "intent", intent, NULL ) ) return( -1 );