diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..da0310f --- /dev/null +++ b/.editorconfig @@ -0,0 +1,13 @@ +# editorconfig.org +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false \ No newline at end of file diff --git a/.jshintrc b/.jshintrc index 6b4c1a9..9a6404a 100644 --- a/.jshintrc +++ b/.jshintrc @@ -10,5 +10,5 @@ "boss": true, "eqnull": true, "node": true, - "es5": true + "esnext": true } diff --git a/.travis.yml b/.travis.yml index f55d551..bbf368c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,18 @@ +--- language: node_js + node_js: - - 0.8 - - '0.10' -before_script: - - npm install -g grunt-cli \ No newline at end of file + - "node" + +before_install: + - npm install -g gulp + - sudo apt-get install graphicsmagick + +install: + - npm install + +os: + #- osx + - linux + +script: npm test diff --git a/Gruntfile.js b/Gruntfile.js deleted file mode 100644 index edb0355..0000000 --- a/Gruntfile.js +++ /dev/null @@ -1,149 +0,0 @@ -/* - * grunt-image-resize - * https://github.com/excellenteasy.com/grunt-image-resize - * - * Copyright (c) 2013 David Pfahler - * Licensed under the MIT license. - */ - -'use strict'; - -module.exports = function(grunt) { - - // Project configuration. - grunt.initConfig({ - jshint: { - all: [ - 'Gruntfile.js', - 'tasks/*.js', - '<%= nodeunit.tests %>' - ], - options: { - jshintrc: '.jshintrc' - }, - }, - - // Before generating any new files, remove any previously-created files. - clean: { - tests: ['tmp/*'] - }, - - // Configuration to be run (and then tested). - image_resize: { - options: { - width: 100 - }, - resize: { - files: [ - {'tmp/gnu.jpg': 'test/fixtures/gnu.jpg'}, - {'tmp/wikipedia.png': 'test/fixtures/wikipedia.png'}, - {'tmp/Rhododendron.jpg': 'test/fixtures/Rhododendron.jpg'}, - {'tmp/TeslaTurbine.png': 'test/fixtures/TeslaTurbine.png'} - ] - }, - no_overwrite: { - options: { - width: 0, - height: 50, - overwrite: false - }, - files: [ - {'tmp/gnu.jpg': 'test/fixtures/gnu.jpg'}, - {'tmp/wikipedia.png': 'test/fixtures/wikipedia.png'}, - {'tmp/Rhododendron.jpg': 'test/fixtures/Rhododendron.jpg'}, - {'tmp/TeslaTurbine.png': 'test/fixtures/TeslaTurbine.png'} - ] - }, - upscale: { - options: { - width: 600, - height: 0, - upscale: true - }, - files: [ - {'tmp/upscale.png': 'test/fixtures/upscale.png'} - ] - }, - upscale2: { - options: { - width: 600, - height: 600, - upscale: true - }, - files: [ - {'tmp/upscale2.png': 'test/fixtures/upscale2.png'} - ] - }, - no_upscale: { - options: { - width: 600, - height: 0, - upscale: false - }, - files: [ - {'tmp/no_upscale.png': 'test/fixtures/no_upscale.png'} - ] - }, - crop: { - options: { - width: 400, - height: 300, - upscale: false, - crop: true - }, - files: [ - {'tmp/crop.png': 'test/fixtures/crop.png'} - ] - }, - cropGravity: { - options: { - width: 400, - height: 300, - upscale: false, - crop: true, - gravity: "NorthWest" - }, - files: [ - {'tmp/crop_gravity.png': 'test/fixtures/crop.png'} - ] - }, - quality: { - options: { - width: 600, - height: 0, - upscale: false, - quality: 0.2 - }, - files: [ - {'tmp/quality.jpg': 'test/fixtures/quality.jpg'} - ] - } - }, - - // Unit tests. - nodeunit: { - tests: ['test/*_test.js'] - }, - - }); - - // Actually load this plugin's task(s). - grunt.loadTasks('tasks'); - - // These plugins provide necessary tasks. - grunt.loadNpmTasks('grunt-contrib-jshint'); - grunt.loadNpmTasks('grunt-contrib-clean'); - grunt.loadNpmTasks('grunt-contrib-nodeunit'); - - // Whenever the "test" task is run, first clean the "tmp" dir, then run this - // plugin's task(s), then test the result. - grunt.registerTask('test', [ - 'clean', - 'image_resize', - 'nodeunit' - ]); - - // By default, lint and run all tests. - grunt.registerTask('default', ['jshint', 'test']); - -}; diff --git a/LICENSE-MIT b/LICENSE-MIT deleted file mode 100644 index f0e40a6..0000000 --- a/LICENSE-MIT +++ /dev/null @@ -1,22 +0,0 @@ -Copyright (c) 2013 David Pfahler - -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. diff --git a/README.md b/README.md index 100c76a..fa1e2bb 100644 --- a/README.md +++ b/README.md @@ -1,172 +1,269 @@ -# grunt-image-resize +# [gulp](https://github.com/wearefractal/gulp)-image-resize [![Build Status](https://api.travis-ci.org/scalableminds/gulp-image-resize.svg?branch=master)](https://travis-ci.org/scalableminds/gulp-image-resize) -> Resizing images made easy - thanks to ImageMagick. -## Getting Started -This plugin requires Grunt `~0.4.1` and ImageMagick. +> Resizing images made easy - thanks to [GraphicsMagick](http://www.graphicsmagick.org/) or [ImageMagick](http://www.imagemagick.org/). +> Fork of [grunt-image-resize](https://github.com/excellenteasy/grunt-image-resize). -### Grunt -If you haven't used [Grunt](http://gruntjs.com/) before, be sure to check out the [Getting Started](http://gruntjs.com/getting-started) guide, as it explains how to create a [Gruntfile](http://gruntjs.com/sample-gruntfile) as well as install and use Grunt plugins. Once you're familiar with that process, you may install this plugin with this command: +## Install -```shell -npm install grunt-image-resize --save-dev -``` +Install with [npm](https://npmjs.org/package/gulp-image-resize) -One the plugin has been installed, it may be enabled inside your Gruntfile with this line of JavaScript: - -```js -grunt.loadNpmTasks('grunt-image-resize'); +``` +npm install --save-dev gulp-image-resize ``` -### ImageMagick -Make sure ImageMagick is installed on your system and properly set up in your `PATH`. +### GraphicsMagick or ImageMagick +Make sure GraphicsMagick or ImageMagick is installed on your system and properly set up in your `PATH`. Ubuntu: ```shell -$ apt-get install imagemagick +apt-get install imagemagick +apt-get install graphicsmagick ``` Mac OS X (using [Homebrew](http://brew.sh/)): ```shell -$ brew install imagemagick +brew install imagemagick +brew install graphicsmagick ``` -Windows & others: +Windows & others: [http://www.imagemagick.org/script/binary-releases.php](http://www.imagemagick.org/script/binary-releases.php) Confirm that ImageMagick is properly set up by executing `convert -help` in a terminal. -## The "image_resize" task -### Overview -In your project's Gruntfile, add a section named `image_resize` to the data object passed into `grunt.initConfig()`. +## Example ```js -grunt.initConfig({ - image_resize: { - options: { - width: 100, - height: 100, - overwrite: true - }, - your_target: { - // Target-specific file lists and/or options go here. - }, - }, -}) +var gulp = require('gulp'); +var imageResize = require('gulp-image-resize'); + +gulp.task('default', function () { + gulp.src('test.png') + .pipe(imageResize({ + width : 100, + height : 100, + crop : true, + upscale : false + })) + .pipe(gulp.dest('dist')); +}); ``` -### Options +## API + +### imageResize(options) #### options.width -Type: `Number` + +Type: `Number` Default value: `0` (only if height is defined) -A number value that is passed as pixel value to imagemagick. +A number value that is passed as pixel or percentage value to imagemagick. + #### options.height -Type: `Number` -Default value: `0` (only if width is defined) -A number value that is passed as pixel value to imagemagick. +Type: `Number` +Default value: `0` (only if width is defined) -#### options.overwrite -Type: `Boolean` -Default value: `true` +A number value that is passed as pixel or percentage value to imagemagick. -Determines whether file that already exist under this destination will be overwritten. #### options.upscale -Type: `Boolean` + +Type: `Boolean` Default value: `false` Determines whether images will be upscaled. If set to `false` (default), image will be copied instead of resized if it would be upscaled by resizing. + #### options.crop -Type: `Boolean` + +Type: `Boolean` Default value: `false` Determines whether images will be cropped after resizing to exactly match `options.width` and `options.height`. + #### options.gravity -Type: `String` -Default value: `Center` + +Type: `String` +Default value: `Center` Possible values: `NorthWest`, `North`, `NorthEast`, `West`, `Center`, `East`, `SouthWest`, `South`, `SouthEast` When cropping images this sets the image gravity. Doesn't have any effect, if `options.crop` is `false`. -#### options.concurrency -Type: `Number` -Default value: Number of CPUs - -Determines how many resize operations are executed in parallel. #### options.quality -Type: `Number` + +Type: `Number` Default value: `1` Determines the output quality of the resized image. Ranges from `0` (really bad) to `1` (almost lossless). Only applies to jpg images. -### Usage Examples -#### Default Options -In this example, the default options are used to resize an image to 100px width. So if the `test/fixtures/wikipedia.png` file has a width of 500px, the generated result would be a 100px wide `tmp/wikipedia.png`. +#### options.format + +Type: `String` +Default value: Format of the input file +Possible values: `gif`, `png`, `jpeg` etc. + +Override the output format of the processed file. + +#### options.filter + +Type: `String` +Possible values: `Point`, `Box`, `Triangle`, `Hermite`, `Hanning`, `Hamming`, `Blackman`, `Gaussian`, `Quadratic`, `Cubic`, `Catrom`, `Mitchell`, `Lanczos`, `Bessel`, `Sinc` + +Set the filter to use when resizing (e.g. Catrom is very good for reduction, while hermite is good for enlargement). + +#### options.sharpen + +Type: `Boolean | String` +Default value: `false` + +Set to `true` to apply a slight unsharp mask after resizing. +Or set a string to setup the unsharp. See [gm unsharp documentation](http://www.graphicsmagick.org/GraphicsMagick.html#details-unsharp). (e.g. '0.5x0.5+0.5+0.008') + +#### options.samplingFactor + +Type: `Array[Cr, Cb]` +Possible values: `[2, 2]` for 4:2:2, `[1, 1]` for 4:1:1 + +Define chroma subsampling + +#### options.noProfile + +Type: `Boolean` +Default value: `false` + +Set to `true` to enforce removal of all embedded profile data like icc, exif, iptc, xmp +and so on. If source files represent e.g. untouched camera data or images optimized for +print this may decrease image size drastically. Therefore this is probably wanted in +cases where thumbnails are generated for web preview purposes. For details look for parameter _+profile "*"_ in the [gm profile documentation](http://www.graphicsmagick.org/GraphicsMagick.html#details-profile). + +#### options.interlace + +Type: `Boolean` +Default value: `false` + +Set to `true` to create interlaced images (scanline interlacing) from PNG, GIF or JPEG files +(also known as "progressive" JPEG). For details look for parameter _-interlace <type>_ with the type value set to +"Line" in the [gm profile documentation](http://www.graphicsmagick.org/GraphicsMagick.html#details-interlace). + +#### options.imageMagick + +Type: `Boolean` +Default value: `false` + +Set to `true` when using ImageMagick instead of GraphicsMagick. + +#### options.background + +Type: `String` +Possible values: `none` to keep transparency, `beige` to set beige background, `#888` for gray. + +Define background color (default is white), for example when converting SVG images to PNGs. +See [gm background documentation](http://www.graphicsmagick.org/GraphicsMagick.html#details-background) + +#### options.flatten + +Type: `Boolean` +Default value: `false` + +Combines image layers into one. Can be used for layered formats such as PNG. See [gm flatten documentation](http://www.graphicsmagick.org/GraphicsMagick.html#details-flatten). + +#### options.percentage + +Type: `Number` +Default value: `null` + +The value that you want the image to be scaled to. + + +## More Examples ```js -grunt.initConfig({ - image_resize: { - resize: { - options: { - width: 100 - }, - files: { - 'tmp/wikipedia.png': 'test/fixtures/wikipedia.png' - } - } - } -}) +// Converting from png to jpeg. No resizing. +gulp.task('convert_png', function () { + return gulp.src('test.png') + .pipe(imageResize({ format : 'jpeg' })) + .pipe(gulp.dest('dist')); +}); + +// Only specify one dimension. Output image won't exceed this value. +gulp.task('width', function () { + gulp.src('test.png') + .pipe(imageResize({ + width : 100 + })) + .pipe(gulp.dest('dist')); +}); + +// Convert with percentage value. +gulp.task('percentage', function() { + gulp.src('test.png') + .pipe(imageResize({ + percentage: 50 + })) + .pipe(gulp.dest('dist')); +}); ``` -#### Prevent overwriting -In this example, we prevent the destination file from being overwritten if it already exists. It the file `tmp/wikipedia.png` already exists, for example because we just ran the task configuration above, this would **not** overwrite `tmp/wikipedia.png`. The file `tmp/wikipedia.png` would still be 100px wide. +## Recommended modules +* [concurrent-transform](https://github.com/segmentio/concurrent-transform): parallelize image resizing ```js -grunt.initConfig({ - image_resize: { - no_overwrite: { - options: { - width: 50, - overwrite: false - }, - files: { - 'tmp/wikipedia.png': 'test/fixtures/wikipedia.png' - } - } - } -}) +var parallel = require("concurrent-transform"); +var os = require("os"); + +gulp.task("parallel", function () { + gulp.src("src/**/*.{jpg,png}") + .pipe(parallel( + imageResize({ width : 100 }), + os.cpus().length + )) + .pipe(gulp.dest("dist")); +}); ``` -#### Allow upscaling -By default, the task does not resize images which would be upscaled. It only allows downscaling. You can allow upscaling by setting the `upscale` option to `true`. Otherwise images will copied instead of resized when they would be upscaled by resizing. +* [gulp-changed](https://www.npmjs.org/package/gulp-changed/): only resize changed images +```js +var changed = require("gulp-changed"); + +gulp.task("changed", function () { + gulp.src("src/**/*.{jpg,png}") + .pipe(changed("dist")) + .pipe(imageResize({ width : 100 })) + .pipe(gulp.dest("dist")); +}); +``` +* [gulp-rename](https://www.npmjs.org/package/gulp-rename/): add a suffix or prefix ```js -grunt.initConfig({ - image_resize: { - no_overwrite: { - options: { - width: 600, - upscale: true - }, - files: { - 'tmp/wikipedia.png': 'test/fixtures/wikipedia.png' - } - } - } -}) +var rename = require("gulp-rename"); + +gulp.task("suffix", function () { + gulp.src("src/**/*.{jpg,png}") + .pipe(imageResize({ width : 100 })) + .pipe(rename(function (path) { path.basename += "-thumbnail"; })) + .pipe(gulp.dest("dist")); +}); ``` -## Contributing -In lieu of a formal styleguide, take care to maintain the existing coding style. Add unit tests for any new or changed functionality. Lint and test your code using [Grunt](http://gruntjs.com/). +## Tests + +1. You need both ImageMagick and GraphicsMagick installed on your system to run the tests. +2. Install all npm dev dependencies `npm install` +3. Install gulp globally `npm install -g gulp` +4. Run `gulp test` + + +## License + +MIT © [scalable minds](http://scm.io) diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 0000000..ae53f6e --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,172 @@ +var gulp = require("gulp"); +var mocha = require("gulp-mocha"); +var clean = require("gulp-clean"); +var jshint = require("gulp-jshint"); +var stylish = require("jshint-stylish"); +var sequence = require("run-sequence"); + +var imageResize = require("./index.js"); + + + +gulp.task("jshint", function () { + gulp.src(["gulpfile.js", "index.js", "test/*.js"]) + .pipe(jshint()) + .pipe(jshint.reporter(stylish)); +}); + +gulp.task("clean", function () { + gulp.src("tmp/*", { read : false }) + .pipe(clean()); +}); + +gulp.task("mocha", ["image_resize"], function () { + return gulp.src("test/*_test.js") + .pipe(mocha({ reporter: "spec" })); +}); + + + +var resizeTasks = []; + +var resize = function(files, key, options) { + + function makeTask(files, key, options) { + gulp.task("image_resize:" + key, function () { + return gulp.src(files) + .pipe(imageResize(options)) + .pipe(gulp.dest("tmp/" + key)); + }); + resizeTasks.push("image_resize:" + key); + } + + makeTask(files, key, options); + + var imOptions = { imageMagick : true }; + for (var k in options) { imOptions[k] = options[k]; } + makeTask(files, key + "_imagemagick", imOptions); +}; + + +resize([ + "test/fixtures/gnu.jpg", + "test/fixtures/wikipedia.png", + "test/fixtures/Rhododendron.jpg", + "test/fixtures/TeslaTurbine.png" +], "resize", { + width: 100 +}); + + +resize("test/fixtures/wikipedia.png", "upscale", { + width: 600, + height: 0, + upscale: true +}); + +resize("test/fixtures/wikipedia.png", "upscale2", { + width: 600, + height: 600, + upscale: true +}); + +resize("test/fixtures/wikipedia.png", "no_upscale", { + width: 600, + upscale: false +}); + +resize("test/fixtures/wikipedia.png", "no_upscale2", { + width: 100, + height: 600, + upscale: false +}); + +resize("test/fixtures/wikipedia.png", "crop", { + width: 400, + height: 300, + upscale: false, + crop: true +}); + +resize("test/fixtures/wikipedia.png", "crop_gravity", { + width: 400, + height: 300, + upscale: false, + crop: true, + gravity: "NorthWest" +}); + +resize("test/fixtures/wikipedia.png", "crop_width_only", { + width: 300, + crop: true +}); + +resize("test/fixtures/Rhododendron.jpg", "quality", { + width: 600, + height: 0, + upscale: false, + quality: 0.2 +}); + +resize("test/fixtures/Rhododendron.jpg", "sharpen", { + width: 600, + height: 0, + sharpen: true +}); + +resize("test/fixtures/Rhododendron.jpg", "filter", { + width: 600, + height: 0, + filter: "catrom" +}); + +resize("test/fixtures/Rhododendron.jpg", "samplingFactor", { + width: 600, + height: 0, + samplingFactor: [2,2] +}); + +resize("test/fixtures/wikipedia.png", "convert", { + format: "jpg" +}); + +resize("test/fixtures/hamburg.jpg", "noProfile", { + noProfile: true +}); + +resize("test/fixtures/wikipedia.png", "flatten", { + format: "jpg", + flatten: true +}); + +resize([ + "test/fixtures/Rhododendron.jpg", + "test/fixtures/wikipedia.png" +], "interlace", { + interlace: true +}); + +resize([ + "test/fixtures/Rhododendron.jpg", + "test/fixtures/wikipedia.png" +], "interlace_and_resize", { + width: 400, + interlace: true +}); + +resize([ + "test/fixtures/gnu.jpg", + "test/fixtures/wikipedia.png", + "test/fixtures/Rhododendron.jpg", + "test/fixtures/TeslaTurbine.png" +], "percentage", { + percentage: 50 +}); + +gulp.task("image_resize", resizeTasks); + +gulp.task("test", function(callback) { + sequence("clean", "jshint", "mocha", callback); +}); + +gulp.task("default", ["test"]); diff --git a/index.js b/index.js new file mode 100644 index 0000000..0d2f41e --- /dev/null +++ b/index.js @@ -0,0 +1,132 @@ +/* + * grunt-image-resize + * https://github.com/scalableminds/gulp-image-resize + * + * Copyright (c) 2014 Norman Rzepka + * Licensed under the MIT license. + */ + +var gm = require("gulp-gm"); +var async = require("async"); +var _ = require("lodash"); + +module.exports = function imageResizer(_options) { + + _options = _.defaults(_options, { + overwrite : true, + upscale : false, + crop : false, + gravity : "Center", + quality : 1, + noProfile : false, + sharpen : false, + imageMagick : false, + format : null, + flatten : false, + interlace : false, + percentage : null + }); + + return gm(function(gmfile, done) { + + async.waterfall([ + + function (callback) { + gmfile.size(callback); + }, + + function (size, callback) { + + var options = JSON.parse(JSON.stringify(_options)); // fix: we must make a copy, because we will change it! + + if (options.filter != null) { + gmfile = gmfile.filter(options.filter); + } + + if (options.height != null || options.width != null) { + + // if upscale is not requested, restrict size + if(!options.upscale){ + if (!isNaN(options.width)) { + options.width = Math.min(options.width, size.width); + } + if (!isNaN(options.height)) { + options.height = Math.min(options.height, size.height); + } + } + + // if one dimension is not set - we fill it proportionally + if (!options.height) { + if (options.crop) { + options.height = size.height; + } else { + options.height = Math.ceil((options.width / size.width) * size.height); + } + } + if (!options.width) { + if (options.crop) { + options.width = size.width; + } else { + options.width = Math.ceil((options.height / size.height) * size.width); + } + } + + if (options.crop) { + gmfile = gmfile + .resize(options.width, options.height, "^") + .gravity(options.gravity) + .crop(options.width, options.height); + } else { + gmfile = gmfile + .resize(options.width, options.height); + } + + } else if (options.percentage) { + gmfile = gmfile + .resize(options.percentage, null, '%'); + } + + if (options.format) { + gmfile = gmfile + .setFormat(options.format); + } + + if (options.quality !== 1) { + gmfile = gmfile.quality(Math.floor(options.quality * 100)); + } + + + if (options.samplingFactor != null) { + gmfile = gmfile + .samplingFactor(options.samplingFactor[0], options.samplingFactor[1]); + } + + if (options.sharpen) { + options.sharpen = (typeof options.sharpen === 'string') ? options.sharpen : '1.5x1+0.7+0.02'; + gmfile = gmfile.unsharp(options.sharpen); + } + + if (options.flatten) { + gmfile = gmfile.flatten(); + } + + if (options.interlace) { + gmfile = gmfile.interlace('Line'); + } + + if (options.background) { + gmfile = gmfile.background(options.background); + } + + if (options.noProfile) { + gmfile = gmfile.noProfile(); + } + + callback(null, gmfile); + } + + ], done); + + }, { imageMagick : _options.imageMagick }); + +}; diff --git a/package.json b/package.json index 1ea67d8..e46aba5 100644 --- a/package.json +++ b/package.json @@ -1,47 +1,44 @@ { - "name": "grunt-image-resize", + "name": "gulp-image-resize", "description": "Resizing images made easy.", - "version": "0.3.0", - "homepage": "https://github.com/excellenteasy/grunt-image-resize", + "version": "0.12.0", + "homepage": "https://github.com/scalableminds/gulp-image-resize", "author": { - "name": "David Pfahler", - "email": "david@excellenteasy.com", - "url": "http://excellenteasy.com" + "name": "Norman Rzepka", + "email": "norman@scm.io", + "url": "http://scm.io" }, "repository": { "type": "git", - "url": "git://github.com/excellenteasy/grunt-image-resize" + "url": "git://github.com/scalableminds/gulp-image-resize" }, "bugs": { - "url": "https://github.com/excellenteasy/grunt-image-resize/issues" + "url": "https://github.com/scalableminds/gulp-image-resize/issues" }, - "licenses": [ - { - "type": "MIT", - "url": "https://github.com/excellenteasy/grunt-image-resize/blob/master/LICENSE-MIT" - } - ], - "main": "Gruntfile.js", + "license": "MIT", + "main": "index.js", "engines": { - "node": ">= 0.8.0" + "node": ">= 0.10.0" }, "scripts": { - "test": "grunt test" + "test": "gulp test" }, "devDependencies": { - "grunt-contrib-jshint": "~0.1.1", - "grunt-contrib-clean": "~0.4.0", - "grunt-contrib-nodeunit": "~0.1.2", - "grunt": "~0.4.1" - }, - "peerDependencies": { - "grunt": "~0.4.1" + "gulp": "~3.5.5", + "gm": "~1.14.2", + "gulp-clean": "~0.2.4", + "gulp-jshint": "~1.5.0", + "run-sequence": "~0.3.6", + "jshint-stylish": "~0.1.5", + "gulp-mocha": "~0.4.1" }, "keywords": [ - "gruntplugin" + "gulpplugin" ], "dependencies": { "async": "~0.2.8", - "gm": "~1.14.2" + "gulp-gm": "~0.0.3", + "through2": "~0.4.1", + "lodash": "~2.4.1" } } diff --git a/tasks/image_resize.js b/tasks/image_resize.js deleted file mode 100644 index eff4dc6..0000000 --- a/tasks/image_resize.js +++ /dev/null @@ -1,120 +0,0 @@ -/* - * grunt-image-resize - * https://github.com/excellenteasy.com/grunt-image-resize - * - * Copyright (c) 2013 David Pfahler - * Licensed under the MIT license. - */ - -'use strict'; - -var gm = require('gm').subClass({ imageMagick: true }); -var async = require('async'); -var path = require('path'); -var os = require('os'); - -module.exports = function(grunt) { - - // Please see the Grunt documentation for more information regarding task - // creation: http://gruntjs.com/creating-tasks - - grunt.registerMultiTask('image_resize', 'resize images made easy', function() { - - var done = this.async(); - var originalOptions = this.options(); - var options = this.options({ - overwrite: true, - upscale: false, - concurrency: os.cpus().length, - crop: false, - gravity: 'Center', - quality: 1 - }); - var series = []; - - if (options.height == null && options.width == null) { - return grunt.fail.warn("Neither height nor width defined."); - } - if (options.height == null && options.width) { - options.height = 0; - } - if (options.width == null && options.height) { - options.width = 0; - } - - // Iterate over all specified file groups. - this.files.forEach(function(f) { - var dirname = path.dirname(f.dest), - filepath = f.src[0], imOptions = { - srcPath: filepath, - dstPath: f.dest, - width: options.width, - height: options.height, - quality: options.quality - }; - - // Prevent failing if destination directory does not exist. - if (!grunt.file.isDir(dirname)) { - grunt.file.mkdir(dirname); - } - // Fail for more than one source file per file group. - if (f.src.length !== 1) { - return grunt.fail.fatal("Can not optimize more than one image per destination.\n"+ - "You need to use a different 'files' format in your Gruntfile."); - } - if (options.overwrite === false && grunt.file.isFile(f.dest)) { - return grunt.log.writeln("Skipping "+filepath+" because destination already exists.\n"+ - "Set options 'overwrite' to true to enable overwriting of files."); - } - - series.push(function(callback) { - // Fail when image would be upscaled unless explicitly allowed - gm(filepath).size(function(err, size) { - if (err) { - grunt.fatal( - "Failed to query image dimensions of '"+filepath+"'.\n"+ - " "+err - ); - callback(err); - } else { - if (!options.upscale && - ((originalOptions.width && size.width < originalOptions.width) || - (originalOptions.height && size.height < originalOptions.height))) { - grunt.log.writeln("Copying "+filepath+" instead of resizing, because image would be upscaled.\n"+ - "To allow upscaling, set option 'upscale' to true."); - grunt.file.copy(filepath, imOptions.dstPath); - grunt.log.ok("Image "+filepath+" copied to "+imOptions.dstPath); - callback(); - } - else { - var resizer; - if (options.crop) { - resizer = gm(filepath) - .resize(imOptions.width, imOptions.height, "^") - .gravity(options.gravity) - .crop(imOptions.width, imOptions.height); - } else { - resizer = gm(filepath) - .resize(imOptions.width, imOptions.height); - } - - resizer - .quality(Math.floor(imOptions.quality * 100)) - .write(imOptions.dstPath, function(err) { - if (err) { - grunt.fail.warn(err.message); - } else { - grunt.log.ok('Image '+filepath+' resized to '+f.dest); - } - return callback(); - }); - } - } - }); - }); - }); - - async.parallelLimit(series, options.concurrency, done); - }); - -}; diff --git a/test/expected/Rhododendron.jpg b/test/expected/Rhododendron.jpg deleted file mode 100644 index f857010..0000000 Binary files a/test/expected/Rhododendron.jpg and /dev/null differ diff --git a/test/expected/TeslaTurbine.png b/test/expected/TeslaTurbine.png deleted file mode 100644 index 359d5a8..0000000 Binary files a/test/expected/TeslaTurbine.png and /dev/null differ diff --git a/test/expected/convert/wikipedia.jpg b/test/expected/convert/wikipedia.jpg new file mode 100644 index 0000000..da9857e Binary files /dev/null and b/test/expected/convert/wikipedia.jpg differ diff --git a/test/expected/crop.png b/test/expected/crop.png deleted file mode 100644 index 749f589..0000000 Binary files a/test/expected/crop.png and /dev/null differ diff --git a/test/expected/crop/wikipedia.png b/test/expected/crop/wikipedia.png new file mode 100644 index 0000000..8dd6ce0 Binary files /dev/null and b/test/expected/crop/wikipedia.png differ diff --git a/test/expected/crop_gravity.png b/test/expected/crop_gravity.png deleted file mode 100644 index 054254f..0000000 Binary files a/test/expected/crop_gravity.png and /dev/null differ diff --git a/test/expected/crop_gravity/wikipedia.png b/test/expected/crop_gravity/wikipedia.png new file mode 100644 index 0000000..d967ca1 Binary files /dev/null and b/test/expected/crop_gravity/wikipedia.png differ diff --git a/test/expected/crop_width_only/wikipedia.png b/test/expected/crop_width_only/wikipedia.png new file mode 100644 index 0000000..d4a0f0e Binary files /dev/null and b/test/expected/crop_width_only/wikipedia.png differ diff --git a/test/expected/filter/Rhododendron.jpg b/test/expected/filter/Rhododendron.jpg new file mode 100644 index 0000000..6e0e554 Binary files /dev/null and b/test/expected/filter/Rhododendron.jpg differ diff --git a/test/expected/flatten/wikipedia.jpg b/test/expected/flatten/wikipedia.jpg new file mode 100644 index 0000000..09ba7be Binary files /dev/null and b/test/expected/flatten/wikipedia.jpg differ diff --git a/test/expected/gnu.jpg b/test/expected/gnu.jpg deleted file mode 100644 index 1b4911b..0000000 Binary files a/test/expected/gnu.jpg and /dev/null differ diff --git a/test/expected/interlace/Rhododendron.jpg b/test/expected/interlace/Rhododendron.jpg new file mode 100644 index 0000000..df392ae Binary files /dev/null and b/test/expected/interlace/Rhododendron.jpg differ diff --git a/test/expected/interlace/wikipedia.png b/test/expected/interlace/wikipedia.png new file mode 100644 index 0000000..4193390 Binary files /dev/null and b/test/expected/interlace/wikipedia.png differ diff --git a/test/expected/interlace_and_resize/Rhododendron.jpg b/test/expected/interlace_and_resize/Rhododendron.jpg new file mode 100644 index 0000000..0954942 Binary files /dev/null and b/test/expected/interlace_and_resize/Rhododendron.jpg differ diff --git a/test/expected/interlace_and_resize/wikipedia.png b/test/expected/interlace_and_resize/wikipedia.png new file mode 100644 index 0000000..1401bad Binary files /dev/null and b/test/expected/interlace_and_resize/wikipedia.png differ diff --git a/test/expected/no_upscale.png b/test/expected/no_upscale/wikipedia.png similarity index 100% rename from test/expected/no_upscale.png rename to test/expected/no_upscale/wikipedia.png diff --git a/test/expected/no_upscale2/wikipedia.png b/test/expected/no_upscale2/wikipedia.png new file mode 100644 index 0000000..dc3d4cc Binary files /dev/null and b/test/expected/no_upscale2/wikipedia.png differ diff --git a/test/expected/percentage/Rhododendron.jpg b/test/expected/percentage/Rhododendron.jpg new file mode 100644 index 0000000..17ae3b7 Binary files /dev/null and b/test/expected/percentage/Rhododendron.jpg differ diff --git a/test/expected/percentage/TeslaTurbine.png b/test/expected/percentage/TeslaTurbine.png new file mode 100644 index 0000000..fbc45fc Binary files /dev/null and b/test/expected/percentage/TeslaTurbine.png differ diff --git a/test/expected/percentage/gnu.jpg b/test/expected/percentage/gnu.jpg new file mode 100644 index 0000000..7d5d0c6 Binary files /dev/null and b/test/expected/percentage/gnu.jpg differ diff --git a/test/expected/percentage/hamburg.jpg b/test/expected/percentage/hamburg.jpg new file mode 100644 index 0000000..73320f4 Binary files /dev/null and b/test/expected/percentage/hamburg.jpg differ diff --git a/test/expected/percentage/wikipedia.png b/test/expected/percentage/wikipedia.png new file mode 100644 index 0000000..a9da5eb Binary files /dev/null and b/test/expected/percentage/wikipedia.png differ diff --git a/test/expected/quality.jpg b/test/expected/quality.jpg deleted file mode 100644 index d438d31..0000000 Binary files a/test/expected/quality.jpg and /dev/null differ diff --git a/test/expected/quality/Rhododendron.jpg b/test/expected/quality/Rhododendron.jpg new file mode 100644 index 0000000..e6b27c3 Binary files /dev/null and b/test/expected/quality/Rhododendron.jpg differ diff --git a/test/expected/resize/Rhododendron.jpg b/test/expected/resize/Rhododendron.jpg new file mode 100644 index 0000000..9ac94bd Binary files /dev/null and b/test/expected/resize/Rhododendron.jpg differ diff --git a/test/expected/resize/TeslaTurbine.png b/test/expected/resize/TeslaTurbine.png new file mode 100644 index 0000000..34b2d5b Binary files /dev/null and b/test/expected/resize/TeslaTurbine.png differ diff --git a/test/expected/resize/gnu.jpg b/test/expected/resize/gnu.jpg new file mode 100644 index 0000000..5db4cc9 Binary files /dev/null and b/test/expected/resize/gnu.jpg differ diff --git a/test/expected/resize/wikipedia.png b/test/expected/resize/wikipedia.png new file mode 100644 index 0000000..dc3d4cc Binary files /dev/null and b/test/expected/resize/wikipedia.png differ diff --git a/test/expected/samplingFactor/Rhododendron.jpg b/test/expected/samplingFactor/Rhododendron.jpg new file mode 100644 index 0000000..ea22640 Binary files /dev/null and b/test/expected/samplingFactor/Rhododendron.jpg differ diff --git a/test/expected/sharpen/Rhododendron.jpg b/test/expected/sharpen/Rhododendron.jpg new file mode 100644 index 0000000..92f5691 Binary files /dev/null and b/test/expected/sharpen/Rhododendron.jpg differ diff --git a/test/expected/upscale.png b/test/expected/upscale/wikipedia.png similarity index 100% rename from test/expected/upscale.png rename to test/expected/upscale/wikipedia.png diff --git a/test/expected/upscale2.png b/test/expected/upscale2/wikipedia.png similarity index 100% rename from test/expected/upscale2.png rename to test/expected/upscale2/wikipedia.png diff --git a/test/expected/wikipedia.png b/test/expected/wikipedia.png deleted file mode 100644 index c9e5d65..0000000 Binary files a/test/expected/wikipedia.png and /dev/null differ diff --git a/test/fixtures/crop.png b/test/fixtures/crop.png deleted file mode 100644 index b4c41f2..0000000 Binary files a/test/fixtures/crop.png and /dev/null differ diff --git a/test/fixtures/hamburg.jpg b/test/fixtures/hamburg.jpg new file mode 100644 index 0000000..dfc1ea9 Binary files /dev/null and b/test/fixtures/hamburg.jpg differ diff --git a/test/fixtures/no_upscale.png b/test/fixtures/no_upscale.png deleted file mode 100644 index b4c41f2..0000000 Binary files a/test/fixtures/no_upscale.png and /dev/null differ diff --git a/test/fixtures/quality.jpg b/test/fixtures/quality.jpg deleted file mode 100644 index 2cdaae5..0000000 Binary files a/test/fixtures/quality.jpg and /dev/null differ diff --git a/test/fixtures/upscale.png b/test/fixtures/upscale.png deleted file mode 100644 index b4c41f2..0000000 Binary files a/test/fixtures/upscale.png and /dev/null differ diff --git a/test/fixtures/upscale2.png b/test/fixtures/upscale2.png deleted file mode 100644 index b4c41f2..0000000 Binary files a/test/fixtures/upscale2.png and /dev/null differ diff --git a/test/image_resize_test.js b/test/image_resize_test.js index 64641d1..4433692 100644 --- a/test/image_resize_test.js +++ b/test/image_resize_test.js @@ -1,100 +1,193 @@ -'use strict'; - -var grunt = require('grunt'), - gm = require('gm').subClass({ imageMagick: true }), - async = require('async'), - fs = require('fs'); - -gm.compare = gm.prototype.compare; - -/* - ======== A Handy Little Nodeunit Reference ======== - https://github.com/caolan/nodeunit - - Test methods: - test.expect(numAssertions) - test.done() - Test assertions: - test.ok(value, [message]) - test.equal(actual, expected, [message]) - test.notEqual(actual, expected, [message]) - test.deepEqual(actual, expected, [message]) - test.notDeepEqual(actual, expected, [message]) - test.strictEqual(actual, expected, [message]) - test.notStrictEqual(actual, expected, [message]) - test.throws(block, [error], [message]) - test.doesNotThrow(block, [error], [message]) - test.ifError(value) -*/ - -var createTest = function(test, filename) { +/*global describe, it, before, beforeEach, after, afterEach */ + +var gm = require("gm"); +var gm_im = require("gm").subClass({imageMagick: true}); +var async = require("async"); +var fs = require("fs"); +var path = require("path"); +var assert = require("assert"); + +const TMP_FOLDER = "tmp/"; +const EXPECTED_FOLDER = "test/expected/"; + +var createTest = function(key, filename, comparisonTolerance) { + if (comparisonTolerance === undefined) { + comparisonTolerance = 0.1; + } return function(callback) { - gm('tmp/'+filename).size(function(err, features) { - gm('test/expected/'+filename).size(function(err, expected) { - test.equal(features.width, expected.width); - test.equal(features.height, expected.height); - gm.compare('tmp/'+filename, 'test/expected/'+filename, 0.1, function (err, isEqual, difference) { - if (err) { - throw err; - } - test.ok(isEqual, 'tmp/'+filename+' is not equal to test/expected/'+filename+' (difference: '+difference+')'); + + var features, expected; + + var tmpFile = path.join(TMP_FOLDER, key, filename); + var expectedFile = path.join(EXPECTED_FOLDER, key.replace("_imagemagick", ""), filename); + + async.waterfall([ + + function(callback) { + fs.exists(tmpFile, function (tmpFileExists) { + assert(tmpFileExists, tmpFile + " is missing"); + callback(); + }); + }, + + function(callback) { + fs.exists(expectedFile, function (expectedFileExists) { + assert(expectedFileExists, expectedFile + " is missing"); callback(); }); - - }); + }, + + function(callback) { + gm(tmpFile).size(callback); + }, + + function(_features, callback) { + features = _features; + gm(expectedFile).size(callback); + }, + + function(_expected, callback) { + expected = _expected; + + assert.equal(features.width, expected.width, filename + " width does not match"); + assert.equal(features.height, expected.height, filename + " height does not match"); + + gm().compare(tmpFile, expectedFile, 0.1, callback); + }, + + function(isEqual, difference, raw, callback) { + assert(isEqual, tmpFile + " is not equal to " + expectedFile + " (difference: " + difference + ")"); + callback(); + } + + ], function (err) { + if (err) { + throw err; + } else { + callback(); + } }); }; }; -exports.image_resize = { - setUp: function(done) { - // setup here if necessary - done(); - }, - resize: function(test) { - test.expect(12); - +var createTests = function (key, filename) { + return function(callback) { async.series([ - createTest(test, 'gnu.jpg'), - createTest(test, 'Rhododendron.jpg'), - createTest(test, 'wikipedia.png'), - createTest(test, 'TeslaTurbine.png'), - ], test.done); + createTest(key, filename), + createTest(key + "_imagemagick", filename) + ], callback); + }; +}; - }, - upscale: function(test) { - test.expect(9); - async.series([ - createTest(test, "upscale.png"), - createTest(test, "upscale2.png"), - createTest(test, "no_upscale.png"), - ], test.done); - }, - crop: function(test) { - test.expect(3); - - createTest(test, "crop.png")(test.done); - }, - cropGravity: function(test) { - test.expect(3); - - createTest(test, "crop_gravity.png")(test.done); - }, - quality: function(test) { - test.expect(4); +describe("resize", function () { + it("should resize gnu.jpg", createTests("resize", "gnu.jpg")); + it("should resize Rhododendron.jpg", createTests("resize", "Rhododendron.jpg")); + it("should resize wikipedia.png", createTests("resize", "wikipedia.png")); + it("should resize TeslaTurbine.png", createTests("resize", "TeslaTurbine.png")); +}); - async.series([ - createTest(test, 'quality.jpg'), - function(callback) { - fs.stat('tmp/quality.jpg', function(err, stats) { - fs.stat('test/expected/quality.jpg', function(err, expected) { - var epsilon = 1024; - test.ok(expected.size - epsilon < stats.size && expected.size + epsilon > stats.size ); - callback(); - }); +describe("upscale", function () { + it("should upscale with one dimension", createTests("upscale", "wikipedia.png")); + it("should upscale with both dimensions", createTests("upscale2", "wikipedia.png")); + it("should not upscale", createTests("no_upscale", "wikipedia.png")); + it("should resize with the smallest dimension", createTests("no_upscale2", "wikipedia.png")); +}); + +describe("crop", function () { + it("should crop", createTests("crop", "wikipedia.png")); + it("should crop with gravity", createTests("crop_gravity", "wikipedia.png")); + it("should crop with only width set", createTests("crop_width_only", "wikipedia.png")); +}); + +describe("sharpen", function () { + it("should sharpen the Rhododendron image", createTests("sharpen", "Rhododendron.jpg", 0)); +}); + +describe("filter", function () { + it("should reduce Rhododendron image using catrom filter", createTests("filter", "Rhododendron.jpg", 0)); +}); + +describe("samplingFactor", function () { + it("should resize the Rhododendron image with a sampling factor of 4:2:2", createTests("samplingFactor", "Rhododendron.jpg", 0)); +}); + +describe("convert", function () { + it("should convert png to jpg", function (callback) { + + async.map([ + path.join(process.cwd(), TMP_FOLDER, "convert", "wikipedia.jpg"), + path.join(process.cwd(), TMP_FOLDER, "convert_imagemagick", "wikipedia.jpg") + ], function (path, callback) { + gm(path).format(callback); + }, function (err, results) { + if (err) { + throw err; + } else { + results.forEach(function (format) { + assert.equal("jpeg", format.toLowerCase()); }); + callback(); } - ], test.done); - } -}; + }); + }); +}); + +describe("quality", function() { + var filename = "Rhododendron.jpg"; + it("should resize with lower quality", createTests("quality", filename)); + it("should have a smaller filesize", function (callback) { + async.map([ + path.join(TMP_FOLDER, "quality", filename), + path.join(EXPECTED_FOLDER, "quality", filename) + ], fs.stat, function(err, results) { + if (err) { + throw err; + } else { + var epsilon = 1024; + assert(results[1].size - epsilon < results[0].size && results[1].size + epsilon > results[0].size, "size doesn't match"); + callback(); + } + }); + }); +}); + +describe("noProfile", function () { + var filename = "hamburg.jpg"; + it("should remove all profiles (8bim, exif, iptc, xmp...) from the Hamburg image", function (callback) { + async.map([ + path.join(process.cwd(), TMP_FOLDER, "noProfile", filename), + path.join(process.cwd(), TMP_FOLDER, "noProfile_imagemagick", filename) + ], function (path, callback) { + gm_im(path).identify("%[profiles]", callback); + }, function (err, results) { + if (err) { + throw err; + } else { + results.forEach(function (format) { + // test passes if result contains no or empty string elements + assert.equal("", format, filename + " contains profiles '" + format + "''" ); + }); + callback(); + } + }); + }); +}); + +describe("flatten", function () { + it("should flatten the wikipedia image", createTests("flatten", "wikipedia.jpg")); +}); + +describe("interlace", function () { + it("should interlace Rhododendron.jpg", createTests("interlace", "Rhododendron.jpg")); + it("should interlace wikipedia.png", createTests("interlace", "wikipedia.png")); + it("should interlace plus resize Rhododendron.jpg", createTests("interlace_and_resize", "Rhododendron.jpg")); + it("should interlace plus resize wikipedia.png", createTests("interlace_and_resize", "wikipedia.png")); +}); + +describe("percentage", function () { + it("should resize gnu.jpg from percentage", createTests("percentage", "gnu.jpg")); + it("should resize Rhododendron.jpg from percentage", createTests("percentage", "Rhododendron.jpg")); + it("should resize wikipedia.png from percentage", createTests("percentage", "wikipedia.png")); + it("should resize TeslaTurbine.png from percentage", createTests("percentage", "TeslaTurbine.png")); +});