Browse Source

Optimize PNG files

Gaël Métais 10 years ago
parent
commit
b52e84d1e7

+ 50 - 2
lib/tools/weightChecker/imageOptimizer.js

@@ -54,6 +54,27 @@ var ImageOptimizer = function() {
                 return entry;
             });
 
+        } else if (isPNG(entry)) {
+
+            debug('File is a PNG');
+
+            // Starting softly with a lossless compression
+            return compressPngLosslessly(entry.weightCheck.body)
+
+            .then(function(newFile) {
+                var newFileSize = newFile.contents.length;
+
+                debug('PNG lossless compression complete for %s', entry.url);
+                
+                if (newFileSize < fileSize) {
+                    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;
+            });
+
         } else {
             debug('File type is not an optimizable image');
             deferred.resolve(entry);
@@ -92,7 +113,7 @@ var ImageOptimizer = function() {
         var deferred = Q.defer();
         var startTime = Date.now();
 
-        debug('Starting JPEG lossly compression');
+        debug('Starting JPEG lossy compression');
 
         new Imagemin()
             .src(imageBody)
@@ -110,11 +131,38 @@ var ImageOptimizer = function() {
         return deferred.promise;
     }
 
+    function isPNG(entry) {
+        return entry.isImage && entry.contentType === 'image/png';
+    }
+
+    function compressPngLosslessly(imageBody) {
+        var deferred = Q.defer();
+        var startTime = Date.now();
+
+        debug('Starting PNG losslessly compression');
+
+        new Imagemin()
+            .src(imageBody)
+            .use(Imagemin.optipng({optimizationLevel: 2}))
+            .run(function (err, files) {
+                if (err) {
+                    deferred.reject(err);
+                } else {
+                    deferred.resolve(files[0]);
+                    var endTime = Date.now();
+                    debug('compressPngLosslessly took %d ms', endTime - startTime);
+                }
+            });
+
+        return deferred.promise;
+    }
+
     return {
         //recompressIfImage: recompressIfImage,
         optimizeImage: optimizeImage,
         compressJpegLosslessly: compressJpegLosslessly,
-        compressJpegLossly: compressJpegLossly
+        compressJpegLossly: compressJpegLossly,
+        compressPngLosslessly: compressPngLosslessly
     };
 };
 

+ 150 - 0
test/core/imageOptimizerTest.js

@@ -76,4 +76,154 @@ describe('imageOptimizer', function() {
         });
     });
 
+    it('should optimize a PNG image losslessly', function(done) {
+        var fileContent = fs.readFileSync(path.resolve(__dirname, '../fixtures/png-image.png'));
+
+        var fileSize = fileContent.length;
+
+        imageOptimizer.compressPngLosslessly(fileContent).then(function(newFile) {
+            var newFileSize = newFile.contents.length;
+            newFileSize.should.be.below(fileSize);
+            done();
+        }).fail(function(err) {
+            done(err);
+        });
+    });
+
+    it('should optimize a png', function(done) {
+        var fileContent = fs.readFileSync(path.resolve(__dirname, '../fixtures/png-image.png'));
+        var fileSize = fileContent.length;
+
+        var entry = {
+            method: 'GET',
+            url: 'http://localhost:8388/an-image.png',
+            requestHeaders: {
+                'User-Agent': 'something',
+                Referer: 'http://www.google.fr/',
+                Accept: '*/*',
+                'Accept-Encoding': 'gzip, deflate'
+            },
+            status: 200,
+            isImage: true,
+            type: 'image',
+            contentType: 'image/png',
+            contentLength: 999,
+            weightCheck: {
+                body: fileContent,
+                totalWeight: fileSize + 200,
+                headersSize: 200,
+                bodySize: fileSize,
+                isCompressed: false,
+                uncompressedSize: fileSize
+            }
+        };
+
+        imageOptimizer.optimizeImage(entry)
+
+        .then(function(newEntry) {
+            newEntry.weightCheck.should.have.a.property('isOptimized').that.equals(false);
+            newEntry.weightCheck.should.have.a.property('lossless').that.is.below(fileSize);
+
+            done();
+        })
+
+        .fail(function(err) {
+            done(err);
+        });
+    });
+
+
+
+
+
+
+
+
+
+    it('shouldn\'t fail optimizing a corrupted jpeg', function(done) {
+
+        // In this test, we try to optimize a PNG but with a falsy "image/jpeg" content type
+
+        var fileContent = fs.readFileSync(path.resolve(__dirname, '../fixtures/png-image.png'));
+        var fileSize = fileContent.length;
+
+        var entry = {
+            method: 'GET',
+            url: 'http://localhost:8388/an-image.png',
+            requestHeaders: {
+                'User-Agent': 'something',
+                Referer: 'http://www.google.fr/',
+                Accept: '*/*',
+                'Accept-Encoding': 'gzip, deflate'
+            },
+            status: 200,
+            isImage: true,
+            type: 'image',
+            contentType: 'image/jpeg',
+            contentLength: 999,
+            weightCheck: {
+                body: fileContent,
+                totalWeight: fileSize + 200,
+                headersSize: 200,
+                bodySize: fileSize,
+                isCompressed: false,
+                uncompressedSize: fileSize
+            }
+        };
+
+        imageOptimizer.optimizeImage(entry)
+
+        .then(function(newEntry) {
+            newEntry.weightCheck.should.not.have.a.property('isOptimized');
+            done();
+        })
+
+        .fail(function(err) {
+            done(err);
+        });
+    });
+
+    it('shouldn\'t fail optimizing a corrupted png', function(done) {
+
+        // In this test, we try to optimize a JPEG but with a falsy "image/png" content type
+
+        var fileContent = fs.readFileSync(path.resolve(__dirname, '../fixtures/jpeg-image.jpg'));
+        var fileSize = fileContent.length;
+
+        var entry = {
+            method: 'GET',
+            url: 'http://localhost:8388/an-image.jpg',
+            requestHeaders: {
+                'User-Agent': 'something',
+                Referer: 'http://www.google.fr/',
+                Accept: '*/*',
+                'Accept-Encoding': 'gzip, deflate'
+            },
+            status: 200,
+            isImage: true,
+            type: 'image',
+            contentType: 'image/png',
+            contentLength: 999,
+            weightCheck: {
+                body: fileContent,
+                totalWeight: fileSize + 200,
+                headersSize: 200,
+                bodySize: fileSize,
+                isCompressed: false,
+                uncompressedSize: fileSize
+            }
+        };
+
+        imageOptimizer.optimizeImage(entry)
+
+        .then(function(newEntry) {
+            newEntry.weightCheck.should.not.have.a.property('isOptimized');
+            done();
+        })
+
+        .fail(function(err) {
+            done(err);
+        });
+    });
+
 });

BIN
test/fixtures/png-image.png