I'm working on a website that I need to crop images on different shapes .
I found lots of libraries and I've tested some ,but the main problem is they only can crop images on pre defined shapes like rectangle , circle . What I need is to crop images in any shapes
for example ,I've written a code the users can define their shapes ( by using Map Area ) and the exact shape is make ,Now I need to crop image or copy this area and make a new image from it .
I can use php ,jquery and other platforms
Could you help me to manage this problem ?
King Regards
Here's one way to do it using html5 Canvas:
1. Use the area element's coords to draw a path on the canvas.
// assume you've put the `coords` points as {x:,y:} objects into a points[] array:
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();
2. Create a clipping path from the coords path you've just defined:
ctx.clip();
3. Draw the image on the canvas. The image will be clipped into your defined path:
ctx.drawImage(yourImageObject,0,0);
4. Create a second canvas sized to the clipping path size and use the clipping version of context.drawImage to draw just the clipped image onto the second canvas.
// see demo below for details
5. Create a new Image() from the second canvas...Mission Accomplished!
// create a new Image() from the second canvas
var clippedImage=new Image();
clippedImage.onload=function(){
// append the new image to the page
document.body.appendChild(clippedImage);
}
clippedImage.src=secondCanvas.toDataURL();
Annotated example code and a Demo:
// canvas related variables
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw,ch;
var $canvas=$("#canvas");
var canvasOffset=$canvas.offset();
var offsetX=canvasOffset.left;
var offsetY=canvasOffset.top;
// set some canvas styles
ctx.strokeStyle='black';
// an array to hold user's click-points that define the clipping area
var points=[];
// load the image
var img=new Image();
img.crossOrigin='anonymous';
img.onload=start;
img.src="https://dl.dropboxusercontent.com/u/139992952/multple/houses1.jpg";
function start(){
// resize canvas to fit the img
cw=canvas.width=img.width;
ch=canvas.height=img.height;
// draw the image at 25% opacity
drawImage(0.25);
// listen for mousedown and button clicks
$('#canvas').mousedown(function(e){handleMouseDown(e);});
$('#reset').click(function(){ points.length=0; drawImage(0.25); });
}
function handleMouseDown(e){
// tell the browser that we're handling this event
e.preventDefault();
e.stopPropagation();
// calculate mouseX & mouseY
mx=parseInt(e.clientX-offsetX);
my=parseInt(e.clientY-offsetY);
// push the clicked point to the points[] array
points.push({x:mx,y:my});
// show the user an outline of their current clipping path
outlineIt();
// if the user clicked back in the original circle
// then complete the clip
if(points.length>1){
var dx=mx-points[0].x;
var dy=my-points[0].y;
if(dx*dx+dy*dy<10*10){
clipIt();
}
}
}
// redraw the image at the specified opacity
function drawImage(alpha){
ctx.clearRect(0,0,cw,ch);
ctx.globalAlpha=alpha;
ctx.drawImage(img,0,0);
ctx.globalAlpha=1.00;
}
// show the current potential clipping path
function outlineIt(){
drawImage(0.25);
ctx.beginPath();
ctx.moveTo(points[0].x,points[0].y);
for(var i=0;i<points.length;i++){
ctx.lineTo(points[i].x,points[i].y);
}
ctx.closePath();
ctx.stroke();
ctx.beginPath();
ctx.arc(points[0].x,points[0].y,10,0,Math.PI*2);
ctx.closePath();
ctx.stroke();
}
// clip the selected path to a new canvas
function clipIt(){
// calculate the size of the user's clipping area
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.onload=function(){
// append the new image to the page
document.body.appendChild(clippedImage);
}
clippedImage.src=c.toDataURL();
// clear the previous points
points.length=0;
// redraw the image on the main canvas for further clipping
drawImage(0.25);
}
body{ background-color: ivory; }
canvas{border:1px solid red;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<h4>Click to outline clipping region.<br>Click back in starting circle to complete the clip.</h4>
<button id=reset>Reset clipping path</button><br>
<canvas id="canvas" width=400 height=300></canvas>
<p>Clipped images by user</p>
Related
I'm drawing an image onto a canvas using drawImage. It's a PNG that is surrounded by transparent pixels, like this:
How can I detect a drawing move path in the transparent part of that image on the canvas? I want to detect if a user draws in a transparent part.
I am trying this tutorial and I did as showing in the tutorial.
var ctx = canvas.getContext('2d'),
img = new Image;
img.onload = draw;
img.src = "http://i.stack.imgur.com/UFBxY.png";
function draw() {
// draw original image in normal mode
ctx.drawImage(img, 10, 10);
}
<canvas id=canvas width=500 height=500></canvas>
Check it out full code on Github
Check it out live demo IonCanvas
To find out is a pixel is transparent get the pixel using ctx.getImageData and look at the alpha value.
Example
// assumes ctx is defined
// returns true if pixel is fully transparent
function isTransparent(x, y) { // x, y coordinate of pixel
return ctx.getImageData(x, y, 1, 1).data[3] === 0; // 4th byte is alpha
}
I have a canvas, where user can write something, and it shows previously saved drawing. But we detected some of them are not using the total canvas area due to some factors. So my questions are:
Can I detect the area where the user's drawn and make it larger by
shredding the usused canvas spaces?
Or, can I detect whether the user
is using the full canvas area or say more than 60% of it? If yes,
how? So that I can put an warning to the user.
Thanks.
Can I detect the area where the user's drawn and make it larger by
shredding the usused canvas spaces?
By your use of the phrase "shred the undrawn space" I assume you want to scale the existing image larger to fill all the canvas area.
Yes, you can...
Step#1
You can use getImageData to fetch the pixel color data from your canvas. This data is an array containing the red, green, blue and alpha values of every pixel on the canvas.
var pixelData=mainContext.getImageData(0,0,mainCanvas.width,mainCanvas.height).data;
The data for canvas pixel[0,0]:
pixelData[0] is the red component of canvas pixel[0,0]
pixelData[1] is the green component of canvas pixel[0,0]
pixelData[2] is the blue component of canvas pixel[0,0]
pixelData[3] is the alpha component of canvas pixel[0,0]
The data for canvas pixel[1,0]:
pixelData[4] is the red component of canvas pixel[1,0]
pixelData[5] is the green component of canvas pixel[1,0]
pixelData[6] is the blue component of canvas pixel[1,0]
pixelData[7] is the alpha component of canvas pixel[1,0]
Use the pixel data to determine the bounding box of your user's opaque pixels. You do this by determining the topmost, leftmost, bottommost and rightmost pixel with alpha>250.
var boundLeft,boundTop,boundRight,boundBottom,boundWidth,boundHeight;
function getBounds(){
boundLeft=1000000;
boundTop=1000000;
boundRight=-1000000;
boundBottom=-1000000;
//
var d=ctx.getImageData(0,0,cw,ch).data;
//
for(var i=0;i<d.length;i+=4){
// test the alpha (d[i+3])
if(d[i+3]>250){
var px=parseInt(i/4);
var pixelY=parseInt(px/cw);
var pixelX=px-pixelY*cw;
if(pixelX<boundLeft){boundLeft=pixelX;}
if(pixelX>boundRight){boundRight=pixelX;}
if(pixelY<boundTop){boundTop=pixelY;}
if(pixelY>boundBottom){boundBottom=pixelY;}
boundWidth=boundRight-boundLeft;
boundHeight=boundBottom-boundTop;
}
}
}
Step#2
Create an in-memory canvas the size of the bounding box.
var memCanvas=document.createElement('canvas');
var memContext=memCanvas.getContext('2d');
memCanvas.width=boundWidth;
memCanvas.height=boundHeight;
Step#3
Use the clipping version of context.drawImage to draw the bounding area from the main canvas to the in-memory canvas.
memContext.drawImage(mainCanvas,
// grab the "used" pixels from the main canvas
boundLeft,boundTop,boundWidth,boundHeight,
// and draw those pixels on the in-memory canvas
0,0,boundWidth,boundHeight
);
Step#4
(Optionally resize the main canvas to the bounding box size)
mainCanvas.width=boundWidth;
mainCanvas.height=boundHeight;
Draw the in-memory canvas onto the main canvas
mainContext.clearRect(0,0,mainCanvas.width,mainCanvas.height);
mainContext.drawImage(memCanvas,0,0);
Can I detect whether the user is using the full canvas area or say
more than 60% of it? If yes, how? So that I can put an warning to the
user.
Yes, you can...
Use the same technique above to calculate the boundWidth & boundHeight
Then you can calculate the percentage of "used" canvas using the ratio of the bounding box size vs the canvas size:
var percent = (boundWidth*boundHeight) / (mainCanvas.width*mainCanvas.height);
Calculating the new bounding box as the user adds to the drawing
Rather than using getImageData to calculate the new bounding box after each new pixel is drawn by the user, you can instead expand the bounding box if the new pixel is outside the existing bounds:
if(newPixelX<leftmost){boundLeft=newPixelX;}
if(newPixelX>rightmost){boundRight=newPixelX;}
if(newPixelY<topmost){boundTop=newPixelY;}
if(newPpixelY>bottommost){boundBottom=newPixelY;}
Example code and 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 isDown=false;
var startX,startY;
var leftmost,topmost,rightmost,bottommost;
// load image
var img=new Image();
img.crossOrigin='anonymous';
img.onload=start;
img.src="https://dl.dropboxusercontent.com/u/139992952/multple/car.png";
function start(){
//
cw=canvas.width=img.width;
ch=canvas.height=img.height;
//
ctx.drawImage(img,0,0);
//
getBounds();
}
function getBounds(){
leftmost=1000000;
topmost=1000000;
rightmost=-1000000;
bottommost=-1000000;
//
var d=ctx.getImageData(0,0,cw,ch).data;
//
for(var i=0;i<d.length;i+=4){
// test the alpha (d[i+3])
if(d[i+3]>250){
var px=parseInt(i/4);
var pixelY=parseInt(px/cw);
var pixelX=px-pixelY*cw;
if(pixelX<leftmost){leftmost=pixelX;}
if(pixelX>rightmost){rightmost=pixelX;}
if(pixelY<topmost){topmost=pixelY;}
if(pixelY>bottommost){bottommost=pixelY;}
}
}
}
function highlightBounds(){
var previousFill=ctx.fillStyle;
ctx.globalAlpha=0.05;
ctx.fillStyle='red';
ctx.fillRect(leftmost,topmost,(rightmost-leftmost),(bottommost-topmost));
ctx.globalAlpha=1.00;
ctx.fillStyle=previousFill;
}
function handleMouseDown(e){
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
startX=parseInt(e.clientX-offsetX);
startY=parseInt(e.clientY-offsetY);
// Put your mousedown stuff here
isDown=true;
}
function handleMouseUp(e){
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// Put your mouseup stuff here
isDown=false;
}
function handleMouseOut(e){
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// Put your mouseOut stuff here
isDown=false;
}
function handleMouseMove(e){
if(!isDown){return;}
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
ctx.beginPath();
ctx.arc(mouseX,mouseY,3,0,Math.PI*2);
ctx.closePath();
ctx.fill();
if(mouseX<leftmost){leftmost=mouseX;}
if(mouseX>rightmost){rightmost=mouseX;}
if(mouseY<topmost){topmost=mouseY;}
if(mouseY>bottommost){bottommost=mouseY;}
var boundsArea=(rightmost-leftmost)*(bottommost-topmost);
var canvasArea=cw*ch;
$pct.text(parseInt(boundsArea/canvasArea*100)+'% of canvas area is used');
}
$("#canvas").mousedown(function(e){handleMouseDown(e);});
$("#canvas").mousemove(function(e){handleMouseMove(e);});
$("#canvas").mouseup(function(e){handleMouseUp(e);});
$("#canvas").mouseout(function(e){handleMouseOut(e);});
var $pct=$('#pct');
$('#shred').click(function(){
var width=rightmost-leftmost;
var height=bottommost-topmost;
var memCanvas=document.createElement('canvas');
var memContext=memCanvas.getContext('2d');
memCanvas.width=width;
memCanvas.height=height;
memContext.drawImage(canvas,
// grab the "used" pixels from the main canvas
leftmost,topmost,width,height,
// and draw those pixels on the in-memory canvas
0,0,width,height
);
canvas.width=width;
canvas.height=height;
ctx.drawImage(memCanvas,0,0);
});
body{ background-color: white; }
#canvas{border:1px solid red;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<button id=shred>Eliminate outside space</button>
<h4 id=pct>Drag mouse</h4>
<canvas id="canvas" width=300 height=300></canvas>
I loaded picture to canvas and draw couple points within this very simple function:
var addPoint = function($x, $y, $context) {
$context.fillStyle="#FF0000";
$context.fillRect($x, $y, 3, 3);
$("#points").append(
$("<li>").append("Point nr "+ totalPoints()).append(
"<a href='#' class='remove_point' data-x='"+$x+"' data-y='"+$y+"'>[delete]</a>"
)
);
bindRemove($context);
};
I would like to add zoom feature. I suppose that I need to use scale() function on my canvas context to achieve my goal. Problem is I want to keep all points (rectangles) drawn on canvas before. I stored those points on simple ul list. After executing scale() I need to redraw canvas and probably will loose those points. After zoom in/out coords for them are different and needs to be calculated again.
How I can zoom my canvas and keep previously added points?
Yes, you can store all your rectangle definitions using javascript objects inside an array.
var rects=[];
rects.push({x:20,y:20,width:25,height:15,color:'red'});
rects.push({x:75,y:100,width:50,height:35,color:'green'});
rects.push({x:150,y:75,width:40,height:75,color:'blue'});
rects.push({x:100,y:225,width:50,height:50,color:'gold'});
Then to zoom you can:
clear the canvas
scale the canvas with context.scale
use the rectangle objects to redraw all your rectangles
unscale the canvas in preparation for any future drawings
Here's example code and a Demo:
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 scale=1.00;
var rects=[];
rects.push({x:20,y:20,width:25,height:15,color:'red'});
rects.push({x:75,y:100,width:50,height:35,color:'green'});
rects.push({x:150,y:75,width:40,height:75,color:'blue'});
rects.push({x:100,y:225,width:50,height:50,color:'gold'});
drawRects();
//
function drawRects(){
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.scale(scale,scale);
for(var i=0;i<rects.length;i++){
var r=rects[i];
ctx.fillStyle=r.color;
ctx.fillRect(r.x,r.y,r.width*scale,r.height*scale);
}
ctx.setTransform(1,0,0,1,0,0);
}
//
canvas.addEventListener('DOMMouseScroll',handleScroll,false);
canvas.addEventListener('mousewheel',handleScroll,false);
//
function handleScroll(e){
e.preventDefault();
e.stopPropagation();
var delta=e.wheelDelta?e.wheelDelta/30:e.detail?-e.detail:0;
if (delta){
scale+=(delta>=0)?.01:-.01;
drawRects();
}
};
body{ background-color: ivory; }
canvas{border:1px solid red;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<h4>Click in Canvas then use mousewheel to zoom</h4>
<canvas id="canvas" width=400 height=400></canvas><br>
I'm trying to do a responsive canvas. All my tests has been doing with a 600x600 canvas and with that height and width it works OK and paint every line correctly. The problem is that I have tried this:
#myCanvas {
background-color: #ffffff;
border:1px solid #000000;
min-height: 600px;
height: 100%;
width: 100%;
}
Just for the record, myCanvas is inside a sm-col-8.
And it looks nice on my laptop and looks nice on my phone but (because of my draw() function, because it was thinking for a square) the draw starts more like in the down-left corner (nearby) and it should start at up-right corner.
So, I don't want to change my draw() function but what I'm looking for is to reescale the canvas size. I mean: If I'm in a laptop/tablet.. with 600x600, show it at that size, but if I'm on my phone which has 384x640 show it like 300x300? I don't know if it could be a good solution.
My draw function:
function drawLines(lines,i,table,xtotal,ytotal){
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
var xIni;
var xFin;
var yIni;
var yFin;
xIni = (c.width*parseInt(lines[i][1])/xtotal);
yIni = (c.height*parseInt(lines[i][2])/ytotal);
xFin = (c.width*parseInt(lines[i][3])/xtotal);
yFin = (c.height*parseInt(lines[i][4])/ytotal);
ctx.beginPath();
ctx.moveTo(xIni,c.height-yIni);
ctx.lineTo(xFin,c.height-yFin);
ctx.lineWidth=4;
ctx.strokeStyle = colorAleatorio();
ctx.stroke();
}
With Bootstrap, use:
<canvas id="canvas" class='img-responsive' style="border: 1px solid black;"></canvas>
You can make your html Canvas responsive by using the context.scale command.
The .scale command will scale the internal coordinates system used by canvas.
This means you do not need to change any of your own drawing coordinates because canvas will automatically transform your coordinates into scaled canvas coordinates for you.
// save the original width,height used in drawLines()
var origWidth=600;
var origHeight=600;
var scale=1.00;
// reference to canvas and context
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
// call this after resizing
// send in the new maximum width,height desired
function resizeAndRedraw(newMaxWidth,newMaxHeight){
// calc the global scaling factor that fits into the new size
// and also maintains the original aspect ratio
scale=Math.min((newMaxWidth/origWidth),(newMaxHeight/origHeight))
// resize the canvas while maintaining correct aspect ratio
canvas.width=origWidth*scale;
canvas.height=origHeight*scale;
// Note: changing the canvas element's width or height will
// erase the canvas so you must reissue all your drawing commands
drawLines(lines,i,table,xtotal,ytotal);
}
// call drawLines
function drawLines(lines,i,table,xtotal,ytotal){
// scale the canvas coordinate system to the current scale
// Note: This scales the coordinates used internally
// by canvas. It does not resize the canvas element
ctx.scale(s,s);
// now do your drawing commands
// You do not need to adjust your drawing coordinates because
// the Canvas will do that for you
var xIni;
var xFin;
var yIni;
var yFin;
xIni = (c.width*parseInt(lines[i][1])/xtotal);
yIni = (c.height*parseInt(lines[i][2])/ytotal);
xFin = (c.width*parseInt(lines[i][3])/xtotal);
yFin = (c.height*parseInt(lines[i][4])/ytotal);
ctx.beginPath();
ctx.moveTo(xIni,c.height-yIni);
ctx.lineTo(xFin,c.height-yFin);
ctx.lineWidth=4;
ctx.strokeStyle = colorAleatorio();
ctx.stroke();
// restore the context to it's unscaled state
ctx.scale(-s,-s);
}
Can I scaling images in js/canvas using custom algorithm, like binary subdivison or another?
Yes...You can use html5 canvas to do custom image subdivision.
If you are doing simple binary subdivision, you can use the extended version of context.drawImage to clip each tile from the main canvas and scale any subdivision as desired. See the example below.
Non-binary subdivisions are a bit tricky...
Your custom algorithm must generate the vertices or curve control points of each individual path of the desired subdivision.
The technique is to repeat this process for every element of the subdivision:
Create a temporary canvas: var tempCanvas=document.createElement('canvas');
Scale the canvas using tempContext.scale
Define a subpath on the image using context path commands: tempContext.beginPath ...
Restrict future drawing into that path using tempContext.clip
Draw the image onto the canvas using tempContext.drawImage. Since a clipping region has been defined, the image will only be drawn inside the path you have defined.
Use this temporary canvas as an image source to draw the subdivision on your main canvas: mainContext.drawImage(tempCanvas,x,y)
Here's example code and a Demo of simple binary subdivision with scaling:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
var subdivisions=[];
var subdivisionIndex=0;
subdivisions.push({
x:0,y:0,
width:174/2,height:110/2,
scale:1.00,maxScale:2.00,scaleDirection:1
});
subdivisions.push({
x:174/2,y:0,
width:174/2,height:110/2,
scale:1.00,maxScale:2.00,scaleDirection:1
});
subdivisions.push({
x:0,y:110/2,
width:174/2,height:110/2,
scale:1.00,maxScale:2.00,scaleDirection:1
});
subdivisions.push({
x:174/2,y:110/2,
width:174/2,height:110/2,
scale:1.00,maxScale:2.00,scaleDirection:1
});
var img=new Image();
img.onload=start;
img.src="https://dl.dropboxusercontent.com/u/139992952/multple/cars.jpg";
function start(){
animate();
}
function draw(s){
var x=s.x;
var y=s.y;
var w=s.width;
var h=s.height;
var scaledW=w*s.scale;
var scaledH=h*s.scale;
ctx.clearRect(0,0,cw,ch);
ctx.drawImage(img,0,0);
ctx.drawImage(
img, // clip from img
x,y,w,h, // clip a subdivision
x,y,scaledW,scaledH // draw the subdivision scaled
)
}
function animate(){
requestAnimationFrame(animate);
var s=subdivisions[subdivisionIndex];
draw(s);
if(s.scaleDirection>0){
s.scale*=1.02;
if(s.scale>s.maxScale){s.scaleDirection=-1;}
}else{
s.scale/=1.02;
if(s.scale<1.00){
s.scale=1.00;
s.scaleDirection=1;
subdivisionIndex++;
if(subdivisionIndex>subdivisions.length-1){
subdivisionIndex=0;
}
}
}
}
body{ background-color: ivory; padding:10px; }
#canvas{border:1px solid red;}
<h4>Scaling subdivisions of a binary subdivided image</h4>
<canvas id="canvas" width=300 height=300></canvas>