How do I create a group containing multiple groups of images in fabricjs - image

I need to dynamically add groups of multiple groups of images to the canvas, these "multi-group" will react differently depending on which of he contained groups has been dragged over by another group. I need these multi-groups to be able to be selected, rotated and dragged around the screen.
Ive chosen to use FabricJS and create images using fabric.Image.fromURL, manipulate, then add them to the canvas in a callback function. To add multiple images to a group, you nest more images in cascading callback functions as shown in this tutorial
I cant find a way to get a reference to a group of images created this way so I can add another group of images. Declaring a variable before any calls to fabric.Image.fromURL and trying to assign a reference to a created image doesnt work. The only way Ive got something working is a horrible desparate hack of adding such a group to the canvas then setting it as the active object then creating a new group and adding that active object to the group, this is soooo wrong and I dont think it will work for 3 groups. Heres my last coding attempt;
fabric.Image.fromURL('images/icon_station.png', function(img) {
var img1 = img;
fabric.Image.fromURL('images/icon_curve.png', function(img) {
img.set({left: 64,top: 64,angle: -90});
var img2 = img;
fabric.Image.fromURL('images/icon_curve.png', function(img) {
var img3 = img;
var tile_loop = new fabric.Group([img1, img2, img3]);
tile_loop.set({left: 0, top: 0, height: 64, width: 64});
canvas.add(tile_loop).setActiveObject(tile_loop);
});
});
});
fabric.Image.fromURL('images/icon_station.png', function(img) {
img.set({left: 64,top: 64});
var img1 = img;
fabric.Image.fromURL('images/icon_straight.png', function(img) {
img.set({left: 64,top: 64});
var img2 = img;
fabric.Image.fromURL('images/icon_curve.png', function(img) {
img.set({left: 64,top: 64});
var img3 = img;
var tile_loop = new fabric.Group([img1,img2,img3,canvas.getActiveObject()]);
tile_loop.set({left: 128, top: 128, height: 128, width: 128});
canvas.add(tile_loop);
});
});
});
The only other way Ive seen is to load images into html tags and create them in fabric from the tags, like this
var imgElement = document.getElementById('my-img');
var imgInstance = new fabric.Image(imgElement, {left: 100,top: 100});
canvas.add(imgInstance);
but that seems like a last resort work around, I would prefer to load images up directly in fabricjs.
Ive tried to do this in Phaser but it doesnt do groups of groups, Fabric has all the other stuff I need so I would like to stay with it, its a shame it does these weird ways of handling images.
Any help is much appreciated, if all else fails Ill use the last resort, it would be great to find a clever correct way.
I created an example in JSFiddle;
http://jsfiddle.net/d7bjbpfb/

Related

JSPDF with autoTable addImage

I'm currently working on creating pdf files with jspdf and the AutoTable plugin.
My plan is to create a table like this:
I have the images as local urls, and I'm trying to add them to the pdf using the new Image and adding them as .src to the image.
When I directly run the jspdf.addImage function with the image, the images display correctly.
But I'm struggling to get the correct scaling to work. So I wanted to use the width and height properties from the image, but for this to work you need to wait for the image to load.
I tried to use the onload function, but it stops rendering the table in general, because it skips over the function and continues with the next table before the images load.
If you have any suggestions of how to get something like this to work it would be greatly appreciated. The images all have variable resolution and need to be scaled down to properly fit in the table. I'll paste my working code (but without the height and width scaling) below.
doc.autoTable({
head: [{comments: 'Photos'}],
body: body,
styles: {
lineColor: [0, 0, 0],
lineWidth: 0.4,
},
headStyles: {
fillColor: [191, 191, 191],
textColor: [0, 0, 0],
},
didDrawCell: function (data) {
if (data.section === 'body') {
console.log(data);
const image = new Image();
// image.onload = function() {
// console.log(this);
// const width = this.width;
// const height = this.height;
// console.log({width, height})
// doc.addImage(
// this,
// 'JPEG',
// data.cell.x + 5,
// data.cell.y + 2,
// )
// }
image.src = QuestionPhotos.link(photosObject[data.cell.raw])
doc.addImage(
image,
'JPEG',
data.cell.x + 5,
data.cell.y + 2,
)
}
}
The commented out part of this code was my other attempt where I would add the image after it was loaded, but this made the image not appear at all in the pdf.
I have been able to solve most of my question using the following functions:
The scaling I was able to fix by storing the width and height together with my images. I then set my height to be fixed (in my case 80mm) and calculated the width like this: photo.height / photo.width * 80.
I tried the other functions Ihsan mentioned, but without any luck.
The didDrawCell is used for inserting the images after the cell has been drawn. willDrawCell draws the image behind the table. didParseCell will insert the images in the top corner of the page instead of in the table itself.
I also initially used the didParseCell to increase the size of the cells to be the correct size for the images, but later on changed this to adding the style when creating the body of the table.
body.push([{content:'', styles: {minCellHeight: height + 10}}]);
I kept track of the row index to know at what rows to insert the image and so that the sizing is correct.

canvas.toDataURL() - For bigger canvas-image

I would like to two show a Chart and also present an url with a link to a bigger Chart. The small preview-image looks and works fine:
<canvas id="image"></canvas>
var ct1 = document.getElementById("image").getContext("2d");
ct1.canvas.width = document.getElementById("image").offsetWidth;
ct1.canvas.height = document.getElementById("image").offsetHeight;
var Chart1 = new Chart(ct1).Line(lineChartData1,options);
The canvas is wrapped in a div, that's why offsetWidth and offsetHeight (to fill this additional div-element). Cause of the responsive-design there is no fixed image. Anyway, this works perfectly. For the URL to the "bigger" image I want to have the URL. I know the toDataURL() will help.
var url = document.getElementById("image").toDataURL();
document.write(url);
There are two disturbing problems with it:
The URL with this way exisists and, but the image has no content.
I also want to give the canvas-image a new size, like I managed with ct1.canvas.width and ct1.canvas.height, but it seems I cannot add this to the toDataURL.
What's wrong with the code?
Okay, I think I got it. Chart.js is animating the charts, so the toDataURL() I mentioned in my first question rendered only an empty image. We have to initiate the toDataURL, not before the animation is done. We handle that with the options:
var options = {
onAnimationComplete: done
}
and a tiny function:
function done() {
console.log('done');
var url=document.getElementById("canvas").toDataURL();
document.getElementById("canvas_link").href=url;
}
I think that's all.

Google Apps Script User Interface

Well, I've been reading the documentation and I believe that I'm calling functions and passing parameters correctly, but for the life of me I can't get this simple UI code to work.
I'm generating a UI for a Spreadsheet using the following code:
function checkOut() {
var app = buildUI();
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
spreadsheet.show(app);
}
function buildUI() {
var gui = UiApp.createApplication();
gui.setTitle("Check-Out/Check-In");
gui.setStyleAttribute("background", "lavender");
// Absolute panel for setting specific locations for elements
var panel = gui.createAbsolutePanel();
// Equipment ID#s Label
var equipmentIDLabel = gui.createLabel("Equipment ID#s");
equipmentIDLabel.setHorizontalAlignment(UiApp.HorizontalAlignment.CENTER);
equipmentIDLabel.setSize("20px", "125px");
equipmentIDLabel.setStyleAttributes({background: "SteelBlue", color: "white"});
// Add all components to panel
panel.add(equipmentIDLabel, 10, 0);
gui.add(panel);
return gui;
}
function getUIdata(eventInfo) {
// I know how to get the data from each element based on ID
}
It generates the Absolute Panel correctly when checkOut() is called, but the EquipmentIDLabel is never added to the panel. I am basing the code on the simplistic design I created in the GUI builder (that will be deprecated in a few days, which is why I am writing the code so that I can change it later):
So what exactly is going wrong here? If I can figure out how to add one element, I can infer the rest by looking at the docs. I've never been any good at GUI development!
You could maybe use grid as an interesting alternative... here is an example :
// define styles
var labelStyle = {background: "SteelBlue", color: "white",'textAlign':'center','line-height':'20px','vertical-align':'middle','font-family':"Arial, sans-serif",'fontSize':'10pt'};// define a common label style
var fieldStyle = {background: "white", color: "SteelBlue",'font-family':"Courrier, serif",'fontSize':'11pt'};// define a common label style
function checkOut() {
var app = buildUI();
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
spreadsheet.show(app);
}
function buildUI() {
var gui = UiApp.createApplication();
gui.setTitle("Check-Out/Check-In");
gui.setStyleAttribute("background", "lavender");
var panel = gui.createAbsolutePanel().setStyleAttribute('padding','10px');
var grid = gui.createGrid(4,2).setWidth('300').setCellPadding(10);//define grid size in number of row & cols
var equipmentID = ['equipmentIDLabel','equipmentIDLabel1','equipmentIDLabel2','equipmentIDLabel3'];// define labels in an array of strings
for(var n=0 ;n<equipmentID.length ; n++){;// iterate
var equipmentIDLabel = gui.createLabel(equipmentID[n]).setWidth('125').setStyleAttributes(labelStyle);
var equipmentIDField = gui.createTextBox().setText('Enter value here').setName(equipmentID[n]).setSize("125", "20").setStyleAttributes(fieldStyle);
grid.setWidget(n,0,equipmentIDLabel).setWidget(n,1,equipmentIDField);
}
gui.add(panel.add(grid));
return gui;
}
It looks like the absolute panel offset method is a little capricious and take control of your positioning, in my tests I have been able to position panels that are visible in the following way:
panel.add(equipmentIDLabel);
panel.add(equipmentIDField,150,0);
panel.add(otherLabel);
panel.add(otherField, 150, 20);
Try it out with trial and error, you may get the UI you need, if not I would move to an alternate layout, verticalPanel is a little better behaved and of course you can use forms as well.
Another small bug is that you inverted the length and hight in equipmentIDLabel.setSize("20px", "125px");
Let me know if I can be of more assitance
The specific problem in your code is the following line :
// Add all components to panel
panel.add(equipmentIDLabel, 10, 0);
Simply change it to : panel.add(equipmentIDLabel);
..and you will see the field (at position 0,0).
As patt0 observes, you can then add OTHER components and use positioning. It seems to be a limitation of adding the first field to an absolutePanel.
Of course, the Google Script gui is now deprecated (since December 2014) but I was interested to try your code and see that it still basically executes (as at Feb 2016).

KineticJS : get image array id

Here is the problem :
I have a canvas, and four (would be more in future, but 4 for testing...anyway, doesn't matter) images that can be "poped" into the canvas by clicking on it.
Each image can be present multiple times in the canvas.
So far, poping is working fine, images are draggable... But I can't add some resize or zIndex function as I can only select the last image add to the canvas.
In a ideal world, I would like, by clicking/dragging an image, put it on top of the canvas, and kinda "select" it, so that I can connect the resize functions to the image.
But with the array of images, I can't manage to identify properly the item dragged, and can't use (or don't manage to use) the selectors.
Thank you.
EDIT : some code
var imgCpt = 0;
var image = [];
function addDetails(img) {
imgCpt++;
var imageObj = new Image();
imageObj.onload = function() {
image[imgCpt] = new Kinetic.Image({
x: 0,
y: 0,
image: imageObj,
draggable: true,
id:image[imgCpt]
});
image[imgCpt].setX((stage.getWidth()/2) - (image[imgCpt].getWidth()/2));
image[imgCpt].setY((stage.getHeight()/2) - (image[imgCpt].getHeight()/2));
eval(image[imgCpt]).on('click', function() {
alert(eval(imgCpt));
});
layer.add(image[imgCpt]);
stage.add(layer);
};
imageObj.src = 'uploads/'+img;
}
I've already tried different solutions : multiple layer, and acting on it instead of acting on image, working with shapes filled with image instead of image, but it's always the same problem : I can't get the id of the concerned element (instead of the id of the last insert element)
This version works with array, but I tried yersterday to build the image id with eval(); without more success.
Thank you for your help
EDIT² : sorry to insist, but I would really be glad to have some assistance on this point, even if I think it's more JS related than pure KineticJS related.
Thank you.
Ok Guys, just solved the problem :
eval("image["+imgCpt+"].on('click', function() {alert("+imgCpt+");});");
Instead of :
eval(image[imgCpt]).on('click', function() {
alert(eval(imgCpt));
});
Now time to set a true action behind the click.
Thank you for helping ;)

Kineticjs - Help uploading images to stage from input file

I am trying to allow users upload their own images to the kineticJS stage through an input in the html. I prefer to keep all my code in a separate js file, here is what i have so far:
$(document).ready(function() {
var stage = new Kinetic.Stage({
container: 'container',
width: 900,
height: 500
});
var layer = new Kinetic.Layer();
});
function addImage(){
var imageObj = new Image();
imageObj.onload = function() {
var myImage = new Kinetic.Image({
x: 140,
y: stage.getHeight() / 2 - 59,
image: imageObj,
width: 106,
height: 118
});
layer.add(myImage);
stage.add(layer);
}
var f = document.getElementById('uploadimage').files[0];
var name = f.name;
var url = window.URL;
var src = url.createObjectURL(f);
imageObj.src = src;
}
How do I expose the stage to the addImage() method? It is out of its scope at the moment and I havent been able to figure out how to solve the problem as the canvas doesn't show in the html until something is added to it. I need these images to be added as layers for future manipulation so want to use kineticJS. Any suggestions would be much appreciated!
http://jsfiddle.net/8XKBM/12/
I managed to get your addImage function working by attaching an event to it. If you use the Firebug console in Firefox or just press Ctrl+Shift+J you can get javascript errors. It turns out your function was being read as undefined, so now the alert is working, but your image isn't added because they aren't stored anywhere yet, like on a server (must be uploaded somewhere first)
I used jQuery to attach the event as you should use that instead of onclick='function()'
$('#addImg').on('click', function() {
addImage();
});
and changed
<div>
<input type="file" name="img" size="5" id="uploadimage" />
<button id='addImg' value="Upload" >Upload</button>
</div>
What you would really want to do is have the user upload the photos (to the server) on the fly using AJAX, (available with jQuery, doesn't interfere with KineticJS). Then, on success, you can draw the photo onto the canvas using your function. Make sure to use:
layer.draw()
or
stage.draw()
at the end of the addImage() function so that the photo is drawn on your canvas, as the browser does not draw the image until after the page loads and img.src is defined at the end. So, this will basically just require things to be in correct order rather than being difficult.
So, step 1: upload using AJAX (to server), step 2: add to stage, step 3: redraw stage

Resources