canvas context.drawImage differences across browsers? - javascript

I'm facing a really bizarre issue when trying to scale and crop an image using the HTML5 canvas API across browsers.
Specifically, I'm working with trying to find the center of an image and crop a square based on its width and height using these parameters as specified by the context API
context.drawImage(img,sx,sy,swidth,sheight,x,y,width,height);
Here's an example of the code I'm using:
<!DOCTYPE html>
<html>
<body>
<p>Image to use:</p>
<img id="scream" width="220" height="277" src="img_the_scream.jpg" alt="The Scream">
<p>Canvas:</p>
<canvas id="myCanvas" width="150" height="150" style="border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas tag.
</canvas>
<script>
window.onload = function() {
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
var img = document.getElementById("scream");
ctx.drawImage(img, 0, 25, 220, 277, 0, 0, 150, 188);
}
</script>
</body>
</html>
You can try the demo here:
http://www.w3schools.com/tags/tryit.asp?filename=tryhtml5_canvas_drawimage2
The code does not display anything on the canvas in Safari, however it correctly scales and crops the images in Chrome.
I would upload images but stackoverflow has an arbitrary rule that doesn't allow me to post images until I have a higher reputation!
Any thoughts here would be appreciated!

The problem in Safari could be sensitivity to source size, the region you specify as source versus available bitmap.
For example, the source in the example says:
// source: x, y , w , h
ctx.drawImage(img, 0, 25, 220, 277, ...
This will mean the bitmap has to be 25+277 pixels in height which of course is not the case as the input image is only 277 pixels in height.
Try adjusting source region to be inside the image, in this case reducing height with y offset:
ctx.drawImage(img, 0, 25, 220, 277-25, 0, 0, 150, 188);
To crop always make sure the source region is inside the image, f.ex. if you want to crop 10 pixels in from all sides:
var iw = img.naturalWidth;
var ih = img.naturalHeight;
var crop = 10;
ctx.drawImage(img, crop, crop, iw - crop*2, ih - crop*2, 0, 0, 150, 188);

Related

Canvas drawImage using the position of div on top of image

I have a div that is on top of the image. I have to get the image content which is inside the overlaying div and draw using canvas.
The problem I am facing is, I am not able to draw the correct image. I have to programmatically get the correct image below the div.
The image below shows the comparison of the original image and drawn image in canvas.
Below is my code which drawsImage by taking the coordinates of the div:
DrawImage() {
var canvas: any = document.getElementById("myCanvas");
var canvasRef = canvas.getBoundingClientRect();
var ctx = canvas.getContext("2d");
var imagemarker = document.getElementById("imagemarker");
var markerRef = imagemarker.getBoundingClientRect();
var img = document.getElementById("myimage");
ctx.drawImage(
img,
markerRef.left,
markerRef.top,
markerRef.width,
markerRef.height,
0,
0,
canvasRef.width,
canvasRef.height
);
}
And the html code to display image and canvas element:
<button (click)="DrawImage()">Draw</button>
<div id="imagemarkercontainer">
<div id="imagemarker"></div>
</div>
<img id="myimage" src="https://imgur.com/NWQo2xk.jpeg" width="300px"
height="250px">
<br>
<canvas id="myCanvas" width="300px" height="250px" style="border:1px
solid #d3d3d3;"></canvas>
I have attached a stackblitz example that shows the problem. https://stackblitz.com/edit/angular-4untgz?file=src%2Fapp%2Fapp.component.ts
How can I achieve this?
You need to take care of img bounding rect and also natural width & height of image.
As you are setting image width & height, get naturalWidth and calculate proportional width as below
var imgRef = img.getBoundingClientRect();
var nWidth = img.naturalWidth;
var nHeight = img.naturalHeight;
ctx.drawImage(
img,
( markerRef.left - imgRef.left) * nWidth /img.width ,
( markerRef.top -imgRef.top) * nHeight / img.height ,
markerRef.width * nWidth /img.width,
markerRef.height * nHeight / img.height ,
0,
0,
canvasRef.width,
canvasRef.height
);
Here is working code

HTML/Javascript Canvas drawImg Function

I am trying to mirror a section of an image, into a below canvas, of exactly the same size, based on fixed values. For example, present it a picture of a map, and mirror below in the same position and the same size a building on that original map image. However, for some unknown reason, it resizes the mirrored section every time? The only botch fix for this is to apply a multiplier to all "d" variables in the draw image function, as seen here: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/drawImage
Any ideas as to why this is?
Code (minus the multiplier, multipler avaliable if needed):
<!DOCTYPE html>
<html>
<body>
<img id="scream" src="image1.jpg" alt="The Scream" width="100%" height="100%">
<!--<canvas id="myCanvas" style="border:1px solid #d3d3d3;">-->
Your browser does not support the HTML5 canvas tag.
<script>
document.getElementById("scream").onload = function() {
var c = document.createElement('canvas');
var imgData = document.getElementById("scream");
c.width = imgData.width;
c.height = imgData.height;
var body = document.getElementsByTagName("body")[0];
body.appendChild(c);
var ctx = c.getContext("2d");
var img = document.getElementById("scream");
console.log(img);
//ctx.drawImage(img, 0, 0);
//ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
ctx.drawImage(img, 200, 200, 100, 143, 200, 200, 100*1.87, 143*1.87);
//console.log(ctx);
};
</script>
<p><strong>Note:</strong> The canvas tag is not supported in Internet
Explorer 8 and earlier versions.</p>
</body>
</html>
In order to mirror the image you need to translate the context ctx.translate(0, this.height) and then scale it ctx.scale(1,-1);. However on resize the canvas will stay the initial size while the image will adapt to the window size. If you need the canvas to adapt as well you will need to recalculate everything on resize.
var imgData = document.getElementById("scream");
imgData.onload = function() {
var c = document.createElement('canvas');
c.width = this.width;
c.height = this.height;
var body = document.getElementsByTagName("body")[0];
body.appendChild(c);
var ctx = c.getContext("2d");
ctx.translate(0, this.height);
ctx.scale(1,-1);
ctx.drawImage(this, 0, 0,this.width,this.height);
};
<img id="scream" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/222579/imgres.jpg" alt="The Scream" width="100%" height="100%">
Next comes an example where the canvas size changes on resize. In this case you need to move all the code that draws the image inside a function to be called on resize.
Quote from MDN:
Since resize events can fire at a high rate, the event handler shouldn't execute computationally expensive operations such as DOM modifications. Instead, it is recommended to throttle the event using requestAnimationFrame, setTimeout or customEvent"
window.onload = function() {
var imgData = document.getElementById("scream");
var c = document.createElement("canvas");
var body = document.getElementsByTagName("body")[0];
body.appendChild(c);
var ctx = c.getContext("2d");
imgData.onload = function() {
init();
};
function init() {
c.width = imgData.width;
c.height = imgData.height;
ctx.translate(0, imgData.height);
ctx.scale(1, -1);
ctx.drawImage(imgData, 0, 0, imgData.width, imgData.height);
}
setTimeout(function() {
init();
addEventListener("resize", init, false);
}, 15);
};
<img id="scream" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/222579/imgres.jpg" alt="The Scream" width="100%" height="100%">
This is because you are using the ImageElement's .width and .height properties. At getting, these should return the computed value of their corresponding attribute, in px.
In the markup, these attributes have both been set to the relative value 100%, which means their computed value will be relative to the ImageElement's parent's size.
So when you ask drawIamge to draw your image using these output size, it will indeed resize / stretch your image. To avoid it, you need to use the original size of the image, not of the ImageElement.
To do so, if all you need is to draw at the same size as the original image, then you can simply call the length 3 version of drawImage(source, x, y), which will use by default the source's original size.
But you'll still need to resize your canvas to the correct size before doing it, and to do this, you need to access the image's original size.
Fortunately, this original size of the image, is available from the ImageElement's naturalWidth and naturalHeight properties.
document.getElementById("scream").onload = function() {
var c = document.createElement('canvas');
var imgData = document.getElementById("scream");
console.log('computed width: ', imgData.width);
console.log('original width: ', imgData.naturalWidth);
c.width = imgData.naturalWidth;
c.height = imgData.naturalHeight;
var body = document.body;
body.appendChild(c);
var ctx = c.getContext("2d");
// the 3 length version is enough here
ctx.drawImage(imgData, 0, 0);
};
<img id="scream" src="https://upload.wikimedia.org/wikipedia/commons/thumb/8/86/Edvard_Munch_-_The_Scream_-_Google_Art_Project.jpg/190px-Edvard_Munch_-_The_Scream_-_Google_Art_Project.jpg" alt="The Scream" width="100%" height="100%">

drawImage - Resize and keep scale

I am trying to resize an image using drawImage but keep the scale. I have this so far...
window.onload = function() {
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
var img = document.getElementById("scream");
ctx.drawImage(img, 0, 0, 600, 428);
}
<p>Image to use:</p>
<img id="scream" width="400" height="300" src="http://lorempixel.com/1280/960/sports/" alt="The Scream">
<p>Canvas:</p>
<canvas id="myCanvas" width="428" height="600" style="border:2px solid black;">
Your browser does not support the HTML5 canvas tag.
</canvas>
I am trying to make the image fill the container to lose the white space at the bottom, if I put the exact dimensions in then it becomes stretched.
Does anyone have an example they can point me in the direction of?
First you need to find the aspect ratios of the image and the canvas. That is done by:
var canvasRatio = canvas.width / canvas.height,
imgRatio = img.width / img.height,
s = 1;
If a ratio is less than 1, then it is portrait, else if it is equal or higher than 1, it is (considered to be) landscape.
Then you need to compare those ratios like so:
//the image is »more portrait«
//to fully cover the canvas
//scale by width
if (imgRatio < canvasRatio) {
s = canvas.width / img.width;
//image has the same, or a »more landscape«
//ratio than the canvas
//to cover scale by height
} else {
s = canvas.height / img.height;
}
//scale the context
ctx.scale(s, s);
cts.drawImage(…);
UPDATE
It is even shorter this way (no ratios, no if):
var s = Math.max(canvas.width/img.width, canvas.height/img.height);
to fit the image use Math.min.

Draw multiple images to canvas and then remove one of them

After getting a Canvas element and its context
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
Let's say I drew 3 images on it.
context.drawImage(image,x,y,100,100);
context.drawImage(image,x+20,y+20,100,100);
context.drawImage(image,x+40,y+40,100,100); //image 3
How do I remove image 3 from the canvas keeping the other two? Won't clearRect remove anything in the drawing area?! If not, why?
Thanks a lot.
It does not a contain any separate element like we see in html when multiple element flow over another with different z-index. Its just like a state. So it is like an array of pixel data (they call it ImageData). You can capture a state.
Just a sample code:
<!DOCTYPE html>
<p>Image to use:</p>
<img id="scream" width="220" height="277" src="img_the_scream.jpg" alt="The Scream">
<table>
<tr>
<td>
<p>Canvas:</p>
<canvas id="myCanvas" width="240" height="297" style="border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas tag.
</canvas>
</td>
<td>
<p>Extra Canvas:</p>
<canvas id="myCanvas2" width="240" height="297" style="border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas tag.
</canvas>
</td>
</table>
<script>
window.onload = function() {
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
var img = document.getElementById("scream");
ctx.drawImage(img, 10, 10);
ctx.drawImage(img, 110, 40);
var c2 = document.getElementById("myCanvas2"); // will copy the sate here
c2.getContext("2d").putImageData(ctx.getImageData(0, 0, 240, 297), 0, 0);
ctx.drawImage(img, 30, 60);
}
</script>
<p><strong>Note:</strong> The canvas tag is not supported in Internet Explorer 8 and earlier versions.</p>
I just modified the sample code of w3schools tryIt (http://www.w3schools.com/tags/tryit.asp?filename=tryhtml5_canvas_drawimage).
You can use ctx.save() to keep a restorable state while making changes using drawImage(). And to restore to that very last saves state, you can use ctx.restore().
You can either simply clear and redraw the images you require, if you're doing this via a loop.
Or, you can simply draw over the image you no longer require.
E.g.
<img id="img1" src="GITS01.jpg" style="display:none;">
<img id="img2" src="Matrix01.jpg" style="display:none;">
<img id="img3" src="silent-hills2.jpg" style="display:none;">
<canvas id="canvas"></canvas>
<script>
var ctx = document.getElementById('canvas').getContext('2d');
ctx.canvas.width = window.innerWidth * 0.8;
ctx.canvas.height = window.innerHeight * 0.8;
var img1 = document.getElementById('img1');
var img2 = document.getElementById('img2');
var img3 = document.getElementById('img3');
// Let's clear the canvas first.
ctx.fillStyle = '#101010';
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
// Draw 3 images, top left, top right and bottom right quadrants.
ctx.drawImage(img1, 0, 0, ctx.canvas.width / 2, ctx.canvas.height / 2);
ctx.drawImage(img2, ctx.canvas.width / 2, 0, ctx.canvas.width / 2, ctx.canvas.height / 2);
ctx.drawImage(img3, 0, ctx.canvas.height / 2, ctx.canvas.width / 2, ctx.canvas.height / 2);
// Now, let's remove that second image in the top right quadrant,
// by simply drawing over it with a blank rectangle.
ctx.fillRect(ctx.canvas.width / 2, 0, ctx.canvas.width / 2, ctx.canvas.height / 2);
</script>
Depending on what else you need to draw, or if there's overlapping elements, then you will need to simply clear the canvas and redraw what you require. This is because the canvas draws in immediate mode. You either need to draw over what you just drew precisely, or you have to clear and start again, removing the draw operations for the things you don't need.
How do I remove image 3 from the canvas keeping the other two?
You need to clear the canvas for example using clearRect(), then redraw only those you want to keep. There are just unbound pixels on the canvas which you need to manually track.
One suggestion on how to do this is encapsulate the information about an image into a simple object:
var oImg1 = {
image: img1, // image object
visible: true, // state
x: 10, // handy
y: 10 // dandy
},
//... etc. for the other images (could use an array if there are many)
Then
if (oImg1.visible) ctx.drawImage(oImg1.image, oImg1.x, oImg1.y);
When you need to remove an image set visible to false, clear and redraw using conditions.

Can't getImageData() in javascript with gif file; can with FillRect. What's wrong?

Looks like getImageData works when I use FillRect to create a rect on the canvas, but it doesn't work when I draw a gif on the canvas despite it drawing correctly and everything. Have no idea why that is - is it a security risk? Help?
<html>
<body>
<p>Image to use:</p>
<img id="scream" width="230" height="60" src="http://www.thekitmaker.com/image/3.gif" alt="The Scream">
<p>Canvas:</p>
<canvas id="myCanvas" width="230" height="60" style="border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas tag.
</canvas>
<script>
function getPixel(imgData, index) {
var i = index*4, d = imgData.data;
return [d[i],d[i+1],d[i+2],d[i+3]] // [R,G,B,A]
}
// AND/OR
function getPixelXY(imgData, x, y) {
return getPixel(imgData, y*imgData.width+x);
}
window.onload = function() {
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
//draw on canvas
var img = document.getElementById("scream");
ctx.drawImage(img,10,10,50,50);//,0,0);// c.width,c.height);
//ctx.fillStyle = "red";
//ctx.fillRect(10, 10, 50, 50);
//get image data
alert("h");
var imgData = ctx.getImageData(10, 10, 50, 50);
alert("h2");
// The magic®
//getPixel(idt, 852); // returns array [red, green, blue, alpha]
alert("p:"+getPixelXY(idt,1,1)[0]); // same pixel using x,y
}
</script>
</body>
</html>
You are correct that it is because of security reasons. If you draw an image from a different origin onto a canvas, the canvas will be marked as "tainted" and you can no longer get image data from it, among other things.
Reference: http://www.w3.org/TR/html51/semantics.html#security-with-canvas-elements

Categories

Resources