├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── Dockerfile ├── README.md ├── binding.gyp ├── get_regvalue.py ├── index.js ├── package.json ├── src ├── imagemagick.cc └── imagemagick.h └── test ├── benchmark.js ├── broken.png ├── leak.js ├── orientation-suite ├── LICENSE ├── Landscape_1.jpg ├── Landscape_2.jpg ├── Landscape_3.jpg ├── Landscape_4.jpg ├── Landscape_5.jpg ├── Landscape_6.jpg ├── Landscape_7.jpg ├── Landscape_8.jpg ├── README.markdown └── VERSION ├── test.CMYK.jpg ├── test.async.js ├── test.auto.orient.js ├── test.background.js ├── test.background.png ├── test.colorspace.js ├── test.crop.js ├── test.crop.png ├── test.ext.tga ├── test.exthint.js ├── test.getPixelColor.png ├── test.gravity.js ├── test.jpg ├── test.js ├── test.maxmemory.jpg ├── test.png ├── test.promises.js ├── test.quantizeColors.png ├── test.stream.js ├── test.trim.jpg ├── test.trim.js └── test.wide.png /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | node_modules/ 3 | test/out* 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: trusty 2 | language: node_js 3 | node_js: 4 | - "14" 5 | - "12" 6 | - "10" 7 | - "8" 8 | env: 9 | - CC=clang CXX=clang++ npm_config_clang=1 10 | addons: 11 | apt: 12 | packages: 13 | - libmagick++-dev 14 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Please: 2 | 3 | * Provide command line arguments, relevant output, and sample images when submitting issues or pull requests. 4 | 5 | * Make sure to note version information for your operating system and ImageMagick. 6 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM dockerfile/nodejs 2 | # https://registry.hub.docker.com/u/dockerfile/nodejs/ 3 | 4 | RUN apt-get update 5 | RUN apt-get -y install \ 6 | git \ 7 | imagemagick \ 8 | libmagick++-dev \ 9 | node-gyp \ 10 | emacs 11 | 12 | RUN cd /data && git clone https://github.com/mash/node-imagemagick-native.git 13 | WORKDIR /data/node-imagemagick-native 14 | 15 | RUN npm install --unsafe-perm 16 | 17 | # to test pull requests 18 | RUN git config --local --add remote.origin.fetch "+refs/pull/*/head:refs/remotes/pr/*" && \ 19 | git fetch 20 | 21 | CMD ["bash"] 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-imagemagick-native 2 | 3 | [ImageMagick](http://www.imagemagick.org/)'s [Magick++](http://www.imagemagick.org/Magick++/) binding for [Node](http://nodejs.org/). 4 | 5 | Features 6 | 7 | * Native bindings to the C/C++ Magick++ library 8 | * Async, sync, stream and promises API 9 | * Support for `convert`, `identify`, `composite`, and other utility functions 10 | 11 | [![Build Status](https://travis-ci.org/elad/node-imagemagick-native.png)](https://travis-ci.org/elad/node-imagemagick-native) 12 | 13 | Table of contents 14 | 15 | * [Examples](#examples) 16 | * [Convert formats](#example-convert) (PNG to JPEG) 17 | * [Blur](#example-blur) 18 | * [Resize](#example-resize) 19 | * [Rotate, flip, and mirror](#example-rotate-flip-mirror) 20 | * [API Reference](#api) 21 | * [`convert`](#convert) 22 | * [`identify`](#identify) 23 | * [`quantizeColors`](#quantizeColors) 24 | * [`composite`](#composite) 25 | * [`getConstPixels`](#getConstPixels) 26 | * [`quantumDepth`](#quantumDepth) 27 | * [`version`](#version) 28 | * [Promises](#promises) 29 | * [Installation](#installation) 30 | * [Linux / Mac OS X](#installation-unix) 31 | * [Windows](#installation-windows) 32 | * [Performance](#performance) 33 | * [Contributing](#contributing) 34 | * [License](#license) 35 | 36 | 37 | 38 | ## Examples 39 | 40 | 41 | 42 | ### Convert formats 43 | 44 | Convert from one format to another with quality control: 45 | 46 | ```js 47 | fs.writeFileSync('after.png', imagemagick.convert({ 48 | srcData: fs.readFileSync('before.jpg'), 49 | format: 'PNG', 50 | quality: 100 // (best) to 1 (worst) 51 | })); 52 | ``` 53 | 54 | Original JPEG: 55 | 56 | ![alt text](http://elad.github.io/node-imagemagick-native/examples/quality.jpg 'Original') 57 | 58 | Converted to PNG: 59 | 60 | quality 100 | quality 50 | quality 1 61 | :---: | :---: | :---: 62 | ![alt text](http://elad.github.io/node-imagemagick-native/examples/quality_100.png 'quality 100') | ![alt text](http://elad.github.io/node-imagemagick-native/examples/quality_50.png 'quality 50') | ![alt text](http://elad.github.io/node-imagemagick-native/examples/quality_1.png 'quality 1') 63 | 64 | *Image courtesy of [David Yu](https://www.flickr.com/photos/davidyuweb/14175248591).* 65 | 66 | 67 | 68 | ### Blur 69 | 70 | Blur image: 71 | 72 | ```js 73 | fs.writeFileSync('after.jpg', imagemagick.convert({ 74 | srcData: fs.readFileSync('before.jpg'), 75 | blur: 5 76 | })); 77 | ``` 78 | 79 | ![alt text](http://elad.github.io/node-imagemagick-native/examples/blur_before.jpg 'Before blur') becomes ![alt text](http://elad.github.io/node-imagemagick-native/examples/blur_after.jpg 'After blur') 80 | 81 | *Image courtesy of [Tambako The Jaguar](https://www.flickr.com/photos/tambako/3574360498).* 82 | 83 | 84 | 85 | ### Resize 86 | 87 | Resized images by specifying `width` and `height`. There are three resizing styles: 88 | 89 | * `aspectfill`: Default. The resulting image will be exactly the specified size, and may be cropped. 90 | * `aspectfit`: Scales the image so that it will not have to be cropped. 91 | * `fill`: Squishes or stretches the image so that it fills exactly the specified size. 92 | 93 | ```js 94 | fs.writeFileSync('after_resize.jpg', imagemagick.convert({ 95 | srcData: fs.readFileSync('before_resize.jpg'), 96 | width: 100, 97 | height: 100, 98 | resizeStyle: 'aspectfill', // is the default, or 'aspectfit' or 'fill' 99 | gravity: 'Center' // optional: position crop area when using 'aspectfill' 100 | })); 101 | ``` 102 | 103 | Original: 104 | 105 | ![alt text](http://elad.github.io/node-imagemagick-native/examples/resize.jpg 'Original') 106 | 107 | Resized: 108 | 109 | aspectfill | aspectfit | fill 110 | :---: | :---: | :---: 111 | ![alt text](http://elad.github.io/node-imagemagick-native/examples/resize_aspectfill.jpg 'aspectfill') | ![alt text](http://elad.github.io/node-imagemagick-native/examples/resize_aspectfit.jpg 'aspectfit') | ![alt text](http://elad.github.io/node-imagemagick-native/examples/resize_fill.jpg 'fill') 112 | 113 | *Image courtesy of [Christoph](https://www.flickr.com/photos/scheinwelten/381994831).* 114 | 115 | 116 | 117 | ### Rotate, flip, and mirror 118 | 119 | Rotate and flip images, and combine the two to mirror: 120 | 121 | ```js 122 | fs.writeFileSync('after_rotateflip.jpg', imagemagick.convert({ 123 | srcData: fs.readFileSync('before_rotateflip.jpg'), 124 | rotate: 180, 125 | flip: true 126 | })); 127 | ``` 128 | 129 | Original: 130 | 131 | ![alt text](http://elad.github.io/node-imagemagick-native/examples/rotateflip.jpg 'Original') 132 | 133 | Modified: 134 | 135 | rotate 90 degrees | rotate 180 degrees | flip | flip + rotate 180 degrees = mirror 136 | :---: | :---: | :---: | :---: 137 | ![alt text](http://elad.github.io/node-imagemagick-native/examples/rotateflip_rotate_90.jpg 'rotate 90') | ![alt text](http://elad.github.io/node-imagemagick-native/examples/rotateflip_rotate_180.jpg 'rotate 180') | ![alt text](http://elad.github.io/node-imagemagick-native/examples/rotateflip_flip.jpg 'flip') | ![alt text](http://elad.github.io/node-imagemagick-native/examples/rotateflip_mirror.jpg 'flip + rotate 180 = mirror') 138 | 139 | *Image courtesy of [Bill Gracey](https://www.flickr.com/photos/9422878@N08/6482704235).* 140 | 141 | 142 | 143 | ## API Reference 144 | 145 | 146 | 147 | ### convert(options, [callback]) 148 | 149 | Convert a buffer provided as `options.srcData` and return a Buffer. 150 | 151 | The `options` argument can have following values: 152 | 153 | { 154 | srcData: required. Buffer with binary image data 155 | srcFormat: optional. force source format if not detected (e.g. 'ICO'), one of http://www.imagemagick.org/script/formats.php 156 | quality: optional. 1-100 integer, default 75. JPEG/MIFF/PNG compression level. 157 | trim: optional. default: false. trims edges that are the background color. 158 | trimFuzz: optional. [0-1) float, default 0. trimmed color distance to edge color, 0 is exact. 159 | width: optional. px. 160 | height: optional. px. 161 | density optional. Integer dpi value to convert 162 | resizeStyle: optional. default: 'aspectfill'. can be 'aspectfit', 'fill' 163 | aspectfill: keep aspect ratio, get the exact provided size. 164 | aspectfit: keep aspect ratio, get maximum image that fits inside provided size 165 | fill: forget aspect ratio, get the exact provided size 166 | crop: no resize, get provided size with [x|y]offset 167 | xoffset: optional. default 0: when use crop resizeStyle x margin 168 | yoffset: optional. default 0: when use crop resizeStyle y margin 169 | gravity: optional. default: 'Center'. used to position the crop area when resizeStyle is 'aspectfill' 170 | can be 'NorthWest', 'North', 'NorthEast', 'West', 171 | 'Center', 'East', 'SouthWest', 'South', 'SouthEast', 'None' 172 | format: optional. output format, ex: 'JPEG'. see below for candidates 173 | filter: optional. resize filter. ex: 'Lagrange', 'Lanczos'. see below for candidates 174 | blur: optional. ex: 0.8 175 | strip: optional. default: false. strips comments out from image. 176 | rotate: optional. degrees. 177 | flip: optional. vertical flip, true or false. 178 | autoOrient: optional. default: false. Auto rotate and flip using orientation info. 179 | colorspace: optional. String: Out file use that colorspace ['CMYK', 'sRGB', ...] 180 | debug: optional. true or false 181 | ignoreWarnings: optional. true or false 182 | } 183 | 184 | Notes 185 | 186 | * `format` values can be found [here](http://www.imagemagick.org/script/formats.php) 187 | * `filter` values can be found [here](http://www.imagemagick.org/script/command-line-options.php?ImageMagick=9qgp8o06f469m3cna9lfigirc5#filter) 188 | 189 | An optional `callback` argument can be provided, in which case `convert` will run asynchronously. When it is done, `callback` will be called with the error and the result buffer: 190 | 191 | ```js 192 | imagemagick.convert({ 193 | // options 194 | }, function (err, buffer) { 195 | // check err, use buffer 196 | }); 197 | ``` 198 | 199 | There is also a stream version: 200 | 201 | ```js 202 | fs.createReadStream('input.png').pipe(imagemagick.streams.convert({ 203 | // options 204 | })).pipe(fs.createWriteStream('output.png')); 205 | ``` 206 | 207 | 208 | 209 | ### identify(options, [callback]) 210 | 211 | Identify a buffer provided as `srcData` and return an object. 212 | 213 | The `options` argument can have following values: 214 | 215 | { 216 | srcData: required. Buffer with binary image data 217 | debug: optional. true or false 218 | ignoreWarnings: optional. true or false 219 | } 220 | 221 | An optional `callback` argument can be provided, in which case `identify` will run asynchronously. When it is done, `callback` will be called with the error and the result object: 222 | 223 | ```js 224 | imagemagick.identify({ 225 | // options 226 | }, function (err, result) { 227 | // check err, use result 228 | }); 229 | ``` 230 | 231 | The method returns an object similar to: 232 | 233 | ```js 234 | { 235 | format: 'JPEG', 236 | width: 3904, 237 | height: 2622, 238 | depth: 8, 239 | colorspace: 'sRGB', 240 | density : { 241 | width : 300, 242 | height : 300 243 | }, 244 | exif: { 245 | orientation: 0 // if none exists or e.g. 3 (portrait iPad pictures) 246 | } 247 | } 248 | ``` 249 | 250 | 251 | 252 | ### quantizeColors(options) 253 | 254 | Quantize the image to a specified amount of colors from a buffer provided as `srcData` and return an array. 255 | 256 | The `options` argument can have following values: 257 | 258 | { 259 | srcData: required. Buffer with binary image data 260 | colors: required. number of colors to extract, defaults to 5 261 | debug: optional. true or false 262 | ignoreWarnings: optional. true or false 263 | } 264 | 265 | The method returns an array similar to: 266 | 267 | ```js 268 | [ 269 | { 270 | r: 83, 271 | g: 56, 272 | b: 35, 273 | hex: '533823' 274 | }, 275 | { 276 | r: 149, 277 | g: 110, 278 | b: 73, 279 | hex: '956e49' 280 | }, 281 | { 282 | r: 165, 283 | g: 141, 284 | b: 111, 285 | hex: 'a58d6f' 286 | } 287 | ] 288 | ``` 289 | 290 | 291 | 292 | ### composite(options, [callback]) 293 | 294 | Composite a buffer provided as `options.compositeData` on a buffer provided as `options.srcData` with gravity specified by `options.gravity` and return a Buffer. 295 | 296 | The `options` argument can have following values: 297 | 298 | { 299 | srcData: required. Buffer with binary image data 300 | compositeData: required. Buffer with binary image data 301 | gravity: optional. Can be one of 'CenterGravity' 'EastGravity' 'ForgetGravity' 'NorthEastGravity' 'NorthGravity' 'NorthWestGravity' 'SouthEastGravity' 'SouthGravity' 'SouthWestGravity' 'WestGravity' 302 | debug: optional. true or false 303 | ignoreWarnings: optional. true or false 304 | } 305 | 306 | An optional `callback` argument can be provided, in which case `composite` will run asynchronously. When it is done, `callback` will be called with the error and the result buffer: 307 | 308 | ```js 309 | imagemagick.composite(options, function (err, buffer) { 310 | // check err, use buffer 311 | }); 312 | ``` 313 | 314 | This library currently provide only these, please try [node-imagemagick](https://github.com/rsms/node-imagemagick/) if you want more. 315 | 316 | 317 | 318 | ### getConstPixels(options) 319 | 320 | Get pixels of provided rectangular region. 321 | 322 | The `options` argument can have following values: 323 | 324 | { 325 | srcData: required. Buffer with binary image data 326 | x: required. x,y,columns,rows provide the area of interest. 327 | y: required. 328 | columns: required. 329 | rows: required. 330 | } 331 | 332 | Example usage: 333 | 334 | ```js 335 | // retrieve first pixel of image 336 | var pixels = imagemagick.getConstPixels({ 337 | srcData: imageBuffer, // white image 338 | x: 0, 339 | y: 0, 340 | columns: 1, 341 | rows: 1 342 | }); 343 | ``` 344 | 345 | Returns: 346 | 347 | ```js 348 | [ { red: 65535, green: 65535, blue: 65535, opacity: 65535 } ] 349 | ``` 350 | 351 | Where each color value's size is `imagemagick.quantumDepth` bits. 352 | 353 | 354 | 355 | ### quantumDepth 356 | 357 | Return ImageMagick's QuantumDepth, which is defined in compile time. 358 | ex: 16 359 | 360 | 361 | 362 | ### version 363 | 364 | Return ImageMagick's version as string. 365 | ex: '6.7.7' 366 | 367 | 368 | 369 | ## Promises 370 | 371 | The namespace promises expose functions convert, composite and identify that returns a Promise. 372 | 373 | Examples: 374 | 375 | ````js 376 | // convert 377 | imagemagick.promises.convert({ /* OPTIONS */ }) 378 | .then(function(buff) { /* Write buffer */ }) 379 | .catch(function(err) { /* log err */ }) 380 | 381 | // ES8 382 | const buff = await imagemagick.promises.convert({ /* OPTIONS */ }) 383 | 384 | // composite 385 | imagemagick.promises.composite({ /* OPTIONS */ }) 386 | .then(function(buff) { /* Write buffer */ }) 387 | .catch(function(err) { /* log err */ }) 388 | 389 | // ES8 390 | const buff = await imagemagick.promises.composite({ /* OPTIONS */ }) 391 | 392 | // identify 393 | imagemagick.promises.identify({ /* OPTIONS */ }) 394 | .then(function(info) { /* Write buffer */ }) 395 | .catch(function(err) { /* log err */ }) 396 | 397 | // ES8 398 | const info = await imagemagick.promises.identify({ /* OPTIONS */ }) 399 | ```` 400 | 401 | 402 | 403 | ## Installation 404 | 405 | 406 | 407 | ### Linux / Mac OS X 408 | 409 | Install [ImageMagick](http://www.imagemagick.org/) with headers before installing this module. 410 | Tested with ImageMagick 6.7.7 on CentOS 6 and Mac OS X Lion, Ubuntu 12.04 . 411 | 412 | brew install imagemagick 413 | 414 | or 415 | 416 | sudo yum install ImageMagick-c++ ImageMagick-c++-devel 417 | 418 | or 419 | 420 | sudo apt-get install libmagick++-dev 421 | 422 | Make sure you can find Magick++-config in your PATH. Packages on some newer distributions, such as Ubuntu 16.04, might be missing a link into `/usr/bin`. If that is the case, do this. 423 | 424 | sudo ln -s `ls /usr/lib/\`uname -p\`-linux-gnu/ImageMagick-*/bin-Q16/Magick++-config | head -n 1` /usr/local/bin/ 425 | 426 | Then: 427 | 428 | npm install imagemagick-native 429 | 430 | **Installation notes** 431 | 432 | * RHEL/CentOS: If the version of ImageMagick required by `node-imagemagick-native` is not available in an official RPM repository, please try the `-last` version offered by Les RPM de Remi, for example: 433 | 434 | ``` 435 | sudo yum remove -y ImageMagick 436 | sudo yum install -y http://rpms.famillecollet.com/enterprise/remi-release-6.rpm 437 | sudo yum install -y --enablerepo=remi ImageMagick-last-c++-devel 438 | ``` 439 | 440 | * Mac OS X: You might need to install `pkgconfig` first: 441 | 442 | ``` 443 | brew install pkgconfig 444 | ``` 445 | 446 | 447 | 448 | ### Windows 449 | 450 | Tested on Windows 7 x64. 451 | 452 | 1. Install Python 2.7.3 only 2.7.3 nothing else works quite right! 453 | 454 | If you use Cygwin ensure you don't have Python installed in Cygwin setup as there will be some confusion about what version to use. 455 | 456 | 2. Install [Visual Studio C++ 2010 Express](http://www.microsoft.com/en-us/download/details.aspx?id=8279) 457 | 458 | 3. (64-bit only) [Install Windows 7 64-bit SDK](http://www.microsoft.com/en-us/download/details.aspx?id=8279) 459 | 460 | 4. Install [Imagemagick dll binary x86 Q16](http://www.imagemagick.org/download/binaries/ImageMagick-6.9.8-4-Q16-x86-dll.exe) or [Imagemagick dll binary x64 Q16](http://www.imagemagick.org/download/binaries/ImageMagick-6.9.8-4-Q16-x64-dll.exe), check for libraries and includes during install. 461 | 462 | Then: 463 | 464 | npm install imagemagick-native 465 | 466 | 467 | 468 | ## Performance - simple thumbnail creation 469 | 470 | imagemagick: 16.09ms per iteration 471 | imagemagick-native: 0.89ms per iteration 472 | 473 | See `node test/benchmark.js` for details. 474 | 475 | **Note:** `node-imagemagick-native`'s primary advantage is that it uses ImageMagick's API directly rather than by executing one of its command line tools. This means that it will be much faster when the amount of time spent inside the library is small and less so otherwise. See [issue #46](https://github.com/mash/node-imagemagick-native/issues/46) for discussion. 476 | 477 | 478 | 479 | ## Contributing 480 | 481 | This project follows the ["OPEN Open Source"](https://gist.github.com/substack/e205f5389890a1425233) philosophy. If you submit a pull request and it gets merged you will most likely be given commit access to this repository. 482 | 483 | 484 | 485 | ## License (MIT) 486 | 487 | Copyright (c) Masakazu Ohtsuka 488 | 489 | Permission is hereby granted, free of charge, to any person obtaining a copy 490 | of this software and associated documentation files (the 'Software'), to deal 491 | in the Software without restriction, including without limitation the rights 492 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 493 | copies of the Software, and to permit persons to whom the Software is 494 | furnished to do so, subject to the following conditions: 495 | 496 | The above copyright notice and this permission notice shall be included in 497 | all copies or substantial portions of the Software. 498 | 499 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 500 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 501 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 502 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 503 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 504 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 505 | THE SOFTWARE. 506 | -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | 'conditions': [ 3 | ['OS=="win"', { 4 | 'variables': { 5 | 'MAGICK_ROOT%': ' { 44 | func(options, function(err, buff) { 45 | if (err) { 46 | return reject(err); 47 | } 48 | resolve(buff); 49 | }); 50 | }); 51 | }; 52 | } 53 | 54 | module.exports.promises = { 55 | convert: promisify(module.exports.convert), 56 | identify: promisify(module.exports.identify), 57 | composite: promisify(module.exports.composite), 58 | }; 59 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "imagemagick-native", 3 | "description": "ImageMagick's Magick++ bindings for NodeJS", 4 | "keywords": [ 5 | "imagemagick", 6 | "magick++", 7 | "resize", 8 | "convert" 9 | ], 10 | "version": "1.9.3", 11 | "license": "MIT", 12 | "repository": { 13 | "type": "git", 14 | "url": "git://github.com/mash/node-imagemagick-native.git" 15 | }, 16 | "author": "Masakazu Ohtsuka (http://maaash.jp/)", 17 | "contributors": [], 18 | "main": "./index.js", 19 | "scripts": { 20 | "test": "tap test/test*.js", 21 | "install": "node-gyp rebuild" 22 | }, 23 | "engines": { 24 | "node": ">=4" 25 | }, 26 | "dependencies": { 27 | "nan": "2.x" 28 | }, 29 | "devDependencies": { 30 | "tap": "*" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/imagemagick.cc: -------------------------------------------------------------------------------- 1 | #ifndef BUILDING_NODE_EXTENSION 2 | #define BUILDING_NODE_EXTENSION 3 | #endif // BUILDING_NODE_EXTENSION 4 | 5 | #if _MSC_VER && _MSC_VER < 1900 6 | #define snprintf _snprintf 7 | #endif 8 | 9 | #include "imagemagick.h" 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | // RAII to reset image magick's resource limit 16 | class LocalResourceLimiter 17 | { 18 | public: 19 | LocalResourceLimiter() 20 | : originalMemory(0), 21 | memoryLimited(0), 22 | originalDisk(0), 23 | diskLimited(0) { 24 | } 25 | ~LocalResourceLimiter() { 26 | if (memoryLimited) { 27 | MagickCore::SetMagickResourceLimit(MagickCore::MemoryResource,originalMemory); 28 | } 29 | if (diskLimited) { 30 | MagickCore::SetMagickResourceLimit(MagickCore::DiskResource,originalDisk); 31 | } 32 | } 33 | void LimitMemory(MagickCore::MagickSizeType to) { 34 | if ( ! memoryLimited ) { 35 | memoryLimited = true; 36 | originalMemory = MagickCore::GetMagickResourceLimit(MagickCore::MemoryResource); 37 | } 38 | MagickCore::SetMagickResourceLimit(MagickCore::MemoryResource, to); 39 | } 40 | void LimitDisk(MagickCore::MagickSizeType to) { 41 | if ( ! diskLimited ) { 42 | diskLimited = true; 43 | originalDisk = MagickCore::GetMagickResourceLimit(MagickCore::DiskResource); 44 | } 45 | MagickCore::SetMagickResourceLimit(MagickCore::DiskResource, to); 46 | } 47 | 48 | private: 49 | MagickCore::MagickSizeType originalMemory; 50 | bool memoryLimited; 51 | MagickCore::MagickSizeType originalDisk; 52 | bool diskLimited; 53 | }; 54 | 55 | // Base context for calls shared on sync and async code paths 56 | struct im_ctx_base { 57 | Nan::Callback * callback; 58 | std::string error; 59 | 60 | char* srcData; 61 | size_t length; 62 | int debug; 63 | int ignoreWarnings; 64 | std::string srcFormat; 65 | 66 | // generated blob by convert or composite 67 | Magick::Blob dstBlob; 68 | 69 | virtual ~im_ctx_base() {} 70 | }; 71 | // Extra context for identify 72 | struct identify_im_ctx : im_ctx_base { 73 | Magick::Image image; 74 | 75 | identify_im_ctx() {} 76 | }; 77 | // Extra context for convert 78 | struct convert_im_ctx : im_ctx_base { 79 | unsigned int maxMemory; 80 | 81 | unsigned int width; 82 | unsigned int height; 83 | unsigned int xoffset; 84 | unsigned int yoffset; 85 | bool strip; 86 | bool trim; 87 | bool autoOrient; 88 | double trimFuzz; 89 | std::string resizeStyle; 90 | std::string gravity; 91 | std::string format; 92 | std::string filter; 93 | std::string blur; 94 | std::string background; 95 | Magick::ColorspaceType colorspace; 96 | unsigned int quality; 97 | int rotate; 98 | int density; 99 | int flip; 100 | 101 | convert_im_ctx() {} 102 | }; 103 | // Extra context for composite 104 | struct composite_im_ctx : im_ctx_base { 105 | char* compositeData; 106 | size_t compositeLength; 107 | 108 | std::string gravity; 109 | 110 | composite_im_ctx() {} 111 | }; 112 | 113 | 114 | inline Local WrapPointer(char *ptr, size_t length) { 115 | Nan::EscapableHandleScope scope; 116 | return scope.Escape(Nan::CopyBuffer(ptr, length).ToLocalChecked()); 117 | } 118 | inline Local WrapPointer(char *ptr) { 119 | return WrapPointer(ptr, 0); 120 | } 121 | 122 | 123 | #define RETURN_BLOB_OR_ERROR(req) \ 124 | do { \ 125 | im_ctx_base* _context = static_cast(req->data); \ 126 | if (!_context->error.empty()) { \ 127 | Nan::ThrowError(_context->error.c_str()); \ 128 | } else { \ 129 | const Local _retBuffer = WrapPointer((char *)_context->dstBlob.data(), _context->dstBlob.length()); \ 130 | info.GetReturnValue().Set(_retBuffer); \ 131 | } \ 132 | delete req; \ 133 | } while(0); 134 | 135 | 136 | bool ReadImageMagick(Magick::Image *image, Magick::Blob srcBlob, std::string srcFormat, im_ctx_base *context) { 137 | if( ! srcFormat.empty() ){ 138 | if (context->debug) printf( "reading with format: %s\n", srcFormat.c_str() ); 139 | image->magick( srcFormat.c_str() ); 140 | } 141 | 142 | try { 143 | image->read( srcBlob ); 144 | } 145 | catch (Magick::Warning& warning) { 146 | if (!context->ignoreWarnings) { 147 | context->error = warning.what(); 148 | return false; 149 | } else if (context->debug) { 150 | printf("warning: %s\n", warning.what()); 151 | } 152 | } 153 | catch (std::exception& err) { 154 | context->error = err.what(); 155 | return false; 156 | } 157 | catch (...) { 158 | context->error = std::string("unhandled error"); 159 | return false; 160 | } 161 | return true; 162 | } 163 | 164 | void AutoOrient(Magick::Image *image) { 165 | switch (image->orientation()) { 166 | case Magick::OrientationType::UndefinedOrientation: // No orientation info 167 | case Magick::OrientationType::TopLeftOrientation: 168 | break; 169 | case Magick::OrientationType::TopRightOrientation: 170 | image->rotate(180); 171 | image->flip(); 172 | break; 173 | case Magick::OrientationType::BottomRightOrientation: 174 | image->rotate(180); 175 | break; 176 | case Magick::OrientationType::BottomLeftOrientation: 177 | image->flip(); 178 | break; 179 | case Magick::OrientationType::LeftTopOrientation: 180 | image->flip(); 181 | image->rotate(90); 182 | break; 183 | case Magick::OrientationType::RightTopOrientation: 184 | image->rotate(90); 185 | break; 186 | case Magick::OrientationType::RightBottomOrientation: 187 | image->flip(); 188 | image->rotate(270); 189 | break; 190 | case Magick::OrientationType::LeftBottomOrientation: 191 | image->rotate(270); 192 | break; 193 | } 194 | 195 | // Erase orientation metadata after rotating the image to avoid double-rotation 196 | image->orientation(Magick::OrientationType::UndefinedOrientation); 197 | } 198 | 199 | void DoConvert(uv_work_t* req) { 200 | 201 | convert_im_ctx* context = static_cast(req->data); 202 | 203 | MagickCore::SetMagickResourceLimit(MagickCore::ThreadResource, 1); 204 | LocalResourceLimiter limiter; 205 | 206 | int debug = context->debug; 207 | 208 | if (debug) printf( "debug: on\n" ); 209 | if (debug) printf( "ignoreWarnings: %d\n", context->ignoreWarnings ); 210 | 211 | if (context->maxMemory > 0) { 212 | limiter.LimitMemory(context->maxMemory); 213 | limiter.LimitDisk(context->maxMemory); // avoid using unlimited disk as cache 214 | if (debug) printf( "maxMemory set to: %d\n", context->maxMemory ); 215 | } 216 | 217 | Magick::Blob srcBlob( context->srcData, context->length ); 218 | 219 | Magick::Image image; 220 | 221 | if ( !ReadImageMagick(&image, srcBlob, context->srcFormat, context) ) 222 | return; 223 | 224 | if (!context->background.empty()) { 225 | try { 226 | Magick::Color bg(context->background.c_str()); 227 | Magick::Image background(image.size(), bg); 228 | 229 | if (debug) { 230 | printf("background: %s\n", static_cast(bg).c_str()); 231 | } 232 | 233 | background.composite(image, Magick::ForgetGravity, Magick::OverCompositeOp); 234 | image.composite(background, Magick::ForgetGravity, Magick::CopyCompositeOp); 235 | } catch ( Magick::WarningOption &warning ){ 236 | if (debug) printf("Warning: %s\n", warning.what()); 237 | } 238 | } 239 | 240 | if (debug) printf("original width,height: %d, %d\n", (int) image.columns(), (int) image.rows()); 241 | 242 | unsigned int width = context->width; 243 | if (debug) printf( "width: %d\n", width ); 244 | 245 | unsigned int height = context->height; 246 | if (debug) printf( "height: %d\n", height ); 247 | 248 | if ( context->strip ) { 249 | if (debug) printf( "strip: true\n" ); 250 | image.strip(); 251 | } 252 | 253 | if ( context->trim ) { 254 | if (debug) printf( "trim: true\n" ); 255 | double trimFuzz = context->trimFuzz; 256 | if ( trimFuzz != trimFuzz ) { 257 | image.trim(); 258 | } else { 259 | if (debug) printf( "fuzz: %lf\n", trimFuzz ); 260 | double fuzz = image.colorFuzz(); 261 | image.colorFuzz(trimFuzz); 262 | image.trim(); 263 | image.colorFuzz(fuzz); 264 | if (debug) printf( "restored fuzz: %lf\n", fuzz ); 265 | } 266 | if (debug) printf( "trimmed width,height: %d, %d\n", (int) image.columns(), (int) image.rows() ); 267 | } 268 | 269 | const char* resizeStyle = context->resizeStyle.c_str(); 270 | if (debug) printf( "resizeStyle: %s\n", resizeStyle ); 271 | 272 | const char* gravity = context->gravity.c_str(); 273 | if ( strcmp("Center", gravity)!=0 274 | && strcmp("East", gravity)!=0 275 | && strcmp("West", gravity)!=0 276 | && strcmp("North", gravity)!=0 277 | && strcmp("South", gravity)!=0 278 | && strcmp("NorthEast", gravity)!=0 279 | && strcmp("NorthWest", gravity)!=0 280 | && strcmp("SouthEast", gravity)!=0 281 | && strcmp("SouthWest", gravity)!=0 282 | && strcmp("None", gravity)!=0 283 | ) { 284 | context->error = std::string("gravity not supported"); 285 | return; 286 | } 287 | if (debug) printf( "gravity: %s\n", gravity ); 288 | 289 | if( ! context->format.empty() ){ 290 | if (debug) printf( "format: %s\n", context->format.c_str() ); 291 | image.magick( context->format.c_str() ); 292 | } 293 | 294 | if( ! context->filter.empty() ){ 295 | const char *filter = context->filter.c_str(); 296 | 297 | ssize_t option_info = MagickCore::ParseCommandOption(MagickCore::MagickFilterOptions, Magick::MagickFalse, filter); 298 | if (option_info != -1) { 299 | if (debug) printf( "filter: %s\n", filter ); 300 | image.filterType( (Magick::FilterTypes)option_info ); 301 | } 302 | else { 303 | context->error = std::string("filter not supported"); 304 | return; 305 | } 306 | } 307 | 308 | if( ! context->blur.empty() ) { 309 | double blur = atof (context->blur.c_str()); 310 | if (debug) printf( "blur: %.1f\n", blur ); 311 | image.blur(0, blur); 312 | } 313 | 314 | if ( width || height ) { 315 | if ( ! width ) { width = image.columns(); } 316 | if ( ! height ) { height = image.rows(); } 317 | 318 | // do resize 319 | if ( strcmp( resizeStyle, "aspectfill" ) == 0 ) { 320 | // ^ : Fill Area Flag ('^' flag) 321 | // is not implemented in Magick++ 322 | // and gravity: center, extent doesnt look like working as exptected 323 | // so we do it ourselves 324 | 325 | // keep aspect ratio, get the exact provided size, crop top/bottom or left/right if necessary 326 | double aspectratioExpected = (double)height / (double)width; 327 | double aspectratioOriginal = (double)image.rows() / (double)image.columns(); 328 | unsigned int xoffset = 0; 329 | unsigned int yoffset = 0; 330 | unsigned int resizewidth; 331 | unsigned int resizeheight; 332 | 333 | if ( aspectratioExpected > aspectratioOriginal ) { 334 | // expected is taller 335 | resizewidth = (unsigned int)( (double)height / (double)image.rows() * (double)image.columns() + 1. ); 336 | resizeheight = height; 337 | if ( strstr(gravity, "West") != NULL ) { 338 | xoffset = 0; 339 | } 340 | else if ( strstr(gravity, "East") != NULL ) { 341 | xoffset = (unsigned int)( resizewidth - width ); 342 | } 343 | else { 344 | xoffset = (unsigned int)( (resizewidth - width) / 2. ); 345 | } 346 | yoffset = 0; 347 | } 348 | else { 349 | // expected is wider 350 | resizewidth = width; 351 | resizeheight = (unsigned int)( (double)width / (double)image.columns() * (double)image.rows() + 1. ); 352 | xoffset = 0; 353 | if ( strstr(gravity, "North") != NULL ) { 354 | yoffset = 0; 355 | } 356 | else if ( strstr(gravity, "South") != NULL ) { 357 | yoffset = (unsigned int)( resizeheight - height ); 358 | } 359 | else { 360 | yoffset = (unsigned int)( (resizeheight - height) / 2. ); 361 | } 362 | } 363 | 364 | if (debug) printf( "resize to: %d, %d\n", resizewidth, resizeheight ); 365 | Magick::Geometry resizeGeometry( resizewidth, resizeheight, 0, 0, 0, 0 ); 366 | try { 367 | image.zoom( resizeGeometry ); 368 | } 369 | catch (std::exception& err) { 370 | std::string message = "image.resize failed with error: "; 371 | message += err.what(); 372 | context->error = message; 373 | return; 374 | } 375 | catch (...) { 376 | context->error = std::string("unhandled error"); 377 | return; 378 | } 379 | 380 | if ( strcmp ( gravity, "None" ) != 0 ) { 381 | // limit canvas size to cropGeometry 382 | if (debug) printf( "crop to: %d, %d, %d, %d\n", width, height, xoffset, yoffset ); 383 | Magick::Geometry cropGeometry( width, height, xoffset, yoffset, 0, 0 ); 384 | 385 | Magick::Color transparent( "transparent" ); 386 | if ( strcmp( context->format.c_str(), "PNG" ) == 0 ) { 387 | // make background transparent for PNG 388 | // JPEG background becomes black if set transparent here 389 | transparent.alpha( 1. ); 390 | } 391 | 392 | #if MagickLibVersion > 0x654 393 | image.extent( cropGeometry, transparent ); 394 | #else 395 | image.extent( cropGeometry ); 396 | #endif 397 | } 398 | 399 | } 400 | else if ( strcmp ( resizeStyle, "aspectfit" ) == 0 ) { 401 | // keep aspect ratio, get the maximum image which fits inside specified size 402 | char geometryString[ 32 ]; 403 | sprintf( geometryString, "%dx%d", width, height ); 404 | if (debug) printf( "resize to: %s\n", geometryString ); 405 | 406 | try { 407 | image.zoom( geometryString ); 408 | } 409 | catch (std::exception& err) { 410 | std::string message = "image.resize failed with error: "; 411 | message += err.what(); 412 | context->error = message; 413 | return; 414 | } 415 | catch (...) { 416 | context->error = std::string("unhandled error"); 417 | return; 418 | } 419 | } 420 | else if ( strcmp ( resizeStyle, "fill" ) == 0 ) { 421 | // change aspect ratio and fill specified size 422 | char geometryString[ 32 ]; 423 | sprintf( geometryString, "%dx%d!", width, height ); 424 | if (debug) printf( "resize to: %s\n", geometryString ); 425 | 426 | try { 427 | image.zoom( geometryString ); 428 | } 429 | catch (std::exception& err) { 430 | std::string message = "image.resize failed with error: "; 431 | message += err.what(); 432 | context->error = message; 433 | return; 434 | } 435 | catch (...) { 436 | context->error = std::string("unhandled error"); 437 | return; 438 | } 439 | } 440 | else if ( strcmp ( resizeStyle, "crop" ) == 0 ) { 441 | unsigned int xoffset = context->xoffset; 442 | unsigned int yoffset = context->yoffset; 443 | 444 | if ( ! xoffset ) { xoffset = 0; } 445 | if ( ! yoffset ) { yoffset = 0; } 446 | 447 | // limit canvas size to cropGeometry 448 | if (debug) printf( "crop to: %d, %d, %d, %d\n", width, height, xoffset, yoffset ); 449 | Magick::Geometry cropGeometry( width, height, xoffset, yoffset, 0, 0 ); 450 | 451 | Magick::Color transparent( "transparent" ); 452 | if ( strcmp( context->format.c_str(), "PNG" ) == 0 ) { 453 | // make background transparent for PNG 454 | // JPEG background becomes black if set transparent here 455 | transparent.alpha( 1. ); 456 | } 457 | 458 | #if MagickLibVersion > 0x654 459 | image.extent( cropGeometry, transparent ); 460 | #else 461 | image.extent( cropGeometry ); 462 | #endif 463 | 464 | } 465 | else { 466 | context->error = std::string("resizeStyle not supported"); 467 | return; 468 | } 469 | if (debug) printf( "resized to: %d, %d\n", (int)image.columns(), (int)image.rows() ); 470 | } 471 | 472 | if ( context->quality ) { 473 | if (debug) printf( "quality: %d\n", context->quality ); 474 | image.quality( context->quality ); 475 | } 476 | 477 | if ( context->autoOrient ) { 478 | if ( debug ) printf( "autoOrient\n" ); 479 | AutoOrient(&image); 480 | } 481 | else { 482 | if ( context->rotate ) { 483 | if (debug) printf( "rotate: %d\n", context->rotate ); 484 | image.rotate( context->rotate ); 485 | } 486 | 487 | if ( context->flip ) { 488 | if ( debug ) printf( "flip\n" ); 489 | image.flip(); 490 | } 491 | } 492 | 493 | if (context->density) { 494 | image.density(Magick::Geometry(context->density, context->density)); 495 | } 496 | 497 | if( context->colorspace != Magick::UndefinedColorspace ){ 498 | if (debug) printf( "colorspace: %s\n", MagickCore::CommandOptionToMnemonic(MagickCore::MagickColorspaceOptions, static_cast(context->colorspace)) ); 499 | image.colorSpace( context->colorspace ); 500 | } 501 | 502 | Magick::Blob dstBlob; 503 | try { 504 | image.write( &dstBlob ); 505 | } 506 | catch (std::exception& err) { 507 | std::string message = "image.write failed with error: "; 508 | message += err.what(); 509 | context->error = message; 510 | return; 511 | } 512 | catch (...) { 513 | context->error = std::string("unhandled error"); 514 | return; 515 | } 516 | context->dstBlob = dstBlob; 517 | } 518 | 519 | // Make callback from convert or composite 520 | void GeneratedBlobAfter(uv_work_t* req) { 521 | Nan::HandleScope scope; 522 | 523 | im_ctx_base* context = static_cast(req->data); 524 | delete req; 525 | 526 | Local argv[2]; 527 | 528 | if (!context->error.empty()) { 529 | argv[0] = Exception::Error(Nan::New(context->error.c_str()).ToLocalChecked()); 530 | argv[1] = Nan::Undefined(); 531 | } 532 | else { 533 | argv[0] = Nan::Undefined(); 534 | argv[1] = WrapPointer((char *)context->dstBlob.data(), context->dstBlob.length()); 535 | } 536 | 537 | Nan::TryCatch try_catch; // don't quite see the necessity of this 538 | 539 | Nan::AsyncResource resource("GeneratedBlobAfter"); 540 | context->callback->Call(2, argv, &resource); 541 | 542 | delete context->callback; 543 | 544 | delete context; 545 | 546 | if (try_catch.HasCaught()) { 547 | #if NODE_VERSION_AT_LEAST(0, 12, 0) 548 | Nan::FatalException(try_catch); 549 | #else 550 | FatalException(try_catch); 551 | #endif 552 | } 553 | } 554 | 555 | // input 556 | // info[ 0 ]: options. required, object with following key,values 557 | // { 558 | // srcData: required. Buffer with binary image data 559 | // quality: optional. 0-100 integer, default 75. JPEG/MIFF/PNG compression level. 560 | // trim: optional. default: false. trims edges that are the background color. 561 | // trimFuzz: optional. [0-1) float, default 0. trimmed color distance to edge color, 0 is exact. 562 | // width: optional. px. 563 | // height: optional. px. 564 | // resizeStyle: optional. default: "aspectfill". can be "aspectfit", "fill" 565 | // gravity: optional. default: "Center". used when resizeStyle is "aspectfill" 566 | // can be "NorthWest", "North", "NorthEast", "West", 567 | // "Center", "East", "SouthWest", "South", "SouthEast", "None" 568 | // format: optional. one of http://www.imagemagick.org/script/formats.php ex: "JPEG" 569 | // filter: optional. ex: "Lagrange", "Lanczos". see ImageMagick's magick/option.c for candidates 570 | // blur: optional. ex: 0.8 571 | // strip: optional. default: false. strips comments out from image. 572 | // maxMemory: optional. set the maximum width * height of an image that can reside in the pixel cache memory. 573 | // debug: optional. 1 or 0 574 | // } 575 | // info[ 1 ]: callback. optional, if present runs async and returns result with callback(error, buffer) 576 | NAN_METHOD(Convert) { 577 | Nan::HandleScope(); 578 | 579 | bool isSync = (info.Length() == 1); 580 | 581 | if ( info.Length() < 1 ) { 582 | return Nan::ThrowError("convert() requires 1 (option) argument!"); 583 | } 584 | 585 | if ( ! info[ 0 ]->IsObject() ) { 586 | return Nan::ThrowError("convert()'s 1st argument should be an object"); 587 | } 588 | 589 | if( ! isSync && ! info[ 1 ]->IsFunction() ) { 590 | return Nan::ThrowError("convert()'s 2nd argument should be a function"); 591 | } 592 | 593 | Local obj = Local::Cast( info[ 0 ] ); 594 | 595 | Local srcData = Local::Cast( Nan::Get( obj, Nan::New("srcData").ToLocalChecked() ).ToLocalChecked() ); 596 | if ( srcData->IsUndefined() || ! Buffer::HasInstance(srcData) ) { 597 | return Nan::ThrowError("convert()'s 1st argument should have \"srcData\" key with a Buffer instance"); 598 | } 599 | 600 | convert_im_ctx* context = new convert_im_ctx(); 601 | context->srcData = Buffer::Data(srcData); 602 | context->length = Buffer::Length(srcData); 603 | 604 | context->debug = Nan::To(Nan::Get( obj, Nan::New("debug").ToLocalChecked() ).ToLocalChecked()).ToLocalChecked()->Value(); 605 | context->ignoreWarnings = Nan::To(Nan::Get( obj, Nan::New("ignoreWarnings").ToLocalChecked() ).ToLocalChecked()).ToLocalChecked()->Value(); 606 | context->maxMemory = Nan::To(Nan::Get( obj, Nan::New("maxMemory").ToLocalChecked() ).ToLocalChecked()).ToLocalChecked()->Value(); 607 | context->width = Nan::To(Nan::Get( obj, Nan::New("width").ToLocalChecked() ).ToLocalChecked()).ToLocalChecked()->Value(); 608 | context->height = Nan::To(Nan::Get( obj, Nan::New("height").ToLocalChecked() ).ToLocalChecked()).ToLocalChecked()->Value(); 609 | context->xoffset = Nan::To(Nan::Get( obj, Nan::New("xoffset").ToLocalChecked() ).ToLocalChecked()).ToLocalChecked()->Value(); 610 | context->yoffset = Nan::To(Nan::Get( obj, Nan::New("yoffset").ToLocalChecked() ).ToLocalChecked()).ToLocalChecked()->Value(); 611 | context->quality = Nan::To(Nan::Get( obj, Nan::New("quality").ToLocalChecked() ).ToLocalChecked()).ToLocalChecked()->Value(); 612 | context->rotate = Nan::To(Nan::Get( obj, Nan::New("rotate").ToLocalChecked() ).ToLocalChecked()).ToLocalChecked()->Value(); 613 | context->flip = Nan::To(Nan::Get( obj, Nan::New("flip").ToLocalChecked() ).ToLocalChecked()).ToLocalChecked()->Value(); 614 | context->density = Nan::To(Nan::Get( obj, Nan::New("density").ToLocalChecked() ).ToLocalChecked()).ToLocalChecked()->Value(); 615 | 616 | Local trimValue = Nan::Get( obj, Nan::New("trim").ToLocalChecked() ).ToLocalChecked(); 617 | if ( (context->trim = ! trimValue->IsUndefined() && Nan::To(trimValue).ToLocalChecked()->IsTrue()) ) { 618 | context->trimFuzz = Nan::To(Nan::Get( obj, Nan::New("trimFuzz").ToLocalChecked() ).ToLocalChecked()).ToLocalChecked()->Value() * (double) (1L << MAGICKCORE_QUANTUM_DEPTH); 619 | } 620 | 621 | Local stripValue = Nan::Get( obj, Nan::New("strip").ToLocalChecked() ).ToLocalChecked(); 622 | context->strip = ! stripValue->IsUndefined() && Nan::To(stripValue).ToLocalChecked()->IsTrue(); 623 | 624 | Local autoOrientValue = Nan::Get( obj, Nan::New("autoOrient").ToLocalChecked() ).ToLocalChecked(); 625 | context->autoOrient = ! autoOrientValue->IsUndefined() && Nan::To(autoOrientValue).ToLocalChecked()->IsTrue(); 626 | 627 | // manage blur as string for detect is empty 628 | Local blurValue = Nan::Get( obj, Nan::New("blur").ToLocalChecked() ).ToLocalChecked(); 629 | context->blur = ""; 630 | if ( ! blurValue->IsUndefined() ) { 631 | double blurD = Nan::To(blurValue).ToLocalChecked()->Value(); 632 | std::ostringstream strs; 633 | strs << blurD; 634 | context->blur = strs.str(); 635 | } 636 | 637 | Local resizeStyleValue = Nan::Get( obj, Nan::New("resizeStyle").ToLocalChecked() ).ToLocalChecked(); 638 | context->resizeStyle = !resizeStyleValue->IsUndefined() ? 639 | *Nan::Utf8String(resizeStyleValue) : "aspectfill"; 640 | 641 | Local gravityValue = Nan::Get( obj, Nan::New("gravity").ToLocalChecked() ).ToLocalChecked(); 642 | context->gravity = !gravityValue->IsUndefined() ? 643 | *Nan::Utf8String(gravityValue) : "Center"; 644 | 645 | Local formatValue = Nan::Get( obj, Nan::New("format").ToLocalChecked() ).ToLocalChecked(); 646 | context->format = !formatValue->IsUndefined() ? 647 | *Nan::Utf8String(formatValue) : ""; 648 | 649 | Local srcFormatValue = Nan::Get( obj, Nan::New("srcFormat").ToLocalChecked() ).ToLocalChecked(); 650 | context->srcFormat = !srcFormatValue->IsUndefined() ? 651 | *Nan::Utf8String(srcFormatValue) : ""; 652 | 653 | Local filterValue = Nan::Get( obj, Nan::New("filter").ToLocalChecked() ).ToLocalChecked(); 654 | context->filter = !filterValue->IsUndefined() ? 655 | *Nan::Utf8String(filterValue) : ""; 656 | 657 | Local backgroundValue = Nan::Get( obj, Nan::New("background").ToLocalChecked() ).ToLocalChecked(); 658 | context->background = !backgroundValue->IsUndefined() ? 659 | *Nan::Utf8String(backgroundValue) : ""; 660 | 661 | ssize_t colorspace = -1; 662 | Local colorspaceValue = Nan::Get( obj, Nan::New("colorspace").ToLocalChecked() ).ToLocalChecked(); 663 | if (!colorspaceValue->IsUndefined()) { 664 | colorspace = MagickCore::ParseCommandOption(MagickCore::MagickColorspaceOptions, MagickCore::MagickFalse, *Nan::Utf8String(colorspaceValue)); 665 | if (context->debug) printf("Parsing colorspace option \"%s\" to %ld\n", *Nan::Utf8String(colorspaceValue), colorspace); 666 | } 667 | context->colorspace = colorspace != (-1) ? (Magick::ColorspaceType) colorspace : Magick::UndefinedColorspace; 668 | 669 | uv_work_t* req = new uv_work_t(); 670 | req->data = context; 671 | if(!isSync) { 672 | context->callback = new Nan::Callback(Local::Cast(info[1])); 673 | 674 | uv_queue_work(uv_default_loop(), req, DoConvert, (uv_after_work_cb)GeneratedBlobAfter); 675 | 676 | return; 677 | } else { 678 | DoConvert(req); 679 | RETURN_BLOB_OR_ERROR(req) 680 | } 681 | } 682 | 683 | void DoIdentify(uv_work_t* req) { 684 | 685 | MagickCore::SetMagickResourceLimit(MagickCore::ThreadResource, 1); 686 | 687 | identify_im_ctx* context = static_cast(req->data); 688 | 689 | Magick::Blob srcBlob( context->srcData, context->length ); 690 | 691 | Magick::Image image; 692 | 693 | if( ! context->srcFormat.empty() ){ 694 | if (context->debug) printf( "reading with format: %s\n", context->srcFormat.c_str() ); 695 | image.magick( context->srcFormat.c_str() ); 696 | } 697 | 698 | try { 699 | image.read( srcBlob ); 700 | } 701 | catch (std::exception& err) { 702 | std::string what (err.what()); 703 | std::string message = std::string("image.read failed with error: ") + what; 704 | std::size_t found = what.find( "warn" ); 705 | if (context->ignoreWarnings && (found != std::string::npos)) { 706 | if (context->debug) printf("warning: %s\n", message.c_str()); 707 | } 708 | else { 709 | context->error = message; 710 | } 711 | } 712 | catch (...) { 713 | context->error = std::string("unhandled error"); 714 | } 715 | if(!context->error.empty()) { 716 | return; 717 | } 718 | 719 | if (context->debug) printf("original width,height: %d, %d\n", (int) image.columns(), (int) image.rows()); 720 | 721 | context->image = image; 722 | } 723 | 724 | void BuildIdentifyResult(uv_work_t *req, Local *argv) { 725 | identify_im_ctx* context = static_cast(req->data); 726 | 727 | if (!context->error.empty()) { 728 | argv[0] = Exception::Error(Nan::New(context->error.c_str()).ToLocalChecked()); 729 | argv[1] = Nan::Undefined(); 730 | } 731 | else { 732 | argv[0] = Nan::Undefined(); 733 | Local out = Nan::New(); 734 | 735 | Nan::Set(out, Nan::New("width").ToLocalChecked(), Nan::New(static_cast(context->image.columns()))); 736 | Nan::Set(out, Nan::New("height").ToLocalChecked(), Nan::New(static_cast(context->image.rows()))); 737 | Nan::Set(out, Nan::New("depth").ToLocalChecked(), Nan::New(static_cast(context->image.depth()))); 738 | Nan::Set(out, Nan::New("format").ToLocalChecked(), Nan::New(context->image.magick().c_str()).ToLocalChecked()); 739 | Nan::Set(out, Nan::New("colorspace").ToLocalChecked(), Nan::New(MagickCore::CommandOptionToMnemonic(MagickCore::MagickColorspaceOptions, static_cast(context->image.colorSpace()))).ToLocalChecked()); 740 | 741 | Local out_density = Nan::New(); 742 | Magick::Geometry density = context->image.density(); 743 | Nan::Set(out_density, Nan::New("width").ToLocalChecked(), Nan::New(static_cast(density.width()))); 744 | Nan::Set(out_density, Nan::New("height").ToLocalChecked(), Nan::New(static_cast(density.height()))); 745 | Nan::Set(out, Nan::New("density").ToLocalChecked(), out_density); 746 | 747 | Local out_exif = Nan::New(); 748 | Nan::Set(out_exif, Nan::New("orientation").ToLocalChecked(), Nan::New(atoi(context->image.attribute("EXIF:Orientation").c_str()))); 749 | Nan::Set(out, Nan::New("exif").ToLocalChecked(), out_exif); 750 | 751 | argv[1] = out; 752 | } 753 | } 754 | 755 | void IdentifyAfter(uv_work_t* req) { 756 | Nan::HandleScope scope; 757 | 758 | Local argv[2]; 759 | BuildIdentifyResult(req,argv); 760 | 761 | identify_im_ctx* context = static_cast(req->data); 762 | 763 | Nan::TryCatch try_catch; // don't quite see the necessity of this 764 | 765 | Nan::AsyncResource resource("GeneratedBlobAfter"); 766 | context->callback->Call(2, argv, &resource); 767 | 768 | delete context->callback; 769 | delete context; 770 | delete req; 771 | 772 | if (try_catch.HasCaught()) { 773 | #if NODE_VERSION_AT_LEAST(0, 12, 0) 774 | Nan::FatalException(try_catch); 775 | #else 776 | FatalException(try_catch); 777 | #endif 778 | } 779 | } 780 | 781 | // input 782 | // info[ 0 ]: options. required, object with following key,values 783 | // { 784 | // srcData: required. Buffer with binary image data 785 | // debug: optional. 1 or 0 786 | // } 787 | // info[ 1 ]: callback. optional, if present runs async and returns result with callback(error, info) 788 | NAN_METHOD(Identify) { 789 | Nan::HandleScope scope; 790 | 791 | bool isSync = info.Length() == 1; 792 | 793 | if ( info.Length() < 1 ) { 794 | return Nan::ThrowError("identify() requires 1 (option) argument!"); 795 | } 796 | Local obj = Local::Cast( info[ 0 ] ); 797 | 798 | Local srcData = Local::Cast( Nan::Get( obj, Nan::New("srcData").ToLocalChecked() ).ToLocalChecked() ); 799 | if ( srcData->IsUndefined() || ! Buffer::HasInstance(srcData) ) { 800 | return Nan::ThrowError("identify()'s 1st argument should have \"srcData\" key with a Buffer instance"); 801 | } 802 | 803 | if( ! isSync && ! info[ 1 ]->IsFunction() ) { 804 | return Nan::ThrowError("identify()'s 2nd argument should be a function"); 805 | } 806 | 807 | identify_im_ctx* context = new identify_im_ctx(); 808 | context->srcData = Buffer::Data(srcData); 809 | context->length = Buffer::Length(srcData); 810 | 811 | context->debug = Nan::To(Nan::Get( obj, Nan::New("debug").ToLocalChecked() ).ToLocalChecked()).ToLocalChecked()->Value(); 812 | context->ignoreWarnings = Nan::To(Nan::Get( obj, Nan::New("ignoreWarnings").ToLocalChecked() ).ToLocalChecked()).ToLocalChecked()->Value(); 813 | 814 | Local srcFormatValue = Nan::Get( obj, Nan::New("srcFormat").ToLocalChecked() ).ToLocalChecked(); 815 | context->srcFormat = !srcFormatValue->IsUndefined() ? 816 | *Nan::Utf8String(srcFormatValue) : ""; 817 | 818 | if (context->debug) printf( "debug: on\n" ); 819 | if (context->debug) printf( "ignoreWarnings: %d\n", context->ignoreWarnings ); 820 | 821 | uv_work_t* req = new uv_work_t(); 822 | req->data = context; 823 | if(!isSync) { 824 | context->callback = new Nan::Callback(Local::Cast(info[1])); 825 | 826 | uv_queue_work(uv_default_loop(), req, DoIdentify, (uv_after_work_cb)IdentifyAfter); 827 | 828 | return; 829 | } else { 830 | DoIdentify(req); 831 | Local argv[2]; 832 | BuildIdentifyResult(req, argv); 833 | delete static_cast(req->data); 834 | delete req; 835 | if(argv[0]->IsUndefined()){ 836 | info.GetReturnValue().Set(argv[1]); 837 | } else { 838 | return Nan::ThrowError(argv[0]); 839 | } 840 | } 841 | } 842 | 843 | // input 844 | // info[ 0 ]: options. required, object with following key,values 845 | // { 846 | // srcData: required. Buffer with binary image data 847 | // x: required. x,y,columns,rows provide the area of interest. 848 | // y: required. 849 | // columns: required. 850 | // rows: required. 851 | // } 852 | NAN_METHOD(GetConstPixels) { 853 | Nan::HandleScope(); 854 | MagickCore::SetMagickResourceLimit(MagickCore::ThreadResource, 1); 855 | 856 | if ( info.Length() != 1 ) { 857 | return Nan::ThrowError("getConstPixels() requires 1 (option) argument!"); 858 | } 859 | Local obj = Local::Cast( info[ 0 ] ); 860 | 861 | Local srcData = Local::Cast( Nan::Get( obj, Nan::New("srcData").ToLocalChecked() ).ToLocalChecked() ); 862 | if ( srcData->IsUndefined() || ! Buffer::HasInstance(srcData) ) { 863 | return Nan::ThrowError("getConstPixels()'s 1st argument should have \"srcData\" key with a Buffer instance"); 864 | } 865 | 866 | unsigned int xValue = Nan::To(Nan::Get( obj, Nan::New("x").ToLocalChecked() ).ToLocalChecked()).ToLocalChecked()->Value(); 867 | unsigned int yValue = Nan::To(Nan::Get( obj, Nan::New("y").ToLocalChecked() ).ToLocalChecked()).ToLocalChecked()->Value(); 868 | unsigned int columnsValue = Nan::To(Nan::Get( obj, Nan::New("columns").ToLocalChecked() ).ToLocalChecked()).ToLocalChecked()->Value(); 869 | unsigned int rowsValue = Nan::To(Nan::Get( obj, Nan::New("rows").ToLocalChecked() ).ToLocalChecked()).ToLocalChecked()->Value(); 870 | 871 | int debug = Nan::To(Nan::Get( obj, Nan::New("debug").ToLocalChecked() ).ToLocalChecked()).ToLocalChecked()->Value(); 872 | int ignoreWarnings = Nan::To(Nan::Get( obj, Nan::New("ignoreWarnings").ToLocalChecked() ).ToLocalChecked()).ToLocalChecked()->Value(); 873 | if (debug) printf( "debug: on\n" ); 874 | if (debug) printf( "ignoreWarnings: %d\n", ignoreWarnings ); 875 | 876 | Magick::Blob srcBlob( Buffer::Data(srcData), Buffer::Length(srcData)); 877 | 878 | Magick::Image image; 879 | try { 880 | image.read( srcBlob ); 881 | } 882 | catch (std::exception& err) { 883 | std::string what (err.what()); 884 | std::string message = std::string("image.read failed with error: ") + what; 885 | std::size_t found = what.find( "warn" ); 886 | if (ignoreWarnings && (found != std::string::npos)) { 887 | if (debug) printf("warning: %s\n", message.c_str()); 888 | } 889 | else { 890 | return Nan::ThrowError(message.c_str()); 891 | } 892 | } 893 | catch (...) { 894 | return Nan::ThrowError("unhandled error"); 895 | } 896 | 897 | size_t w = image.columns(); 898 | size_t h = image.rows(); 899 | 900 | if (xValue+columnsValue > w || yValue+rowsValue > h) { 901 | return Nan::ThrowError("x/y/columns/rows values are beyond the image\'s dimensions"); 902 | } 903 | 904 | const Magick::PixelPacket *pixels = image.getConstPixels(xValue, yValue, columnsValue, rowsValue); 905 | 906 | Local out = Nan::New(); 907 | for (unsigned int i=0; i color = Nan::New(); 910 | 911 | Nan::Set(color, Nan::New("red").ToLocalChecked(), Nan::New(pixel.red)); 912 | Nan::Set(color, Nan::New("green").ToLocalChecked(), Nan::New(pixel.green)); 913 | Nan::Set(color, Nan::New("blue").ToLocalChecked(), Nan::New(pixel.blue)); 914 | Nan::Set(color, Nan::New("opacity").ToLocalChecked(), Nan::New(pixel.opacity)); 915 | 916 | Nan::Set(out, i, color); 917 | } 918 | 919 | info.GetReturnValue().Set(out); 920 | } 921 | 922 | // input 923 | // info[ 0 ]: options. required, object with following key,values 924 | // { 925 | // srcData: required. Buffer with binary image data 926 | // colors: optional. 5 by default 927 | // debug: optional. 1 or 0 928 | // } 929 | NAN_METHOD(QuantizeColors) { 930 | Nan::HandleScope(); 931 | MagickCore::SetMagickResourceLimit(MagickCore::ThreadResource, 1); 932 | 933 | if ( info.Length() != 1 ) { 934 | return Nan::ThrowError("quantizeColors() requires 1 (option) argument!"); 935 | } 936 | Local obj = Local::Cast( info[ 0 ] ); 937 | 938 | Local srcData = Local::Cast( Nan::Get( obj, Nan::New("srcData").ToLocalChecked() ).ToLocalChecked() ); 939 | if ( srcData->IsUndefined() || ! Buffer::HasInstance(srcData) ) { 940 | return Nan::ThrowError("quantizeColors()'s 1st argument should have \"srcData\" key with a Buffer instance"); 941 | } 942 | 943 | int colorsCount = Nan::To(Nan::Get( obj, Nan::New("colors").ToLocalChecked() ).ToLocalChecked()).ToLocalChecked()->Value(); 944 | if (!colorsCount) colorsCount = 5; 945 | 946 | int debug = Nan::To(Nan::Get( obj, Nan::New("debug").ToLocalChecked() ).ToLocalChecked()).ToLocalChecked()->Value(); 947 | int ignoreWarnings = Nan::To(Nan::Get( obj, Nan::New("ignoreWarnings").ToLocalChecked() ).ToLocalChecked()).ToLocalChecked()->Value(); 948 | if (debug) printf( "debug: on\n" ); 949 | if (debug) printf( "ignoreWarnings: %d\n", ignoreWarnings ); 950 | 951 | Magick::Blob srcBlob( Buffer::Data(srcData), Buffer::Length(srcData) ); 952 | 953 | Magick::Image image; 954 | try { 955 | image.read( srcBlob ); 956 | } 957 | catch (std::exception& err) { 958 | std::string what (err.what()); 959 | std::string message = std::string("image.read failed with error: ") + what; 960 | std::size_t found = what.find( "warn" ); 961 | if (ignoreWarnings && (found != std::string::npos)) { 962 | if (debug) printf("warning: %s\n", message.c_str()); 963 | } 964 | else { 965 | return Nan::ThrowError(message.c_str()); 966 | } 967 | } 968 | catch (...) { 969 | return Nan::ThrowError("unhandled error"); 970 | } 971 | 972 | ssize_t rows = 196; ssize_t columns = 196; 973 | 974 | if (debug) printf( "resize to: %d, %d\n", (int) rows, (int) columns ); 975 | Magick::Geometry resizeGeometry( rows, columns, 0, 0, 0, 0 ); 976 | image.zoom( resizeGeometry ); 977 | 978 | if (debug) printf("totalColors before: %d\n", (int) image.totalColors()); 979 | 980 | image.quantizeColors(colorsCount + 1); 981 | image.quantize(); 982 | 983 | if (debug) printf("totalColors after: %d\n", (int) image.totalColors()); 984 | 985 | Magick::PixelPacket* pixels = image.getPixels(0, 0, image.columns(), image.rows()); 986 | 987 | Magick::PixelPacket* colors = new Magick::PixelPacket[colorsCount](); 988 | int index = 0; 989 | 990 | for ( ssize_t x = 0; x < rows ; x++ ) { 991 | for ( ssize_t y = 0; y < columns ; y++ ) { 992 | Magick::PixelPacket pixel = pixels[rows * x + y]; 993 | 994 | bool found = false; 995 | for(int x = 0; x < colorsCount; x++) 996 | if (pixel.red == colors[x].red && pixel.green == colors[x].green && pixel.blue == colors[x].blue) found = true; 997 | 998 | if (!found) colors[index++] = pixel; 999 | if (index >= colorsCount) break; 1000 | } 1001 | if (index >= colorsCount) break; 1002 | } 1003 | 1004 | Local out = Nan::New(); 1005 | 1006 | for(int x = 0; x < colorsCount; x++) 1007 | if (debug) printf("found rgb : %d %d %d\n", ((int) colors[x].red) / 255, ((int) colors[x].green) / 255, ((int) colors[x].blue) / 255); 1008 | 1009 | for(int x = 0; x < colorsCount; x++) { 1010 | Local color = Nan::New(); 1011 | 1012 | int r = ((int) colors[x].red) / 255; 1013 | if (r > 255) r = 255; 1014 | 1015 | int g = ((int) colors[x].green) / 255; 1016 | if (g > 255) g = 255; 1017 | 1018 | int b = ((int) colors[x].blue) / 255; 1019 | if (b > 255) b = 255; 1020 | 1021 | Nan::Set(color, Nan::New("r").ToLocalChecked(), Nan::New(r)); 1022 | Nan::Set(color, Nan::New("g").ToLocalChecked(), Nan::New(g)); 1023 | Nan::Set(color, Nan::New("b").ToLocalChecked(), Nan::New(b)); 1024 | 1025 | char hexcol[16]; 1026 | snprintf(hexcol, sizeof hexcol, "%02x%02x%02x", r, g, b); 1027 | Nan::Set(color, Nan::New("hex").ToLocalChecked(), Nan::New(hexcol).ToLocalChecked()); 1028 | 1029 | Nan::Set(out, x, color); 1030 | } 1031 | 1032 | delete[] colors; 1033 | 1034 | info.GetReturnValue().Set(out); 1035 | } 1036 | 1037 | void DoComposite(uv_work_t* req) { 1038 | 1039 | composite_im_ctx* context = static_cast(req->data); 1040 | 1041 | MagickCore::SetMagickResourceLimit(MagickCore::ThreadResource, 1); 1042 | 1043 | if (context->debug) printf( "debug: on\n" ); 1044 | if (context->debug) printf( "ignoreWarnings: %d\n", context->ignoreWarnings ); 1045 | 1046 | Magick::Blob srcBlob( context->srcData, context->length ); 1047 | Magick::Blob compositeBlob( context->compositeData, context->compositeLength ); 1048 | 1049 | Magick::Image image; 1050 | 1051 | if ( !ReadImageMagick(&image, srcBlob, "", context) ) 1052 | return; 1053 | 1054 | Magick::GravityType gravityType; 1055 | 1056 | const char* gravity = context->gravity.c_str(); 1057 | 1058 | if(strcmp("CenterGravity", gravity)==0) gravityType=Magick::CenterGravity; 1059 | else if(strcmp("EastGravity", gravity)==0) gravityType=Magick::EastGravity; 1060 | else if(strcmp("ForgetGravity", gravity)==0) gravityType=Magick::ForgetGravity; 1061 | else if(strcmp("NorthEastGravity", gravity)==0) gravityType=Magick::NorthEastGravity; 1062 | else if(strcmp("NorthGravity", gravity)==0) gravityType=Magick::NorthGravity; 1063 | else if(strcmp("NorthWestGravity", gravity)==0) gravityType=Magick::NorthWestGravity; 1064 | else if(strcmp("SouthEastGravity", gravity)==0) gravityType=Magick::SouthEastGravity; 1065 | else if(strcmp("SouthGravity", gravity)==0) gravityType=Magick::SouthGravity; 1066 | else if(strcmp("SouthWestGravity", gravity)==0) gravityType=Magick::SouthWestGravity; 1067 | else if(strcmp("WestGravity", gravity)==0) gravityType=Magick::WestGravity; 1068 | else { 1069 | gravityType = Magick::ForgetGravity; 1070 | if (context->debug) printf( "invalid gravity: '%s' fell through to ForgetGravity\n", gravity); 1071 | } 1072 | 1073 | if (context->debug) printf( "gravity: %s (%d)\n", gravity,(int) gravityType); 1074 | 1075 | Magick::Image compositeImage; 1076 | 1077 | if ( !ReadImageMagick(&compositeImage, compositeBlob, "", context) ) 1078 | return; 1079 | 1080 | image.composite(compositeImage,gravityType,Magick::OverCompositeOp); 1081 | 1082 | Magick::Blob dstBlob; 1083 | image.write( &dstBlob ); 1084 | 1085 | context->dstBlob = dstBlob; 1086 | } 1087 | 1088 | // input 1089 | // info[ 0 ]: options. required, object with following key,values 1090 | // { 1091 | // srcData: required. Buffer with binary image data 1092 | // compositeData: required. Buffer with image to composite 1093 | // gravity: optional. One of CenterGravity EastGravity 1094 | // ForgetGravity NorthEastGravity NorthGravity 1095 | // NorthWestGravity SouthEastGravity SouthGravity 1096 | // SouthWestGravity WestGravity 1097 | // debug: optional. 1 or 0 1098 | // } 1099 | // info[ 1 ]: callback. optional, if present runs async and returns result with callback(error, buffer) 1100 | NAN_METHOD(Composite) { 1101 | Nan::HandleScope(); 1102 | 1103 | bool isSync = (info.Length() == 1); 1104 | 1105 | if ( info.Length() < 1 ) { 1106 | return Nan::ThrowError("composite() requires 1 (option) argument!"); 1107 | } 1108 | Local obj = Local::Cast( info[ 0 ] ); 1109 | 1110 | Local srcData = Local::Cast( Nan::Get( obj, Nan::New("srcData").ToLocalChecked() ).ToLocalChecked() ); 1111 | if ( srcData->IsUndefined() || ! Buffer::HasInstance(srcData) ) { 1112 | return Nan::ThrowError("composite()'s 1st argument should have \"srcData\" key with a Buffer instance"); 1113 | } 1114 | 1115 | Local compositeData = Local::Cast( 1116 | Nan::Get( obj, Nan::New("compositeData").ToLocalChecked() ).ToLocalChecked() ); 1117 | if ( compositeData->IsUndefined() || ! Buffer::HasInstance(compositeData) ) { 1118 | return Nan::ThrowError("composite()'s 1st argument should have \"compositeData\" key with a Buffer instance"); 1119 | } 1120 | 1121 | if( ! isSync && ! info[ 1 ]->IsFunction() ) { 1122 | return Nan::ThrowError("composite()'s 2nd argument should be a function"); 1123 | } 1124 | 1125 | composite_im_ctx* context = new composite_im_ctx(); 1126 | context->debug = Nan::To(Nan::Get( obj, Nan::New("debug").ToLocalChecked() ).ToLocalChecked()).ToLocalChecked()->Value(); 1127 | context->ignoreWarnings = Nan::To(Nan::Get( obj, Nan::New("ignoreWarnings").ToLocalChecked() ).ToLocalChecked()).ToLocalChecked()->Value(); 1128 | 1129 | context->srcData = Buffer::Data(srcData); 1130 | context->length = Buffer::Length(srcData); 1131 | 1132 | context->compositeData = Buffer::Data(compositeData); 1133 | context->compositeLength = Buffer::Length(compositeData); 1134 | 1135 | Local gravityValue = Nan::Get( obj, Nan::New("gravity").ToLocalChecked() ).ToLocalChecked(); 1136 | context->gravity = !gravityValue->IsUndefined() ? 1137 | *Nan::Utf8String(gravityValue) : ""; 1138 | 1139 | uv_work_t* req = new uv_work_t(); 1140 | req->data = context; 1141 | if(!isSync) { 1142 | context->callback = new Nan::Callback(Local::Cast(info[1])); 1143 | 1144 | uv_queue_work(uv_default_loop(), req, DoComposite, (uv_after_work_cb)GeneratedBlobAfter); 1145 | 1146 | return; 1147 | } else { 1148 | DoComposite(req); 1149 | RETURN_BLOB_OR_ERROR(req) 1150 | } 1151 | } 1152 | 1153 | NAN_METHOD(Version) { 1154 | Nan::HandleScope(); 1155 | 1156 | info.GetReturnValue().Set(Nan::New(MagickLibVersionText).ToLocalChecked()); 1157 | } 1158 | 1159 | NAN_METHOD(GetQuantumDepth) { 1160 | Nan::HandleScope(); 1161 | 1162 | info.GetReturnValue().Set(Nan::New(MAGICKCORE_QUANTUM_DEPTH)); 1163 | } 1164 | 1165 | void init(Local exports) { 1166 | Nan::SetMethod(exports, "convert", Convert); 1167 | Nan::SetMethod(exports, "identify", Identify); 1168 | Nan::SetMethod(exports, "quantizeColors", QuantizeColors); 1169 | Nan::SetMethod(exports, "composite", Composite); 1170 | Nan::SetMethod(exports, "version", Version); 1171 | Nan::SetMethod(exports, "getConstPixels", GetConstPixels); 1172 | Nan::SetMethod(exports, "quantumDepth", GetQuantumDepth); // QuantumDepth is already defined 1173 | } 1174 | 1175 | // There is no semi-colon after NODE_MODULE as it's not a function (see node.h). 1176 | // see http://nodejs.org/api/addons.html 1177 | NODE_MODULE(imagemagick, init) 1178 | -------------------------------------------------------------------------------- /src/imagemagick.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "nan.h" 6 | 7 | using namespace v8; 8 | using namespace node; 9 | -------------------------------------------------------------------------------- /test/benchmark.js: -------------------------------------------------------------------------------- 1 | var ben = require('ben') 2 | , im = require('imagemagick') 3 | , im_native = require('..') 4 | , async = require('async') 5 | , assert = require('assert') 6 | , debug = 0 7 | ; 8 | 9 | var file = process.argv[2]; 10 | var body = require('fs').readFileSync( file ); 11 | 12 | function resize (callback) { 13 | im.resize({ 14 | width: 48, 15 | height: 48, 16 | format: 'jpg', 17 | quality: 0.8, 18 | srcData: body 19 | }, function (err,stdout,stderr) { 20 | assert( stdout.length > 0 ); 21 | // console.log( "im length: "+stdout.length ); 22 | // require('fs').writeFileSync( "./test/out.fork.jpg", stdout, 'binary' ); 23 | callback(); 24 | }); 25 | } 26 | function resize_native (callback) { 27 | var stdout = im_native.convert({ 28 | srcData: body, 29 | width: 48, 30 | height: 48, 31 | resizeStyle: 'aspectfit', 32 | quality: 80, 33 | format: 'JPEG', 34 | filter: 'Lagrange', 35 | blur: 0.8, 36 | strip: true, 37 | debug: debug 38 | }); 39 | assert( stdout.length > 0 ); 40 | // console.log( "im_native length: "+stdout.length ); 41 | // require('fs').writeFileSync( "./test/out.native.jpg", stdout, 'binary' ); 42 | callback(); 43 | } 44 | 45 | async.waterfall([ 46 | function (callback) { 47 | // console.log( "before resize: ", process.memoryUsage() ); 48 | callback(); 49 | }, 50 | function (callback) { 51 | // callback(); 52 | ben.async( resize, function (ms) { 53 | console.log( "resize: " + ms + "ms per iteration" ); 54 | callback(); 55 | }); 56 | }, 57 | function (callback) { 58 | // console.log( "after resize: ", process.memoryUsage() ); 59 | callback(); 60 | }, 61 | function (callback) { 62 | resize_native( callback ); 63 | }, 64 | function (callback) { 65 | // console.log( "after resize_native 1st: ", process.memoryUsage() ); 66 | callback(); 67 | }, 68 | function (callback) { 69 | // callback(); 70 | ben.async( resize_native, function (ms) { 71 | console.log( "resize_native: " + ms + "ms per iteration" ); 72 | callback(); 73 | }); 74 | }, 75 | function (callback) { 76 | // console.log( "after resize_native: ", process.memoryUsage() ); 77 | callback(); 78 | }, 79 | ], function(err) { 80 | if ( err ) { 81 | console.log("err: ", err); 82 | } 83 | }); 84 | 85 | /* 86 | resize: 16.09ms per iteration 87 | resize_native: 0.89ms per iteration 88 | */ 89 | -------------------------------------------------------------------------------- /test/broken.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elad/node-imagemagick-native/1831117866d41b87b19fb5f93564a0ea994aff12/test/broken.png -------------------------------------------------------------------------------- /test/leak.js: -------------------------------------------------------------------------------- 1 | var imagemagick = require('..') 2 | , debug = 1 3 | , memwatch = require('memwatch') 4 | ; 5 | 6 | memwatch.on( 'leak', function( info ) { 7 | console.log( "leak: ", info ); 8 | }); 9 | memwatch.on('stats', function(stats) { 10 | console.log( "stats: ", stats ); 11 | }); 12 | var srcData = require('fs').readFileSync( "./test/test.jpg" ); 13 | 14 | var hd = new memwatch.HeapDiff(); 15 | 16 | var ret = imagemagick.convert({ 17 | srcData: srcData, 18 | width: 100, 19 | height: 100, 20 | resizeStyle: "fill", 21 | format: 'JPEG', 22 | debug: debug 23 | }); 24 | require('assert').ok( ret ); 25 | 26 | var diff = hd.end(); 27 | console.log("diff: ", diff ); 28 | -------------------------------------------------------------------------------- /test/orientation-suite/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010 Dave Perrett, http://recursive-design.com/ 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /test/orientation-suite/Landscape_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elad/node-imagemagick-native/1831117866d41b87b19fb5f93564a0ea994aff12/test/orientation-suite/Landscape_1.jpg -------------------------------------------------------------------------------- /test/orientation-suite/Landscape_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elad/node-imagemagick-native/1831117866d41b87b19fb5f93564a0ea994aff12/test/orientation-suite/Landscape_2.jpg -------------------------------------------------------------------------------- /test/orientation-suite/Landscape_3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elad/node-imagemagick-native/1831117866d41b87b19fb5f93564a0ea994aff12/test/orientation-suite/Landscape_3.jpg -------------------------------------------------------------------------------- /test/orientation-suite/Landscape_4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elad/node-imagemagick-native/1831117866d41b87b19fb5f93564a0ea994aff12/test/orientation-suite/Landscape_4.jpg -------------------------------------------------------------------------------- /test/orientation-suite/Landscape_5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elad/node-imagemagick-native/1831117866d41b87b19fb5f93564a0ea994aff12/test/orientation-suite/Landscape_5.jpg -------------------------------------------------------------------------------- /test/orientation-suite/Landscape_6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elad/node-imagemagick-native/1831117866d41b87b19fb5f93564a0ea994aff12/test/orientation-suite/Landscape_6.jpg -------------------------------------------------------------------------------- /test/orientation-suite/Landscape_7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elad/node-imagemagick-native/1831117866d41b87b19fb5f93564a0ea994aff12/test/orientation-suite/Landscape_7.jpg -------------------------------------------------------------------------------- /test/orientation-suite/Landscape_8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elad/node-imagemagick-native/1831117866d41b87b19fb5f93564a0ea994aff12/test/orientation-suite/Landscape_8.jpg -------------------------------------------------------------------------------- /test/orientation-suite/README.markdown: -------------------------------------------------------------------------------- 1 | EXIF Orientation-flag example images 2 | ==================================== 3 | 4 | Example images using each of the EXIF orientation flags (1-to-8), in both landscape and portrait orientations. 5 | 6 | [See here](http://www.daveperrett.com/articles/2012/07/28/exif-orientation-handling-is-a-ghetto/) for more information. 7 | 8 | Change history 9 | ----------- 10 | 11 | * **Version 1.0.2 (2017-03-06)** : Remove Apple Copyrighted ICC profile from orientations 2-8 (thanks @mans0954!). 12 | * **Version 1.0.1 (2013-03-10)** : Add MIT license and some contact details. 13 | * **Version 1.0.0 (2012-07-28)** : 1.0 release. 14 | 15 | Contributing 16 | ------------ 17 | 18 | Once you've made your commits: 19 | 20 | 1. [Fork](http://help.github.com/fork-a-repo/) exif-orientation-examples 21 | 2. Create a topic branch - `git checkout -b my_branch` 22 | 3. Push to your branch - `git push origin my_branch` 23 | 4. Create a [Pull Request](http://help.github.com/pull-requests/) from your branch 24 | 5. That's it! 25 | 26 | Author 27 | ------ 28 | 29 | Dave Perrett :: hello@daveperrett.com :: [@daveperrett](http://twitter.com/daveperrett) 30 | 31 | 32 | Copyright 33 | --------- 34 | 35 | These images are licensed under the [MIT License](http://opensource.org/licenses/MIT). 36 | 37 | Copyright (c) 2010 Dave Perrett. See [License](https://github.com/recurser/exif-orientation-examples/blob/master/LICENSE) for details. -------------------------------------------------------------------------------- /test/orientation-suite/VERSION: -------------------------------------------------------------------------------- 1 | 1.0.1 -------------------------------------------------------------------------------- /test/test.CMYK.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elad/node-imagemagick-native/1831117866d41b87b19fb5f93564a0ea994aff12/test/test.CMYK.jpg -------------------------------------------------------------------------------- /test/test.async.js: -------------------------------------------------------------------------------- 1 | 2 | var test = require('tap').test 3 | , imagemagick = require('..') 4 | , debug = 0 5 | ; 6 | 7 | process.chdir(__dirname); 8 | 9 | console.log("image magick's version is: " + imagemagick.version()); 10 | var versions = imagemagick.version().split("."); 11 | 12 | function saveToFileIfDebug (buffer, file) { 13 | if (debug) { 14 | require('fs').writeFileSync( file, buffer, 'binary' ); 15 | console.log( "wrote file: "+file ); 16 | } 17 | } 18 | 19 | test( 'convert filter Lagrange', function (t) { 20 | t.plan(1); 21 | imagemagick.convert({ 22 | srcData: require('fs').readFileSync( "test.png" ), // 58x66 23 | width: 100, 24 | height: 100, 25 | filter: 'Lagrange', 26 | quality: 80, 27 | format: 'PNG', 28 | debug: debug 29 | },function(err,buffer){ 30 | t.equal( Buffer.isBuffer(buffer), true, 'buffer is Buffer' ); 31 | saveToFileIfDebug( buffer, "out.png-lagrange.png" ); 32 | t.end(); 33 | }); 34 | }); 35 | 36 | test( 'identify results async', function (t) { 37 | t.plan(5); 38 | imagemagick.identify({ 39 | srcData: require('fs').readFileSync( "test.png" ) 40 | },function(err,info){ 41 | t.equal( info.width, 58, 'width is 58' ); 42 | t.equal( info.height, 66, 'height is 66' ); 43 | t.equal( info.depth, 8, 'depth is 8' ); 44 | t.equal( info.format, 'PNG', 'format is PNG' ); 45 | t.equal( info.exif.orientation, 0, 'orientation doesnt exist' ); 46 | t.end(); 47 | }); 48 | }); 49 | 50 | test( 'composite async', function (t) { 51 | t.plan(1); 52 | imagemagick.composite({ 53 | srcData: require('fs').readFileSync( "test.quantizeColors.png" ), 54 | compositeData: require('fs').readFileSync("test.png"), 55 | debug: debug 56 | },function(err,buffer){ 57 | t.equal( Buffer.isBuffer(buffer), true, 'buffer is Buffer' ); 58 | saveToFileIfDebug( buffer, "out.composite-async.png" ); 59 | t.end(); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /test/test.auto.orient.js: -------------------------------------------------------------------------------- 1 | /* jshint node: true */ 2 | var fs = require('fs'); 3 | var path = require('path'); 4 | var test = require('tap').test; 5 | var imagemagick = require('..'); 6 | var debug = false; 7 | 8 | console.log('image magick\'s version is: ' + imagemagick.version()); 9 | 10 | function saveToFileIfDebug (buffer, file) { 11 | if (debug) { 12 | fs.writeFileSync( file, buffer, 'binary' ); 13 | console.log( 'wrote file: '+file ); 14 | } 15 | } 16 | 17 | // Test suite from https://github.com/recurser/exif-orientation-examples 18 | var testFiles = [ 19 | 'Landscape_1.jpg', 20 | 'Landscape_2.jpg', 21 | 'Landscape_3.jpg', 22 | 'Landscape_4.jpg', 23 | 'Landscape_5.jpg', 24 | 'Landscape_6.jpg', 25 | 'Landscape_7.jpg', 26 | 'Landscape_8.jpg', 27 | ]; 28 | 29 | test( 'convert autoOrient option', function (t) { 30 | t.plan(8); 31 | 32 | testFiles.forEach( function (f) { 33 | 34 | var buffer = imagemagick.convert({ 35 | srcData: fs.readFileSync( path.join(__dirname, 'orientation-suite', f) ), 36 | format: 'JPEG', 37 | autoOrient: true, 38 | debug: debug 39 | }); 40 | t.equal( Buffer.isBuffer(buffer), true, 'buffer is Buffer' ); 41 | saveToFileIfDebug( buffer, path.join(__dirname, 'out.' + f) ); 42 | }); 43 | 44 | t.end(); 45 | 46 | }); 47 | -------------------------------------------------------------------------------- /test/test.background.js: -------------------------------------------------------------------------------- 1 | var test = require('tap').test 2 | , imagemagick = require('..') 3 | , debug = false 4 | ; 5 | 6 | function saveToFileIfDebug (buffer, file) { 7 | if (debug) { 8 | require('fs').writeFileSync( file, buffer, 'binary' ); 9 | console.log( "wrote file: "+ file ); 10 | } 11 | } 12 | 13 | var colors = [ 14 | { 15 | name: 'red', 16 | value: 2 17 | }, 18 | { 19 | name: 'green', 20 | value: 4 21 | }, 22 | { 23 | name: 'blue', 24 | value: 8, 25 | }, 26 | { 27 | name: 'white', 28 | value: 14 29 | }, 30 | { 31 | name: 'black', 32 | value: 0 33 | } 34 | ]; 35 | 36 | test('background', function (t) { 37 | colors.forEach(function (color) { 38 | ['png', 'gif', 'jpg'].forEach(function (format) { 39 | testBackground(t, color, format); 40 | }); 41 | }); 42 | 43 | t.end(); 44 | }); 45 | 46 | function testBackground (t, color, format) { 47 | var srcData = require('fs').readFileSync(__dirname + "/test.background.png"); 48 | 49 | var pixels = getPixels(srcData); 50 | 51 | t.deepEqual(pixels, [0, 16]); 52 | 53 | var buffer = imagemagick.convert({ 54 | srcData: srcData, 55 | format: format, 56 | background: color.name, 57 | quality: 100, 58 | debug: debug 59 | }); 60 | 61 | t.equal( Buffer.isBuffer(buffer), true, 'buffer is Buffer' ); 62 | 63 | pixels = getPixels(buffer); 64 | 65 | t.equal(pixels[0], 0); 66 | t.equal(pixels[1], color.value); 67 | 68 | saveToFileIfDebug( 69 | buffer, 70 | __dirname + "/out.background-" + color.name + "." + format); 71 | } 72 | 73 | function getPixels(buffer) { 74 | return imagemagick.getConstPixels({ 75 | srcData: buffer, 76 | x: 0, 77 | y: 0, 78 | columns: 2, 79 | rows: 1 80 | }).map(function (pixel) { 81 | return ['red', 'green', 'blue', 'opacity'].reduce(function (num, key, index) { 82 | if (pixel[key] > 500) { 83 | num += Math.pow(2, index + 1); 84 | } 85 | return num; 86 | }, 0); 87 | }); 88 | } 89 | -------------------------------------------------------------------------------- /test/test.background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elad/node-imagemagick-native/1831117866d41b87b19fb5f93564a0ea994aff12/test/test.background.png -------------------------------------------------------------------------------- /test/test.colorspace.js: -------------------------------------------------------------------------------- 1 | /* jshint node: true */ 2 | var fs = require('fs'); 3 | var test = require('tap').test; 4 | var imagemagick = require('..'); 5 | var debug = false; 6 | 7 | process.chdir(__dirname); 8 | 9 | console.log('image magick\'s version is: ' + imagemagick.version()); 10 | 11 | function saveToFileIfDebug (buffer, file) { 12 | if (debug) { 13 | fs.writeFileSync( file, buffer, 'binary' ); 14 | console.log( 'wrote file: '+file ); 15 | } 16 | } 17 | 18 | test( 'Get colorspace from identify', function (t) { 19 | var info = imagemagick.identify({ 20 | srcData: require('fs').readFileSync( 'test.CMYK.jpg' ), 21 | debug: debug 22 | }); 23 | 24 | t.equal( info.colorspace, 'CMYK', 'colorspace is CMYK' ); 25 | t.end(); 26 | }); 27 | 28 | test( 'convert CMYK JPEG -> sRGB PNG', function (t) { 29 | t.plan(2); 30 | 31 | var buffer = imagemagick.convert({ 32 | srcData: require('fs').readFileSync( 'test.CMYK.jpg' ), // 58x66 33 | format: 'PNG', 34 | colorspace: 'sRGB', 35 | debug: debug 36 | }); 37 | t.equal( Buffer.isBuffer(buffer), true, 'buffer is Buffer' ); 38 | saveToFileIfDebug( buffer, 'out.CMYK.to.sRGB.png' ); 39 | 40 | var info = imagemagick.identify({ srcData: buffer, debug: debug }); 41 | t.equal( info.colorspace, 'sRGB', 'colorspace is sRGB' ); 42 | t.end(); 43 | }); 44 | -------------------------------------------------------------------------------- /test/test.crop.js: -------------------------------------------------------------------------------- 1 | /* jshint node: true */ 2 | var fs = require('fs'); 3 | var test = require('tap').test; 4 | var imagemagick = require('..'); 5 | var debug = false; 6 | 7 | process.chdir(__dirname); 8 | 9 | console.log('image magick\'s version is: ' + imagemagick.version()); 10 | 11 | function saveToFileIfDebug (buffer, file) { 12 | if (debug) { 13 | fs.writeFileSync( file, buffer, 'binary' ); 14 | console.log( 'wrote file: '+file ); 15 | } 16 | } 17 | 18 | test( 'Allow crop resizeStyle without [x|y]offset', function (t) { 19 | var buffer = imagemagick.convert({ 20 | srcData: require('fs').readFileSync( 'test.crop.png' ), // 30x30 21 | width: 3, 22 | format: 'PNG', 23 | resizeStyle: 'crop', 24 | debug: debug 25 | }); 26 | t.equal( Buffer.isBuffer(buffer), true, 'buffer is Buffer' ); 27 | saveToFileIfDebug( buffer, 'out.crop1.png' ); 28 | 29 | var info = imagemagick.identify({ srcData: buffer, debug: debug }); 30 | t.equal( info.width, 3); 31 | t.equal( info.height, 30); 32 | var quantize = imagemagick.quantizeColors({ srcData: buffer, colors: 1, debug: debug }); 33 | t.equal( quantize[0].hex, 'ff0000'); 34 | t.end(); 35 | }); 36 | 37 | test( 'Allow crop resizeStyle with [x|y]offset', function (t) { 38 | var buffer = imagemagick.convert({ 39 | srcData: require('fs').readFileSync( 'test.crop.png' ), // 30x30 40 | width: 5, 41 | height: 5, 42 | xoffset: 4, 43 | yoffset: 2, 44 | format: 'PNG', 45 | resizeStyle: 'crop', 46 | debug: debug 47 | }); 48 | t.equal( Buffer.isBuffer(buffer), true, 'buffer is Buffer' ); 49 | saveToFileIfDebug( buffer, 'out.crop2.png' ); 50 | 51 | var info = imagemagick.identify({ srcData: buffer, debug: debug }); 52 | t.equal( info.width, 5); 53 | t.equal( info.height, 5); 54 | var quantize = imagemagick.quantizeColors({ srcData: buffer, colors: 1, debug: debug }); 55 | t.equal( quantize[0].hex, '0000ff'); 56 | t.end(); 57 | }); 58 | -------------------------------------------------------------------------------- /test/test.crop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elad/node-imagemagick-native/1831117866d41b87b19fb5f93564a0ea994aff12/test/test.crop.png -------------------------------------------------------------------------------- /test/test.ext.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elad/node-imagemagick-native/1831117866d41b87b19fb5f93564a0ea994aff12/test/test.ext.tga -------------------------------------------------------------------------------- /test/test.exthint.js: -------------------------------------------------------------------------------- 1 | var test = require('tap').test; 2 | var imagemagick = require('..'); 3 | 4 | process.chdir(__dirname); 5 | 6 | test( 'identify results', function (t) { 7 | t.plan(6); 8 | var info; 9 | 10 | try { 11 | info = imagemagick.identify({ 12 | srcData: require('fs').readFileSync('test.ext.tga'), 13 | }); 14 | } catch (ex) { 15 | t.like(ex.message, /no decode delegate for this image format/, 'err message'); 16 | } 17 | 18 | info = imagemagick.identify({ 19 | srcData: require('fs').readFileSync( 'test.ext.tga' ), 20 | srcFormat: 'tga', 21 | }); 22 | 23 | t.equal( info.width, 31, 'width is 31' ); 24 | t.equal( info.height, 16, 'height is 16' ); 25 | t.equal( info.depth, 8, 'depth is 8' ); 26 | t.equal( info.format, 'TGA', 'format is TGA' ); 27 | t.equal( info.exif.orientation, 0, 'orientation doesnt exist' ); 28 | t.end(); 29 | }); 30 | -------------------------------------------------------------------------------- /test/test.getPixelColor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elad/node-imagemagick-native/1831117866d41b87b19fb5f93564a0ea994aff12/test/test.getPixelColor.png -------------------------------------------------------------------------------- /test/test.gravity.js: -------------------------------------------------------------------------------- 1 | var test = require('tap').test 2 | , imagemagick = require('..') 3 | , debug = false 4 | ; 5 | 6 | process.chdir(__dirname); 7 | 8 | console.log("image magick's version is: " + imagemagick.version()); 9 | var versions = imagemagick.version().split("."); 10 | 11 | function saveToFileIfDebug (buffer, file) { 12 | if (debug) { 13 | require('fs').writeFileSync( file, buffer, 'binary' ); 14 | console.log( "wrote file: "+file ); 15 | } 16 | } 17 | 18 | test( 'taller default', function (t) { 19 | var buffer = imagemagick.convert({ 20 | srcData: require('fs').readFileSync( "test.png" ), // 58x66 21 | width: 100, 22 | height: 100, 23 | resizeStyle: 'aspectfill', 24 | quality: 80, 25 | format: 'PNG', 26 | debug: debug 27 | }); 28 | t.equal( Buffer.isBuffer(buffer), true, 'buffer is Buffer' ); 29 | saveToFileIfDebug( buffer, "out.gravity-default.png" ); 30 | t.end(); 31 | }); 32 | 33 | [ "Center", 34 | "East", 35 | "West", 36 | "North", 37 | "South", 38 | "NorthEast", 39 | "NorthWest", 40 | "SouthEast", 41 | "SouthWest", 42 | "None", 43 | ].forEach( function (gravity) { 44 | test( 'taller ' + gravity, function (t) { 45 | var buffer = imagemagick.convert({ 46 | srcData: require('fs').readFileSync( "test.png" ), // 58x66 47 | width: 100, 48 | height: 100, 49 | resizeStyle: 'aspectfill', 50 | gravity: gravity, 51 | quality: 80, 52 | format: 'PNG', 53 | debug: debug 54 | }); 55 | t.equal( Buffer.isBuffer(buffer), true, 'buffer is Buffer' ); 56 | saveToFileIfDebug( buffer, "out.gravity-"+gravity+".png" ); 57 | t.end(); 58 | }); 59 | test( 'wide ' + gravity, function (t) { 60 | var buffer = imagemagick.convert({ 61 | srcData: require('fs').readFileSync( "test.wide.png" ), // 58x66 62 | width: 100, 63 | height: 100, 64 | resizeStyle: 'aspectfill', 65 | gravity: gravity, 66 | quality: 80, 67 | format: 'PNG', 68 | debug: debug 69 | }); 70 | t.equal( Buffer.isBuffer(buffer), true, 'buffer is Buffer' ); 71 | saveToFileIfDebug( buffer, "out.wide.gravity-"+gravity+".png" ); 72 | t.end(); 73 | }); 74 | } ); 75 | -------------------------------------------------------------------------------- /test/test.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elad/node-imagemagick-native/1831117866d41b87b19fb5f93564a0ea994aff12/test/test.jpg -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | var test = require('tap').test 2 | , imagemagick = require('..') 3 | , debug = 0 4 | ; 5 | 6 | process.chdir(__dirname); 7 | 8 | console.log("image magick's version is: " + imagemagick.version()); 9 | var versions = imagemagick.version().split("."); 10 | 11 | function saveToFileIfDebug (buffer, file) { 12 | if (debug) { 13 | require('fs').writeFileSync( file, buffer, 'binary' ); 14 | console.log( "wrote file: "+file ); 15 | } 16 | } 17 | 18 | test( 'convert invalid format', function (t) { 19 | var buffer; 20 | try { 21 | buffer = imagemagick.convert({ 22 | srcData: require('fs').readFileSync( "test.png" ), // 58x66 23 | width: 100, 24 | height: 100, 25 | quality: 80, 26 | format: 'PNGX', 27 | debug: debug 28 | }); 29 | } catch (e) { 30 | t.like( e.message, /no decode delegate for this image format/, 'err message' ); 31 | } 32 | t.equal( buffer, undefined, 'buffer undefined' ); 33 | t.end(); 34 | }); 35 | 36 | test( 'convert invalid number of arguments', function (t) { 37 | var error = 0; 38 | try { 39 | imagemagick.convert(); 40 | } catch (e) { 41 | error = e; 42 | } 43 | 44 | t.equal( error.name, 'Error' ); 45 | t.equal( error.message, 'convert() requires 1 (option) argument!' ); 46 | t.end(); 47 | }); 48 | 49 | test( 'convert invalid resizeStyle', function (t) { 50 | var buffer; 51 | try { 52 | buffer = imagemagick.convert({ 53 | srcData: require('fs').readFileSync( "test.png" ), // 58x66 54 | width: 100, 55 | height: 100, 56 | resizeStyle: "fit", // not defined 57 | quality: 80, 58 | format: 'PNG', 59 | debug: debug 60 | }); 61 | } catch (e) { 62 | t.equal( e.message, 'resizeStyle not supported', 'err message' ); 63 | } 64 | t.equal( buffer, undefined, 'buffer undefined' ); 65 | t.end(); 66 | }); 67 | 68 | test( 'convert srcData is a Buffer', function (t) { 69 | var buffer; 70 | try { 71 | imagemagick.convert({ 72 | srcData: require('fs').readFileSync( "test.png", 'binary' ), 73 | width: 100, 74 | height: 100, 75 | resizeStyle: "fit", // not defined 76 | quality: 80, 77 | format: 'PNG', 78 | debug: debug 79 | }); 80 | } catch (e) { 81 | t.equal( e.message, "convert()'s 1st argument should have \"srcData\" key with a Buffer instance" ); 82 | } 83 | t.equal( buffer, undefined, 'buffer undefined' ); 84 | t.end(); 85 | }); 86 | 87 | test( 'convert filter not supported', function (t) { 88 | var buffer; 89 | try { 90 | imagemagick.convert({ 91 | srcData: require('fs').readFileSync( "test.png" ), 92 | width: 100, 93 | height: 100, 94 | quality: 80, 95 | format: 'PNG', 96 | filter: 'Lagrang', // typo 97 | debug: debug 98 | }); 99 | } catch (e) { 100 | t.equal( e.message, "filter not supported" ); 101 | } 102 | t.equal( buffer, undefined, 'buffer undefined' ); 103 | t.end(); 104 | }); 105 | 106 | test( 'convert filter Lagrange', function (t) { 107 | var buffer = imagemagick.convert({ 108 | srcData: require('fs').readFileSync( "test.png" ), // 58x66 109 | width: 100, 110 | height: 100, 111 | filter: 'Lagrange', 112 | quality: 80, 113 | format: 'PNG', 114 | debug: debug 115 | }); 116 | t.equal( Buffer.isBuffer(buffer), true, 'buffer is Buffer' ); 117 | saveToFileIfDebug( buffer, "out.png-lagrange.png" ); 118 | t.end(); 119 | }); 120 | 121 | test( 'convert blur', function (t) { 122 | var buffer = imagemagick.convert({ 123 | srcData: require('fs').readFileSync( "test.png" ), // 58x66 124 | width: 100, 125 | height: 100, 126 | quality: 80, 127 | format: 'PNG', 128 | blur: 0.8, 129 | debug: debug 130 | }); 131 | t.equal( Buffer.isBuffer(buffer), true, 'buffer is Buffer' ); 132 | saveToFileIfDebug( buffer, "out.png-blur.png" ); 133 | t.end(); 134 | }); 135 | 136 | test( 'convert strip', function (t) { 137 | var buffer = imagemagick.convert({ 138 | srcData: require('fs').readFileSync( "test.png" ), 139 | width: 100, 140 | height: 100, 141 | quality: 80, 142 | format: 'PNG', 143 | strip: true, 144 | debug: debug 145 | }); 146 | t.equal( Buffer.isBuffer(buffer), true, 'buffer is Buffer' ); 147 | saveToFileIfDebug( buffer, "out.png-strip.png" ); 148 | t.end(); 149 | }); 150 | 151 | test( 'convert png -> png aspectfill', function (t) { 152 | var buffer = imagemagick.convert({ 153 | srcData: require('fs').readFileSync( "test.png" ), // 58x66 154 | width: 100, 155 | height: 100, 156 | resizeStyle: 'aspectfill', // default. 157 | quality: 80, 158 | format: 'PNG', 159 | debug: debug 160 | }); 161 | t.equal( Buffer.isBuffer(buffer), true, 'buffer is Buffer' ); 162 | // t.equal( buffer.length, 9545, 'converted buffer size ok' ); 163 | saveToFileIfDebug( buffer, "out.png-aspectfill.png" ); 164 | t.end(); 165 | }); 166 | 167 | test( 'convert png -> jpg aspectfill', function (t) { 168 | var buffer = imagemagick.convert({ 169 | srcData: require('fs').readFileSync( "test.png" ), 170 | width: 100, 171 | height: 100, 172 | quality: 80, 173 | format: 'JPEG', 174 | debug: debug 175 | }); 176 | t.equal( Buffer.isBuffer(buffer), true, 'buffer is Buffer' ); 177 | // t.equal( buffer.length, 2295, 'converted buffer size ok' ); 178 | saveToFileIfDebug( buffer, "out.png-aspectfill.jpg" ); 179 | t.end(); 180 | }); 181 | 182 | test( 'convert png.wide -> png.wide aspectfill', function (t) { 183 | var buffer = imagemagick.convert({ 184 | srcData: require('fs').readFileSync( "test.wide.png" ), 185 | width: 100, 186 | height: 100, 187 | quality: 80, 188 | format: 'PNG', 189 | debug: debug 190 | }); 191 | t.equal( Buffer.isBuffer(buffer), true, 'buffer is Buffer' ); 192 | // t.equal( buffer.length, 9615, 'converted buffer size ok' ); 193 | saveToFileIfDebug( buffer, "out.wide.png-aspectfill.png" ); 194 | t.end(); 195 | }); 196 | 197 | test( 'convert jpg -> jpg fill', function (t) { 198 | var buffer = imagemagick.convert({ 199 | srcData: require('fs').readFileSync( "test.jpg" ), 200 | width: 100, 201 | height: 100, 202 | resizeStyle: "fill", 203 | quality: 80, 204 | format: 'JPEG', 205 | debug: debug 206 | }); 207 | t.equal( Buffer.isBuffer(buffer), true, 'buffer is Buffer' ); 208 | // t.equal( buffer.length, 3184, 'converted buffer size ok' ); 209 | saveToFileIfDebug( buffer, "out.jpg-fill.jpg" ); 210 | t.end(); 211 | }); 212 | 213 | test( 'convert jpg -> jpg aspectfit', function (t) { 214 | var buffer = imagemagick.convert({ 215 | srcData: require('fs').readFileSync( "test.jpg" ), 216 | width: 100, 217 | height: 100, 218 | resizeStyle: "aspectfit", 219 | quality: 80, 220 | format: 'JPEG', 221 | debug: debug 222 | }); 223 | t.equal( Buffer.isBuffer(buffer), true, 'buffer is Buffer' ); 224 | // t.equal( buffer.length, 3012, 'converted buffer size ok' ); 225 | saveToFileIfDebug( buffer, "out.jpg-aspectfit.jpg" ); 226 | t.end(); 227 | }); 228 | 229 | test( 'convert broken png', function (t) { 230 | var srcData = require('fs').readFileSync( "broken.png" ) 231 | , buffer; 232 | 233 | try { 234 | buffer = imagemagick.convert({ 235 | srcData: srcData, 236 | width: 100, 237 | height: 100, 238 | resizeStyle: "aspectfit", 239 | quality: 80, 240 | format: 'JPEG', 241 | // debug: debug 242 | }); 243 | } catch (e) { 244 | t.similar( e.message, 245 | new RegExp("CRC error|image\\.read failed with error: Magick:|corrupt image") ); 246 | } 247 | t.end(); 248 | }); 249 | 250 | // segmentation faults on Mac... 251 | // if (versions[1] > 6) { 252 | // test( 'convert too wide jpg', function (t) { 253 | // var srcData = require('fs').readFileSync( "test.maxmemory.jpg" ) 254 | // , buffer 255 | // , seenError = 0; 256 | 257 | // try { 258 | // buffer = imagemagick.convert({ 259 | // srcData: srcData, 260 | // width: 640, 261 | // height: 960, 262 | // resizeStyle: "aspectfill", 263 | // quality: 80, 264 | // format: 'JPEG', 265 | // maxMemory: 100 * 1000, // 100kB 266 | // debug: debug 267 | // }); 268 | // } catch (e) { 269 | // seenError = 1; 270 | // t.similar( e.message, 271 | // new RegExp("cache resources exhausted") ); 272 | // } 273 | // saveToFileIfDebug( buffer, "out.jpg-maxmemory.jpg" ); 274 | // t.equal( seenError, 1 ); 275 | // t.end(); 276 | // }); 277 | // } 278 | 279 | test( 'convert to rotate 90 degrees', function (t) { 280 | var buffer = imagemagick.convert({ 281 | srcData: require('fs').readFileSync( "test.jpg" ), 282 | rotate: 90, 283 | debug: debug 284 | }); 285 | var info = imagemagick.identify({srcData: buffer }); 286 | t.equal( info.width, 66 ); 287 | t.equal( info.height, 58 ); 288 | saveToFileIfDebug( buffer, "out.jpg-rotate90.jpg" ); 289 | t.end(); 290 | }); 291 | 292 | test( 'convert to rotate -90 degrees', function (t) { 293 | var buffer = imagemagick.convert({ 294 | srcData: require('fs').readFileSync( "test.jpg" ), 295 | rotate: -90, 296 | debug: debug 297 | }); 298 | var info = imagemagick.identify({srcData: buffer }); 299 | t.equal( info.width, 66 ); 300 | t.equal( info.height, 58 ); 301 | saveToFileIfDebug( buffer, "out.jpg-rotate-90.jpg" ); 302 | t.end(); 303 | }); 304 | 305 | test( 'identify invalid number of arguments', function (t) { 306 | var error = 0; 307 | try { 308 | imagemagick.identify(); 309 | } catch (e) { 310 | error = e; 311 | } 312 | 313 | t.equal( error.name, 'Error' ); 314 | t.equal( error.message, 'identify() requires 1 (option) argument!' ); 315 | t.end(); 316 | }); 317 | 318 | test( 'identify srcData is a Buffer', function (t) { 319 | var buffer; 320 | try { 321 | buffer = imagemagick.identify({ 322 | srcData: require('fs').readFileSync( "test.png", 'binary' ) 323 | }); 324 | } catch (e) { 325 | t.equal( e.message, "identify()'s 1st argument should have \"srcData\" key with a Buffer instance" ); 326 | } 327 | t.equal( buffer, undefined, 'buffer undefined' ); 328 | t.end(); 329 | }); 330 | 331 | test( 'identify results', function (t) { 332 | var info = imagemagick.identify({ 333 | srcData: require('fs').readFileSync( "test.png" ) 334 | }); 335 | t.equal( info.width, 58, 'width is 58' ); 336 | t.equal( info.height, 66, 'height is 66' ); 337 | t.equal( info.depth, 8, 'depth is 8' ); 338 | t.equal( info.format, 'PNG', 'format is PNG' ); 339 | t.equal( info.exif.orientation, 0, 'orientation doesnt exist' ); 340 | t.end(); 341 | }); 342 | 343 | test( 'quantizeColors invalid number of arguments', function (t) { 344 | var error = 0; 345 | try { 346 | imagemagick.quantizeColors(); 347 | } catch (e) { 348 | error = e; 349 | } 350 | 351 | t.equal( error.name, 'Error' ); 352 | t.equal( error.message, 'quantizeColors() requires 1 (option) argument!' ); 353 | t.end(); 354 | }); 355 | 356 | test( 'quantizeColors srcData is a Buffer', function (t) { 357 | var buffer; 358 | try { 359 | buffer = imagemagick.quantizeColors({ 360 | srcData: require('fs').readFileSync( "test.png", 'binary' ) 361 | }); 362 | } catch (e) { 363 | t.equal( e.message, "quantizeColors()'s 1st argument should have \"srcData\" key with a Buffer instance" ); 364 | } 365 | t.equal( buffer, undefined, 'buffer undefined' ); 366 | t.end(); 367 | }); 368 | 369 | test( 'quantizeColors results, 1 color', function (t) { 370 | var results = imagemagick.quantizeColors({ 371 | srcData: require('fs').readFileSync( "test.quantizeColors.png" ), 372 | colors: 1 373 | }); 374 | 375 | t.equal( results[0].r, 161, 'results[0] red is 161' ); 376 | t.equal( results[0].g, 93, 'results[0] green is 93' ); 377 | t.equal( results[0].b, 85, 'results[0] blue is 85' ); 378 | t.equal( results[0].hex, 'a15d55', 'results[0] hex is a15d55' ); 379 | 380 | t.end(); 381 | }); 382 | 383 | 384 | test( 'quantizeColors results, 5 colors', function (t) { 385 | var results = imagemagick.quantizeColors({ 386 | srcData: require('fs').readFileSync( "test.quantizeColors.png" ) 387 | }); 388 | 389 | t.equal( results[0].r, 255, 'results[0] red is 255' ); 390 | t.equal( results[0].g, 8, 'results[0] green is 8' ); 391 | t.equal( results[0].b, 8, 'results[0] blue is 8' ); 392 | t.equal( results[0].hex, 'ff0808', 'results[0] hex is ff0808' ); 393 | 394 | t.equal( results[1].r, 223, 'results[1] red is 223' ); 395 | t.equal( results[1].g, 0, 'results[1] green is 0' ); 396 | t.equal( results[1].b, 234, 'results[1] blue is 234' ); 397 | t.equal( results[1].hex, 'df00ea', 'results[1] hex is df00ea' ); 398 | 399 | t.equal( results[2].r, 28, 'results[2] red is 28' ); 400 | t.equal( results[2].g, 27, 'results[2] green is 27' ); 401 | t.equal( results[2].b, 255, 'results[2] blue is 255' ); 402 | t.equal( results[2].hex, '1c1bff', 'results[2] hex is 1c1bff' ); 403 | 404 | t.equal( results[3].r, 0, 'results[3] red is 0' ); 405 | t.equal( results[3].g, 231, 'results[3] green is 231' ); 406 | t.equal( results[3].b, 226, 'results[3] blue is 226' ); 407 | t.equal( results[3].hex, '00e7e2', 'results[3] hex is 00e7e2' ); 408 | 409 | t.equal( results[4].r, 25, 'results[4] red is 25' ); 410 | t.equal( results[4].g, 255, 'results[4] green is 255' ); 411 | t.equal( results[4].b, 30, 'results[4] blue is 30' ); 412 | t.equal( results[4].hex, '19ff1e', 'results[4] hex is 19ff1e' ); 413 | 414 | t.end(); 415 | }); 416 | 417 | test( 'composite invalid number of arguments', function (t) { 418 | var error = 0; 419 | try { 420 | imagemagick.composite(); 421 | } catch (e) { 422 | error = e; 423 | } 424 | 425 | t.equal( error.name, 'Error' ); 426 | t.equal( error.message, 'composite() requires 1 (option) argument!' ); 427 | t.end(); 428 | }); 429 | 430 | test( 'composite srcData is a Buffer', function (t) { 431 | var buffer; 432 | try { 433 | buffer = imagemagick.composite({ 434 | srcData: require('fs').readFileSync( "test.png", 'binary' ) 435 | }); 436 | } catch (e) { 437 | t.equal( e.message, "composite()'s 1st argument should have \"srcData\" key with a Buffer instance" ); 438 | } 439 | t.equal( buffer, undefined, 'buffer undefined' ); 440 | t.end(); 441 | }); 442 | 443 | test( 'composite compositeData is a Buffer', function (t) { 444 | var buffer; 445 | try { 446 | buffer = imagemagick.composite({ 447 | srcData: require('fs').readFileSync( "test.quantizeColors.png" ), 448 | compositeData: require('fs').readFileSync("test.png","binary") 449 | }); 450 | } catch (e) { 451 | t.equal( e.message, "composite()'s 1st argument should have \"compositeData\" key with a Buffer instance" ); 452 | } 453 | t.equal( buffer, undefined, 'buffer undefined' ); 454 | t.end(); 455 | }); 456 | 457 | test( 'composite image not source image',function(t) { 458 | 459 | var srcData = require('fs').readFileSync( "test.quantizeColors.png" ); 460 | var compositeData = require('fs').readFileSync( "test.png" ); 461 | 462 | var buffer = imagemagick.composite({ 463 | srcData: srcData, 464 | compositeData: compositeData, 465 | gravity: "SouthEastGravity" 466 | }); 467 | 468 | t.notSame( buffer,srcData ); 469 | t.end(); 470 | }); 471 | 472 | test( 'get pixel colors: pixel colors from each 6x6 square', function(t) { 473 | var srcData = require('fs').readFileSync("test.getPixelColor.png"); 474 | 475 | var targetInfo = { 476 | srcData : srcData, 477 | x : 0, // (6 * i), 478 | y : 0, 479 | columns : 1, 480 | rows : 1 481 | }; 482 | var pixelInfo = imagemagick.getConstPixels(targetInfo); 483 | t.equal(pixelInfo.length, 1); 484 | // don't test exact pixel value, it may differ according to quantum depth 485 | t.equal(typeof(pixelInfo[0].red), "number"); 486 | t.equal(typeof(pixelInfo[0].green), "number"); 487 | t.equal(typeof(pixelInfo[0].blue), "number"); 488 | t.equal(typeof(pixelInfo[0].opacity), "number"); 489 | if (debug) { 490 | console.log(pixelInfo); 491 | } 492 | t.end(); 493 | }); 494 | 495 | test( 'quantumDepth', function(t) { 496 | var q = imagemagick.quantumDepth(); 497 | t.equal(typeof(q), "number"); 498 | t.equal(q >= 8, true); 499 | t.end(); 500 | }); 501 | -------------------------------------------------------------------------------- /test/test.maxmemory.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elad/node-imagemagick-native/1831117866d41b87b19fb5f93564a0ea994aff12/test/test.maxmemory.jpg -------------------------------------------------------------------------------- /test/test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elad/node-imagemagick-native/1831117866d41b87b19fb5f93564a0ea994aff12/test/test.png -------------------------------------------------------------------------------- /test/test.promises.js: -------------------------------------------------------------------------------- 1 | 2 | var test = require('tap').test; 3 | var imagemagick = require('..'); 4 | var debug = 0; 5 | 6 | process.chdir(__dirname); 7 | 8 | function saveToFileIfDebug (buffer, file) { 9 | if (debug) { 10 | require('fs').writeFileSync( file, buffer, 'binary' ); 11 | console.log( 'wrote file: '+file ); 12 | } 13 | } 14 | 15 | test( 'promise convert', function (t) { 16 | t.plan(1); 17 | imagemagick.promises 18 | .convert({ 19 | srcData: require('fs').readFileSync( 'test.png' ), // 58x66 20 | width: 100, 21 | height: 100, 22 | filter: 'Lagrange', 23 | quality: 80, 24 | format: 'PNG', 25 | debug: debug 26 | }) 27 | .then(function(buffer) { 28 | t.equal( Buffer.isBuffer(buffer), true, 'buffer is Buffer' ); 29 | saveToFileIfDebug( buffer, 'out.png-lagrange.png' ); 30 | t.end(); 31 | }); 32 | }); 33 | 34 | test( 'promise identify', function (t) { 35 | t.plan(5); 36 | imagemagick.promises 37 | .identify({ 38 | srcData: require('fs').readFileSync( 'test.png' ) 39 | }) 40 | .then(function(info){ 41 | t.equal( info.width, 58, 'width is 58' ); 42 | t.equal( info.height, 66, 'height is 66' ); 43 | t.equal( info.depth, 8, 'depth is 8' ); 44 | t.equal( info.format, 'PNG', 'format is PNG' ); 45 | t.equal( info.exif.orientation, 0, 'orientation doesnt exist' ); 46 | t.end(); 47 | }); 48 | }); 49 | 50 | test( 'promise composite', function (t) { 51 | t.plan(1); 52 | imagemagick.promises 53 | .composite({ 54 | srcData: require('fs').readFileSync( 'test.quantizeColors.png' ), 55 | compositeData: require('fs').readFileSync('test.png'), 56 | debug: debug 57 | }) 58 | .then(function(buffer){ 59 | t.equal( Buffer.isBuffer(buffer), true, 'buffer is Buffer' ); 60 | saveToFileIfDebug( buffer, 'out.composite-async.png' ); 61 | t.end(); 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /test/test.quantizeColors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elad/node-imagemagick-native/1831117866d41b87b19fb5f93564a0ea994aff12/test/test.quantizeColors.png -------------------------------------------------------------------------------- /test/test.stream.js: -------------------------------------------------------------------------------- 1 | 2 | var test = require('tap').test 3 | , imagemagick = require('..') 4 | , fs = require('fs') 5 | , debug = 0 6 | ; 7 | 8 | process.chdir(__dirname); 9 | 10 | console.log("image magick's version is: " + imagemagick.version()); 11 | var versions = imagemagick.version().split("."); 12 | 13 | test( 'stream.convert returns a stream like object', function (t) { 14 | t.plan(2); 15 | var stream = imagemagick.streams.convert({ 16 | width: 100, 17 | height: 100, 18 | filter: 'Lagrange', 19 | quality: 80, 20 | format: 'PNG', 21 | debug: debug 22 | }); 23 | 24 | t.equal( typeof(stream.on), 'function' ); 25 | t.equal( typeof(stream.pipe), 'function' ); 26 | t.end() 27 | }); 28 | 29 | test( 'stream.convert can pipes streams', function (t) { 30 | t.plan(2); 31 | var stream = imagemagick.streams.convert({ 32 | width: 100, 33 | height: 100, 34 | filter: 'Lagrange', 35 | quality: 80, 36 | format: 'PNG', 37 | debug: debug 38 | }); 39 | 40 | var input = fs.createReadStream('test.png'); 41 | var output = fs.createWriteStream('test-async-temp.png'); 42 | 43 | output.on('close',function(){ 44 | var ret = imagemagick.identify({ 45 | srcData: fs.readFileSync( 'test-async-temp.png' ) 46 | }); 47 | t.equal( ret.width, 100 ); 48 | t.equal( ret.height, 100 ); 49 | fs.unlinkSync( 'test-async-temp.png' ); 50 | t.end(); 51 | }); 52 | 53 | input.pipe(stream).pipe(output); 54 | }); 55 | -------------------------------------------------------------------------------- /test/test.trim.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elad/node-imagemagick-native/1831117866d41b87b19fb5f93564a0ea994aff12/test/test.trim.jpg -------------------------------------------------------------------------------- /test/test.trim.js: -------------------------------------------------------------------------------- 1 | var test = require('tap').test 2 | , imagemagick = require('..') 3 | , debug = 0 4 | ; 5 | 6 | process.chdir(__dirname); 7 | 8 | console.log("image magick's version is: " + imagemagick.version()); 9 | var versions = imagemagick.version().split("."); 10 | 11 | function saveToFileIfDebug (buffer, file) { 12 | if (debug) { 13 | require('fs').writeFileSync( file, buffer, 'binary' ); 14 | console.log( "wrote file: "+file ); 15 | } 16 | } 17 | 18 | test( 'trim default', function (t) { 19 | var buffer = imagemagick.convert({ 20 | srcData: require('fs').readFileSync( "test.trim.jpg" ), // 87x106 21 | format: 'PNG', 22 | trim: true, 23 | debug: debug 24 | }); 25 | t.equal( Buffer.isBuffer(buffer), true, 'buffer is Buffer' ); 26 | var info = imagemagick.identify({srcData: buffer }); 27 | t.equal( info.width, 61 ); 28 | t.equal( info.height, 72 ); 29 | saveToFileIfDebug( buffer, "out.trim-default.png" ); 30 | t.end(); 31 | }); 32 | 33 | test( 'trim exact color fuzz', function (t) { 34 | var buffer = imagemagick.convert({ 35 | srcData: require('fs').readFileSync( "test.trim.jpg" ), // 87x106 36 | format: 'PNG', 37 | trim: true, 38 | trimFuzz: 0, 39 | debug: debug 40 | }); 41 | t.equal( Buffer.isBuffer(buffer), true, 'buffer is Buffer' ); 42 | var info = imagemagick.identify({srcData: buffer }); 43 | t.equal( info.width, 61 ); 44 | t.equal( info.height, 72 ); 45 | saveToFileIfDebug( buffer, "out.trim-exact.png" ); 46 | t.end(); 47 | }); 48 | 49 | test( 'trim half color fuzz', function (t) { 50 | var buffer = imagemagick.convert({ 51 | srcData: require('fs').readFileSync( "test.trim.jpg" ), // 87x106 52 | format: 'PNG', 53 | trim: true, 54 | trimFuzz: 0.5, 55 | debug: debug 56 | }); 57 | t.equal( Buffer.isBuffer(buffer), true, 'buffer is Buffer' ); 58 | var info = imagemagick.identify({srcData: buffer }); 59 | t.equal( info.width, 57 ); 60 | t.equal( info.height, 65 ); 61 | saveToFileIfDebug( buffer, "out.trim-half.png" ); 62 | t.end(); 63 | }); 64 | 65 | test( 'trim 92% color fuzz', function (t) { 66 | var buffer = imagemagick.convert({ 67 | srcData: require('fs').readFileSync( "test.trim.jpg" ), // 87x106 68 | format: 'PNG', 69 | trim: true, 70 | trimFuzz: 0.92, 71 | debug: debug 72 | }); 73 | t.equal( Buffer.isBuffer(buffer), true, 'buffer is Buffer' ); 74 | var info = imagemagick.identify({srcData: buffer }); 75 | t.equal( info.width, 1 ); 76 | t.equal( info.height, 1 ); 77 | saveToFileIfDebug( buffer, "out.trim-92.png" ); 78 | t.end(); 79 | }); 80 | 81 | test( 'trim half color fuzz resize', function (t) { 82 | var buffer = imagemagick.convert({ 83 | srcData: require('fs').readFileSync( "test.trim.jpg" ), // 87x106 84 | format: 'PNG', 85 | trim: true, 86 | trimFuzz: 0.5, 87 | width: 50, 88 | height: 50, 89 | resizeStyle: 'aspectfill', 90 | debug: debug 91 | }); 92 | t.equal( Buffer.isBuffer(buffer), true, 'buffer is Buffer' ); 93 | var info = imagemagick.identify({srcData: buffer }); 94 | t.equal( info.width, 50 ); 95 | t.equal( info.height, 50 ); 96 | saveToFileIfDebug( buffer, "out.trim-half-resize.png" ); 97 | t.end(); 98 | }); 99 | 100 | -------------------------------------------------------------------------------- /test/test.wide.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elad/node-imagemagick-native/1831117866d41b87b19fb5f93564a0ea994aff12/test/test.wide.png --------------------------------------------------------------------------------