mirror of
https://github.com/igorski/bitmappery.git
synced 2026-06-16 19:25:38 +02:00
Improved performance of filter application by omitting idle effects, implemented brightness and vibrance filters
This commit is contained in:
@@ -28,9 +28,9 @@
|
||||
<template #content>
|
||||
<div class="form" @keyup.enter="requestLayerAdd()">
|
||||
<div class="wrapper input">
|
||||
<label v-t="'levels'"></label>
|
||||
<label v-t="'gamma'"></label>
|
||||
<slider
|
||||
v-model="levels"
|
||||
v-model="gamma"
|
||||
:min="0"
|
||||
:max="100"
|
||||
:tooltip="'none'"
|
||||
@@ -45,6 +45,24 @@
|
||||
:tooltip="'none'"
|
||||
/>
|
||||
</div>
|
||||
<div class="wrapper input">
|
||||
<label v-t="'brightness'"></label>
|
||||
<slider
|
||||
v-model="brightness"
|
||||
:min="0"
|
||||
:max="100"
|
||||
:tooltip="'none'"
|
||||
/>
|
||||
</div>
|
||||
<div class="wrapper input">
|
||||
<label v-t="'vibrance'"></label>
|
||||
<slider
|
||||
v-model="vibrance"
|
||||
:min="0"
|
||||
:max="100"
|
||||
:tooltip="'none'"
|
||||
/>
|
||||
</div>
|
||||
<div class="wrapper input">
|
||||
<label v-t="'desaturate'"></label>
|
||||
<toggle-button
|
||||
@@ -103,12 +121,20 @@ export default {
|
||||
filters() {
|
||||
return this.activeLayer.filters;
|
||||
},
|
||||
levels: {
|
||||
gamma: {
|
||||
get() {
|
||||
return this.internalValue.levels * 100;
|
||||
return this.internalValue.gamma * 100;
|
||||
},
|
||||
set( value ) {
|
||||
this.internalValue.levels = value / 100;
|
||||
this.internalValue.gamma = value / 100;
|
||||
}
|
||||
},
|
||||
brightness: {
|
||||
get() {
|
||||
return this.internalValue.brightness * 100;
|
||||
},
|
||||
set( value ) {
|
||||
this.internalValue.brightness = value / 100;
|
||||
}
|
||||
},
|
||||
contrast: {
|
||||
@@ -119,6 +145,14 @@ export default {
|
||||
this.internalValue.contrast = value / 100;
|
||||
}
|
||||
},
|
||||
vibrance: {
|
||||
get() {
|
||||
return this.internalValue.vibrance * 100;
|
||||
},
|
||||
set( value ) {
|
||||
this.internalValue.vibrance = value / 100;
|
||||
}
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
internalValue: {
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
{
|
||||
"en-US": {
|
||||
"filters": "Filters",
|
||||
"levels": "Levels",
|
||||
"gamma": "Gamma",
|
||||
"contrast": "Contrast",
|
||||
"brightness": "Brightness",
|
||||
"vibrance": "Vibrance",
|
||||
"desaturate": "Desaturate",
|
||||
"save": "Save",
|
||||
"reset": "Reset",
|
||||
|
||||
@@ -23,11 +23,13 @@
|
||||
let defaultFilters = null;
|
||||
|
||||
const FiltersFactory = {
|
||||
create({ levels = .5, contrast = 0, desaturate = false } = {}) {
|
||||
create({ gamma = .5, brightness = .5, contrast = 0, vibrance = .5, desaturate = false } = {}) {
|
||||
return {
|
||||
levels,
|
||||
gamma,
|
||||
brightness,
|
||||
contrast,
|
||||
desaturate,
|
||||
vibrance,
|
||||
};
|
||||
},
|
||||
|
||||
@@ -37,9 +39,11 @@ const FiltersFactory = {
|
||||
*/
|
||||
serialize( filters ) {
|
||||
return {
|
||||
l: filters.levels,
|
||||
g: filters.gamma,
|
||||
b: filters.brightness,
|
||||
c: filters.contrast,
|
||||
d: filters.desaturate,
|
||||
v: filters.vibrance,
|
||||
};
|
||||
},
|
||||
|
||||
@@ -49,9 +53,11 @@ const FiltersFactory = {
|
||||
*/
|
||||
deserialize( filters = {} ) {
|
||||
return FiltersFactory.create({
|
||||
levels: filters.l,
|
||||
gamma: filters.g,
|
||||
brightness: filters.b,
|
||||
contrast: filters.c,
|
||||
desaturate: filters.d,
|
||||
vibrance: filters.v,
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -61,7 +67,9 @@ export const hasFilters = filters => {
|
||||
if ( !defaultFilters ) {
|
||||
defaultFilters = FiltersFactory.create();
|
||||
}
|
||||
return filters.levels !== defaultFilters.levels ||
|
||||
filters.contrast !== defaultFilters.contrast ||
|
||||
filters.desaturate !== defaultFilters.desaturate;
|
||||
return filters.gamma !== defaultFilters.gamma ||
|
||||
filters.brightness !== defaultFilters.brightness ||
|
||||
filters.contrast !== defaultFilters.contrast ||
|
||||
filters.desaturate !== defaultFilters.desaturate ||
|
||||
filters.vibrance !== defaultFilters.vibrance;
|
||||
};
|
||||
|
||||
@@ -20,9 +20,13 @@
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
import FiltersFactory from "@/factories/filters-factory";
|
||||
|
||||
const MAX_8BIT = 255;
|
||||
const HALF = .5;
|
||||
|
||||
const defaultFilters = FiltersFactory.create();
|
||||
|
||||
self.addEventListener( "message", async ({ data }) => {
|
||||
const { id, cmd } = data;
|
||||
let imageData;
|
||||
@@ -37,21 +41,29 @@ self.addEventListener( "message", async ({ data }) => {
|
||||
/* internal methods */
|
||||
|
||||
const renderFilters = ( imageData, width, height, filters ) => {
|
||||
const brightness = ( filters.brightness * 2 );//( filters.brightness * 2 ) - 1; // -1 to 1 range
|
||||
const contrast = Math.pow((( filters.contrast * 100 ) + 100 ) / 100, 2 ); // -100 to 100 range
|
||||
const levels = filters.levels * 2; // 0 to 2 range
|
||||
const gamma = ( filters.gamma * 2 ); // 0 to 2 range
|
||||
const vibrance = -(( filters.vibrance * 200 ) - 100 ); // -100 to 100 range
|
||||
const { desaturate } = filters; // boolean
|
||||
|
||||
const pixels = imageData.data;
|
||||
let r, g, b, a;
|
||||
|
||||
const doBrightness = filters.brightness !== defaultFilters.brightness;
|
||||
const doContrast = filters.contrast !== defaultFilters.contrast;
|
||||
const doGamma = filters.gamma !== defaultFilters.gamma;
|
||||
const doVibrance = filters.vibrance !== defaultFilters.vibrance;
|
||||
|
||||
for ( let x = 0; x < width; ++x ) {
|
||||
for ( let y = 0; y < height; ++y ) {
|
||||
const i = ( y * width + x ) * 4;
|
||||
|
||||
// 1. adjust level (note we leave the alpha channel unchanged)
|
||||
if ( levels ) {
|
||||
pixels[ i ] = pixels[ i ] * levels * levels; // R
|
||||
pixels[ i + 1 ] = pixels[ i + 1 ] * levels * levels; // G
|
||||
pixels[ i + 2 ] = pixels[ i + 2 ] * levels * levels; // B
|
||||
// 1. adjust gamma (note we leave the alpha channel unchanged)
|
||||
if ( doGamma ) {
|
||||
pixels[ i ] = pixels[ i ] * gamma * gamma; // R
|
||||
pixels[ i + 1 ] = pixels[ i + 1 ] * gamma * gamma; // G
|
||||
pixels[ i + 2 ] = pixels[ i + 2 ] * gamma * gamma; // B
|
||||
}
|
||||
// 2. desaturate (note we leave the alpha channel unchanged)
|
||||
if ( desaturate ) {
|
||||
@@ -60,12 +72,31 @@ const renderFilters = ( imageData, width, height, filters ) => {
|
||||
pixels[ i + 1 ] = grayScale; // G
|
||||
pixels[ i + 2 ] = grayScale; // B
|
||||
}
|
||||
// 3. adjust contrast (note we leave the alpha channel unchanged)
|
||||
if ( contrast ) {
|
||||
// 3. adjust brightness (note we leave the alpha channel unchanged)
|
||||
if ( doBrightness ) {
|
||||
pixels[ i ] = pixels[ i ] * brightness;
|
||||
pixels[ i + 1 ] = pixels[ i + 1 ] * brightness;
|
||||
pixels[ i + 2 ] = pixels[ i + 2 ] * brightness;
|
||||
}
|
||||
// 4. adjust contrast (note we leave the alpha channel unchanged)
|
||||
if ( doContrast ) {
|
||||
pixels[ i ] = (( pixels[ i ] / MAX_8BIT - HALF ) * contrast + HALF ) * MAX_8BIT; // R
|
||||
pixels[ i + 1 ] = (( pixels[ i + 1 ] / MAX_8BIT - HALF ) * contrast + HALF ) * MAX_8BIT; // G
|
||||
pixels[ i + 2 ] = (( pixels[ i + 2 ] / MAX_8BIT - HALF ) * contrast + HALF ) * MAX_8BIT; // B
|
||||
}
|
||||
// 5. adjust vibrance (note we leave the alpha channel unchanged)
|
||||
if ( doVibrance ) {
|
||||
r = pixels[ i ];
|
||||
g = pixels[ i + 1 ];
|
||||
b = pixels[ i + 2 ];
|
||||
const max = Math.max( r, g, b );
|
||||
const avg = ( r + g + b ) / 3;
|
||||
const amt = (( Math.abs( max - avg ) * 2 / MAX_8BIT ) * vibrance ) / 10; // 100;
|
||||
|
||||
pixels[ i ] = r !== max ? r + ( max - r ) * amt : r;
|
||||
pixels[ i + 1 ] = g !== max ? g + ( max - g ) * amt : g;
|
||||
pixels[ i + 2 ] = b !== max ? b + ( max - b ) * amt : b;
|
||||
}
|
||||
}
|
||||
}
|
||||
return imageData;
|
||||
|
||||
@@ -5,21 +5,27 @@ describe( "Filters factory", () => {
|
||||
it( "should create a default filter structure when no arguments are passed", () => {
|
||||
const filters = FiltersFactory.create();
|
||||
expect( filters ).toEqual({
|
||||
levels: .5,
|
||||
gamma: .5,
|
||||
brightness: .5,
|
||||
contrast: 0,
|
||||
vibrance: .5,
|
||||
desaturate: false,
|
||||
});
|
||||
});
|
||||
|
||||
it( "should be able to create a filters list from given arguments", () => {
|
||||
const filters = FiltersFactory.create({
|
||||
levels: .7,
|
||||
gamma: .7,
|
||||
brightness: .6,
|
||||
contrast: .3,
|
||||
vibrance: .2,
|
||||
desaturate: true,
|
||||
});
|
||||
expect( filters ).toEqual({
|
||||
levels: .7,
|
||||
gamma: .7,
|
||||
brightness: .6,
|
||||
contrast: .3,
|
||||
vibrance: .2,
|
||||
desaturate: true,
|
||||
});
|
||||
});
|
||||
@@ -28,8 +34,10 @@ describe( "Filters factory", () => {
|
||||
describe( "when serializing and deserializing a filters list", () => {
|
||||
it( "should do so without data loss", async () => {
|
||||
const filters = FiltersFactory.create({
|
||||
levels: .7,
|
||||
gamma: .7,
|
||||
brightness: .6,
|
||||
contrast: .3,
|
||||
vibrance: .2,
|
||||
desaturate: true,
|
||||
});
|
||||
const serialized = FiltersFactory.serialize( filters );
|
||||
@@ -46,10 +54,16 @@ describe( "Filters factory", () => {
|
||||
});
|
||||
|
||||
it( "should consider a configuration where one of the properties deviates from the default as active", () => {
|
||||
let filter = FiltersFactory.create({ levels: .7 });
|
||||
let filter = FiltersFactory.create({ gamma: .7 });
|
||||
expect( hasFilters( filter )).toBe( true );
|
||||
|
||||
filter = FiltersFactory.create({ contrast: .3 });
|
||||
filter = FiltersFactory.create({ brightness: .3 });
|
||||
expect( hasFilters( filter )).toBe( true );
|
||||
|
||||
filter = FiltersFactory.create({ contrast: .6 });
|
||||
expect( hasFilters( filter )).toBe( true );
|
||||
|
||||
filter = FiltersFactory.create({ vibrance: .4 });
|
||||
expect( hasFilters( filter )).toBe( true );
|
||||
|
||||
filter = FiltersFactory.create({ desaturate: true });
|
||||
|
||||
Reference in New Issue
Block a user