imageOptimizer.js 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. var debug = require('debug')('ylt:imageOptimizer');
  2. var Q = require('q');
  3. var Imagemin = require('imagemin');
  4. var jpegoptim = require('imagemin-jpegoptim');
  5. var ImageOptimizer = function() {
  6. var MAX_JPEG_QUALITY = 85;
  7. function optimizeImage(entry) {
  8. var deferred = Q.defer();
  9. var fileSize = entry.weightCheck.body.length;
  10. debug('Current file size is %d', fileSize);
  11. if (isJpeg(entry)) {
  12. debug('File is a JPEG');
  13. // Starting softly with a lossless compression
  14. return compressJpegLosslessly(entry.weightCheck.body)
  15. .then(function(newFile) {
  16. var newFileSize = newFile.contents.length;
  17. debug('JPEG lossless compression complete for %s', entry.url);
  18. if (newFileSize < fileSize) {
  19. entry.weightCheck.lossless = entry.weightCheck.optimized = newFileSize;
  20. entry.weightCheck.isOptimized = false;
  21. debug('Filesize is %d bytes smaller (-%d%)', fileSize - newFileSize, Math.round((fileSize - newFileSize) * 100 / fileSize));
  22. }
  23. // Now let's compress lossy to MAX_JPEG_QUALITY
  24. return compressJpegLossly(entry.weightCheck.body);
  25. })
  26. .then(function(newFile) {
  27. var newFileSize = newFile.contents.length;
  28. debug('JPEG lossy compression complete for %s', entry.url);
  29. if (newFileSize < fileSize) {
  30. entry.weightCheck.lossy = newFileSize;
  31. entry.weightCheck.isOptimized = false;
  32. debug('Filesize is %d bytes smaller (-%d%)', fileSize - newFileSize, Math.round((fileSize - newFileSize) * 100 / fileSize));
  33. if (newFileSize < entry.weightCheck.lossless) {
  34. entry.weightCheck.optimized = newFileSize;
  35. }
  36. }
  37. return entry;
  38. });
  39. } else if (isPNG(entry)) {
  40. debug('File is a PNG');
  41. // Starting softly with a lossless compression
  42. return compressPngLosslessly(entry.weightCheck.body)
  43. .then(function(newFile) {
  44. var newFileSize = newFile.contents.length;
  45. debug('PNG lossless compression complete for %s', entry.url);
  46. if (newFileSize < fileSize) {
  47. entry.weightCheck.lossless = entry.weightCheck.optimized = newFileSize;
  48. entry.weightCheck.isOptimized = false;
  49. debug('Filesize is %d bytes smaller (-%d%)', fileSize - newFileSize, Math.round((fileSize - newFileSize) * 100 / fileSize));
  50. }
  51. return entry;
  52. });
  53. } else if (isSVG(entry)) {
  54. debug('File is an SVG');
  55. // Starting softly with a lossless compression
  56. return compressSvgLosslessly(entry.weightCheck.body)
  57. .then(function(newFile) {
  58. var newFileSize = newFile.contents.length;
  59. debug('SVG lossless compression complete for %s', entry.url);
  60. if (newFileSize < fileSize) {
  61. entry.weightCheck.lossless = entry.weightCheck.optimized = newFileSize;
  62. entry.weightCheck.isOptimized = false;
  63. debug('Filesize is %d bytes smaller (-%d%)', fileSize - newFileSize, Math.round((fileSize - newFileSize) * 100 / fileSize));
  64. }
  65. return entry;
  66. });
  67. } else {
  68. debug('File type is not an optimizable image');
  69. deferred.resolve(entry);
  70. }
  71. return deferred.promise;
  72. }
  73. function isJpeg(entry) {
  74. return entry.isImage && entry.contentType === 'image/jpeg';
  75. }
  76. function compressJpegLosslessly(imageBody) {
  77. var deferred = Q.defer();
  78. var startTime = Date.now();
  79. debug('Starting JPEG lossless compression');
  80. new Imagemin()
  81. .src(imageBody)
  82. .use(Imagemin.jpegtran())
  83. .run(function (err, files) {
  84. if (err) {
  85. deferred.reject(err);
  86. } else {
  87. deferred.resolve(files[0]);
  88. var endTime = Date.now();
  89. debug('compressJpegLosslessly took %d ms', endTime - startTime);
  90. }
  91. });
  92. return deferred.promise;
  93. }
  94. function compressJpegLossly(imageBody) {
  95. var deferred = Q.defer();
  96. var startTime = Date.now();
  97. debug('Starting JPEG lossy compression');
  98. new Imagemin()
  99. .src(imageBody)
  100. .use(jpegoptim({max: MAX_JPEG_QUALITY}))
  101. .run(function (err, files) {
  102. if (err) {
  103. deferred.reject(err);
  104. } else {
  105. deferred.resolve(files[0]);
  106. var endTime = Date.now();
  107. debug('compressJpegLossly took %d ms', endTime - startTime);
  108. }
  109. });
  110. return deferred.promise;
  111. }
  112. function isPNG(entry) {
  113. return entry.isImage && entry.contentType === 'image/png';
  114. }
  115. function compressPngLosslessly(imageBody) {
  116. var deferred = Q.defer();
  117. var startTime = Date.now();
  118. debug('Starting PNG losslessly compression');
  119. new Imagemin()
  120. .src(imageBody)
  121. .use(Imagemin.optipng({optimizationLevel: 2}))
  122. .run(function (err, files) {
  123. if (err) {
  124. deferred.reject(err);
  125. } else {
  126. deferred.resolve(files[0]);
  127. var endTime = Date.now();
  128. debug('compressPngLosslessly took %d ms', endTime - startTime);
  129. }
  130. });
  131. return deferred.promise;
  132. }
  133. function isSVG(entry) {
  134. return entry.isImage && entry.contentType === 'image/svg+xml';
  135. }
  136. function compressSvgLosslessly(imageBody) {
  137. var deferred = Q.defer();
  138. var startTime = Date.now();
  139. debug('Starting SVG losslessly compression');
  140. new Imagemin()
  141. .src(imageBody)
  142. .use(Imagemin.svgo())
  143. .run(function (err, files) {
  144. if (err) {
  145. deferred.reject(err);
  146. } else {
  147. deferred.resolve(files[0]);
  148. var endTime = Date.now();
  149. debug('compressSvgLosslessly took %d ms', endTime - startTime);
  150. }
  151. });
  152. return deferred.promise;
  153. }
  154. return {
  155. //recompressIfImage: recompressIfImage,
  156. optimizeImage: optimizeImage,
  157. compressJpegLosslessly: compressJpegLosslessly,
  158. compressJpegLossly: compressJpegLossly,
  159. compressPngLosslessly: compressPngLosslessly,
  160. compressSvgLosslessly: compressSvgLosslessly
  161. };
  162. };
  163. module.exports = new ImageOptimizer();