I have a large canvas containing an image, as shown in the example below:
I have the position and rotation angle of the red rectangle:
red : {
top : top,
left : left,
width : width,
height : height,
angle : angle
}
I also have a full set of translated coordinates denoting the actual corner points of the red rotated rectangle.
Finally, I have the position of the blue rectangle relative to the red rectangle:
blue : {
left : left,
top : top,
width : width,
height : height
}
What I need to do is create a new canvas that is the size of the blue rectangle. The new canvas should contain the correctly rotated portion of the image that is contained within the blue rectangle. The resulting image should look like this:
Here is my JavaScript code so far:
var c = getCenterPoint(); // returns center x/y positions of the RED rectangle
canvas.width = blue.width;
canvas.height = blue.height;
var blueX = red.left + blue.left;
var blueY = red.top + blue.top;
var tx = blueX - c.x;
var ty = blueY - c.y;
this.cursorContext.translate(tx, ty);
this.cursorContext.rotate(angle * (Math.PI / 180));
this.cursorContext.translate(-tx, -ty);
this.cursorContext.drawImage(image, -blueX, -blueY, blue.width, blue.height);
The problem I am having is getting the correct portion of the image when the rectangle is rotated. How can I do this?
You can use a temporary canvas to clip and unrotate your blue box
Clip the boundingbox of the blue rectangle from the image
Unrotate the boundingbox so the blue rectangle is unrotated (angle==0)
Clip the extra boundingbox area away to reveal only the blue rectangle
Draw the blue rectangle to the display canvas
Here’s code and a Demo: http://jsfiddle.net/m1erickson/28EkG/
<!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");
// blue rect's info
var blueX=421;
var blueY=343;
var blueWidth=81;
var blueHeight=44;
var blueAngle=-25.00*Math.PI/180;
// load the image
var img=new Image();
img.onload=start;
img.src="https://dl.dropboxusercontent.com/u/139992952/stackoverflow/temp6.jpg";
function start(){
// create 2 temporary canvases
var canvas1=document.createElement("canvas");
var ctx1=canvas1.getContext("2d");
var canvas2=document.createElement("canvas");
var ctx2=canvas2.getContext("2d");
// get the boundingbox of the rotated blue box
var rectBB=getRotatedRectBB(blueX,blueY,blueWidth,blueHeight,blueAngle);
// clip the boundingbox of the rotated blue rect
// to a temporary canvas
canvas1.width=canvas2.width=rectBB.width;
canvas1.height=canvas2.height=rectBB.height;
ctx1.drawImage(img,
rectBB.cx-rectBB.width/2,
rectBB.cy-rectBB.height/2,
rectBB.width,
rectBB.height,
0,0,rectBB.width,rectBB.height
);
// unrotate the blue rect on the temporary canvas
ctx2.translate(canvas1.width/2,canvas1.height/2);
ctx2.rotate(-blueAngle);
ctx2.drawImage(canvas1,-canvas1.width/2,-canvas1.height/2);
// draw the blue rect to the display canvas
var offX=rectBB.width/2-blueWidth/2;
var offY=rectBB.height/2-blueHeight/2;
canvas.width=blueWidth;
canvas.height=blueHeight;
ctx.drawImage(canvas2,-offX,-offY);
} // end start
// Utility: get bounding box of rotated rectangle
function getRotatedRectBB(x,y,width,height,rAngle){
var absCos=Math.abs(Math.cos(rAngle));
var absSin=Math.abs(Math.sin(rAngle));
var cx=x+width/2*Math.cos(rAngle)-height/2*Math.sin(rAngle);
var cy=y+width/2*Math.sin(rAngle)+height/2*Math.cos(rAngle);
var w=width*absCos+height*absSin;
var h=width*absSin+height*absCos;
return({cx:cx,cy:cy,width:w,height:h});
}
}); // end $(function(){});
</script>
</head>
<body>
<canvas id="canvas" width=300 height=300></canvas>
</body>
</html>
Related
I have a bunch of HTML elements that I want to connect with lines via Canvas. Here's a mockup of what I'm trying to achieve:
Currently, I just have the lines, with no text. I want to place text halfway between each line, but seeing as they're diagonals I'm not sure how to do it.
Current code:
// 'connectors' is an array of points corresponding to
// the middle of each big blue buttons' x-value
ctx.clearRect(0,0,canvas.width,canvas.height);
for(var i=0;i<connectors.length;i++){
var wpoint = connectors[i];
var pos1 = {w: wpoint, h: 0};
var pos2 = {w: canvas.width / 2, h: canvas.height};
ctx.beginPath();
ctx.moveTo(pos1.w,pos1.h);
ctx.lineTo(pos2.w,pos2.h);
ctx.stroke();
// Write Text Halfway
ctx.fillStyle = "blue";
ctx.font = "bold 16px Arial";
ctx.fillText("2702", 100, canvas.height / 2);
// No idea what to put as the x value here
}
What's the best way to achieve this? Potentially drawing half the line, writing the text, then drawing the rest of the line?
EDIT: Perhaps a better title / question would be: How do I find the midpoint between two arbitrary points in HTML Canvas? I want to draw text there.
Here's how:
Calculate the line's midpoint
Draw the line
Erase the line at its midpoint
Tell canvas to horizontally & vertically center any drawn text around a specified [x,y]
Draw the text at the midpoint
Here's annotated code and a Demo:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var line={x0:20,y0:20,x1:150,y1:150};
textAtMidLine(line,'2702','verdana',14)
function textAtMidLine(line,text,fontface,fontsize){
// save the unmodified context state
ctx.save();
// calc line's midpoint
var midX=line.x0+(line.x1-line.x0)*0.50;
var midY=line.y0+(line.y1-line.y0)*0.50;
// calc width of text
ctx.font=fontsize+'px '+fontface;
var textwidth=ctx.measureText(text).width;
// draw the line
ctx.beginPath();
ctx.moveTo(line.x0,line.y0);
ctx.lineTo(line.x1,line.y1);
ctx.lineWidth=2;
ctx.strokeStyle='lightgray';
ctx.stroke();
// clear the line at the midpoint
ctx.globalCompositeOperation='destination-out'; // "erases"
ctx.fillRect(midX-textwidth/2,midY-fontsize/2,textwidth,fontsize*1.286);
ctx.globalCompositeOperation='source-over'; // reset to default
// tell canvas to horizontally & vertically center text around an [x,y]
ctx.textAlign='center';
ctx.textBaseline='middle'
// draw text at the midpoint
ctx.fillText(text,midX,midY);
// restore the unmodified context state
ctx.restore();
}
body{ background-color: ivory; }
#canvas{border:1px solid red; margin:0 auto; }
<canvas id="canvas" width=300 height=300></canvas>
I tried to create an HTML canvas, place a rectangle... then INSIDE THAT rectangle, draw various shapes and an RGBa PNG... all of them clipped inside the dimensions of the rectangle. Then I tried to change the color of the PNG when you press an HTML button input. (Further comments in code.)
Heres the problems... A. You have to draw to a temporary canvas and apply "GlobalCompositeOperation source-atop" just AFTER the clipping rectangle. Everything drawn after that is successfully clipped into the rect shape. Then the whole thing is copied (drawn) to a MAIN canvas. I was told to do it this way in order for the programming to recognize MULTIPLE elements after a "composite" operation. I have to say this works BEAUTIFULLY!! but here's problem B...
To to a "getData" on an image (to change color), I think you have to place the image on a canvas, and doing all the image pixel manipulation screws up the "composite" operation, so I tried to draw the PNG to a THIRD canvas, do the pixel changes, and then draw it to the temporary canvas... adding it to the rest of the elements....THEEENNN draw it all to the main canvas. Does not work. See code. Please help, Im mad enough to chew neutronium.
<!DOCTYPE HTML>
<html>
<head>
</head>
<body>
<canvas id="theCanvas" width="200" height="200" style="border:2px solid #000000;"></canvas>
<canvas id="tempCanvas" width="200" height="200" style="display:none;"></canvas>
<canvas id="anotherCanvas" width="200" height="200" style="display:none;"></canvas>
<form>
<input type="button" id="changeColor" value="Click to Change Color of Graphic">
</form>
<script type="text/javascript" src="hereWeGoAgain_GHELP.js"></script>
</body>
</html>
//------------------------------------- JS
window.addEventListener("load", eventWindowLoaded, false);
function eventWindowLoaded () {
canvasApp();
}
function canvasApp() {
var canvas = document.getElementById('theCanvas');// the main canvas, where it all goes in the end
var context = canvas.getContext('2d');
var tempCanvas = document.getElementById('tempCanvas');// the canvas to do my "source-atop" stuff...
var tempContext = tempCanvas.getContext('2d');
var anotherCanvas = document.getElementById('anotherCanvas');
var anotherContext = anotherCanvas.getContext('2d');
// ...and Im thinking I should draw the RGBA PNG here, before placing it in the temp canvas, with the other elements
var cc = document.getElementById('changeColor');
cc.addEventListener('click', function(){changeColorFunction('ff0000');}, false);
// the HTML form button to change the PNG color
var colorOfThePlacedPNG = "#000000";
var imagesToLoad = 0;
var imagesLoaded = 0;
function drawScreen() {
tempContext.fillStyle="#999999";
tempContext.fillRect(0,0,200,200); //color the whole temp canvas area grey....
tempContext.fillStyle="#2baae1";
tempContext.fillRect(30,30,140,140);//now draw a light blue rect inside....
tempContext.globalCompositeOperation="source-atop"; // now make everything drawn AFTERWARDS be clipped (masked) inside the blue rect
// when I comment out the above "global Comp Op"... everything draws to the main canvas normally...just not clipped(masked) however
tempContext.fillStyle="#f47e1f";
tempContext.fillRect(150,100,150,150);//SO heres an orange box intentionally clipped off the bottom right in the blue rect
tempContext.fillStyle="#d89bc5";
tempContext.fillRect(40,50,80,200);//AND heres a light purple rect intentionally clipped at the bottom of the blue rect
getTheImageData(); //draw PNG to another canvas, convert image data, put in tempContext
//tempContext.restore();//dont know if I need this
context.drawImage(tempCanvas, 0, 0);// and then FINALLY draw all to the main canvas
}
var loaded = function(){
imagesLoaded += 1;
if(imagesLoaded === imagesToLoad){
drawScreen();
}
}
var loadImage = function(url){
var image = new Image();
image.addEventListener("load",loaded);
imagesToLoad += 1;
image.src = url;
return image;
}
function changeColorFunction(e) {
colorOfThePlacedPNG = e;
drawScreen();
}
function getTheImageData(){
anotherContext.drawImage(testPNGimage, 0, 0);// draw to the third canvas(another canvas)
var imgData = anotherContext.getImageData(0, 0, 200, 200);
// how do i color it red? ....like #ff0000 ???
var i;
for (i = 0; i < imgData.data.length; i += 4) {
imgData.data[i] = 255 - imgData.data[i];
imgData.data[i+1] = 255 - imgData.data[i+1];
imgData.data[i+2] = 255 - imgData.data[i+2];
imgData.data[i+3] = 255;
}
tempContext.putImageData(imgData, 0, 0);
}
var testPNGimage = loadImage("test.png");// the PNG is just a 75X75px black squiggle drawn in pshop
}
You're overcomplicating things!
There is a clipping method built into canvas.
Use clipping instead of compositing and multiple canvases.
Do this and all new drawings will be clipped inside your 140x140 rect:
context.beginPath();
context.rect(30,30,140,140);
context.clip();
Here's a simplified redesign of your code:
Draw a grey rect filling the canvas.
Draw a blue 140x140 rect at [30,30].
Clip all new drawings into the blue rect with context.clip()
Draw a clipped orange rect.
Draw a clipped purple rect.
Unclip so new drawings will be visible anywhere on the canvas.
Draw the squiggle image (it's not clipped).
Use .getImageData to invert every pixel's color.
And a Demo:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
var img=new Image();
img.crossOrigin='anonymous';
img.onload=start;
img.src="https://dl.dropboxusercontent.com/u/139992952/multple/squiggle.png";
function start(){
//color the whole canvas area grey....
ctx.fillStyle="#999999";
ctx.fillRect(0,0,200,200);
//now draw a light blue rect inside....
ctx.fillStyle="#2baae1";
ctx.beginPath();
ctx.rect(30,30,140,140);
ctx.fill();
// save the unclipped context state
ctx.save();
// cause all new drawings to be clipped inside
// the blue 140x140 rect at [30,30]
ctx.clip();
//SO heres an orange box intentionally clipped off the bottom right in the blue rect
ctx.fillStyle="#f47e1f";
ctx.fillRect(150,100,150,150);
//AND heres a light purple rect intentionally clipped at the bottom of the blue rect
ctx.fillStyle="#d89bc5";
ctx.fillRect(40,50,80,200);
// restore the context state (releases clipping for new drawings)
ctx.restore();
// draw the squiggley line image -- it's not clipped in the blue rect
ctx.drawImage(img,0,0);
// invert the colors using getImageData
var imgData = ctx.getImageData(0, 0, 200, 200);
var i;
for (i = 0; i < imgData.data.length; i += 4) {
imgData.data[i] = 255 - imgData.data[i];
imgData.data[i+1] = 255 - imgData.data[i+1];
imgData.data[i+2] = 255 - imgData.data[i+2];
imgData.data[i+3] = 255;
}
ctx.putImageData(imgData, 0, 0);
}
body{ background-color: ivory; }
#canvas{border:1px solid red; margin:0 auto; }
<canvas id="canvas" width=200 height=200></canvas>
I have an image which is being displayed by a canvas and I have coordinates like this
428,361,432,374,475,399,470,472,420,
494,400,486,328,461,183,409,176,386,
178,356,182,345,270,309,192,288,072,
257,069,228,069,219,077,212,112,205,
133,207,185,201,213,203,277,215,291,
220,298,222,323,225,388,235,418,244,
412,269,393,312,387,332,388,349,407,
356,414,360
which are in an <area> tag format.
What I want to do is to extract that area from my image and display it (that extracted area) inside another canvas.
How can I do that?
Thanks
Your area points are actually point-pairs of a polygon. That polygon is the desired area of the source image.
Here's how to extract the area mapped pixels from the source image:
Calculate the bounding box of the area coordinates: minX,minY & maxX,maxY
Resize the destination canvas to the size of the bounding box:
width=maxX-minX, height=maxY-minY
Create a clipping path on the destination. The clipping path is the original area moved to the top-left of the destination canvas.
// draw the clipping path
ctx.beginPath();
ctx.moveTo(pointpairs[0]-minX,pointpairs[1]-minY);
for(var i=2;i<pointpairs.length;i+=2){
var x=pointpairs[i];
var y=pointpairs[i+1];
ctx.lineTo(x-minX,y-minY);
}
ctx.closePath();
ctx.clip();
Draw the image onto the destination canvas, but offset by minY & minY
ctx.drawImage(img,-minX,-minY);
Here's example code and a Demo:
var srcCanvas=document.getElementById("source");
var sctx=srcCanvas.getContext("2d");
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var pointpairs=[
428,361,432,374,475,399,470,472,420,
494,400,486,328,461,183,409,176,386,
178,356,182,345,270,309,192,288,072,
257,069,228,069,219,077,212,112,205,
133,207,185,201,213,203,277,215,291,
220,298,222,323,225,388,235,418,244,
412,269,393,312,387,332,388,349,407,
356,414,360];
var minX=1000000;
var minY=minX;
var maxX=-1000000;
var maxY=maxX;
for(var i=0;i<pointpairs.length;i+=2){
var x=pointpairs[i];
var y=pointpairs[i+1];
if(x<minX){minX=x;}
if(y<minY){minY=y;}
if(x>maxX){maxX=x;}
if(y>maxY){maxY=y;}
}
var img=new Image();
img.onload=start;
img.src='https://dl.dropboxusercontent.com/u/139992952/multple/transportation.jpg';
function start(){
srcCanvas.width=img.width;
srcCanvas.height=img.height;
sctx.drawImage(img,0,0);
// draw the source image
// and also stroke the text area
sctx.lineWidth=2;
sctx.strokeStyle='blue';
sctx.beginPath();
sctx.moveTo(pointpairs[0],pointpairs[1]);
for(var i=2;i<pointpairs.length;i+=2){
var x=pointpairs[i];
var y=pointpairs[i+1];
sctx.lineTo(x,y);
}
sctx.closePath();
sctx.stroke();
// resize the canvas to the <area> bounding box size
canvas.width=maxX-minX;
canvas.height=maxY-minY;
// draw the clipping path
ctx.beginPath();
ctx.moveTo(pointpairs[0]-minX,pointpairs[1]-minY);
for(var i=2;i<pointpairs.length;i+=2){
var x=pointpairs[i];
var y=pointpairs[i+1];
ctx.lineTo(x-minX,y-minY);
}
ctx.closePath();
ctx.clip();
ctx.drawImage(img,-minX,-minY);
}
body{ background-color:black; padding:10px; }
canvas{border:1px solid red;}
<canvas id="source" width=300 height=300></canvas>
<br/>
<canvas id="canvas" width=300 height=300></canvas>
This is the code that I have used to make those coordinates
function clipIt(){
// calculate the size of the user's clipping area
if(points=="")
bootbox.alert("Not selected", function() {
});
else{
var minX=10000;
var minY=10000;
var maxX=-10000;
var maxY=-10000;
for(var i=1;i<points.length;i++){
var p=points[i];
if(p.x<minX){minX=p.x;}
if(p.y<minY){minY=p.y;}
if(p.x>maxX){maxX=p.x;}
if(p.y>maxY){maxY=p.y;}
}
var width=maxX-minX;
var height=maxY-minY;
// clip the image into the user's clipping area
ctx.save();
ctx.clearRect(0,0,cw,ch);
ctx.beginPath();
ctx.moveTo(points[0].x,points[0].y);
for(var i=1;i<points.length;i++){
var p=points[i];
ctx.lineTo(points[i].x,points[i].y);
}
ctx.closePath();
ctx.clip();
ctx.drawImage(img,0,0);
ctx.restore();
// create a new canvas
var c=document.createElement('canvas');
var cx=c.getContext('2d');
// resize the new canvas to the size of the clipping area
c.width=width;
c.height=height;
// draw the clipped image from the main canvas to the new canvas
cx.drawImage(canvas, minX,minY,width,height, 0,0,width,height);
// create a new Image() from the new canvas
var clippedImage=new Image();
//clippedImage.name="img"+ nameimageis
clippedImage.id="img"+ nameimageis
clippedImage.onload=function(){
// append the new image to the page
//document.body.appendChild(clippedImage );
new_parts=new_parts+1;
$("#contain").append(clippedImage)
$("#img"+nameimageis ).wrap( "<div class='ins' id='in_"+nameimageis+"'></div>" );
$("#in_"+nameimageis ).append("<input type='hidden' id='new_"+nameimageis+"' value='new'/><div class='inner' id='inner_"+nameimageis+"' onclick='removeit("+nameimageis+")'></div>");
nameimageis++;
}
clippedImage.src=c.toDataURL();
// clear the previous points
points.length=0;
// redraw the image on the main canvas for further clipping
drawImage(1);
}
}
I am trying to grab the RGB value from a click on a colour wheel image.
Now i am trying to grab the RGB from this Colour wheel image below without using canvas getImageData.
How can i do this without canvas?
With canvas you can run.
$('#picker').click(function(e) { // mouse move handler
// get coordinates of current position
var canvasOffset = $(canvas).offset();
var canvasX = Math.floor(e.pageX - canvasOffset.left);
var canvasY = Math.floor(e.pageY - canvasOffset.top);
alert("canvasX " + canvasX + " canvasY " + canvasY);
// get current pixel
var imageData = ctx.getImageData(canvasX, canvasY, 1, 1);
var pixel = imageData.data;
alert(JSON.stringify(pixel));
});
I cannot use canvas for this only javascript can anyone tell me the best way to getImageData from a image would i need to build a rgb grid with a for loop over the top and work out the selected pixel from the x and y value and then match it.
Really stumped on where to start any help?
Here's #Pointy's good idea put to code:
Illustrating with just 1 of your color wheel wedges. You can expand this starting point to include all wedges in your color wheel.
Example code and a Demo: http://jsfiddle.net/m1erickson/9pc4orw8/
<!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; }
#wheel{border:1px solid red;}
#results{width:50px;height:25px;background-color:black;border:1px solid green;}
</style>
<script>
$(function(){
var $wheel=$("#wheel");
var wheelOffset=$wheel.offset();
var offsetX=wheelOffset.left;
var offsetY=wheelOffset.top;
var PI=Math.PI;
var cx=0;
var cy=0;
// define the angle & radius of each color in the color wheel
var colors=[];
colors.push({sAngle:0,eAngle:PI/6,sRadius:0,eRadius:15,color:'rgb(255,255,255)'});
colors.push({sAngle:0,eAngle:PI/6,sRadius:15,eRadius:32,color:'rgb(253,237,238)'});
colors.push({sAngle:0,eAngle:PI/6,sRadius:32,eRadius:53,color:'rgb(251,211,212)'});
colors.push({sAngle:0,eAngle:PI/6,sRadius:53,eRadius:87,color:'rgb(246,145,149)'});
colors.push({sAngle:0,eAngle:PI/6,sRadius:87,eRadius:117,color:'rgb(240,73,80)'});
colors.push({sAngle:0,eAngle:PI/6,sRadius:117,eRadius:138,color:'rgb(237,27,36)'});
$("#wheel").mousedown(function(e){handleMouseDown(e);});
// test the current mouse position against the saved angle & radius of each color
// return the colors index of any color under the mouse
function hit(mx,my){
var hitIndex=-1;
for(var i=0;i<colors.length;i++){
var c=colors[i];
var a0=c.sAngle;
var a1=c.eAngle;
var r0=c.sRadius;
var r1=c.eRadius;
var dx=mx-cx;
var dy=my-cy;
var radius=Math.sqrt(dx*dx+dy*dy);
var angle=Math.atan2(dy,dx);
if(angle>a0 && angle<a1 && radius>r0 && radius<r1){
hitIndex=i;
break;
}
}
return(hitIndex);
}
function handleMouseDown(e){
e.preventDefault();
e.stopPropagation();
mx=parseInt(e.clientX-offsetX);
my=parseInt(e.clientY-offsetY);
var colorIndex=hit(mx,my);
if(colorIndex>=0){
$('#results').css('background-color',colors[colorIndex].color);
}
}
}); // end $(function(){});
</script>
</head>
<body>
Click to select color:<div id=results></div>
<img id=wheel src='https://dl.dropboxusercontent.com/u/139992952/multple/colorwheel1.png'/>
</body>
</html>
I have one image like this and I want to fill with pattern like this to make result like this .
I can fill the pattern using following code but I don't know how to curve pattern properly along the collar shape because it should look like real but my result become like this. .
Here is my sample script
$(function(){
drawCanvas("body","collar","images/collar.png", 180);
function drawCanvas(overlayType, canvasID, imageSource, degreeRotate){
var canvas=document.getElementById(canvasID);
var ctx=canvas.getContext("2d");
var imgBody=new Image();
var imgPattern=new Image();
imgPattern.onload=function(){
imgBody.onload=function(){
start();
}
imgBody.src=imageSource;
}
imgPattern.src="images/pattern.png";
function start(){
ctx.drawImage(imgBody,0,0);
if(overlayType=="body"){
ctx.globalCompositeOperation="source-atop";
ctx.globalAlpha=.85;
var pattern = ctx.createPattern(imgPattern, 'repeat');
ctx.fillStyle = pattern;
ctx.rect(0, 0, canvas.width, canvas.height);
ctx.rotate(degreeRotate * Math.PI/180);
ctx.fill();
ctx.translate(150,0);
ctx.globalAlpha=.1;
ctx.drawImage(imgBody,150,0);
}
}
}});
Can someone guide me to how to manage pattern to curve along side collar shape to look like real?
You can get this close by simply slicing and offsetting your pattern vertically
Original "collar" image:
Collar filled with curved checkered texture
**The Method:*
Create an image tiled with your checkered texture.
Draw that texture image onto a temporary canvas.
Get the image data for the temporary canvas.
For each column of pixels, offset that entire column by the curve of your collar.
A quadratic curve fits the curve of your collar well, so I used that in the example to determin the Y offset.
Put the image data back on the temporary canvas.
(You now have a curved checkered pattern to apply to your collar image).
Draw the collar image on your main canvas.
Set context.globalCompositeOperation=”source-atop”
Draw the texture from the temporary canvas onto the main canvas.
(The compositing mode will paint the texture only on the collar—not the background)
Here is code and a Fiddle: http://jsfiddle.net/m1erickson/hdXyk/
<!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; padding:20px; }
canvas{border:1px solid red;}
</style>
<script>
$(function(){
// get canvas references (canvas=collar, canvas1=texture)
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var canvas1=document.getElementById("canvas1");
var ctx1=canvas1.getContext("2d");
// preload the texture and collar images before starting
var textureImg,collarImg;
var imageURLs=[];
var imagesOK=0;
var imgs=[];
imageURLs.push("https://dl.dropboxusercontent.com/u/139992952/stackoverflow/checkered.png");
imageURLs.push("https://dl.dropboxusercontent.com/u/139992952/stackoverflow/collar.png");
loadAllImages();
function loadAllImages(callback){
for (var i = 0; i < imageURLs.length; i++) {
var img = new Image();
img.crossOrigin="anonymous";
imgs.push(img);
img.onload = function(){
imagesOK++;
if (imagesOK==imageURLs.length ) {
textureImg=imgs[0];
collarImg=imgs[1];
start();
}
};
img.src = imageURLs[i];
}
}
function start(){
// set both canvas dimensions
canvas.width=collarImg.width;
canvas.height=collarImg.height+5;
canvas1.width=textureImg.width;
canvas1.height=textureImg.height;
// draw the textureImg on canvas1
ctx1.drawImage(textureImg,0,0,canvas1.width,canvas1.height);
// curve the texture into a collar shaped curved
curveTexture(collarImg.width,collarImg.height);
// draw the collarImg on canvas
ctx.drawImage(collarImg,0,0);
// set compositing to source-atop
// any new drawing will ONLY fill existing non-transparent pixels
ctx.globalCompositeOperation="source-atop";
// draw the curved texture from canvas1 onto the collar of canvas
// (the existing pixels are the collar, so only the collar is filled)
ctx.drawImage(canvas1,0,0);
}
function curveTexture(w,h){
// define a quadratic curve that fits the collar bottom
// These values change if the collar image changes (+5,-32)
var x0=0;
var y0=h+5;
var cx=w/2;
var cy=h-32;
var x1=w;
var y1=h+5;
// get a,b,c for quadratic equation
// equation is used to offset columns of texture pixels
// in the same shape as the collar
var Q=getQuadraticEquation(x0,y0,cx,cy,x1,y1);
// get the texture canvas pixel data
// 2 copies to avoid self-referencing
var imageData0 = ctx1.getImageData(0,0,w,h);
var data0 = imageData0.data;
var imageData1 = ctx1.getImageData(0,0,w,h);
var data1 = imageData1.data;
// loop thru each vertical column of pixels
// Offset the pixel column into the shape of the quad-curve
for(var y=0; y<h; y++) {
for(var x=0; x<w; x++) {
// the pixel to write
var n=((w*y)+x)*4;
// the vertical offset amount
var yy=parseInt(y+h-(Q.a * x*x + Q.b*x + Q.c));
// the offset pixel to read
var nn=((w*yy)+x)*4;
// offset this pixel by the quadCurve Y value (yy)
data0[n+0]=data1[nn+0];
data0[n+1]=data1[nn+1];
data0[n+2]=data1[nn+2];
data0[n+3]=data1[nn+3];
}
}
ctx1.putImageData(imageData0,0,0);
}
// Quadratic Curve: given x coordinate, find y coordinate
function getQuadraticY(x,Q){
return(Q.a * x*x + Q.b*x + Q.c);
}
// Quadratic Curve:
// Given: start,control,end points
// Find: a,b,c in quadratic equation ( y=a*x*x+b*x+c )
function getQuadraticEquation(x0,y0,cx,cy,x2,y2){
// need 1 more point on q-curve, so calc its midpoint XY
// Note: since T=0.5 therefore TT=(1-T)=0.5 also [so could simplify]
var T=0.50;
var TT=1-T;
var x1=TT*TT*x0+2*TT*T*cx+T*T*x2;
var y1=TT*TT*y0+2*TT*T*cy+T*T*y2;
var A = ((y1-y0)*(x0-x2)
+ (y2-y0)*(x1-x0))/((x0-x2)*(x1*x1-x0*x0)
+ (x1-x0)*(x2*x2-x0*x0));
var B = ((y1-y0)-A*(x1*x1-x0*x0))/(x1-x0);
var C = y0-A*x0*x0-B*x0;
return({a:A,b:B,c:C});
}
}); // end $(function(){});
</script>
</head>
<body>
<p>"Curve" a texture by offsetting Y pixels based on Q-curve</p>
<canvas id="canvas" width=300 height=300></canvas>
<p>The temporary texture canvas</p>
<canvas id="canvas1" width=300 height=300></canvas>
</body>
</html>
You can triangulate the polygon and then bend the mesh. Then you can fill the mesh with the pattern. Here is an example of a triangulation in Java: How does this code for delaunay triangulation work?. Here is an example of a triangulation and a bit of work to remove long edges. It's a concave hull of a 2d point set.