I do a game like jigsaw puzzle, i face issue when i move or scroll my puzzle parts then parts shadow came with it, like below image.
i tried canvas.clearRect(0,0,this.canvas.width,this.canvas.height); but it couldn't help me.
my js code here and i want like this on mouse move or drag time.
How to reuse a shadow for efficiency
Adding a shadow is one of the most expensive tasks that canvas does. So you might not want to recalculate the shadow with every mousemove. Instead, you might create a single shadow for each piece-shape at the start of your app. Then reuse the shadow during mousemove by first drawing the shadow and then drawing the piece image on top of the shadow.
Assume you have a jigsaw puzzle and many (but not all) of the pieces have this slot-tab-slot-tab shape:
... and ...
Then you can create one single shadow image that will fit under any of the slot-tab-slot-tab shapes to give that shape a shadow:
So if you first draw the shadow and then draw any slot-tab-slot-tab piece on top of that shadow you have one shadow serving any piece with that shape:
... and ...
Here's annotated code and a Demo:
// canvas vars
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(); }
window.onresize=function(e){ reOffset(); }
// jigsaw vars
var pieces=[];
var shadows=[];
var blurSize=10;
// jigsaw piece#1
var piece1=new Image();
piece1.onload=start;
piece1.src="https://dl.dropboxusercontent.com/u/139992952/stackoverflow/Piece1.png";
// jigsaw piece#2
var piece2=new Image();
piece2.onload=start;
piece2.src="https://dl.dropboxusercontent.com/u/139992952/stackoverflow/Piece2.png";
var imgcount=2;
// start() after all images are fully loaded
function start(){
if(--imgcount>0){return;}
// create piece definitions
pieces.push({img:piece1,x:25,y:75,w:piece1.width,h:piece1.height,shadowIndex:0,showShadow:false});
pieces.push({img:piece2,x:300,y:75,w:piece2.width,h:piece2.height,shadowIndex:0,showShadow:false});
// make one shadow that fits all pieces with this outline
shadows.push(makeShadowUnderlay(piece1,blurSize));
// draw the pieces
drawAllPieces();
// listen for mouse events
$("#canvas").mousedown(function(e){handleMouseDown(e);});
$("#canvas").mouseup(function(e){handleMouseUpOut(e);});
$("#canvas").mouseout(function(e){handleMouseUpOut(e);});
}
function drawAllPieces(){
ctx.clearRect(0,0,cw,ch);
for(var i=0;i<pieces.length;i++){
drawPiece(i);
}
}
function drawPiece(pieceIndex){
// cache the piece
var piece=pieces[pieceIndex];
// draw the shadow, if desired
if(piece.showShadow){
var shadow=shadows[piece.shadowIndex]
ctx.drawImage(shadow,piece.x-blurSize,piece.y-blurSize);
}
// draw the piece
ctx.drawImage(piece.img,piece.x,piece.y);
}
function makeShadowUnderlay(img,blurSize){
// create a new canvas containing the shadowed
// outline of the img
var c=document.createElement('canvas');
var cctx=c.getContext('2d');
c.width=img.width+blurSize*2;
c.height=img.height+blurSize*2;
cctx.shadowColor='black';
cctx.shadowBlur=blurSize;
cctx.shadowOffsetX=500;
cctx.drawImage(img,blurSize-500,blurSize);
return(c);
}
function handleMouseDown(e){
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
// get mouse postion
var mx=parseInt(e.clientX-offsetX);
var my=parseInt(e.clientY-offsetY);
// set showShadow flag for any piece(s) under mouse
for(var i=0;i<pieces.length;i++){
var p=pieces[i];
p.showShadow=false;
if(mx>p.x && mx<p.x+p.w && my>p.y && my<p.y+p.h){
p.showShadow=true;
}
}
// redraw all pieces
drawAllPieces();
}
//
function handleMouseUpOut(e){
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
// clear all shadow flags
for(var i=0;i<pieces.length;i++){ pieces[i].showShadow=false; }
// redraw all pieces
drawAllPieces();
}
body{ background-color: ivory; }
#canvas{border:1px solid red; margin:0 auto; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<h4>Mousedown on piece to shadow it using a reusable(!) shadow image</h4>
<canvas id="canvas" width=600 height=300></canvas>
Related
just wondering has anyone got any solution for zooming into a region on a graph. When I say region I mean, you mouse down at a point on the canvas, move the mouse to another location while still pressed, and when mouse up I want to zoom into that block. and only scale the x axis, no the the y.
This is exactly what I want to do - http://canvasjs.com/docs/charts/basics-of-creating-html5-chart/zooming-panning/
Now I've been able to zoom in, but my graph is a mess after the zoom. Lines and points are all stretched, twisted etc, which I've tried to adjust using the scale.
This is what I've been doing so far.
let y = heightOfCanvas / 2,
// Get the region in pixels to zoom to. If canvas width
// is 500, this is lets say 100px to 400px.
regionToZoom = toX - fromX,
// Subtract from actual width, so we get what will be scaled
// out.
difference = widthOfCanvas - regionToZoom,
// Scale = fullWidth / partWidth
scale = widthOfCanvas / difference,
// This is how I worked out the value to translate back,
// so that it lines up right
translateBack = widthOfCanvas * scale
ctx.translate(from, y);
ctx.scale(scale, 1);
from = from * translateBack;
ctx.translate(-from, -y);
// REDRAW GRAPH AFTER
Like this zooms in fine, but as I said, it becomes a mess after.
If anyone has done something similar in the past, like in the example above, I would be so grateful for the help.
Sean.
Here's a quick example showing how to select & display a subset of a graph
// canvas vars
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
ctx.font='14px arial';
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(); }
window.onresize=function(e){ reOffset(); }
var isDrilled=false;
var isDown=false;
var startX,startY;
// graph vars
var axisY=ch/3;
var points=[];
// DEMO: add random data points
var demoPointCount=50;
for(var i=0;i<demoPointCount;i++){
points.push({y:Math.random()*150-75});
}
// draw the full data graph
draw(points,axisY);
// listen to mouse events
window.onmousedown=(function(e){handleMouseDown(e);});
window.onmousemove=(function(e){handleMouseMove(e);});
window.onmouseup=(function(e){handleMouseUpOut(e);});
window.onmouseout=(function(e){handleMouseUpOut(e);});
function draw(pts,axisY,startX,mouseX,drillStart,drillEnd){
// redraw the given pts data
ctx.clearRect(0,0,cw,ch);
ctx.beginPath();
ctx.moveTo(0,pts[0].y+axisY);
for(var i=0;i<pts.length;i++){
var x=cw/(pts.length-1)*i;
ctx.lineTo(x,pts[i].y+axisY);
}
ctx.stroke();
// used when highlighting a drilldown section of full data
if(startX && mouseX){
ctx.globalAlpha=0.10;
ctx.fillStyle='black';
ctx.fillRect(startX,0,mouseX-startX,ch);
ctx.globalAlpha=1.00;
}else if(drillStart && drillEnd){
ctx.fillText('Viewing '+drillStart+' - '+drillEnd+'. Click to return to all data view.',10,20);
}else{
ctx.fillText('Drag to select data to drill into.',10,20);
}
}
function handleMouseDown(e){
// if displaying drilled data, return to full data
if(isDrilled){
isDrilled=false;
draw(points,axisY);
return;
}
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
// start mouse position
startX=parseInt(e.clientX-offsetX);
startY=parseInt(e.clientY-offsetY);
// Put your mousedown stuff here
isDown=true;
}
function handleMouseUpOut(e){
if(!isDown){return;}
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
// mouse position
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// Put your mouseup stuff here
isDown=false;
isDrilled=true;
// normalize
if(mouseX<startX){ var t=startX; startX=mouseX; mouseX=t; }
// fetch highlighted start & end data points
drillStart=parseInt(startX/cw*points.length);
drillEnd=parseInt(mouseX/cw*points.length);
var subset=points.slice(drillStart,drillEnd+1);
// draw the data subset
draw(subset,axisY,null,null,drillStart,drillEnd);
}
function handleMouseMove(e){
if(!isDown){return;}
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
// mouse position
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// Put your mousemove stuff here
draw(points,axisY,startX,mouseX);
}
body{ background-color:white; }
#canvas{border:1px solid red; }
<canvas id="canvas" width=512 height=512></canvas>
[Previous outdated answer]
Here's a way to zoom without all the fuss about remembering transformations
This is a quick-thought so some tinkering may be needed.
Copy the graph by drawing it on a second in-memory canvas:
var memCanvas=myCanvas.cloneNode();
var memCtx=memCanvas.getContext('2d');
memCtx.drawImage(myCanvas,0,0);
DrawImage the memCanvas onto the displayed canvas:
context.drawImage(memCanvas,0,0);
On mousedown, grab the top-left coordinates of your selection box.
On mouseup, grab the bottom-right coordinates of your selection box. Note: you might have to flip top & bottom or left & right if top>bottom or left>right.
Calculate the scaling factor required to draw just the selection box full-frame into the visible canvas:
var scale = Math.min(
(myCanvas.width/selectionboxWidth),
(myCanvas.height/selectionboxHeight)
);
Use the clipping form of drawImage to pull just the selection box from the memCanvas and draw it scaled into the visible canvas:
var x=sboxLeft;
var y=sboxTop;
var w=sboxRight-sboxLeft;
var h=sboxBottom-sboxTop;
sboxcontext.drawImage(
memCanvas, // fetch from the memCanvas
x,y,w,h, // clip the selected box
0,0,w*scale,h*scale); // scale the selected box into the visible canvas
Then when you want to unzoom you just redraw the full memCanvas to the visible canvas (no need to untransform). The resulting graph is not a mess!
context.drawImage(memCanvas,0,0); // unzoomed -- simply!
I have three questions. The first question is the most important but I appreciate to get answers to the others.
First question, see this jsfiddle:
https://jsfiddle.net/owLdgrdq/10
//copia nariz
$(document).on('click', '#add-nariz', function(){
var a = $('#add-nariz');
var src = a.attr('src');
var elem = $('<img class="objetos" src="' + src + '" width="30px" height="30px" style="positon: relative;" />');
$('#fotoAlterada').append(elem);
elem.draggable();
});
//copia bolinha azul
$(document).on('click', '#add-bb', function(){
var a = $('#add-bb');
var src = a.find('img:first').attr('src');
var elem = $('<img class="objetos" src="' + src + '" width="30px" height="30px" style="positon: relative;" />');
$('#fotoAlterada').append(elem);
elem.draggable();
});
$(document).on('click', '#salvaImagem', function(){
var m = $('#foto');
var totX = parseInt(m.css('width'));
var totY = parseInt(m.css('height'));
var c = document.getElementById('myCanvas');
c.width = totX;
c.height = totY;
var ctx = c.getContext('2d');
alert(totX + '\n' + totY);
var base = document.getElementById('foto');
ctx.drawImage(base,0,0,totX,totY,0,0,totX,totY);
var posicoes = [];
$(".objetos").each(function(){
var img = $(this);
x = parseInt(img.css("left"))+totX;
y = parseInt(img.css("top"))+totY;
altura = parseInt(img.css("width"));
largura = parseInt(img.css("height"));
posicoes.push([
x,
y,
largura,
altura
]);
});
alert( JSON.stringify(posicoes));
var j;
var numAderecos = posicoes.length;
for(j = 0; j < numAderecos; j++){
ctx.drawImage(base,posicoes[j][0],posicoes[j][1],posicoes[j][2],posicoes[j][3]);
}
});
I have some icons (Adicionar isto:) what I want add to a photo (Editar isto:). When the user clicks on the icons, a copy is made beside the photo. More click make more copies. These copies are draggable. The user choose the position of the objects and click in a button to save ("Salvar Mudanças"). This button makes a copy of the original photo with the new objects on it (inside a canvas).
At final of the JS code I put little photos (same as original) as little objects added to the photo in the canvas, but just because I don't know how to copy each object added. How can I do that?
Second question, when click on the button, the positions are not the same in the canvas, they are pull to down a little (and left too). Why is this happening if I put padding and margin 0px in CSS code?
Third question, the variable 'base' I used $('#foto') to get by id, but I can't use it at canvas arguments. When I use document.getElementById('foto') I can. What is the difference among them?
----editing ----
My aim with this is to make a mobile application, through cordova/phonegap. The user must be able to get a picture from the device or a selfie with the cam (it's already ok). When the picture/photo is gotten, some new objects like a crown's nose, colorful hat and other silly things appear on the window (it's ok, just change style="display: none;" to be displayed), when the user clicks on these things they make a copy at the bottom of the photo and these copies must be draggable (it's nice too). Then, with jQuery-UI I'm getting the position of all dragged objects into the photo, the user clicks on a button "Salvar Mudanças" ("save changes"), and this action copy the photo plus objects to a canvas area (this is the way I got to make the app). After, the user clicks on another button to share the modified image (in canvas) through whatsapp, facebook, email and any other way which the device could do.
Here is an alternate way of appending icons onto an image using just a canvas
Carve out a toolbar area at the top of an html5 canvas and fill it with your desired icons.
Put the destination image below the toolbar.
Listen for mouse events.
Allow the user to icons from the toolbar onto the image below.
When the user "drops" the dragging icon by releasing the mouse, create a duplicate of the dragged icon at the dropped position.
Here is annotated 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(); }
window.onresize=function(e){ reOffset(); }
var isDown=false;
var startX,startY;
ctx.lineWidth=0.50;
ctx.strokeStyle='lightgray';
ctx.fillStyle='white';
// vars for icons, etc
var tbar={width:cw,height:35}
var tw=30;
var th=30;
var dragging=null;
var dropped=[];
var icons=[
{name:'Flower',x:tw*0,y:2,url:'https://dl.dropboxusercontent.com/u/139992952/multple/flower.png'},
{name:'Star',x:tw*1,y:2,url:'https://dl.dropboxusercontent.com/u/139992952/multple/star.png'},
{name:'Plane',x:tw*2,y:2,url:'https://dl.dropboxusercontent.com/u/139992952/multple/plane2.png'},
{name:'Mario',x:tw*3,y:2,url:'https://dl.dropboxusercontent.com/u/139992952/multple/marioStanding.png'},
];
var thumbs=[];
var mainImg=new Image();
mainImg.crossOrigin='anonymous';
mainImg.onload=start;
mainImg.src='https://dl.dropboxusercontent.com/u/139992952/multple/husky.jpg';
var imgCount=icons.length+1;
for(var i=0;i<icons.length;i++){
var icon=icons[i];
icon.index=i;
icon.img=new Image();
icon.img.crossOrigin='anonymous';
icon.img.onload=start;
icon.img.src=icon.url;
}
// start is called when each image is fully loaded
function start(){
// wait for all images to load
if(--imgCount>0){return;}
// create
for(var i=0;i<icons.length;i++){
var icon=icons[i];
thumbs.push(thumb(icon.img,tw,th));
}
// draw the toolbar & image
draw();
// listen for mouse events
$("#canvas").mousedown(function(e){handleMouseDown(e);});
$("#canvas").mousemove(function(e){handleMouseMove(e);});
$("#canvas").mouseup(function(e){handleMouseUpOut(e);});
$("#canvas").mouseout(function(e){handleMouseUpOut(e);});
}
// create thumbnails for each image (to be used as icons in the toolbar)
function thumb(img,w,h){
var iw=img.width;
var ih=img.height;
var s=Math.min((w/iw),(h/ih))
var c=document.createElement('canvas');
c.width=iw*s;
c.height=ih*s;
c.getContext('2d').drawImage(img,0,0,iw,ih,0,0,iw*s,ih*s);
return(c);
}
// draw the toolbar, image & any clone thumbnails added to the image
function draw(){
ctx.clearRect(0,0,cw,ch);
ctx.fillRect(0,0,cw,tbar.height);
ctx.strokeRect(0,0,cw,tbar.height);
ctx.drawImage(mainImg,0,tbar.height);
for(var i=0;i<icons.length;i++){
var icon=icons[i];
ctx.drawImage(thumbs[icon.index],icon.x,icon.y);
}
for(var i=0;i<dropped.length;i++){
var icon=dropped[i];
ctx.drawImage(thumbs[icon.thumbIndex],icon.x,icon.y);
}
if(dragging){
ctx.drawImage(thumbs[dragging.thumbIndex],dragging.x,dragging.y);
}
}
//
function handleMouseDown(e){
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
// get the mouse position
startX=parseInt(e.clientX-offsetX);
startY=parseInt(e.clientY-offsetY);
// hit test the toolbar icons and any dropped icons
var mx=startX;
var my=startY;
// hit test toolbar icons
if(mx>0 && mx<tw*icons.length && my>2 && my<2+tw){
var icon=icons[parseInt(mx/tw)];
dragging={thumbIndex:icon.index,x:icon.x,y:icon.y};
dragging.source='icons';
return;
}
// hit test dropped icons
for(var i=0;i<dropped.length;i++){
var icon=dropped[i];
if(mx>icon.x && mx<icon.x+tw && my>icon.y && my<icon.y+th){
dragging=dropped[i];
dragging.source='dropped';
dragging.droppedIndex=i;
return;
}
}
}
// Add any
function handleMouseUpOut(e){
if(!dragging){return;}
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
//
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// add the icon to its dropped position
if(dragging.source=='icons'){
if(dragging.y>tbar.height){
dropped.push(dragging);
}
// remove the dropped icon if it's back in the toolbar
}else if(dragging.y<tbar.height){
dropped.splice(dragging.droppedIndex,1);
}
// clear dragging
dragging=null;
// redraw
draw();
}
// move any icon that's being dragged
function handleMouseMove(e){
if(!dragging){return;}
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
// get mouse position
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// move the dragged icon by the distance the mouse
// has moved since the last mousemove event
var dx=mouseX-startX;
var dy=mouseY-startY;
startX=mouseX;
startY=mouseY;
dragging.x+=dx;
dragging.y+=dy;
// redraw
draw();
}
// save just the image with dropped icons
$('#save').click(function(){
var c=document.createElement('canvas');
c.width=cw;
c.height=ch-tbar.height;
c.getContext('2d').drawImage(canvas,0,tbar.height,cw,ch-tbar.height,0,0,c.width,c.height);
var img=new Image();
img.onload=function(){
document.body.appendChild(img);
}
img.src=c.toDataURL();
});
body{ background-color: ivory; }
#canvas{border:1px solid lightgray; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<button id='save'>Save</button>
<h4>Drag from top toolbar & icon will be drop-copied<br>Drag dropped icons around image<br>Drag dropped icon back to toolbar to delete.</h4>
<canvas id="canvas" width=300 height=300></canvas>
It's a little tough to follow, but I suspect you're asking about compositing all of the existing DOM images onto a single canvas context. For that you'll probably want to look at the globalCompositeOperation property on the 2dcanvas:
globalCompositeOperation - the property
Compositing Tutorial - basic outline of how the different types work
Also your 3rd question: $('#foto') returns a jquery object, and document.getElementById returns a DOM object. To get the actual DOM element you would do something like: var theEl = $('#foto')[0]
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 want to make a website that will be a room, and I want users to be able to look at that room in limited panoramic view, e.g. up/down 30 degrees, left/right 45 degrees, and I want to put objects in that panoramic view that user could interact with.
I have found that google street view could give the panoramic effect, but I'm not quite sure if it would suit my needs as I would want to put objects in it.
Are there any alternative panoramic libraries that are good and could give me tools to support what I want to achieve?
You are basically talking about panning around a view.
You can do that by drawing the view with horizontal & vertical offsets.
Here's annotated code and a Demo: http://jsfiddle.net/m1erickson/32Y5A/
<!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(){
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
ctx.strokeStyle="red";
ctx.lineWidth=5;
var canvasOffset=$("#canvas").offset();
var offsetX=canvasOffset.left;
var offsetY=canvasOffset.top;
var lastX=0;
var lastY=0;
var panX=0;
var panY=0;
var dragging=[];
var isDown=false;
// create "draggable" rects
var images=[];
images.push({x:200,y:150,width:25,height:25,color:"green"});
images.push({x:80,y:235,width:25,height:25,color:"gold"});
// load the tiger image
var tiger=new Image();
tiger.onload=function(){
draw();
}
tiger.src="https://dl.dropboxusercontent.com/u/139992952/stackoverflow/tiger.png";
function draw(){
ctx.clearRect(0,0,canvas.width,canvas.height);
// draw tiger
ctx.globalAlpha=0.25;
ctx.drawImage(tiger,panX,panY,tiger.width,tiger.height);
// draw color images
ctx.globalAlpha=1.00;
for(var i=0;i<images.length;i++){
var img=images[i];
ctx.beginPath();
ctx.rect(img.x+panX,img.y+panY,img.width,img.height);
ctx.fillStyle=img.color;
ctx.fill();
ctx.stroke();
}
}
// create an array of any "hit" colored-images
function imagesHitTests(x,y){
// adjust for panning
x-=panX;
y-=panY;
// create var to hold any hits
var hits=[];
// hit-test each image
// add hits to hits[]
for(var i=0;i<images.length;i++){
var img=images[i];
if(x>img.x && x<img.x+img.width && y>img.y && y<img.y+img.height){
hits.push(i);
}
}
return(hits);
}
function handleMouseDown(e){
// get mouse coordinates
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// set the starting drag position
lastX=mouseX;
lastY=mouseY;
// test if we're over any of the images
dragging=imagesHitTests(mouseX,mouseY);
// set the dragging flag
isDown=true;
}
function handleMouseUp(e){
// clear the dragging flag
isDown=false;
}
function handleMouseMove(e){
// if we're not dragging, exit
if(!isDown){return;}
//get mouse coordinates
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// calc how much the mouse has moved since we were last here
var dx=mouseX-lastX;
var dy=mouseY-lastY;
// set the lastXY for next time we're here
lastX=mouseX;
lastY=mouseY;
// handle drags/pans
if(dragging.length>0){
// we're dragging images
// move all affected images by how much the mouse has moved
for(var i=0;i<dragging.length;i++){
img=images[dragging[i]];
img.x+=dx;
img.y+=dy;
}
}else{
// we're panning the tiger
// set the panXY by how much the mouse has moved
panX+=dx;
panY+=dy;
}
draw();
}
// use jQuery to handle mouse events
$("#canvas").mousedown(function(e){handleMouseDown(e);});
$("#canvas").mousemove(function(e){handleMouseMove(e);});
$("#canvas").mouseup(function(e){handleMouseUp(e);});
}); // end $(function(){});
</script>
</head>
<body>
<h4>Drag the tiger and<br>independently drag the rectangles.</h4>
<canvas id="canvas" width=300 height=300></canvas>
</body>
</html>