123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239 |
- var debug = require('debug')('ylt:imageOptimizer');
- var Q = require('q');
- var Imagemin = require('imagemin');
- var jpegoptim = require('imagemin-jpegoptim');
- var ImageOptimizer = function() {
- var MAX_JPEG_QUALITY = 85;
- var OPTIPNG_COMPRESSION_LEVEL = 1;
- function optimizeImage(entry) {
- var deferred = Q.defer();
- if (!entry.weightCheck || !entry.weightCheck.body) {
- // No valid file available
- deferred.resolve(entry);
- return deferred.promise;
- }
- var fileSize = entry.weightCheck.uncompressedSize;
- debug('Let\'s try to optimize %s', entry.url);
- debug('Current file size is %d', fileSize);
- if (isJpeg(entry)) {
- debug('File is a JPEG');
- // Starting softly with a lossless compression
- return compressJpegLosslessly(new Buffer(entry.weightCheck.body, 'binary'))
- .then(function(newFile) {
- if (!newFile) {
- debug('Optimization didn\'t work');
- return entry;
- }
- var newFileSize = newFile.contents.length;
- debug('JPEG lossless compression complete for %s', entry.url);
-
- if (gainIsEnough(fileSize, newFileSize)) {
- entry.weightCheck.lossless = entry.weightCheck.optimized = newFileSize;
- entry.weightCheck.isOptimized = false;
- debug('Filesize is %d bytes smaller (-%d%)', fileSize - newFileSize, Math.round((fileSize - newFileSize) * 100 / fileSize));
- }
- // Now let's compress lossy to MAX_JPEG_QUALITY
- return compressJpegLossly(new Buffer(entry.weightCheck.body, 'binary'));
- })
-
- .then(function(newFile) {
- if (!newFile) {
- debug('Optimization didn\'t work');
- return entry;
- }
- var newFileSize = newFile.contents.length;
- debug('JPEG lossy compression complete for %s', entry.url);
- if (gainIsEnough(fileSize, newFileSize)) {
-
- if (entry.weightCheck.isOptimized !== false || newFileSize < entry.weightCheck.lossless) {
- entry.weightCheck.optimized = newFileSize;
- }
- entry.weightCheck.lossy = newFileSize;
- entry.weightCheck.isOptimized = false;
- debug('Filesize is %d bytes smaller (-%d%)', fileSize - newFileSize, Math.round((fileSize - newFileSize) * 100 / fileSize));
- }
- return entry;
- })
- .fail(function() {
- return entry;
- });
- } else if (isPNG(entry)) {
- debug('File is a PNG');
- // Starting softly with a lossless compression
- return compressPngLosslessly(new Buffer(entry.weightCheck.body, 'binary'))
- .then(function(newFile) {
- if (!newFile) {
- debug('Optimization didn\'t work');
- return entry;
- }
-
- var newFileSize = newFile.contents.length;
- debug('PNG lossless compression complete for %s', entry.url);
-
- debug('Old file size: %d', fileSize);
- debug('New file size: %d', newFileSize);
- debug('newgainIsEnough: %s', gainIsEnough(fileSize, newFileSize) ? 'true':'false');
- if (gainIsEnough(fileSize, newFileSize)) {
- entry.weightCheck.lossless = entry.weightCheck.optimized = newFileSize;
- entry.weightCheck.isOptimized = false;
- debug('Filesize is %d bytes smaller (-%d%)', fileSize - newFileSize, Math.round((fileSize - newFileSize) * 100 / fileSize));
- }
- return entry;
- })
- .fail(function() {
- return entry;
- });
- } else if (isSVG(entry)) {
- debug('File is an SVG');
- // Starting softly with a lossless compression
- return compressSvgLosslessly(new Buffer(entry.weightCheck.body, 'utf8'))
- .then(function(newFile) {
- if (!newFile) {
- debug('Optimization didn\'t work');
- return entry;
- }
- var newFileSize = newFile.contents.length;
- debug('SVG lossless compression complete for %s', entry.url);
-
- if (gainIsEnough(fileSize, newFileSize)) {
- entry.weightCheck.lossless = entry.weightCheck.optimized = newFileSize;
- entry.weightCheck.isOptimized = false;
- debug('Filesize is %d bytes smaller (-%d%)', fileSize - newFileSize, Math.round((fileSize - newFileSize) * 100 / fileSize));
- }
- return entry;
- })
- .fail(function() {
- return entry;
- });
- } else {
- debug('File type %s is not an (optimizable) image', entry.contentType);
- deferred.resolve(entry);
- }
- return deferred.promise;
- }
- // The gain is estimated of enough value if it's over 2KB or over 20%,
- // but it's ignored if is below 100 bytes
- function gainIsEnough(oldWeight, newWeight) {
- var gain = oldWeight - newWeight;
- var ratio = gain / oldWeight;
- return (gain > 2048 || (ratio > 0.2 && gain > 100));
- }
- function isJpeg(entry) {
- return entry.isImage && entry.contentType === 'image/jpeg';
- }
- function isPNG(entry) {
- return entry.isImage && entry.contentType === 'image/png';
- }
- function isSVG(entry) {
- return entry.isImage && entry.contentType === 'image/svg+xml';
- }
- function compressJpegLosslessly(imageBody) {
- return imageminLauncher(imageBody, 'jpeg', false);
- }
- function compressJpegLossly(imageBody) {
- return imageminLauncher(imageBody, 'jpeg', true);
- }
- function compressPngLosslessly(imageBody) {
- return imageminLauncher(imageBody, 'png', false);
- }
- function compressSvgLosslessly(imageBody) {
- return imageminLauncher(imageBody, 'svg', false);
- }
- function imageminLauncher(imageBody, type, lossy) {
- var deferred = Q.defer();
- var startTime = Date.now();
- debug('Starting %s %s optimization', type, lossy ? 'lossy' : 'lossless');
- var engine;
- if (type === 'jpeg' && !lossy) {
- engine = Imagemin.jpegtran();
- } else if (type === 'jpeg' && lossy) {
- engine = jpegoptim({max: MAX_JPEG_QUALITY});
- } else if (type === 'png' && !lossy) {
- engine = Imagemin.optipng({optimizationLevel: OPTIPNG_COMPRESSION_LEVEL});
- } else if (type === 'svg' && !lossy) {
- engine = Imagemin.svgo();
- } else {
- deferred.reject('No optimization engine found for imagemin');
- }
- try {
- new Imagemin()
- .src(imageBody)
- .use(engine)
- .run(function (err, files) {
- if (err) {
- deferred.reject(err);
- } else {
- deferred.resolve(files[0]);
- var endTime = Date.now();
- debug('Optimization for %s took %d ms', type, endTime - startTime);
- }
- });
- } catch(err) {
- deferred.reject(err);
- }
- return deferred.promise;
- }
- return {
- optimizeImage: optimizeImage,
- compressJpegLosslessly: compressJpegLosslessly,
- compressJpegLossly: compressJpegLossly,
- compressPngLosslessly: compressPngLosslessly,
- compressSvgLosslessly: compressSvgLosslessly,
- gainIsEnough: gainIsEnough
- };
- };
- module.exports = new ImageOptimizer();
|