How to scale image in canvas? - javascript

I'm trying to display the image using cover simulation in canvas. I've found some cool answer on how to do it.
So far my image changes depending on the screen resolution, but only after refreshing the page.
How can I get the following scaling effect without refreshing a page?
Try to resize the window out there.
HTML
<canvas id="canvas"></canvas>
JS
var ctx = canvas.getContext('2d'),
img = new Image;
canvas.setAttribute('width', window.innerWidth);
canvas.setAttribute('height', window.innerHeight);
img.onload = draw;
img.src = 'https://upload.wikimedia.org/wikipedia/commons/0/0f/2010-02-19_3000x2000_chicago_skyline.jpg';
function draw() {
drawImageProp(ctx, this, 0, 0, canvas.width, canvas.height);
}
/**
* By Ken Fyrstenberg
*
* drawImageProp(context, image [, x, y, width, height [,offsetX, offsetY]])
*
* If image and context are only arguments rectangle will equal canvas
*/
function drawImageProp(ctx, img, x, y, w, h, offsetX, offsetY) {
if (arguments.length === 2) {
x = y = 0;
w = ctx.canvas.width;
h = ctx.canvas.height;
}
/// default offset is center
offsetX = offsetX ? offsetX : 0.5;
offsetY = offsetY ? offsetY : 0.5;
/// keep bounds [0.0, 1.0]
if (offsetX < 0) offsetX = 0;
if (offsetY < 0) offsetY = 0;
if (offsetX > 1) offsetX = 1;
if (offsetY > 1) offsetY = 1;
var iw = img.width,
ih = img.height,
r = Math.min(w / iw, h / ih),
nw = iw * r, /// new prop. width
nh = ih * r, /// new prop. height
cx, cy, cw, ch, ar = 1;
/// decide which gap to fill
if (nw < w) ar = w / nw;
if (nh < h) ar = h / nh;
nw *= ar;
nh *= ar;
/// calc source rectangle
cw = iw / (nw / w);
ch = ih / (nh / h);
cx = (iw - cw) * offsetX;
cy = (ih - ch) * offsetY;
/// make sure source rectangle is valid
if (cx < 0) cx = 0;
if (cy < 0) cy = 0;
if (cw > iw) cw = iw;
if (ch > ih) ch = ih;
/// fill image in dest. rectangle
ctx.drawImage(img, cx, cy, cw, ch, x, y, w, h);
}

Add a call to draw on the window resize event:
window.onresize = draw;
That should do the trick.
Instead of:
drawImageProp(ctx, this, 0, 0, canvas.width, canvas.height);
Pass img in the draw function:
drawImageProp(ctx, img, 0, 0, canvas.width, canvas.height);
Then, move the width / heigth settings into the draw function, resulting in:
function draw() {
canvas.setAttribute('width', window.innerWidth);
canvas.setAttribute('height', window.innerHeight);
drawImageProp(ctx, img, 0, 0, canvas.width, canvas.height);
}
You may also want to debounce the re-drawing a bit:
var timeOut;
window.onresize = function(){
if(timeOut)
clearTimeout(timeOut);
timeOut = setTimeout(draw, 10);
}
This prevents the draw function from being called a couple of times per second while the window is being resized.
Here's a working example, forked from your codepen.

Well this works.
http://codepen.io/anon/pen/KdRwVY
var ctx = canvas.getContext('2d'),
img = new Image;
canvas.setAttribute('width', window.innerWidth);
canvas.setAttribute('height', window.innerHeight);
img.onload = draw;
img.src = 'https://upload.wikimedia.org/wikipedia/commons/0/0f/2010-02-19_3000x2000_chicago_skyline.jpg';
function draw() {
drawImageProp(ctx, img, 0, 0, canvas.width, canvas.height);
}
window.onresize = resize;
function resize() {
canvas.setAttribute('width', window.innerWidth);
canvas.setAttribute('height', window.innerHeight);
draw()
}
/**
* By Ken Fyrstenberg
*
* drawImageProp(context, image [, x, y, width, height [,offsetX, offsetY]])
*
* If image and context are only arguments rectangle will equal canvas
*/
function drawImageProp(ctx, img, x, y, w, h, offsetX, offsetY) {
if (arguments.length === 2) {
x = y = 0;
w = ctx.canvas.width;
h = ctx.canvas.height;
}
/// default offset is center
offsetX = offsetX ? offsetX : 0.5;
offsetY = offsetY ? offsetY : 0.5;
/// keep bounds [0.0, 1.0]
if (offsetX < 0) offsetX = 0;
if (offsetY < 0) offsetY = 0;
if (offsetX > 1) offsetX = 1;
if (offsetY > 1) offsetY = 1;
var iw = img.width,
ih = img.height,
r = Math.min(w / iw, h / ih),
nw = iw * r, /// new prop. width
nh = ih * r, /// new prop. height
cx, cy, cw, ch, ar = 1;
/// decide which gap to fill
if (nw < w) ar = w / nw;
if (nh < h) ar = h / nh;
nw *= ar;
nh *= ar;
/// calc source rectangle
cw = iw / (nw / w);
ch = ih / (nh / h);
cx = (iw - cw) * offsetX;
cy = (ih - ch) * offsetY;
/// make sure source rectangle is valid
if (cx < 0) cx = 0;
if (cy < 0) cy = 0;
if (cw > iw) cw = iw;
if (ch > ih) ch = ih;
/// fill image in dest. rectangle
ctx.drawImage(img, cx, cy, cw, ch, x, y, w, h);
}
<canvas id="canvas"></canvas>

Related

How to add SVG image to javascript html5 canvas animation?

I currently have a rotating bouncing ball within an html5 canvas and I am looking to insert an SVG image inside the ball that moves and rotates with it
I have this code from researching this but unsure if this is correct
var img = new Image();
img.onload = function() {
ctx.drawImage(img, 0, 0);
}
img.src = "";
Does anyone have any suggestion on how I might achieve this?
Here is my code
<canvas id="myCanvas"></canvas>
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
var x = canvas.width / 2;
var y = canvas.height / 2;
// SPEED
var dx = 4;
var dy = -4;
var radius = 120;
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2);
ctx.fillStyle = "#9370DB";
ctx.fill();
ctx.closePath();
if (x + dx > canvas.width - radius) {
dx = -dx;
}
if (x + dx < radius) {
dx = -dx;
}
if (y + dy > canvas.height - radius) {
dy = -dy;
}
if (y + dy < radius) {
dy = -dy;
}
x += dx;
y += dy;
}
window.addEventListener('resize', resizeCanvas, false);
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
resizeCanvas();
x = canvas.width / 2;
y = canvas.height / 2;
setInterval(draw, 10);
var img = new Image();
img.src = ""; // Put the path to you SVG image here.
img.onload = function() {
ctx.drawImage(img, 0, 0);
}
This should work
One way of doing it would be putting the image hidden in the HTML. In this case the image is an svg as data uri and has an id="apple" and you can say:
var img = apple;
To draw the image inside the ball you need to use the center of the ball, for example like this:
ctx.drawImage(img, x-img.width/2,y-img.height/2)
Also instead of using setInterval I'm using requestAnimationFrame and the image is not getting out of the screen on resize. I hope you will find my answer useful.
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
var x = canvas.width / 2;
var y = canvas.height / 2;
var rid = null;// request animation id
// SPEED
var dx = 4;
var dy = -4;
var radius = 120;
var img = apple;// the image is the one with the id="apple"
function draw() {
rid = window.requestAnimationFrame(draw);
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2);
ctx.fillStyle = "#9370DB";
ctx.fill();
ctx.closePath();
//draw the image in the center of the ball
ctx.drawImage(img, x-img.width/2,y-img.height/2)
if (x + dx > canvas.width - radius) {
dx = -dx;
}
if (x + dx < radius) {
dx = -dx;
}
if (y + dy > canvas.height - radius) {
dy = -dy;
}
if (y + dy < radius) {
dy = -dy;
}
x += dx;
y += dy;
}
window.addEventListener('resize', resizeCanvas, false);
function resizeCanvas() {
//stop the animation
if(rid){window.cancelAnimationFrame(rid); rid= null;}
//get the size of the canvas
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
x = canvas.width / 2;
y = canvas.height / 2;
//restart the animation
draw()
}
window.setTimeout(function() {
resizeCanvas();
window.addEventListener('resize', resizeCanvas, false);
}, 15);
<canvas id="myCanvas">
<img id="apple" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' version='1.1' id='Layer_1' x='0px' y='0px' width='106px' height='122px' viewBox='41 54 106 122'%3E%3Cg%3E%3Cpath fill='%23FFFFFF' stroke='%23ED1D24' stroke-width='2' stroke-miterlimit='10' d='M143.099,93.757c0,0-14.173,8.549-13.724,23.173 c0.449,14.624,11.954,23.413,15.974,24.073c1.569,0.258-9.245,22.049-15.984,27.448c-6.74,5.4-13.714,6.524-24.513,2.25c-10.8-4.275-18.449,0.275-24.749,2.612c-6.299,2.337-13.949-0.137-24.298-14.987c-10.349-14.849-21.823-49.271-6.074-66.146c15.749-16.874,33.298-10.124,38.022-7.875c4.725,2.25,13.05,2.025,22.499-2.25C119.7,77.782,138.374,86.782,143.099,93.757z'/%3E%3C/g%3E%3Cg%3E%3Cpath fill='%23FFFFFF' stroke='%23ED1D24' stroke-width='2' stroke-miterlimit='10' d='M118.575,54.609c0,0,0.9,5.625-1.35,10.349 s-10.718,20.936-22.994,17.999c-0.308-0.073-2.102-5.506,0.532-11.027C98.48,64.138,108.171,55.399,118.575,54.609z'/%3E%3C/g%3E%3C/svg%3E" />
</canvas>
Please run the code on full page.

Draw cropped image in canvas

How to get the right position of image and draw what I see in lens into canvas?
Here is my example with a demo.
var isDown,
lens,
img,
canvas,
cx,
cy,
ctx,
image;
img = document.getElementById('img');
lens = document.getElementById('lens');
canvas = document.getElementById('canvas');
cx = canvas.offsetWidth / lens.offsetWidth;
cy = canvas.offsetHeight / lens.offsetHeight;
ctx = canvas.getContext('2d');
image = new Image();
image.src = img.src;
lens.addEventListener("mousemove", (e) => {
moveLens(e);
});
img.addEventListener("mousemove", (e) => {
moveLens(e);
});
function moveLens(e) {
var pos, x, y;
e.preventDefault();
pos = getCursorPos(e);
x = pos.x - (lens.offsetWidth / 2);
y = pos.y - (lens.offsetHeight / 2);
if (x > img.width - lens.offsetWidth) {
x = img.width - lens.offsetWidth;
}
if (x < 0) {
x = 0;
}
if (y > img.height - lens.offsetHeight) {
y = img.height - lens.offsetHeight;
}
if (y < 0) {
y = 0;
}
lens.style.left = x + "px";
lens.style.top = y + "px";
draw(x, y);
}
function getCursorPos(e) {
var a, x = 0,
y = 0;
e = e || window.event;
a = img.getBoundingClientRect();
x = e.pageX - a.left;
y = e.pageY - a.top;
x = x - window.pageXOffset;
y = y - window.pageYOffset;
return {
x: x,
y: y
};
}
function draw(x, y) {
ctx.drawImage(image, x, y, canvas.width, canvas.height, 0, 0, canvas.width, canvas.height);
}
#container {
position: relative;
width: 300px;
display:inline-block;
}
#container>img {
max-width: 100%;
}
#lens {
position: absolute;
border: 1px solid #d4d4d4;
width: 80px;
height: 80px;
}
<div id="container">
<div id="lens"></div>
<img src="https://cdn.pixabay.com/photo/2016/06/18/17/42/image-1465348_960_720.jpg" id="img">
</div>
<canvas width="200" height="200" id="canvas"></canvas>
ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)
The sx,sy,sWidth,sHeight are measured according to the source image. Say we have an image in way HIGH resolution, for instance, 2000 * 1000, then the sWidth and sHeight should be scaled up to that resolution. In your case, it should be 80 / 300 * 2000 for sWidth.
I've made those adjustments to your code.
function draw(x, y) {
ctx.clearRect(0, 0, canvas.width, canvas.height); // you might want to draw on a clean canvas each time
let scaleX = x / img.offsetWidth * image.width;
let scaleY = y / img.offsetHeight * image.height;
let scaleWidth = lens.offsetWidth / img.width * image.width;
let scaleHeight = lens.offsetHeight / img.height * image.height;
ctx.drawImage(image, scaleX, scaleY, scaleWidth, scaleHeight, 0, 0, canvas.width, canvas.height);
}

canvas js fill an arc with image

I am trying to fill a circle (arc) with an image.
Here is my code :
draw() {
ctx.save();
let boulePat = new Image();
switch(this.couleur) {
case "red":
boulePat.src = "images/red.png";
break;
case "green":
boulePat.src = "images/green.png";
break;
case "orange":
boulePat.src = "images/orange.png";
break;
case "yellow":
boulePat.src = "images/yellow.png";
break;
case "purple":
boulePat.src = "images/purple.png";
break;
}
var pattern = ctx.createPattern(boulePat, "repeat");
ctx.beginPath();
ctx.arc(this.x, this.y, 15, 0, 2 * Math.PI);
ctx.fillStyle = pattern;
ctx.fill();
ctx.restore();
}
With this, I have empty or black circles ...
Could you help me, please ?
Thank you very much.
You have to wait for the image to load before you can use it in createPattern, like so:
See this stack answer
Keep in mind, the image will start from 0,0 coordinates in relation to the canvas. You will need to factor this into any offsets (using ctx.transform(xOffset, yOffset)) if necessary.
var canvas = document.getElementById("myCanvasNoTranslate");
var canvas2 = document.getElementById("myCanvasWithTranslate");
function drawCanvas(_canvas) {
var context = _canvas.getContext("2d");
draw(context);
}
function draw(ctx) {
ctx.save();
ctx.strokeSyle = "rgb(0, 0, 0)";
ctx.lineWidth = 3;
ctx.strokeRect(0, 0, 400, 200);
let boulePat = new Image();
boulePat.src = "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e9/16777216colors.png/100px-16777216colors.png";
boulePat.onload = function () {
var pattern = ctx.createPattern(boulePat, "repeat");
ctx.fillStyle = pattern;
ctx.beginPath();
ctx.arc(100, 100, 50, 0, 2 * Math.PI);
// translate canvas to offset where the image coordinates are for .fill()
if (ctx.canvas === canvas2) {
ctx.translate(50, 50);
}
ctx.fill();
// restore ctx back to before the translate()
ctx.restore();
}
}
drawCanvas(canvas);
drawCanvas(canvas2);
With ctx.translate() for background fill<br/>
<canvas id="myCanvasWithTranslate" height="200px" width="400px"></canvas>
<br/><br/>
Without ctx.translate() for background fill<br/>
<canvas id="myCanvasNoTranslate" height="200px" width="400px"></canvas>
The code below takes an image URL, the radius of the arc, and the angle/size of the arc, and returns a canvas that has that image clipped to the given arc, and "contained" within the arc such that the image fills the arc without changing the image's aspect ratio:
async function getArcClippedCanvas(imageUrl, radius, arcSizeDeg) {
let arcSizeRad = (arcSizeDeg/360)*2*Math.PI;
// derive required width and height of canvas from radius and arc size
let width;
if(arcSizeDeg >= 180) {
width = radius*2;
} else {
width = radius*Math.sin(arcSizeRad/2)*2;
}
let height;
if(arcSizeDeg <= 180) {
height = radius;
} else {
height = radius + radius*Math.sin( (arcSizeRad-Math.PI)/2 );
}
let arcCenterX = width/2;
let arcCenterY = radius; // remember, y axis starts from top of canvas
let canvas = document.createElement("canvas");
let ctx = canvas.getContext("2d");
canvas.width = width;
canvas.height = height;
let img = new Image();
await new Promise(resolve => {
img.onload = resolve;
img.src = imageUrl;
});
let centerAngle = -Math.PI/2;
ctx.beginPath();
ctx.moveTo(arcCenterX, arcCenterY);
ctx.arc(arcCenterX, arcCenterY, radius, centerAngle - (arcSizeDeg/2)*2*Math.PI/360, centerAngle + (arcSizeDeg/2)*2*Math.PI/360);
ctx.clip();
// we want to "cover" the canvas with the image without changing the image's aspect ratio
drawImageToCanvasContained(ctx, img, 0, 0, canvas.width, canvas.height);
return canvas;
}
function drawImageToCanvasContained(ctx, img, x, y, w, h, offsetX, offsetY) {
// By Ken Fyrstenberg Nilsen: https://stackoverflow.com/a/21961894/11950764
if(arguments.length === 2) {
x = y = 0;
w = ctx.canvas.width;
h = ctx.canvas.height;
}
// default offset is center
offsetX = typeof offsetX === "number" ? offsetX : 0.5;
offsetY = typeof offsetY === "number" ? offsetY : 0.5;
// keep bounds [0.0, 1.0]
if(offsetX < 0) offsetX = 0;
if(offsetY < 0) offsetY = 0;
if(offsetX > 1) offsetX = 1;
if(offsetY > 1) offsetY = 1;
let iw = img.width;
let ih = img.height;
let r = Math.min(w / iw, h / ih);
let nw = iw * r; // new prop. width
let nh = ih * r; // new prop. height
let cx, cy, cw, ch, ar = 1;
// decide which gap to fill
if(nw < w) ar = w / nw;
if(Math.abs(ar - 1) < 1e-14 && nh < h) ar = h / nh; // updated
nw *= ar;
nh *= ar;
// calc source rectangle
cw = iw / (nw / w);
ch = ih / (nh / h);
cx = (iw - cw) * offsetX;
cy = (ih - ch) * offsetY;
// make sure source rectangle is valid
if(cx < 0) cx = 0;
if(cy < 0) cy = 0;
if(cw > iw) cw = iw;
if(ch > ih) ch = ih;
// fill image in dest. rectangle
ctx.drawImage(img, cx, cy, cw, ch, x, y, w, h);
}

I can not draw a rectangle on the image

When I press the button "Draw". I can't draw rectangle on image.
https://jsfiddle.net/ikat/5xzc13r9/
function drawRect() {
// creating a square
var w = endX - startX;
var h = endY - startY;
var offsetX = (w < 0) ? w : 0;
var offsetY = (h < 0) ? h : 0;
var width = Math.abs(w);
var height = Math.abs(h);
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.save();
ctx.beginPath();
ctx.rect(startX + offsetX, startY + offsetY, width, height);
ctx.lineWidth = 7;
ctx.strokeStyle = 'red';
ctx.stroke();
ctx.closePath();
}

Getting ugly image while simulating cover in canvas

I'm trying to display the image using cover simulation in canvas. I've found some cool answer on how to do it.
The thing is when I do it with a large picture, it's being displayed ugly. How to fix that?
Here's my Codepen
HTML
<canvas id="canvas"></canvas>
CSS
canvas {
width: 100%;
height: 100vh;
}
JS
var ctx = canvas.getContext('2d'),
img = new Image;
img.onload = draw;
img.src = 'https://upload.wikimedia.org/wikipedia/commons/0/0f/2010-02-19_3000x2000_chicago_skyline.jpg';
function draw() {
drawImageProp(ctx, this, 0, 0, canvas.width, canvas.height);
//drawImageProp(ctx, this, 0, 0, canvas.width, canvas.height, 0.5, 0.5);
}
/**
* By Ken Fyrstenberg
*
* drawImageProp(context, image [, x, y, width, height [,offsetX, offsetY]])
*
* If image and context are only arguments rectangle will equal canvas
*/
function drawImageProp(ctx, img, x, y, w, h, offsetX, offsetY) {
if (arguments.length === 2) {
x = y = 0;
w = ctx.canvas.width;
h = ctx.canvas.height;
}
/// default offset is center
offsetX = offsetX ? offsetX : 0.5;
offsetY = offsetY ? offsetY : 0.5;
/// keep bounds [0.0, 1.0]
if (offsetX < 0) offsetX = 0;
if (offsetY < 0) offsetY = 0;
if (offsetX > 1) offsetX = 1;
if (offsetY > 1) offsetY = 1;
var iw = img.width,
ih = img.height,
r = Math.min(w / iw, h / ih),
nw = iw * r, /// new prop. width
nh = ih * r, /// new prop. height
cx, cy, cw, ch, ar = 1;
/// decide which gap to fill
if (nw < w) ar = w / nw;
if (nh < h) ar = h / nh;
nw *= ar;
nh *= ar;
/// calc source rectangle
cw = iw / (nw / w);
ch = ih / (nh / h);
cx = (iw - cw) * offsetX;
cy = (ih - ch) * offsetY;
/// make sure source rectangle is valid
if (cx < 0) cx = 0;
if (cy < 0) cy = 0;
if (cw > iw) cw = iw;
if (ch > ih) ch = ih;
/// fill image in dest. rectangle
ctx.drawImage(img, cx, cy, cw, ch, x, y, w, h);
}
To accomplish this you could use several techniques like HTML/CSS, CSS Only, Jquery or JS/Canvas. For more on this look here.
You do not have to set the width and height of your canvas in HTML like David Skx mentioned. You do have to erase your CSS, remove it completely.
In your JS you should set your canvas size (just define it in 1 place, don't let different languages interfere):
var canvas = document.getElementById('canvas');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
Window means the whole browser, Use pixels here if you want to limit it to just a canvas and not the whole background
That's all.
You have to specify the width and height in pixels directly on the <canvas>-Element, else it will distort it:
<canvas id="canvas" width="500" height="500"></canvas>
Use JavaScript to measure the window width and height and set it dynamically. Something like:
var canvas = document.getElementById('canvas');
canvas.setAttribute('width', window.innerWidth);
canvas.setAttribute('height', window.innerHeight);
UPDATE:
As Matthijs van Hest pointed out, the width and height attributes on the <canvas>-element are just optional.

Categories

Resources