diff --git a/.editorconfig b/.editorconfig index b570cb16..07050c9a 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,12 +1,20 @@ root = true -[*] +[*.go] +charset = utf-8 indent_style = tab indent_size = 2 end_of_line = lf +trim_trailing_whitespace = true +insert_final_newline = true + +[Makefile] charset = utf-8 +indent_style = tab +indent_size = 2 +end_of_line = lf trim_trailing_whitespace = true insert_final_newline = true -[*.md] -trim_trailing_whitespace = false +[*.yml] +indent_style = space diff --git a/.travis.yml b/.travis.yml index fa62cfcc..11b8a542 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,102 +1,33 @@ language: go -dist: trusty -sudo: false +dist: focal +sudo: required -go: - # - "1.10" - #- "1.11" - - "1.12" - - "1.13" - # - "tip" +services: + - docker env: - # - LIBVIPS=7.42.3 - # - LIBVIPS=8.2.3 - # - LIBVIPS=8.3.3 - # - LIBVIPS=8.4.6 - # - LIBVIPS=8.5.8 - - LIBVIPS=8.6.2 - - LIBVIPS=8.8.4 - - LIBVIPS=8.9.1 - - LIBVIPS=master + # - LIBVIPS=8.6.2 + # - LIBVIPS=8.7.4 + # - LIBVIPS=8.8.4 + # - LIBVIPS=8.9.2 + - LIBVIPS=8.10.1 + - LIBVIPS=8.10.2 matrix: allow_failures: - - env: LIBVIPS=7.42.3 - - env: LIBVIPS=8.2.3 - - env: LIBVIPS=8.3.3 + - env: LIBVIPS=8.8.4 cache: apt: directories: - $HOME/libvips -addons: - apt: - packages: - - gobject-introspection - - gtk-doc-tools - - libcfitsio3-dev - - libfftw3-dev - - libgif-dev - - libgs-dev - - libgsf-1-dev - - libmatio-dev - - libopenslide-dev - - liborc-0.4-dev - - libpango1.0-dev - - libpoppler-glib-dev - - libwebp-dev - -# VIPS 8.3.3 requires Poppler 0.30 which is not released on Trusty. -before_install: - - > - test "$LIBVIPS" != "master" -a "$LIBVIPS" \< "8.4" \ - && wget http://www.vips.ecs.soton.ac.uk/supported/${LIBVIPS%.*}/vips-${LIBVIPS}.tar.gz -O vips.tgz \ - || echo ":-)" - - > - test "$LIBVIPS" != "master" -a "$LIBVIPS" \> "8.4" \ - && wget https://github.com/libvips/libvips/archive/v${LIBVIPS}.tar.gz -O vips.tgz \ - || echo ":-)" - - > - test $LIBVIPS == "master" \ - && wget https://github.com/libvips/libvips/archive/${LIBVIPS}.tar.gz -O vips.tgz \ - || echo ":-)" - - mkdir libvips - - tar xf vips.tgz -C libvips --strip-components 1 - - cd libvips - - test -f autogen.sh && ./autogen.sh || ./bootstrap.sh - - > - CXXFLAGS=-D_GLIBCXX_USE_CXX11_ABI=0 - ./configure - --disable-debug - --disable-dependency-tracking - --disable-introspection - --disable-static - --enable-gtk-doc-html=no - --enable-gtk-doc=no - --enable-pyvips8=no - --without-orc - --without-python - --prefix=$HOME/libvips - $1 - - make - - make install - - cd .. - - export PATH=$PATH:$HOME/libvips/bin - - export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:$HOME/libvips/lib/pkgconfig - - export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$HOME/libvips/lib - - vips --vips-version - install: - - go get -u golang.org/x/lint/golint + - docker build -t h2non/bimg:ci --build-arg LIBVIPS_VERSION=$LIBVIPS . script: - - diff -u <(echo -n) <(gofmt -s -d ./) - - diff -u <(echo -n) <(go vet ./) - - diff -u <(echo -n) <(golint ./) - - go test -v -race -covermode=atomic -coverprofile=coverage.out + - docker run h2non/bimg:ci sh -c 'export LD_LIBRARY_PATH=/vips/lib:/usr/local/lib:$LD_LIBRARY_PATH; export PKG_CONFIG_PATH=/vips/lib/pkgconfig:/usr/local/lib/pkgconfig:/usr/lib/pkgconfig:/usr/X11/lib/pkgconfig; go vet . && golint . && go test -v -race -covermode=atomic -coverprofile=coverage.out' -after_success: - - goveralls -coverprofile=coverage.out -service=travis-ci +# after_success: +# - goveralls -coverprofile=coverage.out -service=travis-ci diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..5a0c29ba --- /dev/null +++ b/Dockerfile @@ -0,0 +1,79 @@ +FROM golang:1.14 +LABEL maintainer "tomas@aparicio.me" + +ARG LIBVIPS_VERSION=8.9.2 +ARG LIBHEIF_VERSION=1.9.1 +ARG GOLANGCILINT_VERSION=1.29.0 + +# Installs libvips + required libraries +RUN DEBIAN_FRONTEND=noninteractive \ + apt-get update && \ + apt-get install --no-install-recommends -y \ + ca-certificates \ + automake build-essential curl \ + gobject-introspection gtk-doc-tools libglib2.0-dev libjpeg62-turbo-dev libpng-dev \ + libwebp-dev libtiff5-dev libgif-dev libexif-dev libxml2-dev libpoppler-glib-dev \ + swig libmagickwand-dev libpango1.0-dev libmatio-dev libopenslide-dev libcfitsio-dev \ + libgsf-1-dev fftw3-dev liborc-0.4-dev librsvg2-dev libimagequant-dev libaom-dev && \ + cd /tmp && \ + curl -fsSLO https://github.com/strukturag/libheif/releases/download/v${LIBHEIF_VERSION}/libheif-${LIBHEIF_VERSION}.tar.gz && \ + tar zvxf libheif-${LIBHEIF_VERSION}.tar.gz && \ + cd /tmp/libheif-${LIBHEIF_VERSION} && \ + ./configure --prefix=/vips && \ + make && \ + make install && \ + echo '/vips/lib' > /etc/ld.so.conf.d/vips.conf && \ + ldconfig -v && \ + export LD_LIBRARY_PATH="/vips/lib:$LD_LIBRARY_PATH" && \ + export PKG_CONFIG_PATH="/vips/lib/pkgconfig:$PKG_CONFIG_PATH" && \ + cd /tmp && \ + curl -fsSLO https://github.com/libvips/libvips/releases/download/v${LIBVIPS_VERSION}/vips-${LIBVIPS_VERSION}.tar.gz && \ + tar zvxf vips-${LIBVIPS_VERSION}.tar.gz && \ + cd /tmp/vips-${LIBVIPS_VERSION} && \ + CFLAGS="-g -O3" CXXFLAGS="-D_GLIBCXX_USE_CXX11_ABI=0 -g -O3" \ + ./configure \ + --disable-debug \ + --disable-dependency-tracking \ + --disable-introspection \ + --disable-static \ + --enable-gtk-doc-html=no \ + --enable-gtk-doc=no \ + --enable-pyvips8=no \ + --prefix=/vips && \ + make && \ + make install && \ + ldconfig + +ENV LD_LIBRARY_PATH="/usr/local/lib:$LD_LIBRARY_PATH" + +# Install runtime dependencies +# RUN DEBIAN_FRONTEND=noninteractive \ +# apt-get update && \ +# apt-get install --no-install-recommends -y \ +# libglib2.0-0 libjpeg62-turbo libpng16-16 libopenexr23 \ +# libwebp6 libwebpmux3 libwebpdemux2 libtiff5 libgif7 libexif12 libxml2 libpoppler-glib8 \ +# libmagickwand-6.q16-6 libpango1.0-0 libmatio4 libopenslide0 \ +# libgsf-1-114 fftw3 liborc-0.4-0 librsvg2-2 libcfitsio7 libimagequant0 libheif1 && \ +# apt-get autoremove -y && \ +# apt-get autoclean && \ +# apt-get clean && \ +# rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* + +# Install Go lint +RUN go get -u golang.org/x/lint/golint + +# ENV LD_LIBRARY_PATH="/vips/lib:$LD_LIBRARY_PATH" +# ENV PKG_CONFIG_PATH="/vips/lib/pkgconfig:/usr/local/lib/pkgconfig:/usr/lib/pkgconfig:/usr/X11/lib/pkgconfig" + +WORKDIR ${GOPATH}/src/github.com/h2non/bimg +COPY . . + +# RUN \ +# # Clean up +# apt-get remove -y automake curl build-essential && \ +# apt-get autoremove -y && \ +# apt-get autoclean && \ +# apt-get clean && \ +# rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* + +CMD [ "/bin/bash" ] diff --git a/History.md b/History.md index 427342df..226675c8 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,859 @@ +v1.1.5 / 2020-11-21 +=================== + + * Adds AVIF support [#356](https://github.com/h2non/bimg/pull/356) + +v1.1.4 / 2020-08-04 +================== + + * Merge pull request #346 from fredeastside/more_exif_data + * add most useful exif data to metadata + +v1.1.3 / 2020-08-04 +=================== + + * feat: version history v1.1.3 + * fix(ci): disable <8.7 libvips + * feat: autorotate + * feat: bump version + * Merge pull request #347 from vansante/master + * Merge pull request #345 from fredeastside/more_exif_data + * add more exif data to metadata + * Merge pull request #3 from laurentiuilie/add-support-for-heifs-file + * add brands heis, hevc + * Merge pull request #2 from laurentiuilie/add-support-for-heifs-file + * add test image for heifs + * remove test file and add the check + * add support for HEIFS file + * fix(palette): indentation + * Merge pull request #337 from theplant/master + * support Palette option for png + +v1.1.2 / 2020-06-08 +=================== + + * feat(history): add changes + * fix(#335): disable image flatten type conditional + +v1.1.1 / 2020-06-08 +=================== + + * feat(history): add changes + * feat(version): bump patch + * refactor(docs): add libvips install reference + * fix(ci): disable old libvips versions + * fix(install): use latest libvips version + * fix(tests): add heif exception in libvips < 8.8 + * refactor(ci): use libvips 8.7 + * fix(History): use proper version + +v1.1.0 / 2020-06-07 +=================== + + * refactor(ci): update libvips versions + * refactor(ci): update libvips versions + * refactor(ci): temporarely disable libvips + * feat(history): add version changes + * feat(ci): enable libvips versions + * fix(ci) + * fix(ci) + * fix(ci): try exporting env vars + * fix + * feat: add Dockerfile / Docker-driven CI job + * fix(co) + * feat(version): bump minor to 1 + * fix(ci): try new install + * fix(ci): try new install + * fix(ci): add curl package + * fix(ci): add curl package + * fix(ci): add curl package + * fix(ci): try new install + * fix(ci): indent style + * fix(ci): indent style + * fix(ci): indent style + * Merge pull request #299 from evanoberholster/master + * refactor(ci): disable verions matrix + * refactor(docs): use github.com package import path + * feat: add test image + * Merge pull request #281 from pohang/skip_smartcrop + * Merge pull request #317 from larrabee/master + * Merge pull request #307 from OrderMyGear/eslam/ch15924/some-product-images-have-a-border + * refactor(travis): adjust matrix versions + * Merge pull request #333 from simia-tech/master + * Fix orientation in vipsFlip call (resizer rotateAndFlipImage) + * chore(docs): delete old contributor + * enable vipsAffine to use `Extend` option value and send it to lipvips this will change the default from the one that lipvips use which is `background` to the ones that bimg use which is `C.VIPS_EXTEND_BLACK` but because the lip add extra 1 or .5 pix the background is considered black anyway so this will not affect anyone but will fix the bug of having border on the right and bottom of some images + * Merge pull request #327 from shoreward/master + * update libvips documentation links + * fix(vips.h): delete preprocessor HEIF version check + * Merge pull request #320 from cgroschupp/feat/reduce-png-save-size + * use VIPS_FOREIGN_PNG_FILTER_ALL in vips_pngsave_bridge + * fix(resizer): add exported error comment + * Merge branch 'master' of https://github.com/h2non/bimg + * chore(ci): temporarily disable go/libvips versions + * Merge pull request #291 from andrioid/patch-1 + * Merge pull request #293 from team-lab/gammaFilter + * Merge pull request #315 from vansante/heif + * feat(version): bump patch + * Fix bug with images with alpha channel on embeding background + * Fix typo + * Dont upgrade version, add missing test file + * Add support for other HEIF mimetype + * Supporting auto rotate for HEIF/HEIC images. + * Adding support for heif (i.e. heic files). + * Merge branch 'master' into master + * feat(travis): add libvips 8.6.0 matrix + * GammaFilter + * Adds support to Elementary OS Loki + * Add min dimension logic to smartcrop + * Merge pull request #271 from Dynom/ImprovingAreaWidthTestCoverage + * Adding a test case that verifies #250 + * Bumping versions in preinstall script + * Update Transform ICC Profiles with Input Profile + +v1.0.19 / 2018-12-09 +==================== + + * feat(travis): remove old Go versions, add Go 1.11 + * Merge pull request #224 from kishorgandham/patch-1 + * Merge pull request #242 from acaloiaro/documentation-url-updates + * Merge pull request #266 from bbernhard/master + * Merge pull request #250 from fisherking/master + * set vips version to 8.6.5 + * add support for Debian 9 to preinstall.sh + * Merge pull request #265 from c93614/master + * Merge branch 'master' into master + * Merge pull request #262 from danpersa/update-vips + * Updated the libvips tarbal_url and also updated the vips version + * Merge pull request #264 from golint-fixer/master + * Fix golint import path + * Make it compatible with the latest vips. Fixes #255 + * Fix AreaWidth calculation + * Libvips documentation URL and README copy updates + * feat(travis): add latest libvips and Go runtime versions + * Merge pull request #226 from muxinc/fix-flip-and-flop-axes + * Fixes #225 by correcting the flip and flop directions + * Fix image crop during embed + +v1.0.18 / 2017-12-22 +==================== + + * feat(version): bump to v1.0.18 + * Merge pull request #216 from Bynder/master + * Merge pull request #208 from mikestead/feature/webp-lossless + * Remove go-debug usage + * refactor(docs): remove codesponsor :( + * fix(options): use float64 type in Options.Threshold + * Merge pull request #206 from tstm/add-trim-options + * Add lossless option for saving webp + * Set the test file to write its own file + * Add the option to use background and threshold options on trim + +v1.0.17 / 2017-11-14 +==================== + + * feat(version): bump to v1.0.17 + * refactor(resizer): remove fmt statement + * fix(type_test): use string formatting + * Merge pull request #207 from traum-ferienwohnungen/nearest-neighbour + * Add nearest-neighbour interpolation + * Merge pull request #203 from traum-ferienwohnungen/fix_icc_memory_leak + * Fix memory leak on icc_transform + +v1.0.16 / 2017-10-30 +==================== + + * feat(version): bump to v1.0.16 + * fix(travis): use install directive + * Merge branch 'master' of https://github.com/h2non/bimg + * feat: add Gopkg manifests, move fixtures to testdata, add vendor dependencies + * Merge pull request #202 from openskydoor/openskydoor/fix-build-tag + * fix build tag + * fix(#199): presinstall.sh tarball download URL + +v1.0.15 / 2017-10-05 +==================== + + * feat(version): bump to v1.0.15 + * feat(History): update version changes + * Merge pull request #198 from greut/webpload + * Add shrink-on-load for webp. + * Merge pull request #197 from greut/typos + * Small typo. + * feat(docs): add codesponsor + +v1.0.14 / 2017-09-12 +==================== + + * feat(version): bump to v1.0.14 + * Merge pull request #192 from greut/trim + * Adding trim operation. + * Merge pull request #191 from greut/alpha4 + * Update 8.6 to alpha4. + +v1.0.13 / 2017-09-11 +==================== + + * feat(version). bump to v1.0.13 + * Merge pull request #190 from greut/typos + * Fix typo and small cleanup. + +v1.0.12 / 2017-09-10 +==================== + + * feat(version): bump to v1.0.12 + * feat(History): update version changes + * Merge branch '99designs-vips-reduce' + * fix(reduce): resolve conflicts with master + * Use vips reduce when downscaling + +v1.0.11 / 2017-09-10 +==================== + + * Merge pull request #186 from h2non/fix/#162-resize-garbage-collection + * feat(version): bump to v1.0.11 + * feat(History): update version changes + * feat(#189): allow strip image metadata via bimg.Options.StripMetadata = bool + * fix(resize): code format issue + * refactor(resize): add Go version comment + * refactor(tests): fix minor code formatting issues + * fix(#162): garbage collection fix. split Resize() implementation for Go runtime specific + * feat(travis): add go 1.9 + * Merge pull request #183 from greut/autorotate + * Proper handling of the EXIF cases. + * Merge pull request #184 from greut/libvips858 + * Merge branch 'master' into libvips858 + * Merge pull request #185 from greut/libvips860 + * Add libvips 8.6 pre-release + * Update to libvips 8.5.8 + * fix(resize): runtime.KeepAlive is only Go + * fix(#159): prevent buf to be freed by the GC before resize function exits + * Merge pull request #171 from greut/fix-170 + * Check the length before jumping into buffer. + * Merge pull request #168 from Traum-Ferienwohnungen/icc_transform + * Add option to convert embedded ICC profiles + * Merge pull request #166 from danjou-a/patch-1 + * Fix Resize verification value + * Merge pull request #165 from greut/libvips846 + * Testing using libvips8.4.6 from Github. + +v1.0.10 / 2017-06-25 +==================== + + * feat(version): bump minor + * Merge pull request #164 from greut/length + * Add Image.Length() + * Merge pull request #163 from greut/libvips856 + * Run libvips 8.5.6 on Travis. + * Merge pull request #161 from henry-blip/master + * Expose vips cache memory management functions. + * feat(docs): add watermark image note in features + +v1.0.9 / 2017-05-25 +=================== + + * feat(docs): add smart crop note + * feat(version): bump to v1.0.9 + * feat(History): update changes + * Merge pull request #156 from Dynom/SmartCropToGravity + * Adding a test, verifying both ways of enabling SmartCrop work + * Merge pull request #149 from waldophotos/master + * Replacing SmartCrop with a Gravity option + * refactor(docs): v8.4 + * Change for older LIBVIPS versions. `vips_bandjoin_const1` is added in libvips 8.2. + * Second try, watermarking memory issue fix + +v1.0.8 / 2017-05-18 +=================== + + * refactor(docs): upgrade recommended version to libvips 8.5 + * feat(version): bump to 1.0.8 + * Merge pull request #145 from greut/smartcrop + * Merge pull request #155 from greut/libvips8.5.5 + * Update libvips to 8.5.5. + * Adding basic smartcrop support. + * Merge pull request #153 from abracadaber/master + * Added Linux Mint 17.3+ distro names + * feat(docs): add new maintainer notice (thanks to @kirillDanshin) + * Merge pull request #152 from greut/libvips85 + * Download latest version of libvips from github. + * Merge pull request #147 from h2non/revert-143-master + * Revert "Fix for memory issue when watermarking images" + * Merge pull request #146 from greut/minor-major + * Merge pull request #143 from waldophotos/master + * Merge pull request #144 from greut/go18 + * Fix tests where minor/major were mixed up + * Enabled go 1.8 builds. + * Fix the unref of images, when image isn't transparent + * Fix for memory issue when watermarking images + * feat(docs): add maintainers sections + * Merge pull request #132 from jaume-pinyol/WATERMARK_SUPPORT + * Add support for image watermarks + * Merge pull request #131 from greut/versions + * Running tests on more specific versions. + * refactor(preinstall.sh): remove deprecation notice + * Update preinstall.sh + * fix(requirements): required libvips 7.42 + * fix(History): typo + * chore(History): add breaking change note + +v1.0.7 / 2017-01-13 +=================== + + * feat(History): update changes + * Merge pull request #124 from greut/tiffsave + * feat(version): bump to v1.0.7 + * Merge pull request #129 from danpersa/fix-128 + * Fix: Crop is doing resize. Closes #128 + * Refactoring IsTypeSupport to deal with save. + * Adding support for TIFF save. + * Saving to TIFF should also fail + * feat(docs): link to preinstall.sh from bimg reposityr + * feat: adds preinstall.sh from sharp project + * Merge pull request #122 from greut/magick + * Raise an error when trying to save as MAGICK type + * Testing the formats that cannot be saved + * feat(docs): update badges + * feat(docs): update badges + +v1.0.6 / 2016-11-12 +=================== + + * feat(version): bump to 1.0.6 + * Merge pull request #118 from shoeboxapp/png16 + * Merge pull request #119 from greut/jp2 + * Merge pull request #121 from greut/matrix + * Build against various libvips versions + * Do not free a pointer you don't own + * Adding JPEG2000 file for the type tests + * Cleaner fix + * Handle 16-bit PNGs + * Fix: remove travis 1.5 golang + * Merge pull request #120 from chonthu/patch-1 + * Update README.md + * Merge pull request #115 from h2non/develop + * Merge pull request #113 from h2non/develop + * Merge pull request #112 from h2non/develop + * Merge pull request #110 from h2non/develop + * Merge pull request #109 from h2non/develop + +v1.0.5 / 2016-10-01 +=================== + + * feat(options): add link to libvips API docs for Extend + * feat(version): bump to 1.0.5 + * fix(options): code style comment + * refactor(resize): use not equal operator (again) + * fix(#106): allow custom area extraction without x/y axis + * feat(#92): support Extend param with optional background + +v1.0.4 / 2016-09-29 +=================== + + * feat(version): bump to 1.0.4 + * fix(vips): check magick type support + +v1.0.3 / 2016-09-28 +=================== + + * feat(docs): update History with API changes + * feat(version): bump to 1.0.3 + * fix(background): pass proper background RGB color + * feat(types): infer types in runtime + * fix(type): svg type checking + * fix(type): check buffer length + * refactor(types): do proper image typ casting + * refactor(docs) + * fix(lint): fix code style + +v1.0.2 / 2016-09-27 +=================== + + * merge(master) + * feat(version): bump to 1.0.2 + * feat(#95): support multiple formats + * fix(tests) + * Merge pull request #108 from mikepulaski/master + * Auto-width and height calculations now round instead of floor. + * Merge pull request #105 from jibingeo/master + * Fixes issue with typecast from GType to int + * Add test to check ICC profile + * Merge pull request #104 from nvartolomei/png-16bit-alpha-background + * fix(flatten): fix flattening with background for 16bit transparent pngs + * Merge pull request #102 from aarti/master + * fix go vet issues + * Build on Go1.7 + * Update travis build + * Adding GIF, PDF and SVG support (libvips 8.3) + * Documentation error + * Merge pull request #96 from greut/rot45 + * Add support for 45° rotation. + * Merge pull request #92 from h2non/develop + +v1.0.1 / 2016-06-22 +=================== + + * chore(version): bump to 1.0.1 + * Merge pull request #91 from h2non/master + * Merge pull request #90 from aarti/master + * Take care to not dereference the original image a second time + * Merge pull request #88 from blippar/master + * Merge pull request #1 from blippar/check_alpha + * Fix formatting + * Check if there is an alpha channel before flattening + * feat(docs): add production note + * Merge pull request #86 from h2non/develop + * Merge pull request #85 from h2non/develop + +v1.0.0 / 2016-04-21 +=================== + + * feat(docs): use v1 in go get + * refactor(travis): remove duplicated command + * feat(version): v1 release. see history for details + +v0.1.24 / 2016-03-01 +==================== + + * fix(docs): minor typo + * Merge pull request #81 from h2non/develop + * feat(travis): use go 1.6 + * feat(docs): add coverage badge + * Merge pull request #79 from h2non/develop + * Merge pull request #77 from h2non/develop + * Merge pull request #76 from h2non/develop + +0.1.24 / 2016-02-09 +=================== + + * feat(version): bump + * fix(resize): auto rotate image before resize calculus + +0.1.23 / 2016-02-05 +=================== + + * feat(versio): bump + * fix(rotation) + +0.1.22 / 2016-01-30 +=================== + + * feat(travis): add GO 1.5 + * feat(version): bump + * fix(rotate): pre-rotate image based on EXIT orientation + * Merge pull request #75 from h2non/master + * feat(test): resize only by height o width + * merge(upstream) + * feat(#72): add helpful debug info in docs + * feat(test): add vertical image fixtures with multiple test cases + * feat(docs): add goreport badge + * Merge pull request #67 from h2non/master + * Merge pull request #66 from cneerdaels/sharpen + * Added interface and test for sharpen + * refactor(resize): clone options by value + * merge(upstream) + * refactor(docs) + * refactor(resize): simplify code + * fix(docs): typo + * feat(docs): add toc, remove API docs + * merge(master) + * refactor(vips): define constant + * fix(docs): typo + * feat(#60): support zero top and left params in extract operation + * refactor(docs): support with libvips 8.0 is stable for now + * feat(docs): add libvips version compatibility note + * refactor(type): simplify image type matching + +0.1.21 / 2015-09-29 +=================== + + * feat(version): bump + * fix(#56) + * merge(#55) + * refactor(#55): minor changes, use proper declarations, unref image + * - Adding a Background option when flattening out a transparent PNG + * feat(docs): update benchmarks + * feat(docs): add list of contributors + * feat(docs): update API docs + * feat(#52): add test case + * vips_gaussblur: remove dependency on libmath + * vips__gaussblur: renamed to vips_gaussblur_bridge + * resize: move effects to more explicit methods + * vips__gaussblur: add the missing sentinel + * transformImage: apply gaussian blur if needed + * vips: add a vips__gaussblur method + +0.1.20 / 2015-09-08 +=================== + + * feat(version): bump + * merge(zllak-debian) + * merge(zllak-debian) + * vips.h: problem with vips_init() + * vips.h: fail to build on Debian Jessie + * refactor(vips): free watermark cache. refactor vips.h + * refactor(vips): use shortcut to VipsImage C type + * fix(docs): remove old badge + +0.1.19 / 2015-07-28 +=================== + + * version(bump) + * feat(#49) + * feat(#49) + * refactor(docs): description + +0.1.18 / 2015-07-11 +=================== + + * feat(version): bump + * refactor(colourspace) + * feat(docs): add force resize example + * fix(#46): transform to proper image size + * feat: remove fixture + * refactor(#47): minor refactors, code normalization and test coverage + * Merge pull request #47 from greut/45-grayscale + * Add support for colourspace (fix #45) + * fix(resize): default options + * refactor(resize) + * fix(#46): infer resize operation + * fix(#46): infer resize operation + * refactor(docs): description + * fix(docs) + * fix(test): bad option field + +0.1.17 / 2015-06-13 +=================== + + * feat(version): bump + * feat(docs): update API + * feat: allow to remove ICC profile metadata + +0.1.16 / 2015-06-13 +=================== + + * feat: save a RGB colorspace + * feat(version): bump + * fix(#43) + +0.1.15 / 2015-06-12 +=================== + + * feat(version): bump + * feat(docs): update API docs + * merge(#42) + * fix(#42): change interlace type. fix C bindings + * This should not have been added. + * Added progressive jpeg functionality. + * fix(docs): minor typo fixes + * feat(docs): add openslide how to install. Related with #40 + * refactor(docs): feature list + * refactor(vips): switch option + * refactor(vips): remove debug statement, add comments + * Merge pull request #39 from bfitzsimmons/patch-1 + * Fixed the JPEG watermark benchmark. + +0.1.14 / 2015-05-24 +=================== + + * feat(version): bump + * refactor(docs): description + * refactor(docs): description + * merge + * refactor(vips) + * fix(badge) + * refactor(badge): release + * refactor(docs): description + * refactor(docs): remove beta note + * fix(docs): watermark example + +0.1.13 / 2015-04-27 +=================== + + * feat(version): bump + * feat(crop): add method shortcuts for crop + +0.1.12 / 2015-04-26 +=================== + + * feat(version): bump + * fix(#35): save webp + * fix(travis): fuck coveralls + +0.1.11 / 2015-04-25 +=================== + + * feat(version): bump + * refactor(docs): description + * fix(#32): bad crop + * fix(#33): bad auto rotatino + * refactor(docs): links + * merge + * feat(docs): update API + * refactor(docs): description + * fix(test): resize + +0.1.10 / 2015-04-16 +=================== + + * fix(test) + * feat(version): bump + * fix(#31) + * refactor(vips): remove obvious code + +0.1.9 / 2015-04-15 +================== + + * ffeat(version): bump + * fix(#30): one concurrent thread by default + * refactor(docs) + * refactor(docs): update badge + * refactor(file) + * feat(docs): add imaginary link + * feat(docs): add imaginary link + +0.1.8 / 2015-04-12 +================== + + * feat(version): bump + * fix(vips): panic error on exif orientation + * refactor(watermark): auto define width + * fix(#28): zoom requires extract params + * fix(#28): zoom requires extract params + * refactor: comparse as pure string + +0.1.7 / 2015-04-11 +================== + + * feat(version): bump + * feat(docs): update docs + * feat(test): better coverage for vips interface + * refactor(vips.h): watermark replicate + * refactor: vips.h, fix(docs): + +0.1.6 / 2015-04-11 +================== + + * refactor(vips.h) + * refactor(resize) + * feat(docs): update benchmark + * refactor(debug) + * refactor: remove colorspace feature + * feat(version): bump + * feat(#15): more benchmarks + * feat: add fixture + * feat(#27, #25): new features + * feat(#26): support zoom. several refactors and fixes + * feat(#25, #21) + +0.1.5 / 2015-04-08 +================== + + * feat(version): bump + * fix(vips): clean reference for interpolator + * feat(image): add method to retrieve the image + * feat(docs): update + * feat: add tests + +0.1.4 / 2015-04-08 +================== + + * feat(version): bump + * feat(image): pass gravity to crop + * fix(rotate): max angle to 270 + * refactor(vips): rename C bridge function + +0.1.3 / 2015-04-08 +================== + + * feat(version): bump + * refactor(resize): remove debug statement + * feat(test): vips + * feat(#20): support flop operation (interface broken, sorry im still beta) + * fix(test): image + * fix(image): tests + * fix(image): tests + * feat(#19): maximum image size + * feat(#15): add benchmark tests + * feat(#18, #17) + * fix(vips): bad argument + * fix(docs): example + * fix(docs): description + * feat(docs): add link to memory tests + * refactor(docs): description + * fix(docs): description + * refactor(docs): description + * fix(docs): description + * refactor(docs): normalize description and examples + * refactor(docs): normalize description and examples + * refactor(docs): description + +0.1.2 / 2015-04-07 +================== + + * feat(version): chore + * fix(extract): detect area options + * feat(version): bump + * feat(docs): force update + +0.1.1 / 2015-04-07 +================== + + * feat(#15): add benchmark tests + * fix(vips): memory inconsistency + * merge + * fix: possible leaks + * refactor(docs) + * feat(travis): add coveralls support + * feat(travis): add coveralls support + * fix(docs): add releases link + +0.1.0 / 2015-04-07 +================== + + * fix(test) + * refactor(docs) + * fix(test): image metadata + * fix(test): image metadata + * feat(docs): add API and examples + * refactor(resize): extract + * feat: add fixtures + * fix(resize): support rotate + * refactor(resize) + * feat(#13): metadata tests + * refactor: bindings + * refactor(vips) + * refactor(vips) + * refactor: remove file + * feat(metadata): add tests + * refactor(docs) + +0.1.0-beta.0 / 2015-04-06 +========================= + + * fix(crop): tests + * refactor: crop and tests + * feat: support resize and enlarge images + * feat: add file helper + * feat: support multiple outputs + * feat(#6, #10, #11) + * refactor + * refactor. feat(test): add fixtures + * refactor(vips): check image type + * refactor(docs): go version + * feat(docs): add Go version support + * update travis.yaml + * feat(#9): add Travis support + * feat(#8): add type alias + * feat(docs): add badge + * refactor: vips.h + * feat(docs): add API example + * refactor(type) + * refactor: indent style + * feat(#3, #5): support image operations + * feat(#1): initial implementation + * feat: add version file + * refactor(docs): description + * feat: add file + * feat: add readme + +v1.1.3 / 2020-08-04 +================== + + * fix(ci): disable <8.7 libvips + * feat: autorotate + * feat: bump version + * Merge pull request #347 from vansante/master + * Merge pull request #345 from fredeastside/more_exif_data + * add more exif data to metadata + * Merge pull request #3 from laurentiuilie/add-support-for-heifs-file + * add brands heis, hevc + * Merge pull request #2 from laurentiuilie/add-support-for-heifs-file + * add test image for heifs + * remove test file and add the check + * add support for HEIFS file + * fix(palette): indentation + * Merge pull request #337 from theplant/master + * support Palette option for png + +v1.1.2 / 2020-06-08 +=================== + + * fix(#335): disable image flatten type conditional + +v1.1.1 / 2020-06-08 +=================== + + * feat(version): bump patch + * refactor(docs): add libvips install reference + * fix(ci): disable old libvips versions + * fix(install): use latest libvips version + * fix(tests): add heif exception in libvips < 8.8 + * refactor(ci): use libvips 8.7 + * fix(History): use proper version + + +v1.1.0 / 2020-06-07 +=================== + + * feat(ci): enable libvips versions + * fix(ci) + * fix(ci) + * fix(ci): try exporting env vars + * fix + * feat: add Dockerfile / Docker-driven CI job + * fix(co) + * feat(version): bump minor to 1 + * fix(ci): try new install + * fix(ci): try new install + * fix(ci): add curl package + * fix(ci): add curl package + * fix(ci): add curl package + * fix(ci): try new install + * fix(ci): indent style + * fix(ci): indent style + * fix(ci): indent style + * Merge pull request #299 from evanoberholster/master + * refactor(ci): disable verions matrix + * refactor(docs): use github.com package import path + * feat: add test image + * Merge pull request #281 from pohang/skip_smartcrop + * Merge pull request #317 from larrabee/master + * Merge pull request #307 from OrderMyGear/eslam/ch15924/some-product-images-have-a-border + * refactor(travis): adjust matrix versions + * Merge pull request #333 from simia-tech/master + * Fix orientation in vipsFlip call (resizer rotateAndFlipImage) + * chore(docs): delete old contributor + * enable vipsAffine to use `Extend` option value and send it to lipvips this will change the default from the one that lipvips use which is `background` to the ones that bimg use which is `C.VIPS_EXTEND_BLACK` but because the lip add extra 1 or .5 pix the background is considered black anyway so this will not affect anyone but will fix the bug of having border on the right and bottom of some images + * Merge pull request #327 from shoreward/master + * update libvips documentation links + * fix(vips.h): delete preprocessor HEIF version check + * Merge pull request #320 from cgroschupp/feat/reduce-png-save-size + * use VIPS_FOREIGN_PNG_FILTER_ALL in vips_pngsave_bridge + * fix(resizer): add exported error comment + * Merge branch 'master' of https://github.com/h2non/bimg + * chore(ci): temporarily disable go/libvips versions + * Merge pull request #291 from andrioid/patch-1 + * Merge pull request #293 from team-lab/gammaFilter + * Merge pull request #315 from vansante/heif + * feat(version): bump patch + * Fix bug with images with alpha channel on embeding background + * Fix typo + * Dont upgrade version, add missing test file + * Add support for other HEIF mimetype + * Supporting auto rotate for HEIF/HEIC images. + * Adding support for heif (i.e. heic files). + * Merge branch 'master' into master + * feat(travis): add libvips 8.6.0 matrix + * GammaFilter + * Adds support to Elementary OS Loki + * Add min dimension logic to smartcrop + * Merge pull request #271 from Dynom/ImprovingAreaWidthTestCoverage + * Adding a test case that verifies #250 + * Bumping versions in preinstall script + * Update Transform ICC Profiles with Input Profile + ## v1.0.18 / 2017-12-22 * Merge pull request #216 from Bynder/master diff --git a/README.md b/README.md index fc39da07..f3640a9f 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # bimg [![Build Status](https://travis-ci.org/h2non/bimg.svg)](https://travis-ci.org/h2non/bimg) [![GoDoc](https://godoc.org/github.com/h2non/bimg?status.svg)](https://godoc.org/github.com/h2non/bimg) [![Go Report Card](http://goreportcard.com/badge/h2non/bimg)](http://goreportcard.com/report/h2non/bimg) [![Coverage Status](https://coveralls.io/repos/github/h2non/bimg/badge.svg?branch=master)](https://coveralls.io/github/h2non/bimg?branch=master) ![License](https://img.shields.io/badge/license-MIT-blue.svg) -Small [Go](http://golang.org) package for fast high-level image processing using [libvips](https://github.com/jcupitt/libvips) via C bindings, providing a simple, elegant and fluent [programmatic API](#examples). +Small [Go](http://golang.org) package for fast high-level image processing using [libvips](https://github.com/jcupitt/libvips) via C bindings, providing a simple [programmatic API](#examples). -bimg was designed to be a small and efficient library supporting a common set of [image operations](#supported-image-operations) such as crop, resize, rotate, zoom or watermark. It can read JPEG, PNG, WEBP natively, and optionally TIFF, PDF, GIF and SVG formats if `libvips@8.3+` is compiled with proper library bindings. +bimg was designed to be a small and efficient library supporting common [image operations](#supported-image-operations) such as crop, resize, rotate, zoom or watermark. It can read JPEG, PNG, WEBP natively, and optionally TIFF, PDF, GIF and SVG formats if `libvips@8.3+` is compiled with proper library bindings. Lastly AVIF is supported as of `libvips@8.9+`. For AVIF support `libheif` needs to be [compiled with an applicable AVIF en-/decoder](https://github.com/strukturag/libheif#compiling). bimg is able to output images as JPEG, PNG and WEBP formats, including transparent conversion across them. @@ -49,26 +49,36 @@ If you're using `gopkg.in`, you can still rely in the `v0` without worrying abou ## Prerequisites -- [libvips](https://github.com/libvips/libvips) 7.42+ or 8+ (8.4+ recommended) +- [libvips](https://github.com/libvips/libvips) 8.3+ (8.8+ recommended) - C compatible compiler such as gcc 4.6+ or clang 3.0+ - Go 1.3+ -**Note**: `libvips` v8.3+ is required for GIF, PDF and SVG support. +**Note**: + * `libvips` v8.3+ is required for GIF, PDF and SVG support. + * `libvips` v8.9+ is required for AVIF support. `libheif` compiled with a AVIF en-/decoder also needs to be present. ## Installation ```bash -go get -u gopkg.in/h2non/bimg.v1 +go get -u github.com/h2non/bimg ``` ### libvips +Follow `libvips` installation instructions: + +[https://libvips.github.io/libvips/install.html](https://libvips.github.io/libvips/install.html) + +##### Installation script + +**Note**: install script is officially deprecated, it might not work as expected. We recommend following [libvips install](https://libvips.github.io/libvips/install.html) instructions. + Run the following script as `sudo` (supports OSX, Debian/Ubuntu, Redhat, Fedora, Amazon Linux): ```bash curl -s https://raw.githubusercontent.com/h2non/bimg/master/preinstall.sh | sudo bash - ``` -If you wanna take the advantage of [OpenSlide](http://openslide.org/), simply add `--with-openslide` to enable it: +If you want to take the advantage of [OpenSlide](http://openslide.org/), simply add `--with-openslide` to enable it: ```bash curl -s https://raw.githubusercontent.com/h2non/bimg/master/preinstall.sh | sudo bash -s --with-openslide ``` @@ -113,7 +123,7 @@ BenchmarkWatermarWebp-8 30 49360369 ns/op import ( "fmt" "os" - "gopkg.in/h2non/bimg.v1" + "github.com/h2non/bimg" ) ``` @@ -325,7 +335,6 @@ See [godoc reference](https://godoc.org/github.com/h2non/bimg) for detailed API ## Authors - [Tomás Aparicio](https://github.com/h2non) - Original author and architect. -- [Kirill Danshin](https://github.com/kirillDanshin) - Maintainer since April 2017. ## Credits diff --git a/image.go b/image.go index 2ab58e04..13011e47 100644 --- a/image.go +++ b/image.go @@ -154,6 +154,11 @@ func (i *Image) Rotate(a Angle) ([]byte, error) { return i.Process(options) } +// AutoRotate automatically rotates the image with no additional transformation based on the EXIF oritentation metadata, if available. +func (i *Image) AutoRotate() ([]byte, error) { + return i.Process(Options{autoRotateOnly: true}) +} + // Flip flips the image about the vertical Y axis. func (i *Image) Flip() ([]byte, error) { options := Options{Flip: true} diff --git a/image_test.go b/image_test.go index b4d9b36f..c3e11de2 100644 --- a/image_test.go +++ b/image_test.go @@ -373,6 +373,43 @@ func TestImageRotate(t *testing.T) { Write("testdata/test_image_rotate_out.jpg", buf) } +func TestImageAutoRotate(t *testing.T) { + if VipsMajorVersion <= 8 && VipsMinorVersion < 10 { + t.Skip("Skip test in libvips < 8.10") + return + } + + tests := []struct { + file string + orientation int + }{ + {"exif/Landscape_1.jpg", 1}, + {"exif/Landscape_2.jpg", 1}, + {"exif/Landscape_3.jpg", 1}, + {"exif/Landscape_4.jpg", 1}, + {"exif/Landscape_5.jpg", 1}, + {"exif/Landscape_6.jpg", 1}, + {"exif/Landscape_7.jpg", 1}, + } + + for index, test := range tests { + img := initImage(test.file) + buf, err := img.AutoRotate() + if err != nil { + t.Errorf("Cannot process the image: %#v", err) + } + Write(fmt.Sprintf("testdata/test_autorotate_%d_out.jpg", index), buf) + + meta, err := img.Metadata() + if err != nil { + t.Errorf("Cannot read image metadata: %#v", err) + } + if meta.Orientation != test.orientation { + t.Errorf("Invalid image orientation for %s: %d != %d", test.file, meta.Orientation, test.orientation) + } + } +} + func TestImageConvert(t *testing.T) { buf, err := initImage("test.jpg").Convert(PNG) if err != nil { diff --git a/metadata.go b/metadata.go index 6c7e0bc8..37bcb67d 100644 --- a/metadata.go +++ b/metadata.go @@ -6,6 +6,61 @@ package bimg */ import "C" +// Common EXIF fields for data extraction +const ( + Make = "exif-ifd0-Make" + Model = "exif-ifd0-Model" + Orientation = "exif-ifd0-Orientation" + XResolution = "exif-ifd0-XResolution" + YResolution = "exif-ifd0-YResolution" + ResolutionUnit = "exif-ifd0-ResolutionUnit" + Software = "exif-ifd0-Software" + Datetime = "exif-ifd0-DateTime" + YCbCrPositioning = "exif-ifd0-YCbCrPositioning" + Compression = "exif-ifd1-Compression" + ExposureTime = "exif-ifd2-ExposureTime" + FNumber = "exif-ifd2-FNumber" + ExposureProgram = "exif-ifd2-ExposureProgram" + ISOSpeedRatings = "exif-ifd2-ISOSpeedRatings" + ExifVersion = "exif-ifd2-ExifVersion" + DateTimeOriginal = "exif-ifd2-DateTimeOriginal" + DateTimeDigitized = "exif-ifd2-DateTimeDigitized" + ComponentsConfiguration = "exif-ifd2-ComponentsConfiguration" + ShutterSpeedValue = "exif-ifd2-ShutterSpeedValue" + ApertureValue = "exif-ifd2-ApertureValue" + BrightnessValue = "exif-ifd2-BrightnessValue" + ExposureBiasValue = "exif-ifd2-ExposureBiasValue" + MeteringMode = "exif-ifd2-MeteringMode" + Flash = "exif-ifd2-Flash" + FocalLength = "exif-ifd2-FocalLength" + SubjectArea = "exif-ifd2-SubjectArea" + MakerNote = "exif-ifd2-MakerNote" + SubSecTimeOriginal = "exif-ifd2-SubSecTimeOriginal" + SubSecTimeDigitized = "exif-ifd2-SubSecTimeDigitized" + ColorSpace = "exif-ifd2-ColorSpace" + PixelXDimension = "exif-ifd2-PixelXDimension" + PixelYDimension = "exif-ifd2-PixelYDimension" + SensingMethod = "exif-ifd2-SensingMethod" + SceneType = "exif-ifd2-SceneType" + ExposureMode = "exif-ifd2-ExposureMode" + WhiteBalance = "exif-ifd2-WhiteBalance" + FocalLengthIn35mmFilm = "exif-ifd2-FocalLengthIn35mmFilm" + SceneCaptureType = "exif-ifd2-SceneCaptureType" + GPSLatitudeRef = "exif-ifd3-GPSLatitudeRef" + GPSLatitude = "exif-ifd3-GPSLatitude" + GPSLongitudeRef = "exif-ifd3-GPSLongitudeRef" + GPSLongitude = "exif-ifd3-GPSLongitude" + GPSAltitudeRef = "exif-ifd3-GPSAltitudeRef" + GPSAltitude = "exif-ifd3-GPSAltitude" + GPSSpeedRef = "exif-ifd3-GPSSpeedRef" + GPSSpeed = "exif-ifd3-GPSSpeed" + GPSImgDirectionRef = "exif-ifd3-GPSImgDirectionRef" + GPSImgDirection = "exif-ifd3-GPSImgDirection" + GPSDestBearingRef = "exif-ifd3-GPSDestBearingRef" + GPSDestBearing = "exif-ifd3-GPSDestBearing" + GPSDateStamp = "exif-ifd3-GPSDateStamp" +) + // ImageSize represents the image width and height values type ImageSize struct { Width int @@ -23,6 +78,62 @@ type ImageMetadata struct { Space string Colourspace string Size ImageSize + EXIF EXIF +} + +// EXIF image metadata +type EXIF struct { + Make string + Model string + Orientation int + XResolution string + YResolution string + ResolutionUnit int + Software string + Datetime string + YCbCrPositioning int + Compression int + ExposureTime string + FNumber string + ExposureProgram int + ISOSpeedRatings int + ExifVersion string + DateTimeOriginal string + DateTimeDigitized string + ComponentsConfiguration string + ShutterSpeedValue string + ApertureValue string + BrightnessValue string + ExposureBiasValue string + MeteringMode int + Flash int + FocalLength string + SubjectArea string + MakerNote string + SubSecTimeOriginal string + SubSecTimeDigitized string + ColorSpace int + PixelXDimension int + PixelYDimension int + SensingMethod int + SceneType string + ExposureMode int + WhiteBalance int + FocalLengthIn35mmFilm int + SceneCaptureType int + GPSLatitudeRef string + GPSLatitude string + GPSLongitudeRef string + GPSLongitude string + GPSAltitudeRef string + GPSAltitude string + GPSSpeedRef string + GPSSpeed string + GPSImgDirectionRef string + GPSImgDirection string + GPSDestBearingRef string + GPSDestBearing string + GPSDateStamp string } // Size returns the image size by width and height pixels. @@ -66,15 +177,70 @@ func Metadata(buf []byte) (ImageMetadata, error) { Height: int(image.Ysize), } + orientation := vipsExifIntTag(image, Orientation) + metadata := ImageMetadata{ Size: size, Channels: int(image.Bands), - Orientation: vipsExifOrientation(image), + Orientation: orientation, Alpha: vipsHasAlpha(image), Profile: vipsHasProfile(image), Space: vipsSpace(image), Type: ImageTypeName(imageType), Pages: int(pageCount), + EXIF: EXIF{ + Make: vipsExifStringTag(image, Make), + Model: vipsExifStringTag(image, Model), + Orientation: orientation, + XResolution: vipsExifStringTag(image, XResolution), + YResolution: vipsExifStringTag(image, YResolution), + ResolutionUnit: vipsExifIntTag(image, ResolutionUnit), + Software: vipsExifStringTag(image, Software), + Datetime: vipsExifStringTag(image, Datetime), + YCbCrPositioning: vipsExifIntTag(image, YCbCrPositioning), + Compression: vipsExifIntTag(image, Compression), + ExposureTime: vipsExifStringTag(image, ExposureTime), + FNumber: vipsExifStringTag(image, FNumber), + ExposureProgram: vipsExifIntTag(image, ExposureProgram), + ISOSpeedRatings: vipsExifIntTag(image, ISOSpeedRatings), + ExifVersion: vipsExifStringTag(image, ExifVersion), + DateTimeOriginal: vipsExifStringTag(image, DateTimeOriginal), + DateTimeDigitized: vipsExifStringTag(image, DateTimeDigitized), + ComponentsConfiguration: vipsExifStringTag(image, ComponentsConfiguration), + ShutterSpeedValue: vipsExifStringTag(image, ShutterSpeedValue), + ApertureValue: vipsExifStringTag(image, ApertureValue), + BrightnessValue: vipsExifStringTag(image, BrightnessValue), + ExposureBiasValue: vipsExifStringTag(image, ExposureBiasValue), + MeteringMode: vipsExifIntTag(image, MeteringMode), + Flash: vipsExifIntTag(image, Flash), + FocalLength: vipsExifStringTag(image, FocalLength), + SubjectArea: vipsExifStringTag(image, SubjectArea), + MakerNote: vipsExifStringTag(image, MakerNote), + SubSecTimeOriginal: vipsExifStringTag(image, SubSecTimeOriginal), + SubSecTimeDigitized: vipsExifStringTag(image, SubSecTimeDigitized), + ColorSpace: vipsExifIntTag(image, ColorSpace), + PixelXDimension: vipsExifIntTag(image, PixelXDimension), + PixelYDimension: vipsExifIntTag(image, PixelYDimension), + SensingMethod: vipsExifIntTag(image, SensingMethod), + SceneType: vipsExifStringTag(image, SceneType), + ExposureMode: vipsExifIntTag(image, ExposureMode), + WhiteBalance: vipsExifIntTag(image, WhiteBalance), + FocalLengthIn35mmFilm: vipsExifIntTag(image, FocalLengthIn35mmFilm), + SceneCaptureType: vipsExifIntTag(image, SceneCaptureType), + GPSLatitudeRef: vipsExifStringTag(image, GPSLatitudeRef), + GPSLatitude: vipsExifStringTag(image, GPSLatitude), + GPSLongitudeRef: vipsExifStringTag(image, GPSLongitudeRef), + GPSLongitude: vipsExifStringTag(image, GPSLongitude), + GPSAltitudeRef: vipsExifStringTag(image, GPSAltitudeRef), + GPSAltitude: vipsExifStringTag(image, GPSAltitude), + GPSSpeedRef: vipsExifStringTag(image, GPSSpeedRef), + GPSSpeed: vipsExifStringTag(image, GPSSpeed), + GPSImgDirectionRef: vipsExifStringTag(image, GPSImgDirectionRef), + GPSImgDirection: vipsExifStringTag(image, GPSImgDirection), + GPSDestBearingRef: vipsExifStringTag(image, GPSDestBearingRef), + GPSDestBearing: vipsExifStringTag(image, GPSDestBearing), + GPSDateStamp: vipsExifStringTag(image, GPSDateStamp), + }, } return metadata, nil diff --git a/metadata_test.go b/metadata_test.go index 97814a10..6c817170 100644 --- a/metadata_test.go +++ b/metadata_test.go @@ -42,6 +42,7 @@ func TestMetadata(t *testing.T) { {"test_icc_prophoto.jpg", "jpeg", 0, false, true, "srgb"}, {"test.png", "png", 0, true, false, "srgb"}, {"test.webp", "webp", 0, false, false, "srgb"}, + {"test.avif", "avif", 0, false, false, "srgb"}, } for _, file := range files { @@ -49,7 +50,6 @@ func TestMetadata(t *testing.T) { if err != nil { t.Fatalf("Cannot read the image: %s -> %s", file.name, err) } - if metadata.Type != file.format { t.Fatalf("Unexpected image format: %s", file.format) } @@ -89,6 +89,289 @@ func TestImageInterpretation(t *testing.T) { } } +func TestEXIF(t *testing.T) { + if VipsMajorVersion <= 8 && VipsMinorVersion < 10 { + t.Skip("Skip test in libvips < 8.10") + return + } + + files := map[string]EXIF { + "test.jpg": {}, + "exif/Landscape_1.jpg": { + Orientation: 1, + XResolution: "72/1", + YResolution: "72/1", + ResolutionUnit: 2, + YCbCrPositioning: 1, + ExifVersion: "Exif Version 2.1", + ColorSpace: 65535, + }, + "test_exif.jpg": { + Make: "Jolla", + Model: "Jolla", + XResolution: "72/1", + YResolution: "72/1", + ResolutionUnit: 2, + Orientation: 1, + Datetime: "2014:09:21 16:00:56", + ExposureTime: "1/25", + FNumber: "12/5", + ISOSpeedRatings: 320, + ExifVersion: "Exif Version 2.3", + DateTimeOriginal: "2014:09:21 16:00:56", + ShutterSpeedValue: "205447286/44240665", + ApertureValue: "334328577/132351334", + ExposureBiasValue: "0/1", + MeteringMode: 1, + Flash: 0, + FocalLength: "4/1", + WhiteBalance: 1, + ColorSpace: 65535, + }, + "test_exif_canon.jpg": { + Make: "Canon", + Model: "Canon EOS 40D", + Orientation: 1, + XResolution: "72/1", + YResolution: "72/1", + ResolutionUnit: 2, + Software: "GIMP 2.4.5", + Datetime: "2008:07:31 10:38:11", + YCbCrPositioning: 2, + Compression: 6, + ExposureTime: "1/160", + FNumber: "71/10", + ExposureProgram: 1, + ISOSpeedRatings: 100, + ExifVersion: "Exif Version 2.21", + DateTimeOriginal: "2008:05:30 15:56:01", + DateTimeDigitized: "2008:05:30 15:56:01", + ComponentsConfiguration: "Y Cb Cr -", + ShutterSpeedValue: "483328/65536", + ApertureValue: "368640/65536", + ExposureBiasValue: "0/1", + MeteringMode: 5, + Flash: 9, + FocalLength: "135/1", + SubSecTimeOriginal: "00", + SubSecTimeDigitized: "00", + ColorSpace: 1, + PixelXDimension: 100, + PixelYDimension: 68, + ExposureMode: 1, + WhiteBalance: 0, + SceneCaptureType: 0, + }, + "test_exif_full.jpg": { + Make: "Apple", + Model: "iPhone XS", + Orientation: 6, + XResolution: "72/1", + YResolution: "72/1", + ResolutionUnit: 2, + Software: "13.3.1", + Datetime: "2020:07:28 19:18:49", + YCbCrPositioning: 1, + Compression: 6, + ExposureTime: "1/835", + FNumber: "9/5", + ExposureProgram: 2, + ISOSpeedRatings: 25, + ExifVersion: "Unknown Exif Version", + DateTimeOriginal: "2020:07:28 19:18:49", + DateTimeDigitized: "2020:07:28 19:18:49", + ComponentsConfiguration: "Y Cb Cr -", + ShutterSpeedValue: "77515/7986", + ApertureValue: "54823/32325", + BrightnessValue: "77160/8623", + ExposureBiasValue: "0/1", + MeteringMode: 5, + Flash: 16, + FocalLength: "17/4", + SubjectArea: "2013 1511 2217 1330", + MakerNote: "1110 bytes undefined data", + SubSecTimeOriginal: "777", + SubSecTimeDigitized: "777", + ColorSpace: 65535, + PixelXDimension: 4032, + PixelYDimension: 3024, + SensingMethod: 2, + SceneType: "Directly photographed", + ExposureMode: 0, + WhiteBalance: 0, + FocalLengthIn35mmFilm: 26, + SceneCaptureType: 0, + GPSLatitudeRef: "N", + GPSLatitude: "55/1 43/1 5287/100", + GPSLongitudeRef: "E", + GPSLongitude: "37/1 35/1 5571/100", + GPSAltitudeRef: "Sea level", + GPSAltitude: "90514/693", + GPSSpeedRef: "K", + GPSSpeed: "114272/41081", + GPSImgDirectionRef: "M", + GPSImgDirection: "192127/921", + GPSDestBearingRef: "M", + GPSDestBearing: "192127/921", + GPSDateStamp: "2020:07:28", + }, + } + + for name, file := range files { + metadata, err := Metadata(readFile(name)) + if err != nil { + t.Fatalf("Cannot read the image: %s -> %s", name, err) + } + if metadata.EXIF.Make != file.Make { + t.Fatalf("Unexpected image exif Make: %s != %s", metadata.EXIF.Make, file.Make) + } + if metadata.EXIF.Model != file.Model { + t.Fatalf("Unexpected image exif Model: %s != %s", metadata.EXIF.Model, file.Model) + } + if metadata.EXIF.Orientation != file.Orientation { + t.Fatalf("Unexpected image exif Orientation: %d != %d", metadata.EXIF.Orientation, file.Orientation) + } + if metadata.EXIF.XResolution != file.XResolution { + t.Fatalf("Unexpected image exif XResolution: %s != %s", metadata.EXIF.XResolution, file.XResolution) + } + if metadata.EXIF.YResolution != file.YResolution { + t.Fatalf("Unexpected image exif YResolution: %s != %s", metadata.EXIF.YResolution, file.YResolution) + } + if metadata.EXIF.ResolutionUnit != file.ResolutionUnit { + t.Fatalf("Unexpected image exif ResolutionUnit: %d != %d", metadata.EXIF.ResolutionUnit, file.ResolutionUnit) + } + if metadata.EXIF.Software != file.Software { + t.Fatalf("Unexpected image exif Software: %s != %s", metadata.EXIF.Software, file.Software) + } + if metadata.EXIF.Datetime != file.Datetime { + t.Fatalf("Unexpected image exif Datetime: %s != %s", metadata.EXIF.Datetime, file.Datetime) + } + if metadata.EXIF.YCbCrPositioning != file.YCbCrPositioning { + t.Fatalf("Unexpected image exif YCbCrPositioning: %d != %d", metadata.EXIF.YCbCrPositioning, file.YCbCrPositioning) + } + if metadata.EXIF.Compression != file.Compression { + t.Fatalf("Unexpected image exif Compression: %d != %d", metadata.EXIF.Compression, file.Compression) + } + if metadata.EXIF.ExposureTime != file.ExposureTime { + t.Fatalf("Unexpected image exif ExposureTime: %s != %s", metadata.EXIF.ExposureTime, file.ExposureTime) + } + if metadata.EXIF.FNumber != file.FNumber { + t.Fatalf("Unexpected image exif FNumber: %s != %s", metadata.EXIF.FNumber, file.FNumber) + } + if metadata.EXIF.ExposureProgram != file.ExposureProgram { + t.Fatalf("Unexpected image exif ExposureProgram: %d != %d", metadata.EXIF.ExposureProgram, file.ExposureProgram) + } + if metadata.EXIF.ISOSpeedRatings != file.ISOSpeedRatings { + t.Fatalf("Unexpected image exif ISOSpeedRatings: %d != %d", metadata.EXIF.ISOSpeedRatings, file.ISOSpeedRatings) + } + if metadata.EXIF.ExifVersion != file.ExifVersion { + t.Fatalf("Unexpected image exif ExifVersion: %s != %s", metadata.EXIF.ExifVersion, file.ExifVersion) + } + if metadata.EXIF.DateTimeOriginal != file.DateTimeOriginal { + t.Fatalf("Unexpected image exif DateTimeOriginal: %s != %s", metadata.EXIF.DateTimeOriginal, file.DateTimeOriginal) + } + if metadata.EXIF.DateTimeDigitized != file.DateTimeDigitized { + t.Fatalf("Unexpected image exif DateTimeDigitized: %s != %s", metadata.EXIF.DateTimeDigitized, file.DateTimeDigitized) + } + if metadata.EXIF.ComponentsConfiguration != file.ComponentsConfiguration { + t.Fatalf("Unexpected image exif ComponentsConfiguration: %s != %s", metadata.EXIF.ComponentsConfiguration, file.ComponentsConfiguration) + } + if metadata.EXIF.ShutterSpeedValue != file.ShutterSpeedValue { + t.Fatalf("Unexpected image exif ShutterSpeedValue: %s != %s", metadata.EXIF.ShutterSpeedValue, file.ShutterSpeedValue) + } + if metadata.EXIF.ApertureValue != file.ApertureValue { + t.Fatalf("Unexpected image exif ApertureValue: %s != %s", metadata.EXIF.ApertureValue, file.ApertureValue) + } + if metadata.EXIF.BrightnessValue != file.BrightnessValue { + t.Fatalf("Unexpected image exif BrightnessValue: %s != %s", metadata.EXIF.BrightnessValue, file.BrightnessValue) + } + if metadata.EXIF.ExposureBiasValue != file.ExposureBiasValue { + t.Fatalf("Unexpected image exif ExposureBiasValue: %s != %s", metadata.EXIF.ExposureBiasValue, file.ExposureBiasValue) + } + if metadata.EXIF.MeteringMode != file.MeteringMode { + t.Fatalf("Unexpected image exif MeteringMode: %d != %d", metadata.EXIF.MeteringMode, file.MeteringMode) + } + if metadata.EXIF.Flash != file.Flash { + t.Fatalf("Unexpected image exif Flash: %d != %d", metadata.EXIF.Flash, file.Flash) + } + if metadata.EXIF.FocalLength != file.FocalLength { + t.Fatalf("Unexpected image exif FocalLength: %s != %s", metadata.EXIF.FocalLength, file.FocalLength) + } + if metadata.EXIF.SubjectArea != file.SubjectArea { + t.Fatalf("Unexpected image exif SubjectArea: %s != %s", metadata.EXIF.SubjectArea, file.SubjectArea) + } + if metadata.EXIF.MakerNote != file.MakerNote { + t.Fatalf("Unexpected image exif MakerNote: %s != %s", metadata.EXIF.MakerNote, file.MakerNote) + } + if metadata.EXIF.SubSecTimeOriginal != file.SubSecTimeOriginal { + t.Fatalf("Unexpected image exif SubSecTimeOriginal: %s != %s", metadata.EXIF.SubSecTimeOriginal, file.SubSecTimeOriginal) + } + if metadata.EXIF.SubSecTimeDigitized != file.SubSecTimeDigitized { + t.Fatalf("Unexpected image exif SubSecTimeDigitized: %s != %s", metadata.EXIF.SubSecTimeDigitized, file.SubSecTimeDigitized) + } + if metadata.EXIF.ColorSpace != file.ColorSpace { + t.Fatalf("Unexpected image exif ColorSpace: %d != %d", metadata.EXIF.ColorSpace, file.ColorSpace) + } + if metadata.EXIF.PixelXDimension != file.PixelXDimension { + t.Fatalf("Unexpected image exif PixelXDimension: %d != %d", metadata.EXIF.PixelXDimension, file.PixelXDimension) + } + if metadata.EXIF.PixelYDimension != file.PixelYDimension { + t.Fatalf("Unexpected image exif PixelYDimension: %d != %d", metadata.EXIF.PixelYDimension, file.PixelYDimension) + } + if metadata.EXIF.SensingMethod != file.SensingMethod { + t.Fatalf("Unexpected image exif SensingMethod: %d != %d", metadata.EXIF.SensingMethod, file.SensingMethod) + } + if metadata.EXIF.SceneType != file.SceneType { + t.Fatalf("Unexpected image exif SceneType: %s != %s", metadata.EXIF.SceneType, file.SceneType) + } + if metadata.EXIF.ExposureMode != file.ExposureMode { + t.Fatalf("Unexpected image exif ExposureMode: %d != %d", metadata.EXIF.ExposureMode, file.ExposureMode) + } + if metadata.EXIF.WhiteBalance != file.WhiteBalance { + t.Fatalf("Unexpected image exif WhiteBalance: %d != %d", metadata.EXIF.WhiteBalance, file.WhiteBalance) + } + if metadata.EXIF.FocalLengthIn35mmFilm != file.FocalLengthIn35mmFilm { + t.Fatalf("Unexpected image exif FocalLengthIn35mmFilm: %d != %d", metadata.EXIF.FocalLengthIn35mmFilm, file.FocalLengthIn35mmFilm) + } + if metadata.EXIF.SceneCaptureType != file.SceneCaptureType { + t.Fatalf("Unexpected image exif SceneCaptureType: %d != %d", metadata.EXIF.SceneCaptureType, file.SceneCaptureType) + } + if metadata.EXIF.GPSLongitudeRef != file.GPSLongitudeRef { + t.Fatalf("Unexpected image exif GPSLongitudeRef: %s != %s", metadata.EXIF.GPSLongitudeRef, file.GPSLongitudeRef) + } + if metadata.EXIF.GPSLongitude != file.GPSLongitude { + t.Fatalf("Unexpected image exif GPSLongitude: %s != %s", metadata.EXIF.GPSLongitude, file.GPSLongitude) + } + if metadata.EXIF.GPSAltitudeRef != file.GPSAltitudeRef { + t.Fatalf("Unexpected image exif GPSAltitudeRef: %s != %s", metadata.EXIF.GPSAltitudeRef, file.GPSAltitudeRef) + } + if metadata.EXIF.GPSAltitude != file.GPSAltitude { + t.Fatalf("Unexpected image exif GPSAltitude: %s != %s", metadata.EXIF.GPSAltitude, file.GPSAltitude) + } + if metadata.EXIF.GPSSpeedRef != file.GPSSpeedRef { + t.Fatalf("Unexpected image exif GPSSpeedRef: %s != %s", metadata.EXIF.GPSSpeedRef, file.GPSSpeedRef) + } + if metadata.EXIF.GPSSpeed != file.GPSSpeed { + t.Fatalf("Unexpected image exif GPSSpeed: %s != %s", metadata.EXIF.GPSSpeed, file.GPSSpeed) + } + if metadata.EXIF.GPSImgDirectionRef != file.GPSImgDirectionRef { + t.Fatalf("Unexpected image exif GPSImgDirectionRef: %s != %s", metadata.EXIF.GPSImgDirectionRef, file.GPSImgDirectionRef) + } + if metadata.EXIF.GPSImgDirection != file.GPSImgDirection { + t.Fatalf("Unexpected image exif GPSImgDirection: %s != %s", metadata.EXIF.GPSImgDirection, file.GPSImgDirection) + } + if metadata.EXIF.GPSDestBearingRef != file.GPSDestBearingRef { + t.Fatalf("Unexpected image exif GPSDestBearingRef: %s != %s", metadata.EXIF.GPSDestBearingRef, file.GPSDestBearingRef) + } + if metadata.EXIF.GPSDestBearing != file.GPSDestBearing { + t.Fatalf("Unexpected image exif GPSDestBearing: %s != %s", metadata.EXIF.GPSDestBearing, file.GPSDestBearing) + } + if metadata.EXIF.GPSDateStamp != file.GPSDateStamp { + t.Fatalf("Unexpected image exif GPSDateStamp: %s != %s", metadata.EXIF.GPSDateStamp, file.GPSDateStamp) + } + } +} + func TestColourspaceIsSupported(t *testing.T) { files := []struct { name string diff --git a/options.go b/options.go index 362dcd22..7b200ea0 100644 --- a/options.go +++ b/options.go @@ -8,7 +8,7 @@ import "C" const ( // Quality defines the default JPEG quality to be used. - Quality = 80 + Quality = 75 // MaxSize defines the maximum pixels width or height supported. MaxSize = 16383 ) @@ -225,4 +225,11 @@ type Options struct { Threshold float64 Gamma float64 OutputICC string + InputICC string + Palette bool + // Speed defines the AVIF encoders CPU effort. Valid values are 0-8. + Speed int + + // private fields + autoRotateOnly bool } diff --git a/preinstall.sh b/preinstall.sh index 7eda6269..70b4c827 100755 --- a/preinstall.sh +++ b/preinstall.sh @@ -1,7 +1,11 @@ #!/bin/bash -vips_version_minimum=8.7.2 -vips_version_latest_major_minor=8.7 +# +# NOTE: deprecated! Try libvips installation: https://libvips.github.io/libvips/install.html +# + +vips_version_minimum=8.9.2 +vips_version_latest_major_minor=8.9 vips_version_latest_patch=2 vips_version_full="$vips_version_latest_major_minor.$vips_version_latest_patch" diff --git a/resizer.go b/resizer.go index fd9bafcf..ce0e7598 100644 --- a/resizer.go +++ b/resizer.go @@ -30,10 +30,20 @@ func resizer(buf []byte, o Options) ([]byte, error) { // Clone and define default options o = applyDefaults(o, imageType) - if !IsTypeSupported(o.Type) { + // Ensure supported type + if !IsTypeSupportedSave(o.Type) { return nil, errors.New("Unsupported image output type") } + // Autorate only + if o.autoRotateOnly { + image, err = vipsAutoRotate(image) + if err != nil { + return nil, err + } + return saveImage(image, o) + } + // Auto rotate image based on EXIF orientation header image, rotated, err := rotateAndFlipImage(image, o) if err != nil { @@ -41,7 +51,7 @@ func resizer(buf []byte, o Options) ([]byte, error) { } // If JPEG or HEIF image, retrieve the buffer - if rotated && (imageType == JPEG || imageType == HEIF) && !o.NoAutoRotate { + if rotated && (imageType == JPEG || imageType == HEIF || imageType == AVIF) && !o.NoAutoRotate { buf, err = getImageBuffer(image) if err != nil { return nil, err @@ -176,9 +186,12 @@ func saveImage(image *C.VipsImage, o Options) ([]byte, error) { Interlace: o.Interlace, NoProfile: o.NoProfile, Interpretation: o.Interpretation, + InputICC: o.InputICC, OutputICC: o.OutputICC, StripMetadata: o.StripMetadata, Lossless: o.Lossless, + Palette: o.Palette, + Speed: o.Speed, } // Finally get the resultant buffer return vipsSave(image, saveOptions) @@ -220,7 +233,7 @@ func transformImage(image *C.VipsImage, o Options, shrink int, residual float64) if residualx < 1 && residualy < 1 { image, err = vipsReduce(image, 1/residualx, 1/residualy) } else { - image, err = vipsAffine(image, residualx, residualy, o.Interpolator) + image, err = vipsAffine(image, residualx, residualy, o.Interpolator, o.Extend) } if err != nil { return nil, err @@ -267,9 +280,19 @@ func extractOrEmbedImage(image *C.VipsImage, o Options) (*C.VipsImage, error) { switch { case o.Gravity == GravitySmart, o.SmartCrop: - image, err = vipsSmartCrop(image, o.Width, o.Height) + // it's already at an appropriate size, return immediately + if inWidth <= o.Width && inHeight <= o.Height { + break + } + width := int(math.Min(float64(inWidth), float64(o.Width))) + height := int(math.Min(float64(inHeight), float64(o.Height))) + image, err = vipsSmartCrop(image, width, height) break case o.Crop: + // it's already at an appropriate size, return immediately + if inWidth <= o.Width && inHeight <= o.Height { + break + } width := int(math.Min(float64(inWidth), float64(o.Width))) height := int(math.Min(float64(inHeight), float64(o.Height))) left, top := calculateCrop(inWidth, inHeight, o.Width, o.Height, o.Gravity) @@ -324,12 +347,12 @@ func rotateAndFlipImage(image *C.VipsImage, o Options) (*C.VipsImage, bool, erro if o.Flip { rotated = true - image, err = vipsFlip(image, Vertical) + image, err = vipsFlip(image, Horizontal) } if o.Flop { rotated = true - image, err = vipsFlip(image, Horizontal) + image, err = vipsFlip(image, Vertical) } return image, rotated, err } @@ -367,7 +390,6 @@ func watermarkImageWithText(image *C.VipsImage, w Watermark) (*C.VipsImage, erro } func watermarkImageWithAnotherImage(image *C.VipsImage, w WatermarkImage) (*C.VipsImage, error) { - if len(w.Buf) == 0 { return image, nil } @@ -386,8 +408,7 @@ func watermarkImageWithAnotherImage(image *C.VipsImage, w WatermarkImage) (*C.Vi } func imageFlatten(image *C.VipsImage, imageType ImageType, o Options) (*C.VipsImage, error) { - // Only PNG images are supported for now - if imageType != PNG || o.Background == ColorBlack { + if o.Background == ColorBlack { return image, nil } return vipsFlattenBackground(image, o.Background) diff --git a/resizer_test.go b/resizer_test.go index 6a116a26..9d34485a 100644 --- a/resizer_test.go +++ b/resizer_test.go @@ -40,7 +40,7 @@ func TestResizeVerticalImage(t *testing.T) { {Width: 1000, Height: 1500}, {Width: 1000}, {Height: 1500}, - {Width: 100, Height: 50}, + {Width: 200, Height: 120}, {Width: 2000, Height: 2000}, {Width: 500, Height: 1000}, {Width: 500}, @@ -72,7 +72,7 @@ func TestResizeVerticalImage(t *testing.T) { for _, options := range tests { image, err := Resize(source.buf, options) if err != nil { - t.Errorf("Resize(imgData, %#v) error: %#v", options, err) + t.Fatalf("Resize(imgData, %#v) error: %#v", options, err) } format := DetermineImageType(image) @@ -590,6 +590,76 @@ func TestIfBothSmartCropOptionsAreIdentical(t *testing.T) { } } +func TestSkipCropIfTooSmall(t *testing.T) { + testCases := []struct { + name string + options Options + }{ + { + name: "smart crop", + options: Options{ + Width: 140, + Height: 140, + Crop: true, + Gravity: GravitySmart, + }, + }, + { + name: "centre crop", + options: Options{ + Width: 140, + Height: 140, + Crop: true, + Gravity: GravityCentre, + }, + }, + { + name: "embed", + options: Options{ + Width: 140, + Height: 140, + Embed: true, + }, + }, + { + name: "extract", + options: Options{ + Top: 0, + Left: 0, + AreaWidth: 140, + AreaHeight: 140, + }, + }, + } + + testImg, err := os.Open("testdata/test_bad_extract_area.jpg") + if err != nil { + t.Fatal(err) + } + defer testImg.Close() + + testImgByte, err := ioutil.ReadAll(testImg) + if err != nil { + t.Fatal(err) + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + + croppedImage, err := Resize(testImgByte, tc.options) + if err != nil { + t.Fatal(err) + } + + size, _ := Size(croppedImage) + if tc.options.Height-size.Height > 1 || tc.options.Width-size.Width > 1 { + t.Fatalf("Invalid image size: %dx%d", size.Width, size.Height) + } + t.Logf("size for %s is %dx%d", tc.name, size.Width, size.Height) + }) + } +} + func runBenchmarkResize(file string, o Options, b *testing.B) { buf, _ := Read(path.Join("testdata", file)) diff --git a/testdata/exif/Landscape_1_out.jpg b/testdata/exif/Landscape_1_out.jpg index 033589e8..6a97a05d 100644 Binary files a/testdata/exif/Landscape_1_out.jpg and b/testdata/exif/Landscape_1_out.jpg differ diff --git a/testdata/exif/Landscape_2_out.jpg b/testdata/exif/Landscape_2_out.jpg index 1b2259f1..26f26aa5 100644 Binary files a/testdata/exif/Landscape_2_out.jpg and b/testdata/exif/Landscape_2_out.jpg differ diff --git a/testdata/exif/Landscape_3_out.jpg b/testdata/exif/Landscape_3_out.jpg index 681964a3..9399669f 100644 Binary files a/testdata/exif/Landscape_3_out.jpg and b/testdata/exif/Landscape_3_out.jpg differ diff --git a/testdata/exif/Landscape_4_out.jpg b/testdata/exif/Landscape_4_out.jpg index fee30ead..1f7ef6dc 100644 Binary files a/testdata/exif/Landscape_4_out.jpg and b/testdata/exif/Landscape_4_out.jpg differ diff --git a/testdata/exif/Landscape_5_out.jpg b/testdata/exif/Landscape_5_out.jpg index fbd8f5b1..280656d8 100644 Binary files a/testdata/exif/Landscape_5_out.jpg and b/testdata/exif/Landscape_5_out.jpg differ diff --git a/testdata/exif/Landscape_6_out.jpg b/testdata/exif/Landscape_6_out.jpg index 1ec5d125..9f9913e1 100644 Binary files a/testdata/exif/Landscape_6_out.jpg and b/testdata/exif/Landscape_6_out.jpg differ diff --git a/testdata/exif/Landscape_7_out.jpg b/testdata/exif/Landscape_7_out.jpg index 8a4f94c5..632da8c4 100644 Binary files a/testdata/exif/Landscape_7_out.jpg and b/testdata/exif/Landscape_7_out.jpg differ diff --git a/testdata/exif/Landscape_8_out.jpg b/testdata/exif/Landscape_8_out.jpg index 3e50c6fd..03f5d89c 100644 Binary files a/testdata/exif/Landscape_8_out.jpg and b/testdata/exif/Landscape_8_out.jpg differ diff --git a/testdata/exif/Portrait_1_out.jpg b/testdata/exif/Portrait_1_out.jpg index e3af98ce..1e5e4f21 100644 Binary files a/testdata/exif/Portrait_1_out.jpg and b/testdata/exif/Portrait_1_out.jpg differ diff --git a/testdata/exif/Portrait_2_out.jpg b/testdata/exif/Portrait_2_out.jpg index 5c5366f4..45cf4a5c 100644 Binary files a/testdata/exif/Portrait_2_out.jpg and b/testdata/exif/Portrait_2_out.jpg differ diff --git a/testdata/exif/Portrait_3_out.jpg b/testdata/exif/Portrait_3_out.jpg index 8a8da006..8625fd3c 100644 Binary files a/testdata/exif/Portrait_3_out.jpg and b/testdata/exif/Portrait_3_out.jpg differ diff --git a/testdata/exif/Portrait_4_out.jpg b/testdata/exif/Portrait_4_out.jpg index 2528749a..05ce8271 100644 Binary files a/testdata/exif/Portrait_4_out.jpg and b/testdata/exif/Portrait_4_out.jpg differ diff --git a/testdata/exif/Portrait_5_out.jpg b/testdata/exif/Portrait_5_out.jpg index c2b2ff3c..675a67a8 100644 Binary files a/testdata/exif/Portrait_5_out.jpg and b/testdata/exif/Portrait_5_out.jpg differ diff --git a/testdata/exif/Portrait_6_out.jpg b/testdata/exif/Portrait_6_out.jpg index a13b3f1a..d982b7a7 100644 Binary files a/testdata/exif/Portrait_6_out.jpg and b/testdata/exif/Portrait_6_out.jpg differ diff --git a/testdata/exif/Portrait_7_out.jpg b/testdata/exif/Portrait_7_out.jpg index e64cc8d3..acde0ccd 100644 Binary files a/testdata/exif/Portrait_7_out.jpg and b/testdata/exif/Portrait_7_out.jpg differ diff --git a/testdata/exif/Portrait_8_out.jpg b/testdata/exif/Portrait_8_out.jpg index ffe52aa2..2c0f901c 100644 Binary files a/testdata/exif/Portrait_8_out.jpg and b/testdata/exif/Portrait_8_out.jpg differ diff --git a/testdata/parameter_trim.png b/testdata/parameter_trim.png new file mode 100644 index 00000000..1334aae4 Binary files /dev/null and b/testdata/parameter_trim.png differ diff --git a/testdata/test.avif b/testdata/test.avif new file mode 100644 index 00000000..d37c2734 Binary files /dev/null and b/testdata/test.avif differ diff --git a/testdata/test3.heic b/testdata/test3.heic new file mode 100644 index 00000000..bbe7b487 Binary files /dev/null and b/testdata/test3.heic differ diff --git a/testdata/test_bad_extract_area.jpg b/testdata/test_bad_extract_area.jpg new file mode 100644 index 00000000..9ce3cd56 Binary files /dev/null and b/testdata/test_bad_extract_area.jpg differ diff --git a/testdata/test_exif.jpg b/testdata/test_exif.jpg new file mode 100644 index 00000000..a0d98b03 Binary files /dev/null and b/testdata/test_exif.jpg differ diff --git a/testdata/test_exif_canon.jpg b/testdata/test_exif_canon.jpg new file mode 100644 index 00000000..6eb33f12 Binary files /dev/null and b/testdata/test_exif_canon.jpg differ diff --git a/testdata/test_exif_full.jpg b/testdata/test_exif_full.jpg new file mode 100644 index 00000000..1bb6a759 Binary files /dev/null and b/testdata/test_exif_full.jpg differ diff --git a/testdata/test_gif.jpg b/testdata/test_gif.jpg index 7f28bcef..a3d61aab 100644 Binary files a/testdata/test_gif.jpg and b/testdata/test_gif.jpg differ diff --git a/testdata/test_pdf.jpg b/testdata/test_pdf.jpg index bbb26c52..6b283075 100644 Binary files a/testdata/test_pdf.jpg and b/testdata/test_pdf.jpg differ diff --git a/testdata/test_smart_crop.jpg b/testdata/test_smart_crop.jpg index ff4a3491..1dd3ec19 100644 Binary files a/testdata/test_smart_crop.jpg and b/testdata/test_smart_crop.jpg differ diff --git a/testdata/test_svg.jpg b/testdata/test_svg.jpg index 77b4714e..adb6d27c 100644 Binary files a/testdata/test_svg.jpg and b/testdata/test_svg.jpg differ diff --git a/testdata/transparent_trim.png b/testdata/transparent_trim.png index 80878106..3be5f96f 100644 Binary files a/testdata/transparent_trim.png and b/testdata/transparent_trim.png differ diff --git a/type.go b/type.go index ea7ef90d..7b027fdf 100644 --- a/type.go +++ b/type.go @@ -6,6 +6,9 @@ import ( "unicode/utf8" ) +// ImageType represents an image type value. +type ImageType int + const ( // UNKNOWN represents an unknow image type value. UNKNOWN ImageType = iota @@ -27,11 +30,10 @@ const ( MAGICK // HEIF represents the HEIC/HEIF/HVEC image type HEIF + // AVIF represents the AVIF image type. + AVIF ) -// ImageType represents an image type value. -type ImageType int - var ( htmlCommentRegex = regexp.MustCompile("(?i)") svgRegex = regexp.MustCompile(`(?i)^\s*(?:<\?xml[^>]*>\s*)?(?:]*>\s*)?]*>[^*]*<\/svg>\s*$`) @@ -48,6 +50,7 @@ var ImageTypes = map[ImageType]string{ SVG: "svg", MAGICK: "magick", HEIF: "heif", + AVIF: "avif", } // imageMutex is used to provide thread-safe synchronization diff --git a/type_test.go b/type_test.go index f3ee2196..7154da29 100644 --- a/type_test.go +++ b/type_test.go @@ -18,9 +18,11 @@ func TestDeterminateImageType(t *testing.T) { {"test.gif", GIF}, {"test.pdf", PDF}, {"test.svg", SVG}, - {"test.jp2", MAGICK}, + // {"test.jp2", MAGICK}, {"test.heic", HEIF}, {"test2.heic", HEIF}, + {"test3.heic", HEIF}, + {"test.avif", AVIF}, } for _, file := range files { @@ -29,8 +31,9 @@ func TestDeterminateImageType(t *testing.T) { defer img.Close() if VipsIsTypeSupported(file.expected) { - if DetermineImageType(buf) != file.expected { - t.Fatalf("Image type is not valid: %s != %s", file.name, ImageTypes[file.expected]) + value := DetermineImageType(buf) + if value != file.expected { + t.Fatalf("Image type is not valid: %s != %s, got: %s", file.name, ImageTypes[file.expected], ImageTypes[value]) } } } @@ -47,17 +50,26 @@ func TestDeterminateImageTypeName(t *testing.T) { {"test.gif", "gif"}, {"test.pdf", "pdf"}, {"test.svg", "svg"}, - {"test.jp2", "magick"}, + // {"test.jp2", "magick"}, {"test.heic", "heif"}, + {"test.avif", "avif"}, } for _, file := range files { + if file.expected == "heif" && VipsMajorVersion <= 8 && VipsMinorVersion < 8 { + continue + } + if file.expected == "avif" && VipsMajorVersion <= 8 && VipsMinorVersion < 9 { + continue + } + img, _ := os.Open(path.Join("testdata", file.name)) buf, _ := ioutil.ReadAll(img) defer img.Close() - if DetermineImageTypeName(buf) != file.expected { - t.Fatalf("Image type is not valid: %s != %s", file.name, file.expected) + value := DetermineImageTypeName(buf) + if value != file.expected { + t.Fatalf("Image type is not valid: %s != %s, got: %s", file.name, file.expected, value) } } } @@ -66,10 +78,16 @@ func TestIsTypeSupported(t *testing.T) { types := []struct { name ImageType }{ - {JPEG}, {PNG}, {WEBP}, {GIF}, {PDF}, {HEIF}, + {JPEG}, {PNG}, {WEBP}, {GIF}, {PDF}, {HEIF}, {AVIF}, } for _, n := range types { + if n.name == HEIF && VipsMajorVersion <= 8 && VipsMinorVersion < 8 { + continue + } + if n.name == AVIF && VipsMajorVersion <= 8 && VipsMinorVersion < 9 { + continue + } if IsTypeSupported(n.name) == false { t.Fatalf("Image type %s is not valid", ImageTypes[n.name]) } @@ -87,9 +105,16 @@ func TestIsTypeNameSupported(t *testing.T) { {"gif", true}, {"pdf", true}, {"heif", true}, + {"avif", true}, } for _, n := range types { + if n.name == "heif" && VipsMajorVersion <= 8 && VipsMinorVersion < 8 { + continue + } + if n.name == "avif" && VipsMajorVersion <= 8 && VipsMinorVersion < 9 { + continue + } if IsTypeNameSupported(n.name) != n.expected { t.Fatalf("Image type %s is not valid", n.name) } @@ -108,6 +133,9 @@ func TestIsTypeSupportedSave(t *testing.T) { if VipsVersion >= "8.8.0" { types = append(types, struct{ name ImageType }{HEIF}) } + if VipsVersion >= "8.9.0" { + types = append(types, struct{ name ImageType }{AVIF}) + } for _, n := range types { if IsTypeSupportedSave(n.name) == false { @@ -128,6 +156,7 @@ func TestIsTypeNameSupportedSave(t *testing.T) { {"pdf", false}, {"tiff", VipsVersion >= "8.5.0"}, {"heif", VipsVersion >= "8.8.0"}, + {"avif", VipsVersion >= "8.9.0"}, } for _, n := range types { diff --git a/version.go b/version.go index e6e84d4d..cf330d2a 100644 --- a/version.go +++ b/version.go @@ -1,4 +1,4 @@ package bimg // Version represents the current package semantic version. -const Version = "1.0.20" +const Version = "1.1.5" diff --git a/vips.go b/vips.go index e9f8b91e..363c59b3 100644 --- a/vips.go +++ b/vips.go @@ -45,6 +45,7 @@ type VipsMemoryInfo struct { // vipsSaveOptions represents the internal option used to talk with libvips. type vipsSaveOptions struct { + Speed int Quality int Compression int Type ImageType @@ -52,8 +53,10 @@ type vipsSaveOptions struct { NoProfile bool StripMetadata bool Lossless bool + InputICC string // Absolute path to the input ICC profile OutputICC string // Absolute path to the output ICC profile Interpretation Interpretation + Palette bool } type vipsWatermarkOptions struct { @@ -192,6 +195,9 @@ func VipsIsTypeSupported(t ImageType) bool { if t == HEIF { return int(C.vips_type_find_bridge(C.HEIF)) != 0 } + if t == AVIF { + return int(C.vips_type_find_bridge(C.HEIF)) != 0 + } return false } @@ -214,13 +220,31 @@ func VipsIsTypeSupportedSave(t ImageType) bool { if t == HEIF { return int(C.vips_type_find_save_bridge(C.HEIF)) != 0 } + if t == AVIF { + return int(C.vips_type_find_save_bridge(C.HEIF)) != 0 + } return false } +func vipsExifStringTag(image *C.VipsImage, tag string) string { + return vipsExifShort(C.GoString(C.vips_exif_tag(image, C.CString(tag)))) +} + +func vipsExifIntTag(image *C.VipsImage, tag string) int { + return int(C.vips_exif_tag_to_int(image, C.CString(tag))) +} + func vipsExifOrientation(image *C.VipsImage) int { return int(C.vips_exif_orientation(image)) } +func vipsExifShort(s string) string { + if strings.Contains(s, " (") { + return s[:strings.Index(s, "(")-1] + } + return s +} + func vipsHasAlpha(image *C.VipsImage) bool { return int(C.has_alpha_channel(image)) > 0 } @@ -243,7 +267,19 @@ func vipsRotate(image *C.VipsImage, angle Angle) (*C.VipsImage, error) { var out *C.VipsImage defer C.g_object_unref(C.gpointer(image)) - err := C.vips_rotate_bimg(image, &out, C.int(angle)) + err := C.vips_rotate_bridge(image, &out, C.int(angle)) + if err != 0 { + return nil, catchVipsError() + } + + return out, nil +} + +func vipsAutoRotate(image *C.VipsImage) (*C.VipsImage, error) { + var out *C.VipsImage + defer C.g_object_unref(C.gpointer(image)) + + err := C.vips_autorot_bridge(image, &out) if err != 0 { return nil, catchVipsError() } @@ -251,6 +287,23 @@ func vipsRotate(image *C.VipsImage, angle Angle) (*C.VipsImage, error) { return out, nil } +func vipsTransformICC(image *C.VipsImage, inputICC string, outputICC string) (*C.VipsImage, error) { + var out *C.VipsImage + defer C.g_object_unref(C.gpointer(image)) + + outputIccPath := C.CString(outputICC) + defer C.free(unsafe.Pointer(outputIccPath)) + inputIccPath := C.CString(inputICC) + defer C.free(unsafe.Pointer(inputIccPath)) + err := C.vips_icc_transform_with_default_bridge(image, &out, outputIccPath, inputIccPath) + //err := C.vips_icc_transform_bridge2(image, &outImage, outputIccPath, inputIccPath) + if int(err) != 0 { + return nil, catchVipsError() + } + + return out, nil +} + func vipsFlip(image *C.VipsImage, direction Direction) (*C.VipsImage, error) { var out *C.VipsImage defer C.g_object_unref(C.gpointer(image)) @@ -396,6 +449,21 @@ func vipsPreSave(image *C.VipsImage, o *vipsSaveOptions) (*C.VipsImage, error) { image = outImage } + if o.OutputICC != "" && o.InputICC != "" { + outputIccPath := C.CString(o.OutputICC) + defer C.free(unsafe.Pointer(outputIccPath)) + + inputIccPath := C.CString(o.InputICC) + defer C.free(unsafe.Pointer(inputIccPath)) + + err := C.vips_icc_transform_with_default_bridge(image, &outImage, outputIccPath, inputIccPath) + if int(err) != 0 { + return nil, catchVipsError() + } + C.g_object_unref(C.gpointer(image)) + return outImage, nil + } + if o.OutputICC != "" && vipsHasProfile(image) { outputIccPath := C.CString(o.OutputICC) defer C.free(unsafe.Pointer(outputIccPath)) @@ -434,6 +502,8 @@ func vipsSave(image *C.VipsImage, o vipsSaveOptions) ([]byte, error) { quality := C.int(o.Quality) strip := C.int(boolToInt(o.StripMetadata)) lossless := C.int(boolToInt(o.Lossless)) + palette := C.int(boolToInt(o.Palette)) + speed := C.int(o.Speed) if o.Type != 0 && !IsTypeSupportedSave(o.Type) { return nil, fmt.Errorf("VIPS cannot save to %#v", ImageTypes[o.Type]) @@ -443,11 +513,13 @@ func vipsSave(image *C.VipsImage, o vipsSaveOptions) ([]byte, error) { case WEBP: saveErr = C.vips_webpsave_bridge(tmpImage, &ptr, &length, strip, quality, lossless) case PNG: - saveErr = C.vips_pngsave_bridge(tmpImage, &ptr, &length, strip, C.int(o.Compression), quality, interlace) + saveErr = C.vips_pngsave_bridge(tmpImage, &ptr, &length, strip, C.int(o.Compression), quality, interlace, palette) case TIFF: saveErr = C.vips_tiffsave_bridge(tmpImage, &ptr, &length) case HEIF: saveErr = C.vips_heifsave_bridge(tmpImage, &ptr, &length, strip, quality, lossless) + case AVIF: + saveErr = C.vips_avifsave_bridge(tmpImage, &ptr, &length, strip, quality, lossless, speed) default: saveErr = C.vips_jpegsave_bridge(tmpImage, &ptr, &length, strip, quality, interlace) } @@ -604,7 +676,11 @@ func vipsEmbed(input *C.VipsImage, left, top, width, height int, extend Extend, return image, nil } -func vipsAffine(input *C.VipsImage, residualx, residualy float64, i Interpolator) (*C.VipsImage, error) { +func vipsAffine(input *C.VipsImage, residualx, residualy float64, i Interpolator, extend Extend) (*C.VipsImage, error) { + if extend > 5 { + extend = ExtendBackground + } + var image *C.VipsImage cstring := C.CString(i.String()) interpolator := C.vips_interpolate_new(cstring) @@ -613,7 +689,7 @@ func vipsAffine(input *C.VipsImage, residualx, residualy float64, i Interpolator defer C.g_object_unref(C.gpointer(input)) defer C.g_object_unref(C.gpointer(interpolator)) - err := C.vips_affine_interpolator(input, &image, C.double(residualx), 0, 0, C.double(residualy), interpolator) + err := C.vips_affine_interpolator(input, &image, C.double(residualx), 0, 0, C.double(residualy), interpolator, C.int(extend)) if err != 0 { return nil, catchVipsError() } @@ -655,14 +731,33 @@ func vipsImageType(buf []byte) ImageType { // https://github.com/strukturag/libheif/issues/83#issuecomment-421427091 if IsTypeSupported(HEIF) && buf[4] == 0x66 && buf[5] == 0x74 && buf[6] == 0x79 && buf[7] == 0x70 && buf[8] == 0x68 && buf[9] == 0x65 && buf[10] == 0x69 && buf[11] == 0x63 { - // This is a HEIC file + // This is a HEIC file, ftypheic return HEIF } if IsTypeSupported(HEIF) && buf[4] == 0x66 && buf[5] == 0x74 && buf[6] == 0x79 && buf[7] == 0x70 && buf[8] == 0x6d && buf[9] == 0x69 && buf[10] == 0x66 && buf[11] == 0x31 { - // This is a HEIF file + // This is a HEIF file, ftypmif1 + return HEIF + } + if IsTypeSupported(HEIF) && buf[4] == 0x66 && buf[5] == 0x74 && buf[6] == 0x79 && buf[7] == 0x70 && + buf[8] == 0x6d && buf[9] == 0x73 && buf[10] == 0x66 && buf[11] == 0x31 { + // This is a HEIFS file, ftypmsf1 + return HEIF + } + if IsTypeSupported(HEIF) && buf[4] == 0x66 && buf[5] == 0x74 && buf[6] == 0x79 && buf[7] == 0x70 && + buf[8] == 0x68 && buf[9] == 0x65 && buf[10] == 0x69 && buf[11] == 0x73 { + // This is a HEIFS file, ftypheis return HEIF } + if IsTypeSupported(HEIF) && buf[4] == 0x66 && buf[5] == 0x74 && buf[6] == 0x79 && buf[7] == 0x70 && + buf[8] == 0x68 && buf[9] == 0x65 && buf[10] == 0x76 && buf[11] == 0x63 { + // This is a HEIFS file, ftyphevc + return HEIF + } + if IsTypeSupported(HEIF) && buf[4] == 0x66 && buf[5] == 0x74 && buf[6] == 0x79 && buf[7] == 0x70 && + buf[8] == 0x61 && buf[9] == 0x76 && buf[10] == 0x69 && buf[11] == 0x66 { + return AVIF + } return UNKNOWN } diff --git a/vips.h b/vips.h index fedabf96..5a7e0ec3 100644 --- a/vips.h +++ b/vips.h @@ -34,6 +34,7 @@ enum types { SVG, MAGICK, HEIF, + AVIF }; typedef struct { @@ -106,8 +107,8 @@ vips_enable_cache_set_trace() { } int -vips_affine_interpolator(VipsImage *in, VipsImage **out, double a, double b, double c, double d, VipsInterpolate *interpolator) { - return vips_affine(in, out, a, b, c, d, "interpolate", interpolator, NULL); +vips_affine_interpolator(VipsImage *in, VipsImage **out, double a, double b, double c, double d, VipsInterpolate *interpolator, int extend) { + return vips_affine(in, out, a, b, c, d, "interpolate", interpolator, "extend", extend, NULL); } int @@ -192,7 +193,7 @@ vips_type_find_save_bridge(int t) { } int -vips_rotate_bimg(VipsImage *in, VipsImage **out, int angle) { +vips_rotate_bridge(VipsImage *in, VipsImage **out, int angle) { int rotate = VIPS_ANGLE_D0; angle %= 360; @@ -223,16 +224,35 @@ vips_rotate_bimg(VipsImage *in, VipsImage **out, int angle) { } int -vips_exif_orientation(VipsImage *image) { - int orientation = 0; +vips_autorot_bridge(VipsImage *in, VipsImage **out) { + return vips_autorot(in, out, NULL); +} + +const char * +vips_exif_tag(VipsImage *image, const char *tag) { const char *exif; if ( - vips_image_get_typeof(image, EXIF_IFD0_ORIENTATION) != 0 && - !vips_image_get_string(image, EXIF_IFD0_ORIENTATION, &exif) + vips_image_get_typeof(image, tag) != 0 && + !vips_image_get_string(image, tag, &exif) ) { - orientation = atoi(&exif[0]); + return &exif[0]; } - return orientation; + return ""; +} + +int +vips_exif_tag_to_int(VipsImage *image, const char *tag) { + int value = 0; + const char *exif = vips_exif_tag(image, tag); + if (strcmp(exif, "")) { + value = atoi(&exif[0]); + } + return value; +} + +int +vips_exif_orientation(VipsImage *image) { + return vips_exif_tag_to_int(image, EXIF_IFD0_ORIENTATION); } int @@ -256,9 +276,14 @@ vips_zoom_bridge(VipsImage *in, VipsImage **out, int xfac, int yfac) { int vips_embed_bridge(VipsImage *in, VipsImage **out, int left, int top, int width, int height, int extend, double r, double g, double b) { if (extend == VIPS_EXTEND_BACKGROUND) { + if (has_alpha_channel(in) == 1) { + double background[4] = {r, g, b, 0.0}; + VipsArrayDouble *vipsBackground = vips_array_double_new(background, 4); + return vips_embed(in, out, left, top, width, height, "extend", extend, "background", vipsBackground, NULL); + } else { double background[3] = {r, g, b}; - VipsArrayDouble *vipsBackground = vips_array_double_new(background, 3); - return vips_embed(in, out, left, top, width, height, "extend", extend, "background", vipsBackground, NULL); + VipsArrayDouble *vipsBackground = vips_array_double_new(background, 3); + return vips_embed(in, out, left, top, width, height, "extend", extend, "background", vipsBackground, NULL);} } return vips_embed(in, out, left, top, width, height, "extend", extend, NULL); } @@ -289,6 +314,13 @@ vips_icc_transform_bridge (VipsImage *in, VipsImage **out, const char *output_ic return vips_icc_transform(in, out, output_icc_profile, "embedded", TRUE, NULL); } + +int +vips_icc_transform_with_default_bridge (VipsImage *in, VipsImage **out, const char *output_icc_profile, const char *input_icc_profile) { + // `output_icc_profile` represents the absolute path to the output ICC profile file + return vips_icc_transform(in, out, output_icc_profile, "input_profile", input_icc_profile, "embedded", FALSE, NULL); +} + int vips_jpegsave_bridge(VipsImage *in, void **buf, size_t *len, int strip, int quality, int interlace) { return vips_jpegsave_buffer(in, buf, len, @@ -301,13 +333,14 @@ vips_jpegsave_bridge(VipsImage *in, void **buf, size_t *len, int strip, int qual } int -vips_pngsave_bridge(VipsImage *in, void **buf, size_t *len, int strip, int compression, int quality, int interlace) { -#if (VIPS_MAJOR_VERSION >= 8 || (VIPS_MAJOR_VERSION >= 7 && VIPS_MINOR_VERSION >= 42)) +vips_pngsave_bridge(VipsImage *in, void **buf, size_t *len, int strip, int compression, int quality, int interlace, int palette) { +#if (VIPS_MAJOR_VERSION >= 8 && VIPS_MINOR_VERSION >= 7) return vips_pngsave_buffer(in, buf, len, "strip", INT_TO_GBOOLEAN(strip), "compression", compression, "interlace", INT_TO_GBOOLEAN(interlace), "filter", VIPS_FOREIGN_PNG_FILTER_ALL, + "palette", INT_TO_GBOOLEAN(palette), NULL ); #else @@ -339,6 +372,30 @@ vips_tiffsave_bridge(VipsImage *in, void **buf, size_t *len) { #endif } +int +vips_avifsave_bridge(VipsImage *in, void **buf, size_t *len, int strip, int quality, int lossless, int speed) { +#if (VIPS_MAJOR_VERSION > 8 || (VIPS_MAJOR_VERSION >= 8 && VIPS_MINOR_VERSION > 10) || (VIPS_MAJOR_VERSION >= 8 && VIPS_MINOR_VERSION >= 10 && VIPS_MICRO_VERSION >= 2)) + return vips_heifsave_buffer(in, buf, len, + "strip", INT_TO_GBOOLEAN(strip), + "Q", quality, + "lossless", INT_TO_GBOOLEAN(lossless), + "compression", VIPS_FOREIGN_HEIF_COMPRESSION_AV1, + "speed", speed, + NULL + ); +#elif (VIPS_MAJOR_VERSION > 8 || (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION >= 9)) + return vips_heifsave_buffer(in, buf, len, + "strip", INT_TO_GBOOLEAN(strip), + "Q", quality, + "lossless", INT_TO_GBOOLEAN(lossless), + "compression", VIPS_FOREIGN_HEIF_COMPRESSION_AV1, + NULL + ); +#else + return 0; +#endif +} + int vips_heifsave_bridge(VipsImage *in, void **buf, size_t *len, int strip, int quality, int lossless) { #if (VIPS_MAJOR_VERSION > 8 || (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION >= 8)) @@ -403,6 +460,10 @@ vips_init_image (void *buf, size_t len, int imageType, VipsImage **out, Options #if (VIPS_MAJOR_VERSION > 8 || (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION >= 8)) } else if (imageType == HEIF) { code = vips_heifload_buffer(buf, len, out, "access", VIPS_ACCESS_RANDOM, NULL); +#endif +#if (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION >= 9) + } else if (imageType == AVIF) { + code = vips_heifload_buffer(buf, len, out, "access", VIPS_ACCESS_RANDOM, NULL); #endif } diff --git a/vips_test.go b/vips_test.go index d8bd2b23..97e02844 100644 --- a/vips_test.go +++ b/vips_test.go @@ -58,6 +58,22 @@ func TestVipsSaveTiff(t *testing.T) { } } +func TestVipsSaveAvif(t *testing.T) { + if !IsTypeSupportedSave(AVIF) { + t.Skipf("Format %#v is not supported", ImageTypes[AVIF]) + } + image, _, _ := vipsRead(readImage("test.jpg")) + options := vipsSaveOptions{Quality: 95, Type: AVIF, Speed: 8} + buf, err := vipsSave(image, options) + if err != nil { + t.Fatalf("Error saving image type %v: %v", ImageTypes[AVIF], err) + } + + if len(buf) == 0 { + t.Fatalf("Empty saved '%v' image", ImageTypes[AVIF]) + } +} + func TestVipsRotate(t *testing.T) { files := []struct { name string @@ -82,6 +98,47 @@ func TestVipsRotate(t *testing.T) { } } +func TestVipsAutoRotate(t *testing.T) { + if VipsMajorVersion <= 8 && VipsMinorVersion < 10 { + t.Skip("Skip test in libvips < 8.10") + return + } + + files := []struct { + name string + orientation int + }{ + {"test.jpg", 0}, + {"test_exif.jpg", 0}, + {"exif/Landscape_1.jpg", 0}, + {"exif/Landscape_2.jpg", 0}, + {"exif/Landscape_3.jpg", 0}, + {"exif/Landscape_4.jpg", 0}, + {"exif/Landscape_5.jpg", 5}, + {"exif/Landscape_6.jpg", 0}, + {"exif/Landscape_7.jpg", 7}, + } + + for _, file := range files { + image, _, _ := vipsRead(readImage(file.name)) + + newImg, err := vipsAutoRotate(image) + if err != nil { + t.Fatal("Cannot auto rotate the image") + } + + orientation := vipsExifOrientation(newImg) + if orientation != file.orientation { + t.Fatalf("Invalid image orientation: %d != %d", orientation, file.orientation) + } + + buf, _ := vipsSave(newImg, vipsSaveOptions{Quality: 95}) + if len(buf) == 0 { + t.Fatal("Empty image") + } + } +} + func TestVipsZoom(t *testing.T) { image, _, _ := vipsRead(readImage("test.jpg"))