pax_global_header00006660000000000000000000000064151716323550014522gustar00rootroot0000000000000052 comment=8e2991f71134d77b8eb1f9a17ea9f607c5c09e55 golang-github-h2non-bimg-1.1.9+ds/000077500000000000000000000000001517163235500166215ustar00rootroot00000000000000golang-github-h2non-bimg-1.1.9+ds/.editorconfig000066400000000000000000000004761517163235500213050ustar00rootroot00000000000000root = 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 [*.yml] indent_style = space golang-github-h2non-bimg-1.1.9+ds/.gitignore000066400000000000000000000001371517163235500206120ustar00rootroot00000000000000/bimg /bundle bin /*.jpg /*.png /*.webp /testdata/*_out.* /.idea/ testdata/test_vertical_*.jpg golang-github-h2non-bimg-1.1.9+ds/.travis.yml000066400000000000000000000013721517163235500207350ustar00rootroot00000000000000language: go dist: focal sudo: required services: - docker env: # - 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=8.8.4 cache: apt: directories: - $HOME/libvips install: - docker build -t h2non/bimg:ci --build-arg LIBVIPS_VERSION=$LIBVIPS . script: - 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 golang-github-h2non-bimg-1.1.9+ds/Dockerfile000066400000000000000000000055241517163235500206210ustar00rootroot00000000000000FROM 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" ] golang-github-h2non-bimg-1.1.9+ds/Gopkg.lock000066400000000000000000000004361517163235500205450ustar00rootroot00000000000000# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. [solve-meta] analyzer-name = "dep" analyzer-version = 1 inputs-digest = "ab4fef131ee828e96ba67d31a7d690bd5f2f42040c6766b1b12fe856f87e0ff7" solver-name = "gps-cdcl" solver-version = 1 golang-github-h2non-bimg-1.1.9+ds/Gopkg.toml000066400000000000000000000010271517163235500205650ustar00rootroot00000000000000 # Gopkg.toml example # # Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md # for detailed Gopkg.toml documentation. # # required = ["github.com/user/thing/cmd/thing"] # ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] # # [[constraint]] # name = "github.com/user/project" # version = "1.0.0" # # [[constraint]] # name = "github.com/user/project2" # branch = "dev" # source = "github.com/myfork/project2" # # [[override]] # name = "github.com/x/y" # version = "2.4.0" golang-github-h2non-bimg-1.1.9+ds/History.md000066400000000000000000001056571517163235500206220ustar00rootroot00000000000000 v1.1.9 / 2022-04-05 =================== * chore(History): version changes * Merge pull request #374 from Mereng/brightness_contrast * Merge pull request #393 from lucor/gifsave * Add GIF save support from libvips 8.12 * Support brightness and contrast v1.1.8 / 2022-04-05 =================== * chore(version): bump * Merge pull request #405 from igsr5/feat/#404-support-way-to-change–MaxSize * Fix review * Add getter, setter for MaxSize v1.1.7 / 2022-02-23 =================== * Merge pull request #398 from vaibsharma/vaibsharma/feature/speed_for_png_buffer * reason for speed=3 added * allow effort param for png encoding when palette is true v1.1.6 / 2022-01-28 =================== * Update README.md * Merge pull request #368 from exaring/fix-shrinking-on-small-webp-images * Merge pull request #360 from jaberwoky/master * Merge pull request #378 from kyfk/fix_typo_and_format * goimports * fix typo in comments * Merge pull request #377 from ZloyDyadka/vector-flag * Vips: cast go.int to c.INT in VipsVectorSetEnabled * Vips: add VipsVectorSetEnabled * Remove debug output * Fix for blurry images from WEBP input and small output dimensions * Merge pull request #367 from Keruspe/segv * unref the image *after* we used it * add test * fix panic on reading Exif 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 * 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 * 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 * 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 * 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 * 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 * Merge pull request #190 from greut/typos * Fix typo and small cleanup. ## v1.0.12 / 2017-09-10 * Merge branch '99designs-vips-reduce' * fix(reduce): resolve conflicts with master * Use vips reduce when downscaling ## v1.0.11 / 2017-09-10 * 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 * 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 * 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 * 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 / 13-01-2017 - fix(#128): crop image calculation for missing width or height axis. - feat: add TIFF save output format (**note**: this introduces a minor interface breaking change in `bimg.IsImageTypeSupportedByVips` auxiliary function). ## v1.0.6 / 12-11-2016 - feat(#118): handle 16-bit PNGs. - feat(#119): adds JPEG2000 file for the type tests. - feat(#121): test bimg against multiple libvips versions. ## v1.0.5 / 01-10-2016 - feat(#92): support Extend param with optional background. - fix(#106): allow image area extraction without explicit x/y axis. - feat(api): add Extend type with `libvips` enum alias. ## v1.0.4 / 29-09-2016 - fix(#111): safe check of magick image type support. ## v1.0.3 / 28-09-2016 - fix(#95): better image type inference and support check. - fix(background): pass proper background RGB color for PNG image conversion. - feat(types): validate supported image types by current `libvips` compilation. - feat(types): consistent SVG image checking. - feat(api): add public functions `VipsIsTypeSupported()`, `IsImageTypeSupportedByVips()` and `IsSVGImage()`. ## v1.0.2 / 27-09-2016 - feat(#95): support GIF, SVG and PDF formats. - fix(#108): auto-width and height calculations now round instead of floor. ## v1.0.1 / 22-06-2016 - fix(#90): Do not not dereference the original image a second time. ## v1.0.0 / 21-04-2016 - refactor(api): breaking changes: normalize public members to follow Go naming idioms. - feat(version): bump to major version. API contract won't be compromised in `v1`. - feat(docs): add missing inline godoc documentation. golang-github-h2non-bimg-1.1.9+ds/LICENSE000066400000000000000000000020761517163235500176330ustar00rootroot00000000000000The MIT License Copyright (c) Tomas Aparicio and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.golang-github-h2non-bimg-1.1.9+ds/README.md000066400000000000000000000236551517163235500201130ustar00rootroot00000000000000# 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) [![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 [programmatic API](#examples). 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. bimg uses internally libvips, a powerful library written in C for image processing which requires a [low memory footprint](https://github.com/jcupitt/libvips/wiki/Speed_and_Memory_Use) and it's typically 4x faster than using the quickest ImageMagick and GraphicsMagick settings or Go native `image` package, and in some cases it's even 8x faster processing JPEG images. If you're looking for an HTTP based image processing solution, see [imaginary](https://github.com/h2non/imaginary). bimg was heavily inspired in [sharp](https://github.com/lovell/sharp), its homologous package built for [node.js](http://nodejs.org). bimg is used in production environments processing thousands of images per day. **v1 notice**: `bimg` introduces some minor breaking changes in `v1` release. If you're using `gopkg.in`, you can still rely in the `v0` without worrying about API breaking changes. ## Contents - [Supported image operations](#supported-image-operations) - [Prerequisites](#prerequisites) - [Installation](#installation) - [Performance](#performance) - [Benchmark](#benchmark) - [Examples](#examples) - [Debugging](#debugging) - [API](#api) - [Authors](#authors) - [Credits](#credits) ## Supported image operations - Resize - Enlarge - Crop (including smart crop support, libvips 8.5+) - Rotate (with auto-rotate based on EXIF orientation) - Flip (with auto-flip based on EXIF metadata) - Flop - Zoom - Thumbnail - Extract area - Watermark (using text or image) - Gaussian blur effect - Custom output color space (RGB, grayscale...) - Format conversion (with additional quality/compression settings) - EXIF metadata (size, alpha channel, profile, orientation...) - Trim (libvips 8.6+) ## Prerequisites - [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. * `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 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 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 ``` The [install script](https://github.com/h2non/bimg/blob/master/preinstall.sh) requires `curl` and `pkg-config`. ## Performance libvips is probably the fastest open source solution for image processing. Here you can see some performance test comparisons for multiple scenarios: - [libvips speed and memory usage](https://github.com/jcupitt/libvips/wiki/Speed-and-memory-use) ## Benchmark Tested using Go 1.5.1 and libvips-7.42.3 in OSX i7 2.7Ghz ``` BenchmarkRotateJpeg-8 20 64686945 ns/op BenchmarkResizeLargeJpeg-8 20 63390416 ns/op BenchmarkResizePng-8 100 18147294 ns/op BenchmarkResizeWebP-8 100 20836741 ns/op BenchmarkConvertToJpeg-8 100 12831812 ns/op BenchmarkConvertToPng-8 10 128901422 ns/op BenchmarkConvertToWebp-8 10 204027990 ns/op BenchmarkCropJpeg-8 30 59068572 ns/op BenchmarkCropPng-8 10 117303259 ns/op BenchmarkCropWebP-8 10 107060659 ns/op BenchmarkExtractJpeg-8 50 30708919 ns/op BenchmarkExtractPng-8 3000 595546 ns/op BenchmarkExtractWebp-8 3000 386379 ns/op BenchmarkZoomJpeg-8 10 160005424 ns/op BenchmarkZoomPng-8 30 44561047 ns/op BenchmarkZoomWebp-8 10 126732678 ns/op BenchmarkWatermarkJpeg-8 20 79006133 ns/op BenchmarkWatermarPng-8 200 8197291 ns/op BenchmarkWatermarWebp-8 30 49360369 ns/op ``` ## Examples ```go import ( "fmt" "os" "github.com/h2non/bimg" ) ``` #### Resize ```go buffer, err := bimg.Read("image.jpg") if err != nil { fmt.Fprintln(os.Stderr, err) } newImage, err := bimg.NewImage(buffer).Resize(800, 600) if err != nil { fmt.Fprintln(os.Stderr, err) } size, err := bimg.NewImage(newImage).Size() if size.Width == 800 && size.Height == 600 { fmt.Println("The image size is valid") } bimg.Write("new.jpg", newImage) ``` #### Rotate ```go buffer, err := bimg.Read("image.jpg") if err != nil { fmt.Fprintln(os.Stderr, err) } newImage, err := bimg.NewImage(buffer).Rotate(90) if err != nil { fmt.Fprintln(os.Stderr, err) } bimg.Write("new.jpg", newImage) ``` #### Convert ```go buffer, err := bimg.Read("image.jpg") if err != nil { fmt.Fprintln(os.Stderr, err) } newImage, err := bimg.NewImage(buffer).Convert(bimg.PNG) if err != nil { fmt.Fprintln(os.Stderr, err) } if bimg.NewImage(newImage).Type() == "png" { fmt.Fprintln(os.Stderr, "The image was converted into png") } ``` #### Force resize Force resize operation without perserving the aspect ratio: ```go buffer, err := bimg.Read("image.jpg") if err != nil { fmt.Fprintln(os.Stderr, err) } newImage, err := bimg.NewImage(buffer).ForceResize(1000, 500) if err != nil { fmt.Fprintln(os.Stderr, err) } size := bimg.Size(newImage) if size.Width != 1000 || size.Height != 500 { fmt.Fprintln(os.Stderr, "Incorrect image size") } ``` #### Custom colour space (black & white) ```go buffer, err := bimg.Read("image.jpg") if err != nil { fmt.Fprintln(os.Stderr, err) } newImage, err := bimg.NewImage(buffer).Colourspace(bimg.INTERPRETATION_B_W) if err != nil { fmt.Fprintln(os.Stderr, err) } colourSpace, _ := bimg.ImageInterpretation(newImage) if colourSpace != bimg.INTERPRETATION_B_W { fmt.Fprintln(os.Stderr, "Invalid colour space") } ``` #### Custom options See [Options](https://godoc.org/github.com/h2non/bimg#Options) struct to discover all the available fields ```go options := bimg.Options{ Width: 800, Height: 600, Crop: true, Quality: 95, Rotate: 180, Interlace: true, } buffer, err := bimg.Read("image.jpg") if err != nil { fmt.Fprintln(os.Stderr, err) } newImage, err := bimg.NewImage(buffer).Process(options) if err != nil { fmt.Fprintln(os.Stderr, err) } bimg.Write("new.jpg", newImage) ``` #### Watermark ```go buffer, err := bimg.Read("image.jpg") if err != nil { fmt.Fprintln(os.Stderr, err) } watermark := bimg.Watermark{ Text: "Chuck Norris (c) 2315", Opacity: 0.25, Width: 200, DPI: 100, Margin: 150, Font: "sans bold 12", Background: bimg.Color{255, 255, 255}, } newImage, err := bimg.NewImage(buffer).Watermark(watermark) if err != nil { fmt.Fprintln(os.Stderr, err) } bimg.Write("new.jpg", newImage) ``` #### Fluent interface ```go buffer, err := bimg.Read("image.jpg") if err != nil { fmt.Fprintln(os.Stderr, err) } image := bimg.NewImage(buffer) // first crop image _, err := image.CropByWidth(300) if err != nil { fmt.Fprintln(os.Stderr, err) } // then flip it newImage, err := image.Flip() if err != nil { fmt.Fprintln(os.Stderr, err) } // save the cropped and flipped image bimg.Write("new.jpg", newImage) ``` ## Debugging Run the process passing the `DEBUG` environment variable ``` DEBUG=bimg ./app ``` Enable libvips traces (note that a lot of data will be written in stdout): ``` VIPS_TRACE=1 ./app ``` You can also dump a core on failure, as [John Cuppit](https://github.com/jcupitt) said: ```c g_log_set_always_fatal( G_LOG_FLAG_RECURSION | G_LOG_FLAG_FATAL | G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING ); ``` Or set the G_DEBUG environment variable: ``` export G_DEBUG=fatal-warnings,fatal-criticals ``` ## API See [godoc reference](https://godoc.org/github.com/h2non/bimg) for detailed API documentation. ## Authors - [Tomás Aparicio](https://github.com/h2non) - Original author and architect. ## Credits People who recurrently contributed to improve `bimg` in some way. - [John Cupitt](https://github.com/jcupitt) - [Yoan Blanc](https://github.com/greut) - [Christophe Eblé](https://github.com/chreble) - [Brant Fitzsimmons](https://github.com/bfitzsimmons) - [Thomas Meson](https://github.com/zllak) Thank you! ## License MIT - Tomas Aparicio [![views](https://sourcegraph.com/api/repos/github.com/h2non/bimg/.counters/views.svg)](https://sourcegraph.com/github.com/h2non/bimg) golang-github-h2non-bimg-1.1.9+ds/file.go000066400000000000000000000005461517163235500200740ustar00rootroot00000000000000package bimg import "io/ioutil" // Read reads all the content of the given file path // and returns it as byte buffer. func Read(path string) ([]byte, error) { return ioutil.ReadFile(path) } // Write writes the given byte buffer into disk // to the given file path. func Write(path string, buf []byte) error { return ioutil.WriteFile(path, buf, 0644) } golang-github-h2non-bimg-1.1.9+ds/file_test.go000066400000000000000000000011341517163235500211250ustar00rootroot00000000000000package bimg import ( "testing" ) func TestRead(t *testing.T) { buf, err := Read("testdata/test.jpg") if err != nil { t.Errorf("Cannot read the image: %#v", err) } if len(buf) == 0 { t.Fatal("Empty buffer") } if DetermineImageType(buf) != JPEG { t.Fatal("Image is not jpeg") } } func TestWrite(t *testing.T) { buf, err := Read("testdata/test.jpg") if err != nil { t.Errorf("Cannot read the image: %#v", err) } if len(buf) == 0 { t.Fatal("Empty buffer") } err = Write("testdata/test_write_out.jpg", buf) if err != nil { t.Fatalf("Cannot write the file: %#v", err) } } golang-github-h2non-bimg-1.1.9+ds/image.go000066400000000000000000000146271517163235500202440ustar00rootroot00000000000000package bimg // Image provides a simple method DSL to transform a given image as byte buffer. type Image struct { buffer []byte } // NewImage creates a new Image struct with method DSL. func NewImage(buf []byte) *Image { return &Image{buf} } // Resize resizes the image to fixed width and height. func (i *Image) Resize(width, height int) ([]byte, error) { options := Options{ Width: width, Height: height, Embed: true, } return i.Process(options) } // ForceResize resizes with custom size (aspect ratio won't be maintained). func (i *Image) ForceResize(width, height int) ([]byte, error) { options := Options{ Width: width, Height: height, Force: true, } return i.Process(options) } // ResizeAndCrop resizes the image to fixed width and height with additional crop transformation. func (i *Image) ResizeAndCrop(width, height int) ([]byte, error) { options := Options{ Width: width, Height: height, Embed: true, Crop: true, } return i.Process(options) } // SmartCrop produces a thumbnail aiming at focus on the interesting part. func (i *Image) SmartCrop(width, height int) ([]byte, error) { options := Options{ Width: width, Height: height, Crop: true, Gravity: GravitySmart, } return i.Process(options) } // Extract area from the by X/Y axis in the current image. func (i *Image) Extract(top, left, width, height int) ([]byte, error) { options := Options{ Top: top, Left: left, AreaWidth: width, AreaHeight: height, } if top == 0 && left == 0 { options.Top = -1 } return i.Process(options) } // Enlarge enlarges the image by width and height. Aspect ratio is maintained. func (i *Image) Enlarge(width, height int) ([]byte, error) { options := Options{ Width: width, Height: height, Enlarge: true, } return i.Process(options) } // EnlargeAndCrop enlarges the image by width and height with additional crop transformation. func (i *Image) EnlargeAndCrop(width, height int) ([]byte, error) { options := Options{ Width: width, Height: height, Enlarge: true, Crop: true, } return i.Process(options) } // Crop crops the image to the exact size specified. func (i *Image) Crop(width, height int, gravity Gravity) ([]byte, error) { options := Options{ Width: width, Height: height, Gravity: gravity, Crop: true, } return i.Process(options) } // CropByWidth crops an image by width only param (auto height). func (i *Image) CropByWidth(width int) ([]byte, error) { options := Options{ Width: width, Crop: true, } return i.Process(options) } // CropByHeight crops an image by height (auto width). func (i *Image) CropByHeight(height int) ([]byte, error) { options := Options{ Height: height, Crop: true, } return i.Process(options) } // Thumbnail creates a thumbnail of the image by the a given width by aspect ratio 4:4. func (i *Image) Thumbnail(pixels int) ([]byte, error) { options := Options{ Width: pixels, Height: pixels, Crop: true, Quality: 95, } return i.Process(options) } // Watermark adds text as watermark on the given image. func (i *Image) Watermark(w Watermark) ([]byte, error) { options := Options{Watermark: w} return i.Process(options) } // WatermarkImage adds image as watermark on the given image. func (i *Image) WatermarkImage(w WatermarkImage) ([]byte, error) { options := Options{WatermarkImage: w} return i.Process(options) } // Zoom zooms the image by the given factor. // You should probably call Extract() before. func (i *Image) Zoom(factor int) ([]byte, error) { options := Options{Zoom: factor} return i.Process(options) } // Rotate rotates the image by given angle degrees (0, 90, 180 or 270). func (i *Image) Rotate(a Angle) ([]byte, error) { options := Options{Rotate: a} 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} return i.Process(options) } // Flop flops the image about the horizontal X axis. func (i *Image) Flop() ([]byte, error) { options := Options{Flop: true} return i.Process(options) } // Convert converts image to another format. func (i *Image) Convert(t ImageType) ([]byte, error) { options := Options{Type: t} return i.Process(options) } // Colourspace performs a color space conversion bsaed on the given interpretation. func (i *Image) Colourspace(c Interpretation) ([]byte, error) { options := Options{Interpretation: c} return i.Process(options) } // Trim removes the background from the picture. It can result in a 0x0 output // if the image is all background. func (i *Image) Trim() ([]byte, error) { options := Options{Trim: true} return i.Process(options) } // Gamma returns the gamma filtered image buffer. func (i *Image) Gamma(exponent float64) ([]byte, error) { options := Options{Gamma: exponent} return i.Process(options) } // Process processes the image based on the given transformation options, // talking with libvips bindings accordingly and returning the resultant // image buffer. func (i *Image) Process(o Options) ([]byte, error) { image, err := Resize(i.buffer, o) if err != nil { return nil, err } i.buffer = image return image, nil } // Metadata returns the image metadata (size, alpha channel, profile, EXIF rotation). func (i *Image) Metadata() (ImageMetadata, error) { return Metadata(i.buffer) } // Interpretation gets the image interpretation type. // See: https://libvips.github.io/libvips/API/current/VipsImage.html#VipsInterpretation func (i *Image) Interpretation() (Interpretation, error) { return ImageInterpretation(i.buffer) } // ColourspaceIsSupported checks if the current image // color space is supported. func (i *Image) ColourspaceIsSupported() (bool, error) { return ColourspaceIsSupported(i.buffer) } // Type returns the image type format (jpeg, png, webp, tiff). func (i *Image) Type() string { return DetermineImageTypeName(i.buffer) } // Size returns the image size as form of width and height pixels. func (i *Image) Size() (ImageSize, error) { return Size(i.buffer) } // Image returns the current resultant image buffer. func (i *Image) Image() []byte { return i.buffer } // Length returns the size in bytes of the image buffer. func (i *Image) Length() int { return len(i.buffer) } golang-github-h2non-bimg-1.1.9+ds/image_test.go000066400000000000000000000312621517163235500212750ustar00rootroot00000000000000package bimg import ( "fmt" "path" "testing" ) func TestImageResize(t *testing.T) { buf, err := initImage("test.jpg").Resize(300, 240) if err != nil { t.Errorf("Cannot process the image: %#v", err) } err = assertSize(buf, 300, 240) if err != nil { t.Error(err) } Write("testdata/test_resize_out.jpg", buf) } func TestImageGifResize(t *testing.T) { if VipsMajorVersion >= 8 && VipsMinorVersion >= 12 { buf, err := initImage("test.gif").Resize(300, 240) if err != nil { t.Errorf("Cannot process the image: %#v", err) } err = assertSize(buf, 300, 240) if err != nil { t.Error(err) } Write("testdata/test_resize_out.gif", buf) } } func TestImagePdfResize(t *testing.T) { _, err := initImage("test.pdf").Resize(300, 240) if err == nil { t.Errorf("PDF cannot be saved within VIPS") } } func TestImageSvgResize(t *testing.T) { _, err := initImage("test.svg").Resize(300, 240) if err == nil { t.Errorf("SVG cannot be saved within VIPS") } } func TestImageGifToJpeg(t *testing.T) { if VipsMajorVersion >= 8 && VipsMinorVersion > 2 { i := initImage("test.gif") options := Options{ Type: JPEG, } buf, err := i.Process(options) if err != nil { t.Errorf("Cannot process the image: %#v", err) } Write("testdata/test_gif.jpg", buf) } } func TestImagePdfToJpeg(t *testing.T) { if VipsMajorVersion >= 8 && VipsMinorVersion > 2 { i := initImage("test.pdf") options := Options{ Type: JPEG, } buf, err := i.Process(options) if err != nil { t.Errorf("Cannot process the image: %#v", err) } Write("testdata/test_pdf.jpg", buf) } } func TestImageSvgToJpeg(t *testing.T) { if VipsMajorVersion >= 8 && VipsMinorVersion > 2 { i := initImage("test.svg") options := Options{ Type: JPEG, } buf, err := i.Process(options) if err != nil { t.Errorf("Cannot process the image: %#v", err) } Write("testdata/test_svg.jpg", buf) } } func TestImageResizeAndCrop(t *testing.T) { buf, err := initImage("test.jpg").ResizeAndCrop(300, 200) if err != nil { t.Errorf("Cannot process the image: %#v", err) } err = assertSize(buf, 300, 200) if err != nil { t.Error(err) } Write("testdata/test_resize_crop_out.jpg", buf) } func TestImageExtract(t *testing.T) { buf, err := initImage("test.jpg").Extract(100, 100, 300, 200) if err != nil { t.Errorf("Cannot process the image: %s", err) } err = assertSize(buf, 300, 200) if err != nil { t.Error(err) } Write("testdata/test_extract_out.jpg", buf) } func TestImageExtractZero(t *testing.T) { buf, err := initImage("test.jpg").Extract(0, 0, 300, 200) if err != nil { t.Errorf("Cannot process the image: %s", err) } err = assertSize(buf, 300, 200) if err != nil { t.Error(err) } Write("testdata/test_extract_zero_out.jpg", buf) } func TestImageEnlarge(t *testing.T) { buf, err := initImage("test.png").Enlarge(500, 375) if err != nil { t.Errorf("Cannot process the image: %#v", err) } err = assertSize(buf, 500, 375) if err != nil { t.Error(err) } Write("testdata/test_enlarge_out.jpg", buf) } func TestImageEnlargeAndCrop(t *testing.T) { buf, err := initImage("test.png").EnlargeAndCrop(800, 480) if err != nil { t.Errorf("Cannot process the image: %#v", err) } err = assertSize(buf, 800, 480) if err != nil { t.Error(err) } Write("testdata/test_enlarge_crop_out.jpg", buf) } func TestImageCrop(t *testing.T) { buf, err := initImage("test.jpg").Crop(800, 600, GravityNorth) if err != nil { t.Errorf("Cannot process the image: %s", err) } err = assertSize(buf, 800, 600) if err != nil { t.Error(err) } Write("testdata/test_crop_out.jpg", buf) } func TestImageCropByWidth(t *testing.T) { buf, err := initImage("test.jpg").CropByWidth(600) if err != nil { t.Errorf("Cannot process the image: %s", err) } err = assertSize(buf, 600, 1050) if err != nil { t.Error(err) } Write("testdata/test_crop_width_out.jpg", buf) } func TestImageCropByHeight(t *testing.T) { buf, err := initImage("test.jpg").CropByHeight(300) if err != nil { t.Errorf("Cannot process the image: %s", err) } err = assertSize(buf, 1680, 300) if err != nil { t.Error(err) } Write("testdata/test_crop_height_out.jpg", buf) } func TestImageThumbnail(t *testing.T) { buf, err := initImage("test.jpg").Thumbnail(100) if err != nil { t.Errorf("Cannot process the image: %s", err) } err = assertSize(buf, 100, 100) if err != nil { t.Error(err) } Write("testdata/test_thumbnail_out.jpg", buf) } func TestImageWatermark(t *testing.T) { image := initImage("test.jpg") _, err := image.Crop(800, 600, GravityNorth) if err != nil { t.Errorf("Cannot process the image: %#v", err) } buf, err := image.Watermark(Watermark{ Text: "Copy me if you can", Opacity: 0.5, Width: 200, DPI: 100, Background: Color{255, 255, 255}, }) if err != nil { t.Error(err) } err = assertSize(buf, 800, 600) if err != nil { t.Error(err) } if DetermineImageType(buf) != JPEG { t.Fatal("Image is not jpeg") } Write("testdata/test_watermark_text_out.jpg", buf) } func TestImageWatermarkWithImage(t *testing.T) { image := initImage("test.jpg") watermark, _ := imageBuf("transparent.png") _, err := image.Crop(800, 600, GravityNorth) if err != nil { t.Errorf("Cannot process the image: %#v", err) } buf, err := image.WatermarkImage(WatermarkImage{Left: 100, Top: 100, Buf: watermark}) if err != nil { t.Error(err) } err = assertSize(buf, 800, 600) if err != nil { t.Error(err) } if DetermineImageType(buf) != JPEG { t.Fatal("Image is not jpeg") } Write("testdata/test_watermark_image_out.jpg", buf) } func TestImageWatermarkNoReplicate(t *testing.T) { image := initImage("test.jpg") _, err := image.Crop(800, 600, GravityNorth) if err != nil { t.Errorf("Cannot process the image: %s", err) } buf, err := image.Watermark(Watermark{ Text: "Copy me if you can", Opacity: 0.5, Width: 200, DPI: 100, NoReplicate: true, Background: Color{255, 255, 255}, }) if err != nil { t.Error(err) } err = assertSize(buf, 800, 600) if err != nil { t.Error(err) } if DetermineImageType(buf) != JPEG { t.Fatal("Image is not jpeg") } Write("testdata/test_watermark_replicate_out.jpg", buf) } func TestImageZoom(t *testing.T) { image := initImage("test.jpg") _, err := image.Extract(100, 100, 400, 300) if err != nil { t.Errorf("Cannot extract the image: %s", err) } buf, err := image.Zoom(1) if err != nil { t.Errorf("Cannot process the image: %s", err) } err = assertSize(buf, 800, 600) if err != nil { t.Error(err) } Write("testdata/test_zoom_out.jpg", buf) } func TestImageFlip(t *testing.T) { buf, err := initImage("test.jpg").Flip() if err != nil { t.Errorf("Cannot process the image: %#v", err) } Write("testdata/test_flip_out.jpg", buf) } func TestImageFlop(t *testing.T) { buf, err := initImage("test.jpg").Flop() if err != nil { t.Errorf("Cannot process the image: %#v", err) } Write("testdata/test_flop_out.jpg", buf) } func TestImageRotate(t *testing.T) { buf, err := initImage("test_flip_out.jpg").Rotate(90) if err != nil { t.Errorf("Cannot process the image: %#v", err) } 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 { t.Errorf("Cannot process the image: %#v", err) } Write("testdata/test_image_convert_out.png", buf) } func TestTransparentImageConvert(t *testing.T) { image := initImage("transparent.png") options := Options{ Type: JPEG, Background: Color{255, 255, 255}, } buf, err := image.Process(options) if err != nil { t.Errorf("Cannot process the image: %#v", err) } Write("testdata/test_transparent_image_convert_out.jpg", buf) } func TestImageMetadata(t *testing.T) { data, err := initImage("test.png").Metadata() if err != nil { t.Errorf("Cannot process the image: %#v", err) } if data.Alpha != true { t.Fatal("Invalid alpha channel") } if data.Size.Width != 400 { t.Fatal("Invalid width size") } if data.Type != "png" { t.Fatal("Invalid image type") } } func TestInterpretation(t *testing.T) { interpretation, err := initImage("test.jpg").Interpretation() if err != nil { t.Errorf("Cannot process the image: %#v", err) } if interpretation != InterpretationSRGB { t.Errorf("Invalid interpretation: %d", interpretation) } } func TestImageColourspace(t *testing.T) { tests := []struct { file string interpretation Interpretation }{ {"test.jpg", InterpretationSRGB}, {"test.jpg", InterpretationBW}, } for _, test := range tests { buf, err := initImage(test.file).Colourspace(test.interpretation) if err != nil { t.Errorf("Cannot process the image: %#v", err) } interpretation, err := ImageInterpretation(buf) if interpretation != test.interpretation { t.Errorf("Invalid colourspace") } } } func TestImageColourspaceIsSupported(t *testing.T) { supported, err := initImage("test.jpg").ColourspaceIsSupported() if err != nil { t.Errorf("Cannot process the image: %#v", err) } if supported != true { t.Errorf("Non-supported colourspace") } } func TestFluentInterface(t *testing.T) { image := initImage("test.jpg") _, err := image.CropByWidth(300) if err != nil { t.Errorf("Cannot process the image: %#v", err) } _, err = image.Flip() if err != nil { t.Errorf("Cannot process the image: %#v", err) } _, err = image.Convert(PNG) if err != nil { t.Errorf("Cannot process the image: %#v", err) } data, _ := image.Metadata() if data.Alpha != false { t.Fatal("Invalid alpha channel") } if data.Size.Width != 300 { t.Fatal("Invalid width size") } if data.Type != "png" { t.Fatal("Invalid image type") } Write("testdata/test_image_fluent_out.png", image.Image()) } func TestImageSmartCrop(t *testing.T) { if !(VipsMajorVersion >= 8 && VipsMinorVersion >= 5) { t.Skipf("Skipping this test, libvips doesn't meet version requirement %s >= 8.5", VipsVersion) } i := initImage("northern_cardinal_bird.jpg") buf, err := i.SmartCrop(300, 300) if err != nil { t.Errorf("Cannot process the image: %#v", err) } err = assertSize(buf, 300, 300) if err != nil { t.Error(err) } Write("testdata/test_smart_crop.jpg", buf) } func TestImageTrim(t *testing.T) { if !(VipsMajorVersion >= 8 && VipsMinorVersion >= 6) { t.Skipf("Skipping this test, libvips doesn't meet version requirement %s >= 8.6", VipsVersion) } i := initImage("transparent.png") buf, err := i.Trim() if err != nil { t.Errorf("Cannot process the image: %#v", err) } err = assertSize(buf, 250, 208) if err != nil { t.Errorf("The image wasn't trimmed.") } Write("testdata/transparent_trim.png", buf) } func TestImageTrimParameters(t *testing.T) { if !(VipsMajorVersion >= 8 && VipsMinorVersion >= 6) { t.Skipf("Skipping this test, libvips doesn't meet version requirement %s >= 8.6", VipsVersion) } i := initImage("test.png") options := Options{ Trim: true, Background: Color{0.0, 0.0, 0.0}, Threshold: 10.0, } buf, err := i.Process(options) if err != nil { t.Errorf("Cannot process the image: %#v", err) } err = assertSize(buf, 400, 257) if err != nil { t.Errorf("The image wasn't trimmed.") } Write("testdata/parameter_trim.png", buf) } func TestImageLength(t *testing.T) { i := initImage("test.jpg") actual := i.Length() expected := 53653 if expected != actual { t.Errorf("Size in Bytes of the image doesn't correspond. %d != %d", expected, actual) } } func initImage(file string) *Image { buf, _ := imageBuf(file) return NewImage(buf) } func imageBuf(file string) ([]byte, error) { return Read(path.Join("testdata", file)) } func assertSize(buf []byte, width, height int) error { size, err := NewImage(buf).Size() if err != nil { return err } if size.Width != width || size.Height != height { return fmt.Errorf("Invalid image size: %dx%d", size.Width, size.Height) } return nil } golang-github-h2non-bimg-1.1.9+ds/metadata.go000066400000000000000000000230161517163235500207320ustar00rootroot00000000000000package bimg /* #cgo pkg-config: vips #include "vips/vips.h" */ 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 Height int } // ImageMetadata represents the basic metadata fields type ImageMetadata struct { Orientation int Channels int Alpha bool Profile bool Type string 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. func Size(buf []byte) (ImageSize, error) { metadata, err := Metadata(buf) if err != nil { return ImageSize{}, err } return ImageSize{ Width: int(metadata.Size.Width), Height: int(metadata.Size.Height), }, nil } // ColourspaceIsSupported checks if the image colourspace is supported by libvips. func ColourspaceIsSupported(buf []byte) (bool, error) { return vipsColourspaceIsSupportedBuffer(buf) } // ImageInterpretation returns the image interpretation type. // See: https://libvips.github.io/libvips/API/current/VipsImage.html#VipsInterpretation func ImageInterpretation(buf []byte) (Interpretation, error) { return vipsInterpretationBuffer(buf) } // Metadata returns the image metadata (size, type, alpha channel, profile, EXIF orientation...). func Metadata(buf []byte) (ImageMetadata, error) { defer C.vips_thread_shutdown() image, imageType, err := vipsRead(buf) if err != nil { return ImageMetadata{}, err } defer C.g_object_unref(C.gpointer(image)) size := ImageSize{ Width: int(image.Xsize), Height: int(image.Ysize), } orientation := vipsExifIntTag(image, Orientation) metadata := ImageMetadata{ Size: size, Channels: int(image.Bands), Orientation: orientation, Alpha: vipsHasAlpha(image), Profile: vipsHasProfile(image), Space: vipsSpace(image), Type: ImageTypeName(imageType), 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 } golang-github-h2non-bimg-1.1.9+ds/metadata_test.go000066400000000000000000000366731517163235500220060ustar00rootroot00000000000000package bimg import ( "io/ioutil" "os" "path" "testing" ) func TestSize(t *testing.T) { files := []struct { name string width int height int }{ {"test.jpg", 1680, 1050}, {"test.png", 400, 300}, {"test.webp", 550, 368}, } for _, file := range files { size, err := Size(readFile(file.name)) if err != nil { t.Fatalf("Cannot read the image: %#v", err) } if size.Width != file.width || size.Height != file.height { t.Fatalf("Unexpected image size: %dx%d", size.Width, size.Height) } } } func TestMetadata(t *testing.T) { files := []struct { name string format string orientation int alpha bool profile bool space string }{ {"test.jpg", "jpeg", 0, false, false, "srgb"}, {"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 { metadata, err := Metadata(readFile(file.name)) 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) } if metadata.Orientation != file.orientation { t.Fatalf("Unexpected image orientation: %d != %d", metadata.Orientation, file.orientation) } if metadata.Alpha != file.alpha { t.Fatalf("Unexpected image alpha: %t != %t", metadata.Alpha, file.alpha) } if metadata.Profile != file.profile { t.Fatalf("Unexpected image profile: %t != %t", metadata.Profile, file.profile) } if metadata.Space != file.space { t.Fatalf("Unexpected image profile: %t != %t", metadata.Profile, file.profile) } } } func TestImageInterpretation(t *testing.T) { files := []struct { name string interpretation Interpretation }{ {"test.jpg", InterpretationSRGB}, {"test.png", InterpretationSRGB}, {"test.webp", InterpretationSRGB}, } for _, file := range files { interpretation, err := ImageInterpretation(readFile(file.name)) if err != nil { t.Fatalf("Cannot read the image: %s -> %s", file.name, err) } if interpretation != file.interpretation { t.Fatalf("Unexpected image interpretation") } } } 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 }{ {"test.jpg"}, {"test.png"}, {"test.webp"}, } for _, file := range files { supported, err := ColourspaceIsSupported(readFile(file.name)) if err != nil { t.Fatalf("Cannot read the image: %s -> %s", file.name, err) } if supported != true { t.Fatalf("Unsupported image colourspace") } } supported, err := initImage("test.jpg").ColourspaceIsSupported() if err != nil { t.Errorf("Cannot process the image: %#v", err) } if supported != true { t.Errorf("Non-supported colourspace") } } func readFile(file string) []byte { data, _ := os.Open(path.Join("testdata", file)) buf, _ := ioutil.ReadAll(data) return buf } golang-github-h2non-bimg-1.1.9+ds/options.go000066400000000000000000000164331517163235500206520ustar00rootroot00000000000000package bimg /* #cgo pkg-config: vips #include "vips/vips.h" */ import "C" import "errors" const ( // Quality defines the default JPEG quality to be used. Quality = 75 ) // maxSize defines maximum pixels width or height supported. var maxSize = 16383 // MaxSize returns maxSize. func MaxSize() int { return maxSize } // SetMaxSize sets maxSize. func SetMaxsize(s int) error { if s <= 0 { return errors.New("Size must be higher than zero.") } maxSize = s return nil } // Gravity represents the image gravity value. type Gravity int const ( // GravityCentre represents the centre value used for image gravity orientation. GravityCentre Gravity = iota // GravityNorth represents the north value used for image gravity orientation. GravityNorth // GravityEast represents the east value used for image gravity orientation. GravityEast // GravitySouth represents the south value used for image gravity orientation. GravitySouth // GravityWest represents the west value used for image gravity orientation. GravityWest // GravitySmart enables libvips Smart Crop algorithm for image gravity orientation. GravitySmart ) // Interpolator represents the image interpolation value. type Interpolator int const ( // Bicubic interpolation value. Bicubic Interpolator = iota // Bilinear interpolation value. Bilinear // Nohalo interpolation value. Nohalo // Nearest neighbour interpolation value. Nearest ) var interpolations = map[Interpolator]string{ Bicubic: "bicubic", Bilinear: "bilinear", Nohalo: "nohalo", Nearest: "nearest", } func (i Interpolator) String() string { return interpolations[i] } // Angle represents the image rotation angle value. type Angle int const ( // D0 represents the rotation angle 0 degrees. D0 Angle = 0 // D45 represents the rotation angle 45 degrees. D45 Angle = 45 // D90 represents the rotation angle 90 degrees. D90 Angle = 90 // D135 represents the rotation angle 135 degrees. D135 Angle = 135 // D180 represents the rotation angle 180 degrees. D180 Angle = 180 // D235 represents the rotation angle 235 degrees. D235 Angle = 235 // D270 represents the rotation angle 270 degrees. D270 Angle = 270 // D315 represents the rotation angle 315 degrees. D315 Angle = 315 ) // Direction represents the image direction value. type Direction int const ( // Horizontal represents the orizontal image direction value. Horizontal Direction = C.VIPS_DIRECTION_HORIZONTAL // Vertical represents the vertical image direction value. Vertical Direction = C.VIPS_DIRECTION_VERTICAL ) // Interpretation represents the image interpretation type. // See: https://libvips.github.io/libvips/API/current/VipsImage.html#VipsInterpretation type Interpretation int const ( // InterpretationError points to the libvips interpretation error type. InterpretationError Interpretation = C.VIPS_INTERPRETATION_ERROR // InterpretationMultiband points to its libvips interpretation equivalent type. InterpretationMultiband Interpretation = C.VIPS_INTERPRETATION_MULTIBAND // InterpretationBW points to its libvips interpretation equivalent type. InterpretationBW Interpretation = C.VIPS_INTERPRETATION_B_W // InterpretationCMYK points to its libvips interpretation equivalent type. InterpretationCMYK Interpretation = C.VIPS_INTERPRETATION_CMYK // InterpretationRGB points to its libvips interpretation equivalent type. InterpretationRGB Interpretation = C.VIPS_INTERPRETATION_RGB // InterpretationSRGB points to its libvips interpretation equivalent type. InterpretationSRGB Interpretation = C.VIPS_INTERPRETATION_sRGB // InterpretationRGB16 points to its libvips interpretation equivalent type. InterpretationRGB16 Interpretation = C.VIPS_INTERPRETATION_RGB16 // InterpretationGREY16 points to its libvips interpretation equivalent type. InterpretationGREY16 Interpretation = C.VIPS_INTERPRETATION_GREY16 // InterpretationScRGB points to its libvips interpretation equivalent type. InterpretationScRGB Interpretation = C.VIPS_INTERPRETATION_scRGB // InterpretationLAB points to its libvips interpretation equivalent type. InterpretationLAB Interpretation = C.VIPS_INTERPRETATION_LAB // InterpretationXYZ points to its libvips interpretation equivalent type. InterpretationXYZ Interpretation = C.VIPS_INTERPRETATION_XYZ ) // Extend represents the image extend mode, used when the edges // of an image are extended, you can specify how you want the extension done. // See: https://libvips.github.io/libvips/API/current/libvips-conversion.html#VIPS-EXTEND-BACKGROUND:CAPS type Extend int const ( // ExtendBlack extend with black (all 0) pixels mode. ExtendBlack Extend = C.VIPS_EXTEND_BLACK // ExtendCopy copy the image edges. ExtendCopy Extend = C.VIPS_EXTEND_COPY // ExtendRepeat repeat the whole image. ExtendRepeat Extend = C.VIPS_EXTEND_REPEAT // ExtendMirror mirror the whole image. ExtendMirror Extend = C.VIPS_EXTEND_MIRROR // ExtendWhite extend with white (all bits set) pixels. ExtendWhite Extend = C.VIPS_EXTEND_WHITE // ExtendBackground with colour from the background property. ExtendBackground Extend = C.VIPS_EXTEND_BACKGROUND // ExtendLast extend with last pixel. ExtendLast Extend = C.VIPS_EXTEND_LAST ) // WatermarkFont defines the default watermark font to be used. var WatermarkFont = "sans 10" // Color represents a traditional RGB color scheme. type Color struct { R, G, B uint8 } // ColorBlack is a shortcut to black RGB color representation. var ColorBlack = Color{0, 0, 0} // Watermark represents the text-based watermark supported options. type Watermark struct { Width int DPI int Margin int Opacity float32 NoReplicate bool Text string Font string Background Color } // WatermarkImage represents the image-based watermark supported options. type WatermarkImage struct { Left int Top int Buf []byte Opacity float32 } // GaussianBlur represents the gaussian image transformation values. type GaussianBlur struct { Sigma float64 MinAmpl float64 } // Sharpen represents the image sharp transformation options. type Sharpen struct { Radius int X1 float64 Y2 float64 Y3 float64 M1 float64 M2 float64 } // Options represents the supported image transformation options. type Options struct { Height int Width int AreaHeight int AreaWidth int Top int Left int Quality int Compression int Zoom int Crop bool SmartCrop bool // Deprecated, use: bimg.Options.Gravity = bimg.GravitySmart Enlarge bool Embed bool Flip bool Flop bool Force bool NoAutoRotate bool NoProfile bool Interlace bool StripMetadata bool Trim bool Lossless bool Extend Extend Rotate Angle Background Color Gravity Gravity Watermark Watermark WatermarkImage WatermarkImage Type ImageType Interpolator Interpolator Interpretation Interpretation GaussianBlur GaussianBlur Sharpen Sharpen Threshold float64 Gamma float64 Brightness float64 Contrast float64 OutputICC string InputICC string Palette bool // Speed defines the AVIF encoders CPU effort. Valid values are: // 0-8 for AVIF encoding. // 0-9 for PNG encoding. Speed int // private fields autoRotateOnly bool } golang-github-h2non-bimg-1.1.9+ds/preinstall.sh000077500000000000000000000322471517163235500213450ustar00rootroot00000000000000#!/bin/bash # # 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" openslide_version_minimum=3.4.0 openslide_version_latest_major_minor=3.4 openslide_version_latest_patch=1 tarbal_url="https://github.com/libvips/libvips/releases/download/v$vips_version_full/vips-$vips_version_full.tar.gz" install_libvips_from_source() { # Download tarball echo "Compiling libvips v$vips_version_full from source" curl -L -o vips-$vips_version_full.tar.gz $tarbal_url tar zvxf vips-$vips_version_full.tar.gz cd vips-$vips_version_full # Compile CXXFLAGS="-D_GLIBCXX_USE_CXX11_ABI=0" ./configure --disable-debug --disable-docs --disable-static --disable-introspection --disable-dependency-tracking --enable-cxx=yes --without-python --without-orc --without-fftw $1 make make install cd .. rm -rf vips-$vips_version_latest_major_minor.$vips_version_latest_patch rm vips-$vips_version_latest_major_minor.$vips_version_latest_patch.tar.gz ldconfig echo "Installed libvips $(PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig:/usr/lib/pkgconfig pkg-config --modversion vips)" } install_libopenslide_from_source() { echo "Compiling openslide $openslide_version_latest_major_minor.$openslide_version_latest_patch from source" curl -O -L https://github.com/openslide/openslide/releases/download/v$openslide_version_latest_major_minor.$openslide_version_latest_patch/openslide-$openslide_version_latest_major_minor.$openslide_version_latest_patch.tar.gz tar xzvf openslide-$openslide_version_latest_major_minor.$openslide_version_latest_patch.tar.gz cd openslide-$openslide_version_latest_major_minor.$openslide_version_latest_patch PKG_CONFIG_PATH=$pkg_config_path ./configure $1 make make install cd .. rm -rf openslide-$openslide_version_latest_major_minor.$openslide_version_latest_patch rm openslide-$openslide_version_latest_major_minor.$openslide_version_latest_patch.tar.gz ldconfig echo "Installed libopenslide $openslide_version_latest_major_minor.$openslide_version_latest_patch" } sorry() { echo "Sorry, I don't yet know how to install lib$1 on $2" exit 1 } pkg_config_path="$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig:/usr/lib/pkgconfig" check_if_library_exists() { PKG_CONFIG_PATH=$pkg_config_path pkg-config --exists $1 if [ $? -eq 0 ]; then version_found=$(PKG_CONFIG_PATH=$pkg_config_path pkg-config --modversion $1) PKG_CONFIG_PATH=$pkg_config_path pkg-config --atleast-version=$2 $1 if [ $? -eq 0 ]; then # Found suitable version of libvips echo "Found lib$1 $version_found" return 1 fi echo "Found lib$1 $version_found but require $2" else echo "Could not find lib$1 using a PKG_CONFIG_PATH of '$pkg_config_path'" fi return 0 } enable_openslide=0 # Is libvips already installed, and is it at least the minimum required version? if [ $# -eq 1 ]; then if [ "$1" = "--with-openslide" ]; then echo "Installing vips with openslide support" enable_openslide=1 else echo "Sorry, $1 is not supported. Did you mean --with-openslide?" exit 1 fi fi if ! type pkg-config >/dev/null; then sorry "vips" "a system without pkg-config" fi openslide_exists=0 if [ $enable_openslide -eq 1 ]; then check_if_library_exists "openslide" "$openslide_version_minimum" openslide_exists=$? fi check_if_library_exists "vips" "$vips_version_minimum" vips_exists=$? if [ $vips_exists -eq 1 ] && [ $enable_openslide -eq 1 ]; then if [ $openslide_exists -eq 1 ]; then # Check if vips compiled with openslide support vips_with_openslide=`vips list classes | grep -i opensli` if [ -z $vips_with_openslide ]; then echo "Vips compiled without openslide support." else exit 0 fi fi elif [ $vips_exists -eq 1 ] && [ $enable_openslide -eq 0 ]; then exit 0 fi # Verify root/sudo access if [ "$(id -u)" -ne "0" ]; then echo "Sorry, I need root/sudo access to continue" exit 1 fi # Deprecation warning if [ "$(arch)" == "x86_64" ]; then echo "This script is no longer required on most 64-bit Linux systems when using sharp v0.12.0+" fi # OS-specific installations of libopenslide follows # Either openslide does not exist, or vips is installed without openslide support if [ $enable_openslide -eq 1 ] && [ -z $vips_with_openslide ] && [ $openslide_exists -eq 0 ]; then if [ -f /etc/debian_version ]; then # Debian Linux DISTRO=$(lsb_release -c -s) echo "Detected Debian Linux '$DISTRO'" case "$DISTRO" in jessie|vivid|wily|xenial|stretch|loki) # Debian 9, Debian 8, Ubuntu 15 echo "Installing libopenslide via apt-get" apt-get install -y libopenslide-dev ;; trusty|utopic|qiana|rebecca|rafaela|freya|rosa|sarah|serena) # Ubuntu 14, Mint 17+ echo "Installing libopenslide dependencies via apt-get" apt-get install -y automake build-essential curl zlib1g-dev libopenjpeg-dev libpng12-dev libjpeg-dev libtiff5-dev libgdk-pixbuf2.0-dev libxml2-dev libsqlite3-dev libcairo2-dev libglib2.0-dev sqlite3 libsqlite3-dev install_libopenslide_from_source ;; precise|wheezy|maya) # Debian 7, Ubuntu 12.04, Mint 13 echo "Installing libopenslide dependencies via apt-get" apt-get install -y automake build-essential curl zlib1g-dev libopenjpeg-dev libpng12-dev libjpeg-dev libtiff5-dev libgdk-pixbuf2.0-dev libxml2-dev libsqlite3-dev libcairo2-dev libglib2.0-dev sqlite3 libsqlite3-dev install_libopenslide_from_source ;; *) # Unsupported Debian-based OS sorry "openslide" "Debian-based $DISTRO" ;; esac elif [ -f /etc/redhat-release ]; then # Red Hat Linux RELEASE=$(cat /etc/redhat-release) echo "Detected Red Hat Linux '$RELEASE'" case $RELEASE in "Red Hat Enterprise Linux release 7."*|"CentOS Linux release 7."*|"Scientific Linux release 7."*) # RHEL/CentOS 7 echo "Installing libopenslide dependencies via yum" yum groupinstall -y "Development Tools" yum install -y tar curl libpng-devel libjpeg-devel libxml2-devel zlib-devel openjpeg-devel libtiff-devel gdk-pixbuf2-devel sqlite-devel cairo-devel glib2-devel expat-devel install_libopenslide_from_source "--prefix=/usr" ;; "Red Hat Enterprise Linux release 6."*|"CentOS release 6."*|"Scientific Linux release 6."*) # RHEL/CentOS 6 echo "Installing libopenslide dependencies via yum" yum groupinstall -y "Development Tools" yum install -y tar curl libpng-devel libjpeg-devel libxml2-devel zlib-devel openjpeg-devel libtiff-devel gdk-pixbuf2-devel sqlite-devel cairo-devel glib2-devel install_libopenslide_from_source "--prefix=/usr" ;; "Fedora release 21 "*|"Fedora release 22 "*) # Fedora 21, 22 echo "Installing libopenslide via yum" yum install -y openslide-devel ;; *) # Unsupported RHEL-based OS sorry "openslide" "$RELEASE" ;; esac elif [ -f /etc/os-release ]; then RELEASE=$(cat /etc/os-release | grep VERSION) echo "Detected OpenSuse Linux '$RELEASE'" case $RELEASE in *"13.2"*) echo "Installing libopenslide via zypper" zypper --gpg-auto-import-keys install -y libopenslide-devel ;; esac elif [ -f /etc/SuSE-brand ]; then RELEASE=$(cat /etc/SuSE-brand | grep VERSION) echo "Detected OpenSuse Linux '$RELEASE'" case $RELEASE in *"13.1") echo "Installing libopenslide dependencies via zypper" zypper --gpg-auto-import-keys install -y --type pattern devel_basis zypper --gpg-auto-import-keys install -y tar curl libpng16-devel libjpeg-turbo libjpeg8-devel libxml2-devel zlib-devel openjpeg-devel libtiff-devel libgdk_pixbuf-2_0-0 sqlite3-devel cairo-devel glib2-devel install_libopenslide_from_source ;; esac else # Unsupported OS sorry "openslide" "$(uname -a)" fi fi # OS-specific installations of libvips follows if [ -f /etc/debian_version ]; then # Debian Linux DISTRO=$(lsb_release -c -s) echo "Detected Debian Linux '$DISTRO'" case "$DISTRO" in jessie|trusty|utopic|vivid|wily|xenial|qiana|rebecca|rafaela|freya|rosa|sarah|serena|loki) # Debian 8, Ubuntu 14.04+, Mint 17+ echo "Installing libvips dependencies via apt-get" apt-get install -y automake build-essential gobject-introspection gtk-doc-tools libglib2.0-dev libjpeg-dev libpng12-dev libwebp-dev libtiff5-dev libexif-dev libgsf-1-dev liblcms2-dev libxml2-dev swig libmagickcore-dev curl install_libvips_from_source ;; stretch) # Debian 9 echo "Installing libvips dependencies via apt-get" apt-get install -y automake build-essential gobject-introspection gtk-doc-tools libglib2.0-dev libjpeg-dev libpng-dev libwebp-dev libtiff5-dev libexif-dev libgsf-1-dev liblcms2-dev libxml2-dev swig libmagickcore-dev curl install_libvips_from_source ;; precise|wheezy|maya) # Debian 7, Ubuntu 12.04, Mint 13 echo "Installing libvips dependencies via apt-get" add-apt-repository -y ppa:lyrasis/precise-backports apt-get update apt-get install -y automake build-essential gobject-introspection gtk-doc-tools libglib2.0-dev libjpeg-dev libpng12-dev libwebp-dev libtiff4-dev libexif-dev libgsf-1-dev liblcms2-dev libxml2-dev swig libmagickcore-dev curl install_libvips_from_source ;; *) # Unsupported Debian-based OS sorry "vips" "Debian-based $DISTRO" ;; esac elif [ -f /etc/redhat-release ]; then # Red Hat Linux RELEASE=$(cat /etc/redhat-release) echo "Detected Red Hat Linux '$RELEASE'" case $RELEASE in "Red Hat Enterprise Linux release 7."*|"CentOS Linux release 7."*|"Scientific Linux release 7."*) # RHEL/CentOS 7 echo "Installing libvips dependencies via yum" yum groupinstall -y "Development Tools" yum install -y tar curl gtk-doc libxml2-devel libjpeg-turbo-devel libpng-devel libtiff-devel libexif-devel libgsf-devel lcms2-devel ImageMagick-devel gobject-introspection-devel libwebp-devel install_libvips_from_source "--prefix=/usr" ;; "Red Hat Enterprise Linux release 6."*|"CentOS release 6."*|"Scientific Linux release 6."*) # RHEL/CentOS 6 echo "Installing libvips dependencies via yum" yum groupinstall -y "Development Tools" yum install -y tar curl gtk-doc libxml2-devel libjpeg-turbo-devel libpng-devel libtiff-devel libexif-devel libgsf-devel lcms-devel ImageMagick-devel yum install -y http://li.nux.ro/download/nux/dextop/el6/x86_64/nux-dextop-release-0-2.el6.nux.noarch.rpm yum install -y --enablerepo=nux-dextop gobject-introspection-devel yum install -y http://rpms.famillecollet.com/enterprise/remi-release-6.rpm yum install -y --enablerepo=remi libwebp-devel install_libvips_from_source "--prefix=/usr" ;; "Fedora"*) # Fedora 21, 22, 23 echo "Installing libvips dependencies via yum" yum groupinstall -y "Development Tools" yum install -y gcc-c++ gtk-doc libxml2-devel libjpeg-turbo-devel libpng-devel libtiff-devel libexif-devel lcms-devel ImageMagick-devel gobject-introspection-devel libwebp-devel curl install_libvips_from_source "--prefix=/usr" ;; *) # Unsupported RHEL-based OS sorry "vips" "$RELEASE" ;; esac elif [ -f /etc/system-release ]; then # Probably Amazon Linux RELEASE=$(cat /etc/system-release) case $RELEASE in "Amazon Linux AMI release 2015.03"|"Amazon Linux AMI release 2015.09") # Amazon Linux echo "Detected '$RELEASE'" echo "Installing libvips dependencies via yum" yum groupinstall -y "Development Tools" yum install -y gtk-doc libxml2-devel libjpeg-turbo-devel libpng-devel libtiff-devel libexif-devel libgsf-devel lcms2-devel ImageMagick-devel gobject-introspection-devel libwebp-devel curl install_libvips_from_source "--prefix=/usr" ;; *) # Unsupported Amazon Linux version sorry "vips" "$RELEASE" ;; esac elif [ -f /etc/os-release ]; then RELEASE=$(cat /etc/os-release | grep VERSION) echo "Detected OpenSuse Linux '$RELEASE'" case $RELEASE in *"13.2"*) echo "Installing libvips dependencies via zypper" zypper --gpg-auto-import-keys install -y --type pattern devel_basis zypper --gpg-auto-import-keys install -y tar curl gtk-doc libxml2-devel libjpeg-turbo libjpeg8-devel libpng16-devel libtiff-devel libexif-devel liblcms2-devel ImageMagick-devel gobject-introspection-devel libwebp-devel install_libvips_from_source ;; esac elif [ -f /etc/SuSE-brand ]; then RELEASE=$(cat /etc/SuSE-brand | grep VERSION) echo "Detected OpenSuse Linux '$RELEASE'" case $RELEASE in *"13.1") echo "Installing libvips dependencies via zypper" zypper --gpg-auto-import-keys install -y --type pattern devel_basis zypper --gpg-auto-import-keys install -y tar curl gtk-doc libxml2-devel libjpeg-turbo libjpeg8-devel libpng16-devel libtiff-devel libexif-devel liblcms2-devel ImageMagick-devel gobject-introspection-devel libwebp-devel install_libvips_from_source ;; esac else # Unsupported OS sorry "vips" "$(uname -a)" fi golang-github-h2non-bimg-1.1.9+ds/resize.go000066400000000000000000000005521517163235500204530ustar00rootroot00000000000000// +build go1.7 package bimg import ( "runtime" ) // Resize is used to transform a given image as byte buffer // with the passed options. func Resize(buf []byte, o Options) ([]byte, error) { // Required in order to prevent premature garbage collection. See: // https://github.com/h2non/bimg/pull/162 defer runtime.KeepAlive(buf) return resizer(buf, o) } golang-github-h2non-bimg-1.1.9+ds/resize_legacy.go000066400000000000000000000004001517163235500217670ustar00rootroot00000000000000// +build !go1.7 package bimg // Resize is used to transform a given image as byte buffer // with the passed options. // Used as proxy to resizer() only in Go <= 1.6 versions func Resize(buf []byte, o Options) ([]byte, error) { return resizer(buf, o) } golang-github-h2non-bimg-1.1.9+ds/resizer.go000066400000000000000000000352001517163235500206330ustar00rootroot00000000000000package bimg /* #cgo pkg-config: vips #include "vips/vips.h" */ import "C" import ( "errors" "fmt" "math" ) var ( // ErrExtractAreaParamsRequired defines a generic extract area error ErrExtractAreaParamsRequired = errors.New("extract area width/height params are required") ) // resizer is used to transform a given image as byte buffer // with the passed options. func resizer(buf []byte, o Options) ([]byte, error) { defer C.vips_thread_shutdown() image, imageType, err := loadImage(buf) if err != nil { return nil, err } // Clone and define default options o = applyDefaults(o, imageType) // 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 { return nil, err } // If JPEG or HEIF image, retrieve the buffer if rotated && (imageType == JPEG || imageType == HEIF || imageType == AVIF) && !o.NoAutoRotate { buf, err = getImageBuffer(image) if err != nil { return nil, err } } inWidth := int(image.Xsize) inHeight := int(image.Ysize) // Infer the required operation based on the in/out image sizes for a coherent transformation normalizeOperation(&o, inWidth, inHeight) // image calculations factor := imageCalculations(&o, inWidth, inHeight) shrink := calculateShrink(factor, o.Interpolator) residual := calculateResidual(factor, shrink) // Do not enlarge the output if the input width or height // are already less than the required dimensions if !o.Enlarge && !o.Force { if inWidth < o.Width && inHeight < o.Height { factor = 1.0 shrink = 1 residual = 0 o.Width = inWidth o.Height = inHeight } } // Try to use libjpeg/libwebp shrink-on-load supportsShrinkOnLoad := imageType == WEBP && VipsMajorVersion >= 8 && VipsMinorVersion >= 3 supportsShrinkOnLoad = supportsShrinkOnLoad || imageType == JPEG if supportsShrinkOnLoad && shrink >= 2 { tmpImage, factor, err := shrinkOnLoad(buf, image, imageType, factor, shrink) if err != nil { return nil, err } image = tmpImage factor = math.Max(factor, 1.0) shrink = int(math.Floor(factor)) residual = float64(shrink) / factor } // Zoom image, if necessary image, err = zoomImage(image, o.Zoom) if err != nil { return nil, err } // Transform image, if necessary if shouldTransformImage(o, inWidth, inHeight) { image, err = transformImage(image, o, shrink, residual) if err != nil { return nil, err } } // Apply effects, if necessary if shouldApplyEffects(o) { image, err = applyEffects(image, o) if err != nil { return nil, err } } // Add watermark, if necessary image, err = watermarkImageWithText(image, o.Watermark) if err != nil { return nil, err } // Add watermark, if necessary image, err = watermarkImageWithAnotherImage(image, o.WatermarkImage) if err != nil { return nil, err } // Flatten image on a background, if necessary image, err = imageFlatten(image, imageType, o) if err != nil { return nil, err } // Apply Gamma filter, if necessary image, err = applyGamma(image, o) if err != nil { return nil, err } // Apply brightness, if necessary image, err = applyBrightness(image, o) if err != nil { return nil, err } // Apply contrast, if necessary image, err = applyContrast(image, o) if err != nil { return nil, err } return saveImage(image, o) } func loadImage(buf []byte) (*C.VipsImage, ImageType, error) { if len(buf) == 0 { return nil, JPEG, errors.New("Image buffer is empty") } image, imageType, err := vipsRead(buf) if err != nil { return nil, JPEG, err } return image, imageType, nil } func applyDefaults(o Options, imageType ImageType) Options { if o.Quality == 0 { o.Quality = Quality } if o.Compression == 0 { o.Compression = 6 } if o.Type == 0 { o.Type = imageType } if o.Interpretation == 0 { o.Interpretation = InterpretationSRGB } if o.Palette { // Default value of effort in libvips is 7. o.Speed = 3 } return o } func saveImage(image *C.VipsImage, o Options) ([]byte, error) { saveOptions := vipsSaveOptions{ Quality: o.Quality, Type: o.Type, Compression: o.Compression, 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) } func normalizeOperation(o *Options, inWidth, inHeight int) { if !o.Force && !o.Crop && !o.Embed && !o.Enlarge && o.Rotate == 0 && (o.Width > 0 || o.Height > 0) { o.Force = true } } func shouldTransformImage(o Options, inWidth, inHeight int) bool { return o.Force || (o.Width > 0 && o.Width != inWidth) || (o.Height > 0 && o.Height != inHeight) || o.AreaWidth > 0 || o.AreaHeight > 0 || o.Trim } func shouldApplyEffects(o Options) bool { return o.GaussianBlur.Sigma > 0 || o.GaussianBlur.MinAmpl > 0 || o.Sharpen.Radius > 0 && o.Sharpen.Y2 > 0 || o.Sharpen.Y3 > 0 } func transformImage(image *C.VipsImage, o Options, shrink int, residual float64) (*C.VipsImage, error) { var err error // Use vips_shrink with the integral reduction if shrink > 1 { image, residual, err = shrinkImage(image, o, residual, shrink) if err != nil { return nil, err } } residualx, residualy := residual, residual if o.Force { residualx = float64(o.Width) / float64(image.Xsize) residualy = float64(o.Height) / float64(image.Ysize) } if o.Force || residual != 0 { if residualx < 1 && residualy < 1 { image, err = vipsReduce(image, 1/residualx, 1/residualy) } else { image, err = vipsAffine(image, residualx, residualy, o.Interpolator, o.Extend) } if err != nil { return nil, err } } if o.Force { o.Crop = false o.Embed = false } image, err = extractOrEmbedImage(image, o) if err != nil { return nil, err } return image, nil } func applyEffects(image *C.VipsImage, o Options) (*C.VipsImage, error) { var err error if o.GaussianBlur.Sigma > 0 || o.GaussianBlur.MinAmpl > 0 { image, err = vipsGaussianBlur(image, o.GaussianBlur) if err != nil { return nil, err } } if o.Sharpen.Radius > 0 && o.Sharpen.Y2 > 0 || o.Sharpen.Y3 > 0 { image, err = vipsSharpen(image, o.Sharpen) if err != nil { return nil, err } } return image, nil } func extractOrEmbedImage(image *C.VipsImage, o Options) (*C.VipsImage, error) { var err error inWidth := int(image.Xsize) inHeight := int(image.Ysize) switch { case o.Gravity == GravitySmart, o.SmartCrop: // 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) left, top = int(math.Max(float64(left), 0)), int(math.Max(float64(top), 0)) image, err = vipsExtract(image, left, top, width, height) break case o.Embed: left, top := (o.Width-inWidth)/2, (o.Height-inHeight)/2 image, err = vipsEmbed(image, left, top, o.Width, o.Height, o.Extend, o.Background) break case o.Trim: left, top, width, height, err := vipsTrim(image, o.Background, o.Threshold) if err == nil { image, err = vipsExtract(image, left, top, width, height) } break case o.Top != 0 || o.Left != 0 || o.AreaWidth != 0 || o.AreaHeight != 0: if o.AreaWidth == 0 { o.AreaWidth = o.Width } if o.AreaHeight == 0 { o.AreaHeight = o.Height } if o.AreaWidth == 0 || o.AreaHeight == 0 { return nil, errors.New("Extract area width/height params are required") } image, err = vipsExtract(image, o.Left, o.Top, o.AreaWidth, o.AreaHeight) break } return image, err } func rotateAndFlipImage(image *C.VipsImage, o Options) (*C.VipsImage, bool, error) { var err error var rotated bool if o.NoAutoRotate == false { rotation, flip := calculateRotationAndFlip(image, o.Rotate) if flip { o.Flip = flip } if rotation > 0 && o.Rotate == 0 { o.Rotate = rotation } } if o.Rotate > 0 { rotated = true image, err = vipsRotate(image, getAngle(o.Rotate)) } if o.Flip { rotated = true image, err = vipsFlip(image, Horizontal) } if o.Flop { rotated = true image, err = vipsFlip(image, Vertical) } return image, rotated, err } func watermarkImageWithText(image *C.VipsImage, w Watermark) (*C.VipsImage, error) { if w.Text == "" { return image, nil } // Defaults if w.Font == "" { w.Font = WatermarkFont } if w.Width == 0 { w.Width = int(math.Floor(float64(image.Xsize / 6))) } if w.DPI == 0 { w.DPI = 150 } if w.Margin == 0 { w.Margin = w.Width } if w.Opacity == 0 { w.Opacity = 0.25 } else if w.Opacity > 1 { w.Opacity = 1 } image, err := vipsWatermark(image, w) if err != nil { return nil, err } return image, nil } func watermarkImageWithAnotherImage(image *C.VipsImage, w WatermarkImage) (*C.VipsImage, error) { if len(w.Buf) == 0 { return image, nil } if w.Opacity == 0.0 { w.Opacity = 1.0 } image, err := vipsDrawWatermark(image, w) if err != nil { return nil, err } return image, nil } func imageFlatten(image *C.VipsImage, imageType ImageType, o Options) (*C.VipsImage, error) { if o.Background == ColorBlack { return image, nil } return vipsFlattenBackground(image, o.Background) } func applyGamma(image *C.VipsImage, o Options) (*C.VipsImage, error) { var err error if o.Gamma > 0 { image, err = vipsGamma(image, o.Gamma) if err != nil { return nil, err } } return image, nil } func zoomImage(image *C.VipsImage, zoom int) (*C.VipsImage, error) { if zoom == 0 { return image, nil } return vipsZoom(image, zoom+1) } func shrinkImage(image *C.VipsImage, o Options, residual float64, shrink int) (*C.VipsImage, float64, error) { // Use vips_shrink with the integral reduction image, err := vipsShrink(image, shrink) if err != nil { return nil, 0, err } // Recalculate residual float based on dimensions of required vs shrunk images residualx := float64(o.Width) / float64(image.Xsize) residualy := float64(o.Height) / float64(image.Ysize) if o.Crop { residual = math.Max(residualx, residualy) } else { residual = math.Min(residualx, residualy) } return image, residual, nil } func shrinkOnLoad(buf []byte, input *C.VipsImage, imageType ImageType, factor float64, shrink int) (*C.VipsImage, float64, error) { var ( image *C.VipsImage err error ) if shrink < 2 { return nil, 0, fmt.Errorf("only available for shrink >=2") } shrinkOnLoad := 1 // Recalculate integral shrink and double residual switch { case shrink >= 8: factor = factor / 8 shrinkOnLoad = 8 case shrink >= 4: factor = factor / 4 shrinkOnLoad = 4 case shrink >= 2: factor = factor / 2 shrinkOnLoad = 2 } // Reload input using shrink-on-load switch imageType { case JPEG: image, err = vipsShrinkJpeg(buf, input, shrinkOnLoad) case WEBP: image, err = vipsShrinkWebp(buf, input, shrinkOnLoad) default: return nil, 0, fmt.Errorf("%v doesn't support shrink on load", ImageTypeName(imageType)) } return image, factor, err } func imageCalculations(o *Options, inWidth, inHeight int) float64 { factor := 1.0 xfactor := float64(inWidth) / float64(o.Width) yfactor := float64(inHeight) / float64(o.Height) switch { // Fixed width and height case o.Width > 0 && o.Height > 0: if o.Crop { factor = math.Min(xfactor, yfactor) } else { factor = math.Max(xfactor, yfactor) } // Fixed width, auto height case o.Width > 0: if o.Crop { o.Height = inHeight } else { factor = xfactor o.Height = roundFloat(float64(inHeight) / factor) } // Fixed height, auto width case o.Height > 0: if o.Crop { o.Width = inWidth } else { factor = yfactor o.Width = roundFloat(float64(inWidth) / factor) } // Identity transform default: o.Width = inWidth o.Height = inHeight break } return factor } func roundFloat(f float64) int { if f < 0 { return int(math.Ceil(f - 0.5)) } return int(math.Floor(f + 0.5)) } func calculateCrop(inWidth, inHeight, outWidth, outHeight int, gravity Gravity) (int, int) { left, top := 0, 0 switch gravity { case GravityNorth: left = (inWidth - outWidth + 1) / 2 case GravityEast: left = inWidth - outWidth top = (inHeight - outHeight + 1) / 2 case GravitySouth: left = (inWidth - outWidth + 1) / 2 top = inHeight - outHeight case GravityWest: top = (inHeight - outHeight + 1) / 2 default: left = (inWidth - outWidth + 1) / 2 top = (inHeight - outHeight + 1) / 2 } return left, top } func calculateRotationAndFlip(image *C.VipsImage, angle Angle) (Angle, bool) { rotate := D0 flip := false if angle > 0 { return rotate, flip } switch vipsExifOrientation(image) { case 6: rotate = D90 break case 3: rotate = D180 break case 8: rotate = D270 break case 2: flip = true break // flip 1 case 7: flip = true rotate = D270 break // flip 6 case 4: flip = true rotate = D180 break // flip 3 case 5: flip = true rotate = D90 break // flip 8 } return rotate, flip } func calculateShrink(factor float64, i Interpolator) int { var shrink float64 // Calculate integral box shrink windowSize := vipsWindowSize(i.String()) if factor >= 2 && windowSize > 3 { // Shrink less, affine more with interpolators that use at least 4x4 pixel window, e.g. bicubic shrink = float64(math.Floor(factor * 3.0 / windowSize)) } else { shrink = math.Floor(factor) } return int(math.Max(shrink, 1)) } func calculateResidual(factor float64, shrink int) float64 { return float64(shrink) / factor } func getAngle(angle Angle) Angle { divisor := angle % 90 if divisor != 0 { angle = angle - divisor } return Angle(math.Min(float64(angle), 270)) } func applyBrightness(image *C.VipsImage, o Options) (*C.VipsImage, error) { var err error if o.Brightness != 0 { image, err = vipsBrightness(image, o.Brightness) if err != nil { return nil, err } } return image, nil } func applyContrast(image *C.VipsImage, o Options) (*C.VipsImage, error) { var err error if o.Contrast > 0 { image, err = vipsContrast(image, o.Contrast) if err != nil { return nil, err } } return image, nil } golang-github-h2non-bimg-1.1.9+ds/resizer_test.go000066400000000000000000000473721517163235500217070ustar00rootroot00000000000000package bimg import ( "bytes" "crypto/md5" "fmt" "image" "image/jpeg" "io/ioutil" "os" "path" "testing" ) func TestResize(t *testing.T) { options := Options{Width: 800, Height: 600} buf, _ := Read("testdata/test.jpg") newImg, err := Resize(buf, options) if err != nil { t.Errorf("Resize(imgData, %#v) error: %#v", options, err) } if DetermineImageType(newImg) != JPEG { t.Fatal("Image is not jpeg") } size, _ := Size(newImg) if size.Height != options.Height || size.Width != options.Width { t.Fatalf("Invalid image size: %dx%d", size.Width, size.Height) } Write("testdata/test_out.jpg", newImg) } func TestResizeVerticalImage(t *testing.T) { tests := []Options{ {Width: 800, Height: 600}, {Width: 1000, Height: 1000}, {Width: 1000, Height: 1500}, {Width: 1000}, {Height: 1500}, {Width: 200, Height: 120}, {Width: 2000, Height: 2000}, {Width: 500, Height: 1000}, {Width: 500}, {Height: 500}, {Crop: true, Width: 500, Height: 1000}, {Crop: true, Enlarge: true, Width: 2000, Height: 1400}, {Enlarge: true, Force: true, Width: 2000, Height: 2000}, {Force: true, Width: 2000, Height: 2000}, } bufJpeg, err := Read("testdata/vertical.jpg") if err != nil { t.Fatal(err) } bufWebp, err := Read("testdata/vertical.webp") if err != nil { t.Fatal(err) } images := []struct { format ImageType buf []byte }{ {JPEG, bufJpeg}, {WEBP, bufWebp}, } for _, source := range images { for _, options := range tests { image, err := Resize(source.buf, options) if err != nil { t.Fatalf("Resize(imgData, %#v) error: %#v", options, err) } format := DetermineImageType(image) if format != source.format { t.Fatalf("Image format is invalid. Expected: %#v got %v", ImageTypeName(source.format), ImageTypeName(format)) } size, _ := Size(image) if options.Height > 0 && size.Height != options.Height { t.Fatalf("Invalid height: %d", size.Height) } if options.Width > 0 && size.Width != options.Width { t.Fatalf("Invalid width: %d", size.Width) } Write( fmt.Sprintf( "testdata/test_vertical_%dx%d_out.%s", options.Width, options.Height, ImageTypeName(source.format)), image) } } } func TestResizeCustomSizes(t *testing.T) { tests := []Options{ {Width: 800, Height: 600}, {Width: 1000, Height: 1000}, {Width: 100, Height: 50}, {Width: 2000, Height: 2000}, {Width: 500, Height: 1000}, {Width: 500}, {Height: 500}, {Crop: true, Width: 500, Height: 1000}, {Crop: true, Enlarge: true, Width: 2000, Height: 1400}, {Enlarge: true, Force: true, Width: 2000, Height: 2000}, {Force: true, Width: 2000, Height: 2000}, } bufJpeg, err := Read("testdata/test.jpg") if err != nil { t.Fatal(err) } bufWebp, err := Read("testdata/test.webp") if err != nil { t.Fatal(err) } images := []struct { format ImageType buf []byte }{ {JPEG, bufJpeg}, {WEBP, bufWebp}, } for _, source := range images { for _, options := range tests { image, err := Resize(source.buf, options) if err != nil { t.Errorf("Resize(imgData, %#v) error: %#v", options, err) } if DetermineImageType(image) != source.format { t.Fatalf("Image format is invalid. Expected: %#v", source.format) } size, _ := Size(image) invalidHeight := options.Height > 0 && size.Height != options.Height if !options.Crop && invalidHeight { t.Fatalf("Invalid height: %d, expected %d", size.Height, options.Height) } invalidWidth := options.Width > 0 && size.Width != options.Width if !options.Crop && invalidWidth { t.Fatalf("Invalid width: %d, expected %d", size.Width, options.Width) } if options.Crop && invalidHeight && invalidWidth { t.Fatalf("Invalid width or height: %dx%d, expected %dx%d (crop)", size.Width, size.Height, options.Width, options.Height) } } } } func TestResizePrecision(t *testing.T) { // see https://github.com/h2non/bimg/issues/99 img := image.NewGray16(image.Rect(0, 0, 1920, 1080)) input := &bytes.Buffer{} jpeg.Encode(input, img, nil) opts := Options{Width: 300} newImg, err := Resize(input.Bytes(), opts) if err != nil { t.Fatalf("Resize(imgData, %#v) error: %#v", opts, err) } size, _ := Size(newImg) if size.Width != opts.Width { t.Fatalf("Invalid width: %d", size.Width) } } func TestRotate(t *testing.T) { options := Options{Width: 800, Height: 600, Rotate: 270, Crop: true} buf, _ := Read("testdata/test.jpg") newImg, err := Resize(buf, options) if err != nil { t.Errorf("Resize(imgData, %#v) error: %#v", options, err) } if DetermineImageType(newImg) != JPEG { t.Error("Image is not jpeg") } size, _ := Size(newImg) if size.Width != options.Width || size.Height != options.Height { t.Errorf("Invalid image size: %dx%d", size.Width, size.Height) } Write("testdata/test_rotate_out.jpg", newImg) } func TestInvalidRotateDegrees(t *testing.T) { options := Options{Width: 800, Height: 600, Rotate: 111, Crop: true} buf, _ := Read("testdata/test.jpg") newImg, err := Resize(buf, options) if err != nil { t.Errorf("Resize(imgData, %#v) error: %#v", options, err) } if DetermineImageType(newImg) != JPEG { t.Errorf("Image is not jpeg") } size, _ := Size(newImg) if size.Width != options.Width || size.Height != options.Height { t.Errorf("Invalid image size: %dx%d", size.Width, size.Height) } Write("testdata/test_rotate_invalid_out.jpg", newImg) } func TestCorruptedImage(t *testing.T) { options := Options{Width: 800, Height: 600} buf, _ := Read("testdata/corrupt.jpg") newImg, err := Resize(buf, options) if err != nil { t.Errorf("Resize(imgData, %#v) error: %#v", options, err) } if DetermineImageType(newImg) != JPEG { t.Fatal("Image is not jpeg") } size, _ := Size(newImg) if size.Height != options.Height || size.Width != options.Width { t.Fatalf("Invalid image size: %dx%d", size.Width, size.Height) } Write("testdata/test_corrupt_out.jpg", newImg) } func TestNoColorProfile(t *testing.T) { options := Options{Width: 800, Height: 600, NoProfile: true} buf, _ := Read("testdata/test.jpg") newImg, err := Resize(buf, options) if err != nil { t.Errorf("Resize(imgData, %#v) error: %#v", options, err) } metadata, err := Metadata(newImg) if metadata.Profile == true { t.Fatal("Invalid profile data") } size, _ := Size(newImg) if size.Height != options.Height || size.Width != options.Width { t.Fatalf("Invalid image size: %dx%d", size.Width, size.Height) } } func TestEmbedExtendColor(t *testing.T) { options := Options{Width: 400, Height: 600, Crop: false, Embed: true, Extend: ExtendWhite, Background: Color{255, 20, 10}} buf, _ := Read("testdata/test_issue.jpg") newImg, err := Resize(buf, options) if err != nil { t.Errorf("Resize(imgData, %#v) error: %#v", options, err) } size, _ := Size(newImg) if size.Height != options.Height || size.Width != options.Width { t.Fatalf("Invalid image size: %dx%d", size.Width, size.Height) } Write("testdata/test_extend_white_out.jpg", newImg) } func TestEmbedExtendWithCustomColor(t *testing.T) { options := Options{Width: 400, Height: 600, Crop: false, Embed: true, Extend: 5, Background: Color{255, 20, 10}} buf, _ := Read("testdata/test_issue.jpg") newImg, err := Resize(buf, options) if err != nil { t.Errorf("Resize(imgData, %#v) error: %#v", options, err) } size, _ := Size(newImg) if size.Height != options.Height || size.Width != options.Width { t.Fatalf("Invalid image size: %dx%d", size.Width, size.Height) } Write("testdata/test_extend_background_out.jpg", newImg) } func TestGaussianBlur(t *testing.T) { options := Options{Width: 800, Height: 600, GaussianBlur: GaussianBlur{Sigma: 5}} buf, _ := Read("testdata/test.jpg") newImg, err := Resize(buf, options) if err != nil { t.Errorf("Resize(imgData, %#v) error: %#v", options, err) } size, _ := Size(newImg) if size.Height != options.Height || size.Width != options.Width { t.Fatalf("Invalid image size: %dx%d", size.Width, size.Height) } Write("testdata/test_gaussian_out.jpg", newImg) } func TestSharpen(t *testing.T) { options := Options{Width: 800, Height: 600, Sharpen: Sharpen{Radius: 1, X1: 1.5, Y2: 20, Y3: 50, M1: 1, M2: 2}} buf, _ := Read("testdata/test.jpg") newImg, err := Resize(buf, options) if err != nil { t.Errorf("Resize(imgData, %#v) error: %#v", options, err) } size, _ := Size(newImg) if size.Height != options.Height || size.Width != options.Width { t.Fatalf("Invalid image size: %dx%d", size.Width, size.Height) } Write("testdata/test_sharpen_out.jpg", newImg) } func TestExtractWithDefaultAxis(t *testing.T) { options := Options{AreaWidth: 200, AreaHeight: 200} buf, _ := Read("testdata/test.jpg") newImg, err := Resize(buf, options) if err != nil { t.Errorf("Resize(imgData, %#v) error: %#v", options, err) } size, _ := Size(newImg) if size.Height != options.AreaHeight || size.Width != options.AreaWidth { t.Fatalf("Invalid image size: %dx%d", size.Width, size.Height) } Write("testdata/test_extract_defaults_out.jpg", newImg) } func TestExtractCustomAxis(t *testing.T) { options := Options{Top: 100, Left: 100, AreaWidth: 200, AreaHeight: 200} buf, _ := Read("testdata/test.jpg") newImg, err := Resize(buf, options) if err != nil { t.Errorf("Resize(imgData, %#v) error: %#v", options, err) } size, _ := Size(newImg) if size.Height != options.AreaHeight || size.Width != options.AreaWidth { t.Fatalf("Invalid image size: %dx%d", size.Width, size.Height) } Write("testdata/test_extract_custom_axis_out.jpg", newImg) } func TestExtractOrEmbedImage(t *testing.T) { buf, _ := Read("testdata/test.jpg") input, _, err := loadImage(buf) if err != nil { t.Fatalf("Unable to load image %s", err) } o := Options{ Top: 10, Left: 10, Width: 100, Height: 200, // Fields to test AreaHeight: 0, AreaWidth: 0, Quality: 100, /* Needs a value to satisfy libvips */ } result, err := extractOrEmbedImage(input, o) if err != nil { if err == ErrExtractAreaParamsRequired { t.Fatalf("Expecting AreaWidth and AreaHeight to have been defined") } t.Fatalf("Unknown error occurred %s", err) } image, err := saveImage(result, o) if err != nil { t.Fatalf("Failed saving image %s", err) } test, err := Size(image) if err != nil { t.Fatalf("Failed fetching the size %s", err) } if test.Height != o.Height { t.Errorf("Extract failed, resulting Height %d doesn't match %d", test.Height, o.Height) } if test.Width != o.Width { t.Errorf("Extract failed, resulting Width %d doesn't match %d", test.Width, o.Width) } } func TestConvert(t *testing.T) { width, height := 300, 240 formats := [3]ImageType{PNG, WEBP, JPEG} files := []string{ "test.jpg", "test.png", "test.webp", } for _, file := range files { img, err := os.Open("testdata/" + file) if err != nil { t.Fatal(err) } buf, err := ioutil.ReadAll(img) if err != nil { t.Fatal(err) } img.Close() for _, format := range formats { options := Options{Width: width, Height: height, Crop: true, Type: format} newImg, err := Resize(buf, options) if err != nil { t.Errorf("Resize(imgData, %#v) error: %#v", options, err) } if DetermineImageType(newImg) != format { t.Fatal("Image is not png") } size, _ := Size(newImg) if size.Height != height || size.Width != width { t.Fatalf("Invalid image size: %dx%d", size.Width, size.Height) } } } } func TestResizePngWithTransparency(t *testing.T) { width, height := 300, 240 options := Options{Width: width, Height: height, Crop: true} img, err := os.Open("testdata/transparent.png") if err != nil { t.Fatal(err) } defer img.Close() buf, err := ioutil.ReadAll(img) if err != nil { t.Fatal(err) } newImg, err := Resize(buf, options) if err != nil { t.Errorf("Resize(imgData, %#v) error: %#v", options, err) } if DetermineImageType(newImg) != PNG { t.Fatal("Image is not png") } size, _ := Size(newImg) if size.Height != height || size.Width != width { t.Fatal("Invalid image size") } Write("testdata/transparent_out.png", newImg) } func TestRotationAndFlip(t *testing.T) { files := []struct { Name string Angle Angle Flip bool }{ {"Landscape_1", 0, false}, {"Landscape_2", 0, true}, {"Landscape_3", D180, false}, {"Landscape_4", D180, true}, {"Landscape_5", D90, true}, {"Landscape_6", D90, false}, {"Landscape_7", D270, true}, {"Landscape_8", D270, false}, {"Portrait_1", 0, false}, {"Portrait_2", 0, true}, {"Portrait_3", D180, false}, {"Portrait_4", D180, true}, {"Portrait_5", D90, true}, {"Portrait_6", D90, false}, {"Portrait_7", D270, true}, {"Portrait_8", D270, false}, } for _, file := range files { img, err := os.Open(fmt.Sprintf("testdata/exif/%s.jpg", file.Name)) if err != nil { t.Fatal(err) } buf, err := ioutil.ReadAll(img) if err != nil { t.Fatal(err) } img.Close() image, _, err := loadImage(buf) if err != nil { t.Fatal(err) } angle, flip := calculateRotationAndFlip(image, D0) if angle != file.Angle { t.Errorf("Rotation for %v expected to be %v. got %v", file.Name, file.Angle, angle) } if flip != file.Flip { t.Errorf("Flip for %v expected to be %v. got %v", file.Name, file.Flip, flip) } // Visual debugging. newImg, err := Resize(buf, Options{}) if err != nil { t.Fatal(err) } Write(fmt.Sprintf("testdata/exif/%s_out.jpg", file.Name), newImg) } } func TestIfBothSmartCropOptionsAreIdentical(t *testing.T) { if !(VipsMajorVersion >= 8 && VipsMinorVersion > 4) { t.Skipf("Skipping this test, libvips doesn't meet version requirement %s > 8.4", VipsVersion) } benchmarkOptions := Options{Width: 100, Height: 100, Crop: true} smartCropOptions := Options{Width: 100, Height: 100, Crop: true, SmartCrop: true} gravityOptions := Options{Width: 100, Height: 100, Crop: true, Gravity: GravitySmart} testImg, err := os.Open("testdata/northern_cardinal_bird.jpg") if err != nil { t.Fatal(err) } defer testImg.Close() testImgByte, err := ioutil.ReadAll(testImg) if err != nil { t.Fatal(err) } scImg, err := Resize(testImgByte, smartCropOptions) if err != nil { t.Fatal(err) } gImg, err := Resize(testImgByte, gravityOptions) if err != nil { t.Fatal(err) } benchmarkImg, err := Resize(testImgByte, benchmarkOptions) if err != nil { t.Fatal(err) } sch, gh, bh := md5.Sum(scImg), md5.Sum(gImg), md5.Sum(benchmarkImg) if gh == bh || sch == bh { t.Error("Expected both options produce a different result from a standard crop.") } if sch != gh { t.Errorf("Expected both options to result in the same output, %x != %x", sch, gh) } } 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)) for n := 0; n < b.N; n++ { Resize(buf, o) } } func BenchmarkRotateJpeg(b *testing.B) { options := Options{Rotate: 180} runBenchmarkResize("test.jpg", options, b) } func BenchmarkResizeLargeJpeg(b *testing.B) { options := Options{ Width: 800, Height: 600, } runBenchmarkResize("test.jpg", options, b) } func BenchmarkResizePng(b *testing.B) { options := Options{ Width: 200, Height: 200, } runBenchmarkResize("test.png", options, b) } func BenchmarkResizeWebp(b *testing.B) { options := Options{ Width: 200, Height: 200, } runBenchmarkResize("test.webp", options, b) } func BenchmarkConvertToJpeg(b *testing.B) { options := Options{Type: JPEG} runBenchmarkResize("test.png", options, b) } func BenchmarkConvertToPng(b *testing.B) { options := Options{Type: PNG} runBenchmarkResize("test.jpg", options, b) } func BenchmarkConvertToWebp(b *testing.B) { options := Options{Type: WEBP} runBenchmarkResize("test.jpg", options, b) } func BenchmarkCropJpeg(b *testing.B) { options := Options{ Width: 800, Height: 600, } runBenchmarkResize("test.jpg", options, b) } func BenchmarkCropPng(b *testing.B) { options := Options{ Width: 800, Height: 600, } runBenchmarkResize("test.png", options, b) } func BenchmarkCropWebp(b *testing.B) { options := Options{ Width: 800, Height: 600, } runBenchmarkResize("test.webp", options, b) } func BenchmarkExtractJpeg(b *testing.B) { options := Options{ Top: 100, Left: 50, AreaWidth: 600, AreaHeight: 480, } runBenchmarkResize("test.jpg", options, b) } func BenchmarkExtractPng(b *testing.B) { options := Options{ Top: 100, Left: 50, AreaWidth: 600, AreaHeight: 480, } runBenchmarkResize("test.png", options, b) } func BenchmarkExtractWebp(b *testing.B) { options := Options{ Top: 100, Left: 50, AreaWidth: 600, AreaHeight: 480, } runBenchmarkResize("test.webp", options, b) } func BenchmarkZoomJpeg(b *testing.B) { options := Options{Zoom: 1} runBenchmarkResize("test.jpg", options, b) } func BenchmarkZoomPng(b *testing.B) { options := Options{Zoom: 1} runBenchmarkResize("test.png", options, b) } func BenchmarkZoomWebp(b *testing.B) { options := Options{Zoom: 1} runBenchmarkResize("test.webp", options, b) } func BenchmarkWatermarkJpeg(b *testing.B) { options := Options{ Watermark: Watermark{ Text: "Chuck Norris (c) 2315", Opacity: 0.25, Width: 200, DPI: 100, Margin: 150, Font: "sans bold 12", Background: Color{255, 255, 255}, }, } runBenchmarkResize("test.jpg", options, b) } func BenchmarkWatermarkPng(b *testing.B) { options := Options{ Watermark: Watermark{ Text: "Chuck Norris (c) 2315", Opacity: 0.25, Width: 200, DPI: 100, Margin: 150, Font: "sans bold 12", Background: Color{255, 255, 255}, }, } runBenchmarkResize("test.png", options, b) } func BenchmarkWatermarkWebp(b *testing.B) { options := Options{ Watermark: Watermark{ Text: "Chuck Norris (c) 2315", Opacity: 0.25, Width: 200, DPI: 100, Margin: 150, Font: "sans bold 12", Background: Color{255, 255, 255}, }, } runBenchmarkResize("test.webp", options, b) } func BenchmarkWatermarkImageJpeg(b *testing.B) { watermark := readFile("transparent.png") options := Options{ WatermarkImage: WatermarkImage{ Buf: watermark, Opacity: 0.25, Left: 100, Top: 100, }, } runBenchmarkResize("test.jpg", options, b) } func BenchmarkWatermarkImagePng(b *testing.B) { watermark := readFile("transparent.png") options := Options{ WatermarkImage: WatermarkImage{ Buf: watermark, Opacity: 0.25, Left: 100, Top: 100, }, } runBenchmarkResize("test.png", options, b) } func BenchmarkWatermarkImageWebp(b *testing.B) { watermark := readFile("transparent.png") options := Options{ WatermarkImage: WatermarkImage{ Buf: watermark, Opacity: 0.25, Left: 100, Top: 100, }, } runBenchmarkResize("test.webp", options, b) } golang-github-h2non-bimg-1.1.9+ds/type.go000066400000000000000000000110451517163235500201320ustar00rootroot00000000000000package bimg import ( "regexp" "sync" "unicode/utf8" ) // ImageType represents an image type value. type ImageType int const ( // UNKNOWN represents an unknow image type value. UNKNOWN ImageType = iota // JPEG represents the JPEG image type. JPEG // WEBP represents the WEBP image type. WEBP // PNG represents the PNG image type. PNG // TIFF represents the TIFF image type. TIFF // GIF represents the GIF image type. GIF // PDF represents the PDF type. PDF // SVG represents the SVG image type. SVG // MAGICK represents the libmagick compatible genetic image type. MAGICK // HEIF represents the HEIC/HEIF/HVEC image type HEIF // AVIF represents the AVIF image type. AVIF ) var ( htmlCommentRegex = regexp.MustCompile("(?i)") svgRegex = regexp.MustCompile(`(?i)^\s*(?:<\?xml[^>]*>\s*)?(?:]*>\s*)?]*>[^*]*<\/svg>\s*$`) ) // ImageTypes stores as pairs of image types supported and its alias names. var ImageTypes = map[ImageType]string{ JPEG: "jpeg", PNG: "png", WEBP: "webp", TIFF: "tiff", GIF: "gif", PDF: "pdf", SVG: "svg", MAGICK: "magick", HEIF: "heif", AVIF: "avif", } // imageMutex is used to provide thread-safe synchronization // for SupportedImageTypes map. var imageMutex = &sync.RWMutex{} // SupportedImageType represents whether a type can be loaded and/or saved by // the current libvips compilation. type SupportedImageType struct { Load bool Save bool } // SupportedImageTypes stores the optional image type supported // by the current libvips compilation. // Note: lazy evaluation as demand is required due // to bootstrap runtime limitation with C/libvips world. var SupportedImageTypes = map[ImageType]SupportedImageType{} // discoverSupportedImageTypes is used to fill SupportedImageTypes map. func discoverSupportedImageTypes() { imageMutex.Lock() for imageType := range ImageTypes { SupportedImageTypes[imageType] = SupportedImageType{ Load: VipsIsTypeSupported(imageType), Save: VipsIsTypeSupportedSave(imageType), } } imageMutex.Unlock() } // isBinary checks if the given buffer is a binary file. func isBinary(buf []byte) bool { if len(buf) < 24 { return false } for i := 0; i < 24; i++ { charCode, _ := utf8.DecodeRuneInString(string(buf[i])) if charCode == 65533 || charCode <= 8 { return true } } return false } // IsSVGImage returns true if the given buffer is a valid SVG image. func IsSVGImage(buf []byte) bool { return !isBinary(buf) && svgRegex.Match(htmlCommentRegex.ReplaceAll(buf, []byte{})) } // DetermineImageType determines the image type format (jpeg, png, webp or tiff) func DetermineImageType(buf []byte) ImageType { return vipsImageType(buf) } // DetermineImageTypeName determines the image type format by name (jpeg, png, webp or tiff) func DetermineImageTypeName(buf []byte) string { return ImageTypeName(vipsImageType(buf)) } // IsImageTypeSupportedByVips returns true if the given image type // is supported by current libvips compilation. func IsImageTypeSupportedByVips(t ImageType) SupportedImageType { imageMutex.RLock() // Discover supported image types and cache the result itShouldDiscover := len(SupportedImageTypes) == 0 if itShouldDiscover { imageMutex.RUnlock() discoverSupportedImageTypes() } // Check if image type is actually supported supported, ok := SupportedImageTypes[t] if !itShouldDiscover { imageMutex.RUnlock() } if ok { return supported } return SupportedImageType{Load: false, Save: false} } // IsTypeSupported checks if a given image type is supported func IsTypeSupported(t ImageType) bool { _, ok := ImageTypes[t] return ok && IsImageTypeSupportedByVips(t).Load } // IsTypeNameSupported checks if a given image type name is supported func IsTypeNameSupported(t string) bool { for imageType, name := range ImageTypes { if name == t { return IsImageTypeSupportedByVips(imageType).Load } } return false } // IsTypeSupportedSave checks if a given image type is support for saving func IsTypeSupportedSave(t ImageType) bool { _, ok := ImageTypes[t] return ok && IsImageTypeSupportedByVips(t).Save } // IsTypeNameSupportedSave checks if a given image type name is supported for // saving func IsTypeNameSupportedSave(t string) bool { for imageType, name := range ImageTypes { if name == t { return IsImageTypeSupportedByVips(imageType).Save } } return false } // ImageTypeName is used to get the human friendly name of an image format. func ImageTypeName(t ImageType) string { imageType := ImageTypes[t] if imageType == "" { return "unknown" } return imageType } golang-github-h2non-bimg-1.1.9+ds/type_test.go000066400000000000000000000072141517163235500211740ustar00rootroot00000000000000package bimg import ( "io/ioutil" "os" "path" "testing" ) func TestDeterminateImageType(t *testing.T) { files := []struct { name string expected ImageType }{ {"test.jpg", JPEG}, {"test.png", PNG}, {"test.webp", WEBP}, {"test.gif", GIF}, {"test.pdf", PDF}, {"test.svg", SVG}, // {"test.jp2", MAGICK}, {"test.heic", HEIF}, {"test2.heic", HEIF}, {"test3.heic", HEIF}, {"test.avif", AVIF}, } for _, file := range files { img, _ := os.Open(path.Join("testdata", file.name)) buf, _ := ioutil.ReadAll(img) defer img.Close() if VipsIsTypeSupported(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]) } } } } func TestDeterminateImageTypeName(t *testing.T) { files := []struct { name string expected string }{ {"test.jpg", "jpeg"}, {"test.png", "png"}, {"test.webp", "webp"}, {"test.gif", "gif"}, {"test.pdf", "pdf"}, {"test.svg", "svg"}, // {"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() value := DetermineImageTypeName(buf) if value != file.expected { t.Fatalf("Image type is not valid: %s != %s, got: %s", file.name, file.expected, value) } } } func TestIsTypeSupported(t *testing.T) { types := []struct { name ImageType }{ {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]) } } } func TestIsTypeNameSupported(t *testing.T) { types := []struct { name string expected bool }{ {"jpeg", true}, {"png", true}, {"webp", true}, {"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) } } } func TestIsTypeSupportedSave(t *testing.T) { types := []struct { name ImageType }{ {JPEG}, {PNG}, {WEBP}, {GIF}, } if VipsVersion >= "8.5.0" { types = append(types, struct{ name ImageType }{TIFF}) } if VipsVersion >= "8.8.0" { types = append(types, struct{ name ImageType }{HEIF}) } if VipsVersion >= "8.9.0" { types = append(types, struct{ name ImageType }{AVIF}) } if VipsVersion >= "8.12.0" { types = append(types, struct{ name ImageType }{GIF}) } for _, n := range types { if IsTypeSupportedSave(n.name) == false { t.Fatalf("Image type %s is not valid", ImageTypes[n.name]) } } } func TestIsTypeNameSupportedSave(t *testing.T) { types := []struct { name string expected bool }{ {"jpeg", true}, {"png", true}, {"webp", true}, {"pdf", false}, {"tiff", VipsVersion >= "8.5.0"}, {"heif", VipsVersion >= "8.8.0"}, {"avif", VipsVersion >= "8.9.0"}, {"gif", VipsVersion >= "8.12.0"}, } for _, n := range types { if IsTypeNameSupportedSave(n.name) != n.expected { t.Fatalf("Image type %s is not valid", n.name) } } } golang-github-h2non-bimg-1.1.9+ds/version.go000066400000000000000000000001421517163235500206320ustar00rootroot00000000000000package bimg // Version represents the current package semantic version. const Version = "1.1.9" golang-github-h2non-bimg-1.1.9+ds/vips.go000066400000000000000000000550171517163235500201410ustar00rootroot00000000000000package bimg /* #cgo pkg-config: vips #include "vips.h" */ import "C" import ( "errors" "fmt" "math" "os" "runtime" "strings" "sync" "unsafe" ) // VipsVersion exposes the current libvips semantic version const VipsVersion = string(C.VIPS_VERSION) // VipsMajorVersion exposes the current libvips major version number const VipsMajorVersion = int(C.VIPS_MAJOR_VERSION) // VipsMinorVersion exposes the current libvips minor version number const VipsMinorVersion = int(C.VIPS_MINOR_VERSION) const ( maxCacheMem = 100 * 1024 * 1024 maxCacheSize = 500 ) var ( m sync.Mutex initialized bool ) // VipsMemoryInfo represents the memory stats provided by libvips. type VipsMemoryInfo struct { Memory int64 MemoryHighwater int64 Allocations int64 } // vipsSaveOptions represents the internal option used to talk with libvips. type vipsSaveOptions struct { Speed int Quality int Compression int Type ImageType Interlace bool 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 { Width C.int DPI C.int Margin C.int NoReplicate C.int Opacity C.float Background [3]C.double } type vipsWatermarkImageOptions struct { Left C.int Top C.int Opacity C.float } type vipsWatermarkTextOptions struct { Text *C.char Font *C.char } func init() { Initialize() } // Initialize is used to explicitly start libvips in thread-safe way. // Only call this function if you have previously turned off libvips. func Initialize() { if C.VIPS_MAJOR_VERSION <= 7 && C.VIPS_MINOR_VERSION < 40 { panic("unsupported libvips version!") } m.Lock() runtime.LockOSThread() defer m.Unlock() defer runtime.UnlockOSThread() err := C.vips_init(C.CString("bimg")) if err != 0 { panic("unable to start vips!") } // Set libvips cache params C.vips_cache_set_max_mem(maxCacheMem) C.vips_cache_set_max(maxCacheSize) // Define a custom thread concurrency limit in libvips (this may generate thread-unsafe issues) // See: https://github.com/jcupitt/libvips/issues/261#issuecomment-92850414 if os.Getenv("VIPS_CONCURRENCY") == "" { C.vips_concurrency_set(1) } // Enable libvips cache tracing if os.Getenv("VIPS_TRACE") != "" { C.vips_enable_cache_set_trace() } initialized = true } // Shutdown is used to shutdown libvips in a thread-safe way. // You can call this to drop caches as well. // If libvips was already initialized, the function is no-op func Shutdown() { m.Lock() defer m.Unlock() if initialized { C.vips_shutdown() initialized = false } } // VipsCacheSetMaxMem Sets the maximum amount of tracked memory allowed before the vips operation cache // begins to drop entries. func VipsCacheSetMaxMem(maxCacheMem int) { C.vips_cache_set_max_mem(C.size_t(maxCacheMem)) } // VipsCacheSetMax sets the maximum number of operations to keep in the vips operation cache. func VipsCacheSetMax(maxCacheSize int) { C.vips_cache_set_max(C.int(maxCacheSize)) } // VipsCacheDropAll drops the vips operation cache, freeing the allocated memory. func VipsCacheDropAll() { C.vips_cache_drop_all() } // VipsVectorSetEnabled enables or disables SIMD vector instructions. This can give speed-up, // but can also be unstable on some systems and versions. func VipsVectorSetEnabled(enable bool) { flag := 0 if enable { flag = 1 } C.vips_vector_set_enabled(C.int(flag)) } // VipsDebugInfo outputs to stdout libvips collected data. Useful for debugging. func VipsDebugInfo() { C.im__print_all() } // VipsMemory gets memory info stats from libvips (cache size, memory allocs...) func VipsMemory() VipsMemoryInfo { return VipsMemoryInfo{ Memory: int64(C.vips_tracked_get_mem()), MemoryHighwater: int64(C.vips_tracked_get_mem_highwater()), Allocations: int64(C.vips_tracked_get_allocs()), } } // VipsIsTypeSupported returns true if the given image type // is supported by the current libvips compilation. func VipsIsTypeSupported(t ImageType) bool { if t == JPEG { return int(C.vips_type_find_bridge(C.JPEG)) != 0 } if t == WEBP { return int(C.vips_type_find_bridge(C.WEBP)) != 0 } if t == PNG { return int(C.vips_type_find_bridge(C.PNG)) != 0 } if t == GIF { return int(C.vips_type_find_bridge(C.GIF)) != 0 } if t == PDF { return int(C.vips_type_find_bridge(C.PDF)) != 0 } if t == SVG { return int(C.vips_type_find_bridge(C.SVG)) != 0 } if t == TIFF { return int(C.vips_type_find_bridge(C.TIFF)) != 0 } if t == MAGICK { return int(C.vips_type_find_bridge(C.MAGICK)) != 0 } 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 } // VipsIsTypeSupportedSave returns true if the given image type // is supported by the current libvips compilation for the // save operation. func VipsIsTypeSupportedSave(t ImageType) bool { if t == JPEG { return int(C.vips_type_find_save_bridge(C.JPEG)) != 0 } if t == WEBP { return int(C.vips_type_find_save_bridge(C.WEBP)) != 0 } if t == PNG { return int(C.vips_type_find_save_bridge(C.PNG)) != 0 } if t == TIFF { return int(C.vips_type_find_save_bridge(C.TIFF)) != 0 } 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 } if t == GIF { return int(C.vips_type_find_save_bridge(C.GIF)) != 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 { i := strings.Index(s, " (") if i > 0 { return s[:i] } return s } func vipsHasAlpha(image *C.VipsImage) bool { return int(C.has_alpha_channel(image)) > 0 } func vipsHasProfile(image *C.VipsImage) bool { return int(C.has_profile_embed(image)) > 0 } func vipsWindowSize(name string) float64 { cname := C.CString(name) defer C.free(unsafe.Pointer(cname)) return float64(C.interpolator_window_size(cname)) } func vipsSpace(image *C.VipsImage) string { return C.GoString(C.vips_enum_nick_bridge(image)) } 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_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() } 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)) err := C.vips_flip_bridge(image, &out, C.int(direction)) if err != 0 { return nil, catchVipsError() } return out, nil } func vipsZoom(image *C.VipsImage, zoom int) (*C.VipsImage, error) { var out *C.VipsImage defer C.g_object_unref(C.gpointer(image)) err := C.vips_zoom_bridge(image, &out, C.int(zoom), C.int(zoom)) if err != 0 { return nil, catchVipsError() } return out, nil } func vipsWatermark(image *C.VipsImage, w Watermark) (*C.VipsImage, error) { var out *C.VipsImage // Defaults noReplicate := 0 if w.NoReplicate { noReplicate = 1 } text := C.CString(w.Text) font := C.CString(w.Font) background := [3]C.double{C.double(w.Background.R), C.double(w.Background.G), C.double(w.Background.B)} textOpts := vipsWatermarkTextOptions{text, font} opts := vipsWatermarkOptions{C.int(w.Width), C.int(w.DPI), C.int(w.Margin), C.int(noReplicate), C.float(w.Opacity), background} defer C.free(unsafe.Pointer(text)) defer C.free(unsafe.Pointer(font)) err := C.vips_watermark(image, &out, (*C.WatermarkTextOptions)(unsafe.Pointer(&textOpts)), (*C.WatermarkOptions)(unsafe.Pointer(&opts))) if err != 0 { return nil, catchVipsError() } return out, nil } func vipsRead(buf []byte) (*C.VipsImage, ImageType, error) { var image *C.VipsImage imageType := vipsImageType(buf) if imageType == UNKNOWN { return nil, UNKNOWN, errors.New("Unsupported image format") } length := C.size_t(len(buf)) imageBuf := unsafe.Pointer(&buf[0]) err := C.vips_init_image(imageBuf, length, C.int(imageType), &image) if err != 0 { return nil, UNKNOWN, catchVipsError() } return image, imageType, nil } func vipsColourspaceIsSupportedBuffer(buf []byte) (bool, error) { image, _, err := vipsRead(buf) if err != nil { return false, err } C.g_object_unref(C.gpointer(image)) return vipsColourspaceIsSupported(image), nil } func vipsColourspaceIsSupported(image *C.VipsImage) bool { return int(C.vips_colourspace_issupported_bridge(image)) == 1 } func vipsInterpretationBuffer(buf []byte) (Interpretation, error) { image, _, err := vipsRead(buf) if err != nil { return InterpretationError, err } interp := vipsInterpretation(image) C.g_object_unref(C.gpointer(image)) return interp, nil } func vipsInterpretation(image *C.VipsImage) Interpretation { return Interpretation(C.vips_image_guess_interpretation_bridge(image)) } func vipsFlattenBackground(image *C.VipsImage, background Color) (*C.VipsImage, error) { var outImage *C.VipsImage backgroundC := [3]C.double{ C.double(background.R), C.double(background.G), C.double(background.B), } if vipsHasAlpha(image) { err := C.vips_flatten_background_brigde(image, &outImage, backgroundC[0], backgroundC[1], backgroundC[2]) if int(err) != 0 { return nil, catchVipsError() } C.g_object_unref(C.gpointer(image)) image = outImage } return image, nil } func vipsPreSave(image *C.VipsImage, o *vipsSaveOptions) (*C.VipsImage, error) { var outImage *C.VipsImage // Remove ICC profile metadata if o.NoProfile { C.remove_profile(image) } // Use a default interpretation and cast it to C type if o.Interpretation == 0 { o.Interpretation = InterpretationSRGB } interpretation := C.VipsInterpretation(o.Interpretation) // Apply the proper colour space if vipsColourspaceIsSupported(image) { err := C.vips_colourspace_bridge(image, &outImage, interpretation) if int(err) != 0 { return nil, catchVipsError() } 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)) err := C.vips_icc_transform_bridge(image, &outImage, outputIccPath) if int(err) != 0 { return nil, catchVipsError() } C.g_object_unref(C.gpointer(image)) image = outImage } return image, nil } func vipsSave(image *C.VipsImage, o vipsSaveOptions) ([]byte, error) { defer C.g_object_unref(C.gpointer(image)) tmpImage, err := vipsPreSave(image, &o) if err != nil { return nil, err } // When an image has an unsupported color space, vipsPreSave // returns the pointer of the image passed to it unmodified. // When this occurs, we must take care to not dereference the // original image a second time; we may otherwise erroneously // free the object twice. if tmpImage != image { defer C.g_object_unref(C.gpointer(tmpImage)) } length := C.size_t(0) saveErr := C.int(0) interlace := C.int(boolToInt(o.Interlace)) 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]) } var ptr unsafe.Pointer switch o.Type { 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, palette, speed) 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) case GIF: saveErr = C.vips_gifsave_bridge(tmpImage, &ptr, &length, strip) default: saveErr = C.vips_jpegsave_bridge(tmpImage, &ptr, &length, strip, quality, interlace) } if int(saveErr) != 0 { return nil, catchVipsError() } buf := C.GoBytes(ptr, C.int(length)) // Clean up C.g_free(C.gpointer(ptr)) C.vips_error_clear() return buf, nil } func getImageBuffer(image *C.VipsImage) ([]byte, error) { var ptr unsafe.Pointer length := C.size_t(0) interlace := C.int(0) quality := C.int(100) err := C.int(0) err = C.vips_jpegsave_bridge(image, &ptr, &length, 1, quality, interlace) if int(err) != 0 { return nil, catchVipsError() } defer C.g_free(C.gpointer(ptr)) defer C.vips_error_clear() return C.GoBytes(ptr, C.int(length)), nil } func vipsExtract(image *C.VipsImage, left, top, width, height int) (*C.VipsImage, error) { var buf *C.VipsImage defer C.g_object_unref(C.gpointer(image)) if width > maxSize || height > maxSize { return nil, errors.New("Maximum image size exceeded") } top, left = max(top), max(left) err := C.vips_extract_area_bridge(image, &buf, C.int(left), C.int(top), C.int(width), C.int(height)) if err != 0 { return nil, catchVipsError() } return buf, nil } func vipsSmartCrop(image *C.VipsImage, width, height int) (*C.VipsImage, error) { var buf *C.VipsImage defer C.g_object_unref(C.gpointer(image)) if width > maxSize || height > maxSize { return nil, errors.New("Maximum image size exceeded") } err := C.vips_smartcrop_bridge(image, &buf, C.int(width), C.int(height)) if err != 0 { return nil, catchVipsError() } return buf, nil } func vipsTrim(image *C.VipsImage, background Color, threshold float64) (int, int, int, int, error) { defer C.g_object_unref(C.gpointer(image)) top := C.int(0) left := C.int(0) width := C.int(0) height := C.int(0) err := C.vips_find_trim_bridge(image, &top, &left, &width, &height, C.double(background.R), C.double(background.G), C.double(background.B), C.double(threshold)) if err != 0 { return 0, 0, 0, 0, catchVipsError() } return int(top), int(left), int(width), int(height), nil } func vipsShrinkJpeg(buf []byte, input *C.VipsImage, shrink int) (*C.VipsImage, error) { var image *C.VipsImage var ptr = unsafe.Pointer(&buf[0]) defer C.g_object_unref(C.gpointer(input)) err := C.vips_jpegload_buffer_shrink(ptr, C.size_t(len(buf)), &image, C.int(shrink)) if err != 0 { return nil, catchVipsError() } return image, nil } func vipsShrinkWebp(buf []byte, input *C.VipsImage, shrink int) (*C.VipsImage, error) { var image *C.VipsImage var ptr = unsafe.Pointer(&buf[0]) defer C.g_object_unref(C.gpointer(input)) err := C.vips_webpload_buffer_shrink(ptr, C.size_t(len(buf)), &image, C.int(shrink)) if err != 0 { return nil, catchVipsError() } return image, nil } func vipsShrink(input *C.VipsImage, shrink int) (*C.VipsImage, error) { var image *C.VipsImage defer C.g_object_unref(C.gpointer(input)) err := C.vips_shrink_bridge(input, &image, C.double(float64(shrink)), C.double(float64(shrink))) if err != 0 { return nil, catchVipsError() } return image, nil } func vipsReduce(input *C.VipsImage, xshrink float64, yshrink float64) (*C.VipsImage, error) { var image *C.VipsImage defer C.g_object_unref(C.gpointer(input)) err := C.vips_reduce_bridge(input, &image, C.double(xshrink), C.double(yshrink)) if err != 0 { return nil, catchVipsError() } return image, nil } func vipsEmbed(input *C.VipsImage, left, top, width, height int, extend Extend, background Color) (*C.VipsImage, error) { var image *C.VipsImage // Max extend value, see: https://libvips.github.io/libvips/API/current/libvips-conversion.html#VipsExtend if extend > 5 { extend = ExtendBackground } defer C.g_object_unref(C.gpointer(input)) err := C.vips_embed_bridge(input, &image, C.int(left), C.int(top), C.int(width), C.int(height), C.int(extend), C.double(background.R), C.double(background.G), C.double(background.B)) if err != 0 { return nil, catchVipsError() } return image, nil } 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) defer C.free(unsafe.Pointer(cstring)) 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, C.int(extend)) if err != 0 { return nil, catchVipsError() } return image, nil } func vipsImageType(buf []byte) ImageType { if len(buf) < 12 { return UNKNOWN } if buf[0] == 0xFF && buf[1] == 0xD8 && buf[2] == 0xFF { return JPEG } if IsTypeSupported(GIF) && buf[0] == 0x47 && buf[1] == 0x49 && buf[2] == 0x46 { return GIF } if buf[0] == 0x89 && buf[1] == 0x50 && buf[2] == 0x4E && buf[3] == 0x47 { return PNG } if IsTypeSupported(TIFF) && ((buf[0] == 0x49 && buf[1] == 0x49 && buf[2] == 0x2A && buf[3] == 0x0) || (buf[0] == 0x4D && buf[1] == 0x4D && buf[2] == 0x0 && buf[3] == 0x2A)) { return TIFF } if IsTypeSupported(PDF) && buf[0] == 0x25 && buf[1] == 0x50 && buf[2] == 0x44 && buf[3] == 0x46 { return PDF } if IsTypeSupported(WEBP) && buf[8] == 0x57 && buf[9] == 0x45 && buf[10] == 0x42 && buf[11] == 0x50 { return WEBP } if IsTypeSupported(SVG) && IsSVGImage(buf) { return SVG } if IsTypeSupported(MAGICK) && strings.HasSuffix(readImageType(buf), "MagickBuffer") { return MAGICK } // NOTE: libheif currently only supports heic sub types; see: // 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, 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, 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 } func readImageType(buf []byte) string { length := C.size_t(len(buf)) imageBuf := unsafe.Pointer(&buf[0]) load := C.vips_foreign_find_load_buffer(imageBuf, length) return C.GoString(load) } func catchVipsError() error { s := C.GoString(C.vips_error_buffer()) C.vips_error_clear() C.vips_thread_shutdown() return errors.New(s) } func boolToInt(b bool) int { if b { return 1 } return 0 } func vipsGaussianBlur(image *C.VipsImage, o GaussianBlur) (*C.VipsImage, error) { var out *C.VipsImage defer C.g_object_unref(C.gpointer(image)) err := C.vips_gaussblur_bridge(image, &out, C.double(o.Sigma), C.double(o.MinAmpl)) if err != 0 { return nil, catchVipsError() } return out, nil } func vipsSharpen(image *C.VipsImage, o Sharpen) (*C.VipsImage, error) { var out *C.VipsImage defer C.g_object_unref(C.gpointer(image)) err := C.vips_sharpen_bridge(image, &out, C.int(o.Radius), C.double(o.X1), C.double(o.Y2), C.double(o.Y3), C.double(o.M1), C.double(o.M2)) if err != 0 { return nil, catchVipsError() } return out, nil } func max(x int) int { return int(math.Max(float64(x), 0)) } func vipsDrawWatermark(image *C.VipsImage, o WatermarkImage) (*C.VipsImage, error) { var out *C.VipsImage watermark, _, e := vipsRead(o.Buf) if e != nil { return nil, e } opts := vipsWatermarkImageOptions{C.int(o.Left), C.int(o.Top), C.float(o.Opacity)} err := C.vips_watermark_image(image, watermark, &out, (*C.WatermarkImageOptions)(unsafe.Pointer(&opts))) if err != 0 { return nil, catchVipsError() } return out, nil } func vipsGamma(image *C.VipsImage, Gamma float64) (*C.VipsImage, error) { var out *C.VipsImage defer C.g_object_unref(C.gpointer(image)) err := C.vips_gamma_bridge(image, &out, C.double(Gamma)) if err != 0 { return nil, catchVipsError() } return out, nil } func vipsBrightness(image *C.VipsImage, brightness float64) (*C.VipsImage, error) { var out *C.VipsImage defer C.g_object_unref(C.gpointer(image)) err := C.vips_brightness_bridge(image, &out, C.double(brightness)) if err != 0 { return nil, catchVipsError() } return out, nil } func vipsContrast(image *C.VipsImage, contrast float64) (*C.VipsImage, error) { var out *C.VipsImage defer C.g_object_unref(C.gpointer(image)) err := C.vips_contrast_bridge(image, &out, C.double(contrast)) if err != 0 { return nil, catchVipsError() } return out, nil } golang-github-h2non-bimg-1.1.9+ds/vips.h000066400000000000000000000456021517163235500177620ustar00rootroot00000000000000#include #include #include #include #include #include /** * Starting libvips 7.41, VIPS_ANGLE_x has been renamed to VIPS_ANGLE_Dx * "to help python". So we provide the macro to correctly build for versions * before 7.41.x. * https://github.com/jcupitt/libvips/blob/master/ChangeLog#L128 */ #if (VIPS_MAJOR_VERSION == 7 && VIPS_MINOR_VERSION < 41) #define VIPS_ANGLE_D0 VIPS_ANGLE_0 #define VIPS_ANGLE_D90 VIPS_ANGLE_90 #define VIPS_ANGLE_D180 VIPS_ANGLE_180 #define VIPS_ANGLE_D270 VIPS_ANGLE_270 #endif #define EXIF_IFD0_ORIENTATION "exif-ifd0-Orientation" #define INT_TO_GBOOLEAN(bool) (bool > 0 ? TRUE : FALSE) enum types { UNKNOWN = 0, JPEG, WEBP, PNG, TIFF, GIF, PDF, SVG, MAGICK, HEIF, AVIF }; typedef struct { const char *Text; const char *Font; } WatermarkTextOptions; typedef struct { int Width; int DPI; int Margin; int NoReplicate; float Opacity; double Background[3]; } WatermarkOptions; typedef struct { int Left; int Top; float Opacity; } WatermarkImageOptions; static unsigned long has_profile_embed(VipsImage *image) { return vips_image_get_typeof(image, VIPS_META_ICC_NAME); } static void remove_profile(VipsImage *image) { vips_image_remove(image, VIPS_META_ICC_NAME); } static int has_alpha_channel(VipsImage *image) { return ( (image->Bands == 2 && image->Type == VIPS_INTERPRETATION_B_W) || (image->Bands == 4 && image->Type != VIPS_INTERPRETATION_CMYK) || (image->Bands == 5 && image->Type == VIPS_INTERPRETATION_CMYK) ) ? 1 : 0; } /** * This method is here to handle the weird initialization of the vips lib. * libvips use a macro VIPS_INIT() that call vips__init() in version < 7.41, * or calls vips_init() in version >= 7.41. * * Anyway, it's not possible to build bimg on Debian Jessie with libvips 7.40.x, * as vips_init() is a macro to VIPS_INIT(), which is also a macro, hence, cgo * is unable to determine the return type of vips_init(), making the build impossible. * In order to correctly build bimg, for version < 7.41, we should undef vips_init and * creates a vips_init() method that calls VIPS_INIT(). */ #if (VIPS_MAJOR_VERSION == 7 && VIPS_MINOR_VERSION < 41) #undef vips_init int vips_init(const char *argv0) { return VIPS_INIT(argv0); } #endif void vips_enable_cache_set_trace() { vips_cache_set_trace(TRUE); } int 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 vips_jpegload_buffer_shrink(void *buf, size_t len, VipsImage **out, int shrink) { return vips_jpegload_buffer(buf, len, out, "shrink", shrink, NULL); } int vips_webpload_buffer_shrink(void *buf, size_t len, VipsImage **out, int shrink) { return vips_webpload_buffer(buf, len, out, "shrink", shrink, NULL); } int vips_flip_bridge(VipsImage *in, VipsImage **out, int direction) { return vips_flip(in, out, direction, NULL); } int vips_shrink_bridge(VipsImage *in, VipsImage **out, double xshrink, double yshrink) { return vips_shrink(in, out, xshrink, yshrink, NULL); } int vips_reduce_bridge(VipsImage *in, VipsImage **out, double xshrink, double yshrink) { return vips_reduce(in, out, xshrink, yshrink, NULL); } int vips_type_find_bridge(int t) { if (t == GIF) { return vips_type_find("VipsOperation", "gifload"); } if (t == PDF) { return vips_type_find("VipsOperation", "pdfload"); } if (t == TIFF) { return vips_type_find("VipsOperation", "tiffload"); } if (t == SVG) { return vips_type_find("VipsOperation", "svgload"); } if (t == WEBP) { return vips_type_find("VipsOperation", "webpload"); } if (t == PNG) { return vips_type_find("VipsOperation", "pngload"); } if (t == JPEG) { return vips_type_find("VipsOperation", "jpegload"); } if (t == MAGICK) { return vips_type_find("VipsOperation", "magickload"); } #if (VIPS_MAJOR_VERSION > 8 || (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION >= 8)) if (t == HEIF) { return vips_type_find("VipsOperation", "heifload"); } #endif return 0; } int vips_type_find_save_bridge(int t) { if (t == TIFF) { return vips_type_find("VipsOperation", "tiffsave_buffer"); } if (t == WEBP) { return vips_type_find("VipsOperation", "webpsave_buffer"); } if (t == PNG) { return vips_type_find("VipsOperation", "pngsave_buffer"); } if (t == JPEG) { return vips_type_find("VipsOperation", "jpegsave_buffer"); } #if (VIPS_MAJOR_VERSION > 8 || (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION >= 8)) if (t == HEIF) { return vips_type_find("VipsOperation", "heifsave_buffer"); } #endif #if (VIPS_MAJOR_VERSION > 8 || (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION >= 12)) if (t == GIF) { return vips_type_find("VipsOperation", "gifsave_buffer"); } #endif return 0; } int vips_rotate_bridge(VipsImage *in, VipsImage **out, int angle) { int rotate = VIPS_ANGLE_D0; angle %= 360; if (angle == 45) { rotate = VIPS_ANGLE45_D45; } else if (angle == 90) { rotate = VIPS_ANGLE_D90; } else if (angle == 135) { rotate = VIPS_ANGLE45_D135; } else if (angle == 180) { rotate = VIPS_ANGLE_D180; } else if (angle == 225) { rotate = VIPS_ANGLE45_D225; } else if (angle == 270) { rotate = VIPS_ANGLE_D270; } else if (angle == 315) { rotate = VIPS_ANGLE45_D315; } else { angle = 0; } if (angle > 0 && angle % 90 != 0) { return vips_rot45(in, out, "angle", rotate, NULL); } else { return vips_rot(in, out, rotate, NULL); } } int 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, tag) != 0 && !vips_image_get_string(image, tag, &exif) ) { return &exif[0]; } 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 interpolator_window_size(char const *name) { VipsInterpolate *interpolator = vips_interpolate_new(name); int window_size = vips_interpolate_get_window_size(interpolator); g_object_unref(interpolator); return window_size; } const char * vips_enum_nick_bridge(VipsImage *image) { return vips_enum_nick(VIPS_TYPE_INTERPRETATION, image->Type); } int vips_zoom_bridge(VipsImage *in, VipsImage **out, int xfac, int yfac) { return vips_zoom(in, out, xfac, yfac, NULL); } 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);} } return vips_embed(in, out, left, top, width, height, "extend", extend, NULL); } int vips_extract_area_bridge(VipsImage *in, VipsImage **out, int left, int top, int width, int height) { return vips_extract_area(in, out, left, top, width, height, NULL); } int vips_colourspace_issupported_bridge(VipsImage *in) { return vips_colourspace_issupported(in) ? 1 : 0; } VipsInterpretation vips_image_guess_interpretation_bridge(VipsImage *in) { return vips_image_guess_interpretation(in); } int vips_colourspace_bridge(VipsImage *in, VipsImage **out, VipsInterpretation space) { return vips_colourspace(in, out, space, NULL); } int vips_icc_transform_bridge (VipsImage *in, VipsImage **out, const char *output_icc_profile) { // `output_icc_profile` represents the absolute path to the output ICC profile file 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, "strip", INT_TO_GBOOLEAN(strip), "Q", quality, "optimize_coding", TRUE, "interlace", INT_TO_GBOOLEAN(interlace), NULL ); } int vips_pngsave_bridge(VipsImage *in, void **buf, size_t *len, int strip, int compression, int quality, int interlace, int palette, int speed) { #if (VIPS_MAJOR_VERSION >= 8 && VIPS_MINOR_VERSION >= 7) int effort = 10 - speed; 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), "Q", quality, "effort", effort, NULL ); #else return vips_pngsave_buffer(in, buf, len, "strip", INT_TO_GBOOLEAN(strip), "compression", compression, "interlace", INT_TO_GBOOLEAN(interlace), NULL ); #endif } int vips_webpsave_bridge(VipsImage *in, void **buf, size_t *len, int strip, int quality, int lossless) { return vips_webpsave_buffer(in, buf, len, "strip", INT_TO_GBOOLEAN(strip), "Q", quality, "lossless", INT_TO_GBOOLEAN(lossless), NULL ); } int vips_tiffsave_bridge(VipsImage *in, void **buf, size_t *len) { #if (VIPS_MAJOR_VERSION >= 8 && VIPS_MINOR_VERSION >= 5) return vips_tiffsave_buffer(in, buf, len, NULL); #else return 0; #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)) return vips_heifsave_buffer(in, buf, len, "strip", INT_TO_GBOOLEAN(strip), "Q", quality, "lossless", INT_TO_GBOOLEAN(lossless), NULL ); #else return 0; #endif } int vips_gifsave_bridge(VipsImage *in, void **buf, size_t *len, int strip) { #if (VIPS_MAJOR_VERSION > 8 || (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION >= 12)) return vips_gifsave_buffer(in, buf, len, "strip", INT_TO_GBOOLEAN(strip), NULL ); #else return 0; #endif } int vips_is_16bit (VipsInterpretation interpretation) { return interpretation == VIPS_INTERPRETATION_RGB16 || interpretation == VIPS_INTERPRETATION_GREY16; } int vips_flatten_background_brigde(VipsImage *in, VipsImage **out, double r, double g, double b) { if (vips_is_16bit(in->Type)) { r = 65535 * r / 255; g = 65535 * g / 255; b = 65535 * b / 255; } double background[3] = {r, g, b}; VipsArrayDouble *vipsBackground = vips_array_double_new(background, 3); return vips_flatten(in, out, "background", vipsBackground, "max_alpha", vips_is_16bit(in->Type) ? 65535.0 : 255.0, NULL ); } int vips_init_image (void *buf, size_t len, int imageType, VipsImage **out) { int code = 1; if (imageType == JPEG) { code = vips_jpegload_buffer(buf, len, out, "access", VIPS_ACCESS_RANDOM, NULL); } else if (imageType == PNG) { code = vips_pngload_buffer(buf, len, out, "access", VIPS_ACCESS_RANDOM, NULL); } else if (imageType == WEBP) { code = vips_webpload_buffer(buf, len, out, "access", VIPS_ACCESS_RANDOM, NULL); } else if (imageType == TIFF) { code = vips_tiffload_buffer(buf, len, out, "access", VIPS_ACCESS_RANDOM, NULL); #if (VIPS_MAJOR_VERSION >= 8) #if (VIPS_MINOR_VERSION >= 3) } else if (imageType == GIF) { code = vips_gifload_buffer(buf, len, out, "access", VIPS_ACCESS_RANDOM, NULL); } else if (imageType == PDF) { code = vips_pdfload_buffer(buf, len, out, "access", VIPS_ACCESS_RANDOM, NULL); } else if (imageType == SVG) { code = vips_svgload_buffer(buf, len, out, "access", VIPS_ACCESS_RANDOM, NULL); #endif } else if (imageType == MAGICK) { code = vips_magickload_buffer(buf, len, out, "access", VIPS_ACCESS_RANDOM, NULL); #endif #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 } return code; } int vips_watermark_replicate (VipsImage *orig, VipsImage *in, VipsImage **out) { VipsImage *cache = vips_image_new(); if ( vips_replicate(in, &cache, 1 + orig->Xsize / in->Xsize, 1 + orig->Ysize / in->Ysize, NULL) || vips_crop(cache, out, 0, 0, orig->Xsize, orig->Ysize, NULL) ) { g_object_unref(cache); return 1; } g_object_unref(cache); return 0; } int vips_watermark(VipsImage *in, VipsImage **out, WatermarkTextOptions *to, WatermarkOptions *o) { double ones[3] = { 1, 1, 1 }; VipsImage *base = vips_image_new(); VipsImage **t = (VipsImage **) vips_object_local_array(VIPS_OBJECT(base), 10); t[0] = in; // Make the mask. if ( vips_text(&t[1], to->Text, "width", o->Width, "dpi", o->DPI, "font", to->Font, NULL) || vips_linear1(t[1], &t[2], o->Opacity, 0.0, NULL) || vips_cast(t[2], &t[3], VIPS_FORMAT_UCHAR, NULL) || vips_embed(t[3], &t[4], 100, 100, t[3]->Xsize + o->Margin, t[3]->Ysize + o->Margin, NULL) ) { g_object_unref(base); return 1; } // Replicate if necessary if (o->NoReplicate != 1) { VipsImage *cache = vips_image_new(); if (vips_watermark_replicate(t[0], t[4], &cache)) { g_object_unref(cache); g_object_unref(base); return 1; } g_object_unref(t[4]); t[4] = cache; } // Make the constant image to paint the text with. if ( vips_black(&t[5], 1, 1, NULL) || vips_linear(t[5], &t[6], ones, o->Background, 3, NULL) || vips_cast(t[6], &t[7], VIPS_FORMAT_UCHAR, NULL) || vips_copy(t[7], &t[8], "interpretation", t[0]->Type, NULL) || vips_embed(t[8], &t[9], 0, 0, t[0]->Xsize, t[0]->Ysize, "extend", VIPS_EXTEND_COPY, NULL) ) { g_object_unref(base); return 1; } // Blend the mask and text and write to output. if (vips_ifthenelse(t[4], t[9], t[0], out, "blend", TRUE, NULL)) { g_object_unref(base); return 1; } g_object_unref(base); return 0; } int vips_gaussblur_bridge(VipsImage *in, VipsImage **out, double sigma, double min_ampl) { #if (VIPS_MAJOR_VERSION == 7 && VIPS_MINOR_VERSION < 41) return vips_gaussblur(in, out, (int) sigma, NULL); #else return vips_gaussblur(in, out, sigma, NULL, "min_ampl", min_ampl, NULL); #endif } int vips_sharpen_bridge(VipsImage *in, VipsImage **out, int radius, double x1, double y2, double y3, double m1, double m2) { #if (VIPS_MAJOR_VERSION == 7 && VIPS_MINOR_VERSION < 41) return vips_sharpen(in, out, radius, x1, y2, y3, m1, m2, NULL); #else return vips_sharpen(in, out, "radius", radius, "x1", x1, "y2", y2, "y3", y3, "m1", m1, "m2", m2, NULL); #endif } int vips_add_band(VipsImage *in, VipsImage **out, double c) { #if (VIPS_MAJOR_VERSION > 8 || (VIPS_MAJOR_VERSION >= 8 && VIPS_MINOR_VERSION >= 2)) return vips_bandjoin_const1(in, out, c, NULL); #else VipsImage *base = vips_image_new(); if ( vips_black(&base, in->Xsize, in->Ysize, NULL) || vips_linear1(base, &base, 1, c, NULL)) { g_object_unref(base); return 1; } g_object_unref(base); return vips_bandjoin2(in, base, out, c, NULL); #endif } int vips_watermark_image(VipsImage *in, VipsImage *sub, VipsImage **out, WatermarkImageOptions *o) { VipsImage *base = vips_image_new(); VipsImage **t = (VipsImage **) vips_object_local_array(VIPS_OBJECT(base), 10); // add in and sub for unreffing and later use t[0] = in; t[1] = sub; if (has_alpha_channel(in) == 0) { vips_add_band(in, &t[0], 255.0); // in is no longer in the array and won't be unreffed, so add it at the end t[8] = in; } if (has_alpha_channel(sub) == 0) { vips_add_band(sub, &t[1], 255.0); // sub is no longer in the array and won't be unreffed, so add it at the end t[9] = sub; } // Place watermark image in the right place and size it to the size of the // image that should be watermarked if ( vips_embed(t[1], &t[2], o->Left, o->Top, t[0]->Xsize, t[0]->Ysize, NULL)) { g_object_unref(base); return 1; } // Create a mask image based on the alpha band from the watermark image // and place it in the right position if ( vips_extract_band(t[1], &t[3], t[1]->Bands - 1, "n", 1, NULL) || vips_linear1(t[3], &t[4], o->Opacity, 0.0, NULL) || vips_cast(t[4], &t[5], VIPS_FORMAT_UCHAR, NULL) || vips_copy(t[5], &t[6], "interpretation", t[0]->Type, NULL) || vips_embed(t[6], &t[7], o->Left, o->Top, t[0]->Xsize, t[0]->Ysize, NULL)) { g_object_unref(base); return 1; } // Blend the mask and watermark image and write to output. if (vips_ifthenelse(t[7], t[2], t[0], out, "blend", TRUE, NULL)) { g_object_unref(base); return 1; } g_object_unref(base); return 0; } int vips_smartcrop_bridge(VipsImage *in, VipsImage **out, int width, int height) { #if (VIPS_MAJOR_VERSION >= 8 && VIPS_MINOR_VERSION >= 5) return vips_smartcrop(in, out, width, height, NULL); #else return 0; #endif } int vips_find_trim_bridge(VipsImage *in, int *top, int *left, int *width, int *height, double r, double g, double b, double threshold) { #if (VIPS_MAJOR_VERSION >= 8 && VIPS_MINOR_VERSION >= 6) if (vips_is_16bit(in->Type)) { r = 65535 * r / 255; g = 65535 * g / 255; b = 65535 * b / 255; } double background[3] = {r, g, b}; VipsArrayDouble *vipsBackground = vips_array_double_new(background, 3); return vips_find_trim(in, top, left, width, height, "background", vipsBackground, "threshold", threshold, NULL); #else return 0; #endif } int vips_gamma_bridge(VipsImage *in, VipsImage **out, double exponent) { return vips_gamma(in, out, "exponent", 1.0 / exponent, NULL); } int vips_brightness_bridge(VipsImage *in, VipsImage **out, double k) { return vips_linear1(in, out, 1.0 , k, NULL); } int vips_contrast_bridge(VipsImage *in, VipsImage **out, double k) { return vips_linear1(in, out, k , 0.0, NULL); } golang-github-h2non-bimg-1.1.9+ds/vips_test.go000066400000000000000000000123541517163235500211750ustar00rootroot00000000000000package bimg import ( "io/ioutil" "os" "path" "testing" ) func TestVipsRead(t *testing.T) { files := []struct { name string expected ImageType }{ {"test.jpg", JPEG}, {"test.png", PNG}, {"test.webp", WEBP}, } for _, file := range files { image, imageType, _ := vipsRead(readImage(file.name)) if image == nil { t.Fatal("Empty image") } if imageType != file.expected { t.Fatal("Invalid image type") } } } func TestVipsSave(t *testing.T) { types := [...]ImageType{JPEG, PNG, WEBP} for _, typ := range types { image, _, _ := vipsRead(readImage("test.jpg")) options := vipsSaveOptions{Quality: 95, Type: typ, StripMetadata: true} buf, err := vipsSave(image, options) if err != nil { t.Fatalf("Cannot save the image as '%v'", ImageTypes[typ]) } if len(buf) == 0 { t.Fatalf("Empty saved '%v' image", ImageTypes[typ]) } } } func TestVipsSaveTiff(t *testing.T) { if !IsTypeSupportedSave(TIFF) { t.Skipf("Format %#v is not supported", ImageTypes[TIFF]) } image, _, _ := vipsRead(readImage("test.jpg")) options := vipsSaveOptions{Quality: 95, Type: TIFF} buf, _ := vipsSave(image, options) if len(buf) == 0 { t.Fatalf("Empty saved '%v' image", ImageTypes[TIFF]) } } 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 rotate Angle }{ {"test.jpg", D90}, {"test_square.jpg", D45}, } for _, file := range files { image, _, _ := vipsRead(readImage(file.name)) newImg, err := vipsRotate(image, file.rotate) if err != nil { t.Fatal("Cannot rotate the image") } buf, _ := vipsSave(newImg, vipsSaveOptions{Quality: 95}) if len(buf) == 0 { t.Fatal("Empty image") } } } 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")) newImg, err := vipsZoom(image, 1) if err != nil { t.Fatal("Cannot save the image") } buf, _ := vipsSave(newImg, vipsSaveOptions{Quality: 95}) if len(buf) == 0 { t.Fatal("Empty image") } } func TestVipsWatermark(t *testing.T) { image, _, _ := vipsRead(readImage("test.jpg")) watermark := Watermark{ Text: "Copy me if you can", Font: "sans bold 12", Opacity: 0.5, Width: 200, DPI: 100, Margin: 100, Background: Color{255, 255, 255}, } newImg, err := vipsWatermark(image, watermark) if err != nil { t.Errorf("Cannot add watermark: %s", err) } buf, _ := vipsSave(newImg, vipsSaveOptions{Quality: 95}) if len(buf) == 0 { t.Fatal("Empty image") } } func TestVipsWatermarkWithImage(t *testing.T) { image, _, _ := vipsRead(readImage("test.jpg")) watermark := readImage("transparent.png") options := WatermarkImage{Left: 100, Top: 100, Opacity: 1.0, Buf: watermark} newImg, err := vipsDrawWatermark(image, options) if err != nil { t.Errorf("Cannot add watermark: %s", err) } buf, _ := vipsSave(newImg, vipsSaveOptions{Quality: 95}) if len(buf) == 0 { t.Fatal("Empty image") } } func TestVipsImageType(t *testing.T) { imgType := vipsImageType(readImage("test.jpg")) if imgType != JPEG { t.Fatal("Invalid image type") } } func TestVipsImageTypeInvalid(t *testing.T) { imgType := vipsImageType([]byte("vip")) if imgType != UNKNOWN { t.Fatal("Invalid image type") } } func TestVipsMemory(t *testing.T) { mem := VipsMemory() if mem.Memory < 1024 { t.Fatal("Invalid memory") } if mem.Allocations == 0 { t.Fatal("Invalid memory allocations") } } func TestVipsExifShort(t *testing.T) { tt := []struct { input string expected string }{ { input: `( ()`, expected: `(`, }, { input: ` ()`, expected: ` ()`, }, { input: `sRGB`, expected: `sRGB`, }, } for _, tc := range tt { got := vipsExifShort(tc.input) if got != tc.expected { t.Fatalf("expected: %s; got: %s", tc.expected, got) } } } func readImage(file string) []byte { img, _ := os.Open(path.Join("testdata", file)) buf, _ := ioutil.ReadAll(img) defer img.Close() return buf }