putImageData(), how to keep old pixels if new pixels are transparent? - javascript

In html5, when you draw to a canvas using putImageData(), if some of the pixels you are drawing are transparent (or semi-transparent), how do you keep old pixels in the canvas unaffected?
example:
var imgData = context.createImageData(30,30);
for(var i=0; i<imgData.data.length; i+=4)
{
imgData.data[i]=255;
imgData.data[i+1]=0;
imgData.data[i+2]=0;
imgData.data[i+3]=255;
if((i/4)%30 > 15)imgData.data[i+3] = 0;
}
context.putImageData(imgData,0,0);
The right half of the 30x30 rect is transparent.
If this is drawn over something on the canvas, pixels behind the right half are removed (or become thransparent). How do I keep them?

You can use getImageData to create a semi-transparent overlay:
create a temporary offscreen canvas
getImageData to get the pixel data from the offscreen canvas
modify the pixels as you desire
putImageData the pixels back on the offscreen canvas
use drawImage to draw the offscreen canvas to the onscreen canvas
Here's example code and a Demo: http://jsfiddle.net/m1erickson/CM7uY/
<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<style>
body{ background-color: ivory; }
canvas{border:1px solid red;}
</style>
<script>
$(function(){
var canvas=document.getElementById("canvas");
var context=canvas.getContext("2d");
// draw an image on the canvas
var img=new Image();
img.onload=start;
img.src="https://dl.dropboxusercontent.com/u/139992952/stack1/landscape1.jpg";
function start(){
canvas.width=img.width;
canvas.height=img.height;
context.drawImage(img,0,0);
// overlay a red gradient
drawSemiTransparentOverlay(canvas.width/2,canvas.height)
}
function drawSemiTransparentOverlay(w,h){
// create a temporary canvas to hold the gradient overlay
var canvas2=document.createElement("canvas");
canvas2.width=w;
canvas2.height=h
var ctx2=canvas2.getContext("2d");
// make gradient using ImageData
var imgData = ctx2.getImageData(0,0,w,h);
var data=imgData.data;
for(var y=0; y<h; y++) {
for(var x=0; x<w; x++) {
var n=((w*y)+x)*4;
data[n]=255;
data[n+1]=0;
data[n+2]=0;
data[n+3]=255;
if(x>w/2){
data[n+3]=255*(1-((x-w/2)/(w/2)));
}
}
}
// put the modified pixels on the temporary canvas
ctx2.putImageData(imgData,0,0);
// draw the temporary gradient canvas on the visible canvas
context.drawImage(canvas2,0,0);
}
}); // end $(function(){});
</script>
</head>
<body>
<canvas id="canvas" width=200 height=200></canvas>
</body>
</html>
Alternatively, you might check out using a linear gradient to do your effect more directly.
http://jsfiddle.net/m1erickson/j6wLR/

Problem
As you know, your statement
if((i/4)%30 > 15)imgData.data[i+3] = 0;
will make pixels on the right half of the image be transparent, so that any other object on the page behind the canvas can be seen through the canvas at that pixel position. However, you are still overwriting the pixel of the canvas itself with context.putImageData, which replaces all of its previous pixels. The transparency that you add will not cause the previous pixels of to show through, because the result of putImageData is not a second set of pixels on top of the previous pixels in the canvas, but rather the replacement of existing pixels.
Solution
I suggest that you begin your code not with createImageData which will begin with a blank set of data, but rather with getImageData which will give you a copy of the existing data to work with. You can then use your conditional statement to avoid overwriting the portion of the image that you wish to preserve. This will also make your function more efficient.
var imgData = context.getImageData(30,30);
for(var i=0; i<imgData.data.length; i+=4)
{
if((i/4)%30 > 15) continue;
imgData.data[i]=255;
imgData.data[i+1]=0;
imgData.data[i+2]=0;
imgData.data[i+3]=255;
}
context.putImageData(imgData,0,0);

Something that tripped me up that may be of use... I had problems with this because I assumed that putImageData() and drawImage() would work in the same way but it seems they don't. putImageData() will overwrite existing pixels with its own transparent data while drawImage() will leave them untouched.
When looking into this I just glanced at the docs for CanvasRenderingContext2D.globalCompositeOperation (should have read more closely), saw that source-over is the default and didn't realise this would not apply to putImageData()
Drawing into a temporary canvas then and using drawImage() to add the temp canvas to the main context was the solution I needed so cheers for that.

I wanted to copy a CRISP, un modified version of the canvas on top of itself. I eventually came up with this solution, which applies.
https://jsfiddle.net/4Le454ak/1/
The copy portion is in this code:
var imageData = canvas.toDataURL(0, 0, w, h);
var tmp = document.createElement('img');
tmp.style.display = 'none'
tmp.src = imageData;
document.body.appendChild(tmp);
ctx.drawImage(tmp, 30, 30);
What's happening:
copy image data from canvas
set image data to a non-displayed <img> (<img> has to be in dom though)
draw that image back onto the canvas
you can delete or reuse the <img> at this point

It is an old question, but I had a similar issue and came up with another solution that fits me better (similar to #popClingwrap's answer, but I'll elaborate a bit more). I have a WebWorker and I want it to copy and paste an svg file multiple times in an existing canvas. If the source of your ImageData is another Canvas, and you want to copy the data to another canvas, there is an easier way than manipulating pixel values in a loop. the ctx.drawImage() function does overlay images respecting transparency and can also take another canvas as source.
So I used Canvg to create a source canvas containing my source image ( For your application this will look different)
const cnv = new OffscreenCanvas(100, 100);
const loadCanvas = async () => {
const v = await Canvg.from(cnv.getContext("2d"), src, preset);
await v.render();
};
For your example this would probably look something like this
var cnv = document.createElement('canvas');
var ctx = cnv.getContext('2d');
cnv.width = 30;
cnv.height = 30;
ctx.putImageData(imgData, 0, 0);
And then you can draw this transparent image on top of an existing image as often as needed with:
ctx.drawImage(cnv, 0, 0);

Related

how to render smothly P5.js in hdpi screen?

Computer with hdpi screen are more and more common and I own one.
I'm building a webpage with a P5.js canvas inside it filling a div.
I have absolutely no problem till this point, but as I have an hdpi screen the number of pixels to render is tremendous and it's very hard to render smoothly.
what I would like to do: render a low-resolution canvas an stretching it to fill all the space. But I have no ideé how to achieve this or even if it's possible.
function setup() {
var canvasDiv = document.getElementById('myCanvas');
var divWidth = canvasDiv.getBoundingClientRect().width;
var divHeight = canvasDiv.getBoundingClientRect().height;
var sketchCanvas = createCanvas(divWidth,divHeight);
sketchCanvas.parent("myCanvas");
background(0)
}
function windowResized() {
var canvasDiv = document.getElementById('myCanvas');
var divWidth = canvasDiv.getBoundingClientRect().width;
var divHeight = canvasDiv.getBoundingClientRect().height;
resizeCanvas(divWidth, divHeight);
background(0)
}
function draw(){
}
Step one: Create an off-screen buffer using the createGraphics() function. More info can be found in the reference. Give it whatever low-res size you want.
Step two: Draw your scene to that off-screen buffer. Use its width and height to draw stuff to scale.
Step three: Draw the off-screen buffer to the screen, using the image() function, which takes parameters for the size to draw. Use the size of the canvas as the arguments. Again, more info can be found in the reference.
You'll still be drawing the same amount of pixels, but that's how you'd draw to a small buffer and resize it to match the canvas size.
If you're still having trouble, please post a MCVE in a new question post, and we'll go from there. Good luck.

Fabric JS pixel by pixel manipulation

I have overlay image with rectangle transparent hole. Beneath is scalable and draggable image. How to cut only visible part of image ?
How to determine size and position of transparent rectangle ? Is it possible to do pixel by pixel alpha channel search only on overlay image ?
Any other ideas ?
EDIT:
Solution to another quoted problem is usefull, although it works only on whole canvas not on individual items like background, overlay or added images or shapes. Is it possible to read pixel values on individual fabric elements ?
I use overlay image as external png files.
The FabricJS API does not include a method to fetch the pixel values of an image.
You will have to draw your overlay image onto an html5 canvas and use getImageData to fetch the pixel data from that canvas.
getImageData().data contains the red, green, blue & alpha information for each pixel on the canvas.
You can test each pixel's alpha value and determine the minimum & maximum boundary of the transparent rectangle.
Here's example code and a Demo:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
var img=new Image();
img.crossOrigin='anonymous';
img.onload=start;
img.src="http://masterblocks.co.in/static/img/logo.png";
function start(){
cw=canvas.width=img.width;
ch=canvas.height=img.height
ctx.drawImage(img,0,0);
// clear a test rectangle
ctx.clearRect(100,100,100,75);
// get clear rect bounds
var bounds=findCutoutBounds();
// test: stroke the bounds
ctx.lineWidth=2;
ctx.strokeStyle='red';
ctx.strokeRect(bounds.x,bounds.y,bounds.width,bounds.height);
}
// Get the imageData of that canvas
function findCutoutBounds(){
var minX=1000000;
var minY=1000000;
var maxX=-1;
var maxY=-1;
var data=ctx.getImageData(0,0,canvas.width,canvas.height).data;
for(var y=0;y<ch;y++){
for(var x=0;x<cw;x++){
var n=(y*cw+x)*4;
if(data[n+3]<5){
if(y<minY){minY=y;}
if(y>maxY){maxY=y;}
if(x<minX){minX=x;}
if(x>maxX){maxX=x;}
}
}}
return({x:minX,y:minY,width:maxX-minX,height:maxY-minY});
}
body{ background-color: ivory; }
#canvas{border:1px solid red;}
<h4>The bounding box of the transparent rectangle is stroked in red</h4>
<canvas id="canvas" width=300 height=300></canvas>

How overlay image over a cup using html5 canvas

I am new to HTML5 canvas. I have a image of a cup, I am rendered that in canvas.
This is image of cup :
Now I am trying render another image (My photo that is in normal rectangular size) in upload your design area of this image. How can I render this image which looks like that image on cup?
I want to get the final image like this :
I am uses canvas element to upload the image.
<!DOCTYPE html>
<html>
<body>
<canvas id="myCanvas" width="400" height="400" style="border:5px solid #c3c3c3;">
Your browser does not support the HTML5 canvas tag.
</canvas>
<script src="js/test.js" type="text/javascript"></script>
</body>
</html>
JS
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
var imageObj = new Image();
var x = 0;
var y = 0;
var width = 290;
var height = 297;
imageObj.onload = function() {
context.drawImage(imageObj, x, y);
};
imageObj.src = 'images/cup.jpg';
You want your second image to "warp" and appear as if it's wrapped around the cup.
You cannot warp your second image into a curved image using "out-of-the-box" context 2d
Using html Canvas 2d Context, you can only do quadrilateral skewing. Therefore, after skewing an image each opposing side will always be parallel.
Therefore, you cannot get your image to warp into a curved image using "out-of-the-box" context 2d.
A few workarounds...You can use an offscreen temporary canvas to "warp" your second image into a curve. Then you can draw that curved image on top of the cup image using context.drawImage. Here are 2 alternatives that let you "fake" curvature of an image.
Alternative #1: Texture Mapping
You can use texture mapping to apply perspective curvature to your second image:
http://archive.gamedev.net/archive/reference/articles/article852.html
Alternative #2: Vertically slice and stretch
You can vertically slice your second image to create perspective curvature. You can use the resizing capability of context.drawImage to "stretch" pixels into your curved shape like in this previously Stackoverflow answer: How to make rooftext effect and valley text effect in HTML5 (or Fabric.js)
jsfiddle.net/AbdiasSoftware/e8hZy/

Fill image with a repeatable texture

Would it be possible to fill a png with transparency with a pattern (a repeatable texture)?
Here's a quick example of loading an image onto the canvas, just not sure how to fill it with a pattern, if that isn't possible then would there be a way to extract a path from the png?
<script>
var c = document.getElementById("a");
var ctx = c.getContext("2d");
var test= new Image();
test.src = "images/test.png";
test.onload = function() {
ctx.drawImage(test, 0, 0);
};
</script>
<body>
<canvas id="a"></canvas>
</body>
I've also created a jsfiddle with an actual loaded png
This is the effect I'm looking to achieve
Update
working example based on Simon Sarris' answer
http://jsfiddle.net/sergeh/G8egW/6/
First, draw the image to Canvas.
Then do globalCompositeOperation = 'source-in';
Then draw the pattern. It will only exist where the image was.
http://jsfiddle.net/G8egW/2/
If you had stuff already on the canvas before this time, you'll need to do the above operations on an in-memory canvas and then draw that canvas to your normal canvas. Like this:
http://jsfiddle.net/G8egW/5/
(notice the difference in the grid)

Problem with getImageData function

So, I'm using this function getImageData on the "context" variable that I've made inside of the <script> part, and when I do something such as draw a rectangle then do ctx.getImageData.data[0] it will show the red value of that rectangle that I drew on the canvas. But, when I import an image and draw it onto the canvas, and try to use the getImageData.data[0] all I get is 0, which makes no sense, I'm not sure why it is not reading the image correctly. I've tried tutorials on this but they're all vague and only have segments written together.
So, when I draw the rectangle, its color value comes out just fine, but again, when I draw the image, even without drawing the rectangle on the canvas, I never get the value of that particular pixel on the image.
Can someone help me? Here's my currrent code:
<html>
<head>
<title>
Color Test :)
</title>
</head>
<body>
<canvas id = "ColorTest" width = "500" height = "500">
please don't use shitty browsers :)
</canvas>
<script>
//netscape.security.PrivilegeManage…
var canvas = document.getElementById("ColorTest")
, ctx = canvas.getContext("2d")
, image = new Image()
image.onload = function(){
ctx.drawImage(image,0,0)
}
image.src = 'Pikachu.gif'
ctx.fillStyle = "rgb(123,40,170)"
ctx.fillRect(0,0,100,100)
var imagedata = ctx.getImageData(0,0,100,100)
, spot = 0
while(imagedata.data[spot] == 0){
spot++
}
console.log(imagedata.data[0])
</script>
</body>
</html>
Does the following alert anything sensible?
image.onload = function() {
ctx.drawImage(image, 0, 0);
var id = ctx.getImageData(0,0,canvas.width,canvas.height);
alert([id.data[0], id.data[1], id.data[2], id.data[3]].join(", "));
}
It could be that the image is transparent. Try with a single color non-transperent image.
What browser are you using? Using getImageData when loading an image from another domain is not allowed (security issue), and if I remember correctly there is some browsers that have problems determining the domain when running scripts localy

Categories

Resources