From 4828bfa06e3dacb8fafb15369288928e15172a3d Mon Sep 17 00:00:00 2001 From: Yifan Mai Date: Mon, 27 Dec 2021 17:06:49 -0800 Subject: [PATCH 1/2] Animated GIF masking resolves #5174 When the mask method is called on an Image that contains an animated GIF, the mask is applied to all of its frames. --- src/image/p5.Image.js | 31 ++++++++++++++--- test/unit/image/p5.Image.js | 66 +++++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 5 deletions(-) diff --git a/src/image/p5.Image.js b/src/image/p5.Image.js index c78fa7959c..53b77750f6 100644 --- a/src/image/p5.Image.js +++ b/src/image/p5.Image.js @@ -629,10 +629,6 @@ p5.Image.prototype.copy = function(...args) { * http://blogs.adobe.com/webplatform/2013/01/28/blending-features-in-canvas/ */ // TODO: - Accept an array of alpha values. -// - Use other channels of an image. p5 uses the -// blue channel (which feels kind of arbitrary). Note: at the -// moment this method does not match native processing's original -// functionality exactly. p5.Image.prototype.mask = function(p5Image) { if (p5Image === undefined) { p5Image = this; @@ -657,7 +653,32 @@ p5.Image.prototype.mask = function(p5Image) { ]; this.drawingContext.globalCompositeOperation = 'destination-in'; - p5.Image.prototype.copy.apply(this, copyArgs); + if (this.gifProperties) { + const prevFrameData = this.drawingContext.getImageData( + 0, + 0, + this.width, + this.height + ); + for (let i = 0; i < this.gifProperties.frames.length; i++) { + this.drawingContext.clearRect(0, 0, this.width, this.height); + this.drawingContext.putImageData( + this.gifProperties.frames[i].image, + 0, + 0 + ); + p5.Image.prototype.copy.apply(this, copyArgs); + this.gifProperties.frames[i].image = this.drawingContext.getImageData( + 0, + 0, + this.width, + this.height + ); + } + this.drawingContext.putImageData(prevFrameData, 0, 0); + } else { + p5.Image.prototype.copy.apply(this, copyArgs); + } this.drawingContext.globalCompositeOperation = currBlend; this.setModified(true); }; diff --git a/test/unit/image/p5.Image.js b/test/unit/image/p5.Image.js index eb83102d85..8b30802d15 100644 --- a/test/unit/image/p5.Image.js +++ b/test/unit/image/p5.Image.js @@ -49,4 +49,70 @@ suite('p5.Image', function() { assert.strictEqual(img.height, 30); }); }); + + suite('p5.Image.prototype.mask', function() { + test('it should mask the image', function() { + let img = myp5.createImage(10, 10); + img.loadPixels(); + for (let i = 0; i < img.height; i++) { + for (let j = 0; j < img.width; j++) { + let alpha = i < 5 ? 255 : 0; + img.set(i, j, myp5.color(0, 0, 0, alpha)); + } + } + img.updatePixels(); + + let mask = myp5.createImage(10, 10); + mask.loadPixels(); + for (let i = 0; i < mask.width; i++) { + for (let j = 0; j < mask.height; j++) { + let alpha = j < 5 ? 255 : 0; + mask.set(i, j, myp5.color(0, 0, 0, alpha)); + } + } + mask.updatePixels(); + + img.mask(mask); + img.loadPixels(); + for (let i = 0; i < img.width; i++) { + for (let j = 0; j < img.height; j++) { + let alpha = i < 5 && j < 5 ? 255 : 0; + assert.strictEqual(img.get(i, j)[3], alpha); + } + } + }); + + test('it should mask the animated gif image', function() { + const imagePath = 'unit/assets/nyan_cat.gif'; + return new Promise(function(resolve, reject) { + myp5.loadImage(imagePath, resolve, reject); + }).then(function(img) { + let mask = myp5.createImage(img.width, img.height); + mask.loadPixels(); + for (let i = 0; i < mask.width; i++) { + for (let j = 0; j < mask.height; j++) { + const alpha = j < img.height < 2 ? 255 : 0; + mask.set(i, j, myp5.color(0, 0, 0, alpha)); + } + } + mask.updatePixels(); + + img.mask(mask); + for ( + frameIndex = 0; + frameIndex < img.gifProperties.numFrames; + frameIndex++ + ) { + const frameData = img.gifProperties.frames[frameIndex].image.data; + for (let i = 0; i < img.width; i++) { + for (let j = 0; j < img.height; j++) { + const index = 4 * (i + j * img.width) + 3; + const alpha = j < img.height < 2 ? 255 : 0; + assert.strictEqual(frameData[index], alpha); + } + } + } + }); + }); + }); }); From fe1c59dfaf12bab96eb968615544c6372e5d31d0 Mon Sep 17 00:00:00 2001 From: Yifan Mai Date: Sat, 30 Jul 2022 16:54:43 -0700 Subject: [PATCH 2/2] Test mask on current gif frame --- src/image/p5.Image.js | 13 +++++-------- test/unit/image/p5.Image.js | 11 +++++++++-- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/image/p5.Image.js b/src/image/p5.Image.js index 53b77750f6..6bbb9fdf11 100644 --- a/src/image/p5.Image.js +++ b/src/image/p5.Image.js @@ -654,14 +654,7 @@ p5.Image.prototype.mask = function(p5Image) { this.drawingContext.globalCompositeOperation = 'destination-in'; if (this.gifProperties) { - const prevFrameData = this.drawingContext.getImageData( - 0, - 0, - this.width, - this.height - ); for (let i = 0; i < this.gifProperties.frames.length; i++) { - this.drawingContext.clearRect(0, 0, this.width, this.height); this.drawingContext.putImageData( this.gifProperties.frames[i].image, 0, @@ -675,7 +668,11 @@ p5.Image.prototype.mask = function(p5Image) { this.height ); } - this.drawingContext.putImageData(prevFrameData, 0, 0); + this.drawingContext.putImageData( + this.gifProperties.frames[this.gifProperties.displayIndex].image, + 0, + 0 + ); } else { p5.Image.prototype.copy.apply(this, copyArgs); } diff --git a/test/unit/image/p5.Image.js b/test/unit/image/p5.Image.js index 8b30802d15..2600be63ff 100644 --- a/test/unit/image/p5.Image.js +++ b/test/unit/image/p5.Image.js @@ -91,13 +91,20 @@ suite('p5.Image', function() { mask.loadPixels(); for (let i = 0; i < mask.width; i++) { for (let j = 0; j < mask.height; j++) { - const alpha = j < img.height < 2 ? 255 : 0; + const alpha = j < img.height / 2 ? 255 : 0; mask.set(i, j, myp5.color(0, 0, 0, alpha)); } } mask.updatePixels(); img.mask(mask); + img.loadPixels(); + for (let i = 0; i < img.width; i++) { + for (let j = 0; j < img.height; j++) { + const alpha = j < img.height / 2 ? 255 : 0; + assert.strictEqual(img.get(i, j)[3], alpha); + } + } for ( frameIndex = 0; frameIndex < img.gifProperties.numFrames; @@ -107,7 +114,7 @@ suite('p5.Image', function() { for (let i = 0; i < img.width; i++) { for (let j = 0; j < img.height; j++) { const index = 4 * (i + j * img.width) + 3; - const alpha = j < img.height < 2 ? 255 : 0; + const alpha = j < img.height / 2 ? 255 : 0; assert.strictEqual(frameData[index], alpha); } }