From 56f5239773b6f0144c7ca34bfe5e0e9c2e60d74b Mon Sep 17 00:00:00 2001 From: Prakash Meena Date: Sat, 12 Sep 2026 03:24:35 +0530 Subject: [PATCH] fix(image): account for destination pixel density in p5.Image copy and blend --- src/image/p5.Image.js | 9 +++--- test/unit/image/p5.Image.js | 62 +++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 4 deletions(-) diff --git a/src/image/p5.Image.js b/src/image/p5.Image.js index 4823ad8bf4..9cbc1c48a5 100644 --- a/src/image/p5.Image.js +++ b/src/image/p5.Image.js @@ -918,6 +918,7 @@ class Image { _copyHelper(dstImage, srcImage, sx, sy, sw, sh, dx, dy, dw, dh) { const s = srcImage.canvas.width / srcImage.width; + const d = dstImage.canvas.width / dstImage.width; // adjust coord system for 3D when renderer // ie top-left = -width/2, -height/2 let sxMod = 0; @@ -951,10 +952,10 @@ class Image { s * (sy + syMod), s * sw, s * sh, - dx, - dy, - dw, - dh + d * dx, + d * dy, + d * dw, + d * dh ); } } diff --git a/test/unit/image/p5.Image.js b/test/unit/image/p5.Image.js index 9962a2dfe6..edc3a3e44d 100644 --- a/test/unit/image/p5.Image.js +++ b/test/unit/image/p5.Image.js @@ -51,6 +51,68 @@ suite('p5.Image', function () { }); }); + suite('p5.Image.prototype.copy', function () { + test('it copies correctly to destination with pixel density > 1', function () { + let src = myp5.createImage(50, 50); + src.loadPixels(); + for (let i = 0; i < src.pixels.length; i += 4) { + src.pixels[i] = 255; + src.pixels[i + 3] = 255; + } + src.updatePixels(); + + let dst = myp5.createImage(100, 100); + dst.pixelDensity(2); + // dst.width is 50, dst.height is 50, dst.canvas is 100x100 + dst.copy(src, 0, 0, 50, 50, 0, 0, 50, 50); + + // (35, 35) maps to physical (70, 70), which without the fix was outside + // the unscaled 50x50 copy region on the 100x100 canvas. + let col = dst.get(35, 35); + assert.strictEqual(col[0], 255, 'red channel at (35, 35)'); + assert.strictEqual(col[3], 255, 'alpha channel at (35, 35)'); + }); + + test('it copies correctly when both source and destination have pixel density > 1', function () { + let src = myp5.createImage(50, 50); + src.pixelDensity(2); + src.loadPixels(); + for (let i = 0; i < src.pixels.length; i += 4) { + src.pixels[i] = 255; + src.pixels[i + 3] = 255; + } + src.updatePixels(); + + let dst = myp5.createImage(100, 100); + dst.pixelDensity(2); + dst.copy(src, 0, 0, 25, 25, 0, 0, 50, 50); + + let col = dst.get(35, 35); + assert.strictEqual(col[0], 255, 'red channel at (35, 35)'); + assert.strictEqual(col[3], 255, 'alpha channel at (35, 35)'); + }); + }); + + suite('p5.Image.prototype.blend', function () { + test('it blends correctly to destination with pixel density > 1', function () { + let src = myp5.createImage(50, 50); + src.loadPixels(); + for (let i = 0; i < src.pixels.length; i += 4) { + src.pixels[i] = 255; + src.pixels[i + 3] = 255; + } + src.updatePixels(); + + let dst = myp5.createImage(100, 100); + dst.pixelDensity(2); + dst.blend(src, 0, 0, 50, 50, 0, 0, 50, 50, myp5.BLEND); + + let col = dst.get(35, 35); + assert.strictEqual(col[0], 255, 'red channel at (35, 35)'); + assert.strictEqual(col[3], 255, 'alpha channel at (35, 35)'); + }); + }); + suite.todo('p5.Image.prototype.mask', function () { for (const density of [1, 2]) { test(`it should mask the image at pixel density ${density}`, function () {