Welis Garagen-Anzeige
This commit is contained in:
+2
@@ -0,0 +1,2 @@
|
||||
test/
|
||||
examples/
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
before_install:
|
||||
- sudo apt-get update
|
||||
- sudo apt-get install imagemagick graphicsmagick
|
||||
language: node_js
|
||||
node_js:
|
||||
- "6"
|
||||
- "4"
|
||||
- "0.10"
|
||||
- "0.12"
|
||||
- "iojs-v2"
|
||||
+574
@@ -0,0 +1,574 @@
|
||||
|
||||
1.23.1 / 2017-12-27
|
||||
|
||||
* fixed: use debug > 2.6.9 because of security issue #685 danez
|
||||
* tests; add nsp check
|
||||
* tests; get tests passing on OSX
|
||||
|
||||
1.23.0 / 2016-08-03
|
||||
|
||||
* fixed; webpack support #547 sean-shirazi
|
||||
* fixed; windows support - use cross-spawn to spawn processes #537 bdukes
|
||||
* added; allow thumbnail to accept the same options as resize #527 Sebmaster
|
||||
* added; dispose support #487 dlwr
|
||||
* docs; add example of loading image from URL #544 wahengchang
|
||||
* docs; Fix a link in README.md #532 clbn
|
||||
* travis; update travis versions #551 amilajack
|
||||
|
||||
1.22.0 / 2016-04-07
|
||||
|
||||
* fixed; identity parser: support multi-value keys by creating an array #508 #509 [emaniacs](https://github.com/emaniacs)
|
||||
* fixed; error handling if gm is not installed #499 [aeo3](https://github.com/aeo3)
|
||||
* fixed; highlightColor typo in compare #504 [DanielHudson](https://github.com/DanielHudson)
|
||||
* docs; Fix typo #475 [rodrigoalviani](https://github.com/rodrigoalviani)
|
||||
|
||||
1.21.1 / 2015-10-26
|
||||
|
||||
* fixed: Fixed #465 hard coded gm binary, also fixed issues with compare and fixed tests so they will fail on subsequent runs when they should do [rwky](https://github.com/rwky)
|
||||
|
||||
1.21.0 / 2015-10-26 **contains security fix**
|
||||
|
||||
* fixed: gm.compare fails to escape arguments properly (Reported by Brendan Scarvell) [rwky](https://github.com/rwky)
|
||||
|
||||
1.20.0 / 2015-09-23
|
||||
|
||||
* changed: Reverted "Add format inference from filename for buffers/streams" due to errors #448
|
||||
|
||||
1.19.0 / 2015-09-16
|
||||
|
||||
* changed: Added error to notify about image magick not supporting minify [encima](https://github.com/encima)
|
||||
* changed: Refactored orientation getter to use faster identify call [lbeschastny](https://github.com/lbeschastny)
|
||||
* added: resizeExact function [DanMMX](https://github.com/DanMMX)
|
||||
* added: thumbExact function [DanMMX](https://github.com/DanMMX)
|
||||
* added: Add format inference from filename for buffers/streams [adurrive](https://github.com/adurrive)
|
||||
* fixed: Hex values when passed to compare aren't quoted automatically [DanMMX](https://github.com/DanMMX)
|
||||
* fixed: identify returning last frame size instead of the larges on animated gifs [preynal](https://github.com/preynal)
|
||||
* docs: Updated docs [laurilehmijoki](https://github.com/laurilehmijoki)
|
||||
|
||||
1.18.1 / 2015-05-18
|
||||
|
||||
* changed: Added io.js support [rwky](https://github.com/rwky)
|
||||
|
||||
1.18.0 / 2015-05-18
|
||||
|
||||
* changed: Removed support for node 0.8 and added support for 0.12 [rwky](https://github.com/rwky)
|
||||
* changed: Listen to stdin error event for spawn errors [kapouer](https://github.com/kapouer)
|
||||
* changed: Improved error handling when gm isn't installed [FreshXOpenSource](https://github.com/FreshXOpenSource)
|
||||
* changed: Allow append method to use an array of arguments [emohacker](https://github.com/emohacker)
|
||||
* changed: appPath option now specifies full path to gm binary John Borkowski
|
||||
* changed: Ignore warning messages for identify [asrail](https://github.com/asrail)
|
||||
* added: Montage method [donaldpcook](https://github.com/donaldpcook)
|
||||
* added: Progressive option to thumb [mohebifar](https://github.com/mohebifar)
|
||||
* added: Native gm auto-orient for use with gm >= 1.3.18 [bog](https://github.com/bog)
|
||||
* added: Timeout support by passing the timeout option in milliseconds [marcbachmann](https://github.com/marcbachmann)
|
||||
* fixed: density when using ImageMagick [syzer](https://github.com/syzer)
|
||||
* fixed: resize behaviour for falsy values [adius](https://github.com/adius)
|
||||
|
||||
|
||||
1.17.0 / 2014-10-28
|
||||
==================
|
||||
|
||||
* changed: extended compare callback also returns the file names #297 [mastix](https://github.com/mastix)
|
||||
* changed: pass spawn crash to callback #306 [medikoo](https://github.com/medikoo)
|
||||
* changed: geometry supports arbitary string as first argument #330 [jdiez17](https://github.com/jdiez17)
|
||||
* added: support for repage+ option #275 [desigens](https://github.com/desigens)
|
||||
* added: added the dissolve command #300 [microadm](https://github.com/microadam)
|
||||
* added: composite method #332 [jdiez17](https://github.com/jdiez17)
|
||||
* fixed: cannot set tolerance to 0 #302 [rwky](https://github.com/rwky)
|
||||
* fixed: handle empty buffers #330 [alcidesv](https://github.com/alcidesv)
|
||||
|
||||
1.16.0 / 2014-05-09
|
||||
==================
|
||||
|
||||
* fixed; dropped "+" when 0 passed as vertical roll amt #267 [dwtkns](https://github.com/dwtkns)
|
||||
* added; highlight-style support #272 [fdecampredon](https://github.com/fdecampredon)
|
||||
|
||||
1.15.0 / 2014-05-03
|
||||
===================
|
||||
|
||||
* changed; gm.compare logic to always run the mse comparison as expected #258 [Vokkim](https://github.com/Vokkim)
|
||||
* added; `tolerance` to gm.compare options object #258 [Vokkim](https://github.com/Vokkim)
|
||||
* added; option to set ImageMagick application path explicitly #250 (akreitals)
|
||||
* fixed; gm.compare: support values like 9.51582e-05 #260 [normanrz](https://github.com/normanrz)
|
||||
* README: add call for maintainers
|
||||
|
||||
1.14.2 / 2013-12-24
|
||||
===================
|
||||
|
||||
* fixed; background is now a setting #246 (PEM--)
|
||||
|
||||
1.14.1 / 2013-12-09
|
||||
===================
|
||||
|
||||
* fixed; identify -verbose colon behavior #240 ludow
|
||||
|
||||
1.14.0 / 2013-12-04
|
||||
===================
|
||||
|
||||
* added; compare method for imagemagick (longlho)
|
||||
|
||||
1.13.3 / 2013-10-22
|
||||
===================
|
||||
|
||||
* fixed; escape diffOptions.file in compare (dwabyick)
|
||||
|
||||
1.13.2 / 2013-10-18
|
||||
===================
|
||||
|
||||
* fixed; density is a setting not an operator
|
||||
|
||||
1.13.1 / 2013-09-15
|
||||
===================
|
||||
|
||||
* added; boolean for % crop
|
||||
|
||||
1.13.0 / 2013-09-07
|
||||
===================
|
||||
|
||||
* added; morph more than two images (overra)
|
||||
|
||||
1.12.2 / 2013-08-29
|
||||
===================
|
||||
|
||||
* fixed; fallback to through in node 0.8
|
||||
|
||||
1.12.1 / 2013-08-29 (unpublished)
|
||||
===================
|
||||
|
||||
* refactor; replace through with stream.PassThrough
|
||||
|
||||
1.12.0 / 2013-08-27
|
||||
===================
|
||||
|
||||
* added; diff image output file (chenglou)
|
||||
|
||||
1.11.1 / 2013-08-17
|
||||
===================
|
||||
|
||||
* added; proto.selectFrame(#)
|
||||
* fixed; getters should not ignore frame selection
|
||||
|
||||
1.11.0 / 2013-07-23
|
||||
===================
|
||||
|
||||
* added; optional formatting string for gm().identify(format, callback) (tornillo)
|
||||
* removed; error messages when gm/im binary is not installed
|
||||
|
||||
1.10.0 / 2013-06-27
|
||||
===================
|
||||
|
||||
* refactor; use native `-auto-orient` for imagemagick
|
||||
|
||||
1.9.2 / 2013-06-12
|
||||
==================
|
||||
|
||||
* refactor; move `streamToBuffer` to a separate module
|
||||
* fixed; .stream(format) without a callback
|
||||
|
||||
1.9.1 / 2013-05-07
|
||||
==================
|
||||
|
||||
* fixed; gm().resize(width) always only resizes width
|
||||
* fixed; gm('img.gif').format() returns the format of the first frame
|
||||
|
||||
1.9.0 / 2013-04-21
|
||||
==================
|
||||
|
||||
* added; node v0.10 support
|
||||
* removed; node < v0.8 support - `Buffer.concat()`
|
||||
* tests; all tests now run on Travis
|
||||
* added; gm().stream() returns a stream when no callback is present
|
||||
* added; gm().toBuffer(callback)
|
||||
* fixed; gm().size() only returns the size of the first frame of a GIF
|
||||
|
||||
1.8.2 / 2013-03-07
|
||||
==================
|
||||
|
||||
* include source path in identify data #126 [soupdiver](https://github.com/soupdiver)
|
||||
|
||||
1.8.1 / 2012-12-21
|
||||
==================
|
||||
|
||||
* Avoid losing already set arguments on identify #105 #113 #109 [JNissi](https://github.com/JNissi)
|
||||
* tests; add autoOrient + thumb() test
|
||||
* tests; add test case for #113
|
||||
* tests; added test for #109
|
||||
* tests; add resize on buffer test
|
||||
|
||||
1.8.0 / 2012-12-14
|
||||
==================
|
||||
|
||||
* added; geometry support to scale() #98
|
||||
* removed; incorrect/broken dissolve() method (never worked)
|
||||
* fixed; handle child_proc error when using Buffer input #109
|
||||
* fixed; use of Buffers with identify() #109
|
||||
* fixed; no longer include -size arg with resize() #98
|
||||
* fixed; remove -size arg from extent() #103
|
||||
* fixed; magnify support
|
||||
* fixed; autoOrient to work with all types of exif orientations [dambalah](https://github.com/dambalah) #108
|
||||
* tests; npm test runs unit only (now compatible with travis)
|
||||
* tests; fix magnify test on imagemagick
|
||||
* tests; added for cmd line args
|
||||
|
||||
1.7.0 / 2012-12-06
|
||||
==================
|
||||
|
||||
* added; gm.compare support
|
||||
* added; passing Buffers directly [danmilon](https://github.com/danmilon)
|
||||
|
||||
1.6.1 / 2012-11-13
|
||||
==================
|
||||
|
||||
* fixed regression; only pass additional params on error #96
|
||||
|
||||
1.6.0 / 2012-11-10
|
||||
==================
|
||||
|
||||
* changed; rename internal buffer to _buffer #88 [kof](https://github.com/kof)
|
||||
* changed; optimized identify getters (format, depth, size, color, filesize). #83 please read this for details: https://github.com/aheckmann/gm/commit/8fcf3f8f84a02cc2001da874cbebb89bf7084409
|
||||
* added; visionmedia/debug support
|
||||
* added; `gm convert -thumbnail` support. _differs from thumb()._ [danmilon](https://github.com/danmilon)
|
||||
* fixed; -rotate 0 support #90
|
||||
* fixed; multi-execution of same gm instance arguments corruption
|
||||
* fixed; gracefully handle parser errors #94 [eldilibra](https://github.com/eldilibra)
|
||||
|
||||
1.5.1 / 2012-10-02
|
||||
==================
|
||||
|
||||
* fixed; passing multiple paths to append() #77
|
||||
|
||||
1.5.0 / 2012-09-15
|
||||
==================
|
||||
|
||||
* fixed; callback scope
|
||||
* fixed; append() usage #77
|
||||
|
||||
1.4.2 / 2012-08-17
|
||||
==================
|
||||
|
||||
* fixed; identify parsing for ImageMagick exif data (#58)
|
||||
* fixed; when in imageMagick mode, complain about missing imageMagick [bcherry](https://github.com/bcherry) (#73)
|
||||
* added; tests
|
||||
|
||||
1.4.1 / 2012-07-31
|
||||
==================
|
||||
|
||||
* fixed; scenes() args
|
||||
* fixed; accept the left-to-right arg of append()
|
||||
* added; _subCommand
|
||||
|
||||
## v1.4 - 07/28/2012
|
||||
|
||||
* added; adjoin() [Math-]
|
||||
* added; affine() [Math-]
|
||||
* added; append() [Math-]
|
||||
* added; authenticate() [Math-]
|
||||
* added; average() [Math-]
|
||||
* added; backdrop() [Math-]
|
||||
* added; blackThreshold() [Math-]
|
||||
* added; bluePrimary() [Math-]
|
||||
* added; border() [Math-]
|
||||
* added; borderColor() [Math-]
|
||||
* added; box() [Math-]
|
||||
* added; channel() [Math-]
|
||||
* added; clip() [Math-]
|
||||
* added; coalesce() [Math-]
|
||||
* added; colorMap() [Math-]
|
||||
* added; compose() [Math-]
|
||||
* added; compress() [Math-]
|
||||
* added; convolve() [Math-]
|
||||
* added; createDirectories() [Math-]
|
||||
* added; deconstruct() [Math-]
|
||||
* added; delay() [Math-]
|
||||
* added; define() [Math-]
|
||||
* added; displace() [Math-]
|
||||
* added; display() [Math-]
|
||||
* added; dispose() [Math-]
|
||||
* added; disolve() [Math-]
|
||||
* added; encoding() [Math-]
|
||||
* added; endian() [Math-]
|
||||
* added; file() [Math-]
|
||||
* added; flatten() [Math-]
|
||||
* added; foreground() [Math-]
|
||||
* added; frame() [Math-]
|
||||
* added; fuzz() [Math-]
|
||||
* added; gaussian() [Math-]
|
||||
* added; geometry() [Math-]
|
||||
* added; greenPrimary() [Math-]
|
||||
* added; highlightColor() [Math-]
|
||||
* added; highlightStyle() [Math-]
|
||||
* added; iconGeometry() [Math-]
|
||||
* added; intent() [Math-]
|
||||
* added; lat() [Math-]
|
||||
* added; level() [Math-]
|
||||
* added; list() [Math-]
|
||||
* added; log() [Math-]
|
||||
* added; map() [Math-]
|
||||
* added; matte() [Math-]
|
||||
* added; matteColor() [Math-]
|
||||
* added; mask() [Math-]
|
||||
* added; maximumError() [Math-]
|
||||
* added; mode() [Math-]
|
||||
* added; monitor() [Math-]
|
||||
* added; mosaic() [Math-]
|
||||
* added; motionBlur() [Math-]
|
||||
* added; name() [Math-]
|
||||
* added; noop() [Math-]
|
||||
* added; normalize() [Math-]
|
||||
* added; opaque() [Math-]
|
||||
* added; operator() [Math-]
|
||||
* added; orderedDither() [Math-]
|
||||
* added; outputDirectory() [Math-]
|
||||
* added; page() [Math-]
|
||||
* added; pause() [Math-]
|
||||
* added; pen() [Math-]
|
||||
* added; ping() [Math-]
|
||||
* added; pointSize() [Math-]
|
||||
* added; preview() [Math-]
|
||||
* added; process() [Math-]
|
||||
* added; profile() [Math-]
|
||||
* added; progress() [Math-]
|
||||
* added; rawSize() [Math-]
|
||||
* added; randomThreshold() [Math-]
|
||||
* added; recolor() [Math-]
|
||||
* added; redPrimary() [Math-]
|
||||
* added; remote() [Math-]
|
||||
* added; render() [Math-]
|
||||
* added; repage() [Math-]
|
||||
* added; sample() [Math-]
|
||||
* added; samplingFactor() [Math-]
|
||||
* added; scene() [Math-]
|
||||
* added; scenes() [Math-]
|
||||
* added; screen() [Math-]
|
||||
* added; segment() [Math-]
|
||||
* added; set() [Math-]
|
||||
* added; shade() [Math-]
|
||||
* added; shadow() [Math-]
|
||||
* added; sharedMemory() [Math-]
|
||||
* added; shave() [Math-]
|
||||
* added; shear() [Math-]
|
||||
* added; silent() [Math-]
|
||||
* added; snaps() [Math-]
|
||||
* added; stagano() [Math-]
|
||||
* added; stereo() [Math-]
|
||||
* added; textFont() [Math-]
|
||||
* added; texture() [Math-]
|
||||
* added; threshold() [Math-]
|
||||
* added; tile() [Math-]
|
||||
* added; transform() [Math-]
|
||||
* added; transparent() [Math-]
|
||||
* added; treeDepth() [Math-]
|
||||
* added; update() [Math-]
|
||||
* added; units() [Math-]
|
||||
* added; unsharp() [Math-]
|
||||
* added; usePixmap() [Math-]
|
||||
* added; view() [Math-]
|
||||
* added; virtualPixel() [Math-]
|
||||
* added; visual() [Math-]
|
||||
* added; watermark() [Math-]
|
||||
* added; wave() [Math-]
|
||||
* added; whitePoint() [Math-]
|
||||
* added; whiteThreshold() [Math-]
|
||||
* added; window() [Math-]
|
||||
* added; windowGroup() [Math-]
|
||||
|
||||
## v1.3.2 - 06/22/2012
|
||||
|
||||
* added; node >= 0.7/0.8 compat
|
||||
|
||||
## v1.3.1 - 06/06/2012
|
||||
|
||||
* fixed; thumb() alignment and cropping [thomaschaaf]
|
||||
* added; hint when graphicsmagick is not installed (#62)
|
||||
* fixed; minify() (#59)
|
||||
|
||||
## v1.3.0 - 04/11/2012
|
||||
|
||||
* added; flatten support [jwarchol]
|
||||
* added; background support [jwarchol]
|
||||
* fixed; identify parser error [chriso]
|
||||
|
||||
## v1.2.0 - 03/30/2012
|
||||
|
||||
* added; extent and gravity support [jwarchol]
|
||||
|
||||
## v1.1.0 - 03/15/2012
|
||||
|
||||
* added; filter() support [travisbeck]
|
||||
* added; density() [travisbeck]
|
||||
* fixed; permit either width or height in resize [dambalah]
|
||||
* updated; docs
|
||||
|
||||
## v1.0.5 - 02/15/2012
|
||||
|
||||
* added; strip() support [Math-]
|
||||
* added; interlace() support [Math-]
|
||||
* added; setFormat() support [Math-]
|
||||
* fixed; regexps for image types [Math-]
|
||||
|
||||
## v1.0.4 - 02/09/2012
|
||||
|
||||
* expose utils
|
||||
|
||||
## v1.0.3 - 01/27/2012
|
||||
|
||||
* removed; console.log
|
||||
|
||||
## v1.0.2 - 01/24/2012
|
||||
|
||||
* added; debugging info on parser errors
|
||||
* fixed; exports.version
|
||||
|
||||
## v1.0.1 - 01/12/2012
|
||||
|
||||
* fixed; use of reserved keyword `super` for node v0.5+
|
||||
|
||||
## v1.0.0 - 01/12/2012
|
||||
|
||||
* added; autoOrient support [kainosnoema] (#21)
|
||||
* added; orientation support [kainosnoema] (#21)
|
||||
* fixed; identify parser now properly JSON formats all data output by `gm identify` such as IPTC, GPS, Make, etc (#20)
|
||||
* added; support for running as imagemagick (#23, #29)
|
||||
* added; subclassing support; useful for setting default constructor options like one constructor for ImageMagick, the other for GM
|
||||
* added; more tests
|
||||
* changed; remove redundant `orientation`, `resolution`, and `filesize` from `this.data` in `indentify()`. Use their uppercase equivalents.
|
||||
|
||||
## v0.6.0 - 12/14/2011
|
||||
|
||||
* added; stream support [kainosnoema] (#22)
|
||||
|
||||
## v0.5.0 - 07/07/2011
|
||||
|
||||
* added; gm#trim() support [lepokle]
|
||||
* added; gm#inputIs() support
|
||||
* fixed; 'geometry does not contain image' error: gh-17
|
||||
|
||||
## v0.4.3 - 05/17/2011
|
||||
|
||||
* added; bunch of tests
|
||||
* fixed; polygon, polyline, bezier drawing bug
|
||||
|
||||
## v0.4.2 - 05/10/2011
|
||||
|
||||
* added; resize options support
|
||||
|
||||
## v0.4.1 - 04/28/2011
|
||||
|
||||
* shell args are now escaped (thanks @visionmedia)
|
||||
* added; gm.in()
|
||||
* added; gm.out()
|
||||
* various refactoring
|
||||
|
||||
## v0.4.0 - 9/21/2010
|
||||
|
||||
* removed deprecated `new` method
|
||||
* added drawing docs
|
||||
|
||||
## v0.3.2 - 9/06/2010
|
||||
|
||||
* new images are now created using same gm() constructor
|
||||
|
||||
## v0.3.1 - 9/06/2010
|
||||
|
||||
* can now create images from scratch
|
||||
* add type method
|
||||
|
||||
## v0.3.0 - 8/26/2010
|
||||
|
||||
* add drawing api
|
||||
|
||||
## v0.2.2 - 8/22/2010
|
||||
|
||||
* add quality option to thumb()
|
||||
* add teropa to contributors
|
||||
* added support for colorspace()
|
||||
|
||||
## v0.2.1 - 7/31/2010
|
||||
|
||||
* fixed naming conflict. depth() manipulation method renamed bitdepth()
|
||||
* added better docs
|
||||
|
||||
## v0.2.0 - 7/29/2010
|
||||
|
||||
new methods
|
||||
|
||||
- swirl
|
||||
- spread
|
||||
- solarize
|
||||
- sharpen
|
||||
- roll
|
||||
- sepia
|
||||
- region
|
||||
- raise
|
||||
- lower
|
||||
- paint
|
||||
- noise
|
||||
- negative
|
||||
- morph
|
||||
- median
|
||||
- antialias
|
||||
- limit
|
||||
- label
|
||||
- implode
|
||||
- gamma
|
||||
- enhance
|
||||
- equalize
|
||||
- emboss
|
||||
- edge
|
||||
- dither
|
||||
- monochrome
|
||||
- despeckle
|
||||
- depth
|
||||
- cycle
|
||||
- contrast
|
||||
- comment
|
||||
- colors
|
||||
|
||||
added more default args to several methods
|
||||
added more examples
|
||||
|
||||
|
||||
## v0.1.2 - 7/28/2010
|
||||
|
||||
* refactor project into separate modules
|
||||
|
||||
|
||||
## v0.1.1 - 7/27/2010
|
||||
|
||||
* add modulate method
|
||||
* add colorize method
|
||||
* add charcoal method
|
||||
* add chop method
|
||||
* bug fix in write without a callback
|
||||
|
||||
|
||||
## v0.1.0 - 6/27/2010
|
||||
|
||||
* no longer supporting mogrify
|
||||
* add image data getter methods
|
||||
|
||||
* size
|
||||
* format
|
||||
* color
|
||||
* res
|
||||
* depth
|
||||
* filesize
|
||||
* identify
|
||||
|
||||
* add new convert methods
|
||||
|
||||
* scale
|
||||
* resample
|
||||
* rotate
|
||||
* flip
|
||||
* flop
|
||||
* crop
|
||||
* magnify
|
||||
* minify
|
||||
* quality
|
||||
* blur
|
||||
* thumb
|
||||
|
||||
|
||||
## v0.0.1 - 6/11/2010
|
||||
Initial release
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
|
||||
test: security
|
||||
@node test/ --integration $(TESTS)
|
||||
|
||||
test-unit: security
|
||||
@node test/ $(TESTS)
|
||||
|
||||
security:
|
||||
@node_modules/.bin/nsp check
|
||||
|
||||
.PHONY: test test-unit security
|
||||
+649
@@ -0,0 +1,649 @@
|
||||
|
||||
# gm [](https://travis-ci.org/aheckmann/gm) [](https://www.npmjs.org/package/gm)
|
||||
|
||||
GraphicsMagick and ImageMagick for node
|
||||
|
||||
## Bug Reports
|
||||
|
||||
When reporting bugs please include the version of graphicsmagick/imagemagick you're using (gm -version/convert -version) as well as the version of this module and copies of any images you're having problems with.
|
||||
|
||||
## Getting started
|
||||
First download and install [GraphicsMagick](http://www.graphicsmagick.org/) or [ImageMagick](http://www.imagemagick.org/). In Mac OS X, you can simply use [Homebrew](http://mxcl.github.io/homebrew/) and do:
|
||||
|
||||
brew install imagemagick
|
||||
brew install graphicsmagick
|
||||
|
||||
If you want WebP support with ImageMagick, you must add the WebP option:
|
||||
|
||||
brew install imagemagick --with-webp
|
||||
|
||||
then either use npm:
|
||||
|
||||
npm install gm
|
||||
|
||||
or clone the repo:
|
||||
|
||||
git clone git://github.com/aheckmann/gm.git
|
||||
|
||||
|
||||
## Use ImageMagick instead of gm
|
||||
|
||||
Subclass `gm` to enable ImageMagick
|
||||
|
||||
```js
|
||||
var fs = require('fs')
|
||||
, gm = require('gm').subClass({imageMagick: true});
|
||||
|
||||
// resize and remove EXIF profile data
|
||||
gm('/path/to/my/img.jpg')
|
||||
.resize(240, 240)
|
||||
...
|
||||
```
|
||||
|
||||
|
||||
## Basic Usage
|
||||
|
||||
```js
|
||||
var fs = require('fs')
|
||||
, gm = require('gm');
|
||||
|
||||
// resize and remove EXIF profile data
|
||||
gm('/path/to/my/img.jpg')
|
||||
.resize(240, 240)
|
||||
.noProfile()
|
||||
.write('/path/to/resize.png', function (err) {
|
||||
if (!err) console.log('done');
|
||||
});
|
||||
|
||||
// some files would not be resized appropriately
|
||||
// http://stackoverflow.com/questions/5870466/imagemagick-incorrect-dimensions
|
||||
// you have two options:
|
||||
// use the '!' flag to ignore aspect ratio
|
||||
gm('/path/to/my/img.jpg')
|
||||
.resize(240, 240, '!')
|
||||
.write('/path/to/resize.png', function (err) {
|
||||
if (!err) console.log('done');
|
||||
});
|
||||
|
||||
// use the .resizeExact with only width and/or height arguments
|
||||
gm('/path/to/my/img.jpg')
|
||||
.resizeExact(240, 240)
|
||||
.write('/path/to/resize.png', function (err) {
|
||||
if (!err) console.log('done');
|
||||
});
|
||||
|
||||
// obtain the size of an image
|
||||
gm('/path/to/my/img.jpg')
|
||||
.size(function (err, size) {
|
||||
if (!err)
|
||||
console.log(size.width > size.height ? 'wider' : 'taller than you');
|
||||
});
|
||||
|
||||
// output all available image properties
|
||||
gm('/path/to/img.png')
|
||||
.identify(function (err, data) {
|
||||
if (!err) console.log(data)
|
||||
});
|
||||
|
||||
// pull out the first frame of an animated gif and save as png
|
||||
gm('/path/to/animated.gif[0]')
|
||||
.write('/path/to/firstframe.png', function (err) {
|
||||
if (err) console.log('aaw, shucks');
|
||||
});
|
||||
|
||||
// auto-orient an image
|
||||
gm('/path/to/img.jpg')
|
||||
.autoOrient()
|
||||
.write('/path/to/oriented.jpg', function (err) {
|
||||
if (err) ...
|
||||
})
|
||||
|
||||
// crazytown
|
||||
gm('/path/to/my/img.jpg')
|
||||
.flip()
|
||||
.magnify()
|
||||
.rotate('green', 45)
|
||||
.blur(7, 3)
|
||||
.crop(300, 300, 150, 130)
|
||||
.edge(3)
|
||||
.write('/path/to/crazy.jpg', function (err) {
|
||||
if (!err) console.log('crazytown has arrived');
|
||||
})
|
||||
|
||||
// annotate an image
|
||||
gm('/path/to/my/img.jpg')
|
||||
.stroke("#ffffff")
|
||||
.drawCircle(10, 10, 20, 10)
|
||||
.font("Helvetica.ttf", 12)
|
||||
.drawText(30, 20, "GMagick!")
|
||||
.write("/path/to/drawing.png", function (err) {
|
||||
if (!err) console.log('done');
|
||||
});
|
||||
|
||||
// creating an image
|
||||
gm(200, 400, "#ddff99f3")
|
||||
.drawText(10, 50, "from scratch")
|
||||
.write("/path/to/brandNewImg.jpg", function (err) {
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
## Streams
|
||||
|
||||
```js
|
||||
// passing a stream
|
||||
var readStream = fs.createReadStream('/path/to/my/img.jpg');
|
||||
gm(readStream, 'img.jpg')
|
||||
.write('/path/to/reformat.png', function (err) {
|
||||
if (!err) console.log('done');
|
||||
});
|
||||
|
||||
|
||||
// passing a downloadable image by url
|
||||
|
||||
var request = require('request');
|
||||
var url = "www.abc.com/pic.jpg"
|
||||
|
||||
gm(request(url))
|
||||
.write('/path/to/reformat.png', function (err) {
|
||||
if (!err) console.log('done');
|
||||
});
|
||||
|
||||
|
||||
// can also stream output to a ReadableStream
|
||||
// (can be piped to a local file or remote server)
|
||||
gm('/path/to/my/img.jpg')
|
||||
.resize('200', '200')
|
||||
.stream(function (err, stdout, stderr) {
|
||||
var writeStream = fs.createWriteStream('/path/to/my/resized.jpg');
|
||||
stdout.pipe(writeStream);
|
||||
});
|
||||
|
||||
// without a callback, .stream() returns a stream
|
||||
// this is just a convenience wrapper for above.
|
||||
var writeStream = fs.createWriteStream('/path/to/my/resized.jpg');
|
||||
gm('/path/to/my/img.jpg')
|
||||
.resize('200', '200')
|
||||
.stream()
|
||||
.pipe(writeStream);
|
||||
|
||||
// pass a format or filename to stream() and
|
||||
// gm will provide image data in that format
|
||||
gm('/path/to/my/img.jpg')
|
||||
.stream('png', function (err, stdout, stderr) {
|
||||
var writeStream = fs.createWriteStream('/path/to/my/reformatted.png');
|
||||
stdout.pipe(writeStream);
|
||||
});
|
||||
|
||||
// or without the callback
|
||||
var writeStream = fs.createWriteStream('/path/to/my/reformatted.png');
|
||||
gm('/path/to/my/img.jpg')
|
||||
.stream('png')
|
||||
.pipe(writeStream);
|
||||
|
||||
// combine the two for true streaming image processing
|
||||
var readStream = fs.createReadStream('/path/to/my/img.jpg');
|
||||
gm(readStream)
|
||||
.resize('200', '200')
|
||||
.stream(function (err, stdout, stderr) {
|
||||
var writeStream = fs.createWriteStream('/path/to/my/resized.jpg');
|
||||
stdout.pipe(writeStream);
|
||||
});
|
||||
|
||||
// GOTCHA:
|
||||
// when working with input streams and any 'identify'
|
||||
// operation (size, format, etc), you must pass "{bufferStream: true}" if
|
||||
// you also need to convert (write() or stream()) the image afterwards
|
||||
// NOTE: this buffers the readStream in memory!
|
||||
var readStream = fs.createReadStream('/path/to/my/img.jpg');
|
||||
gm(readStream)
|
||||
.size({bufferStream: true}, function(err, size) {
|
||||
this.resize(size.width / 2, size.height / 2)
|
||||
this.write('/path/to/resized.jpg', function (err) {
|
||||
if (!err) console.log('done');
|
||||
});
|
||||
});
|
||||
|
||||
```
|
||||
|
||||
## Buffers
|
||||
|
||||
```js
|
||||
// A buffer can be passed instead of a filepath as well
|
||||
var buf = require('fs').readFileSync('/path/to/image.jpg');
|
||||
|
||||
gm(buf, 'image.jpg')
|
||||
.noise('laplacian')
|
||||
.write('/path/to/out.jpg', function (err) {
|
||||
if (err) return handle(err);
|
||||
console.log('Created an image from a Buffer!');
|
||||
});
|
||||
|
||||
/*
|
||||
A buffer can also be returned instead of a stream
|
||||
The first argument to toBuffer is optional, it specifies the image format
|
||||
*/
|
||||
gm('img.jpg')
|
||||
.resize(100, 100)
|
||||
.toBuffer('PNG',function (err, buffer) {
|
||||
if (err) return handle(err);
|
||||
console.log('done!');
|
||||
})
|
||||
```
|
||||
|
||||
## Custom Arguments
|
||||
|
||||
If `gm` does not supply you with a method you need or does not work as you'd like, you can simply use `gm().in()` or `gm().out()` to set your own arguments.
|
||||
|
||||
- `gm().command()` - Custom command such as `identify` or `convert`
|
||||
- `gm().in()` - Custom input arguments
|
||||
- `gm().out()` - Custom output arguments
|
||||
|
||||
The command will be formatted in the following order:
|
||||
|
||||
1. `command` - ie `convert`
|
||||
2. `in` - the input arguments
|
||||
3. `source` - stdin or an image file
|
||||
4. `out` - the output arguments
|
||||
5. `output` - stdout or the image file to write to
|
||||
|
||||
For example, suppose you want the following command:
|
||||
|
||||
```bash
|
||||
gm "convert" "label:Offline" "PNG:-"
|
||||
```
|
||||
|
||||
However, using `gm().label()` may not work as intended for you:
|
||||
|
||||
```js
|
||||
gm()
|
||||
.label('Offline')
|
||||
.stream();
|
||||
```
|
||||
|
||||
would yield:
|
||||
|
||||
```bash
|
||||
gm "convert" "-label" "\"Offline\"" "PNG:-"
|
||||
```
|
||||
|
||||
Instead, you can use `gm().out()`:
|
||||
|
||||
```js
|
||||
gm()
|
||||
.out('label:Offline')
|
||||
.stream();
|
||||
```
|
||||
|
||||
which correctly yields:
|
||||
|
||||
```bash
|
||||
gm "convert" "label:Offline" "PNG:-"
|
||||
```
|
||||
|
||||
### Custom Identify Format String
|
||||
|
||||
When identifying an image, you may want to use a custom formatting string instead of using `-verbose`, which is quite slow.
|
||||
You can use your own [formatting string](http://www.imagemagick.org/script/escape.php) when using `gm().identify(format, callback)`.
|
||||
For example,
|
||||
|
||||
```js
|
||||
gm('img.png').format(function (err, format) {
|
||||
|
||||
})
|
||||
|
||||
// is equivalent to
|
||||
|
||||
gm('img.png').identify('%m', function (err, format) {
|
||||
|
||||
})
|
||||
```
|
||||
|
||||
since `%m` is the format option for getting the image file format.
|
||||
|
||||
## Platform differences
|
||||
|
||||
Please document and refer to any [platform or ImageMagick/GraphicsMagick issues/differences here](https://github.com/aheckmann/gm/wiki/GraphicsMagick-and-ImageMagick-versions).
|
||||
|
||||
## Examples:
|
||||
|
||||
Check out the [examples](http://github.com/aheckmann/gm/tree/master/examples/) directory to play around.
|
||||
Also take a look at the [extending gm](http://wiki.github.com/aheckmann/gm/extending-gm)
|
||||
page to see how to customize gm to your own needs.
|
||||
|
||||
## Constructor:
|
||||
|
||||
There are a few ways you can use the `gm` image constructor.
|
||||
|
||||
- 1) `gm(path)` When you pass a string as the first argument it is interpreted as the path to an image you intend to manipulate.
|
||||
- 2) `gm(stream || buffer, [filename])` You may also pass a ReadableStream or Buffer as the first argument, with an optional file name for format inference.
|
||||
- 3) `gm(width, height, [color])` When you pass two integer arguments, gm will create a new image on the fly with the provided dimensions and an optional background color. And you can still chain just like you do with pre-existing images too. See [here](http://github.com/aheckmann/gm/blob/master/examples/new.js) for an example.
|
||||
|
||||
The links below refer to an older version of gm but everything should still work, if anyone feels like updating them please make a PR
|
||||
|
||||
## Methods
|
||||
|
||||
- getters
|
||||
- [size](http://aheckmann.github.com/gm/docs.html#getters) - returns the size (WxH) of the image
|
||||
- [orientation](http://aheckmann.github.com/gm/docs.html#getters) - returns the EXIF orientation of the image
|
||||
- [format](http://aheckmann.github.com/gm/docs.html#getters) - returns the image format (gif, jpeg, png, etc)
|
||||
- [depth](http://aheckmann.github.com/gm/docs.html#getters) - returns the image color depth
|
||||
- [color](http://aheckmann.github.com/gm/docs.html#getters) - returns the number of colors
|
||||
- [res](http://aheckmann.github.com/gm/docs.html#getters) - returns the image resolution
|
||||
- [filesize](http://aheckmann.github.com/gm/docs.html#getters) - returns image filesize
|
||||
- [identify](http://aheckmann.github.com/gm/docs.html#getters) - returns all image data available. Takes an optional format string.
|
||||
|
||||
- manipulation
|
||||
- [adjoin](http://aheckmann.github.com/gm/docs.html#adjoin)
|
||||
- [affine](http://aheckmann.github.com/gm/docs.html#affine)
|
||||
- [antialias](http://aheckmann.github.com/gm/docs.html#antialias)
|
||||
- [append](http://aheckmann.github.com/gm/docs.html#append)
|
||||
- [authenticate](http://aheckmann.github.com/gm/docs.html#authenticate)
|
||||
- [autoOrient](http://aheckmann.github.com/gm/docs.html#autoOrient)
|
||||
- [average](http://aheckmann.github.com/gm/docs.html#average)
|
||||
- [backdrop](http://aheckmann.github.com/gm/docs.html#backdrop)
|
||||
- [bitdepth](http://aheckmann.github.com/gm/docs.html#bitdepth)
|
||||
- [blackThreshold](http://aheckmann.github.com/gm/docs.html#blackThreshold)
|
||||
- [bluePrimary](http://aheckmann.github.com/gm/docs.html#bluePrimary)
|
||||
- [blur](http://aheckmann.github.com/gm/docs.html#blur)
|
||||
- [border](http://aheckmann.github.com/gm/docs.html#border)
|
||||
- [borderColor](http://aheckmann.github.com/gm/docs.html#borderColor)
|
||||
- [box](http://aheckmann.github.com/gm/docs.html#box)
|
||||
- [channel](http://aheckmann.github.com/gm/docs.html#channel)
|
||||
- [charcoal](http://aheckmann.github.com/gm/docs.html#charcoal)
|
||||
- [chop](http://aheckmann.github.com/gm/docs.html#chop)
|
||||
- [clip](http://aheckmann.github.com/gm/docs.html#clip)
|
||||
- [coalesce](http://aheckmann.github.com/gm/docs.html#coalesce)
|
||||
- [colors](http://aheckmann.github.com/gm/docs.html#colors)
|
||||
- [colorize](http://aheckmann.github.com/gm/docs.html#colorize)
|
||||
- [colorMap](http://aheckmann.github.com/gm/docs.html#colorMap)
|
||||
- [colorspace](http://aheckmann.github.com/gm/docs.html#colorspace)
|
||||
- [comment](http://aheckmann.github.com/gm/docs.html#comment)
|
||||
- [compose](http://aheckmann.github.com/gm/docs.html#compose)
|
||||
- [compress](http://aheckmann.github.com/gm/docs.html#compress)
|
||||
- [contrast](http://aheckmann.github.com/gm/docs.html#contrast)
|
||||
- [convolve](http://aheckmann.github.com/gm/docs.html#convolve)
|
||||
- [createDirectories](http://aheckmann.github.com/gm/docs.html#createDirectories)
|
||||
- [crop](http://aheckmann.github.com/gm/docs.html#crop)
|
||||
- [cycle](http://aheckmann.github.com/gm/docs.html#cycle)
|
||||
- [deconstruct](http://aheckmann.github.com/gm/docs.html#deconstruct)
|
||||
- [delay](http://aheckmann.github.com/gm/docs.html#delay)
|
||||
- [define](http://aheckmann.github.com/gm/docs.html#define)
|
||||
- [density](http://aheckmann.github.com/gm/docs.html#density)
|
||||
- [despeckle](http://aheckmann.github.com/gm/docs.html#despeckle)
|
||||
- [dither](http://aheckmann.github.com/gm/docs.html#dither)
|
||||
- [displace](http://aheckmann.github.com/gm/docs.html#dither)
|
||||
- [display](http://aheckmann.github.com/gm/docs.html#display)
|
||||
- [dispose](http://aheckmann.github.com/gm/docs.html#dispose)
|
||||
- [dissolve](http://aheckmann.github.com/gm/docs.html#dissolve)
|
||||
- [edge](http://aheckmann.github.com/gm/docs.html#edge)
|
||||
- [emboss](http://aheckmann.github.com/gm/docs.html#emboss)
|
||||
- [encoding](http://aheckmann.github.com/gm/docs.html#encoding)
|
||||
- [enhance](http://aheckmann.github.com/gm/docs.html#enhance)
|
||||
- [endian](http://aheckmann.github.com/gm/docs.html#endian)
|
||||
- [equalize](http://aheckmann.github.com/gm/docs.html#equalize)
|
||||
- [extent](http://aheckmann.github.com/gm/docs.html#extent)
|
||||
- [file](http://aheckmann.github.com/gm/docs.html#file)
|
||||
- [filter](http://aheckmann.github.com/gm/docs.html#filter)
|
||||
- [flatten](http://aheckmann.github.com/gm/docs.html#flatten)
|
||||
- [flip](http://aheckmann.github.com/gm/docs.html#flip)
|
||||
- [flop](http://aheckmann.github.com/gm/docs.html#flop)
|
||||
- [foreground](http://aheckmann.github.com/gm/docs.html#foreground)
|
||||
- [frame](http://aheckmann.github.com/gm/docs.html#frame)
|
||||
- [fuzz](http://aheckmann.github.com/gm/docs.html#fuzz)
|
||||
- [gamma](http://aheckmann.github.com/gm/docs.html#gamma)
|
||||
- [gaussian](http://aheckmann.github.com/gm/docs.html#gaussian)
|
||||
- [geometry](http://aheckmann.github.com/gm/docs.html#geometry)
|
||||
- [gravity](http://aheckmann.github.com/gm/docs.html#gravity)
|
||||
- [greenPrimary](http://aheckmann.github.com/gm/docs.html#greenPrimary)
|
||||
- [highlightColor](http://aheckmann.github.com/gm/docs.html#highlightColor)
|
||||
- [highlightStyle](http://aheckmann.github.com/gm/docs.html#highlightStyle)
|
||||
- [iconGeometry](http://aheckmann.github.com/gm/docs.html#iconGeometry)
|
||||
- [implode](http://aheckmann.github.com/gm/docs.html#implode)
|
||||
- [intent](http://aheckmann.github.com/gm/docs.html#intent)
|
||||
- [interlace](http://aheckmann.github.com/gm/docs.html#interlace)
|
||||
- [label](http://aheckmann.github.com/gm/docs.html#label)
|
||||
- [lat](http://aheckmann.github.com/gm/docs.html#lat)
|
||||
- [level](http://aheckmann.github.com/gm/docs.html#level)
|
||||
- [list](http://aheckmann.github.com/gm/docs.html#list)
|
||||
- [limit](http://aheckmann.github.com/gm/docs.html#limit)
|
||||
- [log](http://aheckmann.github.com/gm/docs.html#log)
|
||||
- [loop](http://aheckmann.github.com/gm/docs.html#loop)
|
||||
- [lower](http://aheckmann.github.com/gm/docs.html#lower)
|
||||
- [magnify](http://aheckmann.github.com/gm/docs.html#magnify)
|
||||
- [map](http://aheckmann.github.com/gm/docs.html#map)
|
||||
- [matte](http://aheckmann.github.com/gm/docs.html#matte)
|
||||
- [matteColor](http://aheckmann.github.com/gm/docs.html#matteColor)
|
||||
- [mask](http://aheckmann.github.com/gm/docs.html#mask)
|
||||
- [maximumError](http://aheckmann.github.com/gm/docs.html#maximumError)
|
||||
- [median](http://aheckmann.github.com/gm/docs.html#median)
|
||||
- [minify](http://aheckmann.github.com/gm/docs.html#minify)
|
||||
- [mode](http://aheckmann.github.com/gm/docs.html#mode)
|
||||
- [modulate](http://aheckmann.github.com/gm/docs.html#modulate)
|
||||
- [monitor](http://aheckmann.github.com/gm/docs.html#monitor)
|
||||
- [monochrome](http://aheckmann.github.com/gm/docs.html#monochrome)
|
||||
- [morph](http://aheckmann.github.com/gm/docs.html#morph)
|
||||
- [mosaic](http://aheckmann.github.com/gm/docs.html#mosaic)
|
||||
- [motionBlur](http://aheckmann.github.com/gm/docs.html#motionBlur)
|
||||
- [name](http://aheckmann.github.com/gm/docs.html#name)
|
||||
- [negative](http://aheckmann.github.com/gm/docs.html#negative)
|
||||
- [noise](http://aheckmann.github.com/gm/docs.html#noise)
|
||||
- [noop](http://aheckmann.github.com/gm/docs.html#noop)
|
||||
- [normalize](http://aheckmann.github.com/gm/docs.html#normalize)
|
||||
- [noProfile](http://aheckmann.github.com/gm/docs.html#profile)
|
||||
- [opaque](http://aheckmann.github.com/gm/docs.html#opaque)
|
||||
- [operator](http://aheckmann.github.com/gm/docs.html#operator)
|
||||
- [orderedDither](http://aheckmann.github.com/gm/docs.html#orderedDither)
|
||||
- [outputDirectory](http://aheckmann.github.com/gm/docs.html#outputDirectory)
|
||||
- [paint](http://aheckmann.github.com/gm/docs.html#paint)
|
||||
- [page](http://aheckmann.github.com/gm/docs.html#page)
|
||||
- [pause](http://aheckmann.github.com/gm/docs.html#pause)
|
||||
- [pen](http://aheckmann.github.com/gm/docs.html#pen)
|
||||
- [ping](http://aheckmann.github.com/gm/docs.html#ping)
|
||||
- [pointSize](http://aheckmann.github.com/gm/docs.html#pointSize)
|
||||
- [preview](http://aheckmann.github.com/gm/docs.html#preview)
|
||||
- [process](http://aheckmann.github.com/gm/docs.html#process)
|
||||
- [profile](http://aheckmann.github.com/gm/docs.html#profile)
|
||||
- [progress](http://aheckmann.github.com/gm/docs.html#progress)
|
||||
- [quality](http://aheckmann.github.com/gm/docs.html#quality)
|
||||
- [raise](http://aheckmann.github.com/gm/docs.html#raise)
|
||||
- [rawSize](http://aheckmann.github.com/gm/docs.html#rawSize)
|
||||
- [randomThreshold](http://aheckmann.github.com/gm/docs.html#randomThreshold)
|
||||
- [recolor](http://aheckmann.github.com/gm/docs.html#recolor)
|
||||
- [redPrimary](http://aheckmann.github.com/gm/docs.html#redPrimary)
|
||||
- [region](http://aheckmann.github.com/gm/docs.html#region)
|
||||
- [remote](http://aheckmann.github.com/gm/docs.html#remote)
|
||||
- [render](http://aheckmann.github.com/gm/docs.html#render)
|
||||
- [repage](http://aheckmann.github.com/gm/docs.html#repage)
|
||||
- [resample](http://aheckmann.github.com/gm/docs.html#resample)
|
||||
- [resize](http://aheckmann.github.com/gm/docs.html#resize)
|
||||
- [roll](http://aheckmann.github.com/gm/docs.html#roll)
|
||||
- [rotate](http://aheckmann.github.com/gm/docs.html#rotate)
|
||||
- [sample](http://aheckmann.github.com/gm/docs.html#sample)
|
||||
- [samplingFactor](http://aheckmann.github.com/gm/docs.html#samplingFactor)
|
||||
- [scale](http://aheckmann.github.com/gm/docs.html#scale)
|
||||
- [scene](http://aheckmann.github.com/gm/docs.html#scene)
|
||||
- [scenes](http://aheckmann.github.com/gm/docs.html#scenes)
|
||||
- [screen](http://aheckmann.github.com/gm/docs.html#screen)
|
||||
- [segment](http://aheckmann.github.com/gm/docs.html#segment)
|
||||
- [sepia](http://aheckmann.github.com/gm/docs.html#sepia)
|
||||
- [set](http://aheckmann.github.com/gm/docs.html#set)
|
||||
- [setFormat](http://aheckmann.github.com/gm/docs.html#setformat)
|
||||
- [shade](http://aheckmann.github.com/gm/docs.html#shade)
|
||||
- [shadow](http://aheckmann.github.com/gm/docs.html#shadow)
|
||||
- [sharedMemory](http://aheckmann.github.com/gm/docs.html#sharedMemory)
|
||||
- [sharpen](http://aheckmann.github.com/gm/docs.html#sharpen)
|
||||
- [shave](http://aheckmann.github.com/gm/docs.html#shave)
|
||||
- [shear](http://aheckmann.github.com/gm/docs.html#shear)
|
||||
- [silent](http://aheckmann.github.com/gm/docs.html#silent)
|
||||
- [solarize](http://aheckmann.github.com/gm/docs.html#solarize)
|
||||
- [snaps](http://aheckmann.github.com/gm/docs.html#snaps)
|
||||
- [stegano](http://aheckmann.github.com/gm/docs.html#stegano)
|
||||
- [stereo](http://aheckmann.github.com/gm/docs.html#stereo)
|
||||
- [strip](http://aheckmann.github.com/gm/docs.html#strip) _imagemagick only_
|
||||
- [spread](http://aheckmann.github.com/gm/docs.html#spread)
|
||||
- [swirl](http://aheckmann.github.com/gm/docs.html#swirl)
|
||||
- [textFont](http://aheckmann.github.com/gm/docs.html#textFont)
|
||||
- [texture](http://aheckmann.github.com/gm/docs.html#texture)
|
||||
- [threshold](http://aheckmann.github.com/gm/docs.html#threshold)
|
||||
- [thumb](http://aheckmann.github.com/gm/docs.html#thumb)
|
||||
- [tile](http://aheckmann.github.com/gm/docs.html#tile)
|
||||
- [transform](http://aheckmann.github.com/gm/docs.html#transform)
|
||||
- [transparent](http://aheckmann.github.com/gm/docs.html#transparent)
|
||||
- [treeDepth](http://aheckmann.github.com/gm/docs.html#treeDepth)
|
||||
- [trim](http://aheckmann.github.com/gm/docs.html#trim)
|
||||
- [type](http://aheckmann.github.com/gm/docs.html#type)
|
||||
- [update](http://aheckmann.github.com/gm/docs.html#update)
|
||||
- [units](http://aheckmann.github.com/gm/docs.html#units)
|
||||
- [unsharp](http://aheckmann.github.com/gm/docs.html#unsharp)
|
||||
- [usePixmap](http://aheckmann.github.com/gm/docs.html#usePixmap)
|
||||
- [view](http://aheckmann.github.com/gm/docs.html#view)
|
||||
- [virtualPixel](http://aheckmann.github.com/gm/docs.html#virtualPixel)
|
||||
- [visual](http://aheckmann.github.com/gm/docs.html#visual)
|
||||
- [watermark](http://aheckmann.github.com/gm/docs.html#watermark)
|
||||
- [wave](http://aheckmann.github.com/gm/docs.html#wave)
|
||||
- [whitePoint](http://aheckmann.github.com/gm/docs.html#whitePoint)
|
||||
- [whiteThreshold](http://aheckmann.github.com/gm/docs.html#whiteThreshold)
|
||||
- [window](http://aheckmann.github.com/gm/docs.html#window)
|
||||
- [windowGroup](http://aheckmann.github.com/gm/docs.html#windowGroup)
|
||||
|
||||
- drawing primitives
|
||||
- [draw](http://aheckmann.github.com/gm/docs.html#draw)
|
||||
- [drawArc](http://aheckmann.github.com/gm/docs.html#drawArc)
|
||||
- [drawBezier](http://aheckmann.github.com/gm/docs.html#drawBezier)
|
||||
- [drawCircle](http://aheckmann.github.com/gm/docs.html#drawCircle)
|
||||
- [drawEllipse](http://aheckmann.github.com/gm/docs.html#drawEllipse)
|
||||
- [drawLine](http://aheckmann.github.com/gm/docs.html#drawLine)
|
||||
- [drawPoint](http://aheckmann.github.com/gm/docs.html#drawPoint)
|
||||
- [drawPolygon](http://aheckmann.github.com/gm/docs.html#drawPolygon)
|
||||
- [drawPolyline](http://aheckmann.github.com/gm/docs.html#drawPolyline)
|
||||
- [drawRectangle](http://aheckmann.github.com/gm/docs.html#drawRectangle)
|
||||
- [drawText](http://aheckmann.github.com/gm/docs.html#drawText)
|
||||
- [fill](http://aheckmann.github.com/gm/docs.html#fill)
|
||||
- [font](http://aheckmann.github.com/gm/docs.html#font)
|
||||
- [fontSize](http://aheckmann.github.com/gm/docs.html#fontSize)
|
||||
- [stroke](http://aheckmann.github.com/gm/docs.html#stroke)
|
||||
- [strokeWidth](http://aheckmann.github.com/gm/docs.html#strokeWidth)
|
||||
- [setDraw](http://aheckmann.github.com/gm/docs.html#setDraw)
|
||||
|
||||
- image output
|
||||
- **write** - writes the processed image data to the specified filename
|
||||
- **stream** - provides a `ReadableStream` with the processed image data
|
||||
- **toBuffer** - returns the image as a `Buffer` instead of a stream
|
||||
|
||||
##compare
|
||||
|
||||
Graphicsmagicks `compare` command is exposed through `gm.compare()`. This allows us to determine if two images can be considered "equal".
|
||||
|
||||
Currently `gm.compare` only accepts file paths.
|
||||
|
||||
gm.compare(path1, path2 [, options], callback)
|
||||
|
||||
```js
|
||||
gm.compare('/path/to/image1.jpg', '/path/to/another.png', function (err, isEqual, equality, raw, path1, path2) {
|
||||
if (err) return handle(err);
|
||||
|
||||
// if the images were considered equal, `isEqual` will be true, otherwise, false.
|
||||
console.log('The images were equal: %s', isEqual);
|
||||
|
||||
// to see the total equality returned by graphicsmagick we can inspect the `equality` argument.
|
||||
console.log('Actual equality: %d', equality);
|
||||
|
||||
// inspect the raw output
|
||||
console.log(raw);
|
||||
|
||||
// print file paths
|
||||
console.log(path1, path2);
|
||||
})
|
||||
```
|
||||
|
||||
You may wish to pass a custom tolerance threshold to increase or decrease the default level of `0.4`.
|
||||
|
||||
|
||||
```js
|
||||
gm.compare('/path/to/image1.jpg', '/path/to/another.png', 1.2, function (err, isEqual) {
|
||||
...
|
||||
})
|
||||
```
|
||||
|
||||
To output a diff image, pass a configuration object to define the diff options and tolerance.
|
||||
|
||||
|
||||
```js
|
||||
var options = {
|
||||
file: '/path/to/diff.png',
|
||||
highlightColor: 'yellow',
|
||||
tolerance: 0.02
|
||||
}
|
||||
gm.compare('/path/to/image1.jpg', '/path/to/another.png', options, function (err, isEqual, equality, raw) {
|
||||
...
|
||||
})
|
||||
```
|
||||
|
||||
##composite
|
||||
|
||||
GraphicsMagick supports compositing one image on top of another. This is exposed through `gm.composite()`. Its first argument is an image path with the changes to the base image, and an optional mask image.
|
||||
|
||||
Currently, `gm.composite()` only accepts file paths.
|
||||
|
||||
gm.composite(other [, mask])
|
||||
|
||||
```js
|
||||
gm('/path/to/image.jpg')
|
||||
.composite('/path/to/second_image.jpg')
|
||||
.geometry('+100+150')
|
||||
.write('/path/to/composite.png', function(err) {
|
||||
if(!err) console.log("Written composite image.");
|
||||
});
|
||||
```
|
||||
|
||||
##montage
|
||||
|
||||
GraphicsMagick supports montage for combining images side by side. This is exposed through `gm.montage()`. Its only argument is an image path with the changes to the base image.
|
||||
|
||||
Currently, `gm.montage()` only accepts file paths.
|
||||
|
||||
gm.montage(other)
|
||||
|
||||
```js
|
||||
gm('/path/to/image.jpg')
|
||||
.montage('/path/to/second_image.jpg')
|
||||
.geometry('+100+150')
|
||||
.write('/path/to/montage.png', function(err) {
|
||||
if(!err) console.log("Written montage image.");
|
||||
});
|
||||
```
|
||||
|
||||
## Contributors
|
||||
[https://github.com/aheckmann/gm/contributors](https://github.com/aheckmann/gm/contributors)
|
||||
|
||||
## Inspiration
|
||||
http://github.com/quiiver/magickal-node
|
||||
|
||||
## Plugins
|
||||
[https://github.com/aheckmann/gm/wiki](https://github.com/aheckmann/gm/wiki)
|
||||
|
||||
## License
|
||||
|
||||
(The MIT License)
|
||||
|
||||
Copyright (c) 2010 [Aaron Heckmann](aaron.heckmann+github@gmail.com)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
'Software'), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var Stream = require('stream').Stream;
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var util = require('util');
|
||||
|
||||
util.inherits(gm, EventEmitter);
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param {String|Number} path - path to img source or ReadableStream or width of img to create
|
||||
* @param {Number} [height] - optional filename of ReadableStream or height of img to create
|
||||
* @param {String} [color] - optional hex background color of created img
|
||||
*/
|
||||
|
||||
function gm (source, height, color) {
|
||||
var width;
|
||||
|
||||
if (!(this instanceof gm)) {
|
||||
return new gm(source, height, color);
|
||||
}
|
||||
|
||||
EventEmitter.call(this);
|
||||
|
||||
this._options = {};
|
||||
this.options(this.__proto__._options);
|
||||
|
||||
this.data = {};
|
||||
this._in = [];
|
||||
this._out = [];
|
||||
this._outputFormat = null;
|
||||
this._subCommand = 'convert';
|
||||
|
||||
if (source instanceof Stream) {
|
||||
this.sourceStream = source;
|
||||
source = height || 'unknown.jpg';
|
||||
} else if (Buffer.isBuffer(source)) {
|
||||
this.sourceBuffer = source;
|
||||
source = height || 'unknown.jpg';
|
||||
} else if (height) {
|
||||
// new images
|
||||
width = source;
|
||||
source = "";
|
||||
|
||||
this.in("-size", width + "x" + height);
|
||||
|
||||
if (color) {
|
||||
this.in("xc:"+ color);
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof source === "string") {
|
||||
// then source is a path
|
||||
|
||||
// parse out gif frame brackets from filename
|
||||
// since stream doesn't use source path
|
||||
// eg. "filename.gif[0]"
|
||||
var frames = source.match(/(\[.+\])$/);
|
||||
if (frames) {
|
||||
this.sourceFrames = source.substr(frames.index, frames[0].length);
|
||||
source = source.substr(0, frames.index);
|
||||
}
|
||||
}
|
||||
|
||||
this.source = source;
|
||||
|
||||
this.addSrcFormatter(function (src) {
|
||||
// must be first source formatter
|
||||
|
||||
var inputFromStdin = this.sourceStream || this.sourceBuffer;
|
||||
var ret = inputFromStdin ? '-' : this.source;
|
||||
|
||||
if (ret && this.sourceFrames) ret += this.sourceFrames;
|
||||
|
||||
src.length = 0;
|
||||
src[0] = ret;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclasses the gm constructor with custom options.
|
||||
*
|
||||
* @param {options} options
|
||||
* @return {gm} the subclasses gm constructor
|
||||
*/
|
||||
|
||||
var parent = gm;
|
||||
gm.subClass = function subClass (options) {
|
||||
function gm (source, height, color) {
|
||||
if (!(this instanceof parent)) {
|
||||
return new gm(source, height, color);
|
||||
}
|
||||
|
||||
parent.call(this, source, height, color);
|
||||
}
|
||||
|
||||
gm.prototype.__proto__ = parent.prototype;
|
||||
gm.prototype._options = {};
|
||||
gm.prototype.options(options);
|
||||
|
||||
return gm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Augment the prototype.
|
||||
*/
|
||||
|
||||
require("./lib/options")(gm.prototype);
|
||||
require("./lib/getters")(gm);
|
||||
require("./lib/args")(gm.prototype);
|
||||
require("./lib/drawing")(gm.prototype);
|
||||
require("./lib/convenience")(gm.prototype);
|
||||
require("./lib/command")(gm.prototype);
|
||||
require("./lib/compare")(gm.prototype);
|
||||
require("./lib/composite")(gm.prototype);
|
||||
require("./lib/montage")(gm.prototype);
|
||||
|
||||
/**
|
||||
* Expose.
|
||||
*/
|
||||
|
||||
module.exports = exports = gm;
|
||||
module.exports.utils = require('./lib/utils');
|
||||
module.exports.compare = require('./lib/compare')();
|
||||
module.exports.version = require('./package.json').version;
|
||||
+1122
File diff suppressed because it is too large
Load Diff
+446
@@ -0,0 +1,446 @@
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var spawn = require('cross-spawn');
|
||||
var utils = require('./utils');
|
||||
var debug = require('debug')('gm');
|
||||
var series = require('array-series');
|
||||
var PassThrough = require('stream').PassThrough;
|
||||
|
||||
/**
|
||||
* Error messaging.
|
||||
*/
|
||||
|
||||
var noBufferConcat = 'gm v1.9.0+ required node v0.8+. Please update your version of node, downgrade gm < 1.9, or do not use `bufferStream`.';
|
||||
|
||||
/**
|
||||
* Extend proto
|
||||
*/
|
||||
|
||||
module.exports = function (proto) {
|
||||
|
||||
function args (prop) {
|
||||
return function args () {
|
||||
var len = arguments.length;
|
||||
var a = [];
|
||||
var i = 0;
|
||||
|
||||
for (; i < len; ++i) {
|
||||
a.push(arguments[i]);
|
||||
}
|
||||
|
||||
this[prop] = this[prop].concat(a);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
function streamToUnemptyBuffer(stream, callback) {
|
||||
var done = false
|
||||
var buffers = []
|
||||
|
||||
stream.on('data', function (data) {
|
||||
buffers.push(data)
|
||||
})
|
||||
|
||||
stream.on('end', function () {
|
||||
var result, err;
|
||||
if (done)
|
||||
return
|
||||
|
||||
done = true
|
||||
result = Buffer.concat(buffers)
|
||||
buffers = null
|
||||
if (result.length==0)
|
||||
{
|
||||
err = new Error("Stream yields empty buffer");
|
||||
callback(err, null);
|
||||
} else {
|
||||
callback(null, result);
|
||||
}
|
||||
})
|
||||
|
||||
stream.on('error', function (err) {
|
||||
done = true
|
||||
buffers = null
|
||||
callback(err)
|
||||
})
|
||||
}
|
||||
|
||||
proto.in = args('_in');
|
||||
proto.out = args('_out');
|
||||
|
||||
proto._preprocessor = [];
|
||||
proto.preprocessor = args('_preprocessor');
|
||||
|
||||
/**
|
||||
* Execute the command and write the image to the specified file name.
|
||||
*
|
||||
* @param {String} name
|
||||
* @param {Function} callback
|
||||
* @return {Object} gm
|
||||
*/
|
||||
|
||||
proto.write = function write (name, callback) {
|
||||
if (!callback) callback = name, name = null;
|
||||
|
||||
if ("function" !== typeof callback) {
|
||||
throw new TypeError("gm().write() expects a callback function")
|
||||
}
|
||||
|
||||
if (!name) {
|
||||
return callback(TypeError("gm().write() expects a filename when writing new files"));
|
||||
}
|
||||
|
||||
this.outname = name;
|
||||
|
||||
var self = this;
|
||||
this._preprocess(function (err) {
|
||||
if (err) return callback(err);
|
||||
self._spawn(self.args(), true, callback);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the command and return stdin and stderr
|
||||
* ReadableStreams providing the image data.
|
||||
* If no callback is passed, a "through" stream will be returned,
|
||||
* and stdout will be piped through, otherwise the error will be passed.
|
||||
*
|
||||
* @param {String} format (optional)
|
||||
* @param {Function} callback (optional)
|
||||
* @return {Stream}
|
||||
*/
|
||||
|
||||
proto.stream = function stream (format, callback) {
|
||||
if (!callback && typeof format === 'function') {
|
||||
callback = format;
|
||||
format = null;
|
||||
}
|
||||
|
||||
var throughStream;
|
||||
|
||||
if ("function" !== typeof callback) {
|
||||
throughStream = new PassThrough();
|
||||
callback = function (err, stdout, stderr) {
|
||||
if (err) throughStream.emit('error', err);
|
||||
else stdout.pipe(throughStream);
|
||||
}
|
||||
}
|
||||
|
||||
if (format) {
|
||||
format = format.split('.').pop();
|
||||
this.outname = format + ":-";
|
||||
}
|
||||
|
||||
var self = this;
|
||||
this._preprocess(function (err) {
|
||||
if (err) return callback(err);
|
||||
return self._spawn(self.args(), false, callback);
|
||||
});
|
||||
|
||||
return throughStream || this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience function for `proto.stream`.
|
||||
* Simply returns the buffer instead of the stream.
|
||||
*
|
||||
* @param {String} format (optional)
|
||||
* @param {Function} callback
|
||||
* @return {null}
|
||||
*/
|
||||
|
||||
proto.toBuffer = function toBuffer (format, callback) {
|
||||
if (!callback) callback = format, format = null;
|
||||
|
||||
if ("function" !== typeof callback) {
|
||||
throw new Error('gm().toBuffer() expects a callback.');
|
||||
}
|
||||
|
||||
return this.stream(format, function (err, stdout) {
|
||||
if (err) return callback(err);
|
||||
|
||||
streamToUnemptyBuffer(stdout, callback);
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Run any preProcessor functions in series. Used by autoOrient.
|
||||
*
|
||||
* @param {Function} callback
|
||||
* @return {Object} gm
|
||||
*/
|
||||
|
||||
proto._preprocess = function _preprocess (callback) {
|
||||
series(this._preprocessor, this, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the command, buffer input and output, return stdout and stderr buffers.
|
||||
*
|
||||
* @param {String} bin
|
||||
* @param {Array} args
|
||||
* @param {Function} callback
|
||||
* @return {Object} gm
|
||||
*/
|
||||
|
||||
proto._exec = function _exec (args, callback) {
|
||||
return this._spawn(args, true, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the command with stdin, returning stdout and stderr streams or buffers.
|
||||
* @param {String} bin
|
||||
* @param {Array} args
|
||||
* @param {ReadableStream} stream
|
||||
* @param {Boolean} shouldBuffer
|
||||
* @param {Function} callback, signature (err, stdout, stderr) -> *
|
||||
* @return {Object} gm
|
||||
* @TODO refactor this mess
|
||||
*/
|
||||
|
||||
proto._spawn = function _spawn (args, bufferOutput, callback) {
|
||||
var appPath = this._options.appPath || '';
|
||||
var bin = this._options.imageMagick
|
||||
? appPath + args.shift()
|
||||
: appPath + 'gm'
|
||||
|
||||
var cmd = bin + ' ' + args.map(utils.escape).join(' ')
|
||||
, self = this
|
||||
, proc, err
|
||||
, timeout = parseInt(this._options.timeout)
|
||||
, disposers = this._options.disposers
|
||||
, timeoutId;
|
||||
|
||||
debug(cmd);
|
||||
//imageMagick does not support minify (https://github.com/aheckmann/gm/issues/385)
|
||||
if(args.indexOf("-minify") > -1 && this._options.imageMagick){
|
||||
err = new Error("imageMagick does not support minify, use -scale or -sample. Alternatively, use graphicsMagick");
|
||||
return cb(err);
|
||||
}
|
||||
try {
|
||||
proc = spawn(bin, args);
|
||||
} catch (e) {
|
||||
return cb(e);
|
||||
}
|
||||
proc.stdin.once('error', cb);
|
||||
|
||||
proc.on('error', function(err){
|
||||
if (err.code === 'ENOENT') {
|
||||
cb(new Error('Could not execute GraphicsMagick/ImageMagick: '+cmd+" this most likely means the gm/convert binaries can't be found"));
|
||||
} else {
|
||||
cb(err);
|
||||
}
|
||||
});
|
||||
|
||||
if (timeout) {
|
||||
timeoutId = setTimeout(function(){
|
||||
dispose('gm() resulted in a timeout.');
|
||||
}, timeout);
|
||||
}
|
||||
|
||||
if (disposers) {
|
||||
disposers.forEach(function(disposer) {
|
||||
disposer.events.forEach(function(event) {
|
||||
disposer.emitter.on(event, dispose);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (self.sourceBuffer) {
|
||||
proc.stdin.write(this.sourceBuffer);
|
||||
proc.stdin.end();
|
||||
} else if (self.sourceStream) {
|
||||
|
||||
if (!self.sourceStream.readable) {
|
||||
err = new Error("gm().stream() or gm().write() with a non-readable stream.");
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
self.sourceStream.pipe(proc.stdin);
|
||||
|
||||
// bufferStream
|
||||
// We convert the input source from a stream to a buffer.
|
||||
if (self.bufferStream && !this._buffering) {
|
||||
if (!Buffer.concat) {
|
||||
throw new Error(noBufferConcat);
|
||||
}
|
||||
|
||||
// Incase there are multiple processes in parallel,
|
||||
// we only need one
|
||||
self._buffering = true;
|
||||
|
||||
streamToUnemptyBuffer(self.sourceStream, function (err, buffer) {
|
||||
self.sourceBuffer = buffer;
|
||||
self.sourceStream = null; // The stream is now dead
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// for _exec operations (identify() mostly), we also
|
||||
// need to buffer the output stream before returning
|
||||
if (bufferOutput) {
|
||||
var stdout = ''
|
||||
, stderr = ''
|
||||
, onOut
|
||||
, onErr
|
||||
, onExit
|
||||
|
||||
proc.stdout.on('data', onOut = function (data) {
|
||||
stdout += data;
|
||||
});
|
||||
|
||||
proc.stderr.on('data', onErr = function (data) {
|
||||
stderr += data;
|
||||
});
|
||||
|
||||
proc.on('close', onExit = function (code, signal) {
|
||||
if (code !== 0 || signal !== null) {
|
||||
err = new Error('Command failed: ' + stderr);
|
||||
err.code = code;
|
||||
err.signal = signal;
|
||||
};
|
||||
cb(err, stdout, stderr, cmd);
|
||||
stdout = stderr = onOut = onErr = onExit = null;
|
||||
});
|
||||
} else {
|
||||
cb(null, proc.stdout, proc.stderr, cmd);
|
||||
}
|
||||
|
||||
return self;
|
||||
|
||||
function cb (err, stdout, stderr, cmd) {
|
||||
if (cb.called) return;
|
||||
if (timeoutId) clearTimeout(timeoutId);
|
||||
cb.called = 1;
|
||||
if (args[0] !== 'identify' && bin !== 'identify') {
|
||||
self._in = [];
|
||||
self._out = [];
|
||||
}
|
||||
callback.call(self, err, stdout, stderr, cmd);
|
||||
}
|
||||
|
||||
function dispose (msg) {
|
||||
var message = msg ? msg : 'gm() was disposed';
|
||||
err = new Error(message);
|
||||
cb(err);
|
||||
if (proc.exitCode === null) {
|
||||
proc.stdin.pause();
|
||||
proc.kill();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns arguments to be used in the command.
|
||||
*
|
||||
* @return {Array}
|
||||
*/
|
||||
|
||||
proto.args = function args () {
|
||||
var outname = this.outname || "-";
|
||||
if (this._outputFormat) outname = this._outputFormat + ':' + outname;
|
||||
|
||||
return [].concat(
|
||||
this._subCommand
|
||||
, this._in
|
||||
, this.src()
|
||||
, this._out
|
||||
, outname
|
||||
).filter(Boolean); // remove falsey
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an img source formatter.
|
||||
*
|
||||
* `formatters` are passed an array of images which will be
|
||||
* used as 'input' images for the command. Useful for methods
|
||||
* like `.append()` where multiple source images may be used.
|
||||
*
|
||||
* @param {Function} formatter
|
||||
* @return {gm} this
|
||||
*/
|
||||
|
||||
proto.addSrcFormatter = function addSrcFormatter (formatter) {
|
||||
if ('function' != typeof formatter)
|
||||
throw new TypeError('sourceFormatter must be a function');
|
||||
this._sourceFormatters || (this._sourceFormatters = []);
|
||||
this._sourceFormatters.push(formatter);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies all _sourceFormatters
|
||||
*
|
||||
* @return {Array}
|
||||
*/
|
||||
|
||||
proto.src = function src () {
|
||||
var arr = [];
|
||||
for (var i = 0; i < this._sourceFormatters.length; ++i) {
|
||||
this._sourceFormatters[i].call(this, arr);
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Image types.
|
||||
*/
|
||||
|
||||
var types = {
|
||||
'jpg': /\.jpe?g$/i
|
||||
, 'png' : /\.png$/i
|
||||
, 'gif' : /\.gif$/i
|
||||
, 'tiff': /\.tif?f$/i
|
||||
, 'bmp' : /(?:\.bmp|\.dib)$/i
|
||||
, 'webp': /\.webp$/i
|
||||
};
|
||||
|
||||
types.jpeg = types.jpg;
|
||||
types.tif = types.tiff;
|
||||
types.dib = types.bmp;
|
||||
|
||||
/**
|
||||
* Determine the type of source image.
|
||||
*
|
||||
* @param {String} type
|
||||
* @return {Boolean}
|
||||
* @example
|
||||
* if (this.inputIs('png')) ...
|
||||
*/
|
||||
|
||||
proto.inputIs = function inputIs (type) {
|
||||
if (!type) return false;
|
||||
|
||||
var rgx = types[type];
|
||||
if (!rgx) {
|
||||
if ('.' !== type[0]) type = '.' + type;
|
||||
rgx = new RegExp('\\' + type + '$', 'i');
|
||||
}
|
||||
|
||||
return rgx.test(this.source);
|
||||
}
|
||||
|
||||
/**
|
||||
* add disposer (like 'close' of http.IncomingMessage) in order to dispose gm() with any event
|
||||
*
|
||||
* @param {EventEmitter} emitter
|
||||
* @param {Array} events
|
||||
* @return {Object} gm
|
||||
* @example
|
||||
* command.addDisposer(req, ['close', 'end', 'finish']);
|
||||
*/
|
||||
|
||||
proto.addDisposer = function addDisposer (emitter, events) {
|
||||
if (!this._options.disposers) {
|
||||
this._options.disposers = [];
|
||||
}
|
||||
this._options.disposers.push({
|
||||
emitter: emitter,
|
||||
events: events
|
||||
});
|
||||
return this;
|
||||
};
|
||||
}
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
// compare
|
||||
|
||||
var spawn = require('cross-spawn');
|
||||
|
||||
/**
|
||||
* Compare two images uses graphicsmagicks `compare` command.
|
||||
*
|
||||
* gm.compare(img1, img2, 0.4, function (err, equal, equality) {
|
||||
* if (err) return handle(err);
|
||||
* console.log('The images are equal: %s', equal);
|
||||
* console.log('There equality was %d', equality);
|
||||
* });
|
||||
*
|
||||
* @param {String} orig Path to an image.
|
||||
* @param {String} compareTo Path to another image to compare to `orig`.
|
||||
* @param {Number|Object} [options] Options object or the amount of difference to tolerate before failing - defaults to 0.4
|
||||
* @param {Function} cb(err, Boolean, equality, rawOutput)
|
||||
*/
|
||||
|
||||
module.exports = exports = function (proto) {
|
||||
function compare(orig, compareTo, options, cb) {
|
||||
|
||||
var isImageMagick = this._options && this._options.imageMagick;
|
||||
var appPath = this._options && this._options.appPath || '';
|
||||
var bin = isImageMagick
|
||||
? appPath + 'compare'
|
||||
: appPath + 'gm'
|
||||
var args = ['-metric', 'mse', orig, compareTo]
|
||||
if (!isImageMagick) {
|
||||
args.unshift('compare');
|
||||
}
|
||||
var tolerance = 0.4;
|
||||
// outputting the diff image
|
||||
if (typeof options === 'object') {
|
||||
|
||||
if (options.highlightColor && options.highlightColor.indexOf('"') < 0) {
|
||||
options.highlightColor = '"' + options.highlightColor + '"';
|
||||
}
|
||||
|
||||
if (options.file) {
|
||||
if (typeof options.file !== 'string') {
|
||||
throw new TypeError('The path for the diff output is invalid');
|
||||
}
|
||||
// graphicsmagick defaults to red
|
||||
if (options.highlightColor) {
|
||||
args.push('-highlight-color');
|
||||
args.push(options.highlightColor);
|
||||
}
|
||||
if (options.highlightStyle) {
|
||||
args.push('-highlight-style')
|
||||
args.push(options.highlightStyle)
|
||||
}
|
||||
// For IM, filename is the last argument. For GM it's `-file <filename>`
|
||||
if (!isImageMagick) {
|
||||
args.push('-file');
|
||||
}
|
||||
args.push(options.file);
|
||||
}
|
||||
|
||||
if (typeof options.tolerance != 'undefined') {
|
||||
if (typeof options.tolerance !== 'number') {
|
||||
throw new TypeError('The tolerance value should be a number');
|
||||
}
|
||||
tolerance = options.tolerance;
|
||||
}
|
||||
} else {
|
||||
// For ImageMagick diff file is required but we don't care about it, so null it out
|
||||
if (isImageMagick) {
|
||||
args.push('null:');
|
||||
}
|
||||
|
||||
if (typeof options == 'function') {
|
||||
cb = options; // tolerance value not provided, flip the cb place
|
||||
} else {
|
||||
tolerance = options
|
||||
}
|
||||
}
|
||||
|
||||
var proc = spawn(bin, args);
|
||||
var stdout = '';
|
||||
var stderr = '';
|
||||
proc.stdout.on('data',function(data) { stdout+=data });
|
||||
proc.stderr.on('data',function(data) { stderr+=data });
|
||||
proc.on('close', function (code) {
|
||||
// ImageMagick returns err code 2 if err, 0 if similar, 1 if dissimilar
|
||||
if (isImageMagick) {
|
||||
if (code === 0) {
|
||||
return cb(null, 0 <= tolerance, 0, stdout);
|
||||
}
|
||||
else if (code === 1) {
|
||||
err = null;
|
||||
stdout = stderr;
|
||||
} else {
|
||||
return cb(stderr);
|
||||
}
|
||||
} else {
|
||||
if(code !== 0) {
|
||||
return cb(stderr);
|
||||
}
|
||||
}
|
||||
// Since ImageMagick similar gives err code 0 and no stdout, there's really no matching
|
||||
// Otherwise, output format for IM is `12.00 (0.123)` and for GM it's `Total: 0.123`
|
||||
var regex = isImageMagick ? /\((\d+\.?[\d\-\+e]*)\)/m : /Total: (\d+\.?\d*)/m;
|
||||
var match = regex.exec(stdout);
|
||||
if (!match) {
|
||||
err = new Error('Unable to parse output.\nGot ' + stdout);
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
var equality = parseFloat(match[1]);
|
||||
cb(null, equality <= tolerance, equality, stdout, orig, compareTo);
|
||||
});
|
||||
}
|
||||
|
||||
if (proto) {
|
||||
proto.compare = compare;
|
||||
}
|
||||
return compare;
|
||||
};
|
||||
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
// composite
|
||||
|
||||
/**
|
||||
* Composite images together using the `composite` command in graphicsmagick.
|
||||
*
|
||||
* gm('/path/to/image.jpg')
|
||||
* .composite('/path/to/second_image.jpg')
|
||||
* .geometry('+100+150')
|
||||
* .write('/path/to/composite.png', function(err) {
|
||||
* if(!err) console.log("Written composite image.");
|
||||
* });
|
||||
*
|
||||
* @param {String} other Path to the image that contains the changes.
|
||||
* @param {String} [mask] Path to the image with opacity informtion. Grayscale.
|
||||
*/
|
||||
|
||||
module.exports = exports = function(proto) {
|
||||
proto.composite = function(other, mask) {
|
||||
this.in(other);
|
||||
|
||||
// If the mask is defined, add it to the output.
|
||||
if(typeof mask !== "undefined")
|
||||
this.out(mask);
|
||||
|
||||
this.subCommand("composite");
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
|
||||
/**
|
||||
* Extend proto
|
||||
*/
|
||||
|
||||
module.exports = function (proto) {
|
||||
require("./convenience/thumb")(proto);
|
||||
require("./convenience/morph")(proto);
|
||||
require("./convenience/sepia")(proto);
|
||||
require("./convenience/autoOrient")(proto);
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
|
||||
/**
|
||||
* Extend proto.
|
||||
*/
|
||||
|
||||
module.exports = function (proto) {
|
||||
|
||||
var exifTransforms = {
|
||||
topleft: ''
|
||||
, topright: ['-flop']
|
||||
, bottomright: ['-rotate', 180]
|
||||
, bottomleft: ['-flip']
|
||||
, lefttop: ['-flip', '-rotate', 90]
|
||||
, righttop: ['-rotate', 90]
|
||||
, rightbottom: ['-flop', '-rotate', 90]
|
||||
, leftbottom: ['-rotate', 270]
|
||||
}
|
||||
|
||||
proto.autoOrient = function autoOrient () {
|
||||
// Always strip EXIF data since we can't
|
||||
// change/edit it.
|
||||
|
||||
// imagemagick has a native -auto-orient option
|
||||
// so does graphicsmagick, but in 1.3.18.
|
||||
// nativeAutoOrient option enables this if you know you have >= 1.3.18
|
||||
if (this._options.nativeAutoOrient || this._options.imageMagick) {
|
||||
this.out('-auto-orient');
|
||||
this.strip();
|
||||
return this;
|
||||
}
|
||||
|
||||
this.preprocessor(function (callback) {
|
||||
this.orientation({bufferStream: true}, function (err, orientation) {
|
||||
if (err) return callback(err);
|
||||
|
||||
var transforms = exifTransforms[orientation.toLowerCase()];
|
||||
if (transforms) {
|
||||
|
||||
// remove any existing transforms that might conflict
|
||||
var index = this._out.indexOf(transforms[0]);
|
||||
if (~index) {
|
||||
this._out.splice(index, transforms.length);
|
||||
}
|
||||
|
||||
// repage to fix coordinates
|
||||
this._out.unshift.apply(this._out, transforms.concat('-page', '+0+0'));
|
||||
}
|
||||
|
||||
this.strip();
|
||||
|
||||
callback();
|
||||
});
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var fs = require('fs');
|
||||
var parallel = require('array-parallel');
|
||||
|
||||
/**
|
||||
* Extend proto.
|
||||
*/
|
||||
|
||||
module.exports = function (proto) {
|
||||
|
||||
/**
|
||||
* Do nothing.
|
||||
*/
|
||||
|
||||
function noop () {}
|
||||
|
||||
// http://www.graphicsmagick.org/GraphicsMagick.html#details-morph
|
||||
proto.morph = function morph (other, outname, callback) {
|
||||
if (!outname) {
|
||||
throw new Error("an output filename is required");
|
||||
}
|
||||
|
||||
callback = (callback || noop).bind(this)
|
||||
|
||||
var self = this;
|
||||
|
||||
if (Array.isArray(other)) {
|
||||
other.forEach(function (img) {
|
||||
self.out(img);
|
||||
});
|
||||
self.out("-morph", other.length);
|
||||
} else {
|
||||
self.out(other, "-morph", 1);
|
||||
}
|
||||
|
||||
self.write(outname, function (err, stdout, stderr, cmd) {
|
||||
if (err) return callback(err, stdout, stderr, cmd);
|
||||
|
||||
// Apparently some platforms create the following temporary files.
|
||||
// Check if the output file exists, if it doesn't, then
|
||||
// work with temporary files.
|
||||
fs.exists(outname, function (exists) {
|
||||
if (exists) return callback(null, stdout, stderr, cmd);
|
||||
|
||||
parallel([
|
||||
fs.unlink.bind(fs, outname + '.0'),
|
||||
fs.unlink.bind(fs, outname + '.2'),
|
||||
fs.rename.bind(fs, outname + '.1', outname)
|
||||
], function (err) {
|
||||
callback(err, stdout, stderr, cmd);
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
return self;
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
|
||||
/**
|
||||
* Extend proto.
|
||||
*/
|
||||
|
||||
module.exports = function (proto) {
|
||||
proto.sepia = function sepia () {
|
||||
return this.modulate(115, 0, 100).colorize(7, 21, 50);
|
||||
}
|
||||
}
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
|
||||
/**
|
||||
* Extend proto.
|
||||
*/
|
||||
|
||||
module.exports = function (proto) {
|
||||
|
||||
proto.thumb = function thumb (w, h, name, quality, align, progressive, callback, opts) {
|
||||
var self = this,
|
||||
args = Array.prototype.slice.call(arguments);
|
||||
|
||||
opts = args.pop();
|
||||
|
||||
if (typeof opts === 'function') {
|
||||
callback = opts;
|
||||
opts = '';
|
||||
} else {
|
||||
callback = args.pop();
|
||||
}
|
||||
|
||||
w = args.shift();
|
||||
h = args.shift();
|
||||
name = args.shift();
|
||||
quality = args.shift() || 63;
|
||||
align = args.shift() || 'topleft';
|
||||
var interlace = args.shift() ? 'Line' : 'None';
|
||||
|
||||
self.size(function (err, size) {
|
||||
if (err) {
|
||||
return callback.apply(self, arguments);
|
||||
}
|
||||
|
||||
w = parseInt(w, 10);
|
||||
h = parseInt(h, 10);
|
||||
|
||||
var w1, h1;
|
||||
var xoffset = 0;
|
||||
var yoffset = 0;
|
||||
|
||||
if (size.width < size.height) {
|
||||
w1 = w;
|
||||
h1 = Math.floor(size.height * (w/size.width));
|
||||
if (h1 < h) {
|
||||
w1 = Math.floor(w1 * (((h-h1)/h) + 1));
|
||||
h1 = h;
|
||||
}
|
||||
} else if (size.width > size.height) {
|
||||
h1 = h;
|
||||
w1 = Math.floor(size.width * (h/size.height));
|
||||
if (w1 < w) {
|
||||
h1 = Math.floor(h1 * (((w-w1)/w) + 1));
|
||||
w1 = w;
|
||||
}
|
||||
} else if (size.width == size.height) {
|
||||
var bigger = (w>h?w:h);
|
||||
w1 = bigger;
|
||||
h1 = bigger;
|
||||
}
|
||||
|
||||
if (align == 'center') {
|
||||
if (w < w1) {
|
||||
xoffset = (w1-w)/2;
|
||||
}
|
||||
if (h < h1) {
|
||||
yoffset = (h1-h)/2;
|
||||
}
|
||||
}
|
||||
|
||||
self
|
||||
.quality(quality)
|
||||
.in("-size", w1+"x"+h1)
|
||||
.scale(w1, h1, opts)
|
||||
.crop(w, h, xoffset, yoffset)
|
||||
.interlace(interlace)
|
||||
.noProfile()
|
||||
.write(name, function () {
|
||||
callback.apply(self, arguments);
|
||||
});
|
||||
});
|
||||
|
||||
return self;
|
||||
};
|
||||
|
||||
proto.thumbExact = function () {
|
||||
var self = this,
|
||||
args = Array.prototype.slice.call(arguments);
|
||||
|
||||
args.push('!');
|
||||
|
||||
self.thumb.apply(self, args);
|
||||
};
|
||||
};
|
||||
+160
@@ -0,0 +1,160 @@
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var escape = require('./utils').escape;
|
||||
|
||||
/**
|
||||
* Extend proto.
|
||||
*/
|
||||
|
||||
module.exports = function (proto) {
|
||||
|
||||
// http://www.graphicsmagick.org/GraphicsMagick.html#details-fill
|
||||
proto.fill = function fill (color) {
|
||||
return this.out("-fill", color || "none");
|
||||
}
|
||||
|
||||
// http://www.graphicsmagick.org/GraphicsMagick.html#details-stroke
|
||||
proto.stroke = function stroke (color, width) {
|
||||
if (width) {
|
||||
this.strokeWidth(width);
|
||||
}
|
||||
|
||||
return this.out("-stroke", color || "none");
|
||||
}
|
||||
|
||||
// http://www.graphicsmagick.org/GraphicsMagick.html#details-strokewidth
|
||||
proto.strokeWidth = function strokeWidth (width) {
|
||||
return this.out("-strokewidth", width);
|
||||
}
|
||||
|
||||
// http://www.graphicsmagick.org/GraphicsMagick.html#details-font
|
||||
proto.font = function font (font, size) {
|
||||
if (size) {
|
||||
this.fontSize(size);
|
||||
}
|
||||
|
||||
return this.out("-font", font);
|
||||
}
|
||||
|
||||
// http://www.graphicsmagick.org/GraphicsMagick.html
|
||||
proto.fontSize = function fontSize (size) {
|
||||
return this.out("-pointsize", size);
|
||||
}
|
||||
|
||||
// http://www.graphicsmagick.org/GraphicsMagick.html#details-draw
|
||||
proto.draw = function draw (args) {
|
||||
return this.out("-draw", [].slice.call(arguments).join(" "));
|
||||
}
|
||||
|
||||
// http://www.graphicsmagick.org/GraphicsMagick.html#details-draw
|
||||
proto.drawPoint = function drawPoint (x, y) {
|
||||
return this.draw("point", x +","+ y);
|
||||
}
|
||||
|
||||
// http://www.graphicsmagick.org/GraphicsMagick.html#details-draw
|
||||
proto.drawLine = function drawLine (x0, y0, x1, y1) {
|
||||
return this.draw("line", x0+","+y0, x1+","+y1);
|
||||
}
|
||||
|
||||
// http://www.graphicsmagick.org/GraphicsMagick.html#details-draw
|
||||
proto.drawRectangle = function drawRectangle (x0, y0, x1, y1, wc, hc) {
|
||||
var shape = "rectangle"
|
||||
, lastarg;
|
||||
|
||||
if ("undefined" !== typeof wc) {
|
||||
shape = "roundRectangle";
|
||||
|
||||
if ("undefined" === typeof hc) {
|
||||
hc = wc;
|
||||
}
|
||||
|
||||
lastarg = wc+","+hc;
|
||||
}
|
||||
|
||||
return this.draw(shape, x0+","+y0, x1+","+y1, lastarg);
|
||||
}
|
||||
|
||||
// http://www.graphicsmagick.org/GraphicsMagick.html#details-draw
|
||||
proto.drawArc = function drawArc (x0, y0, x1, y1, a0, a1) {
|
||||
return this.draw("arc", x0+","+y0, x1+","+y1, a0+","+a1);
|
||||
}
|
||||
|
||||
// http://www.graphicsmagick.org/GraphicsMagick.html#details-draw
|
||||
proto.drawEllipse = function drawEllipse (x0, y0, rx, ry, a0, a1) {
|
||||
if (a0 == undefined) a0 = 0;
|
||||
if (a1 == undefined) a1 = 360;
|
||||
return this.draw("ellipse", x0+","+y0, rx+","+ry, a0+","+a1);
|
||||
}
|
||||
|
||||
// http://www.graphicsmagick.org/GraphicsMagick.html#details-draw
|
||||
proto.drawCircle = function drawCircle (x0, y0, x1, y1) {
|
||||
return this.draw("circle", x0+","+y0, x1+","+y1);
|
||||
}
|
||||
|
||||
// http://www.graphicsmagick.org/GraphicsMagick.html#details-draw
|
||||
proto.drawPolyline = function drawPolyline () {
|
||||
return this.draw("polyline", formatPoints(arguments));
|
||||
}
|
||||
|
||||
// http://www.graphicsmagick.org/GraphicsMagick.html#details-draw
|
||||
proto.drawPolygon = function drawPolygon () {
|
||||
return this.draw("polygon", formatPoints(arguments));
|
||||
}
|
||||
|
||||
// http://www.graphicsmagick.org/GraphicsMagick.html#details-draw
|
||||
proto.drawBezier = function drawBezier () {
|
||||
return this.draw("bezier", formatPoints(arguments));
|
||||
}
|
||||
|
||||
proto._gravities = [
|
||||
"northwest"
|
||||
, "north"
|
||||
, "northeast"
|
||||
, "west"
|
||||
, "center"
|
||||
, "east"
|
||||
, "southwest"
|
||||
, "south"
|
||||
, "southeast"];
|
||||
|
||||
// http://www.graphicsmagick.org/GraphicsMagick.html#details-draw
|
||||
proto.drawText = function drawText (x0, y0, text, gravity) {
|
||||
var gravity = String(gravity || "").toLowerCase()
|
||||
, arg = ["text " + x0 + "," + y0 + " " + escape(text)];
|
||||
|
||||
if (~this._gravities.indexOf(gravity)) {
|
||||
arg.unshift("gravity", gravity);
|
||||
}
|
||||
|
||||
return this.draw.apply(this, arg);
|
||||
}
|
||||
|
||||
proto._drawProps = ["color", "matte"];
|
||||
|
||||
// http://www.graphicsmagick.org/GraphicsMagick.html#details-draw
|
||||
proto.setDraw = function setDraw (prop, x, y, method) {
|
||||
prop = String(prop || "").toLowerCase();
|
||||
|
||||
if (!~this._drawProps.indexOf(prop)) {
|
||||
return this;
|
||||
}
|
||||
|
||||
return this.draw(prop, x+","+y, method);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function formatPoints (points) {
|
||||
var len = points.length
|
||||
, result = []
|
||||
, i = 0;
|
||||
|
||||
for (; i < len; ++i) {
|
||||
result.push(points[i].join(","));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
+346
@@ -0,0 +1,346 @@
|
||||
/**
|
||||
* Extend proto.
|
||||
*/
|
||||
|
||||
module.exports = function (gm) {
|
||||
|
||||
var proto = gm.prototype;
|
||||
|
||||
/**
|
||||
* `identify` states
|
||||
*/
|
||||
|
||||
const IDENTIFYING = 1;
|
||||
const IDENTIFIED = 2;
|
||||
|
||||
/**
|
||||
* Map getter functions to output names.
|
||||
*
|
||||
* - format: specifying the -format argument (see man gm)
|
||||
* - verbose: use -verbose instead of -format (only if necessary b/c its slow)
|
||||
* - helper: use the conversion helper
|
||||
*/
|
||||
|
||||
var map = {
|
||||
'format': { key: 'format', format: '%m ', helper: 'Format' }
|
||||
, 'depth': { key: 'depth', format: '%q' }
|
||||
, 'filesize': { key: 'Filesize', format: '%b' }
|
||||
, 'size': { key: 'size', format: '%wx%h ', helper: 'Geometry' }
|
||||
, 'color': { key: 'color', format: '%k', helper: 'Colors' }
|
||||
, 'orientation': { key: 'Orientation', format: '%[EXIF:Orientation]', helper: 'Orientation' }
|
||||
, 'res': { key: 'Resolution', verbose: true }
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter functions
|
||||
*/
|
||||
|
||||
Object.keys(map).forEach(function (getter) {
|
||||
proto[getter] = function (opts, callback) {
|
||||
if (!callback) callback = opts, opts = {};
|
||||
if (!callback) return this;
|
||||
|
||||
var val = map[getter]
|
||||
, key = val.key
|
||||
, self = this;
|
||||
|
||||
if (self.data[key]) {
|
||||
callback.call(self, null, self.data[key]);
|
||||
return self;
|
||||
}
|
||||
|
||||
self.on(getter, callback);
|
||||
|
||||
self.bufferStream = !!opts.bufferStream;
|
||||
|
||||
if (val.verbose) {
|
||||
self.identify(opts, function (err, stdout, stderr, cmd) {
|
||||
if (err) {
|
||||
self.emit(getter, err, self.data[key], stdout, stderr, cmd);
|
||||
} else {
|
||||
self.emit(getter, err, self.data[key]);
|
||||
}
|
||||
});
|
||||
return self;
|
||||
}
|
||||
|
||||
var args = makeArgs(self, val);
|
||||
self._exec(args, function (err, stdout, stderr, cmd) {
|
||||
if (err) {
|
||||
self.emit(getter, err, self.data[key], stdout, stderr, cmd);
|
||||
return;
|
||||
}
|
||||
|
||||
var result = (stdout||'').trim();
|
||||
|
||||
if (val.helper in helper) {
|
||||
helper[val.helper](self.data, result);
|
||||
} else {
|
||||
self.data[key] = result;
|
||||
}
|
||||
|
||||
self.emit(getter, err, self.data[key]);
|
||||
});
|
||||
|
||||
return self;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* identify command
|
||||
*
|
||||
* Overwrites all internal data with the parsed output
|
||||
* which is more accurate than the fast shortcut
|
||||
* getters.
|
||||
*/
|
||||
|
||||
proto.identify = function identify (opts, callback) {
|
||||
// identify with pattern
|
||||
if (typeof(opts) === 'string') {
|
||||
opts = {
|
||||
format: opts
|
||||
}
|
||||
}
|
||||
if (!callback) callback = opts, opts = {};
|
||||
if (!callback) return this;
|
||||
if (opts && opts.format) return identifyPattern.call(this, opts, callback);
|
||||
|
||||
var self = this;
|
||||
|
||||
if (IDENTIFIED === self._identifyState) {
|
||||
callback.call(self, null, self.data);
|
||||
return self;
|
||||
}
|
||||
|
||||
self.on('identify', callback);
|
||||
|
||||
if (IDENTIFYING === self._identifyState) {
|
||||
return self;
|
||||
}
|
||||
|
||||
self._identifyState = IDENTIFYING;
|
||||
|
||||
self.bufferStream = !!opts.bufferStream;
|
||||
|
||||
var args = makeArgs(self, { verbose: true });
|
||||
|
||||
self._exec(args, function (err, stdout, stderr, cmd) {
|
||||
if (err) {
|
||||
self.emit('identify', err, self.data, stdout, stderr, cmd);
|
||||
return;
|
||||
}
|
||||
|
||||
err = parse(stdout, self);
|
||||
|
||||
if (err) {
|
||||
self.emit('identify', err, self.data, stdout, stderr, cmd);
|
||||
return;
|
||||
}
|
||||
|
||||
self.data.path = self.source;
|
||||
|
||||
self.emit('identify', null, self.data);
|
||||
self._identifyState = IDENTIFIED;
|
||||
});
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* identify with pattern
|
||||
*
|
||||
* Execute `identify -format` with custom pattern
|
||||
*/
|
||||
|
||||
function identifyPattern (opts, callback) {
|
||||
var self = this;
|
||||
|
||||
self.bufferStream = !!opts.bufferStream;
|
||||
|
||||
var args = makeArgs(self, opts);
|
||||
self._exec(args, function (err, stdout, stderr, cmd) {
|
||||
if (err) {
|
||||
return callback.call(self, err, undefined, stdout, stderr, cmd);
|
||||
}
|
||||
|
||||
callback.call(self, err, (stdout||'').trim());
|
||||
});
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parses `identify` responses.
|
||||
*
|
||||
* @param {String} stdout
|
||||
* @param {Gm} self
|
||||
* @return {Error} [optionally]
|
||||
*/
|
||||
|
||||
function parse (stdout, self) {
|
||||
// normalize
|
||||
var parts = (stdout||"").trim().replace(/\r\n|\r/g, "\n").split("\n");
|
||||
|
||||
// skip the first line (its just the filename)
|
||||
parts.shift();
|
||||
|
||||
try {
|
||||
var len = parts.length
|
||||
, rgx1 = /^( *)(.+?): (.*)$/ // key: val
|
||||
, rgx2 = /^( *)(.+?):$/ // key: begin nested object
|
||||
, out = { indent: {} }
|
||||
, level = null
|
||||
, lastkey
|
||||
, i = 0
|
||||
, res
|
||||
, o
|
||||
|
||||
for (; i < len; ++i) {
|
||||
res = rgx1.exec(parts[i]) || rgx2.exec(parts[i]);
|
||||
if (!res) continue;
|
||||
|
||||
var indent = res[1].length
|
||||
, key = res[2] ? res[2].trim() : '';
|
||||
|
||||
if ('Image' == key || 'Warning' == key) continue;
|
||||
|
||||
var val = res[3] ? res[3].trim() : null;
|
||||
|
||||
// first iteration?
|
||||
if (null === level) {
|
||||
level = indent;
|
||||
o = out.root = out.indent[level] = self.data;
|
||||
} else if (indent < level) {
|
||||
// outdent
|
||||
if (!(indent in out.indent)) {
|
||||
continue;
|
||||
}
|
||||
o = out.indent[indent];
|
||||
} else if (indent > level) {
|
||||
// dropping into a nested object
|
||||
out.indent[level] = o;
|
||||
// weird format, key/val pair with nested children. discard the val
|
||||
o = o[lastkey] = {};
|
||||
}
|
||||
|
||||
level = indent;
|
||||
|
||||
if (val) {
|
||||
// if previous key was exist and we got the same key
|
||||
// cast it to an array.
|
||||
if(o.hasOwnProperty(key)){
|
||||
// cast it to an array and dont forget the previous value
|
||||
if(!Array.isArray(o[key])){
|
||||
var tmp = o[key];
|
||||
o[key] = [tmp];
|
||||
}
|
||||
|
||||
// set value
|
||||
o[key].push(val);
|
||||
} else {
|
||||
o[key] = val;
|
||||
}
|
||||
|
||||
if (key in helper) {
|
||||
helper[key](o, val);
|
||||
}
|
||||
}
|
||||
|
||||
lastkey = key;
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
err.message = err.message + "\n\n Identify stdout:\n " + stdout;
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an argument array for the identify command.
|
||||
*
|
||||
* @param {gm} self
|
||||
* @param {Object} val
|
||||
* @return {Array}
|
||||
*/
|
||||
|
||||
function makeArgs (self, val) {
|
||||
var args = [
|
||||
'identify'
|
||||
, '-ping'
|
||||
];
|
||||
|
||||
if (val.format) {
|
||||
args.push('-format', val.format);
|
||||
}
|
||||
|
||||
if (val.verbose) {
|
||||
args.push('-verbose');
|
||||
}
|
||||
|
||||
args = args.concat(self.src());
|
||||
return args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Map exif orientation codes to orientation names.
|
||||
*/
|
||||
|
||||
var orientations = {
|
||||
'1': 'TopLeft'
|
||||
, '2': 'TopRight'
|
||||
, '3': 'BottomRight'
|
||||
, '4': 'BottomLeft'
|
||||
, '5': 'LeftTop'
|
||||
, '6': 'RightTop'
|
||||
, '7': 'RightBottom'
|
||||
, '8': 'LeftBottom'
|
||||
}
|
||||
|
||||
/**
|
||||
* identify -verbose helpers
|
||||
*/
|
||||
|
||||
var helper = gm.identifyHelpers = {};
|
||||
|
||||
helper.Geometry = function Geometry (o, val) {
|
||||
// We only want the size of the first frame.
|
||||
// Each frame is separated by a space.
|
||||
var split = val.split(" ").shift().split("x");
|
||||
var width = parseInt(split[0], 10);
|
||||
var height = parseInt(split[1], 10);
|
||||
if (o.size && o.size.width && o.size.height) {
|
||||
if (width > o.size.width) o.size.width = width;
|
||||
if (height > o.size.height) o.size.height = height;
|
||||
} else {
|
||||
o.size = {
|
||||
width: width,
|
||||
height: height
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
helper.Format = function Format (o, val) {
|
||||
o.format = val.split(" ")[0];
|
||||
};
|
||||
|
||||
helper.Depth = function Depth (o, val) {
|
||||
o.depth = parseInt(val, 10);
|
||||
};
|
||||
|
||||
helper.Colors = function Colors (o, val) {
|
||||
o.color = parseInt(val, 10);
|
||||
};
|
||||
|
||||
helper.Orientation = function Orientation (o, val) {
|
||||
if (val in orientations) {
|
||||
o['Profile-EXIF'] || (o['Profile-EXIF'] = {});
|
||||
o['Profile-EXIF'].Orientation = val;
|
||||
o.Orientation = orientations[val];
|
||||
} else {
|
||||
o.Orientation = val || 'Unknown';
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
// montage
|
||||
|
||||
/**
|
||||
* Montage images next to each other using the `montage` command in graphicsmagick.
|
||||
*
|
||||
* gm('/path/to/image.jpg')
|
||||
* .montage('/path/to/second_image.jpg')
|
||||
* .geometry('+100+150')
|
||||
* .write('/path/to/montage.png', function(err) {
|
||||
* if(!err) console.log("Written montage image.");
|
||||
* });
|
||||
*
|
||||
* @param {String} other Path to the image that contains the changes.
|
||||
*/
|
||||
|
||||
module.exports = exports = function(proto) {
|
||||
proto.montage = function(other) {
|
||||
this.in(other);
|
||||
|
||||
this.subCommand("montage");
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
|
||||
module.exports = exports = function (proto) {
|
||||
proto._options = {};
|
||||
|
||||
proto.options = function setOptions (options) {
|
||||
var keys = Object.keys(options)
|
||||
, i = keys.length
|
||||
, key
|
||||
|
||||
while (i--) {
|
||||
key = keys[i];
|
||||
this._options[key] = options[key];
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
|
||||
/**
|
||||
* Escape the given shell `arg`.
|
||||
*
|
||||
* @param {String} arg
|
||||
* @return {String}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.escape = function escape (arg) {
|
||||
return '"' + String(arg).trim().replace(/"/g, '\\"') + '"';
|
||||
};
|
||||
|
||||
exports.unescape = function escape (arg) {
|
||||
return String(arg).trim().replace(/"/g, "");
|
||||
};
|
||||
|
||||
exports.argsToArray = function (args) {
|
||||
var arr = [];
|
||||
|
||||
for (var i = 0; i <= arguments.length; i++) {
|
||||
if ('undefined' != typeof arguments[i])
|
||||
arr.push(arguments[i]);
|
||||
}
|
||||
|
||||
return arr;
|
||||
};
|
||||
|
||||
exports.isUtil = function (v) {
|
||||
var ty = 'object';
|
||||
switch (Object.prototype.toString.call(v)) {
|
||||
case '[object String]':
|
||||
ty = 'String';
|
||||
break;
|
||||
case '[object Array]':
|
||||
ty = 'Array';
|
||||
break;
|
||||
case '[object Boolean]':
|
||||
ty = 'Boolean';
|
||||
break;
|
||||
}
|
||||
return ty;
|
||||
}
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
{
|
||||
"_args": [
|
||||
[
|
||||
{
|
||||
"raw": "gm",
|
||||
"scope": null,
|
||||
"escapedName": "gm",
|
||||
"name": "gm",
|
||||
"rawSpec": "",
|
||||
"spec": "latest",
|
||||
"type": "tag"
|
||||
},
|
||||
"/Users/gerrit/Documents/node.js-Projects/tinkerforge"
|
||||
]
|
||||
],
|
||||
"_from": "gm@latest",
|
||||
"_id": "gm@1.23.1",
|
||||
"_inCache": true,
|
||||
"_location": "/gm",
|
||||
"_nodeVersion": "6.12.2",
|
||||
"_npmOperationalInternal": {
|
||||
"host": "s3://npm-registry-packages",
|
||||
"tmp": "tmp/gm-1.23.1.tgz_1514436565904_0.7165352762676775"
|
||||
},
|
||||
"_npmUser": {
|
||||
"name": "aaron",
|
||||
"email": "aaron.heckmann+github@gmail.com"
|
||||
},
|
||||
"_npmVersion": "3.10.10",
|
||||
"_phantomChildren": {},
|
||||
"_requested": {
|
||||
"raw": "gm",
|
||||
"scope": null,
|
||||
"escapedName": "gm",
|
||||
"name": "gm",
|
||||
"rawSpec": "",
|
||||
"spec": "latest",
|
||||
"type": "tag"
|
||||
},
|
||||
"_requiredBy": [
|
||||
"#USER",
|
||||
"/"
|
||||
],
|
||||
"_resolved": "https://registry.npmjs.org/gm/-/gm-1.23.1.tgz",
|
||||
"_shasum": "2edeeb958084d0f8ea7988e5d995b1c7dfc14777",
|
||||
"_shrinkwrap": null,
|
||||
"_spec": "gm",
|
||||
"_where": "/Users/gerrit/Documents/node.js-Projects/tinkerforge",
|
||||
"author": {
|
||||
"name": "Aaron Heckmann",
|
||||
"email": "aaron.heckmann+github@gmail.com"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "http://github.com/aheckmann/gm/issues"
|
||||
},
|
||||
"dependencies": {
|
||||
"array-parallel": "~0.1.3",
|
||||
"array-series": "~0.1.5",
|
||||
"cross-spawn": "^4.0.0",
|
||||
"debug": "^3.1.0"
|
||||
},
|
||||
"description": "GraphicsMagick and ImageMagick for node.js",
|
||||
"devDependencies": {
|
||||
"async": "~0.9.0",
|
||||
"nsp": "^3"
|
||||
},
|
||||
"directories": {},
|
||||
"dist": {
|
||||
"shasum": "2edeeb958084d0f8ea7988e5d995b1c7dfc14777",
|
||||
"tarball": "https://registry.npmjs.org/gm/-/gm-1.23.1.tgz"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.10.0"
|
||||
},
|
||||
"gitHead": "0286f47b1d5c1479c136616a73bced0ee0d2835c",
|
||||
"homepage": "https://github.com/aheckmann/gm#readme",
|
||||
"keywords": [
|
||||
"graphics",
|
||||
"magick",
|
||||
"image",
|
||||
"graphicsmagick",
|
||||
"imagemagick",
|
||||
"gm",
|
||||
"convert",
|
||||
"identify",
|
||||
"compare"
|
||||
],
|
||||
"license": "MIT",
|
||||
"licenses": [
|
||||
{
|
||||
"type": "MIT",
|
||||
"url": "http://www.opensource.org/licenses/mit-license.php"
|
||||
}
|
||||
],
|
||||
"main": "./index",
|
||||
"maintainers": [
|
||||
{
|
||||
"name": "jongleberry",
|
||||
"email": "jonathanrichardong@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "rwky",
|
||||
"email": "admin@rwky.net"
|
||||
},
|
||||
{
|
||||
"name": "fragphace",
|
||||
"email": "fragphace@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "aaron",
|
||||
"email": "aaron.heckmann+github@gmail.com"
|
||||
}
|
||||
],
|
||||
"name": "gm",
|
||||
"optionalDependencies": {},
|
||||
"readme": "ERROR: No README data found!",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/aheckmann/gm.git"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "make test;"
|
||||
},
|
||||
"version": "1.23.1"
|
||||
}
|
||||
Reference in New Issue
Block a user