1

Here is my problem: I have created an image collage function in javascript. (I started off with some code from this post btw: dragging and resizing an image on html5 canvas)

I have 10 canvas elements stacked on top of each other and all parameters, including 2dcontext, image data, positions etc. for each canvas is held in instances of the function 'collage'.

This is working fine, I can manipulate each canvas separately (drag, resize, adding frames, etc). But now and I want the user to be able to save the current work.

So I figure that maybe it would be possible to create a blob, that contains all the object instances, and then save the blob as a file on disk.

This is the function collage (I also push each instance to the array collage.instances, to be able to have numbered indexes)

function collage() {
  this.canvas_board = '';
  this.canvas = '';
  this.ctx = '';
  this.canvasOffset = '';
  this.offsetX = '';
  this.offsetY = '';
  this.startX = '';
  this.startY = '';
  this.imageX = '';
  this.imageY = '';
  this.mouseX = '';
  this.mouseY = '';
  this.imageWidth = '';
  this.imageHeight = '';
  this.imageRight = '';
  this.imageBottom = '';
  this.imgframe = '';
  this.frame = 'noframe';
  this.img = '';
  collage.instances.push(this);
}
collage.instances = [];

I tried with something like this:

var oMyBlob = new Blob(collage.instances, {type: 'multipart/form-data'});

But that doesn't work (only contains about 300 bits of data).

Anyone who can help? Or maybe suggest an alternative way to save the current collage work. It must of course must be possible to open the blob and repopulate the object instances.

Or maybe I am making this a bit more complicated than it has to be... but I am stuck right now, so I would appreciate any hints.

Community
  • 1
  • 1
ToJo_195
  • 51
  • 1
  • 9
  • you could getImageData() on each of your canvases and save that data, or you could canvas.getDataURL() from each to convert to compressed formats before saving. – Patrick Gunderson May 28 '15 at 19:39
  • Thanks for answering. I am in the process of trying out toDataURL (maybe that's what you meant?) on each and sending the data to the server side with ajax... not quite there yet though. – ToJo_195 May 28 '15 at 20:12
  • yea, I meant `toDataURL()`, my answer below could work with ajax as well as it does with saving a local file. – Patrick Gunderson May 28 '15 at 20:17

2 Answers2

1

You can extract each layer's image data to DataURLs and save the result as a json object.

Here's a quick demo: http://codepen.io/gunderson/pen/PqWZwW

The process literally takes each canvas and saves out its data for later import.

The use of jquery here is for convenience:

$(".save-button").click(function() {
  var imgData = JSON.stringify({
    layers: getLayerData()
  });
  save(imgData, "myfile.json");
});

function save(filecontents, filename) {
  try {
    var $a = $("<a>").attr({
      href: "data:application/json;," + filecontents,
      download: filename
    })[0].click();
    return filecontents;
  } catch (err) {
    console.error(err);
    return null;
  }
}

function getLayerData() {
  var imgData = [];
  $(".layer").each(function(i, el) {
    imgData.push(el.toDataURL("image/png"));
  });
  return imgData;
}

To restore, you can use a FileReader to read the contents of the JSON back into the browser, then make <img>s for each layer, set img.src to the dataURLs in your JSON and from there you can draw the <img> into onload canvases.

Patrick Gunderson
  • 3,263
  • 17
  • 28
  • Thanks for the jquery! I already had the same resulting json file, but I now have changed my code (borrowed you getLayerData). I now struggle a bit with separating the layers from the json.file... it would be nice with a key for each one. I am trying to come up with something... – ToJo_195 May 29 '15 at 16:09
  • Got it! As arrays don't have keys but objects do, I just made this small change to be able to navigate by keys in the resulting file (sorry don't know how to format as code here): `function getLayerData() { code: var imgData = {}; $("#board canvas").each(function(i, el) { imgData[i] = el.toDataURL("image/png"); }); console.log('imgdata ' + imgData); return imgData; }` – ToJo_195 May 29 '15 at 18:49
0

Add a reference (src URL) for the image to the instance, then serialize the instance array as JSON and use f.ex. localStorage.

localStorage.setItem("currentwork", JSON.stringify(collage.instances));

Then to restore you would need to do:

var tmp = localStorage.getItem("currentwork");
collage.instances = tmp ? JSON.parse(tmp) : [];

You then need to iterate through the array and reload the images using proper onload handling. Finally re-render everything.

Can you store image data on client? Yes, but not recommended. This will take a lot of space and if too much you will not be able to save all the data, the user may refuse to allow more storage space etc.

Keeping a link to the image on a server is a better approach for these things IMO. But if you disagree, look into IndexedDB (or WebSQL although deprecated) to have local storage which can be expanded in available space. localStorage can only hold between 2.5 - 5 mb, ie. no image data and only strings. Each char takes two bytes, data-uris adds 33% on top, so this will run empty pretty fast...

  • I'd advise against local storage for images since most browsers only allow ~2-3mb of storage. – Patrick Gunderson May 28 '15 at 20:12
  • Thanks for editing my code snippet, And yes, I agree on sending data to the server side. I have succeeded sending the data with ajax to the server side, but I haven't yet figured out how to make the file accessible for the user. Maybe it would be best with plain old http. Work in progress – ToJo_195 May 28 '15 at 20:18
  • @ToJo_195 that will have a wide compatibility. Otherwise IndexedDB is a good option if you want to keep everything locally on user's browser (though, the user may want to switch browser, or use a different computer/device and so on, so online storage is probably the best option all-in-all). –  May 28 '15 at 20:25
  • There's a big downsize to saving image data, especially multiple layer image data to the server: Images are huge, and even on a fat pipe will take a while to download/upload. I think saving the data locally is the right call, just that it should be done in a flat file rather than localstorage. – Patrick Gunderson May 28 '15 at 20:31
  • @PatrickGunderson agreed, though you can create "spritesheets" with the layers spread around on a single image (depending on final size as there is width/height limits for canvas), and then compress the data before uploading (ie. using toBlob/ArrayBuffer. data-uri will x2 + 33% the size of the actual file data so that is not a good option). If user is willing to kill some quality you can further reduce f.ex. PNG more than 2x. –  May 28 '15 at 20:36
  • Sprite sheets are probably not optimal for this type of operation,because of canvas size limitations. Saving the layers as PNG is lossless, and compresses large sections of like-color, so all the extra transparent area will compress pretty flatly. Photoshop actually crops each layer to the "active area" on layers so you can have image data that's persistent off-stage. Then it just moves the top corner of the cropped layer to position when compositiing. – Patrick Gunderson May 28 '15 at 20:47
  • 1
    @PatrickGunderson PNG can be made lossy (see [here](https://github.com/epistemex/lossy-png)) and it uses LZ77 compression (together with scanline filters) so knocking out the lower bits can help reduce the size, but at the cost of quality. Jpeg will introduce artifacts++ Yes, size can be an issue but with a strategy splitting up optimal sized sheets can deal with that. OP doesn't state how large the images are though, If for print then the browser is not best suited in any case, if for screen a collage should offer too much problems as each image will be much smaller than the screen. –  May 28 '15 at 20:50
  • @PatrickGunderson OPs question do invite for many approaches though so I guess it should be closed-voted as "primarily opinion-based". –  May 28 '15 at 20:52
  • 1
    I don't know about closing the question. I wouldn't call it so much "opinion based" as inspecific. We've come up with a few different solutions that all satisfy the original question. If we knew more about the application, we would probably both end up in agreement about the best way/place to store the data. – Patrick Gunderson May 28 '15 at 20:57