diff --git a/docs/api-operation.md b/docs/api-operation.md index 5a6d26e8..6efae330 100644 --- a/docs/api-operation.md +++ b/docs/api-operation.md @@ -65,6 +65,61 @@ The use of `flop` implies the removal of the EXIF `Orientation` tag, if any. Returns **Sharp** +## affine + +Perform an affine transform on an image. This operation will always occur after resizing, extraction and rotation, if any. + +You must provide an array of length 4 or a 2x2 affine transformation matrix. +By default, new pixels are filled with a black background. You can provide a background color with the `background` option. +A particular interpolator may also be specified. Set the `interpolator` option to an attribute of the `sharp.interpolator` Object e.g. `sharp.interpolator.nohalo`. + +In the case of a 2x2 matrix, the transform is: + +- X = `matrix[0, 0]` \* (x + `idx`) + `matrix[0, 1]` \* (y + `idy`) + `odx` +- Y = `matrix[1, 0]` \* (x + `idx`) + `matrix[1, 1]` \* (y + `idy`) + `ody` + +where: + +- x and y are the coordinates in input image. +- X and Y are the coordinates in output image. +- (0,0) is the upper left corner. + +### Parameters + +- `matrix` **([Array][7]<[Array][7]<[number][1]>> | [Array][7]<[number][1]>)** affine transformation matrix +- `options` **[Object][2]?** if present, is an Object with optional attributes. + - `options.background` **([String][3] \| [Object][2])** parsed by the [color][4] module to extract values for red, green, blue and alpha. (optional, default `"#000000"`) + - `options.idx` **[Number][1]** input horizontal offset (optional, default `0`) + - `options.idy` **[Number][1]** input vertical offset (optional, default `0`) + - `options.odx` **[Number][1]** output horizontal offset (optional, default `0`) + - `options.ody` **[Number][1]** output vertical offset (optional, default `0`) + - `options.interpolator` **[String][3]** interpolator (optional, default `sharp.interpolators.bicubic`) + +### Examples + +```javascript +const pipeline = sharp() + .affine([[1, 0.3], [0.1, 0.7]], { + background: 'white', + interpolate: sharp.interpolators.nohalo + }) + .toBuffer((err, outputBuffer, info) => { + // outputBuffer contains the transformed image + // info.width and info.height contain the new dimensions + }); + +inputStream + .pipe(pipeline); +``` + +- Throws **[Error][5]** Invalid parameters + +Returns **Sharp** + +**Meta** + +- **since**: 0.27.0 + ## sharpen Sharpen the image. diff --git a/docs/api-utility.md b/docs/api-utility.md index 9b450c18..01cfdb6b 100644 --- a/docs/api-utility.md +++ b/docs/api-utility.md @@ -12,6 +12,36 @@ console.log(sharp.format); Returns **[Object][1]** +## interpolators + +An Object containing the available interpolators and their proper values + +Type: [string][2] + +### nearest + +[Nearest neighbour interpolation][3]. Suitable for image enlargement only. + +### bilinear + +[Bilinear interpolation][4]. Faster than bicubic but with less smooth results. + +### bicubic + +[Bicubic interpolation][5] (the default). + +### locallyBoundedBicubic + +[LBB interpolation][6]. Prevents some "[acutance][7]" but typically reduces performance by a factor of 2. + +### nohalo + +[Nohalo interpolation][8]. Prevents acutance but typically reduces performance by a factor of 3. + +### vertexSplitQuadraticBasisSpline + +[VSQBS interpolation][9]. Prevents "staircasing" when enlarging. + ## versions An Object containing the version numbers of libvips and its dependencies. @@ -31,10 +61,10 @@ useful for determining how much working memory is required for a particular task ### Parameters -- `options` **([Object][1] \| [boolean][2])** Object with the following attributes, or boolean where true uses default cache settings and false removes all caching (optional, default `true`) - - `options.memory` **[number][3]** is the maximum memory in MB to use for this cache (optional, default `50`) - - `options.files` **[number][3]** is the maximum number of files to hold open (optional, default `20`) - - `options.items` **[number][3]** is the maximum number of operations to cache (optional, default `100`) +- `options` **([Object][1] \| [boolean][10])** Object with the following attributes, or boolean where true uses default cache settings and false removes all caching (optional, default `true`) + - `options.memory` **[number][11]** is the maximum memory in MB to use for this cache (optional, default `50`) + - `options.files` **[number][11]** is the maximum number of files to hold open (optional, default `20`) + - `options.items` **[number][11]** is the maximum number of operations to cache (optional, default `100`) ### Examples @@ -64,7 +94,7 @@ This method always returns the current concurrency. ### Parameters -- `concurrency` **[number][3]?** +- `concurrency` **[number][11]?** ### Examples @@ -74,7 +104,7 @@ sharp.concurrency(2); // 2 sharp.concurrency(0); // 4 ``` -Returns **[number][3]** concurrency +Returns **[number][11]** concurrency ## queue @@ -116,7 +146,7 @@ by taking advantage of the SIMD vector unit of the CPU, e.g. Intel SSE and ARM N ### Parameters -- `simd` **[boolean][2]** (optional, default `true`) +- `simd` **[boolean][10]** (optional, default `true`) ### Examples @@ -130,10 +160,26 @@ const simd = sharp.simd(false); // prevent libvips from using liborc at runtime ``` -Returns **[boolean][2]** +Returns **[boolean][10]** [1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object -[2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean +[2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String -[3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number +[3]: http://en.wikipedia.org/wiki/Nearest-neighbor_interpolation + +[4]: http://en.wikipedia.org/wiki/Bilinear_interpolation + +[5]: http://en.wikipedia.org/wiki/Bicubic_interpolation + +[6]: https://github.com/jcupitt/libvips/blob/master/libvips/resample/lbb.cpp#L100 + +[7]: http://en.wikipedia.org/wiki/Acutance + +[8]: http://eprints.soton.ac.uk/268086/ + +[9]: https://github.com/jcupitt/libvips/blob/master/libvips/resample/vsqbs.cpp#L48 + +[10]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean + +[11]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number diff --git a/docs/search-index.json b/docs/search-index.json index 44678a15..deaea0d4 100644 --- a/docs/search-index.json +++ b/docs/search-index.json @@ -1 +1 @@ -[{"t":"Prerequisites","d":"Node.js v10.16.0","k":"prerequisites node","l":"/install#prerequisites"},{"t":"Prebuilt binaries","d":"Ready-compiled sharp and libvips binaries are provided for use with Node.js v10.16.0 on the most common platforms macOS x64 10.13 Linux x64","k":"prebuilt binaries ready compiled sharp libvips binaries provided node most common platforms macos linux","l":"/install#prebuilt-binaries"},{"t":"Common problems","d":"The architecture and platform of Node.js used for npm install must be the same as the architecture and platform of Node.js used at runtime.","k":"common problems architecture platform node used npm install must same architecture platform node used runtime","l":"/install#common-problems"},{"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 c","k":"custom libvips custom globally installed version libvips instead provided binaries make sure least version listed under","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 set the SHARP_IGNORE_GLOBAL_LIBVI","k":"building source module compiled source npm install time globally installed libvips detected sharpignoregloballibvi","l":"/install#building-from-source"},{"t":"Custom prebuilt binaries","d":"This is an advanced approach that most people will not require. To install the prebuilt sharp binaries from a custom URL, set the sharp_bina","k":"custom prebuilt binaries advanced approach most people require install prebuilt sharp binaries custom url sharpbina","l":"/install#custom-prebuilt-binaries"},{"t":"Chinese mirror","d":"Alibaba provide a mirror site based in China containing binaries for both sharp and libvips. To use this either set the following configurat","k":"chinese mirror alibaba provide mirror site based china containing binaries both sharp libvips following configurat","l":"/install#chinese-mirror"},{"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 cl","k":"freebsd vips package must installed before npm install run pkg install pkgconf vips usr ports graphics vips make install","l":"/install#freebsd"},{"t":"Heroku","d":"Add the jemalloc buildpack to reduce the effects of memory fragmentation. Set NODE_MODULES_CACHE","k":"heroku add jemalloc buildpack reduce effects memory fragmentation nodemodulescache","l":"/install#heroku"},{"t":"AWS Lambda","d":"Set the Lambda runtime to nodejs12.x. The binaries in the node_modules directory of the deployment package must be for the Linux x64 platfor","k":"aws lambda lambda runtime nodejs binaries nodemodules directory deployment package must linux platfor","l":"/install#aws-lambda"},{"t":"Electron","d":"Electron provides versions of the V8 JavaScript engine that are incompatible with Node.js. To ensure the correct binaries are used, run the","k":"electron electron provides versions javascript engine incompatible node ensure correct binaries used run","l":"/install#electron"},{"t":"Worker threads","d":"The main thread must call requiresharp before worker threads are created to ensure shared libraries remain loaded in memory until after all","k":"worker threads main thread must call requiresharp before worker threads created ensure shared libraries remain loaded memory until after","l":"/install#worker-threads"},{"t":"Sharp","d":"Constructor factory to create an instance of sharp, to which further methods are chained.","k":"sharp constructor factory create instance sharp further methods chained","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 mu","k":"clone take snapshot sharp instance returning new instance cloned instances inherit input parent instance allows","l":"/api-constructor#clone"},{"t":"metadata","d":"Fast access to uncached image metadata without decoding any compressed image data. A Promise is returned when callback is not provided.","k":"metadata fast access uncached image metadata without decoding compressed image data promise returned callback provided","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 image statistics every channel image promise returned callback provided","l":"/api-input#stats"},{"t":"toFile","d":"Write output image data to a file.","k":"tofile write output image data file","l":"/api-output#tofile"},{"t":"toBuffer","d":"Write output to a Buffer. JPEG, PNG, WebP, TIFF and RAW output are supported.","k":"tobuffer write output buffer jpeg png webp tiff raw output supported","l":"/api-output#tobuffer"},{"t":"withMetadata","d":"Include all metadata EXIF, XMP, IPTC from the input image in the output image. This will also convert to and add a web-friendly sRGB ICC pro","k":"withmetadata include metadata exif xmp iptc input image output image also convert add web friendly srgb icc pro","l":"/api-output#withmetadata"},{"t":"toFormat","d":"Force output to a given format.","k":"toformat force output given format","l":"/api-output#toformat"},{"t":"jpeg","d":"Use these JPEG options for output image.","k":"jpeg jpeg options output image","l":"/api-output#jpeg"},{"t":"png","d":"Use these PNG options for output image.","k":"png png options output image","l":"/api-output#png"},{"t":"webp","d":"Use these WebP options for output image.","k":"webp webp options output image","l":"/api-output#webp"},{"t":"gif","d":"Use these GIF options for output image.","k":"gif gif options output image","l":"/api-output#gif"},{"t":"tiff","d":"Use these TIFF options for output image.","k":"tiff tiff options output image","l":"/api-output#tiff"},{"t":"heif","d":"Use these HEIF options for output image.","k":"heif heif options output image","l":"/api-output#heif"},{"t":"raw","d":"Force output to be raw, uncompressed, 8-bit unsigned integer unit8 pixel data. Pixel ordering is left-to-right, top-to-bottom, without paddi","k":"raw force output raw uncompressed bit unsigned integer unit pixel data pixel ordering left right top bottom without paddi","l":"/api-output#raw"},{"t":"tile","d":"Use tile-based deep zoom image pyramid output. Set the format and options for tile images via the toFormat, jpeg, png or webp functions. Use","k":"tile tile based deep zoom image pyramid output format options tile images via toformat jpeg png webp functions","l":"/api-output#tile"},{"t":"resize","d":"Resize image to width, height or width x height.","k":"resize resize image width height width height","l":"/api-resize#resize"},{"t":"extend","d":"Extends/pads the edges of the image with the provided background colour. This operation will always occur after resizing and extraction, if","k":"extend extends pads edges image provided background colour operation occur after resizing extraction","l":"/api-resize#extend"},{"t":"extract","d":"Extract/crop a region of the image.","k":"extract extract crop region image","l":"/api-resize#extract"},{"t":"trim","d":"Trim boring pixels from all edges that contain values similar to the top-left pixel. Images consisting entirely of a single colour will calc","k":"trim trim boring pixels edges contain values similar top left pixel images consisting entirely single colour calc","l":"/api-resize#trim"},{"t":"composite","d":"Composite images over the processed resized, extracted etc. image.","k":"composite composite images processed resized extracted image","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 rotate output image explicit angle auto orient based exif orientation tag","l":"/api-operation#rotate"},{"t":"flip","d":"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 Orienta","k":"flip flip image vertical axis occurs after rotation flip implies removal exif orienta","l":"/api-operation#flip"},{"t":"flop","d":"Flop the image about the horizontal X axis. This always occurs after rotation, if any. The use of flop implies the removal of the EXIF Orien","k":"flop flop image horizontal axis occurs after rotation flop implies removal exif orien","l":"/api-operation#flop"},{"t":"sharpen","d":"Sharpen the image. When used without parameters, performs a fast, mild sharpen of the output image. When a sigma is provided, performs a slo","k":"sharpen sharpen image used without parameters performs fast mild sharpen output image sigma provided performs slo","l":"/api-operation#sharpen"},{"t":"median","d":"Apply median filter. When used without parameters the default window is 3x3.","k":"median apply median filter used without parameters default window","l":"/api-operation#median"},{"t":"blur","d":"Blur the image. When used without parameters, performs a fast, mild blur of the output image. When a sigma is provided, performs a slower, m","k":"blur blur image used without parameters performs fast mild blur output image sigma provided performs slower","l":"/api-operation#blur"},{"t":"flatten","d":"Merge alpha transparency channel, if any, with a background.","k":"flatten merge alpha transparency channel background","l":"/api-operation#flatten"},{"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","k":"gamma apply gamma correction reducing encoding darken pre resize factor gamma then increasing encoding brighten post resize","l":"/api-operation#gamma"},{"t":"negate","d":"Produce the negative of the image.","k":"negate produce negative image","l":"/api-operation#negate"},{"t":"normalise","d":"Enhance output image contrast by stretching its luminance to cover the full dynamic range.","k":"normalise enhance output image contrast stretching luminance cover full dynamic range","l":"/api-operation#normalise"},{"t":"normalize","d":"Alternative spelling of normalise.","k":"normalize alternative spelling normalise","l":"/api-operation#normalize"},{"t":"convolve","d":"Convolve the image with the specified kernel.","k":"convolve convolve image specified kernel","l":"/api-operation#convolve"},{"t":"threshold","d":"Any pixel value greather than or equal to the threshold value will be set to 255, otherwise it will be set to 0.","k":"threshold pixel value greather than equal threshold value otherwise","l":"/api-operation#threshold"},{"t":"boolean","d":"Perform a bitwise boolean operation with operand image.","k":"boolean perform bitwise boolean operation operand image","l":"/api-operation#boolean"},{"t":"linear","d":"Apply the linear formula a input b to the image levels adjustment","k":"linear apply linear formula input image levels adjustment","l":"/api-operation#linear"},{"t":"recomb","d":"Recomb the image with the specified matrix.","k":"recomb recomb image specified matrix","l":"/api-operation#recomb"},{"t":"modulate","d":"Transforms the image using brightness, saturation and hue rotation.","k":"modulate transforms image brightness saturation hue rotation","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 image alpha channel","l":"/api-channel#removealpha"},{"t":"ensureAlpha","d":"Ensure alpha channel, if missing. The added alpha channel will be fully opaque. This is a no-op if the image already has an alpha channel.","k":"ensurealpha ensure alpha channel missing added alpha channel fully opaque image alpha channel","l":"/api-channel#ensurealpha"},{"t":"extractChannel","d":"Extract a single channel from a multi-channel image.","k":"extractchannel extract single channel multi channel image","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 defa","k":"joinchannel join one channels image meaning added channels depends output colourspace tocolourspace defa","l":"/api-channel#joinchannel"},{"t":"bandbool","d":"Perform a bitwise boolean operation on all input image channels bands to produce a single channel output image.","k":"bandbool perform bitwise boolean operation input image channels bands produce single channel output image","l":"/api-channel#bandbool"},{"t":"tint","d":"Tint the image using the provided chroma while preserving the image luminance. An alpha channel may be present and will be unchanged by the","k":"tint tint image provided chroma while preserving image luminance alpha channel present unchanged","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,","k":"greyscale convert bit greyscale shades grey linear operation input image non linear colour space such srgb","l":"/api-colour#greyscale"},{"t":"grayscale","d":"Alternative spelling of greyscale.","k":"grayscale alternative spelling greyscale","l":"/api-colour#grayscale"},{"t":"toColourspace","d":"Set the output colourspace. By default output image will be web-friendly sRGB, with additional channels interpreted as alpha channels.","k":"tocolourspace output colourspace default output image web friendly srgb additional channels interpreted alpha channels","l":"/api-colour#tocolourspace"},{"t":"toColorspace","d":"Alternative spelling of toColourspace.","k":"tocolorspace alternative spelling tocolourspace","l":"/api-colour#tocolorspace"},{"t":"format","d":"An Object containing nested boolean values representing the available input and output formats/methods.","k":"format object containing nested boolean values representing available input output formats methods","l":"/api-utility#format"},{"t":"versions","d":"An Object containing the version numbers of libvips and its dependencies.","k":"versions object containing version numbers libvips dependencies","l":"/api-utility#versions"},{"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 ch","k":"cache options provided limits libvips operation cache existing entries cache trimmed after","l":"/api-utility#cache"},{"t":"concurrency","d":"Gets or, when a concurrency is provided, sets the number of threads _libvips_ should create to process each image. The default value is the","k":"concurrency concurrency provided number threads libvips create process image default value","l":"/api-utility#concurrency"},{"t":"queue","d":"An EventEmitter that emits a change event when a task is either","k":"queue eventemitter emits change event task","l":"/api-utility#queue"},{"t":"counters","d":"Provides access to internal task counters.","k":"counters provides access internal task counters","l":"/api-utility#counters"},{"t":"simd","d":"Get and set use of SIMD vector unit instructions. Requires libvips to have been compiled with liborc support.","k":"simd simd vector unit instructions requires libvips compiled liborc support","l":"/api-utility#simd"}] \ No newline at end of file +[{"t":"Prerequisites","d":"Node.js v10.16.0","k":"prerequisites node","l":"/install#prerequisites"},{"t":"Prebuilt binaries","d":"Ready-compiled sharp and libvips binaries are provided for use with Node.js v10.16.0 on the most common platforms macOS x64 10.13 Linux x64","k":"prebuilt binaries ready compiled sharp libvips binaries provided node most common platforms macos linux","l":"/install#prebuilt-binaries"},{"t":"Common problems","d":"The architecture and platform of Node.js used for npm install must be the same as the architecture and platform of Node.js used at runtime.","k":"common problems architecture platform node used npm install must same architecture platform node used runtime","l":"/install#common-problems"},{"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 c","k":"custom libvips custom globally installed version libvips instead provided binaries make sure least version listed under","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 set the SHARP_IGNORE_GLOBAL_LIBVI","k":"building source module compiled source npm install time globally installed libvips detected sharpignoregloballibvi","l":"/install#building-from-source"},{"t":"Custom prebuilt binaries","d":"This is an advanced approach that most people will not require. To install the prebuilt sharp binaries from a custom URL, set the sharp_bina","k":"custom prebuilt binaries advanced approach most people require install prebuilt sharp binaries custom url sharpbina","l":"/install#custom-prebuilt-binaries"},{"t":"Chinese mirror","d":"Alibaba provide a mirror site based in China containing binaries for both sharp and libvips. To use this either set the following configurat","k":"chinese mirror alibaba provide mirror site based china containing binaries both sharp libvips following configurat","l":"/install#chinese-mirror"},{"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 cl","k":"freebsd vips package must installed before npm install run pkg install pkgconf vips usr ports graphics vips make install","l":"/install#freebsd"},{"t":"Heroku","d":"Add the jemalloc buildpack to reduce the effects of memory fragmentation. Set NODE_MODULES_CACHE","k":"heroku add jemalloc buildpack reduce effects memory fragmentation nodemodulescache","l":"/install#heroku"},{"t":"AWS Lambda","d":"Set the Lambda runtime to nodejs12.x. The binaries in the node_modules directory of the deployment package must be for the Linux x64 platfor","k":"aws lambda lambda runtime nodejs binaries nodemodules directory deployment package must linux platfor","l":"/install#aws-lambda"},{"t":"Electron","d":"Electron provides versions of the V8 JavaScript engine that are incompatible with Node.js. To ensure the correct binaries are used, run the","k":"electron electron provides versions javascript engine incompatible node ensure correct binaries used run","l":"/install#electron"},{"t":"Worker threads","d":"The main thread must call requiresharp before worker threads are created to ensure shared libraries remain loaded in memory until after all","k":"worker threads main thread must call requiresharp before worker threads created ensure shared libraries remain loaded memory until after","l":"/install#worker-threads"},{"t":"Sharp","d":"Constructor factory to create an instance of sharp, to which further methods are chained.","k":"sharp constructor factory create instance sharp further methods chained","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 mu","k":"clone take snapshot sharp instance returning new instance cloned instances inherit input parent instance allows","l":"/api-constructor#clone"},{"t":"metadata","d":"Fast access to uncached image metadata without decoding any compressed image data. A Promise is returned when callback is not provided.","k":"metadata fast access uncached image metadata without decoding compressed image data promise returned callback provided","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 image statistics every channel image promise returned callback provided","l":"/api-input#stats"},{"t":"toFile","d":"Write output image data to a file.","k":"tofile write output image data file","l":"/api-output#tofile"},{"t":"toBuffer","d":"Write output to a Buffer. JPEG, PNG, WebP, TIFF and RAW output are supported.","k":"tobuffer write output buffer jpeg png webp tiff raw output supported","l":"/api-output#tobuffer"},{"t":"withMetadata","d":"Include all metadata EXIF, XMP, IPTC from the input image in the output image. This will also convert to and add a web-friendly sRGB ICC pro","k":"withmetadata include metadata exif xmp iptc input image output image also convert add web friendly srgb icc pro","l":"/api-output#withmetadata"},{"t":"toFormat","d":"Force output to a given format.","k":"toformat force output given format","l":"/api-output#toformat"},{"t":"jpeg","d":"Use these JPEG options for output image.","k":"jpeg jpeg options output image","l":"/api-output#jpeg"},{"t":"png","d":"Use these PNG options for output image.","k":"png png options output image","l":"/api-output#png"},{"t":"webp","d":"Use these WebP options for output image.","k":"webp webp options output image","l":"/api-output#webp"},{"t":"gif","d":"Use these GIF options for output image.","k":"gif gif options output image","l":"/api-output#gif"},{"t":"tiff","d":"Use these TIFF options for output image.","k":"tiff tiff options output image","l":"/api-output#tiff"},{"t":"heif","d":"Use these HEIF options for output image.","k":"heif heif options output image","l":"/api-output#heif"},{"t":"raw","d":"Force output to be raw, uncompressed, 8-bit unsigned integer unit8 pixel data. Pixel ordering is left-to-right, top-to-bottom, without paddi","k":"raw force output raw uncompressed bit unsigned integer unit pixel data pixel ordering left right top bottom without paddi","l":"/api-output#raw"},{"t":"tile","d":"Use tile-based deep zoom image pyramid output. Set the format and options for tile images via the toFormat, jpeg, png or webp functions. Use","k":"tile tile based deep zoom image pyramid output format options tile images via toformat jpeg png webp functions","l":"/api-output#tile"},{"t":"resize","d":"Resize image to width, height or width x height.","k":"resize resize image width height width height","l":"/api-resize#resize"},{"t":"extend","d":"Extends/pads the edges of the image with the provided background colour. This operation will always occur after resizing and extraction, if","k":"extend extends pads edges image provided background colour operation occur after resizing extraction","l":"/api-resize#extend"},{"t":"extract","d":"Extract/crop a region of the image.","k":"extract extract crop region image","l":"/api-resize#extract"},{"t":"trim","d":"Trim boring pixels from all edges that contain values similar to the top-left pixel. Images consisting entirely of a single colour will calc","k":"trim trim boring pixels edges contain values similar top left pixel images consisting entirely single colour calc","l":"/api-resize#trim"},{"t":"composite","d":"Composite images over the processed resized, extracted etc. image.","k":"composite composite images processed resized extracted image","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 rotate output image explicit angle auto orient based exif orientation tag","l":"/api-operation#rotate"},{"t":"flip","d":"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 Orienta","k":"flip flip image vertical axis occurs after rotation flip implies removal exif orienta","l":"/api-operation#flip"},{"t":"flop","d":"Flop the image about the horizontal X axis. This always occurs after rotation, if any. The use of flop implies the removal of the EXIF Orien","k":"flop flop image horizontal axis occurs after rotation flop implies removal exif orien","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 perform affine transform image operation occur after resizing extraction rotation","l":"/api-operation#affine"},{"t":"sharpen","d":"Sharpen the image. When used without parameters, performs a fast, mild sharpen of the output image. When a sigma is provided, performs a slo","k":"sharpen sharpen image used without parameters performs fast mild sharpen output image sigma provided performs slo","l":"/api-operation#sharpen"},{"t":"median","d":"Apply median filter. When used without parameters the default window is 3x3.","k":"median apply median filter used without parameters default window","l":"/api-operation#median"},{"t":"blur","d":"Blur the image. When used without parameters, performs a fast, mild blur of the output image. When a sigma is provided, performs a slower, m","k":"blur blur image used without parameters performs fast mild blur output image sigma provided performs slower","l":"/api-operation#blur"},{"t":"flatten","d":"Merge alpha transparency channel, if any, with a background.","k":"flatten merge alpha transparency channel background","l":"/api-operation#flatten"},{"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","k":"gamma apply gamma correction reducing encoding darken pre resize factor gamma then increasing encoding brighten post resize","l":"/api-operation#gamma"},{"t":"negate","d":"Produce the negative of the image.","k":"negate produce negative image","l":"/api-operation#negate"},{"t":"normalise","d":"Enhance output image contrast by stretching its luminance to cover the full dynamic range.","k":"normalise enhance output image contrast stretching luminance cover full dynamic range","l":"/api-operation#normalise"},{"t":"normalize","d":"Alternative spelling of normalise.","k":"normalize alternative spelling normalise","l":"/api-operation#normalize"},{"t":"convolve","d":"Convolve the image with the specified kernel.","k":"convolve convolve image specified kernel","l":"/api-operation#convolve"},{"t":"threshold","d":"Any pixel value greather than or equal to the threshold value will be set to 255, otherwise it will be set to 0.","k":"threshold pixel value greather than equal threshold value otherwise","l":"/api-operation#threshold"},{"t":"boolean","d":"Perform a bitwise boolean operation with operand image.","k":"boolean perform bitwise boolean operation operand image","l":"/api-operation#boolean"},{"t":"linear","d":"Apply the linear formula a input b to the image levels adjustment","k":"linear apply linear formula input image levels adjustment","l":"/api-operation#linear"},{"t":"recomb","d":"Recomb the image with the specified matrix.","k":"recomb recomb image specified matrix","l":"/api-operation#recomb"},{"t":"modulate","d":"Transforms the image using brightness, saturation and hue rotation.","k":"modulate transforms image brightness saturation hue rotation","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 image alpha channel","l":"/api-channel#removealpha"},{"t":"ensureAlpha","d":"Ensure alpha channel, if missing. The added alpha channel will be fully opaque. This is a no-op if the image already has an alpha channel.","k":"ensurealpha ensure alpha channel missing added alpha channel fully opaque image alpha channel","l":"/api-channel#ensurealpha"},{"t":"extractChannel","d":"Extract a single channel from a multi-channel image.","k":"extractchannel extract single channel multi channel image","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 defa","k":"joinchannel join one channels image meaning added channels depends output colourspace tocolourspace defa","l":"/api-channel#joinchannel"},{"t":"bandbool","d":"Perform a bitwise boolean operation on all input image channels bands to produce a single channel output image.","k":"bandbool perform bitwise boolean operation input image channels bands produce single channel output image","l":"/api-channel#bandbool"},{"t":"tint","d":"Tint the image using the provided chroma while preserving the image luminance. An alpha channel may be present and will be unchanged by the","k":"tint tint image provided chroma while preserving image luminance alpha channel present unchanged","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,","k":"greyscale convert bit greyscale shades grey linear operation input image non linear colour space such srgb","l":"/api-colour#greyscale"},{"t":"grayscale","d":"Alternative spelling of greyscale.","k":"grayscale alternative spelling greyscale","l":"/api-colour#grayscale"},{"t":"toColourspace","d":"Set the output colourspace. By default output image will be web-friendly sRGB, with additional channels interpreted as alpha channels.","k":"tocolourspace output colourspace default output image web friendly srgb additional channels interpreted alpha channels","l":"/api-colour#tocolourspace"},{"t":"toColorspace","d":"Alternative spelling of toColourspace.","k":"tocolorspace alternative spelling tocolourspace","l":"/api-colour#tocolorspace"},{"t":"format","d":"An Object containing nested boolean values representing the available input and output formats/methods.","k":"format object containing nested boolean values representing available input output formats methods","l":"/api-utility#format"},{"t":"interpolators","d":"An Object containing the available interpolators and their proper values","k":"interpolators object containing available interpolators proper values","l":"/api-utility#interpolators"},{"t":"versions","d":"An Object containing the version numbers of libvips and its dependencies.","k":"versions object containing version numbers libvips dependencies","l":"/api-utility#versions"},{"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 ch","k":"cache options provided limits libvips operation cache existing entries cache trimmed after","l":"/api-utility#cache"},{"t":"concurrency","d":"Gets or, when a concurrency is provided, sets the number of threads _libvips_ should create to process each image. The default value is the","k":"concurrency concurrency provided number threads libvips create process image default value","l":"/api-utility#concurrency"},{"t":"queue","d":"An EventEmitter that emits a change event when a task is either","k":"queue eventemitter emits change event task","l":"/api-utility#queue"},{"t":"counters","d":"Provides access to internal task counters.","k":"counters provides access internal task counters","l":"/api-utility#counters"},{"t":"simd","d":"Get and set use of SIMD vector unit instructions. Requires libvips to have been compiled with liborc support.","k":"simd simd vector unit instructions requires libvips compiled liborc support","l":"/api-utility#simd"}] \ No newline at end of file diff --git a/lib/constructor.js b/lib/constructor.js index becc49b9..547fdef9 100644 --- a/lib/constructor.js +++ b/lib/constructor.js @@ -155,6 +155,13 @@ const Sharp = function (input, options) { extendRight: 0, extendBackground: [0, 0, 0, 255], withoutEnlargement: false, + affineMatrix: [], + affineBackground: [0, 0, 0, 255], + affineIdx: 0, + affineIdy: 0, + affineOdx: 0, + affineOdy: 0, + affineInterpolator: this.constructor.interpolators.bilinear, kernel: 'lanczos3', fastShrinkOnLoad: true, // operations diff --git a/lib/operation.js b/lib/operation.js index 43adc44e..1075c7f5 100644 --- a/lib/operation.js +++ b/lib/operation.js @@ -1,5 +1,6 @@ 'use strict'; +const { flatten: flattenArray } = require('array-flatten'); const color = require('color'); const is = require('./is'); @@ -82,6 +83,103 @@ function flop (flop) { return this; } +/** + * Perform an affine transform on an image. This operation will always occur after resizing, extraction and rotation, if any. + * + * You must provide an array of length 4 or a 2x2 affine transformation matrix. + * By default, new pixels are filled with a black background. You can provide a background color with the `background` option. + * A particular interpolator may also be specified. Set the `interpolator` option to an attribute of the `sharp.interpolator` Object e.g. `sharp.interpolator.nohalo`. + * + * In the case of a 2x2 matrix, the transform is: + * - X = `matrix[0, 0]` \* (x + `idx`) + `matrix[0, 1]` \* (y + `idy`) + `odx` + * - Y = `matrix[1, 0]` \* (x + `idx`) + `matrix[1, 1]` \* (y + `idy`) + `ody` + * + * where: + * - x and y are the coordinates in input image. + * - X and Y are the coordinates in output image. + * - (0,0) is the upper left corner. + * + * @since 0.27.0 + * + * @example + * const pipeline = sharp() + * .affine([[1, 0.3], [0.1, 0.7]], { + * background: 'white', + * interpolate: sharp.interpolators.nohalo + * }) + * .toBuffer((err, outputBuffer, info) => { + * // outputBuffer contains the transformed image + * // info.width and info.height contain the new dimensions + * }); + * + * inputStream + * .pipe(pipeline); + * + * @param {Array>|Array} matrix - affine transformation matrix + * @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. + * @param {Number} [options.idx=0] - input horizontal offset + * @param {Number} [options.idy=0] - input vertical offset + * @param {Number} [options.odx=0] - output horizontal offset + * @param {Number} [options.ody=0] - output vertical offset + * @param {String} [options.interpolator=sharp.interpolators.bicubic] - interpolator + * @returns {Sharp} + * @throws {Error} Invalid parameters + */ +function affine (matrix, options) { + const flatMatrix = flattenArray(matrix); + if (flatMatrix.length === 4 && flatMatrix.every(is.number)) { + this.options.affineMatrix = flatMatrix; + } else { + throw is.invalidParameterError('matrix', '1x4 or 2x2 array', matrix); + } + + if (is.defined(options)) { + if (is.object(options)) { + this._setBackgroundColourOption('affineBackground', options.background); + if (is.defined(options.idx)) { + if (is.number(options.idx)) { + this.options.affineIdx = options.idx; + } else { + throw is.invalidParameterError('options.idx', 'number', options.idx); + } + } + if (is.defined(options.idy)) { + if (is.number(options.idy)) { + this.options.affineIdy = options.idy; + } else { + throw is.invalidParameterError('options.idy', 'number', options.idy); + } + } + if (is.defined(options.odx)) { + if (is.number(options.odx)) { + this.options.affineOdx = options.odx; + } else { + throw is.invalidParameterError('options.odx', 'number', options.odx); + } + } + if (is.defined(options.ody)) { + if (is.number(options.ody)) { + this.options.affineOdy = options.ody; + } else { + throw is.invalidParameterError('options.ody', 'number', options.ody); + } + } + if (is.defined(options.interpolator)) { + if (is.inArray(options.interpolator, Object.values(this.constructor.interpolators))) { + this.options.affineInterpolator = options.interpolator; + } else { + throw is.invalidParameterError('options.interpolator', 'valid interpolator name', options.interpolator); + } + } + } else { + throw is.invalidParameterError('options', 'object', options); + } + } + + return this; +} + /** * Sharpen the image. * When used without parameters, performs a fast, mild sharpen of the output image. @@ -482,6 +580,7 @@ module.exports = function (Sharp) { rotate, flip, flop, + affine, sharpen, median, blur, diff --git a/lib/utility.js b/lib/utility.js index f7232f89..1da7ae9b 100644 --- a/lib/utility.js +++ b/lib/utility.js @@ -13,6 +13,26 @@ const sharp = require('../build/Release/sharp.node'); */ const format = sharp.format(); +/** + * An Object containing the available interpolators and their proper values + * @readonly + * @enum {string} + */ +const interpolators = { + /** [Nearest neighbour interpolation](http://en.wikipedia.org/wiki/Nearest-neighbor_interpolation). Suitable for image enlargement only. */ + nearest: 'nearest', + /** [Bilinear interpolation](http://en.wikipedia.org/wiki/Bilinear_interpolation). Faster than bicubic but with less smooth results. */ + bilinear: 'bilinear', + /** [Bicubic interpolation](http://en.wikipedia.org/wiki/Bicubic_interpolation) (the default). */ + bicubic: 'bicubic', + /** [LBB interpolation](https://github.com/jcupitt/libvips/blob/master/libvips/resample/lbb.cpp#L100). Prevents some "[acutance](http://en.wikipedia.org/wiki/Acutance)" but typically reduces performance by a factor of 2. */ + locallyBoundedBicubic: 'lbb', + /** [Nohalo interpolation](http://eprints.soton.ac.uk/268086/). Prevents acutance but typically reduces performance by a factor of 3. */ + nohalo: 'nohalo', + /** [VSQBS interpolation](https://github.com/jcupitt/libvips/blob/master/libvips/resample/vsqbs.cpp#L48). Prevents "staircasing" when enlarging. */ + vertexSplitQuadraticBasisSpline: 'vsqbs' +}; + /** * An Object containing the version numbers of libvips and its dependencies. * @member @@ -146,6 +166,7 @@ module.exports = function (Sharp) { Sharp[f.name] = f; }); Sharp.format = format; + Sharp.interpolators = interpolators; Sharp.versions = versions; Sharp.queue = queue; }; diff --git a/package.json b/package.json index f612ee21..03bdf614 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,8 @@ "Edward Silverton ", "Roman Malieiev ", "Tomas Szabo ", - "Robert O'Rourke " + "Robert O'Rourke ", + "Guillermo Alfonso Varela ChouciƱo " ], "scripts": { "install": "(node install/libvips && node install/dll-copy && prebuild-install) || (node-gyp rebuild && node install/dll-copy)", @@ -112,6 +113,7 @@ "vips" ], "dependencies": { + "array-flatten": "^3.0.0", "color": "^3.1.2", "detect-libc": "^1.0.3", "node-addon-api": "^3.0.2", diff --git a/src/common.cc b/src/common.cc index 13fc80f4..5da9d400 100644 --- a/src/common.cc +++ b/src/common.cc @@ -54,13 +54,13 @@ namespace sharp { bool AttrAsBool(Napi::Object obj, std::string attr) { return obj.Get(attr).As().Value(); } - std::vector AttrAsRgba(Napi::Object obj, std::string attr) { - Napi::Array background = obj.Get(attr).As(); - std::vector rgba(background.Length()); - for (unsigned int i = 0; i < background.Length(); i++) { - rgba[i] = AttrAsDouble(background, i); + std::vector AttrAsVectorOfDouble(Napi::Object obj, std::string attr) { + Napi::Array napiArray = obj.Get(attr).As(); + std::vector vectorOfDouble(napiArray.Length()); + for (unsigned int i = 0; i < napiArray.Length(); i++) { + vectorOfDouble[i] = AttrAsDouble(napiArray, i); } - return rgba; + return vectorOfDouble; } std::vector AttrAsInt32Vector(Napi::Object obj, std::string attr) { Napi::Array array = obj.Get(attr).As(); @@ -109,7 +109,7 @@ namespace sharp { descriptor->createChannels = AttrAsUint32(input, "createChannels"); descriptor->createWidth = AttrAsUint32(input, "createWidth"); descriptor->createHeight = AttrAsUint32(input, "createHeight"); - descriptor->createBackground = AttrAsRgba(input, "createBackground"); + descriptor->createBackground = AttrAsVectorOfDouble(input, "createBackground"); } // Limit input images to a given number of pixels, where pixels = width * height descriptor->limitInputPixels = AttrAsUint32(input, "limitInputPixels"); diff --git a/src/common.h b/src/common.h index 71ff1697..ffc54afc 100644 --- a/src/common.h +++ b/src/common.h @@ -92,7 +92,7 @@ namespace sharp { double AttrAsDouble(Napi::Object obj, std::string attr); double AttrAsDouble(Napi::Object obj, unsigned int const attr); bool AttrAsBool(Napi::Object obj, std::string attr); - std::vector AttrAsRgba(Napi::Object obj, std::string attr); + std::vector AttrAsVectorOfDouble(Napi::Object obj, std::string attr); std::vector AttrAsInt32Vector(Napi::Object obj, std::string attr); // Create an InputDescriptor instance from a Napi::Object describing an input image diff --git a/src/pipeline.cc b/src/pipeline.cc index b182d7bb..883ebbe2 100644 --- a/src/pipeline.cc +++ b/src/pipeline.cc @@ -485,6 +485,18 @@ class PipelineWorker : public Napi::AsyncWorker { baton->leftOffsetPost, baton->topOffsetPost, baton->widthPost, baton->heightPost); } + // Affine transform + if (baton->affineMatrix.size() > 0) { + std::vector background; + std::tie(image, background) = sharp::ApplyAlpha(image, baton->affineBackground); + image = image.affine(baton->affineMatrix, VImage::option()->set("background", background) + ->set("idx", baton->affineIdx) + ->set("idy", baton->affineIdy) + ->set("odx", baton->affineOdx) + ->set("ody", baton->affineOdy) + ->set("interpolate", baton->affineInterpolator)); + } + // Extend edges if (baton->extendTop > 0 || baton->extendBottom > 0 || baton->extendLeft > 0 || baton->extendRight > 0) { std::vector background; @@ -1249,7 +1261,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) { // Resize options baton->withoutEnlargement = sharp::AttrAsBool(options, "withoutEnlargement"); baton->position = sharp::AttrAsInt32(options, "position"); - baton->resizeBackground = sharp::AttrAsRgba(options, "resizeBackground"); + baton->resizeBackground = sharp::AttrAsVectorOfDouble(options, "resizeBackground"); baton->kernel = sharp::AttrAsStr(options, "kernel"); baton->fastShrinkOnLoad = sharp::AttrAsBool(options, "fastShrinkOnLoad"); // Join Channel Options @@ -1262,7 +1274,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) { } // Operators baton->flatten = sharp::AttrAsBool(options, "flatten"); - baton->flattenBackground = sharp::AttrAsRgba(options, "flattenBackground"); + baton->flattenBackground = sharp::AttrAsVectorOfDouble(options, "flattenBackground"); baton->negate = sharp::AttrAsBool(options, "negate"); baton->blurSigma = sharp::AttrAsDouble(options, "blurSigma"); baton->brightness = sharp::AttrAsDouble(options, "brightness"); @@ -1284,7 +1296,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) { baton->useExifOrientation = sharp::AttrAsBool(options, "useExifOrientation"); baton->angle = sharp::AttrAsInt32(options, "angle"); baton->rotationAngle = sharp::AttrAsDouble(options, "rotationAngle"); - baton->rotationBackground = sharp::AttrAsRgba(options, "rotationBackground"); + baton->rotationBackground = sharp::AttrAsVectorOfDouble(options, "rotationBackground"); baton->rotateBeforePreExtract = sharp::AttrAsBool(options, "rotateBeforePreExtract"); baton->flip = sharp::AttrAsBool(options, "flip"); baton->flop = sharp::AttrAsBool(options, "flop"); @@ -1292,8 +1304,15 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) { baton->extendBottom = sharp::AttrAsInt32(options, "extendBottom"); baton->extendLeft = sharp::AttrAsInt32(options, "extendLeft"); baton->extendRight = sharp::AttrAsInt32(options, "extendRight"); - baton->extendBackground = sharp::AttrAsRgba(options, "extendBackground"); + baton->extendBackground = sharp::AttrAsVectorOfDouble(options, "extendBackground"); baton->extractChannel = sharp::AttrAsInt32(options, "extractChannel"); + baton->affineMatrix = sharp::AttrAsVectorOfDouble(options, "affineMatrix"); + baton->affineBackground = sharp::AttrAsVectorOfDouble(options, "affineBackground"); + baton->affineIdx = sharp::AttrAsDouble(options, "affineIdx"); + baton->affineIdy = sharp::AttrAsDouble(options, "affineIdy"); + baton->affineOdx = sharp::AttrAsDouble(options, "affineOdx"); + baton->affineOdy = sharp::AttrAsDouble(options, "affineOdy"); + baton->affineInterpolator = vips::VInterpolate::new_from_name(sharp::AttrAsStr(options, "affineInterpolator").data()); baton->removeAlpha = sharp::AttrAsBool(options, "removeAlpha"); baton->ensureAlpha = sharp::AttrAsBool(options, "ensureAlpha"); @@ -1392,7 +1411,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) { baton->tileSize = sharp::AttrAsUint32(options, "tileSize"); baton->tileOverlap = sharp::AttrAsUint32(options, "tileOverlap"); baton->tileAngle = sharp::AttrAsInt32(options, "tileAngle"); - baton->tileBackground = sharp::AttrAsRgba(options, "tileBackground"); + baton->tileBackground = sharp::AttrAsVectorOfDouble(options, "tileBackground"); baton->tileSkipBlanks = sharp::AttrAsInt32(options, "tileSkipBlanks"); baton->tileContainer = static_cast( vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_DZ_CONTAINER, diff --git a/src/pipeline.h b/src/pipeline.h index f4ebf6f1..bc6fe5c7 100644 --- a/src/pipeline.h +++ b/src/pipeline.h @@ -119,6 +119,13 @@ struct PipelineBaton { int extendRight; std::vector extendBackground; bool withoutEnlargement; + std::vector affineMatrix; + std::vector affineBackground; + double affineIdx; + double affineIdy; + double affineOdx; + double affineOdy; + vips::VInterpolate affineInterpolator; int jpegQuality; bool jpegProgressive; std::string jpegChromaSubsampling; @@ -231,6 +238,13 @@ struct PipelineBaton { extendRight(0), extendBackground{ 0.0, 0.0, 0.0, 255.0 }, withoutEnlargement(false), + affineMatrix{ 1.0, 0.0, 0.0, 1.0 }, + affineBackground{ 0.0, 0.0, 0.0, 255.0 }, + affineIdx(0), + affineIdy(0), + affineOdx(0), + affineOdy(0), + affineInterpolator(vips::VInterpolate::new_from_name("bicubic")), jpegQuality(80), jpegProgressive(false), jpegChromaSubsampling("4:2:0"), diff --git a/test/fixtures/expected/affine-background-all-offsets-expected.jpg b/test/fixtures/expected/affine-background-all-offsets-expected.jpg new file mode 100644 index 00000000..6a0efb2a Binary files /dev/null and b/test/fixtures/expected/affine-background-all-offsets-expected.jpg differ diff --git a/test/fixtures/expected/affine-background-expected.jpg b/test/fixtures/expected/affine-background-expected.jpg new file mode 100644 index 00000000..cbce2cb1 Binary files /dev/null and b/test/fixtures/expected/affine-background-expected.jpg differ diff --git a/test/fixtures/expected/affine-background-output-offsets-expected.jpg b/test/fixtures/expected/affine-background-output-offsets-expected.jpg new file mode 100644 index 00000000..89789c9a Binary files /dev/null and b/test/fixtures/expected/affine-background-output-offsets-expected.jpg differ diff --git a/test/fixtures/expected/affine-bicubic-2x-upscale-expected.jpg b/test/fixtures/expected/affine-bicubic-2x-upscale-expected.jpg new file mode 100644 index 00000000..819e4dd9 Binary files /dev/null and b/test/fixtures/expected/affine-bicubic-2x-upscale-expected.jpg differ diff --git a/test/fixtures/expected/affine-bilinear-2x-upscale-expected.jpg b/test/fixtures/expected/affine-bilinear-2x-upscale-expected.jpg new file mode 100644 index 00000000..b0266667 Binary files /dev/null and b/test/fixtures/expected/affine-bilinear-2x-upscale-expected.jpg differ diff --git a/test/fixtures/expected/affine-extract-expected.jpg b/test/fixtures/expected/affine-extract-expected.jpg new file mode 100644 index 00000000..b152fbc1 Binary files /dev/null and b/test/fixtures/expected/affine-extract-expected.jpg differ diff --git a/test/fixtures/expected/affine-extract-rotate-expected.jpg b/test/fixtures/expected/affine-extract-rotate-expected.jpg new file mode 100644 index 00000000..ced3189a Binary files /dev/null and b/test/fixtures/expected/affine-extract-rotate-expected.jpg differ diff --git a/test/fixtures/expected/affine-lbb-2x-upscale-expected.jpg b/test/fixtures/expected/affine-lbb-2x-upscale-expected.jpg new file mode 100644 index 00000000..f6caf657 Binary files /dev/null and b/test/fixtures/expected/affine-lbb-2x-upscale-expected.jpg differ diff --git a/test/fixtures/expected/affine-nearest-2x-upscale-expected.jpg b/test/fixtures/expected/affine-nearest-2x-upscale-expected.jpg new file mode 100644 index 00000000..e1007693 Binary files /dev/null and b/test/fixtures/expected/affine-nearest-2x-upscale-expected.jpg differ diff --git a/test/fixtures/expected/affine-nohalo-2x-upscale-expected.jpg b/test/fixtures/expected/affine-nohalo-2x-upscale-expected.jpg new file mode 100644 index 00000000..b4064042 Binary files /dev/null and b/test/fixtures/expected/affine-nohalo-2x-upscale-expected.jpg differ diff --git a/test/fixtures/expected/affine-resize-expected.jpg b/test/fixtures/expected/affine-resize-expected.jpg new file mode 100644 index 00000000..b7d06265 Binary files /dev/null and b/test/fixtures/expected/affine-resize-expected.jpg differ diff --git a/test/fixtures/expected/affine-rotate-expected.jpg b/test/fixtures/expected/affine-rotate-expected.jpg new file mode 100644 index 00000000..8b54a3c3 Binary files /dev/null and b/test/fixtures/expected/affine-rotate-expected.jpg differ diff --git a/test/fixtures/expected/affine-vsqbs-2x-upscale-expected.jpg b/test/fixtures/expected/affine-vsqbs-2x-upscale-expected.jpg new file mode 100644 index 00000000..4c76554b Binary files /dev/null and b/test/fixtures/expected/affine-vsqbs-2x-upscale-expected.jpg differ diff --git a/test/unit/affine.js b/test/unit/affine.js new file mode 100644 index 00000000..03f4aced --- /dev/null +++ b/test/unit/affine.js @@ -0,0 +1,175 @@ +'use strict'; + +const assert = require('assert'); + +const sharp = require('../../'); +const fixtures = require('../fixtures'); + +describe('Affine transform', () => { + describe('Invalid input', () => { + it('Missing matrix', () => { + assert.throws(() => { + sharp(fixtures.inputJpg) + .affine(); + }); + }); + it('Invalid 1d matrix', () => { + assert.throws(() => { + sharp(fixtures.inputJpg) + .affine(['123', 123, 123, 123]); + }); + }); + it('Invalid 2d matrix', () => { + assert.throws(() => { + sharp(fixtures.inputJpg) + .affine([[123, 123], [null, 123]]); + }); + }); + it('Invalid options parameter type', () => { + assert.throws(() => { + sharp(fixtures.inputJpg) + .affine([[1, 0], [0, 1]], 'invalid options type'); + }); + }); + it('Invalid background color', () => { + assert.throws(() => { + sharp(fixtures.inputJpg) + .affine([4, 4, 4, 4], { background: 'not a color' }); + }); + }); + it('Invalid idx offset type', () => { + assert.throws(() => { + sharp(fixtures.inputJpg) + .affine([[4, 4], [4, 4]], { idx: 'invalid idx type' }); + }); + }); + it('Invalid idy offset type', () => { + assert.throws(() => { + sharp(fixtures.inputJpg) + .affine([4, 4, 4, 4], { idy: 'invalid idy type' }); + }); + }); + it('Invalid odx offset type', () => { + assert.throws(() => { + sharp(fixtures.inputJpg) + .affine([[4, 4], [4, 4]], { odx: 'invalid odx type' }); + }); + }); + it('Invalid ody offset type', () => { + assert.throws(() => { + sharp(fixtures.inputJpg) + .affine([[4, 4], [4, 4]], { ody: 'invalid ody type' }); + }); + }); + it('Invalid interpolator', () => { + assert.throws(() => { + sharp(fixtures.inputJpg) + .affine([[4, 4], [4, 4]], { interpolator: 'cubic' }); + }); + }); + }); + it('Applies identity matrix', done => { + const input = fixtures.inputJpg; + sharp(input) + .affine([[1, 0], [0, 1]]) + .toBuffer((err, data) => { + if (err) throw err; + fixtures.assertSimilar(input, data, done); + }); + }); + it('Applies resize affine matrix', done => { + const input = fixtures.inputJpg; + const inputWidth = 2725; + const inputHeight = 2225; + sharp(input) + .affine([[0.2, 0], [0, 1.5]]) + .toBuffer((err, data, info) => { + if (err) throw err; + fixtures.assertSimilar(input, data, done); + assert.strictEqual(info.width, Math.ceil(inputWidth * 0.2)); + assert.strictEqual(info.height, Math.ceil(inputHeight * 1.5)); + }); + }); + it('Resizes and applies affine transform', done => { + const input = fixtures.inputJpg; + sharp(input) + .resize(500, 500) + .affine([[0.5, 1], [1, 0.5]]) + .toBuffer((err, data) => { + if (err) throw err; + fixtures.assertSimilar(data, fixtures.expected('affine-resize-expected.jpg'), done); + }); + }); + it('Extracts and applies affine transform', done => { + sharp(fixtures.inputJpg) + .extract({ left: 300, top: 300, width: 600, height: 600 }) + .affine([0.3, 0, -0.5, 0.3]) + .toBuffer((err, data) => { + if (err) throw err; + fixtures.assertSimilar(data, fixtures.expected('affine-extract-expected.jpg'), done); + }); + }); + it('Rotates and applies affine transform', done => { + sharp(fixtures.inputJpg320x240) + .rotate(90) + .affine([[-1.2, 0], [0, -1.2]]) + .toBuffer((err, data) => { + if (err) throw err; + fixtures.assertSimilar(data, fixtures.expected('affine-rotate-expected.jpg'), done); + }); + }); + it('Extracts, rotates and applies affine transform', done => { + sharp(fixtures.inputJpg) + .extract({ left: 1000, top: 1000, width: 200, height: 200 }) + .rotate(45, { background: 'blue' }) + .affine([[2, 1], [2, -0.5]], { background: 'red' }) + .toBuffer((err, data) => { + if (err) throw err; + fixtures.assertSimilar(fixtures.expected('affine-extract-rotate-expected.jpg'), data, done); + }); + }); + it('Applies affine transform with background color', done => { + sharp(fixtures.inputJpg320x240) + .rotate(180) + .affine([[-1.5, 1.2], [-1, 1]], { background: 'red' }) + .toBuffer((err, data) => { + if (err) throw err; + fixtures.assertSimilar(fixtures.expected('affine-background-expected.jpg'), data, done); + }); + }); + it('Applies affine transform with background color and output offsets', done => { + sharp(fixtures.inputJpg320x240) + .rotate(180) + .affine([[-2, 1.5], [-1, 2]], { background: 'blue', odx: 40, ody: -100 }) + .toBuffer((err, data) => { + if (err) throw err; + fixtures.assertSimilar(fixtures.expected('affine-background-output-offsets-expected.jpg'), data, done); + }); + }); + it('Applies affine transform with background color and all offsets', done => { + sharp(fixtures.inputJpg320x240) + .rotate(180) + .affine([[-1.2, 1.8], [-1, 2]], { background: 'yellow', idx: 10, idy: -40, odx: 10, ody: -50 }) + .toBuffer((err, data) => { + if (err) throw err; + fixtures.assertSimilar(fixtures.expected('affine-background-all-offsets-expected.jpg'), data, done); + }); + }); + describe('Interpolations', () => { + const input = fixtures.inputJpg320x240; + const inputWidth = 320; + const inputHeight = 240; + for (const interp in sharp.interpolators) { + it(`Performs 2x upscale with ${interp} interpolation`, done => { + sharp(input) + .affine([[2, 0], [0, 2]], { interpolator: sharp.interpolators[interp] }) + .toBuffer((err, data, info) => { + if (err) throw err; + assert.strictEqual(info.width, Math.ceil(inputWidth * 2)); + assert.strictEqual(info.height, Math.ceil(inputHeight * 2)); + fixtures.assertSimilar(fixtures.expected(`affine-${sharp.interpolators[interp]}-2x-upscale-expected.jpg`), data, done); + }); + }); + } + }); +});