how to display part of the canvas after scaling in html5 - html

How to display part of canvas after scaling in html5
For ex:
var c =document.getElementById("mycanvas");
var canvas = c.getContext("2d");
canvas.scale(4,4);
canvas.drawImage(img,0,0);
canvas.drawImage(img,200,200);
img is some image.
Here i have scaled it some value, now it displays the top-left region of the canvas(with only the top-left image) but what if i want it to display bottom-right region(only the bottom-right image) or according to the coordinates i give to it. How can i do that?
Can someone plz help me on this? I will be very grateful.....

If you are scaling you must remember that the coordinates you use to position will also be scaled up, so if you are scaling by a factor of 4 than your coordinates will be 200 * 4 and not 200. To scale the image alone you can use the call drawImage(img,x,y,width,height) and use...
var c = document.getElementById("mycanvas");
var ctx = c.getContext("2d");
var scale = 4;
var width = img.width * scale;
var height = img.height * scale;
ctx.drawImage(img, 0, 0, width, height);
ctx.drawImage(img, 200, 200, width, height);
Or you will need to divide your coordinates by the scale factor...
var c = document.getElementById("mycanvas");
var ctx = c.getContext("2d");
var scale = 4;
ctx.scale(scale, scale);
ctx.drawImage(img, 0, 0);
ctx.drawImage(img, 200 / scale, 200 / scale);
I've put together a fiddle showing the latter approach using clipping to ensure that the image stays in its quadrant http://jsfiddle.net/ujtd2/
Edit using the state stack you can prevent having to do the conversion yourself.
var c = document.getElementById("mycanvas");
var ctx = c.getContext("2d");
var scale = 4;
// add a new item to the context state stack
ctx.save();
ctx.scale(scale, scale);
ctx.drawImage(img, 0, 0);
// discard the previous state be restoring the last state
// back to normal scale
ctx.restore();
// Set translation
ctx.translate(200, 200);
// Repeat for second image
ctx.save();
ctx.scale(scale, scale);
ctx.drawImage(img, 0, 0);
I follow now. To zoom in and show the part of the scene from a specific coordinate use translate.
ctx.scale(4, 4);
ctx.translate(-200, -200);
ctx.drawImage(img, 0, 0);
ctx.drawImage(img, 200, 200);
This zooms in by 4 and then moves the visible portion down and right by 200 pixels, by translating the drawing coordinates up and left by 200 pixels.

You can use drawImage the following way :
drawImage(
image,
sourceX,
sourceY,
sourceWidth,
sourceHeight,
destinationX,
destinationY,
destinationWidth,
destinationHeight
);
You determine the region of the source you want and then the place you want to put it on your canvas.
You can find some info here : MDN Draw image documentation

Related

HTML5 canvas rotation not centered at origin [duplicate]

Hi I want to rotate this shape around its center when I move my mouse, but currently it's rotating around (0, 0). How to change my code?
Source code (also see jsfiddle):
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
class Circle {
constructor(options) {
this.cx = options.x;
this.cy = options.y;
this.radius = options.radius;
this.color = options.color;
this.angle = 0;
this.toAngle = this.angle;
this.binding();
}
binding() {
const self = this;
window.addEventListener('mousemove', (e) => {
self.update(e.clientX, e.clientY);
});
}
update(nx, ny) {
this.toAngle = Math.atan2(ny - this.cy, nx - this.cx);
}
render() {
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.save();
ctx.beginPath();
ctx.lineWidth = 1;
if (this.toAngle !== this.angle) {
ctx.rotate(this.toAngle - this.angle);
}
ctx.strokeStyle = this.color;
ctx.arc(this.cx, this.cy, this.radius, 0, Math.PI * 2);
ctx.stroke();
ctx.closePath();
ctx.beginPath();
ctx.fillStyle = 'black';
ctx.fillRect(this.cx - this.radius / 4, this.cy - this.radius / 4, 20, 20);
ctx.closePath();
ctx.restore();
}
}
var rotatingCircle = new Circle({
x: 150,
y: 100,
radius: 40,
color: 'black'
});
function animate() {
rotatingCircle.render();
requestAnimationFrame(animate);
}
animate();
All good answers, well frustratingly no... they fail to mention that the solution only works if the current transform is at it default. They fail to mention how to get back to the default state and save and restore states.
To get the default transformation state
ctx.setTransform(1,0,0,1,0,0);
To save and restore all states
ctx.save();
ctx.transform(10,0,0,2,200,100); // set some transform state
ctx.globalAlpha = 0.4;
ctx.restore(); // each save must be followed by a restore at some point
and they can be nested
ctx.save(); // save default state
ctx.globalAlpha = 0.4;
ctx.save(); // save state with alpha = 0.4
ctx.transform(10,0,0,2,200,100); // set some transform state
ctx.restore(); // restore to alpha at 0.4
ctx.restore(); // restore to default.
setTransform completely replaces the current transformation. while transform, scale, rotate, translate, multiply the existing transform with the appropriate transform. This is handy if you have an object attached to another, and want the transformation of the first to apply to the second, and additional transforms to the second but not to the first.
ctx.rotate(Math.PI /2); // Rotates everything 90 clockwise
ctx.rotate(Math.PI /2); // Rotates everything another 90 clockwise so that
// everything is 180 from default
ctx.translate(1,1); // move diagonally down by 1. Origin is now at 1,1
ctx.translate(1,1); // move diagonally down by 1. Origin is now at 2,2
ctx.translate(1,1); // move diagonally down by 1. Origin is now at 3,3
ctx.translate(1,1); // move diagonally down by 1. Origin is now at 4,4
ctx.scale(2,2); // scale by 2 everything twice as big
ctx.scale(2,2); // scale by 2 everything four times as big
And an alternative that does not require the default transform state of ctx
// scaleX, scaleY are scales along axis x,y
// posX, posY is position of center point
// rotate is in radians clockwise with 0 representing the x axis across the screen
// image is an image to draw.
ctx.setTransform(scaleX,0,0,scaleY, posX, posY);
ctx.rotate(rotate);
ctx.drawImage(image,-image.width / 2, -image.height / 2);
Or if not a image but a object
ctx.setTransform(scaleX,0,0,scaleY, posX, posY);
ctx.rotate(rotate);
ctx.translate(-object.width / 2, -object.height / 2);
You need to:
first translate to the point of rotation (pivot)
then rotate
then either:
A: draw in at (0,0) using (-width/2, -height/2) as relative coordinate (for centered drawings)
B: translate back and use the object's absolute position and subtract relative coordinates for centered drawing
Modified code:
ctx.beginPath();
ctx.lineWidth = 1;
ctx.translate(this.cx, this.cy); // translate to pivot
if (this.toAngle !== this.angle) {
ctx.rotate(this.toAngle - this.angle);
}
ctx.strokeStyle = this.color;
ctx.arc(0, 0, this.radius, 0, Math.PI * 2); // render at pivot
ctx.closePath(); // must come before stroke() btw.
ctx.stroke();
ctx.beginPath();
ctx.fillStyle = 'black';
ctx.fillRect(-this.radius / 4, -this.radius / 4, 20, 20); // render at pivot
Modified Fiddle
Bonus tip: you're currently using save()/restore() calls to maintain the transformation matrix. Another way could be to set the matrix using absolute values initially replacing the save()/restore() - so instead of the first translate():
ctx.setTranform(1,0,0,1,this.cx, this.cy); // translate to pivot
You can also set things like styles on an individual basis for each. Regardless, it doesn't change the core solution though.
You have to first translate to the circle centre, make the rotation and then translate back
Do this before rendering the circle and the square
ctx.translate(this.cx, this.cy);
ctx.rotate(this.toAngle - this.angle);
ctx.translate(-this.cx, -this.cy);
jsfiddle below:
https://jsfiddle.net/1st8Lbu8/2/

canvas drawImage upscale and then crop

Using drawImage, I am trying to do the following with an image that is 1280x720...
Upscale it to 1920x1080
Crop it so that only 600x1080 remains from the centre
I have this so far...
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
img=new Image();
img.onload=function(){
canvas.width=1920;
canvas.height=1080;
ctx.drawImage(img,0,0,img.width,img.height,0,0,1920,1080);
}
img.src="https://dummyimage.com/1280x720/000/fff";
//img.src="https://dummyimage.com/1920x1080/000/fff";
body{ background-color: ivory; }
canvas{border:1px solid red;}
<canvas id="canvas" width=100 height=100></canvas>
The upscaling part I have got working but now I am looking at the crop, anyone have an example I can see?
Is there any benefit from cropping first before rescaling?
ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, 1920, 1080);
Something like that:
x = (img.width - 600) / 2;
y = (img.height - 1080) / 2;
ctx.drawImage(img, x, y, 600, 1080, 0, 0, 1920, 1080);
but check for the destination area parameters depending on what exactly you want to get.
Clip image to fit canvas
The canvas will clip the image for you.
By default all rendering has a clip region set to the canvas size. Because the clip is performed regardless of the size of the content (all content must be checked against the clip region and is done in hardware (GPU)) rendering the full image is slightly quicker than rendering a portion of the image.
ctx.drawImage(image,x,y); // is the quicker function
ctx.drawImage(image,ix,iy,iw,ih,x,y,w,h); // the slower function
Note; This is not true when the rendered visible destination content is significantly smaller than the image source
Thus to render the image cropped to a smaller canvas you only need to find the center and then render the image at half its size away from that center.
ctx.drawImage(
image, // image to render
(ctx.canvas.width - image.width) / 2, // center sub half image width
(ctx.canvas.height - image.height) / 2 // center sub half image height
);
If you need to up scale first the following will render any size image to fit 1080 height.
const imgW = 1920;
const imgH = 1080;
ctx.drawImage(
image, // image to render
(ctx.canvas.width - imgW) / 2, // center sub half image display width
(ctx.canvas.height - imgH) / 2, // center sub half image display height
imgW, imgH
);
Crop image
If you wish to save memory and crop the image you use a canvas to hold the cropped image.
function cropImageCenter(image,w,h){
const c = document.createElement("canvas");
c.width = w;
c.height = h;
const ctx = c.getContext("2d");
ctx.drawImage(image,(w - image.width) / 2, (h - image.height) / 2);
return c;
}
var img = new Image;
img.src = "imageURL1280by720.jpg";
img.onload = () => {
img = cropImageCenter(img, 600, 1080);
ctx.drawImage(img,0,0); /// render cropped image on to canvas
};
Or to upscale and crop
function scaleCropToHeight(image,w,h){
const c = document.createElement("canvas");
c.width = w;
c.height = h;
const scale = h / image.height;
const ctx = c.getContext("2d");
ctx.drawImage(
image,
(w - image.width * scale) / 2,
(h - image.height * scale) / 2,
image.width * scale,
image.height * scale
);
return c;
}
var img = new Image;
img.src = "imageURL1920by1080.jpg";
img.onload = () => {
img = scaleCropToHeight(img, 600, 1080);
ctx.drawImage(img,0,0); /// render cropped image on to canvas
};

Are HTML canvas clip paths inclusive or exclusive?

I've been working on a Typescript based touch screen client for our CQC home automation platform, and ran across something odd. There are lots of places where various graphical elements are layered over images. When it's time to update some area of the screen, I set a clip area and update.
But I always ended up with a line around everything, which was the color of the underlying color fill behind the image. I of course blamed myself. But, in the end, instead of committing suicide, I did a little test program.
It seems to indicate that drawImage() does NOT include the clip path boundary, while a color fill does. So blitting over the part of the images that underlies the area I'm updating doesn't completely fill the target area, leaving a line around the area.
After that simple program demonstrated the problem, I went back and for image updates I inflated the clip area by one, but left it alone for everything else, and now it's all working. I tested this in Chrome and Edge, just to make sure it wasn't some bug, and they both act exactly the same.
Strangely, I've never see any statement in the docs about whether clip paths are intended to be exclusive or inclusive of the boundary, but surely it shouldn't be one way for one type of primitive and another way for others, right?
function drawRect(ctx, x, y, w, h) {
ctx.moveTo(x, y);
ctx.lineTo(x + w, y);
ctx.lineTo(x + w, y + h);
ctx.lineTo(x, y + h);
ctx.lineTo(x, y);
}
function init()
{
var canvas = document.getElementById("output");
canvas.style.width = 480;
canvas.style.height = 480;
canvas.width = 480;
canvas.height = 480;
var drawCtx = canvas.getContext("2d");
drawCtx.translate(0.5, 0.5);
var img = new Image();
img.src = "test.jpg";
img.onload = function() {
// DRaw the image
drawCtx.drawImage(img, 0, 0);
// SEt a clip path
drawCtx.beginPath();
drawRect(drawCtx, 10, 10, 200, 200);
drawCtx.clip();
// Fill the whole area, which fills the clip area
drawCtx.fillStyle = "black";
drawCtx.fillRect(0, 0, 480, 480);
// Draw the image again, which should fill the area
drawCtx.drawImage(img, 0, 0);
// But it ends up with a black line around it
}
}
window.addEventListener("load", init, false);
I think they behave same.
Clip region are not inclusive of the border, but they can use anti aliasing.
Chrome was not using this techinque and was giving jagged lines on clipping. ( probably they changed recently ).
The thin black border is the side effect of a compositing operation.
The clip region is across a pixel. so the fillRect will draw black everywhere, but the border will be 50% black and 50% transparent, compositing with the first image draw.
The second draw image get clpped, at the border with 50% opacity to simulate the half pixel. at this point at the clip border you have:
image 100%
black fill 50%
image 50%
This will make a small dark border appear.
function drawRect(ctx, x, y, w, h) {
ctx.moveTo(x, y);
ctx.lineTo(x, y + h);
ctx.lineTo(x + w, y + h);
ctx.lineTo(x + w, y);
ctx.closePath();
}
function init()
{
var canvas = document.getElementById("output");
canvas.style.width = 480;
canvas.style.height = 480;
canvas.width = 480;
canvas.height = 480;
var drawCtx = canvas.getContext("2d");
drawCtx.translate(0.5, 0.5);
var img = new Image();
img.src = "http://fabricjs.com/assets/printio.png";
img.onload = function() {
// DRaw the image
drawCtx.drawImage(img, 0, 0);
// SEt a clip path
drawCtx.beginPath();
drawRect(drawCtx, 10, 10, 200, 200);
drawCtx.clip();
// Fill the whole area, which fills the clip area
drawCtx.fillStyle = "black";
drawCtx.fillRect(0, 0, 480, 480);
// Draw the image again, which should fill the area
drawCtx.drawImage(img, 0, 0);
// But it ends up with a black line around it
}
}
init();
<canvas id="output" />

How to show part of element from other side of canvas

How to show part of element outside of canvas from opposite side canvas. Illustration:
You need to draw twice when the shape is outside canvas' boundaries. Draw the main part first, then the same part offset by width so it gives the illusion of showing on the other side.
Manually Draw twice
This draws a shape going from right to left, when the shape is outside the left edge it will be redrawn at the right edge representing the part that is non-visible on the left side. For the opposite way (left to right) the principle is just the same, just use x with canvas' width instead of 0.
var ctx = document.querySelector("canvas").getContext("2d"),
x = 100, // start position
w = 200; // shape width
ctx.fillStyle = "#777";
(function loop() {
ctx.clearRect(0, 0, 300, 150); // clear canvas
ctx.fillRect(x, 0, w, 150); // draw main part/image/shape
if (x < 0) { // should rotate? draw secondary
ctx.fillRect(ctx.canvas.width + x, 0, w, 150); // use canvas width + x (x<0)
}
x -= 7; // animate
if (x <= -w) x = ctx.canvas.width + x; // at some point reset x
requestAnimationFrame(loop)
})();
<canvas></canvas>
Translated Pattern
To simplify this a CanvasPattern can be used. The later version of canvas allows local transforms on the pattern itself, but since this is not currently widely spread I'll show an example using normal transforms and compensated x position:
var ctx = document.querySelector("canvas").getContext("2d"),
pattern,
x = 100, // start position
w = 200; // shape width
// create pattern
ctx.fillStyle = "#777";
ctx.fillRect(x, 0, w, 150); // draw main part/image/shape
pattern = ctx.createPattern(ctx.canvas, "repeat"); // use current canvas as pattern
ctx.fillStyle = pattern; // set pattern as fillStyle
(function loop() {
ctx.setTransform(1,0,0,1,0,0); // reset transforms
ctx.clearRect(0, 0, 300, 150); // clear canvas
ctx.setTransform(1,0,0,1,x,0); // translate absolute x
ctx.fillRect(-x, 0, 300, 150); // fill using pattern, compensate transform
x -= 7; // animate
requestAnimationFrame(loop)
})();
<canvas></canvas>

HTML5 canvas image rotate left rotate right

I have drawn an image into canvas.
Now what i am trying is if user click on roate left button total canvas should rotate left (i.e., Image rotate to 90 degress) and if rotate right canvas should rotate right.
this is what i tried for rotation. Please suggest me the code to achieve this
var canvasId = document.getElementById("preview");
var cntxt = canvasId.getContext("2d");
cntxt.translate($("#preview").width()-1, $("#preview").height()-1);
cntxt.rotate(convertToRadians(90));
cntxt.drawImage(canvasId, 0, 0, $("#preview").width(), $("#preview").height());
function convertToRadians(degree) {
return degree*(Math.PI/180);
}
Working Solution:
// Getting the canvas
var canvasId = document.getElementById(id);
var ctx = canvasId.getContext("2d");
// Store the current transformation matrix
ctx.save();
ctx.setTransform(1,0,0,1,0,0);
// Getting the canvas width and height
var w = $("#preview").width();
var h = $("#preview").height();
ctx.clearRect(0, 0, w, h);
// Restore the transform
ctx.restore();
// TranslateX should be canvaswidth/2 and translateY should be canvasheight/2
var translateX = w/2;
var translateY = h/2;
// translating the context
ctx.translate(translateX,translateY);
// As we need to rotate the image to 90 degrees
// Where r can be 1 to 36 for 10 to 360 degree rotation
var r = 9;
ctx.rotate(r*10*Math.PI/180);
ctx.translate(-translateX,-translateY);
// Drawing canvas
ctx.drawImage(ImageObject, 0, 0, w, h);
One more doubt can't we rotate the whole canvas to keep the proper aspect ratio of the image
Here is the simple code for that,
var canvasId = document.getElementById("preview");
var ctx = canvasId.getContext("2d");
ctx.setTransform(1,0,0,1,0,0);
ctx.clearRect(0, 0, $("#preview").width(), $("#preview").height());
var translateX = ($("#preview").width())/2;
var translateY = ($("#preview").height())/2;
ctx.translate(translateX,translateY);
var r = 9;
ctx.rotate(r*10*Math.PI/180);
ctx.translate(-translateX,-translateY);
ctx.drawImage(imgsrc, 0, 0, $("#preview").width(), $("#preview").height());
Where r can be 1 to 36 for 10 to 360 degree rotation.
As i verified in the net it is lot simpler than what we did in the above process.
So here is the link which i have posted in my blog HTML5 canvas image rotation