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>
Related
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>
One way I can think of is with an animated svg, but there is probably a better way. What would you do if you had to animate these wavy blobs (mobile compatible)
Link to the only pin I've found similar
var wave = document.createElement("div");
wave.className += " wave";
docFrag.appendChild(wave);
wave.style.left = i * waveWidth + "px";
wave.style.webkitAnimationDelay = (i / 100) + "s";
Touch interaction would be nice too. Would there be any problems with canvas stuff ?
Here's an implementation of #DA.'s good answer:
var canvas=document.getElementById('canvas');
var ctx=canvas.getContext('2d');
var cw=canvas.width;
var ch=canvas.height;
ctx.textAlign='center';
ctx.textBaseline='middle';
ctx.font='16px verdana';
ctx.lineWidth=5;
ctx.strokeStyle='white';
ctx.fillStyle='white';
var offsetX=0;
var bk=makeWave(canvas.width,canvas.height-120,10,2,'lightskyblue','cornflowerblue');
requestAnimationFrame(animate);
function animate(time){
ctx.clearRect(0,0,cw,ch);
ctx.drawImage(bk,offsetX,0);
ctx.fillStyle='white';
ctx.font='18px verdana';
ctx.fillText('Multiple Lists',cw/2,30);
ctx.strokeRect(cw/2-50,85,100,50);
ctx.fillStyle='gray';
ctx.font='12px verdana';
ctx.fillText('You can create and save multiple ...',cw/2,250);
offsetX-=1;
if(offsetX< -bk.width/2){offsetX=0;}
requestAnimationFrame(animate);
}
function makeWave(width,midWaveY,amplitude,wavesPerWidth,grad0,grad1){
var PI2=Math.PI*2;
var totValue=PI2*wavesPerWidth;
var c=document.createElement('canvas');
var ctx=c.getContext('2d');
c.width=width*2;
c.height=midWaveY+amplitude;
var grad=ctx.createLinearGradient(0,0,0,midWaveY);
grad.addColorStop(0.00,grad0);
grad.addColorStop(1.00,grad1);
//
ctx.beginPath();
ctx.moveTo(0,0);
for (x=0;x<=200;x++) {
var n=totValue*x/100;
ctx.lineTo(width*x/100,Math.sin(n)*amplitude+midWaveY);
}
ctx.lineTo(c.width,0);
ctx.closePath();
ctx.fillStyle=grad;
ctx.fill();
return(c);
}
body{ background-color:white; }
canvas{border:1px solid red; margin:0 auto; }
<canvas id=canvas width=300 height=300></canvas>
I'd make the wave a PNG (bottom solid gray, top transparent). Place it in a div twice the width of the card, and place that in a div the width of the card (this second div is the 'mask').
Then via CSS, have the nested give transform on the x axis to animate it sideways.
You shouldn't need any JS for this.
I would do this:
on page load create an off-screen canvas (just set display: none)
in a for loop compute the wave:
clear with transparency
paint only the white part because the colored part has a gradient
after each paint, get the PNG data out of the canvas and store it in an array
after the loop you will have an array of PNG images (frames)
cycle through those frames without recomputing the wave over and over again
This requires the wave to have a period that is affine to the number of frames you take (say a 2 second animation at 10 Hz would require 20 frames to be cyclic)
To be honest, you could store that server-side and just download it, without computing it client-side. Those PNG images would be very tiny because there isn't any color involved (just transparent/white/alpha channel). There are optimal settings for this, I guesstimate some 1KB per frame would suffice, that's a tiny 20 KB of images).
I am fairly new to Javascript / HTML5 front end programming. I am wondering how to have a circular canvas view within which there is a live graph.
I have looked at D3.js and cubism for graphing and I have a brief understanding of using canvas for animation etc. Usually the graphs are displayed in a rectangular view, within which the graph is displayed.
Please see the image that I have attached. I have looked around and didn't find any libraries that would do it, so have decided to do it myself but need some points on how to go about it in an efficient manner.
So basically some form of masking as shown in the following image
resulting in the following type of graph
You can use the context.clip method to display your chart only within a circle:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
var nextTime=0;
var delay=16*10;
var cx=150;
var cy=150;
var radius=50;
var data=[];
for(var i=0;i<radius*2;i++){
data.push(Math.random()*radius*2);
}
ctx.fillStyle='rgb(186,228,180)';
ctx.beginPath();
ctx.arc(cx,cy,radius,0,Math.PI*2);
ctx.clip();
requestAnimationFrame(animate);
function animate(time){
requestAnimationFrame(animate);
if(time<nextTime){return;}
nextTime+=delay;
// new data point
data.shift();
data.push(Math.random()*radius*2);
var x=cx-radius;
ctx.clearRect(0,0,cw,ch);
ctx.beginPath();
ctx.moveTo(x,cy);
for(var i=0;i<data.length;i++){
x+=5;
ctx.lineTo(x,cy-radius+data[i]);
}
ctx.lineTo(cx+radius,cw);
ctx.lineTo(cx-radius-1,cy+radius);
ctx.closePath();
ctx.fillStyle='rgb(186,228,180)';
ctx.fill();
ctx.beginPath();
ctx.arc(cx,cy,radius,0,Math.PI*2);
ctx.stroke();
}
body{ background-color: ivory; padding:10px; }
#canvas{border:1px solid red;}
<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);
}