mirror of
https://github.com/lovell/sharp.git
synced 2025-12-19 07:15:08 +01:00
Premultiply alpha channel to avoid dark artifacts during tranformation
Add `Sharp.compare(file1, file2, callback)` function for comparing images using mean squared error (MSE). This is useful for unit tests. See: - https://github.com/jcupitt/libvips/issues/291 - http://entropymine.com/imageworsener/resizealpha/
This commit is contained in:
committed by
Lovell Fuller
parent
c792a047b1
commit
ef8db1eebf
@@ -1,9 +1,8 @@
|
||||
'use strict';
|
||||
|
||||
var assert = require('assert');
|
||||
|
||||
var sharp = require('../../index');
|
||||
var fixtures = require('../fixtures');
|
||||
var sharp = require('../../index');
|
||||
|
||||
sharp.cache(0);
|
||||
|
||||
@@ -76,4 +75,30 @@ describe('Alpha transparency', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('Enlargement with non-nearest neighbor interpolation shouldn’t cause dark edges', function(done) {
|
||||
var BASE_NAME = 'alpha-premultiply-enlargement-2048x1536-paper.png';
|
||||
var actual = fixtures.path('output.' + BASE_NAME);
|
||||
var expected = fixtures.expected(BASE_NAME);
|
||||
sharp(fixtures.inputPngAlphaPremultiplicationSmall)
|
||||
.resize(2048, 1536)
|
||||
.interpolateWith('bicubic')
|
||||
.toFile(actual, function(err) {
|
||||
if (err) throw err;
|
||||
fixtures.assertEqual(actual, expected, done);
|
||||
});
|
||||
});
|
||||
|
||||
it('Reduction with non-nearest neighbor interpolation shouldn’t cause dark edges', function(done) {
|
||||
var BASE_NAME = 'alpha-premultiply-reduction-1024x768-paper.png';
|
||||
var actual = fixtures.path('output.' + BASE_NAME);
|
||||
var expected = fixtures.expected(BASE_NAME);
|
||||
sharp(fixtures.inputPngAlphaPremultiplicationLarge)
|
||||
.resize(1024, 768)
|
||||
.interpolateWith('bicubic')
|
||||
.toFile(actual, function(err) {
|
||||
if (err) throw err;
|
||||
fixtures.assertEqual(actual, expected, done);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
77
test/unit/compare.js
Normal file
77
test/unit/compare.js
Normal file
@@ -0,0 +1,77 @@
|
||||
'use strict';
|
||||
|
||||
var assert = require('assert');
|
||||
var fixtures = require('../fixtures');
|
||||
var fs = require('fs');
|
||||
var sharp = require('../../index');
|
||||
|
||||
sharp.cache(0);
|
||||
|
||||
|
||||
// Constants
|
||||
var MAX_ALLOWED_MEAN_SQUARED_ERROR = 0.0005;
|
||||
|
||||
// Tests
|
||||
describe('sharp.compare', function() {
|
||||
it('should report equality when comparing an image to itself', function(done) {
|
||||
var image = fixtures.inputPngOverlayLayer0;
|
||||
|
||||
sharp.compare(image, image, function (error, info) {
|
||||
if (error) return done(error);
|
||||
|
||||
assert.strictEqual(info.isEqual, true, 'image is equal to itself');
|
||||
assert.strictEqual(info.status, 'success', 'status is correct');
|
||||
assert(0 <= info.meanSquaredError &&
|
||||
info.meanSquaredError <= MAX_ALLOWED_MEAN_SQUARED_ERROR,
|
||||
'MSE is within tolerance');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should report that two images have a mismatched number of bands (channels)', function(done) {
|
||||
var actual = fixtures.inputPngOverlayLayer1;
|
||||
var expected = fixtures.inputJpg;
|
||||
|
||||
sharp.compare(actual, expected, function (error, info) {
|
||||
if (error) return done(error);
|
||||
|
||||
assert.strictEqual(info.isEqual, false);
|
||||
assert.strictEqual(info.status, 'mismatchedBands');
|
||||
assert(typeof info.meanSquaredError === 'undefined', 'MSE is undefined');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should report that two images have a mismatched dimensions', function(done) {
|
||||
var actual = fixtures.inputJpg;
|
||||
var expected = fixtures.inputJpgWithExif;
|
||||
|
||||
sharp.compare(actual, expected, function (error, info) {
|
||||
if (error) return done(error);
|
||||
|
||||
assert.strictEqual(info.isEqual, false);
|
||||
assert.strictEqual(info.status, 'mismatchedDimensions');
|
||||
assert(typeof info.meanSquaredError === 'undefined', 'MSE is undefined');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should report the correct mean squared error for two different images', function(done) {
|
||||
var actual = fixtures.inputPngOverlayLayer0;
|
||||
var expected = fixtures.inputPngOverlayLayer1;
|
||||
|
||||
sharp.compare(actual, expected, function (error, info) {
|
||||
if (error) return done(error);
|
||||
|
||||
var meanSquaredError = info.meanSquaredError;
|
||||
assert.strictEqual(info.isEqual, false);
|
||||
assert.strictEqual(info.status, 'success');
|
||||
// ImageMagick reports: 42242.5
|
||||
// `compare -metric mse 'actual' 'expected' comparison.png`
|
||||
assert(41900 <= meanSquaredError && meanSquaredError <= 41950,
|
||||
'Expected: 41900 <= meanSquaredError <= 41950. Actual: ' + meanSquaredError);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
@@ -6,19 +6,78 @@ var sharp = require('../../index');
|
||||
|
||||
sharp.cache(0);
|
||||
|
||||
// Main
|
||||
|
||||
// Constants
|
||||
var MAX_ALLOWED_IMAGE_MAGICK_MEAN_SQUARED_ERROR = 0.3;
|
||||
|
||||
// Helpers
|
||||
var getPaths = function(baseName, extension) {
|
||||
if (typeof extension === 'undefined') {
|
||||
extension = 'png';
|
||||
}
|
||||
|
||||
var actual = fixtures.path('output.' + baseName + '.' + extension);
|
||||
var expected = fixtures.expected(baseName + '.' + extension);
|
||||
var expectedMagick = fixtures.expected(baseName + '-imagemagick.' + extension);
|
||||
|
||||
return {
|
||||
actual: actual,
|
||||
expected: expected,
|
||||
expectedMagick: expectedMagick
|
||||
};
|
||||
};
|
||||
|
||||
var assertEqual = function (paths, callback) {
|
||||
if (typeof callback !== 'function') {
|
||||
throw new TypeError('`callback` must be a function');
|
||||
}
|
||||
|
||||
fixtures.assertEqual(paths.actual, paths.expected, function (error) {
|
||||
if (error) return callback(error);
|
||||
|
||||
sharp.compare(paths.actual, paths.expectedMagick, function (error, info) {
|
||||
if (error) return callback(error);
|
||||
|
||||
if (info.meanSquaredError > MAX_ALLOWED_IMAGE_MAGICK_MEAN_SQUARED_ERROR) {
|
||||
return callback(new Error('Expected MSE against ImageMagick to be <= ' +
|
||||
MAX_ALLOWED_IMAGE_MAGICK_MEAN_SQUARED_ERROR + '. Actual: ' +
|
||||
info.meanSquaredError));
|
||||
}
|
||||
|
||||
callback();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Test
|
||||
describe('Overlays', function() {
|
||||
it('Overlay transparent PNG on solid background', function(done) {
|
||||
var paths = getPaths('alpha-layer-01');
|
||||
|
||||
sharp(fixtures.inputPngOverlayLayer0)
|
||||
.overlayWith(fixtures.inputPngOverlayLayer1)
|
||||
.toBuffer(function (error, data, info) {
|
||||
.toFile(paths.actual, function (error) {
|
||||
if (error) return done(error);
|
||||
|
||||
fixtures.assertSimilar(fixtures.expected('alpha-layer-01.png'), data, {threshold: 0}, done);
|
||||
assertEqual(paths, done);
|
||||
});
|
||||
});
|
||||
|
||||
it('Overlay low-alpha transparent PNG on solid background', function(done) {
|
||||
var paths = getPaths('alpha-layer-01-low-alpha');
|
||||
|
||||
sharp(fixtures.inputPngOverlayLayer0)
|
||||
.overlayWith(fixtures.inputPngOverlayLayer1LowAlpha)
|
||||
.toFile(paths.actual, function (error) {
|
||||
if (error) return done(error);
|
||||
|
||||
assertEqual(paths, done);
|
||||
});
|
||||
});
|
||||
|
||||
it('Composite three transparent PNGs into one', function(done) {
|
||||
var paths = getPaths('alpha-layer-012');
|
||||
|
||||
sharp(fixtures.inputPngOverlayLayer0)
|
||||
.overlayWith(fixtures.inputPngOverlayLayer1)
|
||||
.toBuffer(function (error, data, info) {
|
||||
@@ -26,26 +85,56 @@ describe('Overlays', function() {
|
||||
|
||||
sharp(data)
|
||||
.overlayWith(fixtures.inputPngOverlayLayer2)
|
||||
.toBuffer(function (error, data, info) {
|
||||
.toFile(paths.actual, function (error) {
|
||||
if (error) return done(error);
|
||||
|
||||
fixtures.assertSimilar(fixtures.expected('alpha-layer-012.png'), data, {threshold: 0}, done);
|
||||
assertEqual(paths, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// This tests that alpha channel unpremultiplication is correct:
|
||||
it('Composite three low-alpha transparent PNGs into one', function(done) {
|
||||
sharp(fixtures.inputPngOverlayLayer1LowAlpha)
|
||||
.overlayWith(fixtures.inputPngOverlayLayer2LowAlpha)
|
||||
.toBuffer(function (error, data, info) {
|
||||
it('Composite two transparent PNGs into one', function(done) {
|
||||
var paths = getPaths('alpha-layer-12');
|
||||
|
||||
sharp(fixtures.inputPngOverlayLayer1)
|
||||
.overlayWith(fixtures.inputPngOverlayLayer2)
|
||||
.toFile(paths.actual, function (error, data, info) {
|
||||
if (error) return done(error);
|
||||
|
||||
fixtures.assertSimilar(fixtures.expected('alpha-layer-012-low-alpha.png'), data, {threshold: 0}, done);
|
||||
assertEqual(paths, done);
|
||||
});
|
||||
});
|
||||
|
||||
it('Composite two low-alpha transparent PNGs into one', function(done) {
|
||||
var paths = getPaths('alpha-layer-12-low-alpha');
|
||||
|
||||
sharp(fixtures.inputPngOverlayLayer1LowAlpha)
|
||||
.overlayWith(fixtures.inputPngOverlayLayer2LowAlpha)
|
||||
.toFile(paths.actual, function (error, data, info) {
|
||||
if (error) return done(error);
|
||||
|
||||
assertEqual(paths, done);
|
||||
});
|
||||
});
|
||||
|
||||
it('Composite three low-alpha transparent PNGs into one', function(done) {
|
||||
var paths = getPaths('alpha-layer-012-low-alpha');
|
||||
|
||||
sharp(fixtures.inputPngOverlayLayer0)
|
||||
.overlayWith(fixtures.inputPngOverlayLayer1LowAlpha)
|
||||
.toBuffer(function (error, data, info) {
|
||||
if (error) return done(error);
|
||||
|
||||
sharp(data)
|
||||
.overlayWith(fixtures.inputPngOverlayLayer2LowAlpha)
|
||||
.toFile(paths.actual, function (error, data, info) {
|
||||
if (error) return done(error);
|
||||
|
||||
assertEqual(paths, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// This tests that alpha channel unpremultiplication is correct:
|
||||
it('Composite transparent PNG onto JPEG', function(done) {
|
||||
sharp(fixtures.inputJpg)
|
||||
.overlayWith(fixtures.inputPngOverlayLayer1)
|
||||
|
||||
Reference in New Issue
Block a user