Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unflatten function - create an alpha channel and make white pixels fully transparent #3461

Merged
merged 12 commits into from
Apr 7, 2023
Merged
1 change: 1 addition & 0 deletions lib/constructor.js
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ const Sharp = function (input, options) {
tintB: 128,
flatten: false,
flattenBackground: [0, 0, 0],
unflatten: false,
negate: false,
negateAlpha: true,
medianSize: 0,
Expand Down
7 changes: 7 additions & 0 deletions lib/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,13 @@ declare namespace sharp {
*/
flatten(flatten?: boolean | FlattenOptions): Sharp;

/**
* Unflatten - add an alpha channel to the image if required, and make white pixels fully transparent. Alpha for non-white pixels will be unchanged/opaque.
* @param unflatten true to enable and false to disable (defaults to true)
* @returns A sharp instance that can be used to chain operations
*/
unflatten(unflatten?: boolean): Sharp;

/**
* Apply a gamma correction by reducing the encoding (darken) pre-resize at a factor of 1/gamma then increasing the encoding (brighten) post-resize at a factor of gamma.
* This can improve the perceived brightness of a resized image in non-linear colour spaces.
Expand Down
20 changes: 20 additions & 0 deletions lib/operation.js
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,25 @@ function flatten (options) {
return this;
}

/**
* Unflatten - add an alpha channel to the image if required, and make white pixels fully transparent. Alpha for non-white pixels will be unchanged/opaque.
*
* @example
* await sharp(rgbInput)
* .unflatten()
* .toBuffer();
*
* @example
* await sharp(rgbInput)
* .threshold(128, { grayscale: false }) // converter bright pixels to white
* .unflatten()
* .toBuffer();
*/
function unflatten (options) {
this.options.unflatten = is.bool(options) ? options : true;
return this;
}

/**
* Apply a gamma correction by reducing the encoding (darken) pre-resize at a factor of `1/gamma`
* then increasing the encoding (brighten) post-resize at a factor of `gamma`.
Expand Down Expand Up @@ -875,6 +894,7 @@ module.exports = function (Sharp) {
median,
blur,
flatten,
unflatten,
gamma,
negate,
normalise,
Expand Down
13 changes: 13 additions & 0 deletions src/operations.cc
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,19 @@ namespace sharp {
}
}

/*
* Unflatten
*/
VImage Unflatten(VImage image) {
if (HasAlpha(image)) {
lovell marked this conversation as resolved.
Show resolved Hide resolved
VImage alpha = image[image.bands() - 1];
VImage noAlpha = RemoveAlpha(image);
return noAlpha.bandjoin(alpha & (noAlpha.colourspace(VIPS_INTERPRETATION_B_W) < 255));
} else {
return image.bandjoin(image.colourspace(VIPS_INTERPRETATION_B_W) < 255);
}
}

/*
* Ensure the image is in a given colourspace
*/
Expand Down
5 changes: 5 additions & 0 deletions src/operations.h
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,11 @@ namespace sharp {
*/
VImage Linear(VImage image, std::vector<double> const a, std::vector<double> const b);

/*
* Unflatten
*/
VImage Unflatten(VImage image);

/*
* Recomb with a Matrix of the given bands/channel size.
* Eg. RGB will be a 3x3 matrix.
Expand Down
8 changes: 8 additions & 0 deletions src/pipeline.cc
Original file line number Diff line number Diff line change
Expand Up @@ -550,7 +550,9 @@ class PipelineWorker : public Napi::AsyncWorker {
if (baton->medianSize > 0) {
image = image.median(baton->medianSize);
}

// Threshold - must happen before blurring, due to the utility of blurring after thresholding
// Threshold - must happen before unflatten to enable non-white unflattening
if (baton->threshold != 0) {
image = sharp::Threshold(image, baton->threshold, baton->thresholdGrayscale);
}
Expand All @@ -560,6 +562,11 @@ class PipelineWorker : public Napi::AsyncWorker {
image = sharp::Blur(image, baton->blurSigma);
}

// Unflatten the image
if (baton->unflatten) {
image = sharp::Unflatten(image);
}

// Convolve
if (shouldConv) {
image = sharp::Convolve(image,
Expand Down Expand Up @@ -1460,6 +1467,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
// Operators
baton->flatten = sharp::AttrAsBool(options, "flatten");
baton->flattenBackground = sharp::AttrAsVectorOfDouble(options, "flattenBackground");
baton->unflatten = sharp::AttrAsBool(options, "unflatten");
baton->negate = sharp::AttrAsBool(options, "negate");
baton->negateAlpha = sharp::AttrAsBool(options, "negateAlpha");
baton->blurSigma = sharp::AttrAsDouble(options, "blurSigma");
Expand Down
2 changes: 2 additions & 0 deletions src/pipeline.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ struct PipelineBaton {
double tintB;
bool flatten;
std::vector<double> flattenBackground;
bool unflatten;
bool negate;
bool negateAlpha;
double blurSigma;
Expand Down Expand Up @@ -239,6 +240,7 @@ struct PipelineBaton {
tintB(128.0),
flatten(false),
flattenBackground{ 0.0, 0.0, 0.0 },
unflatten(false),
negate(false),
negateAlpha(true),
blurSigma(0.0),
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/fixtures/expected/unflatten-swiss.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
37 changes: 37 additions & 0 deletions test/unit/unflatten.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
'use strict';

const sharp = require('../../');
const fixtures = require('../fixtures');

// const assert = require('assert');

describe('Unflatten', function () {
it('unflatten white background', function (done) {
sharp(fixtures.inputPng).unflatten()
.toBuffer(function (err, data) {
if (err) throw err;
fixtures.assertSimilar(fixtures.expected('unflatten-white-transparent.png'), data, { threshold: 0 }, done);
});
});
it('unflatten transparent image', function (done) {
sharp(fixtures.inputPngTrimSpecificColourIncludeAlpha).unflatten()
.toBuffer(function (err, data) {
if (err) throw err;
fixtures.assertSimilar(fixtures.expected('unflatten-flag-white-transparent.png'), data, { threshold: 0 }, done);
});
});
it('unflatten using threshold', function (done) {
sharp(fixtures.inputPngPalette).unflatten(true).threshold(128, { grayscale: false })
.toBuffer(function (err, data) {
if (err) throw err;
fixtures.assertSimilar(fixtures.expected('unflatten-swiss.png'), data, { threshold: 1 }, done);
});
});
it('no unflatten', function (done) {
sharp(fixtures.inputPng).unflatten(false)
.toBuffer(function (err, data) {
if (err) throw err;
fixtures.assertSimilar(fixtures.inputPng, data, { threshold: 0 }, done);
});
});
});