Swapping between layers in kineticJS - html

I'm trying to treat layers as pages -- i.e. I draw on one page, then turn the page and draw on another, each time storing the previous page in case the user goes back to it.
In my mind this translates as:
Create current_layer global pointer.
Each time newPage() is called, store the old layer in an array, and overwrite the pointer
layer_array.push(current_layer); //store old layer
current_layer = new Kinetic.Layer(); //overwrite with a new
New objects are then added to the current_layer which binds them to the layer, whether they are drawn or not. (e.g. current_layer.add(myCircle) )
Retrieving a page is simply updating the pointer to the requesting layer in the array, and redrawing the page. All the child nodes attached to the layer will also be drawn too
current_layer = layer_array[num-1]; //num is Page 2 e.g
current_layer.draw()
However nothing is happening! I can create new pages, and store them appropriately - but I cannot retrieve them again...
Here's my full code (my browser is having problems using jsfiddle):
<html>
<head>
<script src="http://d3lp1msu2r81bx.cloudfront.net/kjs/js/lib/kinetic-v4.3.0.min.js"></script>
<script>
//Global
var stage; //canvas
var layer_array = [];
var current_page; //pointer to current layer
window.onload = function() {
stage = new Kinetic.Stage({
container: 'container',
width: 400,
height: 400
});
//Add initial page to stage to draw on
newPage()
};
//--- Functions ----//
function newPage(){
if(!current_page){
console.log("current page undefined");
} else {
layer_array.push(current_page);
// stage.remove(current_page);
//Nope, not working.
stage.removeChildren();
//Works, but I think it unbinds all objects
// from their specific layers...
// stage.draw()
console.log("Stored layer and removed it from stage");
}
current_page = new Kinetic.Layer();
console.log("Currently on page:"+(layer_array.length+1));
stage.add(current_page);
stage.draw();
}
function gotoPage(num){
stage.removeChildren()
stage.draw()
num = num-1;
if(num >= 0) {
current_page = layer_array[num];
console.log("Now on page"+(num+1));
stage.add(current_page);
stage.draw();
}
}
function addCircletoCurrentPage()
{
var rand = Math.floor(3+(Math.random()*10));
var obj = new Kinetic.Circle({
x: rand*16, y: rand*16,
radius: rand,
fill: 'red'
})
var imagelayer = current_page;
imagelayer.add(obj);
imagelayer.draw();
}
</script>
</head>
<body>
<div id="container"></div>
<button onclick="addCircletoCurrentPage()" >click</button>
<button onclick="newPage()" >new</button>
<button onclick="gotoPage(1)" >page1</button>
<button onclick="gotoPage(2)" >page2</button>
<button onclick="gotoPage(3)" >page3</button>
</body>
</html>

This was a fun problem. I think this fixes your troubles: http://jsfiddle.net/LRNHk/3/
Basically, you shouldn't remove() or removeChildren() as you risk de-referencing them.
Instead you should use:
layer.hide(); and layer.show();
this way, you keep all things equal and you get speedy draw performance.
So your go to page function should be like this:
function gotoPage(num){
for(var i=0; i<layer_array.length; i++) {
layer_array[i].hide();
}
layer_array[num].show();
console.log("Currently on page:"+(num));
console.log("Current layer: " + layer_array[num].getName());
stage.draw();
}
I also modified your other functions, which you can see in the jsfiddle.

Okay I changed my approach and instead of swapping layers (100x easier and makes more sense), I instead opted for serializing the entire stage and loading it back.
It works, but it really shouldn't have to be like this dammit
//Global
var stage; //canvas
var layer_array = [];
var current_page; //pointer to current layer
var page_num = 0;
window.onload = function() {
stage = new Kinetic.Stage({
container: 'container',
width: 400,
height: 400
});
//Add initial page to stage to draw on
newPage()
};
//--- Functions ----//
function newPage(){
if(!current_page){
console.log("current page undefined");
} else {
savePage(page_num)
stage.removeChildren()
console.log("Stored layer and removed it from stage");
}
current_page = new Kinetic.Layer();
console.log("Currently on page:"+(layer_array.length+1));
stage.add(current_page);
stage.draw();
page_num ++;
}
function savePage(num){
if( (num-1) >=0){
var store = stage.toJSON();
layer_array[num-1] = store;
console.log("Stored page:"+num)
}
}
function gotoPage(num){
savePage(page_num);
stage.removeChildren()
if(num-1 >= 0) {
var load = layer_array[num-1];
document.getElementById('container').innerHTML = ""; //blank
stage = Kinetic.Node.create(load, 'container');
var images = stage.get(".image");
for(i=0;i<images.length;i++)
{
//function to induce scope
(function() {
var image = images[i];
var imageObj = new Image();
imageObj.onload = function() {
image.setImage(imageObj);
current_page.draw();
};
imageObj.src = image.attrs.src;
})();
}
stage.draw();
page_num =num //update page
}
}
function addCircletoCurrentPage()
{
var rand = Math.floor(3+(Math.random()*10));
var obj = new Kinetic.Circle({
x: rand*16, y: rand*16, name: "image",
radius: rand,
fill: 'red'
})
var imagelayer = current_page;
imagelayer.add(obj);
imagelayer.draw();
}

Related

Forge MarkupUtils renderToCanvas with multiple layers?

We currently have a PDF loaded into Forge viewer with multiple markup layers (created using a custom DrawMode), each layers visibility toggleable.
We want to give the user the ability of printing what they currently see (the PDF with the layered markup). I was able to find posts offering potential solutions for printing (using canvas, getScreenshot and MarkupUtils renderToCanvas).
Example post: Autodesk Forge get screenshot with markups
The solution at first looked to be working great but I've noticed only one of our markup layers is ever rendered to the canvas (seemingly the last one added), the other layers are ignored.
All markups are loaded and are visible on screen. Additionally if I hide that layer, it is still printed.
Is there any way to add the markup from all loaded markup layers using renderToCanvas?
Or any potential known workaround?
Any help appreciated. Thanks in advance.
Code snippet of the function I've wrote which works (but loading the most recently added layer only).
export const printViewerToPDF = (markupsCore: MarkupsCore, jsPDF: any) => {
// Create new image
var screenshot = new Image();
// Get the canvas element
var canvas = document.getElementById('snapshot') as HTMLCanvasElement;
// Fit canvas to match viewer
if (canvas) {
canvas.width = markupsCore.bounds.width;
canvas.height = markupsCore.bounds.height;
// Create a context
var ctx = canvas.getContext('2d');
// Clear
if (ctx) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(screenshot, 0, 0, canvas.width, canvas.height);
}
// Screenshot viewer and render to canvas
markupsCore.viewer.getScreenShot(canvas.width, canvas.height, function(
blobUrl: any,
) {
screenshot.onload = function() {
if (ctx) {
ctx.drawImage(screenshot, 0, 0);
}
};
screenshot.src = blobUrl;
});
// Render markup to canvas
setTimeout(function() {
markupsCore.renderToCanvas(ctx, function() {
var pdf = new jsPDF('l', 'px', [canvas.height, canvas.width]);
pdf.addImage(ctx!.canvas, 0, 0, canvas.width, canvas.height);
pdf.save(Date.now().toString() + '.pdf');
});
}, 300);
}
};
Here's what the renderToCanvas method looks like (you can find it in https://developer.api.autodesk.com/modelderivative/v2/viewers/7.*/extensions/Markup/Markup.js):
MarkupsCore.prototype.renderToCanvas = function(context, callback, renderAllMarkups) {
var width = this.bounds.width;
var height = this.bounds.height;
var viewBox = this.getSvgViewBox(width, height);
var numberOfScreenshotsTaken = 0;
var markups = [];
var layer;
var onMarkupScreenshotTaken = function () {
if (callback && (++numberOfScreenshotsTaken === markups.length)) {
callback();
}
}.bind(this);
if (renderAllMarkups) {
var svgKeys = Object.keys(this.svg.childNodes);
var layersKeys = Object.keys(this.svgLayersMap);
// Append only markups that their parent layer is contained inside the svg main container.
for (var i = 0; i < svgKeys.length; i++) {
for (var j = 0; j < layersKeys.length; j++) {
layer = this.svgLayersMap[layersKeys[j]];
if (this.svg.childNodes[svgKeys[i]] === layer.svg) {
markups = markups.concat(layer.markups);
}
}
}
} else {
layer = this.svgLayersMap[this.activeLayer] || this.editModeSvgLayerNode;
markups = layer.markups;
}
if (markups.length === 0) {
callback();
} else {
markups.forEach(function(markup) {
markup.renderToCanvas(context, viewBox, width, height, onMarkupScreenshotTaken);
});
}
};
As you can see, if you leave the 3rd parameter undefined or set to false, only markups from the active layer will be rendered. If you set the 3rd parameter to true, markups from all layers should be rendered.
Try stepping into the method yourself and double-check the list of markups towards the end, before markup.renderToCanvas is called for every item in the list.

Canvas images as buttons

I have spent hours trying to find an answer to this, but can't find anything that exactly describes what I'm trying to do. I have 6 images that are shaped like jigsaw puzzle pieces, and I place them in proper position on a canvas. What I really want is for each of those puzzle pieces to also act like a button, so when a user clicks on a piece, I can capture that event and then navigate to a new page.
Everything I have found talks about using html buttons and then placing them on the canvas using css- but with these images all being oddly shaped jigsaw pieces, I can't do that.
Is it even possible to capture mouse events when they are on top of a particular image?
Thanks....
Ok, I've managed to track the cursor over each individual puzzle piece. Now, I'm trying to display a different version of the image when cursor hovers over a piece (a prelude to opening a new page). I am trying to store the original image and hover image in the points array, but nothing I try seems to work. I need to be able to show the hover image when the cursor is over the piece, and then restore it when the cursor moves away (haven't gotten that far yet). Right now, I get 404 errors when i try to pull the image out of the points array- tried storing the actual image variable and image pathname, to no avail.
Here's the code:
<script type="text/javascript" language="JavaScript">
var canvas;
var canvasWidth;
var ctx;
function init() {
HideContent('readLess');
var cursors=['default','w-resize','n-resize'];
var currentCursor=0;
canvas = document.getElementById('puzzle-container');
canvas.width = 815;
canvas.height = 425;
canvas.align = 'center';
ctx = canvas.getContext("2d");
var search = new Image();
search.src = 'img/puzzleSearch.png';
var searchHover = new Image();
search.onload = function() {
ctx.drawImage(search, 0, 0);
};
var nav = new Image();
nav.src = 'img/puzzleNav.png';
var navHover = new Image();
nav.onload = function() {
ctx.drawImage(nav, 119, 2.5 );
}
.
.
.
.
var events = new Image();
events.src = 'img/puzzleEvents.png';
var eventsHover = new Image();
eventsHover.src = 'img/puzzleEventsHover.png';
events.onload = function() {
ctx.drawImage(events, 564, 265 );
}
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(); }
$("#puzzle-container").mousemove(function(e){handleMouseMove(e);});
var shapes=[];
shapes.push({
points:[{x:0,y:2.5},{x:155,y:2.5},{x:155,y:205},{x:0,y:205}], cursor:1, img:search, imgHov:searchHover,
});
.
.
.
shapes.push({
points:[{x:0,y:310},{x:250,y:310},{x:250,y:400},{x:0,y:400}], cursor:1, img:events, imgHov:'img/eventsHover.png',
});
for(var i=0;i<shapes.length;i++){
var s=shapes[i];
definePath(s.points);
ctx.stroke();
}
function definePath(p){
ctx.beginPath();
ctx.moveTo(p[0].x,p[0].y);
for(var i=1;i<p.length;i++){
ctx.lineTo(p[i].x,p[i].y);
}
ctx.closePath();
}
function handleMouseMove(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 mousemove stuff here
var newCursor;
for(var i=0;i<shapes.length;i++){
var s=shapes[i];
definePath(s.points);
if(ctx.isPointInPath(mouseX,mouseY)){
if (i === 6 ) {
var img = new Image();
var imgSrc = s.imgHov;
img.src = imgSrc;
console.log("hover image is: " + s.imgHov );
ctx.drawImage(img, 564, 265 );
}
//console("the mouse is in shape "+ i );
newCursor=s.cursor;
break;
}
}
if(!newCursor){
if(currentCursor>0){
currentCursor=0;
canvas.style.cursor=cursors[currentCursor];
}
}else if(!newCursor==currentCursor){
currentCursor=newCursor;
canvas.style.cursor=cursors[currentCursor];
}
}
}
function HideContent(d) {
document.getElementById(d).style.display = "none";
}
function ShowContent(d) {
document.getElementById(d).style.display = "block";
}
function ReverseDisplay(d) {
if(document.getElementById(d).style.display == "none") { document.getElementById(d).style.display = "block"; }
else { document.getElementById(d).style.display = "none"; }
}
</script>
On the console I get the following:
[Error] Failed to load resource: the server responded with a status of 404 (Not Found) ([object HTMLImageElement], line 0)
[Log] hover image is: [object HTMLImageElement] (index.html, line 168)
Is there some trivial thing I'm missing on how to save the images in the points array?
Thanks.....

How can I save both the HTML5 canvas marks AND the image loaded in the canvas?

I have a canvas that I load an image into. The user then makes marks on the canvas with the loaded image as a background. When I attempt to save the image (using toDataURL()) AND the marks made by the user, it only saves the marks, but not the "background" image I loaded into the canvas. Can I save both in one shot?
I want to reload the image and the marks later. If I can't save both in one Base64 string, then I'd have to do some kind of overlay of images if that's even possible. It would be best to just save it.
Below is the code to load the image and save the marks. I didn't think making the marks code was relevant so I left details out.
Thanks for any help.
function SetUp() {
/// load the image
LoadImage();
/// Draw existing marks
DrawMarkedItems();
}
function LoadImage() {
var canvas = document.getElementById("imageView");
if (canvas != null) {
if (canvas.getContext) {
var context = canvas.getContext('2d');
var img = new Image();
img.onload = function () {
context.drawImage(img, 15, 15, 620, 475);
}
img.src = '../Images/Outline.png';
}
}
}
function DrawMarkedItems() {
var canvas = document.getElementById("imageView");
if (canvas != null) {
if (canvas.getContext) {
var list = GetInfoList();
if (list.length == 0)
return;
var pairs = list.split('|').length;
for (var i = 0; i < pairs; i++) {
/// Get the X,Y cooridinates other data
/// saved previously in GetInfoList()
/// and draw the marks back on the
/// canvas with image backgroun
}
}
}
}
function SaveImage()
{
var canvas = document.getElementById("imageView");
var image = canvas.toDataURL("image/png", 0.1);
image = image.replace('data:image/png;base64,', '');
/// WebMethod in code behind
var retval = PageMethods.SaveImage(image);
}
OK, I figured it out. I was loading the background image first, then drawing the existing marks on the canvas (In Setup() calling LoadImage() then DrawMarkedItems()).
I moved the call to DrawMarkedItems() into the LoadImage() function, specifically in the img.onload function.
Below is the modified function. Hope this helps someone else:
function LoadImage() {
var canvas = document.getElementById("imageView");
if (canvas != null) {
if (canvas.getContext) {
var context = canvas.getContext('2d');
var img = new Image();
img.src = '../Images/Outline.png'; //moved up for cosmetics
img.onload = function () {
context.drawImage(img, 15, 15, 620, 475);
***DrawMarkedItems();***
}
}
}
}

why is my canvas zoom/pan slowing down.

I'm a new programmer just trying to learn how to make webpages. I found a code to zoom and pan canvas elements but when I implemented it into an extJS window It started becoming sluggish. It doesn't become sluggish if the image I render is just a shape, only if It's from a file image. I thought at first I was creating instances of objects over and over but I tried deleting the objects after use and it didn't change anything. Why is my zooming slowing down?
Ext.onReady(function(){
Ext.define("w",{
width: 1000,
height: 750,
extend: "Ext.Window",
html: '<canvas id="myCanvas" width="1000" height="750">'
+ 'alternate content'
+ '</canvas>'
,afterRender: function() {
this.callParent(arguments);
var canvas= document.getElementById("myCanvas");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
var stage = new createjs.Stage("myCanvas");
/*function addCircle(r,x,y){
var g=new createjs.Graphics().beginFill("#ff0000").drawCircle(0,0,r);
var s=new createjs.Shape(g)
s.x=x;
s.y=y;
stage.addChild(s);
stage.update();
}*///// If I use this function instead of loading an img there's no slowdown.
function setBG(){
var myImage = new Image();
myImage.src = "dbz.jpg";
myImage.onload = setBG;
var bgrd = new createjs.Bitmap(myImage);
stage.addChild(bgrd);
stage.update();
delete myImage;
delete bgrd;
};
setBG();
//addCircle(40,200,100);
//addCircle(50,400,400);
canvas.addEventListener("mousewheel", MouseWheelHandler, false);
canvas.addEventListener("DOMMouseScroll", MouseWheelHandler, false);
var zoom;
function MouseWheelHandler(e) {
if(Math.max(-1, Math.min(1, (e.wheelDelta || -e.detail)))>0)
zoom=1.1;
else
zoom=1/1.1;
stage.regX=stage.mouseX;
stage.regY=stage.mouseY;
stage.x=stage.mouseX;
stage.y=stage.mouseY;
stage.scaleX=stage.scaleY*=zoom;
stage.update();
delete zoom;
}
stage.addEventListener("stagemousedown", function(e) {
var offset={x:stage.x-e.stageX,y:stage.y-e.stageY};
stage.addEventListener("stagemousemove",function(ev) {
stage.x = ev.stageX+offset.x;
stage.y = ev.stageY+offset.y;
stage.update();
delete offset;
});
stage.addEventListener("stagemouseup", function(){
stage.removeAllEventListeners("stagemousemove");
});
});
} //end aferrender
}); //end define
Ext.create("w", {
autoShow: true });
}); //end onready
It looks like you are infinitely re-loading the BG image. After your BG image finishes loading, your onload function callback just makes it call getBG again which will just repeat the same process forever.
function setBG() {
...
myImage.onload = setBG;
...
}
I'm not sure exactly what you expect by doing this.
You really shouldn't need to delete the image. Off the top of my head, this is how I would generally load an image for use in canvas, (based on how your train of thought is working).
function setBG(){
var myImage = new Image();
myImage.src = "dbz.jpg";
myImage.onload = function(){
var bgrd = new createjs.Bitmap(this);
stage.addChild(bgrd);
stage.update();
}
};
setBG();

I am not able to draw shapes except for random lines onthe canvas. What will be the code for other options eg any line, rectangle or a triangle?

<script type="text/javascript">
var canvas, context, tool, e;
var varblurup=0;
var varsizeup=1;
var swtchclr;
// Keep everything in anonymous function, called on window load.
if(window.addEventListener) {
window.addEventListener('load', function () {
//check tool is pen or line or shape
var chktool="pen";
function init () {
alert("Line3");
// Find the canvas element.
canvas = document.getElementById('canvas');
context = canvas.getContext('2d');
//varblurup=10;
context.shadowColor = 'colour';
context.shadowBlur = 0;
context.lineWidth=1;
context.lineJoin = 'miter';
context.miterLimit = 4;
this.context.save();
// Pencil tool instance.
//tool = new tool_pencil();
//alert("Pen");
if(chktool=="pen")
{ tool = new tool_pencil();
alert("Pen");
}else if (chktool=="line")
{
tool2 = new tool_line();
alert("Line");
}
// Attach the mousedown, mousemove and mouseup event listeners.
canvas.addEventListener('mousedown', ev_canvas, false);
canvas.addEventListener('mousemove', ev_canvas, false);
canvas.addEventListener('mouseup', ev_canvas, false);
}
// This painting tool works like a drawing pencil which tracks the mouse
// movements.
function tool_pencil () {
var tool = this;
this.started = false;
// This is called when you start holding down the mouse button.
// This starts the pencil drawing.
this.mousedown = function (ev) {
context.beginPath();
context.moveTo(ev._x, ev._y);
tool.started = true;
};
// This function is called every time you move the mouse. Obviously, it only
// draws if the tool.started state is set to true (when you are holding down
// the mouse button).
this.mousemove = function (ev) {
if (tool.started) {
context.lineTo(ev._x, ev._y);
//this.style('stroke-opacity').value
context.stroke();
}
};
// This is called when you release the mouse button.
this.mouseup = function (ev) {
if (tool.started) {
tool.mousemove(ev);
tool.started = false;
}
};
}
// The general-purpose event handler. This function just determines the mouse
// position relative to the canvas element.
function ev_canvas (ev) {
if (ev.layerX || ev.layerX == 0) { // Firefox
ev._x = ev.layerX;
ev._y = ev.layerY;
} else if (ev.offsetX || ev.offsetX == 0) { // Opera
ev._x = ev.offsetX;
ev._y = ev.offsetY;
}
// Call the event handler of the tool.
var func = tool[ev.type];
if (func) {
func(ev);
}
}
init();
}, false); }
I found the Mozilla canvas tutorial to be a helpful guide. It covers most areas including shape drawing:
https://developer.mozilla.org/en/Canvas_tutorial
https://developer.mozilla.org/en/Canvas_tutorial/Drawing_shapes
It is quite an extended question so here are a couple of links about it..
HTML5 Canvas: Shape Drawing
A Quick Introduction to the HTML 5 Canvas
A Quick Introduction to the HTML 5 Canvas – Part Two