diff --git a/docs/api-composite.md b/docs/api-composite.md index e8b96de1..3403646a 100644 --- a/docs/api-composite.md +++ b/docs/api-composite.md @@ -46,6 +46,7 @@ and https://www.cairographics.org/operators/ | [images[].input.text.dpi] | number | 72 | the resolution (size) at which to render the text. Does not take effect if `height` is specified. | | [images[].input.text.rgba] | boolean | false | set this to true to enable RGBA output. This is useful for colour emoji rendering, or support for Pango markup features like `Red!`. | | [images[].input.text.spacing] | number | 0 | text line height in points. Will use the font line height if none is specified. | +| [images[].autoOrient] | Boolean | false | set to true to use EXIF orientation data, if present, to orient the image. | | [images[].blend] | String | 'over' | how to blend this image with the image below. | | [images[].gravity] | String | 'centre' | gravity at which to place the overlay. | | [images[].top] | Number | | the pixel offset from the top edge. | diff --git a/docs/api-constructor.md b/docs/api-constructor.md index 32a79a01..9ee176a5 100644 --- a/docs/api-constructor.md +++ b/docs/api-constructor.md @@ -33,6 +33,7 @@ where the overall height is the `pageHeight` multiplied by the number of `pages` | [options.failOn] | string | "'warning'" | When to abort processing of invalid pixel data, one of (in order of sensitivity, least to most): 'none', 'truncated', 'error', 'warning'. Higher levels imply lower levels. Invalid metadata will always abort. | | [options.limitInputPixels] | number \| boolean | 268402689 | Do not process input images where the number of pixels (width x height) exceeds this limit. Assumes image dimensions contained in the input metadata can be trusted. An integral Number of pixels, zero or false to remove limit, true to use default limit of 268402689 (0x3FFF x 0x3FFF). | | [options.unlimited] | boolean | false | Set this to `true` to remove safety features that help prevent memory exhaustion (JPEG, PNG, SVG, HEIF). | +| [options.autoOrient] | boolean | false | Set this to `true` to rotate/flip the image to match EXIF `Orientation`, if any. | | [options.sequentialRead] | boolean | true | Set this to `false` to use random access rather than sequential read. Some operations will do this automatically. | | [options.density] | number | 72 | number representing the DPI for vector images in the range 1 to 100000. | | [options.ignoreIcc] | number | false | should the embedded ICC profile, if any, be ignored. | diff --git a/docs/api-input.md b/docs/api-input.md index eaf00e23..ce30bb7d 100644 --- a/docs/api-input.md +++ b/docs/api-input.md @@ -72,15 +72,9 @@ image ``` **Example** ```js -// Based on EXIF rotation metadata, get the right-side-up width and height: - -const size = getNormalSize(await sharp(input).metadata()); - -function getNormalSize({ width, height, orientation }) { - return (orientation || 0) >= 5 - ? { width: height, height: width } - : { width, height }; -} +// Get dimensions taking EXIF Orientation into account. +const { autoOrient } = await sharp(input).metadata(); +const { width, height } = autoOrient; ``` diff --git a/docs/api-operation.md b/docs/api-operation.md index d38e209f..54f3a719 100644 --- a/docs/api-operation.md +++ b/docs/api-operation.md @@ -1,22 +1,19 @@ ## rotate > rotate([angle], [options]) ⇒ Sharp -Rotate the output image by either an explicit angle -or auto-orient based on the EXIF `Orientation` tag. +Rotate the output image. -If an angle is provided, it is converted to a valid positive degree rotation. +The provided angle is converted to a valid positive degree rotation. For example, `-450` will produce a 270 degree rotation. When rotating by an angle other than a multiple of 90, the background colour can be provided with the `background` option. -If no angle is provided, it is determined from the EXIF data. -Mirroring is supported and may infer the use of a flip operation. +For backwards compatibility, if no angle is provided, `.autoOrient()` will be called. -The use of `rotate` without an angle will remove the EXIF `Orientation` tag, if any. - -Only one rotation can occur per pipeline. -Previous calls to `rotate` in the same pipeline will be ignored. +Only one rotation can occur per pipeline (aside from an initial call without +arguments to orient via EXIF data). Previous calls to `rotate` in the same +pipeline will be ignored. Multi-page images can only be rotated by 180 degrees. @@ -35,18 +32,6 @@ for example `.rotate(x).extract(y)` will produce a different result to `.extract | [options] | Object | | if present, is an Object with optional attributes. | | [options.background] | string \| Object | "\"#000000\"" | parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha. | -**Example** -```js -const pipeline = sharp() - .rotate() - .resize(null, 200) - .toBuffer(function (err, outputBuffer, info) { - // outputBuffer contains 200px high JPEG image data, - // auto-rotated using EXIF Orientation tag - // info.width and info.height contain the dimensions of the resized image - }); -readableStream.pipe(pipeline); -``` **Example** ```js const rotateThenResize = await sharp(input) @@ -60,6 +45,34 @@ const resizeThenRotate = await sharp(input) ``` +## autoOrient +> autoOrient() ⇒ Sharp + +Auto-orient based on the EXIF `Orientation` tag, then remove the tag. +Mirroring is supported and may infer the use of a flip operation. + +Previous or subsequent use of `rotate(angle)` and either `flip()` or `flop()` +will logically occur after auto-orientation, regardless of call order. + + +**Example** +```js +const output = await sharp(input).autoOrient().toBuffer(); +``` +**Example** +```js +const pipeline = sharp() + .autoOrient() + .resize(null, 200) + .toBuffer(function (err, outputBuffer, info) { + // outputBuffer contains 200px high JPEG image data, + // auto-oriented using EXIF Orientation tag + // info.width and info.height contain the dimensions of the resized image + }); +readableStream.pipe(pipeline); +``` + + ## flip > flip([flip]) ⇒ Sharp diff --git a/docs/changelog.md b/docs/changelog.md index 9cc0280d..9582fbb0 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -16,6 +16,10 @@ Requires libvips v8.16.0 * Expose WebP `smartDeblock` output option. +* Add `autoOrient` operation and constructor option. + [#4151](https://github.com/lovell/sharp/pull/4151) + [@happycollision](https://github.com/happycollision) + * TypeScript: Ensure channel counts use the correct range. [#4197](https://github.com/lovell/sharp/pull/4197) [@DavidVaness](https://github.com/DavidVaness) diff --git a/docs/humans.txt b/docs/humans.txt index ece008ca..12c2bc6f 100644 --- a/docs/humans.txt +++ b/docs/humans.txt @@ -308,3 +308,6 @@ GitHub: https://github.com/sumitd2 Name: Caleb Meredith GitHub: https://github.com/calebmer + +Name: Don Denton +GitHub: https://github.com/happycollision diff --git a/docs/search-index.json b/docs/search-index.json index e880a55c..45f4be40 100644 --- a/docs/search-index.json +++ b/docs/search-index.json @@ -1 +1 @@ -[{"t":"Prerequisites","d":"Node-API v9 compatible runtime e.g. Node.js 18.17.0 or 20.3.0.","k":"prerequisites node api compatible runtime","l":"/install#prerequisites"},{"t":"Prebuilt binaries","d":"Ready-compiled sharp and libvips binaries are provided for use on the most common platforms macOS x64 10.15 macOS ARM64 Linux ARM glibc 2.31 Linux ARM64 glibc 2.26, musl 1.2.2 Linux ppc64 glibc 2.31 L","k":"prebuilt binaries compiled libvips common platforms macos arm linux glibc musl ppc","l":"/install#prebuilt-binaries"},{"t":"Custom libvips","d":"To use a custom, globally-installed version of libvips instead of the provided binaries, make sure it is at least the version listed under config.libvips in the package.json file and that it can be lo","k":"custom libvips globally installed version instead binaries listed config package json file","l":"/install#custom-libvips"},{"t":"Building from source","d":"This module will be compiled from source at npm install time when a globally-installed libvips is detected, or when the npm install --build-from-source flag is used. The logic to detect a globally-ins","k":"building source module compiled npm install time globally installed libvips detected build flag logic detect ins","l":"/install#building-from-source"},{"t":"WebAssembly","d":"Experimental support is provided for runtime environments that provide multi-threaded Wasm via Workers. Use in web browsers is unsupported. Native text rendering is unsupported. Tile-based output/api-","k":"webassembly experimental runtime environments multi threaded wasm workers web browsers native text rendering tile output api","l":"/install#webassembly"},{"t":"FreeBSD","d":"The vips package must be installed before npm install is run. sh pkg install -y pkgconf vips sh cd /usr/ports/graphics/vips/ make install clean","k":"freebsd vips package installed npm install pkg pkgconf usr ports graphics clean","l":"/install#freebsd"},{"t":"Linux memory allocator","d":"The default memory allocator on most glibc-based Linux systems e.g. Debian, Red Hat is unsuitable for long-running, multi-threaded processes that involve lots of small memory allocations. For this rea","k":"linux memory allocator glibc systems debian red hat long running multi threaded processes small allocations rea","l":"/install#linux-memory-allocator"},{"t":"AWS Lambda","d":"The node_modules directory of the deployment package must include binaries for either the linux-x64 or linux-arm64 platforms depending on the chosen architecture. When building your deployment package","k":"aws lambda nodemodules directory deployment package binaries linux arm platforms depending chosen architecture building","l":"/install#aws-lambda"},{"t":"webpack","d":"Ensure sharp is excluded from bundling via the externals configuration. js externals sharp commonjs sharp","k":"webpack excluded bundling externals configuration commonjs","l":"/install#webpack"},{"t":"esbuild","d":"Ensure sharp is excluded from bundling via the external","k":"esbuild excluded bundling external","l":"/install#esbuild"},{"t":"vite","d":"Ensure sharp is excluded from bundling via the build.rollupOptions configuration. js import defineConfig from vite export default defineConfig build rollupOptions external sharp","k":"vite excluded bundling build rollupoptions configuration import defineconfig export external","l":"/install#vite"},{"t":"TypeScript","d":"TypeScript definitions are published as part of the sharp package from v0.32.0. Previously these were available via the types/sharp package, which is now deprecated. When using Typescript, please ensu","k":"typescript definitions published package types ensu","l":"/install#typescript"},{"t":"Fonts","d":"When creating text images or rendering SVG images that contain text elements, fontconfig is used to find the relevant fonts. On Windows and macOS systems, all system fonts are available for use. On ma","k":"fonts creating text images rendering svg contain elements fontconfig find relevant windows macos systems system","l":"/install#fonts"},{"t":"Canvas and Windows","d":"If both canvas and sharp modules are used in the same Windows process, the following error may occur The specified procedure could not be found.","k":"canvas windows modules process error procedure could found","l":"/install#canvas-and-windows"},{"t":"Sharp","d":"Emits codeSharpeventinfo/code, codeSharpeventwarning/code a namenew_Sharp_new/a","k":"emits code fail limit input pixels unlimited sequential read density ignore icc pages page subifd level pdf background animated raw create text","l":"/api-constructor#sharp"},{"t":"clone","d":"Take a snapshot of the Sharp instance, returning a new instance. Cloned instances inherit the input of their parent instance. This allows multiple output Streams and therefore multiple processing pip","k":"clone snapshot instance returning new cloned instances inherit input parent multiple output streams processing pip","l":"/api-constructor#clone"},{"t":"metadata","d":"Fast access to uncached image metadata without decoding any compressed pixel data.","k":"metadata fast access uncached decoding compressed pixel data","l":"/api-input#metadata"},{"t":"stats","d":"Access to pixel-derived image statistics for every channel in the image. A Promise is returned when callback is not provided.","k":"stats access pixel derived statistics channel promise","l":"/api-input#stats"},{"t":"toFile","d":"Write output image data to a file.","k":"tofile write output data file","l":"/api-output#tofile"},{"t":"toBuffer","d":"Write output to a Buffer. JPEG, PNG, WebP, AVIF, TIFF, GIF and raw pixel data output are supported.","k":"tobuffer write output buffer jpeg png webp avif tiff gif raw pixel data resolve object","l":"/api-output#tobuffer"},{"t":"keepExif","d":"Keep all EXIF metadata from the input image in the output image.","k":"keepexif keep exif metadata input output","l":"/api-output#keepexif"},{"t":"withExif","d":"Set EXIF metadata in the output image, ignoring any EXIF in the input image.","k":"withexif exif metadata output ignoring input","l":"/api-output#withexif"},{"t":"withExifMerge","d":"Update EXIF metadata from the input image in the output image.","k":"withexifmerge update exif metadata input output","l":"/api-output#withexifmerge"},{"t":"keepIccProfile","d":"Keep ICC profile from the input image in the output image.","k":"keepiccprofile keep icc profile input output","l":"/api-output#keepiccprofile"},{"t":"withIccProfile","d":"Transform using an ICC profile and attach to the output image.","k":"withiccprofile transform icc profile attach output","l":"/api-output#withiccprofile"},{"t":"keepMetadata","d":"Keep all metadata EXIF, ICC, XMP, IPTC from the input image in the output image.","k":"keepmetadata keep metadata exif icc xmp iptc input output","l":"/api-output#keepmetadata"},{"t":"withMetadata","d":"Keep most metadata EXIF, XMP, IPTC from the input image in the output image.","k":"withmetadata keep metadata exif xmp iptc input output orientation density","l":"/api-output#withmetadata"},{"t":"toFormat","d":"Force output to a given format.","k":"toformat force output format","l":"/api-output#toformat"},{"t":"jpeg","d":"Use these JPEG options for output image.","k":"jpeg output quality progressive chroma subsampling optimise coding optimize mozjpeg trellis quantisation overshoot deringing scans table quantization force","l":"/api-output#jpeg"},{"t":"png","d":"Use these PNG options for output image.","k":"png output progressive compression level adaptive filtering palette quality effort colours colors dither force","l":"/api-output#png"},{"t":"webp","d":"Use these WebP options for output image.","k":"webp output quality alpha lossless near smart subsample deblock preset effort loop delay min size mixed force","l":"/api-output#webp"},{"t":"gif","d":"Use these GIF options for the output image.","k":"gif output reuse progressive colours colors effort dither inter frame max error palette loop delay force","l":"/api-output#gif"},{"t":"tiff","d":"Use these TIFF options for output image.","k":"tiff output quality force compression predictor pyramid tile width height xres yres resolution unit bitdepth miniswhite","l":"/api-output#tiff"},{"t":"avif","d":"Use these AVIF options for output image.","k":"avif output quality lossless effort chroma subsampling bitdepth","l":"/api-output#avif"},{"t":"heif","d":"Use these HEIF options for output image.","k":"heif output compression quality lossless effort chroma subsampling bitdepth","l":"/api-output#heif"},{"t":"jxl","d":"Use these JPEG-XL JXL options for output image.","k":"jxl jpeg output distance quality decoding tier lossless effort loop delay depth","l":"/api-output#jxl"},{"t":"tile","d":"Use tile-based deep zoom image pyramid output.","k":"tile deep zoom pyramid output size overlap angle background depth skip blanks container layout centre center basename","l":"/api-output#tile"},{"t":"timeout","d":"Set a timeout for processing, in seconds. Use a value of zero to continue processing indefinitely, the default behaviour.","k":"timeout processing seconds zero continue indefinitely behaviour","l":"/api-output#timeout"},{"t":"resize","d":"Resize image to width, height or width x height.","k":"resize width height fit position background kernel enlargement reduction fast shrink load","l":"/api-resize#resize"},{"t":"extend","d":"Extend / pad / extrude one or more edges of the image with either the provided background colour or pixels derived from the image. This operation will always occur after resizing and extraction, if a","k":"extend pad extrude edges background colour pixels derived operation resizing extraction","l":"/api-resize#extend"},{"t":"extract","d":"Extract/crop a region of the image.","k":"extract crop region left top width height","l":"/api-resize#extract"},{"t":"trim","d":"Trim pixels from all edges that contain values similar to the given background colour, which defaults to that of the top-left pixel.","k":"trim pixels edges contain similar background colour defaults top left pixel threshold line art","l":"/api-resize#trim"},{"t":"composite","d":"Composite images over the processed resized, extracted etc. image.","k":"composite images processed resized extracted","l":"/api-composite#composite"},{"t":"rotate","d":"Rotate the output image by either an explicit angle or auto-orient based on the EXIF Orientation tag.","k":"rotate output explicit angle auto orient exif orientation tag background","l":"/api-operation#rotate"},{"t":"flip","d":"Mirror the image vertically up-down about the x-axis. This always occurs before rotation, if any.","k":"flip mirror vertically down axis rotation","l":"/api-operation#flip"},{"t":"flop","d":"Mirror the image horizontally left-right about the y-axis. This always occurs before rotation, if any.","k":"flop mirror horizontally left right axis rotation","l":"/api-operation#flop"},{"t":"affine","d":"Perform an affine transform on an image. This operation will always occur after resizing, extraction and rotation, if any.","k":"affine transform operation resizing extraction rotation background idx idy odx ody interpolator","l":"/api-operation#affine"},{"t":"sharpen","d":"Sharpen the image.","k":"sharpen sigma","l":"/api-operation#sharpen"},{"t":"median","d":"Apply median filter. When used without parameters the default window is 3x3.","k":"median apply filter parameters window","l":"/api-operation#median"},{"t":"blur","d":"Blur the image.","k":"blur sigma precision min amplitude","l":"/api-operation#blur"},{"t":"flatten","d":"Merge alpha transparency channel, if any, with a background, then remove the alpha channel.","k":"flatten merge alpha transparency channel background remove","l":"/api-operation#flatten"},{"t":"unflatten","d":"Ensure the image has an alpha channel with all white pixel values made fully transparent.","k":"unflatten alpha channel white pixel made fully transparent","l":"/api-operation#unflatten"},{"t":"gamma","d":"Apply a gamma correction by reducing the encoding darken pre-resize at a factor of 1/gamma then increasing the encoding brighten post-resize at a factor of gamma. This can improve the perceived brigh","k":"gamma apply correction reducing encoding darken resize factor increasing brighten post improve perceived brigh alpha","l":"/api-operation#gamma"},{"t":"normalise","d":"Enhance output image contrast by stretching its luminance to cover a full dynamic range.","k":"normalise enhance output contrast stretching luminance cover full dynamic range lower upper","l":"/api-operation#normalise"},{"t":"normalize","d":"Alternative spelling of normalise.","k":"normalize normalise lower upper","l":"/api-operation#normalize"},{"t":"clahe","d":"Perform contrast limiting adaptive histogram equalization CLAHE.","k":"clahe contrast limiting adaptive histogram equalization width height max slope","l":"/api-operation#clahe"},{"t":"convolve","d":"Convolve the image with the specified kernel.","k":"convolve kernel","l":"/api-operation#convolve"},{"t":"threshold","d":"Any pixel value greater than or equal to the threshold value will be set to 255, otherwise it will be set to 0.","k":"threshold pixel greater equal otherwise greyscale grayscale raw","l":"/api-operation#threshold"},{"t":"recomb","d":"Recombine the image with the specified matrix.","k":"recomb recombine matrix","l":"/api-operation#recomb"},{"t":"modulate","d":"Transforms the image using brightness, saturation, hue rotation, and lightness. Brightness and lightness both operate on luminance, with the difference being that brightness is multiplicative whereas","k":"modulate transforms brightness saturation hue rotation lightness operate luminance difference being multiplicative whereas","l":"/api-operation#modulate"},{"t":"removeAlpha","d":"Remove alpha channel, if any. This is a no-op if the image does not have an alpha channel.","k":"removealpha remove alpha channel","l":"/api-channel#removealpha"},{"t":"ensureAlpha","d":"Ensure the output image has an alpha transparency channel. If missing, the added alpha channel will have the specified transparency level, defaulting to fully-opaque 1. This is a no-op if the image a","k":"ensurealpha output alpha transparency channel missing added level defaulting fully opaque","l":"/api-channel#ensurealpha"},{"t":"extractChannel","d":"Extract a single channel from a multi-channel image.","k":"extractchannel extract single channel multi","l":"/api-channel#extractchannel"},{"t":"joinChannel","d":"Join one or more channels to the image. The meaning of the added channels depends on the output colourspace, set with toColourspace. By default the output image will be web-friendly sRGB, with additi","k":"joinchannel join channels added depends output colourspace tocolourspace web friendly srgb additi","l":"/api-channel#joinchannel"},{"t":"tint","d":"Tint the image using the provided colour. An alpha channel may be present and will be unchanged by the operation.","k":"tint colour alpha channel present unchanged operation","l":"/api-colour#tint"},{"t":"greyscale","d":"Convert to 8-bit greyscale 256 shades of grey. This is a linear operation. If the input image is in a non-linear colour space such as sRGB, use gamma with greyscale for the best results. By default t","k":"greyscale convert bit shades grey linear operation input colour space srgb gamma results","l":"/api-colour#greyscale"},{"t":"grayscale","d":"Alternative spelling of greyscale.","k":"grayscale greyscale","l":"/api-colour#grayscale"},{"t":"pipelineColorspace","d":"Alternative spelling of pipelineColourspace.","k":"","l":"/api-colour#pipelinecolorspace"},{"t":"versions","d":"An Object containing the version numbers of sharp, libvips and when using prebuilt binaries its dependencies.","k":"versions object version numbers libvips prebuilt binaries dependencies","l":"/api-utility#versions"},{"t":"interpolators","d":"An Object containing the available interpolators and their proper values","k":"interpolators object","l":"/api-utility#interpolators"},{"t":"queue","d":"An EventEmitter that emits a change event when a task is either - queued, waiting for _libuv_ to provide a worker thread - complete","k":"queue eventemitter emits change event queued waiting libuv worker thread complete","l":"/api-utility#queue"},{"t":"cache","d":"Gets or, when options are provided, sets the limits of _libvips_ operation cache. Existing entries in the cache will be trimmed after any change in limits. This method always returns cache statistics","k":"cache limits libvips operation existing entries trimmed change method returns statistics memory files items","l":"/api-utility#cache"},{"t":"concurrency","d":"Gets or, when a concurrency is provided, sets the maximum number of threads _libvips_ should use to process _each image_. These are from a thread pool managed by glib, which helps avoid the overhead","k":"concurrency maximum number threads libvips process thread pool managed glib avoid overhead","l":"/api-utility#concurrency"},{"t":"counters","d":"Provides access to internal task counters. - queue is the number of tasks this module has queued waiting for _libuv_ to provide a worker thread from its pool. - process is the number of resize tasks","k":"counters provides access internal queue number tasks module queued waiting libuv worker thread pool process resize","l":"/api-utility#counters"},{"t":"simd","d":"Get and set use of SIMD vector unit instructions. Requires libvips to have been compiled with highway support.","k":"simd vector unit instructions libvips compiled highway","l":"/api-utility#simd"},{"t":"block","d":"Block libvips operations at runtime.","k":"block libvips operations runtime operation","l":"/api-utility#block"},{"t":"unblock","d":"Unblock libvips operations at runtime.","k":"unblock libvips operations runtime operation","l":"/api-utility#unblock"}] \ No newline at end of file +[{"t":"Prerequisites","d":"Node-API v9 compatible runtime e.g. Node.js 18.17.0 or 20.3.0.","k":"prerequisites node api compatible runtime","l":"/install#prerequisites"},{"t":"Prebuilt binaries","d":"Ready-compiled sharp and libvips binaries are provided for use on the most common platforms macOS x64 10.15 macOS ARM64 Linux ARM glibc 2.31 Linux ARM64 glibc 2.26, musl 1.2.2 Linux ppc64 glibc 2.31 L","k":"prebuilt binaries compiled libvips common platforms macos arm linux glibc musl ppc","l":"/install#prebuilt-binaries"},{"t":"Custom libvips","d":"To use a custom, globally-installed version of libvips instead of the provided binaries, make sure it is at least the version listed under config.libvips in the package.json file and that it can be lo","k":"custom libvips globally installed version instead binaries listed config package json file","l":"/install#custom-libvips"},{"t":"Building from source","d":"This module will be compiled from source at npm install time when a globally-installed libvips is detected, or when the npm install --build-from-source flag is used. The logic to detect a globally-ins","k":"building source module compiled npm install time globally installed libvips detected build flag logic detect ins","l":"/install#building-from-source"},{"t":"WebAssembly","d":"Experimental support is provided for runtime environments that provide multi-threaded Wasm via Workers. Use in web browsers is unsupported. Native text rendering is unsupported. Tile-based output/api-","k":"webassembly experimental runtime environments multi threaded wasm workers web browsers native text rendering tile output api","l":"/install#webassembly"},{"t":"FreeBSD","d":"The vips package must be installed before npm install is run. sh pkg install -y pkgconf vips sh cd /usr/ports/graphics/vips/ make install clean","k":"freebsd vips package installed npm install pkg pkgconf usr ports graphics clean","l":"/install#freebsd"},{"t":"Linux memory allocator","d":"The default memory allocator on most glibc-based Linux systems e.g. Debian, Red Hat is unsuitable for long-running, multi-threaded processes that involve lots of small memory allocations. For this rea","k":"linux memory allocator glibc systems debian red hat long running multi threaded processes small allocations rea","l":"/install#linux-memory-allocator"},{"t":"AWS Lambda","d":"The node_modules directory of the deployment package must include binaries for either the linux-x64 or linux-arm64 platforms depending on the chosen architecture. When building your deployment package","k":"aws lambda nodemodules directory deployment package binaries linux arm platforms depending chosen architecture building","l":"/install#aws-lambda"},{"t":"webpack","d":"Ensure sharp is excluded from bundling via the externals configuration. js externals sharp commonjs sharp","k":"webpack excluded bundling externals configuration commonjs","l":"/install#webpack"},{"t":"esbuild","d":"Ensure sharp is excluded from bundling via the external","k":"esbuild excluded bundling external","l":"/install#esbuild"},{"t":"vite","d":"Ensure sharp is excluded from bundling via the build.rollupOptions configuration. js import defineConfig from vite export default defineConfig build rollupOptions external sharp","k":"vite excluded bundling build rollupoptions configuration import defineconfig export external","l":"/install#vite"},{"t":"TypeScript","d":"TypeScript definitions are published as part of the sharp package from v0.32.0. Previously these were available via the types/sharp package, which is now deprecated. When using Typescript, please ensu","k":"typescript definitions published package types ensu","l":"/install#typescript"},{"t":"Fonts","d":"When creating text images or rendering SVG images that contain text elements, fontconfig is used to find the relevant fonts. On Windows and macOS systems, all system fonts are available for use. On ma","k":"fonts creating text images rendering svg contain elements fontconfig find relevant windows macos systems system","l":"/install#fonts"},{"t":"Canvas and Windows","d":"If both canvas and sharp modules are used in the same Windows process, the following error may occur The specified procedure could not be found.","k":"canvas windows modules process error procedure could found","l":"/install#canvas-and-windows"},{"t":"Sharp","d":"Emits codeSharpeventinfo/code, codeSharpeventwarning/code a namenew_Sharp_new/a","k":"emits code fail limit input pixels unlimited auto orient sequential read density ignore icc pages page subifd level pdf background animated raw create text","l":"/api-constructor#sharp"},{"t":"clone","d":"Take a snapshot of the Sharp instance, returning a new instance. Cloned instances inherit the input of their parent instance. This allows multiple output Streams and therefore multiple processing pip","k":"clone snapshot instance returning new cloned instances inherit input parent multiple output streams processing pip","l":"/api-constructor#clone"},{"t":"metadata","d":"Fast access to uncached image metadata without decoding any compressed pixel data.","k":"metadata fast access uncached decoding compressed pixel data","l":"/api-input#metadata"},{"t":"stats","d":"Access to pixel-derived image statistics for every channel in the image. A Promise is returned when callback is not provided.","k":"stats access pixel derived statistics channel promise","l":"/api-input#stats"},{"t":"toFile","d":"Write output image data to a file.","k":"tofile write output data file","l":"/api-output#tofile"},{"t":"toBuffer","d":"Write output to a Buffer. JPEG, PNG, WebP, AVIF, TIFF, GIF and raw pixel data output are supported.","k":"tobuffer write output buffer jpeg png webp avif tiff gif raw pixel data resolve object","l":"/api-output#tobuffer"},{"t":"keepExif","d":"Keep all EXIF metadata from the input image in the output image.","k":"keepexif keep exif metadata input output","l":"/api-output#keepexif"},{"t":"withExif","d":"Set EXIF metadata in the output image, ignoring any EXIF in the input image.","k":"withexif exif metadata output ignoring input","l":"/api-output#withexif"},{"t":"withExifMerge","d":"Update EXIF metadata from the input image in the output image.","k":"withexifmerge update exif metadata input output","l":"/api-output#withexifmerge"},{"t":"keepIccProfile","d":"Keep ICC profile from the input image in the output image.","k":"keepiccprofile keep icc profile input output","l":"/api-output#keepiccprofile"},{"t":"withIccProfile","d":"Transform using an ICC profile and attach to the output image.","k":"withiccprofile transform icc profile attach output","l":"/api-output#withiccprofile"},{"t":"keepMetadata","d":"Keep all metadata EXIF, ICC, XMP, IPTC from the input image in the output image.","k":"keepmetadata keep metadata exif icc xmp iptc input output","l":"/api-output#keepmetadata"},{"t":"withMetadata","d":"Keep most metadata EXIF, XMP, IPTC from the input image in the output image.","k":"withmetadata keep metadata exif xmp iptc input output orientation density","l":"/api-output#withmetadata"},{"t":"toFormat","d":"Force output to a given format.","k":"toformat force output format","l":"/api-output#toformat"},{"t":"jpeg","d":"Use these JPEG options for output image.","k":"jpeg output quality progressive chroma subsampling optimise coding optimize mozjpeg trellis quantisation overshoot deringing scans table quantization force","l":"/api-output#jpeg"},{"t":"png","d":"Use these PNG options for output image.","k":"png output progressive compression level adaptive filtering palette quality effort colours colors dither force","l":"/api-output#png"},{"t":"webp","d":"Use these WebP options for output image.","k":"webp output quality alpha lossless near smart subsample deblock preset effort loop delay min size mixed force","l":"/api-output#webp"},{"t":"gif","d":"Use these GIF options for the output image.","k":"gif output reuse progressive colours colors effort dither inter frame max error palette loop delay force","l":"/api-output#gif"},{"t":"tiff","d":"Use these TIFF options for output image.","k":"tiff output quality force compression predictor pyramid tile width height xres yres resolution unit bitdepth miniswhite","l":"/api-output#tiff"},{"t":"avif","d":"Use these AVIF options for output image.","k":"avif output quality lossless effort chroma subsampling bitdepth","l":"/api-output#avif"},{"t":"heif","d":"Use these HEIF options for output image.","k":"heif output compression quality lossless effort chroma subsampling bitdepth","l":"/api-output#heif"},{"t":"jxl","d":"Use these JPEG-XL JXL options for output image.","k":"jxl jpeg output distance quality decoding tier lossless effort loop delay depth","l":"/api-output#jxl"},{"t":"tile","d":"Use tile-based deep zoom image pyramid output.","k":"tile deep zoom pyramid output size overlap angle background depth skip blanks container layout centre center basename","l":"/api-output#tile"},{"t":"timeout","d":"Set a timeout for processing, in seconds. Use a value of zero to continue processing indefinitely, the default behaviour.","k":"timeout processing seconds zero continue indefinitely behaviour","l":"/api-output#timeout"},{"t":"resize","d":"Resize image to width, height or width x height.","k":"resize width height fit position background kernel enlargement reduction fast shrink load","l":"/api-resize#resize"},{"t":"extend","d":"Extend / pad / extrude one or more edges of the image with either the provided background colour or pixels derived from the image. This operation will always occur after resizing and extraction, if a","k":"extend pad extrude edges background colour pixels derived operation resizing extraction","l":"/api-resize#extend"},{"t":"extract","d":"Extract/crop a region of the image.","k":"extract crop region left top width height","l":"/api-resize#extract"},{"t":"trim","d":"Trim pixels from all edges that contain values similar to the given background colour, which defaults to that of the top-left pixel.","k":"trim pixels edges contain similar background colour defaults top left pixel threshold line art","l":"/api-resize#trim"},{"t":"composite","d":"Composite images over the processed resized, extracted etc. image.","k":"composite images processed resized extracted","l":"/api-composite#composite"},{"t":"rotate","d":"Rotate the output image.","k":"rotate output background","l":"/api-operation#rotate"},{"t":"autoOrient","d":"Auto-orient based on the EXIF Orientation tag, then remove the tag. Mirroring is supported and may infer the use of a flip operation.","k":"autoorient auto orient exif orientation tag remove mirroring infer flip operation","l":"/api-operation#autoorient"},{"t":"flip","d":"Mirror the image vertically up-down about the x-axis. This always occurs before rotation, if any.","k":"flip mirror vertically down axis rotation","l":"/api-operation#flip"},{"t":"flop","d":"Mirror the image horizontally left-right about the y-axis. This always occurs before rotation, if any.","k":"flop mirror horizontally left right axis rotation","l":"/api-operation#flop"},{"t":"affine","d":"Perform an affine transform on an image. This operation will always occur after resizing, extraction and rotation, if any.","k":"affine transform operation resizing extraction rotation background idx idy odx ody interpolator","l":"/api-operation#affine"},{"t":"sharpen","d":"Sharpen the image.","k":"sharpen sigma","l":"/api-operation#sharpen"},{"t":"median","d":"Apply median filter. When used without parameters the default window is 3x3.","k":"median apply filter parameters window","l":"/api-operation#median"},{"t":"blur","d":"Blur the image.","k":"blur sigma precision min amplitude","l":"/api-operation#blur"},{"t":"flatten","d":"Merge alpha transparency channel, if any, with a background, then remove the alpha channel.","k":"flatten merge alpha transparency channel background remove","l":"/api-operation#flatten"},{"t":"unflatten","d":"Ensure the image has an alpha channel with all white pixel values made fully transparent.","k":"unflatten alpha channel white pixel made fully transparent","l":"/api-operation#unflatten"},{"t":"gamma","d":"Apply a gamma correction by reducing the encoding darken pre-resize at a factor of 1/gamma then increasing the encoding brighten post-resize at a factor of gamma. This can improve the perceived brigh","k":"gamma apply correction reducing encoding darken resize factor increasing brighten post improve perceived brigh alpha","l":"/api-operation#gamma"},{"t":"normalise","d":"Enhance output image contrast by stretching its luminance to cover a full dynamic range.","k":"normalise enhance output contrast stretching luminance cover full dynamic range lower upper","l":"/api-operation#normalise"},{"t":"normalize","d":"Alternative spelling of normalise.","k":"normalize normalise lower upper","l":"/api-operation#normalize"},{"t":"clahe","d":"Perform contrast limiting adaptive histogram equalization CLAHE.","k":"clahe contrast limiting adaptive histogram equalization width height max slope","l":"/api-operation#clahe"},{"t":"convolve","d":"Convolve the image with the specified kernel.","k":"convolve kernel","l":"/api-operation#convolve"},{"t":"threshold","d":"Any pixel value greater than or equal to the threshold value will be set to 255, otherwise it will be set to 0.","k":"threshold pixel greater equal otherwise greyscale grayscale raw","l":"/api-operation#threshold"},{"t":"recomb","d":"Recombine the image with the specified matrix.","k":"recomb recombine matrix","l":"/api-operation#recomb"},{"t":"modulate","d":"Transforms the image using brightness, saturation, hue rotation, and lightness. Brightness and lightness both operate on luminance, with the difference being that brightness is multiplicative whereas","k":"modulate transforms brightness saturation hue rotation lightness operate luminance difference being multiplicative whereas","l":"/api-operation#modulate"},{"t":"removeAlpha","d":"Remove alpha channel, if any. This is a no-op if the image does not have an alpha channel.","k":"removealpha remove alpha channel","l":"/api-channel#removealpha"},{"t":"ensureAlpha","d":"Ensure the output image has an alpha transparency channel. If missing, the added alpha channel will have the specified transparency level, defaulting to fully-opaque 1. This is a no-op if the image a","k":"ensurealpha output alpha transparency channel missing added level defaulting fully opaque","l":"/api-channel#ensurealpha"},{"t":"extractChannel","d":"Extract a single channel from a multi-channel image.","k":"extractchannel extract single channel multi","l":"/api-channel#extractchannel"},{"t":"joinChannel","d":"Join one or more channels to the image. The meaning of the added channels depends on the output colourspace, set with toColourspace. By default the output image will be web-friendly sRGB, with additi","k":"joinchannel join channels added depends output colourspace tocolourspace web friendly srgb additi","l":"/api-channel#joinchannel"},{"t":"tint","d":"Tint the image using the provided colour. An alpha channel may be present and will be unchanged by the operation.","k":"tint colour alpha channel present unchanged operation","l":"/api-colour#tint"},{"t":"greyscale","d":"Convert to 8-bit greyscale 256 shades of grey. This is a linear operation. If the input image is in a non-linear colour space such as sRGB, use gamma with greyscale for the best results. By default t","k":"greyscale convert bit shades grey linear operation input colour space srgb gamma results","l":"/api-colour#greyscale"},{"t":"grayscale","d":"Alternative spelling of greyscale.","k":"grayscale greyscale","l":"/api-colour#grayscale"},{"t":"pipelineColorspace","d":"Alternative spelling of pipelineColourspace.","k":"","l":"/api-colour#pipelinecolorspace"},{"t":"versions","d":"An Object containing the version numbers of sharp, libvips and when using prebuilt binaries its dependencies.","k":"versions object version numbers libvips prebuilt binaries dependencies","l":"/api-utility#versions"},{"t":"interpolators","d":"An Object containing the available interpolators and their proper values","k":"interpolators object","l":"/api-utility#interpolators"},{"t":"queue","d":"An EventEmitter that emits a change event when a task is either - queued, waiting for _libuv_ to provide a worker thread - complete","k":"queue eventemitter emits change event queued waiting libuv worker thread complete","l":"/api-utility#queue"},{"t":"cache","d":"Gets or, when options are provided, sets the limits of _libvips_ operation cache. Existing entries in the cache will be trimmed after any change in limits. This method always returns cache statistics","k":"cache limits libvips operation existing entries trimmed change method returns statistics memory files items","l":"/api-utility#cache"},{"t":"concurrency","d":"Gets or, when a concurrency is provided, sets the maximum number of threads _libvips_ should use to process _each image_. These are from a thread pool managed by glib, which helps avoid the overhead","k":"concurrency maximum number threads libvips process thread pool managed glib avoid overhead","l":"/api-utility#concurrency"},{"t":"counters","d":"Provides access to internal task counters. - queue is the number of tasks this module has queued waiting for _libuv_ to provide a worker thread from its pool. - process is the number of resize tasks","k":"counters provides access internal queue number tasks module queued waiting libuv worker thread pool process resize","l":"/api-utility#counters"},{"t":"simd","d":"Get and set use of SIMD vector unit instructions. Requires libvips to have been compiled with highway support.","k":"simd vector unit instructions libvips compiled highway","l":"/api-utility#simd"},{"t":"block","d":"Block libvips operations at runtime.","k":"block libvips operations runtime operation","l":"/api-utility#block"},{"t":"unblock","d":"Unblock libvips operations at runtime.","k":"unblock libvips operations runtime operation","l":"/api-utility#unblock"}] \ No newline at end of file diff --git a/lib/composite.js b/lib/composite.js index 2dd122fb..98f8aefe 100644 --- a/lib/composite.js +++ b/lib/composite.js @@ -110,6 +110,7 @@ const blend = { * @param {number} [images[].input.text.dpi=72] - the resolution (size) at which to render the text. Does not take effect if `height` is specified. * @param {boolean} [images[].input.text.rgba=false] - set this to true to enable RGBA output. This is useful for colour emoji rendering, or support for Pango markup features like `Red!`. * @param {number} [images[].input.text.spacing=0] - text line height in points. Will use the font line height if none is specified. + * @param {Boolean} [images[].autoOrient=false] - set to true to use EXIF orientation data, if present, to orient the image. * @param {String} [images[].blend='over'] - how to blend this image with the image below. * @param {String} [images[].gravity='centre'] - gravity at which to place the overlay. * @param {Number} [images[].top] - the pixel offset from the top edge. diff --git a/lib/constructor.js b/lib/constructor.js index 2031359b..7b9c9673 100644 --- a/lib/constructor.js +++ b/lib/constructor.js @@ -132,6 +132,7 @@ const debuglog = util.debuglog('sharp'); * (width x height) exceeds this limit. Assumes image dimensions contained in the input metadata can be trusted. * An integral Number of pixels, zero or false to remove limit, true to use default limit of 268402689 (0x3FFF x 0x3FFF). * @param {boolean} [options.unlimited=false] - Set this to `true` to remove safety features that help prevent memory exhaustion (JPEG, PNG, SVG, HEIF). + * @param {boolean} [options.autoOrient=false] - Set this to `true` to rotate/flip the image to match EXIF `Orientation`, if any. * @param {boolean} [options.sequentialRead=true] - Set this to `false` to use random access rather than sequential read. Some operations will do this automatically. * @param {number} [options.density=72] - number representing the DPI for vector images in the range 1 to 100000. * @param {number} [options.ignoreIcc=false] - should the embedded ICC profile, if any, be ignored. @@ -194,7 +195,6 @@ const Sharp = function (input, options) { canvas: 'crop', position: 0, resizeBackground: [0, 0, 0, 255], - useExifOrientation: false, angle: 0, rotationAngle: 0, rotationBackground: [0, 0, 0, 255], diff --git a/lib/index.d.ts b/lib/index.d.ts index 75ca902c..05d6a2b5 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -364,24 +364,72 @@ declare namespace sharp { //#region Operation functions /** - * Rotate the output image by either an explicit angle or auto-orient based on the EXIF Orientation tag. + * Rotate the output image by either an explicit angle + * or auto-orient based on the EXIF `Orientation` tag. * - * If an angle is provided, it is converted to a valid positive degree rotation. For example, -450 will produce a 270deg rotation. + * If an angle is provided, it is converted to a valid positive degree rotation. + * For example, `-450` will produce a 270 degree rotation. * - * When rotating by an angle other than a multiple of 90, the background colour can be provided with the background option. + * When rotating by an angle other than a multiple of 90, + * the background colour can be provided with the `background` option. * - * If no angle is provided, it is determined from the EXIF data. Mirroring is supported and may infer the use of a flip operation. + * If no angle is provided, it is determined from the EXIF data. + * Mirroring is supported and may infer the use of a flip operation. * - * The use of rotate implies the removal of the EXIF Orientation tag, if any. + * The use of `rotate` without an angle will remove the EXIF `Orientation` tag, if any. * - * Method order is important when both rotating and extracting regions, for example rotate(x).extract(y) will produce a different result to extract(y).rotate(x). - * @param angle angle of rotation. (optional, default auto) - * @param options if present, is an Object with optional attributes. + * Only one rotation can occur per pipeline (aside from an initial call without + * arguments to orient via EXIF data). Previous calls to `rotate` in the same + * pipeline will be ignored. + * + * Multi-page images can only be rotated by 180 degrees. + * + * Method order is important when rotating, resizing and/or extracting regions, + * for example `.rotate(x).extract(y)` will produce a different result to `.extract(y).rotate(x)`. + * + * @example + * const pipeline = sharp() + * .rotate() + * .resize(null, 200) + * .toBuffer(function (err, outputBuffer, info) { + * // outputBuffer contains 200px high JPEG image data, + * // auto-rotated using EXIF Orientation tag + * // info.width and info.height contain the dimensions of the resized image + * }); + * readableStream.pipe(pipeline); + * + * @example + * const rotateThenResize = await sharp(input) + * .rotate(90) + * .resize({ width: 16, height: 8, fit: 'fill' }) + * .toBuffer(); + * const resizeThenRotate = await sharp(input) + * .resize({ width: 16, height: 8, fit: 'fill' }) + * .rotate(90) + * .toBuffer(); + * + * @param {number} [angle=auto] angle of rotation. + * @param {Object} [options] - if present, is an Object with optional attributes. + * @param {string|Object} [options.background="#000000"] parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha. + * @returns {Sharp} * @throws {Error} Invalid parameters - * @returns A sharp instance that can be used to chain operations */ rotate(angle?: number, options?: RotateOptions): Sharp; + /** + * Alias for calling `rotate()` with no arguments, which orients the image based + * on EXIF orientsion. + * + * This operation is aliased to emphasize its purpose, helping to remove any + * confusion between rotation and orientation. + * + * @example + * const output = await sharp(input).autoOrient().toBuffer(); + * + * @returns {Sharp} + */ + autoOrient(): Sharp + /** * Flip the image about the vertical Y axis. This always occurs after rotation, if any. * The use of flip implies the removal of the EXIF Orientation tag, if any. @@ -898,6 +946,13 @@ declare namespace sharp { } interface SharpOptions { + /** + * Auto-orient based on the EXIF `Orientation` tag, if present. + * Mirroring is supported and may infer the use of a flip operation. + * + * Using this option will remove the EXIF `Orientation` tag, if any. + */ + autoOrient?: boolean; /** * When to abort processing of invalid pixel data, one of (in order of sensitivity): * 'none' (least), 'truncated', 'error' or 'warning' (most), highers level imply lower levels, invalid metadata will always abort. (optional, default 'warning') @@ -1062,6 +1117,13 @@ declare namespace sharp { width?: number | undefined; /** Number of pixels high (EXIF orientation is not taken into consideration) */ height?: number | undefined; + /** Any changed metadata after the image orientation is applied. */ + autoOrient: { + /** Number of pixels wide (EXIF orientation is taken into consideration) */ + width: number; + /** Number of pixels high (EXIF orientation is taken into consideration) */ + height: number; + }; /** Name of colour space interpretation */ space?: keyof ColourspaceEnum | undefined; /** Number of bands e.g. 3 for sRGB, 4 for CMYK */ @@ -1512,6 +1574,8 @@ declare namespace sharp { failOn?: FailOnOptions | undefined; /** see sharp() constructor, (optional, default 268402689) */ limitInputPixels?: number | boolean | undefined; + /** see sharp() constructor, (optional, default false) */ + autoOrient?: boolean | undefined; } interface TileOptions { diff --git a/lib/input.js b/lib/input.js index 3a1562d4..74fac713 100644 --- a/lib/input.js +++ b/lib/input.js @@ -24,9 +24,9 @@ const align = { * @private */ function _inputOptionsFromObject (obj) { - const { raw, density, limitInputPixels, ignoreIcc, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd, pdfBackground } = obj; - return [raw, density, limitInputPixels, ignoreIcc, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd, pdfBackground].some(is.defined) - ? { raw, density, limitInputPixels, ignoreIcc, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd, pdfBackground } + const { raw, density, limitInputPixels, ignoreIcc, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd, pdfBackground, autoOrient } = obj; + return [raw, density, limitInputPixels, ignoreIcc, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd, pdfBackground, autoOrient].some(is.defined) + ? { raw, density, limitInputPixels, ignoreIcc, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd, pdfBackground, autoOrient } : undefined; } @@ -36,6 +36,7 @@ function _inputOptionsFromObject (obj) { */ function _createInputDescriptor (input, inputOptions, containerOptions) { const inputDescriptor = { + autoOrient: false, failOn: 'warning', limitInputPixels: Math.pow(0x3FFF, 2), ignoreIcc: false, @@ -93,6 +94,14 @@ function _createInputDescriptor (input, inputOptions, containerOptions) { throw is.invalidParameterError('failOn', 'one of: none, truncated, error, warning', inputOptions.failOn); } } + // autoOrient + if (is.defined(inputOptions.autoOrient)) { + if (is.bool(inputOptions.autoOrient)) { + inputDescriptor.autoOrient = inputOptions.autoOrient; + } else { + throw is.invalidParameterError('autoOrient', 'boolean', inputOptions.autoOrient); + } + } // Density if (is.defined(inputOptions.density)) { if (is.inRange(inputOptions.density, 1, 100000)) { @@ -475,15 +484,9 @@ function _isStreamInput () { * }); * * @example - * // Based on EXIF rotation metadata, get the right-side-up width and height: - * - * const size = getNormalSize(await sharp(input).metadata()); - * - * function getNormalSize({ width, height, orientation }) { - * return (orientation || 0) >= 5 - * ? { width: height, height: width } - * : { width, height }; - * } + * // Get dimensions taking EXIF Orientation into account. + * const { autoOrient } = await sharp(input).metadata(); + * const { width, height } = autoOrient; * * @param {Function} [callback] - called with the arguments `(err, metadata)` * @returns {Promise|Sharp} diff --git a/lib/operation.js b/lib/operation.js index 53828649..b65cebbf 100644 --- a/lib/operation.js +++ b/lib/operation.js @@ -18,22 +18,19 @@ const vipsPrecision = { }; /** - * Rotate the output image by either an explicit angle - * or auto-orient based on the EXIF `Orientation` tag. + * Rotate the output image. * - * If an angle is provided, it is converted to a valid positive degree rotation. + * The provided angle is converted to a valid positive degree rotation. * For example, `-450` will produce a 270 degree rotation. * * When rotating by an angle other than a multiple of 90, * the background colour can be provided with the `background` option. * - * If no angle is provided, it is determined from the EXIF data. - * Mirroring is supported and may infer the use of a flip operation. + * For backwards compatibility, if no angle is provided, `.autoOrient()` will be called. * - * The use of `rotate` without an angle will remove the EXIF `Orientation` tag, if any. - * - * Only one rotation can occur per pipeline. - * Previous calls to `rotate` in the same pipeline will be ignored. + * Only one rotation can occur per pipeline (aside from an initial call without + * arguments to orient via EXIF data). Previous calls to `rotate` in the same + * pipeline will be ignored. * * Multi-page images can only be rotated by 180 degrees. * @@ -41,17 +38,6 @@ const vipsPrecision = { * for example `.rotate(x).extract(y)` will produce a different result to `.extract(y).rotate(x)`. * * @example - * const pipeline = sharp() - * .rotate() - * .resize(null, 200) - * .toBuffer(function (err, outputBuffer, info) { - * // outputBuffer contains 200px high JPEG image data, - * // auto-rotated using EXIF Orientation tag - * // info.width and info.height contain the dimensions of the resized image - * }); - * readableStream.pipe(pipeline); - * - * @example * const rotateThenResize = await sharp(input) * .rotate(90) * .resize({ width: 16, height: 8, fit: 'fill' }) @@ -68,12 +54,15 @@ const vipsPrecision = { * @throws {Error} Invalid parameters */ function rotate (angle, options) { - if (this.options.useExifOrientation || this.options.angle || this.options.rotationAngle) { - this.options.debuglog('ignoring previous rotate options'); - } if (!is.defined(angle)) { - this.options.useExifOrientation = true; - } else if (is.integer(angle) && !(angle % 90)) { + return this.autoOrient(); + } + if (this.options.angle || this.options.rotationAngle) { + this.options.debuglog('ignoring previous rotate options'); + this.options.angle = 0; + this.options.rotationAngle = 0; + } + if (is.integer(angle) && !(angle % 90)) { this.options.angle = angle; } else if (is.number(angle)) { this.options.rotationAngle = angle; @@ -92,6 +81,34 @@ function rotate (angle, options) { return this; } +/** + * Auto-orient based on the EXIF `Orientation` tag, then remove the tag. + * Mirroring is supported and may infer the use of a flip operation. + * + * Previous or subsequent use of `rotate(angle)` and either `flip()` or `flop()` + * will logically occur after auto-orientation, regardless of call order. + * + * @example + * const output = await sharp(input).autoOrient().toBuffer(); + * + * @example + * const pipeline = sharp() + * .autoOrient() + * .resize(null, 200) + * .toBuffer(function (err, outputBuffer, info) { + * // outputBuffer contains 200px high JPEG image data, + * // auto-oriented using EXIF Orientation tag + * // info.width and info.height contain the dimensions of the resized image + * }); + * readableStream.pipe(pipeline); + * + * @returns {Sharp} + */ +function autoOrient () { + this.options.input.autoOrient = true; + return this; +} + /** * Mirror the image vertically (up-down) about the x-axis. * This always occurs before rotation, if any. @@ -935,6 +952,7 @@ function modulate (options) { */ module.exports = function (Sharp) { Object.assign(Sharp.prototype, { + autoOrient, rotate, flip, flop, diff --git a/lib/resize.js b/lib/resize.js index b33bb017..6b321fbe 100644 --- a/lib/resize.js +++ b/lib/resize.js @@ -107,7 +107,7 @@ const mapFitToCanvas = { * @private */ function isRotationExpected (options) { - return (options.angle % 360) !== 0 || options.useExifOrientation === true || options.rotationAngle !== 0; + return (options.angle % 360) !== 0 || options.input.autoOrient === true || options.rotationAngle !== 0; } /** diff --git a/src/common.cc b/src/common.cc index 1d6a8f63..3b0610e7 100644 --- a/src/common.cc +++ b/src/common.cc @@ -166,6 +166,8 @@ namespace sharp { descriptor->access = AttrAsBool(input, "sequentialRead") ? VIPS_ACCESS_SEQUENTIAL : VIPS_ACCESS_RANDOM; // Remove safety features and allow unlimited input descriptor->unlimited = AttrAsBool(input, "unlimited"); + // Use the EXIF orientation to auto orient the image + descriptor->autoOrient = AttrAsBool(input, "autoOrient"); return descriptor; } diff --git a/src/common.h b/src/common.h index 67d86411..b8a6d438 100644 --- a/src/common.h +++ b/src/common.h @@ -33,6 +33,7 @@ namespace sharp { struct InputDescriptor { // NOLINT(runtime/indentation_namespace) std::string name; std::string file; + bool autoOrient; char *buffer; VipsFailOn failOn; uint64_t limitInputPixels; @@ -73,6 +74,7 @@ namespace sharp { std::vector pdfBackground; InputDescriptor(): + autoOrient(false), buffer(nullptr), failOn(VIPS_FAIL_ON_WARNING), limitInputPixels(0x3FFF * 0x3FFF), diff --git a/src/metadata.cc b/src/metadata.cc index 2c1dfc79..5bf86898 100644 --- a/src/metadata.cc +++ b/src/metadata.cc @@ -242,6 +242,15 @@ class MetadataWorker : public Napi::AsyncWorker { if (baton->orientation > 0) { info.Set("orientation", baton->orientation); } + Napi::Object autoOrient = Napi::Object::New(env); + info.Set("autoOrient", autoOrient); + if (baton->orientation >= 5) { + autoOrient.Set("width", baton->height); + autoOrient.Set("height", baton->width); + } else { + autoOrient.Set("width", baton->width); + autoOrient.Set("height", baton->height); + } if (baton->exifLength > 0) { info.Set("exif", Napi::Buffer::NewOrCopy(env, baton->exif, baton->exifLength, sharp::FreeCallback)); } diff --git a/src/pipeline.cc b/src/pipeline.cc index cd929449..3a7bf0d0 100644 --- a/src/pipeline.cc +++ b/src/pipeline.cc @@ -63,14 +63,14 @@ class PipelineWorker : public Napi::AsyncWorker { bool autoFlip = false; bool autoFlop = false; - if (baton->useExifOrientation) { + if (baton->input->autoOrient) { // Rotate and flip image according to Exif orientation std::tie(autoRotation, autoFlip, autoFlop) = CalculateExifRotationAndFlip(sharp::ExifOrientation(image)); image = sharp::RemoveExifOrientation(image); - } else { - rotation = CalculateAngleRotation(baton->angle); } + rotation = CalculateAngleRotation(baton->angle); + // Rotate pre-extract bool const shouldRotateBefore = baton->rotateBeforePreExtract && (rotation != VIPS_ANGLE_D0 || autoRotation != VIPS_ANGLE_D0 || @@ -92,18 +92,14 @@ class PipelineWorker : public Napi::AsyncWorker { image = image.rot(autoRotation); autoRotation = VIPS_ANGLE_D0; } - if (autoFlip) { + if (autoFlip != baton->flip) { image = image.flip(VIPS_DIRECTION_VERTICAL); autoFlip = false; - } else if (baton->flip) { - image = image.flip(VIPS_DIRECTION_VERTICAL); baton->flip = false; } - if (autoFlop) { + if (autoFlop != baton->flop) { image = image.flip(VIPS_DIRECTION_HORIZONTAL); autoFlop = false; - } else if (baton->flop) { - image = image.flip(VIPS_DIRECTION_HORIZONTAL); baton->flop = false; } if (rotation != VIPS_ANGLE_D0) { @@ -396,11 +392,11 @@ class PipelineWorker : public Napi::AsyncWorker { image = image.rot(autoRotation); } // Mirror vertically (up-down) about the x-axis - if (baton->flip || autoFlip) { + if (baton->flip != autoFlip) { image = image.flip(VIPS_DIRECTION_VERTICAL); } // Mirror horizontally (left-right) about the y-axis - if (baton->flop || autoFlop) { + if (baton->flop != autoFlop) { image = image.flip(VIPS_DIRECTION_HORIZONTAL); } // Rotate post-extract 90-angle @@ -631,6 +627,30 @@ class PipelineWorker : public Napi::AsyncWorker { composite->input->access = access; std::tie(compositeImage, compositeImageType) = sharp::OpenInput(composite->input); compositeImage = sharp::EnsureColourspace(compositeImage, baton->colourspacePipeline); + + if (composite->input->autoOrient) { + // Respect EXIF Orientation + VipsAngle compositeAutoRotation = VIPS_ANGLE_D0; + bool compositeAutoFlip = false; + bool compositeAutoFlop = false; + std::tie(compositeAutoRotation, compositeAutoFlip, compositeAutoFlop) = + CalculateExifRotationAndFlip(sharp::ExifOrientation(compositeImage)); + + compositeImage = sharp::RemoveExifOrientation(compositeImage); + compositeImage = sharp::StaySequential(compositeImage, + compositeAutoRotation != VIPS_ANGLE_D0 || compositeAutoFlip); + + if (compositeAutoRotation != VIPS_ANGLE_D0) { + compositeImage = compositeImage.rot(compositeAutoRotation); + } + if (compositeAutoFlip) { + compositeImage = compositeImage.flip(VIPS_DIRECTION_VERTICAL); + } + if (compositeAutoFlop) { + compositeImage = compositeImage.flip(VIPS_DIRECTION_HORIZONTAL); + } + } + // Verify within current dimensions if (compositeImage.width() > image.width() || compositeImage.height() > image.height()) { throw vips::VError("Image to composite must have same dimensions or smaller"); @@ -1567,7 +1587,6 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) { baton->claheWidth = sharp::AttrAsUint32(options, "claheWidth"); baton->claheHeight = sharp::AttrAsUint32(options, "claheHeight"); baton->claheMaxSlope = sharp::AttrAsUint32(options, "claheMaxSlope"); - baton->useExifOrientation = sharp::AttrAsBool(options, "useExifOrientation"); baton->angle = sharp::AttrAsInt32(options, "angle"); baton->rotationAngle = sharp::AttrAsDouble(options, "rotationAngle"); baton->rotationBackground = sharp::AttrAsVectorOfDouble(options, "rotationBackground"); diff --git a/src/pipeline.h b/src/pipeline.h index 4610b809..530da5b6 100644 --- a/src/pipeline.h +++ b/src/pipeline.h @@ -109,7 +109,6 @@ struct PipelineBaton { int claheWidth; int claheHeight; int claheMaxSlope; - bool useExifOrientation; int angle; double rotationAngle; std::vector rotationBackground; @@ -282,7 +281,6 @@ struct PipelineBaton { claheWidth(0), claheHeight(0), claheMaxSlope(3), - useExifOrientation(false), angle(0), rotationAngle(0.0), rotationBackground{ 0.0, 0.0, 0.0, 255.0 }, diff --git a/test/fixtures/expected/Landscape_1_flip-out.jpg b/test/fixtures/expected/Landscape_1_flip-out.jpg new file mode 100644 index 00000000..5d9c7b1c Binary files /dev/null and b/test/fixtures/expected/Landscape_1_flip-out.jpg differ diff --git a/test/fixtures/expected/Landscape_1_flip_flop-out.jpg b/test/fixtures/expected/Landscape_1_flip_flop-out.jpg new file mode 100644 index 00000000..8df75ba7 Binary files /dev/null and b/test/fixtures/expected/Landscape_1_flip_flop-out.jpg differ diff --git a/test/fixtures/expected/Landscape_1_flop-out.jpg b/test/fixtures/expected/Landscape_1_flop-out.jpg new file mode 100644 index 00000000..f68ba94c Binary files /dev/null and b/test/fixtures/expected/Landscape_1_flop-out.jpg differ diff --git a/test/fixtures/expected/Landscape_1_rotate180-out.jpg b/test/fixtures/expected/Landscape_1_rotate180-out.jpg new file mode 100644 index 00000000..8df75ba7 Binary files /dev/null and b/test/fixtures/expected/Landscape_1_rotate180-out.jpg differ diff --git a/test/fixtures/expected/Landscape_1_rotate270-out.jpg b/test/fixtures/expected/Landscape_1_rotate270-out.jpg new file mode 100644 index 00000000..378b021d Binary files /dev/null and b/test/fixtures/expected/Landscape_1_rotate270-out.jpg differ diff --git a/test/fixtures/expected/Landscape_1_rotate45-out.jpg b/test/fixtures/expected/Landscape_1_rotate45-out.jpg new file mode 100644 index 00000000..e195b9d4 Binary files /dev/null and b/test/fixtures/expected/Landscape_1_rotate45-out.jpg differ diff --git a/test/fixtures/expected/Landscape_1_rotate90-out.jpg b/test/fixtures/expected/Landscape_1_rotate90-out.jpg new file mode 100644 index 00000000..e01b9088 Binary files /dev/null and b/test/fixtures/expected/Landscape_1_rotate90-out.jpg differ diff --git a/test/fixtures/expected/Landscape_2_flip-out.jpg b/test/fixtures/expected/Landscape_2_flip-out.jpg new file mode 100644 index 00000000..1a54946f Binary files /dev/null and b/test/fixtures/expected/Landscape_2_flip-out.jpg differ diff --git a/test/fixtures/expected/Landscape_2_flip_flop-out.jpg b/test/fixtures/expected/Landscape_2_flip_flop-out.jpg new file mode 100644 index 00000000..382b7324 Binary files /dev/null and b/test/fixtures/expected/Landscape_2_flip_flop-out.jpg differ diff --git a/test/fixtures/expected/Landscape_2_flop-out.jpg b/test/fixtures/expected/Landscape_2_flop-out.jpg new file mode 100644 index 00000000..5d260769 Binary files /dev/null and b/test/fixtures/expected/Landscape_2_flop-out.jpg differ diff --git a/test/fixtures/expected/Landscape_2_rotate180-out.jpg b/test/fixtures/expected/Landscape_2_rotate180-out.jpg new file mode 100644 index 00000000..382b7324 Binary files /dev/null and b/test/fixtures/expected/Landscape_2_rotate180-out.jpg differ diff --git a/test/fixtures/expected/Landscape_2_rotate270-out.jpg b/test/fixtures/expected/Landscape_2_rotate270-out.jpg new file mode 100644 index 00000000..6627376b Binary files /dev/null and b/test/fixtures/expected/Landscape_2_rotate270-out.jpg differ diff --git a/test/fixtures/expected/Landscape_2_rotate45-out.jpg b/test/fixtures/expected/Landscape_2_rotate45-out.jpg new file mode 100644 index 00000000..d46a3540 Binary files /dev/null and b/test/fixtures/expected/Landscape_2_rotate45-out.jpg differ diff --git a/test/fixtures/expected/Landscape_2_rotate90-out.jpg b/test/fixtures/expected/Landscape_2_rotate90-out.jpg new file mode 100644 index 00000000..0600f6eb Binary files /dev/null and b/test/fixtures/expected/Landscape_2_rotate90-out.jpg differ diff --git a/test/fixtures/expected/Landscape_3_flip-out.jpg b/test/fixtures/expected/Landscape_3_flip-out.jpg new file mode 100644 index 00000000..2a9d2404 Binary files /dev/null and b/test/fixtures/expected/Landscape_3_flip-out.jpg differ diff --git a/test/fixtures/expected/Landscape_3_flip_flop-out.jpg b/test/fixtures/expected/Landscape_3_flip_flop-out.jpg new file mode 100644 index 00000000..b3eb2e87 Binary files /dev/null and b/test/fixtures/expected/Landscape_3_flip_flop-out.jpg differ diff --git a/test/fixtures/expected/Landscape_3_flop-out.jpg b/test/fixtures/expected/Landscape_3_flop-out.jpg new file mode 100644 index 00000000..0a105d95 Binary files /dev/null and b/test/fixtures/expected/Landscape_3_flop-out.jpg differ diff --git a/test/fixtures/expected/Landscape_3_rotate180-out.jpg b/test/fixtures/expected/Landscape_3_rotate180-out.jpg new file mode 100644 index 00000000..b3eb2e87 Binary files /dev/null and b/test/fixtures/expected/Landscape_3_rotate180-out.jpg differ diff --git a/test/fixtures/expected/Landscape_3_rotate270-out.jpg b/test/fixtures/expected/Landscape_3_rotate270-out.jpg new file mode 100644 index 00000000..98c3bcc4 Binary files /dev/null and b/test/fixtures/expected/Landscape_3_rotate270-out.jpg differ diff --git a/test/fixtures/expected/Landscape_3_rotate45-out.jpg b/test/fixtures/expected/Landscape_3_rotate45-out.jpg new file mode 100644 index 00000000..6f74fdb3 Binary files /dev/null and b/test/fixtures/expected/Landscape_3_rotate45-out.jpg differ diff --git a/test/fixtures/expected/Landscape_3_rotate90-out.jpg b/test/fixtures/expected/Landscape_3_rotate90-out.jpg new file mode 100644 index 00000000..3026268b Binary files /dev/null and b/test/fixtures/expected/Landscape_3_rotate90-out.jpg differ diff --git a/test/fixtures/expected/Landscape_4_flip-out.jpg b/test/fixtures/expected/Landscape_4_flip-out.jpg new file mode 100644 index 00000000..6cb2e8e5 Binary files /dev/null and b/test/fixtures/expected/Landscape_4_flip-out.jpg differ diff --git a/test/fixtures/expected/Landscape_4_flip_flop-out.jpg b/test/fixtures/expected/Landscape_4_flip_flop-out.jpg new file mode 100644 index 00000000..440c2374 Binary files /dev/null and b/test/fixtures/expected/Landscape_4_flip_flop-out.jpg differ diff --git a/test/fixtures/expected/Landscape_4_flop-out.jpg b/test/fixtures/expected/Landscape_4_flop-out.jpg new file mode 100644 index 00000000..aa9d3df9 Binary files /dev/null and b/test/fixtures/expected/Landscape_4_flop-out.jpg differ diff --git a/test/fixtures/expected/Landscape_4_rotate180-out.jpg b/test/fixtures/expected/Landscape_4_rotate180-out.jpg new file mode 100644 index 00000000..440c2374 Binary files /dev/null and b/test/fixtures/expected/Landscape_4_rotate180-out.jpg differ diff --git a/test/fixtures/expected/Landscape_4_rotate270-out.jpg b/test/fixtures/expected/Landscape_4_rotate270-out.jpg new file mode 100644 index 00000000..55d17a1a Binary files /dev/null and b/test/fixtures/expected/Landscape_4_rotate270-out.jpg differ diff --git a/test/fixtures/expected/Landscape_4_rotate45-out.jpg b/test/fixtures/expected/Landscape_4_rotate45-out.jpg new file mode 100644 index 00000000..76237462 Binary files /dev/null and b/test/fixtures/expected/Landscape_4_rotate45-out.jpg differ diff --git a/test/fixtures/expected/Landscape_4_rotate90-out.jpg b/test/fixtures/expected/Landscape_4_rotate90-out.jpg new file mode 100644 index 00000000..c3543e07 Binary files /dev/null and b/test/fixtures/expected/Landscape_4_rotate90-out.jpg differ diff --git a/test/fixtures/expected/Landscape_5_flip-out.jpg b/test/fixtures/expected/Landscape_5_flip-out.jpg new file mode 100644 index 00000000..7b047485 Binary files /dev/null and b/test/fixtures/expected/Landscape_5_flip-out.jpg differ diff --git a/test/fixtures/expected/Landscape_5_flip_flop-out.jpg b/test/fixtures/expected/Landscape_5_flip_flop-out.jpg new file mode 100644 index 00000000..06a1fb92 Binary files /dev/null and b/test/fixtures/expected/Landscape_5_flip_flop-out.jpg differ diff --git a/test/fixtures/expected/Landscape_5_flop-out.jpg b/test/fixtures/expected/Landscape_5_flop-out.jpg new file mode 100644 index 00000000..bd59dff2 Binary files /dev/null and b/test/fixtures/expected/Landscape_5_flop-out.jpg differ diff --git a/test/fixtures/expected/Landscape_5_rotate180-out.jpg b/test/fixtures/expected/Landscape_5_rotate180-out.jpg new file mode 100644 index 00000000..06a1fb92 Binary files /dev/null and b/test/fixtures/expected/Landscape_5_rotate180-out.jpg differ diff --git a/test/fixtures/expected/Landscape_5_rotate270-out.jpg b/test/fixtures/expected/Landscape_5_rotate270-out.jpg new file mode 100644 index 00000000..06d77252 Binary files /dev/null and b/test/fixtures/expected/Landscape_5_rotate270-out.jpg differ diff --git a/test/fixtures/expected/Landscape_5_rotate45-out.jpg b/test/fixtures/expected/Landscape_5_rotate45-out.jpg new file mode 100644 index 00000000..0df79d2b Binary files /dev/null and b/test/fixtures/expected/Landscape_5_rotate45-out.jpg differ diff --git a/test/fixtures/expected/Landscape_5_rotate90-out.jpg b/test/fixtures/expected/Landscape_5_rotate90-out.jpg new file mode 100644 index 00000000..ad21a7d8 Binary files /dev/null and b/test/fixtures/expected/Landscape_5_rotate90-out.jpg differ diff --git a/test/fixtures/expected/Landscape_6_flip-out.jpg b/test/fixtures/expected/Landscape_6_flip-out.jpg new file mode 100644 index 00000000..c8012cbe Binary files /dev/null and b/test/fixtures/expected/Landscape_6_flip-out.jpg differ diff --git a/test/fixtures/expected/Landscape_6_flip_flop-out.jpg b/test/fixtures/expected/Landscape_6_flip_flop-out.jpg new file mode 100644 index 00000000..ad093bff Binary files /dev/null and b/test/fixtures/expected/Landscape_6_flip_flop-out.jpg differ diff --git a/test/fixtures/expected/Landscape_6_flop-out.jpg b/test/fixtures/expected/Landscape_6_flop-out.jpg new file mode 100644 index 00000000..86af2d2e Binary files /dev/null and b/test/fixtures/expected/Landscape_6_flop-out.jpg differ diff --git a/test/fixtures/expected/Landscape_6_rotate180-out.jpg b/test/fixtures/expected/Landscape_6_rotate180-out.jpg new file mode 100644 index 00000000..ad093bff Binary files /dev/null and b/test/fixtures/expected/Landscape_6_rotate180-out.jpg differ diff --git a/test/fixtures/expected/Landscape_6_rotate270-out.jpg b/test/fixtures/expected/Landscape_6_rotate270-out.jpg new file mode 100644 index 00000000..4cbbff54 Binary files /dev/null and b/test/fixtures/expected/Landscape_6_rotate270-out.jpg differ diff --git a/test/fixtures/expected/Landscape_6_rotate45-out.jpg b/test/fixtures/expected/Landscape_6_rotate45-out.jpg new file mode 100644 index 00000000..07a10956 Binary files /dev/null and b/test/fixtures/expected/Landscape_6_rotate45-out.jpg differ diff --git a/test/fixtures/expected/Landscape_6_rotate90-out.jpg b/test/fixtures/expected/Landscape_6_rotate90-out.jpg new file mode 100644 index 00000000..6fe10649 Binary files /dev/null and b/test/fixtures/expected/Landscape_6_rotate90-out.jpg differ diff --git a/test/fixtures/expected/Landscape_7_flip-out.jpg b/test/fixtures/expected/Landscape_7_flip-out.jpg new file mode 100644 index 00000000..ed14166c Binary files /dev/null and b/test/fixtures/expected/Landscape_7_flip-out.jpg differ diff --git a/test/fixtures/expected/Landscape_7_flip_flop-out.jpg b/test/fixtures/expected/Landscape_7_flip_flop-out.jpg new file mode 100644 index 00000000..522130cc Binary files /dev/null and b/test/fixtures/expected/Landscape_7_flip_flop-out.jpg differ diff --git a/test/fixtures/expected/Landscape_7_flop-out.jpg b/test/fixtures/expected/Landscape_7_flop-out.jpg new file mode 100644 index 00000000..a792fab3 Binary files /dev/null and b/test/fixtures/expected/Landscape_7_flop-out.jpg differ diff --git a/test/fixtures/expected/Landscape_7_rotate180-out.jpg b/test/fixtures/expected/Landscape_7_rotate180-out.jpg new file mode 100644 index 00000000..522130cc Binary files /dev/null and b/test/fixtures/expected/Landscape_7_rotate180-out.jpg differ diff --git a/test/fixtures/expected/Landscape_7_rotate270-out.jpg b/test/fixtures/expected/Landscape_7_rotate270-out.jpg new file mode 100644 index 00000000..b7bbae5b Binary files /dev/null and b/test/fixtures/expected/Landscape_7_rotate270-out.jpg differ diff --git a/test/fixtures/expected/Landscape_7_rotate45-out.jpg b/test/fixtures/expected/Landscape_7_rotate45-out.jpg new file mode 100644 index 00000000..76881c29 Binary files /dev/null and b/test/fixtures/expected/Landscape_7_rotate45-out.jpg differ diff --git a/test/fixtures/expected/Landscape_7_rotate90-out.jpg b/test/fixtures/expected/Landscape_7_rotate90-out.jpg new file mode 100644 index 00000000..b33cd9cb Binary files /dev/null and b/test/fixtures/expected/Landscape_7_rotate90-out.jpg differ diff --git a/test/fixtures/expected/Landscape_8_flip-out.jpg b/test/fixtures/expected/Landscape_8_flip-out.jpg new file mode 100644 index 00000000..7ab9fd40 Binary files /dev/null and b/test/fixtures/expected/Landscape_8_flip-out.jpg differ diff --git a/test/fixtures/expected/Landscape_8_flip_flop-out.jpg b/test/fixtures/expected/Landscape_8_flip_flop-out.jpg new file mode 100644 index 00000000..e931394c Binary files /dev/null and b/test/fixtures/expected/Landscape_8_flip_flop-out.jpg differ diff --git a/test/fixtures/expected/Landscape_8_flop-out.jpg b/test/fixtures/expected/Landscape_8_flop-out.jpg new file mode 100644 index 00000000..ea275beb Binary files /dev/null and b/test/fixtures/expected/Landscape_8_flop-out.jpg differ diff --git a/test/fixtures/expected/Landscape_8_rotate180-out.jpg b/test/fixtures/expected/Landscape_8_rotate180-out.jpg new file mode 100644 index 00000000..e931394c Binary files /dev/null and b/test/fixtures/expected/Landscape_8_rotate180-out.jpg differ diff --git a/test/fixtures/expected/Landscape_8_rotate270-out.jpg b/test/fixtures/expected/Landscape_8_rotate270-out.jpg new file mode 100644 index 00000000..09beada7 Binary files /dev/null and b/test/fixtures/expected/Landscape_8_rotate270-out.jpg differ diff --git a/test/fixtures/expected/Landscape_8_rotate45-out.jpg b/test/fixtures/expected/Landscape_8_rotate45-out.jpg new file mode 100644 index 00000000..cd015766 Binary files /dev/null and b/test/fixtures/expected/Landscape_8_rotate45-out.jpg differ diff --git a/test/fixtures/expected/Landscape_8_rotate90-out.jpg b/test/fixtures/expected/Landscape_8_rotate90-out.jpg new file mode 100644 index 00000000..d806c013 Binary files /dev/null and b/test/fixtures/expected/Landscape_8_rotate90-out.jpg differ diff --git a/test/fixtures/expected/Portrait_1_flip-out.jpg b/test/fixtures/expected/Portrait_1_flip-out.jpg new file mode 100644 index 00000000..13fbbd66 Binary files /dev/null and b/test/fixtures/expected/Portrait_1_flip-out.jpg differ diff --git a/test/fixtures/expected/Portrait_1_flip_flop-out.jpg b/test/fixtures/expected/Portrait_1_flip_flop-out.jpg new file mode 100644 index 00000000..30b74859 Binary files /dev/null and b/test/fixtures/expected/Portrait_1_flip_flop-out.jpg differ diff --git a/test/fixtures/expected/Portrait_1_flop-out.jpg b/test/fixtures/expected/Portrait_1_flop-out.jpg new file mode 100644 index 00000000..09453d1d Binary files /dev/null and b/test/fixtures/expected/Portrait_1_flop-out.jpg differ diff --git a/test/fixtures/expected/Portrait_1_rotate180-out.jpg b/test/fixtures/expected/Portrait_1_rotate180-out.jpg new file mode 100644 index 00000000..30b74859 Binary files /dev/null and b/test/fixtures/expected/Portrait_1_rotate180-out.jpg differ diff --git a/test/fixtures/expected/Portrait_1_rotate270-out.jpg b/test/fixtures/expected/Portrait_1_rotate270-out.jpg new file mode 100644 index 00000000..941ae8ed Binary files /dev/null and b/test/fixtures/expected/Portrait_1_rotate270-out.jpg differ diff --git a/test/fixtures/expected/Portrait_1_rotate45-out.jpg b/test/fixtures/expected/Portrait_1_rotate45-out.jpg new file mode 100644 index 00000000..d9316889 Binary files /dev/null and b/test/fixtures/expected/Portrait_1_rotate45-out.jpg differ diff --git a/test/fixtures/expected/Portrait_1_rotate90-out.jpg b/test/fixtures/expected/Portrait_1_rotate90-out.jpg new file mode 100644 index 00000000..cd91b58e Binary files /dev/null and b/test/fixtures/expected/Portrait_1_rotate90-out.jpg differ diff --git a/test/fixtures/expected/Portrait_2_flip-out.jpg b/test/fixtures/expected/Portrait_2_flip-out.jpg new file mode 100644 index 00000000..af48a72c Binary files /dev/null and b/test/fixtures/expected/Portrait_2_flip-out.jpg differ diff --git a/test/fixtures/expected/Portrait_2_flip_flop-out.jpg b/test/fixtures/expected/Portrait_2_flip_flop-out.jpg new file mode 100644 index 00000000..7e83d64f Binary files /dev/null and b/test/fixtures/expected/Portrait_2_flip_flop-out.jpg differ diff --git a/test/fixtures/expected/Portrait_2_flop-out.jpg b/test/fixtures/expected/Portrait_2_flop-out.jpg new file mode 100644 index 00000000..f746afbf Binary files /dev/null and b/test/fixtures/expected/Portrait_2_flop-out.jpg differ diff --git a/test/fixtures/expected/Portrait_2_rotate180-out.jpg b/test/fixtures/expected/Portrait_2_rotate180-out.jpg new file mode 100644 index 00000000..7e83d64f Binary files /dev/null and b/test/fixtures/expected/Portrait_2_rotate180-out.jpg differ diff --git a/test/fixtures/expected/Portrait_2_rotate270-out.jpg b/test/fixtures/expected/Portrait_2_rotate270-out.jpg new file mode 100644 index 00000000..08ab320d Binary files /dev/null and b/test/fixtures/expected/Portrait_2_rotate270-out.jpg differ diff --git a/test/fixtures/expected/Portrait_2_rotate45-out.jpg b/test/fixtures/expected/Portrait_2_rotate45-out.jpg new file mode 100644 index 00000000..81251c9b Binary files /dev/null and b/test/fixtures/expected/Portrait_2_rotate45-out.jpg differ diff --git a/test/fixtures/expected/Portrait_2_rotate90-out.jpg b/test/fixtures/expected/Portrait_2_rotate90-out.jpg new file mode 100644 index 00000000..db4ed979 Binary files /dev/null and b/test/fixtures/expected/Portrait_2_rotate90-out.jpg differ diff --git a/test/fixtures/expected/Portrait_3_flip-out.jpg b/test/fixtures/expected/Portrait_3_flip-out.jpg new file mode 100644 index 00000000..43aa137a Binary files /dev/null and b/test/fixtures/expected/Portrait_3_flip-out.jpg differ diff --git a/test/fixtures/expected/Portrait_3_flip_flop-out.jpg b/test/fixtures/expected/Portrait_3_flip_flop-out.jpg new file mode 100644 index 00000000..2b7f34d1 Binary files /dev/null and b/test/fixtures/expected/Portrait_3_flip_flop-out.jpg differ diff --git a/test/fixtures/expected/Portrait_3_flop-out.jpg b/test/fixtures/expected/Portrait_3_flop-out.jpg new file mode 100644 index 00000000..654dbb5f Binary files /dev/null and b/test/fixtures/expected/Portrait_3_flop-out.jpg differ diff --git a/test/fixtures/expected/Portrait_3_rotate180-out.jpg b/test/fixtures/expected/Portrait_3_rotate180-out.jpg new file mode 100644 index 00000000..2b7f34d1 Binary files /dev/null and b/test/fixtures/expected/Portrait_3_rotate180-out.jpg differ diff --git a/test/fixtures/expected/Portrait_3_rotate270-out.jpg b/test/fixtures/expected/Portrait_3_rotate270-out.jpg new file mode 100644 index 00000000..d8cc2d08 Binary files /dev/null and b/test/fixtures/expected/Portrait_3_rotate270-out.jpg differ diff --git a/test/fixtures/expected/Portrait_3_rotate45-out.jpg b/test/fixtures/expected/Portrait_3_rotate45-out.jpg new file mode 100644 index 00000000..d282dd93 Binary files /dev/null and b/test/fixtures/expected/Portrait_3_rotate45-out.jpg differ diff --git a/test/fixtures/expected/Portrait_3_rotate90-out.jpg b/test/fixtures/expected/Portrait_3_rotate90-out.jpg new file mode 100644 index 00000000..3f668810 Binary files /dev/null and b/test/fixtures/expected/Portrait_3_rotate90-out.jpg differ diff --git a/test/fixtures/expected/Portrait_4_flip-out.jpg b/test/fixtures/expected/Portrait_4_flip-out.jpg new file mode 100644 index 00000000..99d25b36 Binary files /dev/null and b/test/fixtures/expected/Portrait_4_flip-out.jpg differ diff --git a/test/fixtures/expected/Portrait_4_flip_flop-out.jpg b/test/fixtures/expected/Portrait_4_flip_flop-out.jpg new file mode 100644 index 00000000..4ba5e0f7 Binary files /dev/null and b/test/fixtures/expected/Portrait_4_flip_flop-out.jpg differ diff --git a/test/fixtures/expected/Portrait_4_flop-out.jpg b/test/fixtures/expected/Portrait_4_flop-out.jpg new file mode 100644 index 00000000..aecd7d14 Binary files /dev/null and b/test/fixtures/expected/Portrait_4_flop-out.jpg differ diff --git a/test/fixtures/expected/Portrait_4_rotate180-out.jpg b/test/fixtures/expected/Portrait_4_rotate180-out.jpg new file mode 100644 index 00000000..4ba5e0f7 Binary files /dev/null and b/test/fixtures/expected/Portrait_4_rotate180-out.jpg differ diff --git a/test/fixtures/expected/Portrait_4_rotate270-out.jpg b/test/fixtures/expected/Portrait_4_rotate270-out.jpg new file mode 100644 index 00000000..5e3833c3 Binary files /dev/null and b/test/fixtures/expected/Portrait_4_rotate270-out.jpg differ diff --git a/test/fixtures/expected/Portrait_4_rotate45-out.jpg b/test/fixtures/expected/Portrait_4_rotate45-out.jpg new file mode 100644 index 00000000..f110605d Binary files /dev/null and b/test/fixtures/expected/Portrait_4_rotate45-out.jpg differ diff --git a/test/fixtures/expected/Portrait_4_rotate90-out.jpg b/test/fixtures/expected/Portrait_4_rotate90-out.jpg new file mode 100644 index 00000000..913626d7 Binary files /dev/null and b/test/fixtures/expected/Portrait_4_rotate90-out.jpg differ diff --git a/test/fixtures/expected/Portrait_5_flip-out.jpg b/test/fixtures/expected/Portrait_5_flip-out.jpg new file mode 100644 index 00000000..e15c97f6 Binary files /dev/null and b/test/fixtures/expected/Portrait_5_flip-out.jpg differ diff --git a/test/fixtures/expected/Portrait_5_flip_flop-out.jpg b/test/fixtures/expected/Portrait_5_flip_flop-out.jpg new file mode 100644 index 00000000..82c999c8 Binary files /dev/null and b/test/fixtures/expected/Portrait_5_flip_flop-out.jpg differ diff --git a/test/fixtures/expected/Portrait_5_flop-out.jpg b/test/fixtures/expected/Portrait_5_flop-out.jpg new file mode 100644 index 00000000..fdea1e99 Binary files /dev/null and b/test/fixtures/expected/Portrait_5_flop-out.jpg differ diff --git a/test/fixtures/expected/Portrait_5_rotate180-out.jpg b/test/fixtures/expected/Portrait_5_rotate180-out.jpg new file mode 100644 index 00000000..82c999c8 Binary files /dev/null and b/test/fixtures/expected/Portrait_5_rotate180-out.jpg differ diff --git a/test/fixtures/expected/Portrait_5_rotate270-out.jpg b/test/fixtures/expected/Portrait_5_rotate270-out.jpg new file mode 100644 index 00000000..ff704400 Binary files /dev/null and b/test/fixtures/expected/Portrait_5_rotate270-out.jpg differ diff --git a/test/fixtures/expected/Portrait_5_rotate45-out.jpg b/test/fixtures/expected/Portrait_5_rotate45-out.jpg new file mode 100644 index 00000000..843fd8f0 Binary files /dev/null and b/test/fixtures/expected/Portrait_5_rotate45-out.jpg differ diff --git a/test/fixtures/expected/Portrait_5_rotate90-out.jpg b/test/fixtures/expected/Portrait_5_rotate90-out.jpg new file mode 100644 index 00000000..5547e7df Binary files /dev/null and b/test/fixtures/expected/Portrait_5_rotate90-out.jpg differ diff --git a/test/fixtures/expected/Portrait_6_flip-out.jpg b/test/fixtures/expected/Portrait_6_flip-out.jpg new file mode 100644 index 00000000..a7bd24c2 Binary files /dev/null and b/test/fixtures/expected/Portrait_6_flip-out.jpg differ diff --git a/test/fixtures/expected/Portrait_6_flip_flop-out.jpg b/test/fixtures/expected/Portrait_6_flip_flop-out.jpg new file mode 100644 index 00000000..c35bbb35 Binary files /dev/null and b/test/fixtures/expected/Portrait_6_flip_flop-out.jpg differ diff --git a/test/fixtures/expected/Portrait_6_flop-out.jpg b/test/fixtures/expected/Portrait_6_flop-out.jpg new file mode 100644 index 00000000..35e7d14e Binary files /dev/null and b/test/fixtures/expected/Portrait_6_flop-out.jpg differ diff --git a/test/fixtures/expected/Portrait_6_rotate180-out.jpg b/test/fixtures/expected/Portrait_6_rotate180-out.jpg new file mode 100644 index 00000000..c35bbb35 Binary files /dev/null and b/test/fixtures/expected/Portrait_6_rotate180-out.jpg differ diff --git a/test/fixtures/expected/Portrait_6_rotate270-out.jpg b/test/fixtures/expected/Portrait_6_rotate270-out.jpg new file mode 100644 index 00000000..d7e093d8 Binary files /dev/null and b/test/fixtures/expected/Portrait_6_rotate270-out.jpg differ diff --git a/test/fixtures/expected/Portrait_6_rotate45-out.jpg b/test/fixtures/expected/Portrait_6_rotate45-out.jpg new file mode 100644 index 00000000..713bb3ee Binary files /dev/null and b/test/fixtures/expected/Portrait_6_rotate45-out.jpg differ diff --git a/test/fixtures/expected/Portrait_6_rotate90-out.jpg b/test/fixtures/expected/Portrait_6_rotate90-out.jpg new file mode 100644 index 00000000..cb6c108d Binary files /dev/null and b/test/fixtures/expected/Portrait_6_rotate90-out.jpg differ diff --git a/test/fixtures/expected/Portrait_7_flip-out.jpg b/test/fixtures/expected/Portrait_7_flip-out.jpg new file mode 100644 index 00000000..3c89dc8b Binary files /dev/null and b/test/fixtures/expected/Portrait_7_flip-out.jpg differ diff --git a/test/fixtures/expected/Portrait_7_flip_flop-out.jpg b/test/fixtures/expected/Portrait_7_flip_flop-out.jpg new file mode 100644 index 00000000..ee99439d Binary files /dev/null and b/test/fixtures/expected/Portrait_7_flip_flop-out.jpg differ diff --git a/test/fixtures/expected/Portrait_7_flop-out.jpg b/test/fixtures/expected/Portrait_7_flop-out.jpg new file mode 100644 index 00000000..2dba39f8 Binary files /dev/null and b/test/fixtures/expected/Portrait_7_flop-out.jpg differ diff --git a/test/fixtures/expected/Portrait_7_rotate180-out.jpg b/test/fixtures/expected/Portrait_7_rotate180-out.jpg new file mode 100644 index 00000000..ee99439d Binary files /dev/null and b/test/fixtures/expected/Portrait_7_rotate180-out.jpg differ diff --git a/test/fixtures/expected/Portrait_7_rotate270-out.jpg b/test/fixtures/expected/Portrait_7_rotate270-out.jpg new file mode 100644 index 00000000..6756e61b Binary files /dev/null and b/test/fixtures/expected/Portrait_7_rotate270-out.jpg differ diff --git a/test/fixtures/expected/Portrait_7_rotate45-out.jpg b/test/fixtures/expected/Portrait_7_rotate45-out.jpg new file mode 100644 index 00000000..f8d60b80 Binary files /dev/null and b/test/fixtures/expected/Portrait_7_rotate45-out.jpg differ diff --git a/test/fixtures/expected/Portrait_7_rotate90-out.jpg b/test/fixtures/expected/Portrait_7_rotate90-out.jpg new file mode 100644 index 00000000..879d2c70 Binary files /dev/null and b/test/fixtures/expected/Portrait_7_rotate90-out.jpg differ diff --git a/test/fixtures/expected/Portrait_8_flip-out.jpg b/test/fixtures/expected/Portrait_8_flip-out.jpg new file mode 100644 index 00000000..e4630027 Binary files /dev/null and b/test/fixtures/expected/Portrait_8_flip-out.jpg differ diff --git a/test/fixtures/expected/Portrait_8_flip_flop-out.jpg b/test/fixtures/expected/Portrait_8_flip_flop-out.jpg new file mode 100644 index 00000000..2f4f2ce2 Binary files /dev/null and b/test/fixtures/expected/Portrait_8_flip_flop-out.jpg differ diff --git a/test/fixtures/expected/Portrait_8_flop-out.jpg b/test/fixtures/expected/Portrait_8_flop-out.jpg new file mode 100644 index 00000000..83e5e9d0 Binary files /dev/null and b/test/fixtures/expected/Portrait_8_flop-out.jpg differ diff --git a/test/fixtures/expected/Portrait_8_rotate180-out.jpg b/test/fixtures/expected/Portrait_8_rotate180-out.jpg new file mode 100644 index 00000000..2f4f2ce2 Binary files /dev/null and b/test/fixtures/expected/Portrait_8_rotate180-out.jpg differ diff --git a/test/fixtures/expected/Portrait_8_rotate270-out.jpg b/test/fixtures/expected/Portrait_8_rotate270-out.jpg new file mode 100644 index 00000000..b0bb7296 Binary files /dev/null and b/test/fixtures/expected/Portrait_8_rotate270-out.jpg differ diff --git a/test/fixtures/expected/Portrait_8_rotate45-out.jpg b/test/fixtures/expected/Portrait_8_rotate45-out.jpg new file mode 100644 index 00000000..482c70ae Binary files /dev/null and b/test/fixtures/expected/Portrait_8_rotate45-out.jpg differ diff --git a/test/fixtures/expected/Portrait_8_rotate90-out.jpg b/test/fixtures/expected/Portrait_8_rotate90-out.jpg new file mode 100644 index 00000000..695ff027 Binary files /dev/null and b/test/fixtures/expected/Portrait_8_rotate90-out.jpg differ diff --git a/test/fixtures/expected/composite-autoOrient.jpg b/test/fixtures/expected/composite-autoOrient.jpg new file mode 100644 index 00000000..75cf137f Binary files /dev/null and b/test/fixtures/expected/composite-autoOrient.jpg differ diff --git a/test/types/sharp.test-d.ts b/test/types/sharp.test-d.ts index 5a502e37..719432c9 100644 --- a/test/types/sharp.test-d.ts +++ b/test/types/sharp.test-d.ts @@ -702,7 +702,8 @@ sharp(input).composite([ animated: true, limitInputPixels: 536805378, density: 144, - failOn: "warning" + failOn: "warning", + autoOrient: true } ]) sharp(input).composite([ @@ -719,3 +720,7 @@ const colour: sharp.Colour = '#fff'; const color: sharp.Color = '#fff'; sharp({ pdfBackground: colour }); sharp({ pdfBackground: color }); + +sharp({ autoOrient: true }); +sharp({ autoOrient: false }); +sharp().autoOrient(); diff --git a/test/unit/avif.js b/test/unit/avif.js index 9f1e6626..9eeb5000 100644 --- a/test/unit/avif.js +++ b/test/unit/avif.js @@ -23,6 +23,10 @@ describe('AVIF', () => { const { size, ...metadata } = await sharp(data) .metadata(); assert.deepStrictEqual(metadata, { + autoOrient: { + height: 13, + width: 32 + }, channels: 3, chromaSubsampling: '4:2:0', density: 72, @@ -48,6 +52,10 @@ describe('AVIF', () => { const { size, ...metadata } = await sharp(data) .metadata(); assert.deepStrictEqual(metadata, { + autoOrient: { + height: 26, + width: 32 + }, channels: 3, compression: 'av1', depth: 'uchar', @@ -72,6 +80,10 @@ describe('AVIF', () => { const { size, ...metadata } = await sharp(data) .metadata(); assert.deepStrictEqual(metadata, { + autoOrient: { + height: 13, + width: 32 + }, channels: 3, compression: 'av1', depth: 'uchar', @@ -97,6 +109,10 @@ describe('AVIF', () => { const { size, ...metadata } = await sharp(data) .metadata(); assert.deepStrictEqual(metadata, { + autoOrient: { + height: 300, + width: 10 + }, channels: 4, compression: 'av1', depth: 'uchar', @@ -123,6 +139,10 @@ describe('AVIF', () => { const { size, ...metadata } = await sharp(data) .metadata(); assert.deepStrictEqual(metadata, { + autoOrient: { + height: 26, + width: 32 + }, channels: 3, compression: 'av1', depth: 'uchar', diff --git a/test/unit/composite.js b/test/unit/composite.js index 326ddb40..865ac598 100644 --- a/test/unit/composite.js +++ b/test/unit/composite.js @@ -138,6 +138,22 @@ describe('composite', () => { fixtures.assertMaxColourDistance(actual, expected); }); + it('autoOrient', async () => { + const data = await sharp({ + create: { + width: 600, height: 600, channels: 4, background: { ...red, alpha: 1 } + } + }) + .composite([{ + input: fixtures.inputJpgWithExif, + autoOrient: true + }]) + .jpeg() + .toBuffer(); + + await fixtures.assertSimilar(fixtures.expected('composite-autoOrient.jpg'), data); + }); + it('zero offset', done => { sharp(fixtures.inputJpg) .resize(80) diff --git a/test/unit/metadata.js b/test/unit/metadata.js index 7aa5c256..b5bb5e59 100644 --- a/test/unit/metadata.js +++ b/test/unit/metadata.js @@ -102,6 +102,8 @@ describe('Image metadata', function () { assert.strictEqual(false, metadata.hasProfile); assert.strictEqual(false, metadata.hasAlpha); assert.strictEqual(1, metadata.orientation); + assert.strictEqual(2464, metadata.autoOrient.width); + assert.strictEqual(3248, metadata.autoOrient.height); assert.strictEqual('undefined', typeof metadata.exif); assert.strictEqual('undefined', typeof metadata.icc); assert.strictEqual('inch', metadata.resolutionUnit); @@ -148,6 +150,8 @@ describe('Image metadata', function () { assert.strictEqual(false, metadata.hasProfile); assert.strictEqual(false, metadata.hasAlpha); assert.strictEqual('undefined', typeof metadata.orientation); + assert.strictEqual(2809, metadata.autoOrient.width); + assert.strictEqual(2074, metadata.autoOrient.height); assert.strictEqual('undefined', typeof metadata.exif); assert.strictEqual('undefined', typeof metadata.icc); done(); @@ -218,7 +222,11 @@ describe('Image metadata', function () { isPalette: false, isProgressive: false, space: 'b-w', - width: 32 + width: 32, + autoOrient: { + width: 32, + height: 32 + } }); }); @@ -239,7 +247,11 @@ describe('Image metadata', function () { isPalette: false, isProgressive: false, space: 'grey16', - width: 32 + width: 32, + autoOrient: { + width: 32, + height: 32 + } }); }); @@ -601,6 +613,10 @@ describe('Image metadata', function () { if (err) throw err; assert.strictEqual(true, metadata.hasProfile); assert.strictEqual(8, metadata.orientation); + assert.strictEqual(320, metadata.width); + assert.strictEqual(240, metadata.height); + assert.strictEqual(240, metadata.autoOrient.width); + assert.strictEqual(320, metadata.autoOrient.height); assert.strictEqual('object', typeof metadata.exif); assert.strictEqual(true, metadata.exif instanceof Buffer); // EXIF @@ -926,7 +942,11 @@ describe('Image metadata', function () { pagePrimary: 0, compression: 'av1', hasProfile: false, - hasAlpha: false + hasAlpha: false, + autoOrient: { + width: 2048, + height: 858 + } }); }); diff --git a/test/unit/png.js b/test/unit/png.js index 8460ad40..84a74cbc 100644 --- a/test/unit/png.js +++ b/test/unit/png.js @@ -138,6 +138,10 @@ describe('PNG', function () { const { size, ...metadata } = await sharp(data).metadata(); assert.deepStrictEqual(metadata, { + autoOrient: { + height: 68, + width: 68 + }, format: 'png', width: 68, height: 68, diff --git a/test/unit/rotate.js b/test/unit/rotate.js index 6fa77f4c..7c317550 100644 --- a/test/unit/rotate.js +++ b/test/unit/rotate.js @@ -9,46 +9,110 @@ const sharp = require('../../'); const fixtures = require('../fixtures'); describe('Rotation', function () { - ['Landscape', 'Portrait'].forEach(function (orientation) { - [1, 2, 3, 4, 5, 6, 7, 8].forEach(function (exifTag) { - const input = fixtures[`inputJpgWith${orientation}Exif${exifTag}`]; - const expectedOutput = fixtures.expected(`${orientation}_${exifTag}-out.jpg`); - it(`Auto-rotate ${orientation} image with EXIF Orientation ${exifTag}`, function (done) { - const [expectedWidth, expectedHeight] = orientation === 'Landscape' ? [600, 450] : [450, 600]; - sharp(input) - .rotate() - .toBuffer(function (err, data, info) { - if (err) throw err; - assert.strictEqual(info.width, expectedWidth); - assert.strictEqual(info.height, expectedHeight); - fixtures.assertSimilar(expectedOutput, data, done); + ['autoOrient', 'constructor'].forEach(function (rotateMethod) { + describe(`Auto orientation via ${rotateMethod}:`, () => { + const options = rotateMethod === 'constructor' ? { autoOrient: true } : {}; + + ['Landscape', 'Portrait'].forEach(function (orientation) { + [1, 2, 3, 4, 5, 6, 7, 8].forEach(function (exifTag) { + const input = fixtures[`inputJpgWith${orientation}Exif${exifTag}`]; + const expectedOutput = fixtures.expected(`${orientation}_${exifTag}-out.jpg`); + it(`${orientation} image with EXIF Orientation ${exifTag}: Auto-rotate`, function (done) { + const [expectedWidth, expectedHeight] = orientation === 'Landscape' ? [600, 450] : [450, 600]; + + const img = sharp(input, options); + rotateMethod === 'autoOrient' && img.autoOrient(); + + img.toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(info.width, expectedWidth); + assert.strictEqual(info.height, expectedHeight); + fixtures.assertSimilar(expectedOutput, data, done); + }); }); - }); - it(`Auto-rotate then resize ${orientation} image with EXIF Orientation ${exifTag}`, function (done) { - const [expectedWidth, expectedHeight] = orientation === 'Landscape' ? [320, 240] : [320, 427]; - sharp(input) - .rotate() - .resize({ width: 320 }) - .toBuffer(function (err, data, info) { - if (err) throw err; - assert.strictEqual(info.width, expectedWidth); - assert.strictEqual(info.height, expectedHeight); - fixtures.assertSimilar(expectedOutput, data, done); + + it(`${orientation} image with EXIF Orientation ${exifTag}: Auto-rotate then resize`, function (done) { + const [expectedWidth, expectedHeight] = orientation === 'Landscape' ? [320, 240] : [320, 427]; + + const img = sharp(input, options); + rotateMethod === 'autoOrient' && img.autoOrient(); + + img.resize({ width: 320 }) + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(info.width, expectedWidth); + assert.strictEqual(info.height, expectedHeight); + fixtures.assertSimilar(expectedOutput, data, done); + }); }); - }); - it(`Resize then auto-rotate ${orientation} image with EXIF Orientation ${exifTag}`, function (done) { - const [expectedWidth, expectedHeight] = orientation === 'Landscape' - ? (exifTag < 5) ? [320, 240] : [320, 240] - : [320, 427]; - sharp(input) - .resize({ width: 320 }) - .rotate() - .toBuffer(function (err, data, info) { - if (err) throw err; - assert.strictEqual(info.width, expectedWidth); - assert.strictEqual(info.height, expectedHeight); - fixtures.assertSimilar(expectedOutput, data, done); + + if (rotateMethod !== 'constructor') { + it(`${orientation} image with EXIF Orientation ${exifTag}: Resize then auto-rotate`, function (done) { + const [expectedWidth, expectedHeight] = orientation === 'Landscape' + ? (exifTag < 5) ? [320, 240] : [320, 240] + : [320, 427]; + + const img = sharp(input, options) + .resize({ width: 320 }); + + rotateMethod === 'autoOrient' && img.autoOrient(); + img.toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(info.width, expectedWidth); + assert.strictEqual(info.height, expectedHeight); + fixtures.assertSimilar(expectedOutput, data, done); + }); + }); + } + + [true, false].forEach((doResize) => { + [90, 180, 270, 45].forEach(function (angle) { + const [inputWidth, inputHeight] = orientation === 'Landscape' ? [600, 450] : [450, 600]; + const expectedOutput = fixtures.expected(`${orientation}_${exifTag}_rotate${angle}-out.jpg`); + it(`${orientation} image with EXIF Orientation ${exifTag}: Auto-rotate then rotate ${angle} ${doResize ? 'and resize' : ''}`, function (done) { + const [width, height] = (angle === 45 ? [742, 742] : [inputWidth, inputHeight]).map((x) => doResize ? Math.floor(x / 1.875) : x); + const [expectedWidth, expectedHeight] = angle % 180 === 0 ? [width, height] : [height, width]; + + const img = sharp(input, options); + rotateMethod === 'autoOrient' && img.autoOrient(); + + img.rotate(angle); + doResize && img.resize(expectedWidth); + + img.toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(info.width, expectedWidth); + assert.strictEqual(info.height, expectedHeight); + fixtures.assertSimilar(expectedOutput, data, done); + }); + }); + }); + + [[true, true], [true, false], [false, true]].forEach(function ([flip, flop]) { + const [inputWidth, inputHeight] = orientation === 'Landscape' ? [600, 450] : [450, 600]; + const flipFlopFileName = [flip && 'flip', flop && 'flop'].filter(Boolean).join('_'); + const flipFlopTestName = [flip && 'flip', flop && 'flop'].filter(Boolean).join(' & '); + it(`${orientation} image with EXIF Orientation ${exifTag}: Auto-rotate then ${flipFlopTestName} ${doResize ? 'and resize' : ''}`, function (done) { + const expectedOutput = fixtures.expected(`${orientation}_${exifTag}_${flipFlopFileName}-out.jpg`); + + const img = sharp(input, options); + + rotateMethod === 'autoOrient' && img.autoOrient(); + + flip && img.flip(); + flop && img.flop(); + doResize && img.resize(orientation === 'Landscape' ? 320 : 240); + + img.toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(info.width, inputWidth / (doResize ? 1.875 : 1)); + assert.strictEqual(info.height, inputHeight / (doResize ? 1.875 : 1)); + fixtures.assertSimilar(expectedOutput, data, done); + }); + }); + }); }); + }); }); }); }); @@ -372,12 +436,36 @@ describe('Rotation', function () { let warningMessage = ''; const s = sharp(); s.on('warning', function (msg) { warningMessage = msg; }); - s.rotate(); + s.rotate(90); assert.strictEqual(warningMessage, ''); - s.rotate(); + s.rotate(180); assert.strictEqual(warningMessage, 'ignoring previous rotate options'); }); + it('Multiple rotate: last one wins (cardinal)', function (done) { + sharp(fixtures.inputJpg) + .rotate(45) + .rotate(90) + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(2225, info.width); + assert.strictEqual(2725, info.height); + done(); + }); + }); + + it('Multiple rotate: last one wins (non cardinal)', function (done) { + sharp(fixtures.inputJpg) + .rotate(90) + .rotate(45) + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(3500, info.width); + assert.strictEqual(3500, info.height); + done(); + }); + }); + it('Flip - vertical', function (done) { sharp(fixtures.inputJpg) .resize(320) @@ -546,4 +634,11 @@ describe('Rotation', function () { assert.strictEqual(width, 3); assert.strictEqual(height, 6); }); + + it('Invalid autoOrient throws', () => + assert.throws( + () => sharp({ autoOrient: 'fail' }), + /Expected boolean for autoOrient but received fail of type string/ + ) + ); });