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]
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 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>
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 want to make a spaceship shoot in a game I am making, but I dont seem to be able to get the positions of the shots to load properly.
I have an array called shots[] and I want to push Shot() objects into it every N ticks when the player is holding down the mouse button.
So basically I want Shot() have x property equal to my ship.x property and y=ship.y.
Then I want it to have a Shot.dx property, which changes, depending on wether the cursor is above the middle of canvas or bellow (+3/-3) or left or right of the center (dy=+3/-3).
// Shooting
var shots = [] //Bullet array
//Object, which should be the position of the bullet.
function Shot() {
this.x=350
this.y=250;
this.dx=Shoot.x
this.dy=Shoot.y
}
//This keeps track of the cursor position in relation to the center of canvas.
function Shoot() {
started = false;
this.mousedown = function (ev) {
started = true;
};
this.mousemove = function (ev) {
if (started) {
if (ev.clientX>=350) this.x=3;
else this.x=-3;
if (ev.clientY>=250) this.y=3;
else this.y=-3;
}
};
this.mouseup = function (ev) {
started = false;
};
}
//The problem is, that when I set this.x in Shot() to be =ship.x the values
//dont get updated and it stays undefined after I push it into the array.
//Now I have it set to the center of canvas, but that it useless because it needs
//to move with the ship, but even now I am getting weird numbers and not the numbers
//that I actually put in. dx and dy dont get updated at all.
// Ship Model
var ship = {
x: 350,
y: 250,
lives: 3,
invulnerable: 0,
}
//Main function- pushes the objects into the array
function mainLoop() {
tick++
count()
interval()
chase()
console.log(started)
if (started && tick%20==0)
shots.push(new Shot());
keyboard()
display()
check()
requestAnimationFrame(mainLoop);
}
// Initialization
window.onload = function() {
// Setting variables
button = document.getElementById("button")
text = document.getElementById("text")
canvas = document.getElementById("canvas")
ctx = canvas.getContext("2d")
weapon = new Shoot();
canvas.onmousedown = weapon.mousedown;
canvas.onmousemove = weapon.mousemove;
canvas.onmouseup = weapon.mouseup;
requestAnimationFrame(mainLoop);
}
//I know this is a lot of code, but its all relevant. I am new to Javascript
//so I expect this to be some sort of a trivial error.
Here is a JSfiddle, but I dont know how that works so I cant get it to draw canvas, sorry: http://jsfiddle.net/JH3M6/
Here's an outline of how to fire your shots:
create an array to hold your shot objects
on mousedown: set the started flag (starts firing)
on mousedown: set the firing position for any new shot(s) to the current mouse position
on mousemove: reset the firing position for any new shot(s) to the current mouse position
on mouseup: clear the started flag (stops firing)
In the animation loop:
add a shot at the current firing position if the mouse is still down
move all shots by their dx,dy
remove any shots from the shots[] array if the shot has moved off-canvas
clear the screen and draw the shots in their new positions
A Demo: http://jsfiddle.net/m1erickson/2f9sf/
Here's example code:
<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<style>
body{ background-color: ivory; }
canvas{border:1px solid red;}
</style>
<script>
$(function(){
// canvas related vars
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
var bb=canvas.getBoundingClientRect();
var offsetX=bb.left;
var offsetY=bb.top;
ctx.fillStyle="blue";
// vars related to firing position and Shot(s)
var shotColor=ctx.fillStyle;
var started,mouseX,mouseY,dx,dy;
// Shots fired
var shots = [] //Bullet array
//Object, which should be the position of the bullet.
function Shot(x,y,dx,dy) {
this.x=x;
this.y=y;
this.dx=dx;
this.dy=dy;
}
Shot.prototype.display=function(){
// make fillstyle blue if it's not already blue
if(!ctx.fillStyle==shotColor){ctx.fillStyle=shotColor;}
// draw the shot on the canvas
ctx.fillRect(this.x,this.y,5,5);
}
// listen for mouse events
canvas.onmousedown=function(e){ started=true; setFirePosition(e); }
canvas.onmouseup=function(e){ started=false; }
canvas.onmousemove=function(e){
if(started){setFirePosition(e);}
}
// start the animation loop
requestAnimationFrame(animate);
// set the firing position of the next shot
function setFirePosition(e){
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
dx=(mouseX>=canvas.width/2)?-3:3;
dy=(mouseY>=canvas.height/2)?-3:3;
}
// animation loop
// add shots if the mouse is down
// move shots until they move off-canvas
function animate(){
// request another frame
requestAnimationFrame(animate);
// if the mouse is down, add a shot
if(started){
shots.push(new Shot(mouseX,mouseY,dx,dy));
}
// if no work to do, return
if(shots.length==0){return;}
// new array of active shots
// "active" == shot has not moved off-canvas
var a=[];
// clear the canvas for this frame
ctx.clearRect(0,0,cw,ch);
for(var i=0;i<shots.length;i++){
// get a shot to process
var shot=shots[i];
// move this shot
shot.x+=shot.dx;
shot.y+=shot.dy;
// if the shot hasn't moved offscreen
// add the shot to "a" (the replacement shots array);
// draw this shot
if(shot.x>=0 && shot.x<=cw && shot.y>0 && shot.y<=ch){
a.push(shot);
shot.display();
}
}
// if shots went off-canvas, remove them from shots[]
if(a.length<shots.length){
shots.length=0;
Array.prototype.push.apply(shots,a);
}
}
}); // end $(function(){});
</script>
</head>
<body>
<h4>Mousedown to fire shots</h4>
<canvas id="canvas" width=300 height=300></canvas>
</body>
</html>
I'm working on a simple diagram editor using KineticJS. I would like to use two separate canvases for the palette area (that contains a number of Kinetic.Groups that represent the different nodes of the network I might create), and the diagramming area where I can add nodes from the palette via drag and drop, and then add connections between the various nodes as specific anchor points. I'm having trouble figuring out the drag and drop process from the palette canvas of (stationary) Kinetic.Groups over to the other canvas containing diagramming area. I'm guessing that I need to fire off of the dragstart event for the palette objects (although I don't want these themselves to be draggable), and then do something like create an opague copy of the palette object that can be dragged around, and finally dropped into the diagramming area (with full opacity).
Can groups be dragged outside of a the staging canvases boundaries? Maybe I need to generate an image when I start to drag from the palette, drag that image over, and then create another group when dropping into the diagramming area.
Does any one know of any examples that might point me in the right direction, or who can offer some insight (even code) into the required process. I've searched the KineticJS examples but can't quite find enough to get me going.
Here’s one way to drag nodes from a source palette into a destination group:
A Fiddle: http://jsfiddle.net/m1erickson/xtVyL/
Network nodes are represented by small icons (which are really small kinetic image objects).
The user can drag any icon from the source palette to any destination group.
The groups are just defined areas on the canvas, but could be Kinetc.Groups for more flexibility.
During the dragend event, a new duplicate copy of the dragged icon is created in the destination group.
After the dragend event is complete, the original palette icon is automatically moved from
The newly created duplicate icon can be dragged around the destination group (but not outside that group).
Here is code and a Fiddle: http://jsfiddle.net/m1erickson/xtVyL/
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Prototype</title>
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<script src="http://d3lp1msu2r81bx.cloudfront.net/kjs/js/lib/kinetic-v4.5.5.min.js"></script>
<style>
#container{
border:solid 1px #ccc;
margin-top: 10px;
width:350px;
height:350px;
}
</style>
<script>
$(function(){
var stage = new Kinetic.Stage({
container: 'container',
width: 350,
height: 350
});
var layer = new Kinetic.Layer();
stage.add(layer);
// image loader
var imageURLs=[];
var imagesOK=0;
var imgs=[];
imageURLs.push("https://dl.dropboxusercontent.com/u/139992952/stackoverflow/tempPC.png");
imageURLs.push("https://dl.dropboxusercontent.com/u/139992952/stackoverflow/tempServer.png");
imageURLs.push("https://dl.dropboxusercontent.com/u/139992952/stackoverflow/tempRouter.png");
loadAllImages();
function loadAllImages(callback){
for (var i = 0; i < imageURLs.length; i++) {
var img = new Image();
imgs.push(img);
img.onload = function(){
imagesOK++;
if (imagesOK==imageURLs.length ) {
start();
}
};
img.src = imageURLs[i];
}
}
// top icon positions
var nextIconX=20;
var nextIconY=20;
// define groups
var groups=[];
groups.push({x:0,y:100,w:175,h:250,fill:"skyblue"});
groups.push({x:175,y:100,w:175,h:250,fill:"cornsilk"});
// add boundary info to each group
// draw colored rect to show group area
for(var i=0;i<groups.length;i++){
var g=groups[i];
g.left=g.x;
g.right=g.x+g.w;
g.top=g.y;
g.bottom=g.y+g.h;
var rect=new Kinetic.Rect({
x:g.x,
y:g.y,
width:g.w,
height:g.h,
fill:g.fill,
stroke:"gray"
});
layer.add(rect);
}
// hittest for each group
function groupHit(x,y){
for(var i=0;i<groups.length;i++){
var g=groups[i];
if(x>g.left && x<g.right && y>g.top && y<g.bottom){return(i);}
}
return(-1);
}
function start(){
makePaletteIcon(imgs[0]);
makePaletteIcon(imgs[1]);
makePaletteIcon(imgs[2]);
layer.draw();
}
function makePaletteIcon(img){
// make an icon that stays in the pallette tray
var fixedIcon=newImage(nextIconX,nextIconY,img,false);
layer.add(fixedIcon);
// make an icon that is dragged from the tray to a group
var dragIcon=makeDraggableIcon(nextIconX,nextIconY,img);
layer.add(dragIcon);
// calc the next icon position
nextIconX+=(img.width+20);
}
function makeDraggableIcon(x,y,img){
var i=newImage(x,y,img,true);
//
i.trayX=x;
i.trayY=y;
//
i.setOpacity(0.50);
i.on("dragend",function(){
var x=this.getX();
var y=this.getY();
// if this pallette icon was not dropped in a group
// put the icon back in the tray and return
var hit=groupHit(x,y);
if(hit==-1){
this.setPosition(this.trayX,this.trayY);
return;
}
// add a copy of this icon to the drop group
var component=newImage(x,y,this.getImage(),true);
// set drag limits
var group=groups[hit];
component.maxDragLeft=group.left;
component.maxDragRight=group.right;
component.maxDragTop=group.top;
component.maxDragBottom=group.bottom;
// limit component dragging to inside the assigned group
component.setDragBoundFunc(function(pos) {
var xx=pos.x;
var yy=pos.y;
var w=this.getWidth();
var h=this.getHeight();
if(pos.x<this.maxDragLeft){xx=this.maxDragLeft;}
if(pos.x+w>this.maxDragRight){xx=this.maxDragRight-w;}
if(pos.y<this.maxDragTop){yy=this.maxDragTop;}
if(pos.y+h>this.maxDragBottom){yy=this.maxDragBottom-h;}
return{ x:xx, y:yy };
});
layer.add(component);
// move the dragIcon back into the pallette tray
this.setPosition(this.trayX,this.trayY);
layer.draw();
});
return(i);
}
// make a new Kinetic.Image
function newImage(x,y,img,isDraggable){
var i=new Kinetic.Image({
image:img,
x: x,
y: y,
width: img.width,
height: img.height,
draggable:isDraggable
});
return(i);
}
}); // end $(function(){});
</script>
</head>
<body>
<p>Drag any icon from top into blue or yellow group</p>
<div id="container"></div>
</body>
</html>