andismith / grunt-responsive-images

Produce images at different sizes for responsive websites.
http://www.andismith.com/grunt-responsive-images/
MIT License
719 stars 96 forks source link

Images are scaled up when using with imagemagick 6 (Ubuntu 14.04) #45

Open JostBaron opened 10 years ago

JostBaron commented 10 years ago

When I have defined this size

{
    name:   '1000',
    width:  3000,
    suffix: '-3x',
    upscale:false
}

an 800x600 image is upscaled to 3000x2250. My other options are:

options: {
    engine: 'im',
     quality: 70
}
andismith commented 10 years ago

Could you provide your full Grunt file please?

JostBaron commented 10 years ago

Ok, here are:

The gruntfile is a bit longish, sorry for that.

configuration.json:

{
    "distribution": {
        "root":             "distribution",
        "pages":            "distribution",
        "styles":           "distribution",
        "scripts":          "distribution",
        "images":           "distribution/images",
        "contentimages":    "distribution/content/images"
    },
    "build": {
        "absRefPrefix":     "/",
        "root":             "build",
        "pages":            "build",
        "styles":           "build/styles",
        "scripts":          "build/scripts",
        "images":           "build/images",
        "contentimages":    "build/content/images"
    },
    "source": {
        "root":             "source",
        "styles":           "source/styles",
        "images":           "source/images",
        "markup":           "source/markup"
    },
    "content": {
        "images":           "source/content/images",
        "data":             "source/content/data",
        "pages":            "source/markup/pages"
    },
    "deployment": {
        "host":             "doesnt-matter",
        "port":             21,
        "authkey":          "somekey",
        "targetdir":        "/",
        "contentimages":    "/content/images"
    }
}

package.json:

{
  "name": "doesnt-matter",
  "version": "0.1.0",
  "dependencies": {},
  "devDependencies": {
    "grunt": "latest",
    "grunt-contrib-less": "latest",
    "grunt-contrib-copy": "latest",
    "grunt-contrib-uglify": "latest",
    "grunt-contrib-cssmin": "latest",
    "grunt-ftp-deploy": "latest",
    "swig": "latest",
    "grunt-swig2": "latest",
    "grunt-contrib-compress": "latest",
    "grunt-contrib-watch": "latest",
    "grunt-contrib-connect": "latest",
    "grunt-modernizr": "latest",
    "grunt-responsive-images": "^0.1.2",
    "markdown": "^0.5.0",
    "grunt-contrib-clean": "latest"
  },
  "engines": {
    "node": ">=0.8.0"
  }
}

Gruntfile.js

"use strict";

module.exports = function (grunt) {

    var path = require('path');

    var LIVERELOAD_PORT = 12345;

    var swig = require('swig');
    var markdown = require( "markdown" ).markdown;

    var configuration = grunt.file.readJSON('configuration.json');

    function getContentImageFileNames() {
        var result = {};

        grunt.file.recurse(
                configuration.content.images,
                function(abspath, rootdir, subdir, filename) {
                    subdir = subdir || '';

                    if (!(subdir in result)) {
                        result[subdir] = [];
                    }

                    if ('data.json' === filename) {
                        var data = grunt.file.readJSON(abspath);

                        for (var i = 0; i < data.length; i++) {
                            var imagedata = data[i];

                            filename = imagedata.name;
                            result[subdir].push(
                                {
                                    subdirectory:   path.relative(configuration.distribution.root, configuration.distribution.contentimages) + '/' + subdir,
                                    extension:      path.extname(filename),
                                    name:           path.basename(filename, path.extname(filename)),
                                    slideshowlink:  'referenzen/' + subdir + '.html',
                                    alttext:        imagedata.alttext,
                                    title:          imagedata.title,
                                    caption:        imagedata.caption
                                }
                            )
                        }
                    }
                }
        )

        return result;
    }

    function getImageSizes() {
        return [
            {
                name:   '320',
                width:  320,
                suffix: '-1x'
            },
            {
                name:   '320',
                width:  480,
                suffix: '-1.5x'
            },
            {
                name:   '320',
                width:  640,
                suffix: '-2x'
            },
            {
                name:   '320',
                width:  960,
                suffix: '-3x'
            },
            {
                name:   '480',
                width:  480,
                suffix: '-1x'
            },
            {
                name:   '480',
                width:  720,
                suffix: '-1.5x'
            },
            {
                name:   '480',
                width:  960,
                suffix: '-2x'
            },
            {
                name:   '480',
                width:  1440,
                suffix: '-3x'
            },
            {
                name:   '720',
                width:  720,
                suffix: '-1x'
            },
            {
                name:   '720',
                width:  1080,
                suffix: '-1.5x'
            },
            {
                name:   '720',
                width:  1440,
                suffix: '-2x'
            },
            {
                name:   '720',
                width:  2160,
                suffix: '-3x'
            },
            {
                name:   '1000',
                width:  1000,
                suffix: '-1x'
            },
            {
                name:   '1000',
                width:  1500,
                suffix: '-1.5x'
            },
            {
                name:   '1000',
                width:  2000,
                suffix: '-2x'
            },
            {
                name:   '1000',
                width:  3000,
                suffix: '-3x'
            },
            {
                name:   'thumb-130',
                width:  130,
                height: 130,
                aspectRatio: false,
                suffix: '-1x'
            },
            {
                name:   'thumb-130',
                width:  185,
                height: 185,
                aspectRatio: false,
                suffix: '-1.5x'
            },
            {
                name:   'thumb-130',
                width:  260,
                height: 260,
                aspectRatio: false,
                suffix: '-2x'
            },
            {
                name:   'thumb-130',
                width:  390,
                height: 390,
                aspectRatio: false,
                suffix: '-3x'
            }
        ];
    }

    grunt.initConfig({
        configuration: configuration,

        less: {
            options: {
                modifyVars: {
                    // example: assetBasePath: '"/test"' (mind the inner quotes)
                }
            },
            all: {
                options: {
                    paths: [
                        'bower_components'
                    ]
                },
                files: {
                    '<%= configuration.build.styles %>/main.css': '<%= configuration.source.styles %>/main.less'
                }
            }
        },

        modernizr: {
            build: {
                "devFile" : "remote",
                "outputFile" : "<%= configuration.build.scripts %>/modernizr-custom.js",

                "extra" : {
                    "shiv" : false,
                    "printshiv" : false,
                    "load" : false,
                    "mq" : false,
                    "cssclasses" : true
                },
                "extensibility" : {
                    "addtest" : false,
                    "prefixed" : false,
                    "teststyles" : false,
                    "testprops" : false,
                    "testallprops" : false,
                    "hasevents" : false,
                    "prefixes" : false,
                    "domprefixes" : false
                },
                "uglify" : false,
                "tests" : [],
                "parseFiles" : false,
                "matchCommunityTests" : false,
                "customTests" : []
            }

        },

        uglify: {
            options: {
                mangle: {
                    except: ['jQuery']
                }
            },
            head: {
                files: {
                    '<%= configuration.build.scripts %>/head.js': [
                        '<%= configuration.build.scripts %>/modernizr-custom.js',
                        'bower_components/html5shiv/dist/html5shiv.js'
                    ]
                }
            },
            body: {
                files: {
                    '<%= configuration.build.scripts %>/body.js': [
                        'bower_components/respond/dest/respond.src.js',
                        'bower_components/picturefill/dist/picturefill.js',
                        'bower_components/swiper/dist/idangerous.swiper.js',
                        'source/scripts/slideshow.js',
                        'source/scripts/menu.js'
                    ]
                }
            }
        },

        swig: {
            options: {
                data: function() {
                    var path = require('path');
                    var result = {};
                    grunt.file.recurse(
                            process.cwd() + '/' + configuration.content.data,
                            function(abspath, rootdir, subdir, filename) {
                                result[path.basename(filename, '.json')] = grunt.file.readJSON(abspath);
                            }
                    );

                    if ('contentimages' in result) {
                        console.warn('Name conflict. The data key \'contentimages\' is reserved.');
                    }
                    result['contentimages'] = getContentImageFileNames();

                    if ('imagesizes' in result) {
                        console.warn('Name conflict. The data key \'imagesizes\' is reserved.');
                    }
                    result['imagesizes'] = getImageSizes();

                    return result;
                },
                filters: {
                    markdown: function(input) {
                        return markdown.toHTML(input, 'Maruku');
                    }
                },
                swigOptions: {
                    loader: swig.loaders.fs(__dirname)
                }
            },
            pages: {
                files: [
                    {
                        expand: true,
                        cwd:    '<%= configuration.content.pages %>',
                        src:    ['**/*.html'],
                        dest:   '<%= configuration.build.pages %>'
                    }
                ]
            }
        },

        copy: {
            images: {
                files: [
                    {
                        expand: true,
                        dot: true,
                        cwd: '<%= configuration.source.images %>',
                        dest: '<%= configuration.build.images %>',
                        src: [
                            '**',
                            '!background/**'
                        ]
                    }
                ]
            },
            htaccess: {
                files: [
                    {
                        expand: true,
                        dot: true,
                        cwd: '<%= configuration.source.root %>',
                        dest: '<%= configuration.build.root %>',
                        src: [
                            '.htaccess'
                        ]
                    }
                ]
            },
            distribution: {
                files: [
                    {
                        expand: true,
                        dot: true,
                        cwd:    '<%= configuration.build.root %>',
                        dest:   '<%= configuration.distribution.root %>',
                        src: [
                            '**',
                            '!styles/**',
                            '!scripts/**'
                        ]
                    }
                ]
            }
        },

        compress: {
            options: {
                mode:       'gzip',
                level:      9,
                pretty:     true
            },
            distribution: {
                files: [
                    {
                        src:    '<%= configuration.build.scripts %>/head.js',
                        dest:   '<%= configuration.distribution.scripts %>/head.gz.js',
                        ext:    '.gz.js',
                        filter: 'isFile'
                    },
                    {
                        src:    '<%= configuration.build.scripts %>/body.js',
                        dest:   '<%= configuration.distribution.scripts %>/body.gz.js',
                        ext:    '.gz.js',
                        filter: 'isFile'
                    },
                    {
                        src:    '<%= configuration.build.styles %>/**',
                        dest:   '<%= configuration.distribution.styles %>/styles.gz.css',
                        ext:    '.gz.css',
                        filter: 'isFile'
                    }
                ]
            }
        },

        watch: {
            livereload: {
                options: {
                    livereload: LIVERELOAD_PORT
                },
                files: [
                    '<%= configuration.source.root %>/**'
                ],
                tasks: ['default']
            }
        },

        connect: {
            options: {
                port: 9010,
                // change this to '0.0.0.0' to access the server from outside
                hostname: '127.0.0.1'
            },
            livereload: {
                options: {
                    livereload: LIVERELOAD_PORT,
                    base: '<%= configuration.distribution.root %>',
                    middleware: function(connect, options, middlewares) {

                        middlewares.unshift(function(req, res, next) {
                            if (-1 !== req.url.search('.gz')) {
                                res.setHeader('Content-Encoding', 'gzip');
                            }
                            next();
                        });

                        return middlewares;
                    }
                }
            }
        },

        responsive_images: {
            contentimages: {
                options: {
                    engine: 'im',
                    quality: 70,
                    sizes: getImageSizes()
                },
                files: [
                    {
                        expand: true,
                        dot: true,
                        cwd: '<%= configuration.content.images %>',
                        dest: '<%= configuration.build.contentimages %>',
                        filter: 'isFile',
                        src: [
                            '**',
                            '!**/data.json'
                        ]
                    }
                ]
            }
        },

        'ftp-deploy': {
            'no-images': {
                auth: {
                    host: '<%= configuration.deployment.host %>',
                    port: '<%= configuration.deployment.port %>',
                    authKey: '<%= configuration.deployment.authkey %>'
                },
                src: '<%= configuration.distribution.root %>',
                dest: '<%= configuration.deployment.targetdir %>',
                exclusions: [
                    '<%= configuration.distribution.contentimages %>/**'
                ]
            },
            'images': {
                auth: {
                    host: '<%= configuration.deployment.host %>',
                    port: '<%= configuration.deployment.port %>',
                    authKey: '<%= configuration.deployment.authkey %>'
                },
                src: '<%= configuration.distribution.contentimages %>',
                dest: '<%= configuration.deployment.contentimages %>'
            }
        },
        clean: {
            options: {
//                'no-write': true
            },
            build: [
                '<%= configuration.build.root %>/**'
            ],
            distribution: [
                '<%= configuration.distribution.root %>/**'
            ]
        }
    });

    grunt.loadNpmTasks('grunt-contrib-less');
    grunt.loadNpmTasks('grunt-contrib-copy');
    grunt.loadNpmTasks('grunt-contrib-compress');
    grunt.loadNpmTasks('grunt-contrib-uglify');
    grunt.loadNpmTasks('grunt-contrib-watch');
    grunt.loadNpmTasks('grunt-contrib-connect');
    grunt.loadNpmTasks('grunt-contrib-clean');
    grunt.loadNpmTasks('grunt-modernizr');
    grunt.loadNpmTasks('grunt-responsive-images');
    grunt.loadNpmTasks('grunt-swig2');
    grunt.loadNpmTasks('grunt-ftp-deploy');

    grunt.registerTask(
            'build',
            [
                'less:all',
                'modernizr:build',
                'uglify:head',
                'uglify:body',
                'swig:pages',
                'copy:images',
                'copy:htaccess',
                'copy:distribution',
                'compress:distribution'
            ]
    );

    grunt.registerTask(
            'server',
            [
                'build',
                'connect:livereload',
                'watch'
            ]
    );

    grunt.registerTask(
            'images',
            [
                'responsive_images:contentimages'
            ]
    );

    grunt.registerTask(
            'full',
            [
                'images',
                'build'
            ]
    );

    grunt.registerTask(
            'deploy',
            [
                'build',
                'ftp-deploy:no-images'
            ]
    );

    grunt.registerTask(
            'deploy-images',
            [
         //       'images',
                'ftp-deploy:images'
            ]
    );

    grunt.registerTask(
            'deploy-full',
            [
                'full',
                'ftp-deploy:no-images',
                'ftp-deploy:images'
            ]
    );

    grunt.registerTask(
            'default',
            [
                'build'
            ]
    );
};
andismith commented 10 years ago

What type of image file is it? Is it an animated GIF?

JostBaron commented 10 years ago

No, all of the images were JPEGs (file extension differed, either jpg or JPG).

andismith commented 9 years ago

I've released a new version that changes the internals for how options work. Does this still happen with v0.1.4? Have you tried running Grunt in verbose mode?

JostBaron commented 9 years ago

The problem is not fixed - upscale: false is respected when using GraphicsMagick, but not when using ImageMagick.

I ran Grunt in verbose mode, but that did not reveal anything.

hamsterbacke23 commented 9 years ago

Seems like sizingMethod = '\>'; would work

At least I tried it and it seems to stop upscaling.. See also: http://stackoverflow.com/questions/3504931/cli-imagemagick-resize-downscale-only

nestordeharo commented 9 years ago

I have the same problem...using a simple configuration file it upscale the image (e.g. 80px to 400px)

'use strict';

module.exports = function(grunt){

  grunt.initConfig({
    responsive_images: {
      options: {
        sizes: [{
          name: "small",
          width: 480,
          upscale: false
        }]
      },
      files:{
        expand: true,
        src: ['**.{jpg,gif,png}'],
        cwd: 'images/',
        dest:'ejemplo/'
      }
    }
  })

  grunt.loadNpmTasks('grunt-responsive-images');
  grunt.registerTask('default', ['responsive_images']);
}
isaacchansky commented 9 years ago

If you adjust the tests to start with a small image, you'll see it get upscaled in the default options test.

I also got it to work (with imagemagick 6 ) thanks to @hamsterbacke23 by changing this upscale bit of code on line 337 to:

 if (sizeOptions.upscale) {
       // upscale
      if (sizeOptions.aspectRatio) {
         sizingMethod = '^';
      } else {
        sizingMethod = '!';
      }
  }else { 
      sizingMethod= '>';
}

which is just setting the sizing method to '>' if options.upscale is set to false. Which is described here as:

"Another commonly used option is to restrict IM so that it will only shrink images to fit into the size given. Never enlarge. This is the '>' resize option. Think of it only applying the resize to images 'greater than' the size given (its a little counter intuitive)."

Happy to open a PR!

hal-gh commented 9 years ago

This issue is also affecting me.

Environment:

malducin-vfxfan commented 9 years ago

I had the same problem, fixed it last year somehow, but can't find that project. This also affects GraphicsMagic on Windows. There is an undocumented option that fixes the problem: createNoScaledImage. Set it to true in your options like:

options: {
    // Task-specific options go here.
    createNoScaledImage: true,
    sizes: [{
        ...
    }]
},

maybe that option is not getting set correctly?

hal-gh commented 9 years ago

@malducin-vfxfan

I suppose it depends on what people here want. As the name implies, createNoScaledImage:true skips over creating any files that would be upscaled. This causes a problem when there are references in static code, with no if/then/else constructs (like css), to images that don't exist (because they were skipped because they would have been upscaled).

Personally, I'd like a new file to be created, which is effectively just the original image duplicated but with a filename that uses the naming convention specified in the options.

@JostBaron - would you clarify which problem you were describing in the ticket?

malducin-vfxfan commented 9 years ago

Thanks, that makes sense. In my case I'm handling the images with code (PHP/Python) so I can check their presence, or just creating a ton of thumbnails (for galleries or contact sheets), so I hadn't considered the CSS case. I'm fine if it keeps working that same way. Maybe just indicate createNoScaledImage:true in the README or a wiki entry for those hitting this problem.

Now I have to check my setup on Windows, because I do have both ImageMagick and GraphicsMagic installed, and even when specifying GraphicsMagic explicitly, it still upscales, so it might be a case where it's finding IM first (maybe because of the registry).

hal-gh commented 9 years ago

Awaiting response from @JostBaron - possibly this can be closed as issue #82 requests documentation enhancement.

iparr commented 9 years ago

I do hope that @isaacchansky commit can be merged, as the suggestion in #82 requires a more complicated Grunt setup involving additional copying of those images that are not resized.

heavysixer commented 9 years ago

another potential fix is to expose sizingMethod as a config variable.