From c22c6c70d098715d610c72afe83ef8f6b3d9d57a Mon Sep 17 00:00:00 2001 From: Rishabh Shukla <42492389+blurry-x-face@users.noreply.github.com> Date: Sat, 18 Jan 2020 02:17:47 +0530 Subject: [PATCH] Adding easier to use interface for blend module and different blend modes (#1453) * Bump data-uri-to-buffer from 2.0.1 to 3.0.0 Bumps [data-uri-to-buffer](https://github.com/TooTallNate/node-data-uri-to-buffer) from 2.0.1 to 3.0.0. - [Release notes](https://github.com/TooTallNate/node-data-uri-to-buffer/releases) - [Commits](https://github.com/TooTallNate/node-data-uri-to-buffer/compare/2.0.1...3.0.0) Signed-off-by: dependabot-preview[bot] * add blend modes * fix toCliString test * add docs link for blend modes * fix crop module * undo last commit * minor change * change default to custom * add docs link Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> Co-authored-by: Harsh Khandeparkar <34770591+HarshKhandeparkar@users.noreply.github.com> Co-authored-by: Jeffrey Warren --- docs/MODULES.md | 11 +++- src/modules/Blend/Module.js | 76 +++++++++++++++++++++++--- src/modules/Blend/info.json | 21 ++++++- test/core/sequencer/image-sequencer.js | 2 +- 4 files changed, 95 insertions(+), 15 deletions(-) diff --git a/docs/MODULES.md b/docs/MODULES.md index a378bf79..d1a9137e 100644 --- a/docs/MODULES.md +++ b/docs/MODULES.md @@ -78,7 +78,8 @@ This module is used for averaging all the pixels of the image. ## blend-module -This module is used for blending two images . +This module is used for blending two images. For More info read: _[wiki](https://en.wikipedia.org/wiki/Blend_modes)_ + #### Usage ```js @@ -88,8 +89,12 @@ This module is used for blending two images . ``` where `options` is an object with the following properties: -* offset: step of image with which current image is to be blended(Two steps back is -2, three steps back is -3 etc; default -2) -* func: function used to blend two images (default : function(r1, g1, b1, a1, r2, g2, b2, a2) { return [ r1, g2, b2, a2 ] }) +* offset: step of image with which current image is to be blended(Two steps back is -2, three steps back is -3 etc; default -2) +* blendMode: Blending mode to use for blending two images by default it uses the given function +* func: function used to blend two images (default : function(r1, g1, b1, a1, r2, g2, b2, a2) { return [ r1, g2, b2, a2 ] }) + +[More info for different blend modes can be found here](http://docs.gimp.org/en/gimp-concepts-layer-modes.html) + ## Blob Analysis diff --git a/src/modules/Blend/Module.js b/src/modules/Blend/Module.js index 2d298f6c..35cadfcd 100644 --- a/src/modules/Blend/Module.js +++ b/src/modules/Blend/Module.js @@ -4,6 +4,7 @@ module.exports = function Blend(options, UI, util) { options.func = options.blend || defaults.blend; options.offset = options.offset || defaults.offset; + options.blendMode = options.blendMode || defaults.blendMode; var output; @@ -29,22 +30,79 @@ module.exports = function Blend(options, UI, util) { callback(); } + // see http://docs.gimp.org/en/gimp-concepts-layer-modes.html for other blend modes + + const multiply_mode = function (i, m) { + return ~~( (i * m) / 255 ); + }; + const divide_mode = function (i, m) { + return ~~( (256 * i) / (m + 1) ); + }; + + const overlay_mode = function (i, m) { + return ~~( (i / 255) * (i + ((2 * m) / 255) * (255 - i)) ); + }; + + const screen_mode = function (i, m) { + return ~~( 255 - ((255 - m) * (255 - i)) / 255 ); + }; + + const sof_light_mode = function (i, m) { + var Rs = screen_mode(i, m); + return ~~( ((((255 - i) * m) + Rs) * i) / 255 ); + }; + + const color_dodge = function (i, m) { + return ~~( (256 * i) / (255 - m + 1) ); + }; + + const burn_mode = function (i, m) { + return ~~( 255 - (256 * (255 - i)) / (m + 1)); + }; + + const grain_extract_mode = function (i, m) { + return ~~( i - m + 128 ); + }; + + const grain_merge_mode = function (i, m) { + return ~~( i + m - 128 ); + }; + + getPixels(priorStep.output.src, function(err, pixels) { options.firstImagePixels = pixels; + // Convert to runnable code. if (typeof options.func === 'string') eval('options.func = ' + options.func); + function changePixel(r2, g2, b2, a2, x, y) { // blend! let p = options.firstImagePixels; - return options.func( - r2, g2, b2, a2, - p.get(x, y, 0), - p.get(x, y, 1), - p.get(x, y, 2), - p.get(x, y, 3), - x, - y - ); + let r1 = p.get(x, y, 0), + g1 = p.get(x, y, 1), + b1 = p.get(x, y, 2), + a1 = p.get(x, y, 3); + + const blends = { + 'Color Dodge': () => [color_dodge(r2, r1), color_dodge(g2, g1), color_dodge(b2, b1), 255], + 'Multiply': () => [multiply_mode(r2, r1), multiply_mode(g2, g1), multiply_mode(b2, b1), multiply_mode(a2, a1)], + 'Divide': () => [divide_mode(r2, r1), divide_mode(g2, g1), divide_mode(b2, b1), 255], + 'Overlay': () => [overlay_mode(r2, r1), overlay_mode(g2, g1), overlay_mode(b2, b1), 255], + 'Screen': () => [screen_mode(r2, r1), screen_mode(g2, g1), screen_mode(b2, b1), 255], + 'Soft Light': () => [sof_light_mode(r2, r1), sof_light_mode(g2, g1), sof_light_mode(b2, b1), 255], + 'Color Burn': () => [burn_mode(r2, r1), burn_mode(g2, g1), burn_mode(b2, b1), 255], + 'Grain Extract': () => [grain_extract_mode(r2, r1), grain_extract_mode(g2, g1), grain_extract_mode(b2, b1), 255], + 'Grain Merge': () => [grain_merge_mode(r2, r1), grain_merge_mode(g2, g1), grain_merge_mode(b2, b1), 255] + }; + + if(options.blendMode == 'custom') + return options.func( + r2, g2, b2, a2, r1, g1, b1, a1 + ); + else { + return blends[options.blendMode](); + } + } function output(image, datauri, mimetype, wasmSuccess) { diff --git a/src/modules/Blend/info.json b/src/modules/Blend/info.json index 10560da3..a9a23238 100755 --- a/src/modules/Blend/info.json +++ b/src/modules/Blend/info.json @@ -1,17 +1,34 @@ { "name": "blend", - "description": "Blend two chosen image steps with the given function. Defaults to using the red channel from image 1 and the green and blue and alpha channels of image 2. Easier to use interfaces coming soon!", + "description": "Blend two chosen image steps with the given function. Defaults to using the red channel from image 1 and the green and blue and alpha channels of image 2.", "inputs": { "offset": { "type": "integer", "desc": "Choose which image to blend the current image with. Two steps back is -2, three steps back is -3 etc.", "default": -2 }, + "blendMode": { + "type": "select", + "desc": "Name of the Blend Mode to use", + "default": "custom", + "values": [ + "custom", + "Multiply", + "Divide", + "Overlay", + "Screen", + "Soft Light", + "Color Burn", + "Color Dodge", + "Grain Extract", + "Grain Merge" + ] + }, "blend": { "type": "string", "desc": "Function to use to blend the two images.", "default": "function(r1, g1, b1, a1, r2, g2, b2, a2, x, y) { return [ r2, g2, b2, a2 ] }" } }, - "docs-link":"https://github.com/publiclab/image-sequencer/blob/main/docs/MODULES.md#blend-module" + "docs-link": "https://github.com/publiclab/image-sequencer/blob/main/docs/MODULES.md#blend-module" } diff --git a/test/core/sequencer/image-sequencer.js b/test/core/sequencer/image-sequencer.js index 6f2723d6..ad56be5d 100644 --- a/test/core/sequencer/image-sequencer.js +++ b/test/core/sequencer/image-sequencer.js @@ -192,7 +192,7 @@ test('getStep(offset) returns the step at offset distance relative to current st }); test('toCliString() returns the CLI command for the sequence', function(t) { - t.deepEqual(sequencer.toCliString(), 'sequencer -i [PATH] -s "channel channel channel channel invert brightness average brightness invert blend" -d \'{"channel":"green","brightness":"1","offset":-2}\'', 'works correctly'); + t.deepEqual(sequencer.toCliString(), 'sequencer -i [PATH] -s "channel channel channel channel invert brightness average brightness invert blend" -d \'{"channel":"green","brightness":"1","offset":-2,"blendMode":"custom"}\'', 'works correctly'); t.end(); });