Scaling HTML Canvas elements - javascript

In my main project I have lots of little canvas elements (think: sprites from a sprite map) and I need to be able to scale them before drawing them to the main canvas. Here is my basic code for this:
var c = document.getElementById("canvas");
var ctx = c.getContext("2d");
ctx.canvas.width = window.innerWidth;
ctx.canvas.height = window.innerHeight;
var miniCanvas = document.createElement('canvas');
var miniCanvasContext = miniCanvas.getContext('2d');
miniCanvasContext.beginPath();
miniCanvasContext.rect(0,0,100,100);
miniCanvasContext.closePath();
miniCanvasContext.fillStyle = 'red';
miniCanvasContext.fill();
miniCanvasContext.scale(2,2);//THIS IS THE PROBLEM
ctx.drawImage(miniCanvas, 100, 100);
This test is up at jsfiddle: JSFIDDLE
Essentially, I need this red square to be increased in size by two times.
Thanks.

The drawImage method has a version that lets you draw the miniCanvas with scaling:
// the last 2 parameters are scaled Width & scaled Height
// parameters:
// 1st: the image source
// 2nd,3rd: the upper-left part of the source to clip from
// 4th,5th: the width & height to clip from the source
// 6th,7th: the upper-left part of the canvas to draw the clipped image
// 8th,9th: the scaled width & height to draw on the canvas
ctx.drawImage(miniCanvas, 0,0,100,100, 0,0,200, 200);

Related

How to remove transparent whitespace from image after a crop HTML5 Canvas/JavaScript?

So I have a function that dynamically produces a cropped section of a world map. The map has various points plotted onto it, plotted by longitude and latitude, depending on the data passed into the function from elsewhere in the script. (Don't worry about how these values are calculated, just accept they are calculated where I have put [number] in my code). I've worked out how to crop my map dynamically, but what I'm noticing is that there is a lot of transparent whitespace to the right of the image after the crop, when the image is appended to a div on the page. How do I remove this whitespace?
Please note that each crop will be of a different size. Setting overflow:hidden property on the containing div and limiting the containing div to a precise pixel width will not achieve what I want to achieve.
Thx u
-- Gaweyne
createZoomedMapImage: function(imageURL){
var imageObj = new Image();
imageObj.onload = _.bind(function(){
var canvas = document.createElement("canvas"),
context = canvas.getContext("2d"),
$canvas = $(canvas),
w = imageObj.width,
h = imageObj.height;
canvas.width = w;
canvas.height = h;
var startingX = [number]
var starting Y = [number]
var deltaWidth = [number]
deltaHeight = [number]
context.drawImage(imageObj, startingX, startingY, deltaWidth, deltaHeight, 0, 0, (deltaWidth*2), (deltaHeight*2));
var zoomedImage = canvas.toDataURL('image/png');
}, this);
imageObj.src = imageURL;
}
jsfiddle.net/Gaweyne/r0t3hoo6
The image tag looks like what is displayed in the result. I have an image of, say, 300 x 600px. But the actual graphic only takes up 300 x 300 pixels. I don't want the graphic to take up the full width of the image. I want the image to be 300 x 300 pixels. I don't want to set this explicitly with CSS because the cropped maps will differ in size depending on the data.
Try to use:
canvas.width = deltaWidth;
canvas.height = deltaHeight;
context.drawImage(imageObj, startingX, startingY, deltaWidth, deltaHeight, 0, 0, (deltaWidth*2), (deltaHeight*2));
var zoomedImage = canvas.toDataURL('image/png');

Scale image in Canvas with offset origin

I've been struggling with this for a few days now. My question is based on code you can find here - http://codepen.io/theOneWhoKnocks/pen/VLExPX. In the example you'll see 3 images, the first scales from the [0,0] origin, the second from the center of the canvas, and the third I want to scale from the center of the offset image.
Basically I want the image to scale up or down, but stay centered on the characters iris. Below you'll find a snippet of code that controls the rendering of the third image.
function renderOffset(){
var dims = getScaledDims();
paintBG(ctx3);
ctx3.drawImage(loadedImg, offsetX, offsetY, dims.width, dims.height);
drawCenterAxis(ctx3);
}
After much Googling and looking through forums I figure I need to utilize the transformMatrix, but nothing I've tried thus far has worked. I look forward to any ideas or suggestions you may have, and thank you for your time.
Further clarification
I'm creating an image editor. For the particular use case I'm presenting here, a user has moved the image to the left 108px & up 8px.
var offsetX = -108;
var offsetY = 8;
When the user scales the offset image, I want it to scale at the center of the viewable canvas area (the red crosshairs, or in this case the characters iris).
Update
I've updated the codepen link to point to the final code. Below is a list of additions:
Added in some of the code mentioned in the accepted answer.
Added the ability to drag the image around.
Added a visual tracker for the offset.
The trick is understanding the way that scale changes a number of variables. Firstly, it changes how much of the source image is visible on the canvas. Next, this in combination with the desired center-point of the scaling influences where in the image we should start drawing from.
With a scale of 1.0, the number of pixels of the source image shown is equal to the number of pixels that the dst canvas has. I.e, if the canvas is 150x150, we can see 150x150 of the input pixels. If however, the scale is 2.0, then we wish to draw things 2 times the size. This then means that we only wish to display 75x75 pixels of the src image on the 150x150 pixels of the dst canvas. Likewise, if we wish to draw at a scale of 0.5, we should expect to see 300x300 pixels of the src image displayed in the 150x150 of the dst canvas. Perhaps you can see the relationship between scale and canvas size by now.
With this in mind, we can set about determining how much of the src image we wish to see. This is straight-forward:
var srcWidth = canvas.width / scale;
var srcHeight = canvas.height / scale;
Now that we know how much of the image will be shown, we can set about determining where in the image we should start drawing from. Since we have a specified center-point for the scaling, we know that this point should always remain in the center of the canvas.
If we remove scaling from the equation, and use the figures from earlier we can see that we want to display 150x150 pixels of the src image, and that we will need to start drawing 75pixels above and to the left of our center-point. Doing so will draw 150x150 pixels of the source image and place our center-point right smack in the middle of the canvas.
If we then re-consider scaling, we know that we're not always going to be drawing 150x150 pixels of the src image, which means that we can't just blindly start 75pixels left and above our center-point - we will have to scale this 75pixels. Since this 75 pixels is equal to half of the width and half of the height of the portion of the image we'll be displaying, we can work out the point at which to start drawing the image by dividing the srcWidth and srcHeight by 2 and then subtracting this value from the center-point.
Doing so gives us the following expression:
ctx.drawImage(image, imgCenterX-(srcWidth/2), imgCenterY-(srcHeight/2), srcWidth, srcHeight, 0,0, canvas.width, canvas.height);
When I put both of these together into a functioning sample, I ended-up with this:
"use strict";
var imgOriginX = 182, imgOriginY = 66;
function byId(id,parent){return (parent == undefined ? document : parent).getElementById(id);}
window.addEventListener('load', onDocLoaded, false);
function onDocLoaded()
{
var targetCanvas = byId('canvas3');
var srcImage = byId('img1');
drawImageScaled(targetCanvas, srcImage, imgOriginX, imgOriginY)
drawCrosshair( byId('canvas3') );
byId('scaleSlider').addEventListener('input', onScaleSliderChange, false);
}
/*
code for scaling an image about an arbitrary point
*/
// canvas - target canvas element
// image - target canvas element
// imgCenterX - x coord of point of scaling centre-point (unit: pixels)
// imgCenterY - y coord of point of scaling centre-point (unit: pixels)
// scale - 1.0 = 100%
function drawImageScaled(canvas, image, imgCenterX, imgCenterY, scale)
{
if (scale === undefined)
scale = 1.0;
var ctx = canvas.getContext('2d');
ctx.clearRect(0,0,canvas.width,canvas.height);
var srcWidth = canvas.width / scale;
var srcHeight = canvas.height / scale;
ctx.drawImage(image, imgCenterX-(srcWidth/2), imgCenterY-(srcHeight/2), srcWidth, srcHeight, 0,0, canvas.width, canvas.height);
}
function drawCrosshair(canvas)
{
var ctx = canvas.getContext('2d');
var width, height;
width = canvas.width;
height = canvas.height;
ctx.save();
ctx.beginPath();
ctx.moveTo(width/2, 0);
ctx.lineTo(width/2, height);
ctx.moveTo(0, height/2);
ctx.lineTo(width, height/2);
ctx.closePath();
ctx.strokeStyle = "red";
ctx.stroke();
ctx.restore();
}
function onScaleSliderChange(evt)
{
var curValue = this.value;
var scale = curValue / 100;
var tgt, src;
tgt = byId('canvas3');
src = byId('img1');
drawImageScaled(tgt, src, imgOriginX, imgOriginY, scale);
drawCrosshair(tgt);
}
input[type=range]
{
width: 18px;
height: 122px;
-webkit-appearance: slider-vertical;
}
canvas
{
border: solid 1px #888;
}
img{ display:none;}
<img id='img1' src='https://i.stack.imgur.com/aFbEw.png'/>
<hr>
<canvas id='canvas3' width=150 height=150>Canvas not supported. :(</canvas>
<input id='scaleSlider' type="range" class="scale-slider js-scaleSlider" min="0" max="200" value="100" orient="vertical"/>
Here's how pull a specified [eyeX,eyeY] to center canvas and zoom the image:
Pull the eye to canvas [0,0] by multiplying -eyeX & -eyeY by the scaling factor.
Push the eye to center canvas by adding half the canvas width,height.
Scale the image by the scaling factor.
Use context.drawImage to draw the image on the canvas.
Example:
context.drawImage(
// start with the image
img,
// scale the eyeX offset by the scaling factor
// and then push the image horizontally to center canvas
-eyeX*scale + canvas.width/2,
// scale the eyeY offset by the scaling factor
// and then push the image vertically to center canvas
-eyeY*scale + canvas.height/2,
// scale whole image by the scaling factor
canvas.width*scale,
canvas.height*scale
);
Illustrations: Centered Eye at 100% and 175%
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;
function reOffset(){
var BB=canvas.getBoundingClientRect();
offsetX=BB.left;
offsetY=BB.top;
}
var offsetX,offsetY;
reOffset();
window.onscroll=function(e){ reOffset(); }
var eyeX=182;
var eyeY=66;
var scale=1.00;
$myslider=$('#myslider');
$myslider.attr({min:25,max:250}).val(100);
$myslider.on('input change',function(){
scale=parseInt($(this).val())/100;
drawAll(eyeX,eyeY,scale);
});
var iw,ih;
var img=new Image();
img.onload=start;
img.src="https://i.stack.imgur.com/aFbEw.png";
function start(){
iw=cw=canvas.width=img.width;
ih=ch=canvas.height=img.height;
drawAll(eyeX,eyeY,scale);
}
function drawAll(x,y,scale){
ctx.clearRect(0,0,cw,ch);
centerAndZoom(x,y,scale);
drawCrosshairs();
}
function centerAndZoom(x,y,scale){
ctx.drawImage(
img,
-x*scale+iw/2,
-y*scale+ih/2,
iw*scale,
ih*scale
);
}
function drawCrosshairs(){
ctx.beginPath();
ctx.moveTo(cw/2,0);
ctx.lineTo(cw/2,ch);
ctx.moveTo(0,ch/2);
ctx.lineTo(cw,ch/2);
ctx.stroke();
}
body{ background-color: white; }
#canvas{border:1px solid red; margin:0 auto; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
Scale: <input id=myslider type=range><br>
<canvas id="canvas" width=300 height=300></canvas>

Drawing on Canvas

I am drawing a cirlce on canvas who's center points are the middle points of the canvas so the circle should start from the center of the page and its radius should be from 1/2 of the screen to 3/4th. I have figured out the way to make the canvas resize itself according to the window size, but i cant get the cirlce to resize automatically. also that i cant figure out the radius to make it look like a cirlce with my specifications, currently it looks like an stretched circle. What value should i give to my radius to make it look like a normal circle again. also it should be able to resize it self on window resize?
currently i have the following values:
var canvas = document.getElementById('canvas');
if (canvas.getContext){
var context = canvas.getContext('2d');
var centerX = canvas.width / 2;
var centerY = canvas.height / 2;
var myRadius = canvas.width / 4;
context.arc(centerX, centerY, myRadius, 0, (Math.PI/180) * 360 , false);
context.fillStyle = 'green';
context.fill()
}
You should handle resize events and redraw the circle for the new canvas dimensions.
If you will resize your canvas using styles circle will redraws as you expect: fiddle (actually whole canvas will act as just an image: try to change dimensions of canvas from 1000 to 100 and you'll see what I mean)
css:
#canvas {
height: 100%;
width: 100%
}
html:
<canvas id="canvas" height="1000" width="1000"></canvas>

Javascript Canvas Drawing moves out of canvas after translate()

I am having a problem with randomising rectangle locations on the screen. I have a 5 x 5 grid set up with in html and they are all formatted properly. I am then drawing bars into this grid which also works fine. The problem is that when I want to randomly jitter these objects they sometimes move off the canvas (to the left, or to the top only) apparently. I am trying to reposition the canvas via jQuery with no success.
Also, I have the feeling my canvas sizes are wrong but if I set the sizes to 100% in the CSS section, the bars are suddenly very small and I have no idea why.
Here is the interesting piece of the code. the problem is the "jitter" part
function drawStimulus(size, color, orientation, location){
// size for each box, refers to the first box in the first row
// box size should be around 40 on a 22inch screen in this jsfiddle
var box_size = $("#testbox").height();
var stimulus_size = box_size * size; // size is either 1 (large) or 2/3
var size_diff = box_size-stimulus_size;
var c = document.getElementById(location);
c.width = box_size;
c.height = box_size;
// if a perfect grid is not wanted, jitter bars randomly
// by a maximum of 1/3 of the box size in any direction
if(jitter){
var hjitter = rand(-box_size/3, box_size/3);
var vjitter = rand(-box_size/3, box_size/3);
c.style.left = String(c.style.left+hjitter)+"px";
c.style.top = String(c.style.top+vjitter)+"px";
}
var ctx = c.getContext("2d");
// rotate around center
ctx.translate(box_size/2, box_size/2);
ctx.rotate(orientation*Math.PI / 180);
ctx.translate(-box_size/2, -box_size/2);
// draw bars
ctx.beginPath();
ctx.strokeStyle = color;
ctx.moveTo(c.offsetLeft+size_diff/2,c.offsetTop+box_size/2);
ctx.lineTo(c.offsetLeft+stimulus_size+size_diff/2,c.offsetTop+box_size/2);
ctx.lineWidth = size*10;
ctx.closePath();
ctx.stroke();
}
Here is also a jsfiddle with the full code: http://jsfiddle.net/msauter/pdrwwm7x/

Rotate a full size canvas image from a centerpoint without moving other canvas images

the example of the code below can be viewed here - http://dev.touch-akl.com/celebtrations/
What I have been trying to do is draw 2 images onto the canvas (glow and then flare. links for these images are below)
http://dev.touch-akl.com/celebtrations/wp-content/themes/beanstalk/img/flare.jpg
http://dev.touch-akl.com/celebtrations/wp-content/themes/beanstalk/img/blue-background.jpg
The goal is for the 'blue-background' image to sit on the canvas at the height and width of the container, and for the 'flare' image to be drawn ontop of this image with a blending mode and rotated with an animation to create a kind of twinkle effect.
My problem is that because the images I am using are rectangular when the 'flare' rotates at certain points you can see the edges of the layer underneath...
What I tried to do was find the diagonal width of the container using trigonometry and draw the 'flare' image at that width so that it always covered the whole canvas but alas you can still see the background layer at some points (but much less than before).
I need a way for the flare image to always cover the whole canvas, can anyone point me in the right direction please?
var banner = $('#banner'),
flare = document.getElementById('flare'),
glow = document.getElementById('glow'),
canvas = document.getElementById("canvas"),
ctx = canvas.getContext("2d"),
blendMode = "multiply";
$window.load(function(){
_canvasWidth = banner.outerWidth(),
_canvasHeight = banner.outerHeight();
canvas.width = _canvasWidth;
canvas.height = _canvasHeight;
var _flareSum = (_canvasWidth * _canvasWidth) + (_canvasHeight * _canvasHeight);
_flareWidth = Math.sqrt(_flareSum);
_angle = 0;
setInterval(function() {
_angle = _angle +0.25;
// draw the bg without a blend mode
ctx.globalCompositeOperation = "source-over";
ctx.drawImage(glow, 0, 0, _canvasWidth, _canvasHeight);
ctx.save();
// clear the canvas
// ctx.clearRect(0, 0, _canvasWidth, _canvasHeight);
ctx.translate( _canvasWidth/2, _canvasHeight); // move to center point
ctx.globalCompositeOperation = blendMode;
ctx.rotate(Math.PI / 180 * (_angle)); // 1/2 a degree
ctx.drawImage(flare, -_flareWidth/2, -_flareWidth/2, _flareWidth, _flareWidth); // redraw ia=mages
ctx.restore();
//console.log(_angle)
}, 1);
If I understand correctly, you need the shortest part of the flare to still cover the canvas when the flare is rotated at any angle.
Since you're only showing half the flare at any time, the shortest part of the flare is the distance from the flare center to the top of the flare:
var flareMinHeight = flare.height/2;
The longest length the flare must cover is from the flare rotation point to the top-left of the canvas.
var dx=rotationPointX;
var dy=rotationPointY;
var requiredLength=Math.sqrt(dx*dx+dy*dy);
So you will need to scale the flare to be at least the length computed above:
var minScale = requiredLength / flareMinHeight;

Categories

Resources