Forge MarkupUtils renderToCanvas with multiple layers? - autodesk-forge

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.

Related

Drawing player in-between Tiled .json parsed Layers

Hello world for the first time famed stack overflow!
I am parsing a Tiled-made .json map(just a test) from Reading JSON Tiled map editor file and displaying to canvas
I don't fully understand the renderLayer function, and if advisable will open a new question on that. From my rough understanding it renders each layer by tile and pushes to the final "scene" img that will be drawn.
My question specifically is how to draw a "player" object between layers. I have a Top, Middle, and Bottom layer. The middle layer is empty and where I intend to draw the player in order to add depth. I don't know where in the code to add my player update function, and I also feel like having a player update function inside the map rendering is not very OOP..
In comments is how I can get the layer name, and I have a player update function that will handle drawing.
Thanks in advance for any suggestions\explanation.
Code for the rendering the map:
$(function() {
var c = document.getElementById("canvas").getContext('2d');
scene = {
layers: [],
renderLayer: function(layer) {
/*
if(layer.name == "Middle"){
player.update();
}
*/
if (layer.type !== "tilelayer" || !layer.opacity) { return; }
var s = c.canvas.cloneNode(),
size = scene.data.tilewidth;
s = s.getContext("2d");
if (scene.layers.length < scene.data.layers.length) {
layer.data.forEach(function(tile_idx, i) {
if (!tile_idx) { return; }
var img_x, img_y, s_x, s_y,
tile = scene.data.tilesets[0];
tile_idx--;
img_x = (tile_idx % (tile.imagewidth / size)) * size;
img_y = ~~(tile_idx / (tile.imagewidth / size)) * size;
s_x = (i % layer.width) * size;
s_y = ~~(i / layer.width) * size;
s.drawImage(scene.tileset, img_x, img_y, size, size, s_x, s_y, size, size);
});
scene.layers.push(s.canvas.toDataURL());
c.drawImage(s.canvas, 0, 0);
}else {
scene.layers.forEach(function(src) {
//console.log(scene);
var i = $("<img />", { src: src })[0];
c.drawImage(i, 0, 0);
});
}
},
renderLayers: function(layers) {
layers = Array.isArray(layers) ? layers : this.data.layers;
layers.forEach(this.renderLayer);
},
loadTileset: function(json) {
this.data = json;
this.tileset = $("<img />", { src: json.tilesets[0].image })[0]
this.tileset.onload = $.proxy(this.renderLayers, this);
},
load: function() {
return $.ajax({
url: "map.json",
dataType: "text json"
}).done($.proxy(this.loadTileset, this));
}
};
scene.load();
});
renderMap = function(){
scene.renderLayers();
}
Your understanding is right.
I suggest to draw each layer separately. With this you can draw your Player between specified layers and you also have more control over visibilty.
As you might know, in tiled you can add several layers. Load them separately in your game and draw them one by one.
So you have to split your renderLayers() in that manner that not all layers are drawn at once (layers.forEach(this.renderLayer); ) but in a given order.
Pseudo Code:
scene.renderLayer(0);
scene.renderLayer(1);
player.Draw()
scene.renderLayer(2);

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();***
}
}
}
}

Cannot drag the image once it dropped on to the target and dragging happens only once

Following is my code.
var canvas = document.getElementById("siddhiCanvas");
if (canvas.getContext)
{
var ctx = canvas.getContext('2d');
//Modify the value of x & y and see the effect
ctx.scale(2, 2);
for (i = 0; i < 1000; i += 10) {
ctx.moveTo(0, i);
ctx.strokeStyle = "#D8D8D8";
ctx.lineTo(canvas.width, i);
ctx.stroke();
}
for (i = 0; i < 1000; i += 10) {
ctx.moveTo(i, 0);
ctx.strokeStyle = "#D8D8D8";
ctx.lineTo(i, canvas.height);
ctx.stroke();
}
}
canvas.style.border = "black 1px solid";
var ctx=canvas.getContext("2d");
var $canvas=$("#siddhiCanvas");
var canvasOffset=$canvas.offset();
var offsetX=canvasOffset.left;
var offsetY=canvasOffset.top;
var image1=new Image();
image1=document.getElementById("arrow").src;
var $arrow=$("#arrow");
var $canvas=$("#siddhiCanvas");
$arrow.draggable( {
cursor: 'move',
helper: 'clone'
} );
$arrow.data("image",image1); // key-value pair
$canvas.droppable({
drop:dragDrop,
});
function dragDrop(e,ui){
var element=ui.draggable;
var data=element.data("url");
var x=parseInt(ui.offset.left-offsetX);
var y=parseInt(ui.offset.top-offsetY);
ctx.drawImage(element.data("image"),x-1,y);
}
above code is within the document.onready function and the html part contains the canvas tag and the image.
As stated earlier i can drag only once and once a image dropped to the target i cannot further move it.. Also i drew strokes on the canvas and when i drag the image it goes to behind of those lines. Can anyone please help me on this...Thanks in advance.
You can combine jQueryUI and KineticJS to drag an image element from the toolbar to the Kinetic.Stage.
Demo: http://jsfiddle.net/m1erickson/LuZbV/
Step#1 Use jQueryUI to make the image element in the toolbar draggable:
// make the toolbar image draggable
$house.draggable({
helper:'clone',
});
Step#2 Set the data payload on the draggable image element
// set the data payload
$house.data("url","house.png"); // key-value pair
$house.data("width","32"); // key-value pair
$house.data("height","32"); // key-value pair
$house.data("image",image1); // key-value pair
Step#3 Make the Kinetic container div a dropzone
// make the Kinetic Container a dropzone
$stageContainer.droppable({
drop:dragDrop,
});
Step#4 When the user drops the draggable image element, create a Kinetic.Image using its data payload.
// hangle a drop into the Kinetic container
function dragDrop(e,ui){
// get the drop point
var x=parseInt(ui.offset.left-offsetX);
var y=parseInt(ui.offset.top-offsetY);
// get the drop payload (here the payload is the image)
var element=ui.draggable;
var data=element.data("url");
var theImage=element.data("image");
// create a new Kinetic.Image at the drop point
// be sure to adjust for any border width (here border==1)
var image = new Kinetic.Image({
name:data,
x:x,
y:y,
image:theImage,
draggable: true
});
layer.add(image);
layer.draw();
}

html5 canvas in durandal

i am trying to use the html5 canvas element to draw in durandal without success
the markup
<body>
<canvas id="canvas"></canvas>
</body>
the js
define(function() {
function viewAttached() {
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var buffer = document.createElement('canvas');
buffer.width = buffer.height = 60;
var bctx = buffer.getContext('2d');
bctx.translate(30, 30);
bctx.rotate(0.5);
bctx.fillStyle = 'rgb(255, 0, 0)';
bctx.fillRect(-15, -15, 30, 30);
ctx.fillStyle = 'rgb(0, 255, 0)';
ctx.fillRect(30, 30, 30, 30);
ctx.drawImage(buffer, 50, 50);
}
var ctor = function () {
this.viewAttached = viewAttached();
return ctor;
};
});
the problem is that canvas doesn't load so all what i can see is an empty canvas. the same code is working well in jsfiddle i mean only jquery and canvas but when i transfer it to durandal it doesnt work anymore can anybody help?
In the comments you mentioned it is working with viewAttached(), but in fact in your question you mention that it is indeed not working. My best bet is that A: nemesv is right and you may be firing that function, but not in the way you intend and B: Your view isn't actually attached when you call viewAttached.
This works in Durandal 1.2 -
define(function() {
function viewAttached() {
// Do stuff
}
var viewModel = {
viewAttached: viewAttached;
};
return viewModel;
});
If you are using Durandal 2.0, do it like this -
define(function() {
function attached() {
// Do stuff
}
var viewModel = {
attached: attached;
};
return viewModel;
});
That assumes you are using the router. You don't need to fire the constructor, Durandal handles that for you. You also don't need to call viewAttached, because Durandal handles that at the proper point in the loading cycle (after the DOM is ready) http://durandaljs.com/documentation/Using-The-Router/

Swapping between layers in kineticJS

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();
}