I need to access individual pixel values of the live canvas which has a pattern(shapes) drawn from user input.
like following,
function draw(){
stroke(255);
if (mouseIsPressed == true) {
if(mouseX != old_mX || mouseY != old_mY)
{
obstacle[obstacle.length] = [mouseX, mouseY];
//line(mouseX, mouseY, pmouseX, pmouseY);
old_mX = mouseX;
old_mY = mouseY;
}
}
fill(255);
beginShape();
for(i = 0; i < obstacle.length;i++){
vertex(obstacle[i][0],obstacle[i][1]);
}
endShape();
}
After drawing is done need to access the individual pixel values
function keyTyped() {
if ( key == 'n')
{
obstacle = []
}
if( key == 'd'){
loadPixels();
//rest of the action
updatePixels();
}}
problem is that loadPixels(); does not loading array with correct values, loaded array is more like a random one containing random patten
is there correct way to access the pixels ?
I tried out your code, and it looks like it loads the pixels into pixels[] normally. I think the problem might be your expectation of 'normal'.
According to https://p5js.org/reference/#/p5/pixels:
The first four values (indices 0-3) in the array will be the R, G, B, A values of the pixel at (0, 0). The second four values (indices 4-7) will contain the R, G, B, A values of the pixel at (1, 0).
So, if you run loadPixels() on completely black canvas, you'll get the following array:
console.log(pixels);
-> [0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255...]
0 red, 0 green, 0 blue, 255 alpha in the first pixel, then
0 red, 0 green, 0 blue, 255 alpha in the second pixel, then...
Alpha is a measure of how transparent a pixel is. 255 means it is fully opaque, 0 means it is fully transparent.
Related
I'm trying to figure out a way to punch holes into a thing, but without the hole also going through whatever is in the background already.
the hole is made of a few arbitrary shapes, and is not a simple path I can use to clip.
The hole is only punched through the foreground shapes, and not all the way into the background (the background should stay as-is).
I figured a way to do this with an external context, and then bringing it in.
My questions: is there a way to do it on my default canvas, and avoid the complications that might arise from the external context (extra memory, color differences etc)?
Here's a working (p5.js) example, which is using a new context:
function setup() {
createCanvas(600,600);
background(255, 0, 0);
noStroke();
}
function draw() {
//blue: stuff in the background that should not change
fill ("blue");
rect (20,20,500,500);
//draw on external canvas
pg = createGraphics(600,600);
//yellow+green foreground shapes
pg.fill("green");
pg.rect(100, 100, 200, 200);
pg.fill("yellow");
pg.rect(80, 80, 100, 300);
//punch a hole in the shapes
pg.fill(0, 0, 255);
pg.blendMode(REMOVE);
pg.circle(140, 140, 150);
pg.circle(180, 180, 150);
//bring in the external canvas with punched shapes
image(pg, 0, 0);
noLoop();
}
html,
body {
margin: 0;
padding: 0;
}
canvas {
display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.js"></script>
There is no easy or built in way to do this without the technique you've already discovered. The only alternative would be to implement boolean geometry operations like subtraction and intersection on arbitrary shapes and splines. That would allow you to make arbitrary bezier splines that represent the composites of multiple complex shapes and then draw those directly. This approach would have different behavior with regards to stroke than the removal approach.
Just FYI, there are also a pair of methods in p5js erase() and noErase() which have a similar behavior to the blendMode(REMOVE) approach. I don't think there's any technical benefit, but it might be more idiomatic to use them rather than blend mode.
I agree, as Paul(+1) mentions as well, using multiple p5.Graphics instances (external contexts as you call them) is the most straight forward/readable method.
You could explicitly uses p5.Image and mask(), however there are few more operations involved and the could would be a little less readable. Here's an example:
function setup() {
createCanvas(600,600);
background(255, 0, 0);
noStroke();
}
function draw() {
//blue: stuff in the background that should not change
fill ("blue");
rect (20,20,500,500);
//draw on external canvas
pg = createGraphics(600,600);
//yellow+green foreground shapes
pg.fill("green");
pg.rect(100, 100, 200, 200);
pg.fill("yellow");
pg.rect(80, 80, 100, 300);
//punch a hole in the shapes
let msk = createGraphics(600, 600);
msk.background(0);
msk.erase();
msk.noStroke();
msk.circle(140, 140, 150);
msk.circle(180, 180, 150);
let mskImage = msk.get();
pgImage = pg.get();
pgImage.mask(mskImage);
image(pgImage, 0, 0);
noLoop();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.min.js"></script>
A (very) hacky workaround would be to do the same thing with one canvas.
This would leave the areas inside the circles completely transparent so make them appear blue, simply make the background element behind the blue:
function setup() {
createCanvas(600,600);
background(255, 0, 0);
noStroke();
}
function draw() {
//blue: stuff in the background that should not change
fill ("blue");
rect (20,20,500,500);
//draw on external canvas
// pg = createGraphics(600,600);
//yellow+green foreground shapes
fill("green");
rect(100, 100, 200, 200);
fill("yellow");
rect(80, 80, 100, 300);
//punch a hole in the shapes
fill(0, 0, 255);
blendMode(REMOVE);
circle(140, 140, 150);
circle(180, 180, 150);
//bring in the external canvas with punched shapes
// image(pg, 0, 0);
noLoop();
}
body{
/* make the HTML background match the canvas blue */
background-color: #00F;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.min.js"></script>
This might not be flexible enough though.
Now, assuming your foreground is made of the yellow and green shapes and the background is blue, another option would be manually accessing the pixels[] array and updating pixel values. In your example the masks are circular so you could check if:
the distance between the current pixel and the circle's centre is smaller than the circle's radius: this means the pixel is inside the circle
also, if the colour inside the circle is a foreground colour (e.g. green or yellow in your case)
If both conditions match then you could replace this pixel with a background colour (blue in your case)
Here's an example of that:
function setup() {
createCanvas(600,600);
pixelDensity(1);
background(255, 0, 0);
noStroke();
}
function draw() {
//blue: stuff in the background that should not change
fill ("blue");
rect (20,20,500,500);
//draw on external canvas
//yellow+green foreground shapes
fill("green");
rect(100, 100, 200, 200);
fill("yellow");
rect(80, 80, 100, 300);
//punch a hole in the shapes
fill(0, 0, 255);
// make pixels available for reading
loadPixels();
// apply each circle "mask" / bg color replacement
// yellow , green , bg blue to replace fg with
circleMask(140, 140, 150, [255, 255, 0], [0, 0x80, 0], [0, 0, 255]);
circleMask(180, 180, 150, [255, 255, 0], [0, 0x80, 0], [0, 0, 255]);
// once all "masks" are applied,
updatePixels();
noLoop();
}
function circleMask(x, y, radius, fg1, fg2, bg){
// total number of pixels
let np = width * height;
let np4 = np*4;
//for each pixel (i = canvas pixel index (taking r,g,b,a order into account)
// id4 is a quarter of "i"
for(let i = 0, id4 =0 ; i < np4; i+=4, id4++){
// compute x from pixel index
let px = id4 % width;
// compute y from pixel index
let py = id4 / width;
// if we're within the circle
if(dist(px, py, x, y) < radius / 2){
// if we've found foreground colours to make transparent
// ([0][1][2] = r, g, b)
if((pixels[i] == fg1[0] || pixels[i] == fg2[0]) &&
(pixels[i+1] == fg1[1] || pixels[i+1] == fg2[1]) &&
(pixels[i+2] == fg1[2] || pixels[i+2] == fg2[2])){
// "mask" => replace fg colour matching pixel with bg pixel
pixels[i] = bg[0];
pixels[i+1] = bg[1];
pixels[i+2] = bg[2];
}
}
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.min.js"></script>
There are a few things to notice here.
pixels[] is faster than set(x, y, clr) but it means you need to remember a few details:
call loadPixels() before accessing pixels[] to read/populate the array
make all the pixels changes required (in this case the circle "masks" / pixels inside circle colour replacement)
call updatePixels() after pixels[] have been updated
Also notices it takes a bit of time to execute.
There could be a few speed improvements such as only iterating over the pixels inside the bounding box of the circle before checking distance and checking squared distance instead of dist(), however this would also make the code less readable.
How can we set different colors for different objects detected using YOLO. Now everything detected are displaying in green rectangle.
function draw() {
image(video, 0, 0, width, height); // Displaying image on a canvas
for (let i = 0; i < objects.length; i++) //Iterating through all objects
{
noStroke();
fill(0, 255, 0); //Color of text
text(objects[i].label, objects[i].x * width, objects[i].y * height - 5);
//Displaying the label
noFill();
strokeWeight(4);
stroke(0, 255, 0); //Defining stroke for rectangular outline
rect(objects[i].x * width, objects[i].y * height, objects[i].w * width,
objects[i].h * height);
}
}
stroke() sets a global state, it sets the color used to draw lines and borders around shapes. This state is kept even beyond frames.
This means all objects which are drawn after the call stroke() are drawn with the set color.
If you want to change the color, you've to call stroke() again.
If you want to use different colors the you can define a list of colors and use the modulo operator (%) to get an index of the color list. e.g. use a red, green and blue color:
colorList = [[255, 0, 0], [0, 255, 0], [0, 0, 255]];
function draw() {
// [...]
for (let i = 0; i < objects.length; i++) {
// [...]
let colorI = i % colorList.length;
stroke(...colorList[colorI]);
// [...]
}
}
Situation is basically draw several moving primitive shapes with a basic linear gradient so they overlap on a transparent blank canvas. Where they overlap the alpha channel value changes (ie, they bleed).
Final stage is rounding the alpha for each individual pixel, so it's either 0 or 255 depending on which it's closer to.
With imageData it's easy to do -
var ctxImage = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
var ctxData = ctxImage.data;
for (var i = 0; i < ctxData.length; i += 4) {
ctxData[i + 3] = ctxData[i + 3] > 128 ? 255 : 0;
// or ctxData[i + 3] = Math.round(ctxData[i + 3] / 255) * 255;
}
ctx.putImageData(ctxImage, 0, 0);
As that getImageData is very expensive in CPU time, I was hoping to work out a solution that used globalCompositeOperation, but just can't seem any way to get it to work, any ideas?
There's no alternative way of snapping the alpha to 0 || 255.
Compositing lets either the source or destination pixel survive, but won't snap the alpha as you describe.
Is it possible to get the position of a colour on canvas.
I know you can get the colour at a position like this
context.getImageData( arguments ).data
But I would like to try and find a colour in canvas, so say I would have this colour black.
rgb(0, 0, 0);
I would like to get the position of that colour if it exists on canvas, I've asked Google but I only get the Get colour at position which is the opposite of what I need.
As mentioned already you need to iterate over the pixel buffer.
Here is one way you can do it:
(Online demo here)
function getPositionFromColor(ctx, color) {
var w = ctx.canvas.width,
h = ctx.canvas.height,
data = ctx.getImageData(0, 0, w, h), /// get image data
buffer = data.data, /// and its pixel buffer
len = buffer.length, /// cache length
x, y = 0, p, px; /// for iterating
/// iterating x/y instead of forward to get position the easy way
for(;y < h; y++) {
/// common value for all x
p = y * 4 * w;
for(x = 0; x < w; x++) {
/// next pixel (skipping 4 bytes as each pixel is RGBA bytes)
px = p + x * 4;
/// if red component match check the others
if (buffer[px] === color[0]) {
if (buffer[px + 1] === color[1] &&
buffer[px + 2] === color[2]) {
return [x, y];
}
}
}
}
return null;
}
This will return the x/y position of the first match of the color you give (color = [r, g, b]). If color is not found the function returns null.
(the code can be optimized in various ways by I haven't addressed that here).
You would need to iterate over the data property and check the RGB values. Basically you would iterate in groups of 4, as each pixel is stored as 4 indices, and compare the values accordingly.
In this acrticle, why in the loop is i incremented by 4 instead of by 1?. I tried changong i+=4 to i++ but it doesn't work properly. Could you please tell me what is the reason behind?
function grayScale(context, canvas) {
var imgData = context.getImageData(0, 0, canvas.width, canvas.height);
var pixels = imgData.data;
for (var i = 0, n = pixels.length; i < n; i += 4) {
var grayscale = pixels[i] * .3 + pixels[i+1] * .59 + pixels[i+2] * .11;
pixels[i ] = grayscale; // red
pixels[i+1] = grayscale; // green
pixels[i+2] = grayscale; // blue
//pixels[i+3] is alpha
}
//redraw the image in black & white
context.putImageData(imgData, 0, 0);
}
//add the function call in the imageObj.onload
imageObj.onload = function(){
context.drawImage(imageObj, destX, destY);
grayScale(context, canvas);
};
Look at the actual code, they are using pixels[i] but also pixels[i+1], pixels[i+2] and a commented out pixels[i+3]. These are four values at a time, not just one.
In this context of pixels from an image it is actually very common to increment by four (or three if alpha is completely absent in the data) since the order comes in as RGBA.
If you look at the comments they even point this fact out (stripped of unnecessary parts):
pixels[i ] // red
pixels[i+1] // green
pixels[i+2] // blue
pixels[i+3] // alpha
If you consider the layout in the array it makes a whole lot of sense:
Array: [r0,g0,b0,a0,r1,g1,b1,a1,r2,...etc]
Positions: 0, 1, 2, 3, 4, 5, 6, 7, 8
We need to go from 0 to 4 to 8 if we are to always get red first.
It looks to me like i=red, i+1=green, i+2=blue, and i+3=alpha. So i+4 would be red again. So the for loop iterates across the array by four instead of one to distinguish separate pixels.
Because what happens in the for-loop is modifying four spots in the array which represent the red, green, blue, and alpha channels which make up a single pixel. The for-loop increments by 4 to jump to the next set of red, green, blue, and alpha channels for the next pixel.
Each iteration of the loop operates on a sequence of four items from the collection, starting on the current value of i.
Therefore the beginning of the next sequence is i + 4.
An ImageData object is basically an array of pixels.
In CSS, when you set color : rgba( 255, 40, 30, 1 ); you're setting red, green, blue and alpha(transparency).
That's the order that each pixel is.
But ALL pixels are stored as four colours, back to back in a straight line.
So a 2x2 black and white image looks like this:
var checkerboard = [ 0, 0, 0, 1, 255, 255, 255, 1, 255, 255, 255, 1, 0, 0, 0, 1 ];
That's the 3 rgbs to make black in the top-left pixel (and 1 for 100% visible), followed by white in the top-right, followed by white in the bottom-left, followed by black in the bottom right.
So when you're dealing with code which modifies individual pixels, you're including ALL of the colour/alpha channels which make up 1 pixel.