Skip to content

Commit ec3fd55

Browse files
committed
Make docs clearer, add more warnings and edge case handling
1 parent 8c94309 commit ec3fd55

File tree

4 files changed

+92
-16
lines changed

4 files changed

+92
-16
lines changed

src/image/pixels.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -510,8 +510,8 @@ p5.prototype.filter = function(operation, value) {
510510
* @method get
511511
* @param {Number} x x-coordinate of the pixel
512512
* @param {Number} y y-coordinate of the pixel
513-
* @param {Number} w width
514-
* @param {Number} h height
513+
* @param {Number} w width of the section to be returned
514+
* @param {Number} h height of the section to be returned
515515
* @return {p5.Image} the rectangle <a href="#/p5.Image">p5.Image</a>
516516
* @example
517517
* <div>

src/webgl/p5.Framebuffer.js

Lines changed: 48 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -951,6 +951,12 @@ class Framebuffer {
951951
this.end();
952952
}
953953

954+
/**
955+
* Call this befpre updating <a href="#/p5.Framebuffer/pixels">pixels</a>
956+
* and calling <a href="#/p5.Framebuffer/updatePixels">updatePixels</a>
957+
* to replace the content of the framebuffer with the data in the pixels
958+
* array.
959+
*/
954960
loadPixels() {
955961
const gl = this.gl;
956962
const prevFramebuffer = gl.getParameter(gl.FRAMEBUFFER_BINDING);
@@ -971,7 +977,9 @@ class Framebuffer {
971977
}
972978

973979
/**
974-
* Get a region of pixels, or a single pixel, from the canvas.
980+
* Get a region of pixels from the canvas in the form of a
981+
* <a href="#/p5.Image">p5.Image</a>, or a single pixel as an array of
982+
* numbers.
975983
*
976984
* Returns an array of [R,G,B,A] values for any pixel or grabs a section of
977985
* an image. If the Framebuffer has been set up to not store alpha values, then
@@ -985,8 +993,8 @@ class Framebuffer {
985993
* @method get
986994
* @param {Number} x x-coordinate of the pixel
987995
* @param {Number} y y-coordinate of the pixel
988-
* @param {Number} w width
989-
* @param {Number} h height
996+
* @param {Number} w width of the section to be returned
997+
* @param {Number} h height of the section to be returned
990998
* @return {p5.Image} the rectangle <a href="#/p5.Image">p5.Image</a>
991999
*/
9921000
/**
@@ -1000,15 +1008,20 @@ class Framebuffer {
10001008
* @return {Number[]} color of pixel at x,y in array format [R, G, B, A]
10011009
*/
10021010
get(x, y, w, h) {
1011+
p5._validateParameters('p5.Framebuffer.get', arguments);
10031012
const colorFormat = this._glColorFormat();
10041013
if (x === undefined && y === undefined) {
10051014
x = 0;
10061015
y = 0;
10071016
w = this.width;
10081017
h = this.height;
10091018
} else if (w === undefined && h === undefined) {
1010-
if (x < 0 || y < 0 || w >= this.width || h >= this.height) {
1011-
return [0, 0, 0, 0];
1019+
if (x < 0 || y < 0 || x >= this.width || y >= this.height) {
1020+
console.warn(
1021+
'The x and y values passed to p5.Framebuffer.get are outside of its range and will be clamped.'
1022+
);
1023+
x = this.target.constrain(x, 0, this.width - 1);
1024+
y = this.target.constrain(y, 0, this.height - 1);
10121025
}
10131026

10141027
return readPixelWebGL(
@@ -1021,6 +1034,11 @@ class Framebuffer {
10211034
);
10221035
}
10231036

1037+
x = this.target.constrain(x, 0, this.width - 1);
1038+
y = this.target.constrain(y, 0, this.height - 1);
1039+
w = this.target.constrain(w, 1, this.width - x);
1040+
h = this.target.constrain(h, 1, this.height - y);
1041+
10241042
const rawData = readPixelsWebGL(
10251043
undefined,
10261044
this.gl,
@@ -1039,18 +1057,23 @@ class Framebuffer {
10391057
const fullData = new Uint8ClampedArray(
10401058
w * h * this.density * this.density * 4
10411059
);
1060+
1061+
// Default channels that aren't in the framebuffer (e.g. alpha, if the
1062+
// framebuffer is in RGB mode instead of RGBA) to 255
1063+
fullData.fill(255);
1064+
10421065
const channels = colorFormat.type === this.gl.RGB ? 3 : 4;
10431066
for (let y = 0; y < h * this.density; y++) {
10441067
for (let x = 0; x < w * this.density; x++) {
10451068
for (let channel = 0; channel < 4; channel++) {
10461069
const idx = (y * w * this.density + x) * 4 + channel;
1047-
if (channel >= channels) {
1048-
fullData[idx] = 255;
1049-
} else {
1050-
const prevIdx = channels === 4
1070+
if (channel < channels) {
1071+
// Find the index of this pixel in `rawData`, which might have a
1072+
// different number of channels
1073+
const rawDataIdx = channels === 4
10511074
? idx
10521075
: (y * w * this.density + x) * channels + channel;
1053-
fullData[idx] = rawData[prevIdx];
1076+
fullData[idx] = rawData[rawDataIdx];
10541077
}
10551078
}
10561079
}
@@ -1138,6 +1161,21 @@ class Framebuffer {
11381161
const gl = this.gl;
11391162
this.colorP5Texture.bindTexture();
11401163
const colorFormat = this._glColorFormat();
1164+
1165+
const channels = colorFormat.format === gl.RGBA ? 4 : 3;
1166+
const len =
1167+
this.width * this.height * this.density * this.density * channels;
1168+
const TypedArrayClass = colorFormat.type === gl.UNSIGNED_BYTE
1169+
? Uint8Array
1170+
: Float32Array;
1171+
if (
1172+
!(this.pixels instanceof TypedArrayClass) || this.pixels.length !== len
1173+
) {
1174+
throw new Error(
1175+
'The pixels array has not been set correctly. Please call loadPixels() before updatePixels().'
1176+
);
1177+
}
1178+
11411179
gl.texImage2D(
11421180
gl.TEXTURE_2D,
11431181
0,

src/webgl/p5.RendererGL.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -944,8 +944,8 @@ p5.RendererGL.prototype._getTempFramebuffer = function() {
944944
* @param {Uint8Array|Float32Array|undefined} pixels An existing pixels array to reuse if the size is the same
945945
* @param {WebGLRenderingContext} gl The WebGL context
946946
* @param {WebGLFramebuffer|null} framebuffer The Framebuffer to read
947-
* @param {Number} x The x coordiante to read (factoring in pixel density)
948-
* @param {Number} y The y coordiante to read (factoring in pixel density)
947+
* @param {Number} x The x coordiante to read, premultiplied by pixel density
948+
* @param {Number} y The y coordiante to read, premultiplied by pixel density
949949
* @param {Number} width The width in pixels to be read (factoring in pixel density)
950950
* @param {Number} height The height in pixels to be read (factoring in pixel density)
951951
* @param {GLEnum} format Either RGB or RGBA depending on how many channels to read
@@ -1014,8 +1014,8 @@ export function readPixelsWebGL(
10141014
* @private
10151015
* @param {WebGLRenderingContext} gl The WebGL context
10161016
* @param {WebGLFramebuffer|null} framebuffer The Framebuffer to read
1017-
* @param {Number} x The x coordinate to read (factoring in pixel density)
1018-
* @param {Number} y The y coordinate to read (factoring in pixel density)
1017+
* @param {Number} x The x coordinate to read, premultiplied by pixel density
1018+
* @param {Number} y The y coordinate to read, premultiplied by pixel density
10191019
* @param {GLEnum} format Either RGB or RGBA depending on how many channels to read
10201020
* @param {GLEnum} type The datatype of each channel, e.g. UNSIGNED_BYTE or FLOAT
10211021
* @param {Number|undefined} flipY If provided, the total height with which to flip the y axis about

test/unit/webgl/p5.Framebuffer.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,4 +414,42 @@ suite('p5.Framebuffer', function() {
414414
}
415415
});
416416
});
417+
418+
test(
419+
'loadPixels works in arbitrary order for multiple framebuffers',
420+
function() {
421+
myp5.createCanvas(20, 20, myp5.WEBGL);
422+
const fbo1 = myp5.createFramebuffer();
423+
const fbo2 = myp5.createFramebuffer();
424+
425+
fbo1.loadPixels();
426+
fbo2.loadPixels();
427+
for (let i = 0; i < fbo1.pixels.length; i += 4) {
428+
// Set everything red
429+
fbo1.pixels[i] = 255;
430+
fbo1.pixels[i + 1] = 0;
431+
fbo1.pixels[i + 2] = 0;
432+
fbo1.pixels[i + 3] = 255;
433+
}
434+
for (let i = 0; i < fbo2.pixels.length; i += 4) {
435+
// Set everything blue
436+
fbo2.pixels[i] = 0;
437+
fbo2.pixels[i + 1] = 0;
438+
fbo2.pixels[i + 2] = 255;
439+
fbo2.pixels[i + 3] = 255;
440+
}
441+
fbo2.updatePixels();
442+
fbo1.updatePixels();
443+
444+
myp5.imageMode(myp5.CENTER);
445+
446+
myp5.clear();
447+
myp5.image(fbo1, 0, 0);
448+
assert.deepEqual(myp5.get(0, 0), [255, 0, 0, 255]);
449+
450+
myp5.clear();
451+
myp5.image(fbo2, 0, 0);
452+
assert.deepEqual(myp5.get(0, 0), [0, 0, 255, 255]);
453+
}
454+
);
417455
});

0 commit comments

Comments
 (0)