Crop the image in irregular shape and stretch it - javascript

I found images that depict what is my problem:
User will able to choose four points on canvas to crop the part of image and than stretch it.
How to do that in HTML5? drawImage function (as I know) works only with rectangles (takes x, y, width and height values) so I can't use irregular shape. The solution have to work in every modern browser, so I don't want things based on webgl or something.
EDIT:
More info: this will be app for editing pictures. I want to let user cut some part of bigger picture and edit that. It will be similar to Paint, so canvas is required to edit pixels.

The effect you're going for is "perspective warping".
Canvas's 2D context cannot do this "out-of-the-box" because it can't turn a rectangle into a trapezoid. Canvas 2D can only do affine transforms which can only form parallelograms.
As user #Canvas says, Canvas 3D (webgl) can do the transforms you're going for.
I did this a while back. It uses Canvas 2d and it redraws an image using 1 pixel wide vertical slices which are stretched to "fake" a perspective warp. You are welcome to use it as a starting point for your project.
Example code and a Demo: http://jsfiddle.net/m1erickson/y4kst2pk/
<!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 ctx=canvas.getContext("2d");
var $canvas=$("#canvas");
var canvasOffset=$canvas.offset();
var offsetX=canvasOffset.left;
var offsetY=canvasOffset.top;
var scrollX=$canvas.scrollLeft();
var scrollY=$canvas.scrollTop();
//
var isDown=false;
var PI2=Math.PI*2;
var selectedGuide=-1;
var guides=[];
//
var marginLeft=50;
var marginTop=50;
var iw,ih,cw,ch;
var img=new Image();
img.onload=start;
img.src='https://dl.dropboxusercontent.com/u/139992952/stack1/buildings1.jpg';
function start(){
iw=img.width;
ih=img.height;
canvas.width=iw+100;
canvas.height=ih+100;
cw=canvas.width;
ch=canvas.height;
ctx.strokeStyle="blue";
ctx.fillStyle="blue";
guides.push({x:0,y:0,r:10});
guides.push({x:0,y:ih,r:10});
guides.push({x:iw,y:0,r:10});
guides.push({x:iw,y:ih,r:10});
//
$("#canvas").mousedown(function(e){handleMouseDown(e);});
$("#canvas").mousemove(function(e){handleMouseMove(e);});
$("#canvas").mouseup(function(e){handleMouseUp(e);});
$("#canvas").mouseout(function(e){handleMouseOut(e);});
drawAll();
}
function drawAll(){
ctx.clearRect(0,0,cw,ch);
drawGuides();
drawImage();
}
function drawGuides(){
for(var i=0;i<guides.length;i++){
var guide=guides[i];
ctx.beginPath();
ctx.arc(guide.x+marginLeft,guide.y+marginTop,guide.r,0,PI2);
ctx.closePath();
ctx.fill();
}
}
function drawImage(){
// TODO use guides
var x1=guides[0].x;
var y1=guides[0].y;
var x2=guides[2].x;
var y2=guides[2].y;
var x3=guides[1].x;
var y3=guides[1].y;
var x4=guides[3].x;
var y4=guides[3].y;
// calc line equations slope & b (m,b)
var m1=Math.tan( Math.atan2((y2-y1),(x2-x1)) );
var b1=y2-m1*x2;
var m2=Math.tan( Math.atan2((y4-y3),(x4-x3)) );
var b2=y4-m2*x4;
// draw vertical slices
for(var X=0;X<iw;X++){
var yTop=m1*X+b1;
var yBottom=m2*X+b2;
ctx.drawImage( img,X,0,1,ih,
X+marginLeft,yTop+marginTop,1,yBottom-yTop );
}
// outline
ctx.save();
ctx.translate(marginLeft,marginTop);
ctx.beginPath();
ctx.moveTo(x1,y1);
ctx.lineTo(x2,y2);
ctx.lineTo(x4,y4);
ctx.lineTo(x3,y3);
ctx.closePath();
ctx.strokeStyle="black";
ctx.stroke();
ctx.restore();
}
function handleMouseDown(e){
e.preventDefault();
var mouseX=parseInt(e.clientX-offsetX);
var mouseY=parseInt(e.clientY-offsetY);
// Put your mousedown stuff here
selectedGuide=-1;
for(var i=0;i<guides.length;i++){
var guide=guides[i];
var dx=mouseX-(guide.x+marginLeft);
var dy=mouseY-(guide.y+marginTop);
if(dx*dx+dy*dy<=guide.r*guide.r){
selectedGuide=i;
break;
}
}
isDown=(selectedGuide>=0);
}
function handleMouseUp(e){
e.preventDefault();
isDown=false;
}
function handleMouseOut(e){
e.preventDefault();
isDown=false;
}
function handleMouseMove(e){
if(!isDown){return;}
e.preventDefault();
var x=parseInt(e.clientX-offsetX)-marginLeft;
var y=parseInt(e.clientY-offsetY)-marginTop;
var guide=guides[selectedGuide];
guides[selectedGuide].y=y;
if(selectedGuide==0 && y>guides[1].y){guide.y=guides[1].y;}
if(selectedGuide==1 && y<guides[0].y){guide.y=guides[0].y;}
if(selectedGuide==2 && y>guides[3].y){guide.y=guides[3].y;}
if(selectedGuide==3 && y<guides[2].y){guide.y=guides[2].y;}
drawAll();
}
}); // end $(function(){});
</script>
</head>
<body>
<h4>Perspective Warp by vertically dragging left or right blue guides.</h4>
<canvas id="canvas" width=300 height=300></canvas>
</body>
</html>

So here's a killing trick : You can use the regular drawImage of the context2d to draw a texture inside a triangle.
The constraint is that the texture coordinates must be axis-aligned.
To draw a textured triangle you have to :
• Compute by yourself the transform required to draw the image.
• Clip to a triangle, since drawImage would draw a quad.
• drawImage with the right transform.
So the idea is to split your quad into two triangles, and render both.
But there's one more trick : when drawing the lower-right triangle, texture reading should start from the lower-right part of the texture, then move up and left. This can't be done in Firefox, which accepts only positive arguments to drawImage. So i compute the 'mirror' of the first point vs the two others, and i can draw in the regular direction again -the clipping will ensure only right part is drawn-.
fiddle is here :
http://jsfiddle.net/gamealchemist/zch3gdrx/
function rasterizeTriangle(v1, v2, v3, mirror) {
var fv1 = {
x: 0,
y: 0,
u: 0,
v: 0
};
fv1.x = v1.x;
fv1.y = v1.y;
fv1.u = v1.u;
fv1.v = v1.v;
ctx.save();
// Clip to draw only the triangle
ctx.beginPath();
ctx.moveTo(v1.x, v1.y);
ctx.lineTo(v2.x, v2.y);
ctx.lineTo(v3.x, v3.y);
ctx.clip();
// compute mirror point and flip texture coordinates for lower-right triangle
if (mirror) {
fv1.x = fv1.x + (v3.x - v1.x) + (v2.x - v1.x);
fv1.y = fv1.y + (v3.y - v1.y) + (v2.y - v1.y);
fv1.u = v3.u;
fv1.v = v2.v;
}
//
var angleX = Math.atan2(v2.y - fv1.y, v2.x - fv1.x);
var angleY = Math.atan2(v3.y - fv1.y, v3.x - fv1.x);
var scaleX = lengthP(fv1, v2);
var scaleY = lengthP(fv1, v3);
var cos = Math.cos,
sin = Math.sin;
// ----------------------------------------
// Transforms
// ----------------------------------------
// projection matrix (world relative to center => screen)
var transfMatrix = [];
transfMatrix[0] = cos(angleX) * scaleX;
transfMatrix[1] = sin(angleX) * scaleX;
transfMatrix[2] = cos(angleY) * scaleY;
transfMatrix[3] = sin(angleY) * scaleY;
transfMatrix[4] = fv1.x;
transfMatrix[5] = fv1.y;
ctx.setTransform.apply(ctx, transfMatrix);
// !! draw !!
ctx.drawImage(bunny, fv1.u, fv1.v, v2.u - fv1.u, v3.v - fv1.v,
0, 0, 1, 1);
//
ctx.restore();
};
Edit : i added the relevant comment of #szym , with his example picture :
This only sort of looks right. If there are any straight lines in the
original image you will see that each triangle is warped differently
(2 different affine transforms rather than a perspective transform).

You need to have a container with perspective and perspective-origin set
You need to use rotateY, skewY and change your heights and width on your image
There is probably a lot of math behind this - personally I just fiddle with it in my browser to make it look pretty close to what I need
So here is a fiddle:
http://jsfiddle.net/6egdevwe/1/
#container {
margin: 50px;
perspective: 166px; perspective-origin: 50% 0px; }
#testimage {
transform: rotateY(93.4deg) skewY(34deg);
width: 207px;
height: 195px; }

css3 transform -> rotation or rotationZ
http://www.w3schools.com/cssref/css3_pr_transform.asp

Related

Draw fixed boxes on HTML canvas image [duplicate]

I'm tyring to rotate a diagram in canvas around its center while keeping the letters upright. I'm trying to use ctx.rotate(#) but it's rotating the entire diagram using what appears to be the left side of the canvas as the center.
The following link offers a visual: I want it to look like the green, not the red, as it currently does with my code. Visual Explanation
The following is the JSFiddle: http://jsfiddle.net/ddxarcag/143/
And my code is below:
<script>
$(document).ready(function () {
init();
function init() {
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
draw(ctx);
}
function draw(ctx) {
// layer1/Line
ctx.rotate(00);
ctx.beginPath();
ctx.moveTo(75.1, 7.7);
ctx.lineTo(162.9, 7.7);
ctx.stroke();
function WordSelector1() {
var word = ['A', 'B', 'C'];
var random = word[Math.floor(Math.random() * word.length)];
return random;
}
var x = WordSelector1();
// layer1/P
ctx.font = "12.0px 'Myriad Pro'";
ctx.rotate(0);
ctx.fillText(x, 60.0, 10.0);
}
});
</script>
Any help would be much appreciated. Thanks!
Drawing rotated graphs in canvas is somewhat easy once you know how to move the origin (0,0) to another point and then rotating the axes around it.
I added some comments in the code as not to repeat code and explanations.
I also moved the functions out of the $(document).ready and changed some numbers for more rounded values.
$(document).ready(function () {
init();
});
function init() {
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
draw(ctx);
}
function draw(ctx) {
ctx.font = "12.0px 'Myriad Pro'";
var angle = Math.random()*150; //Run more times to see other angles
//This translates the 0,0 to the center of the horizontal line
ctx.translate(100, 100);
//This draws the original straight line
ctx.beginPath();
ctx.moveTo(-50, 0); //The coordinates are relative to the new origin
ctx.lineTo(50, 0);
ctx.stroke();
//Draw the first letter
var x = WordSelector1();
ctx.fillText(x, -60, 0);
//This section draws the rotated line with the text straight
//Rotate the canvas axes by "angle"
ctx.rotate(angle * Math.PI/180);
ctx.beginPath();
ctx.moveTo(-50, 0); //The relative coordinates DO NOT change
ctx.lineTo(50, 0); //This shows that the axes rotate, not the drawing
ctx.stroke();
var x = WordSelector1();
ctx.translate(-60,0); //The origin must now move where the letter is to be placed
ctx.rotate(-angle * Math.PI/180); //Counter-rotate by "-angle"
ctx.fillText(x, 0, 0); //Draw the letter
}
function WordSelector1() {
var word = ['A', 'B', 'C'];
var random = word[Math.floor(Math.random() * word.length)];
return random;
}
canvas{
border: 1px solid;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<canvas id="canvas" width="200" height="200"></canvas>
One warning: after everything is drawn the axes end parallel to the canvas borders because it was rotated by -angle, but the origin is where the last letter was placed. You may want to use ctx.save() and ctx.restore() to avoid having to revert translations and rotations.

Moving a clipped canvas

How would I go by if I wanted to move my clipped canvas? I need to move them to a specific area, but I'm not sure where to put the coordinates. I'm still very new to this, and would appreciate any help I can get.
I know I can make a "var" for the position, but since I have 6 pieces to move that would mean an awful lot of var's. Is there a simpler way?
Code:
<script>
var can=document.getElementById("NewCanvas");
var Jctx=can.getContext("2d");
var ctx=can.getContext("2d");
var img = new Image();
img.onload = function() {
ctx.moveTo(305,307);
ctx.lineTo(560,152);
ctx.lineTo(450,10);
ctx.lineTo(305,10);
ctx.closePath();
ctx.clip();
ctx.drawImage(img,0,0); // new image is clipped;
}
img.src='Prototype22.png';
</script>
Here's how to move a clipped area to a desired offset.
Put your clipping coordinates in an array:
var clips=[{x:305,y:307},{x:560,y:152},{x:450,y:10},{x:305,y:10}];
Then pass the image, clipping-coordinates & desired offset [x,y] to a function that does this:
Calculates the minimum x & y in the clipping coordinates array.
Translates (moves the whole canvas) to -minX+offsetX & -minY+offsetY. This will both normalize the clipped area to [0,0] and also then move that normalized area to the desired offset coordinate.
Defines the clipping path using the unaltered clipping coordinates.
Creates the clipping area with context.clip
Draws the image at [0,0]
The result will be a clipped area of the image that has been moved to the desired offset.
If you have multiple clips then you can create multiple arrays of clipping coordinates and pass them each into drawClippedImgAtXY.
Here is example code and a Demo:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
var clips=[{x:305,y:307},{x:560,y:152},{x:450,y:10},{x:305,y:10}];
var img = new Image();
img.onload = function() {
drawClippedImgAtXY(img,clips,100,20)
}
img.src='https://dl.dropboxusercontent.com/u/139992952/stackoverflow/city-q-c-640-480-5.jpg';
function drawClippedImgAtXY(img,clipPts,x,y){
var minX=10000000;
var minY=10000000;
for(var i=0;i<clipPts.length;i++){
var pt=clipPts[i];
if(pt.x<minX){minX=pt.x;}
if(pt.y<minY){minY=pt.y;}
}
ctx.save();
ctx.translate(-minX+x,-minY+y);
ctx.moveTo(clipPts[0].x,clipPts[0].y);
for(var i=1;i<clipPts.length;i++){
ctx.lineTo(clipPts[i].x,clipPts[i].y);
}
ctx.clip();
ctx.drawImage(img,0,0);
ctx.restore();
}
body{ background-color: ivory; }
#canvas{border:1px solid red; margin:0 auto; }
<h4>Move a clipped area to x==100, y==20</h4>
<canvas id="canvas" width=600 height=400></canvas>

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>

Draw image to canvas with points

I want to draw simple rectangle image to canvas. I have a four point like a;
(0) 345,223
(1) 262,191
(2) 262,107
(3) 347,77
Rendered rectangle and image are bellow;
What is the best practice to do this?
Well that was some fun. Haven't done software texture mapping in over 10 years. Nostalgia is great, but openGL is better. :D
Basically, the idea is to draw vertical slices of the image. The ctx only lets us draw images or parts of them with vertical or horizontal stretching. So, to get around this, we divide the image up into vertical slices, stretching each of them to fill a rectangle 1 pixel wide and from the top edge to the bottom edge.
First, we calculate the slope of the top and bottom edges. This corresponds to the amount that the edge rises (or falls) for each pixel travelled in the +X direction. Next, since the image may be larger or smaller than the are it will be draw onto, we must calculate how wide the strips are that correspond to 1 pixel in the X direction in the canvas.
Note, it isn't perspective-correct. Each step to the right on the canvas represents a step of the same width slice on the image - perspective correct mapping would step by varying amounts across the width of the image. Less as the image got closer, more as the image was further away from us.
Finally, it should be noted that there are a few assumptions made about the entered coordinates.
The coords appear as pairs of x and y
The coords list starts with the top-left corner
The coords must be listed in a clockwise direction
The left-edge and the right-edge must be vertical.
With these assumptions adhered to, I get the following:
Result
Code:
<!DOCTYPE html>
<html>
<head>
<script>
function byId(e){return document.getElementById(e);}
function newEl(tag){return document.createElement(tag);}
window.addEventListener('load', onDocLoaded, false);
function onDocLoaded()
{
var mImg = newEl('img');
mImg.onload = function() { stretchImage(this, quadPoints, byId('tgtCanvas') ); }
mImg.src = imgSrc;
}
var quadPoints = [ [262,107], [347,77], [347,223], [262,191] ];
var imgSrc = "img/rss128.png";
function stretchImage(srcImgElem, points, canvasElem)
{
var ctx = canvasElem.getContext('2d');
var yTopStart = points[0][1];
var yTopEnd = points[1][1];
var tgtWidth = points[1][0] - points[0][0];
var dX = tgtWidth;
var topDy = (yTopEnd-yTopStart) / dX;
var yBotStart = points[3][1];
var yBotEnd = points[2][1];
tgtWidth = points[2][0] - points[3][0];
dX = tgtWidth;
var botDy = (yBotEnd-yBotStart) / dX;
var imgW, imgH, imgDx;
imgW = srcImgElem.naturalWidth;
imgH = srcImgElem.naturalHeight;
imgDx = imgW / dX;
var curX, curYtop, curYbot, curImgX;
var i = 0;
// ctx.beginPath();
for (curX=points[0][0]; curX<points[1][0]; curX++)
{
curYtop = yTopStart + (i * topDy);
curYbot = yBotStart + (i * botDy);
curImgX = i * imgDx;
// ctx.moveTo(curX, curYtop);
// ctx.lineTo(curX, curYbot);
var sliceHeight = curYbot - curYtop;
// console.log(sliceHeight);
ctx.drawImage(srcImgElem, curImgX, 0, 1,imgH, curX, curYtop, imgDx, sliceHeight);
i++;
}
// ctx.closePath();
// ctx.stroke();
}
</script>
<style>
canvas
{
border: solid 1px black;
}
</style>
</head>
<body>
<canvas width=512 height=512 id='tgtCanvas'></canvas>
</body>
</html>
Src image:

Rotating a canvas display depending on where the user clicks

I have a html5 canvas display that i would like to rotate once a user clicks. I am trying to figure out the best way to allow the animation take effect. I would like the rotation to be smooth and no matter where the user clicks the selected panel would rotate to the top.
I am not sure of the best way to go about this since this is my very first canvas project, and I figured i would open the floor to all of you.
Here is a fiddle: http://jsfiddle.net/JRgtg/
Thanks in advance for the help!
Your task is fairly complex:
create an object for each arc segment around the circle
save the arc objects in an arcs[] array
create a function that draws a specified arc at a specified angle
(the arc is drawn as a path)
listen for mousedown events
in the mousedown handler, use context.isPointInPath to see if user clicked on an arc-segment-path
if clicked, rotate the clicked arc to the top of the circle using animation
Illustration of arc positions before and after the gold arc was clicked.
The gold arc rotated to the top:
Commented Example code and a Demo: http://jsfiddle.net/m1erickson/ZUtL8/
<!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(){
// canvas and context reference variables
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var $canvas=$("#canvas");
var canvasOffset=$canvas.offset();
var offsetX=canvasOffset.left;
var offsetY=canvasOffset.top;
// save PI to variables since they are used often
var PI=Math.PI;
var PI2=Math.PI*2;
// animation variables
var rotation;
var desiredRotation;
var deltaRotation=PI/120; // rotate at about 360 degrees over 2 seconds
// define a color for each segment
var colors=["red","green","blue","gold","purple"];
var topAngle=clampAngle(PI*3/2-(PI2/colors.length)/2);
var gapAngle=2*PI/180; // 3 degree gap between arcs
// hold the arc objects in an arcs[] array
var arcs=createArcs(150,150,50,75,colors);
// draw the arcs
for(var i=0;i<arcs.length;i++){
drawArc(arcs[i],true);
}
// listen for mouse events
$("#canvas").mousedown(function(e){handleMouseDown(e);});
// utility function
// make sure angles are expressed between 0 & 2*PI
function clampAngle(a){
return((a+PI2*2)%PI2);
}
// create arc objects for each color
function createArcs(cx,cy,insideRadius,outsideRadius,colors){
var arcs=[];
for(var i=0;i<colors.length;i++){
var a1=clampAngle(i*PI2/colors.length+topAngle);
var a2=clampAngle(a1+PI2/colors.length-gapAngle);
arcs.push({
segment:i,
x:cy,
y:cy,
r1:insideRadius,
r2:outsideRadius,
a1:a1,
a2:a2,
color:colors[i],
rotation:0
});
}
return(arcs);
}
// draw one arc
function drawArc(arc,draw){
var x = arc.x + arc.r1 * Math.cos(arc.a2);
var y = arc.y + arc.r1 * Math.sin(arc.a2);
// define
ctx.beginPath();
ctx.arc(arc.x,arc.y,arc.r2,arc.a1,arc.a2);
ctx.lineTo(x, y);
ctx.arc(arc.x,arc.y,arc.r1,arc.a2,arc.a1,true);
ctx.closePath();
//
if(draw){
ctx.fillStyle=arc.color;
ctx.fill();
}
}
// handle mouse events
function handleMouseDown(e){
e.preventDefault();
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// hit test each arc
var hit=-1;
for(var i=0;i<arcs.length;i++){
// define the target arc
drawArc(arcs[i]);
if(ctx.isPointInPath(mouseX,mouseY)){
hit=i;
}
}
// if use clicked on arc, rotate it to the top
if(hit>=0){
rotation=0;
desiredRotation=clampAngle(topAngle-arcs[hit].a1);
animate();
}
}
// animate the rotation of the clicked arc
function animate(){
// stop animating if the arc has been rotated to the top
if(rotation<=desiredRotation){ requestAnimationFrame(animate); }
if(rotation>desiredRotation){ rotation=desiredRotation; }
// clear the canvas
ctx.clearRect(0,0,canvas.width,canvas.height);
// add a rotation increment to each arc
for(var i=0;i<arcs.length;i++){
var arc=arcs[i];
arc.a1=clampAngle(arc.a1+deltaRotation);
arc.a2=clampAngle(arc.a2+deltaRotation);
drawArc(arc,true);
}
// increase the rotation angle by the rotation increment
rotation=clampAngle(rotation+deltaRotation);
}
}); // end $(function(){});
</script>
</head>
<body>
<h4>Click on a color-arc and it will rotate to top.</h4>
<canvas id="canvas" width=300 height=300></canvas>
</body>
</html>

Categories

Resources