Viewed   89 times

I'm working on a generative art project where I would like to allow users to save the resulting images from an algorithm. The general idea is:

  • Create an image on an HTML5 Canvas using a generative algorithm
  • When the image is completed, allow users to save the canvas as an image file to the server
  • Allow the user to either download the image or add it to a gallery of pieces of produced using the algorithm.

However, I’m stuck on the second step. After some help from Google, I found this blog post, which seemed to be exactly what I wanted:

Which led to the JavaScript code:

function saveImage() {
  var canvasData = canvas.toDataURL("image/png");
  var ajax = new XMLHttpRequest();

  ajax.open("POST", "testSave.php", false);
  ajax.onreadystatechange = function() {
    console.log(ajax.responseText);
  }
  ajax.setRequestHeader("Content-Type", "application/upload");
  ajax.send("imgData=" + canvasData);
}

and corresponding PHP (testSave.php):

<?php
if (isset($GLOBALS["HTTP_RAW_POST_DATA"])) {
  $imageData = $GLOBALS['HTTP_RAW_POST_DATA'];
  $filteredData = substr($imageData, strpos($imageData, ",") + 1);
  $unencodedData = base64_decode($filteredData);
  $fp = fopen('/path/to/file.png', 'wb');

  fwrite($fp, $unencodedData);
  fclose($fp);
}
?>

But this doesn’t seem to do anything at all.

More Googling turns up this blog post which is based off of the previous tutorial. Not very different, but perhaps worth a try:

$data = $_POST['imgData'];
$file = "/path/to/file.png";
$uri = substr($data,strpos($data, ",") + 1);

file_put_contents($file, base64_decode($uri));
echo $file;

This one creates a file (yay) but it’s corrupted and doesn’t seem to contain anything. It also appears to be empty (file size of 0).

Is there anything really obvious that I’m doing wrong? The path where I’m storing my file is writable, so that isn’t an issue, but nothing seems to be happening and I’m not really sure how to debug this.

Edit

Following Salvidor Dali’s link I changed the AJAX request to be:

function saveImage() {
  var canvasData = canvas.toDataURL("image/png");
  var xmlHttpReq = false;

  if (window.XMLHttpRequest) {
    ajax = new XMLHttpRequest();
  }
  else if (window.ActiveXObject) {
    ajax = new ActiveXObject("Microsoft.XMLHTTP");
  }

  ajax.open("POST", "testSave.php", false);
  ajax.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
  ajax.onreadystatechange = function() {
    console.log(ajax.responseText);
  }
  ajax.send("imgData=" + canvasData);
}

And now the image file is created and isn’t empty! It seems as if the content type matters and that changing it to x-www-form-urlencoded allowed the image data to be sent.

The console returns the (rather large) string of base64 code and the datafile is ~140 kB. However, I still can’t open it and it seems to not be formatted as an image.

 Answers

1

Here is an example of how to achieve what you need:

  1. Draw something (taken from canvas tutorial)

<canvas id="myCanvas" width="578" height="200"></canvas>
<script>
  var canvas = document.getElementById('myCanvas');
  var context = canvas.getContext('2d');

  // begin custom shape
  context.beginPath();
  context.moveTo(170, 80);
  context.bezierCurveTo(130, 100, 130, 150, 230, 150);
  context.bezierCurveTo(250, 180, 320, 180, 340, 150);
  context.bezierCurveTo(420, 150, 420, 120, 390, 100);
  context.bezierCurveTo(430, 40, 370, 30, 340, 50);
  context.bezierCurveTo(320, 5, 250, 20, 250, 50);
  context.bezierCurveTo(200, 5, 150, 20, 170, 80);

  // complete custom shape
  context.closePath();
  context.lineWidth = 5;
  context.fillStyle = '#8ED6FF';
  context.fill();
  context.strokeStyle = 'blue';
  context.stroke();
</script>
  1. Convert canvas image to URL format (base64)

    var dataURL = canvas.toDataURL();

  2. Send it to your server via Ajax

    $.ajax({
      type: "POST",
      url: "script.php",
      data: { 
         imgBase64: dataURL
      }
    }).done(function(o) {
      console.log('saved'); 
      // If you want the file to be visible in the browser 
      // - please modify the callback in javascript. All you
      // need is to return the url to the file, you just saved 
      // and than put the image in your browser.
    });
  1. Save base64 on your server as an image (here is how to do this in PHP, the same ideas is in every language. Server side in PHP can be found here):
Sunday, November 13, 2022
1

The short answer:

SVG would be easier for you, since selection and moving it around is already built in. SVG objects are DOM objects, so they have "click" handlers, etc.

DIVs are okay but clunky and have awful performance loading at large numbers.

Canvas has the best performance hands-down, but you have to implement all concepts of managed state (object selection, etc) yourself, or use a library.


The long answer:

HTML5 Canvas is simply a drawing surface for a bit-map. You set up to draw (Say with a color and line thickness), draw that thing, and then the Canvas has no knowledge of that thing: It doesn't know where it is or what it is that you've just drawn, it's just pixels. If you want to draw rectangles and have them move around or be selectable then you have to code all of that from scratch, including the code to remember that you drew them.

SVG on the other hand must maintain references to each object that it renders. Every SVG/VML element you create is a real element in the DOM. By default this allows you to keep much better track of the elements you create and makes dealing with things like mouse events easier by default, but it slows down significantly when there are a large number of objects

Those SVG DOM references mean that some of the footwork of dealing with the things you draw is done for you. And SVG is faster when rendering really large objects, but slower when rendering many objects.

A game would probably be faster in Canvas. A huge map program would probably be faster in SVG. If you do want to use Canvas, I have some tutorials on getting movable objects up and running here.

Canvas would be better for faster things and heavy bitmap manipulation (like animation), but will take more code if you want lots of interactivity.

I've run a bunch of numbers on HTML DIV-made drawing versus Canvas-made drawing. I could make a huge post about the benefits of each, but I will give some of the relevant results of my tests to consider for your specific application:

I made Canvas and HTML DIV test pages, both had movable "nodes." Canvas nodes were objects I created and kept track of in Javascript. HTML nodes were movable Divs.

I added 100,000 nodes to each of my two tests. They performed quite differently:

The HTML test tab took forever to load (timed at slightly under 5 minutes, chrome asked to kill the page the first time). Chrome's task manager says that tab is taking up 168MB. It takes up 12-13% CPU time when I am looking at it, 0% when I am not looking.

The Canvas tab loaded in one second and takes up 30MB. It also takes up 13% of CPU time all of the time, regardless of whether or not one is looking at it. (2013 edit: They've mostly fixed that)

Dragging on the HTML page is smoother, which is expected by the design, since the current setup is to redraw EVERYTHING every 30 milliseconds in the Canvas test. There are plenty of optimizations to be had for Canvas for this. (canvas invalidation being the easiest, also clipping regions, selective redrawing, etc.. just depends on how much you feel like implementing)

There is no doubt you could get Canvas to be faster at object manipulation as the divs in that simple test, and of course far faster in the load time. Drawing/loading is faster in Canvas and has far more room for optimizations, too (ie, excluding things that are off-screen is very easy).

Conclusion:

  • SVG is probably better for applications and apps with few items (less than 1000? Depends really)
  • Canvas is better for thousands of objects and careful manipulation, but a lot more code (or a library) is needed to get it off the ground.
  • HTML Divs are clunky and do not scale, making a circle is only possible with rounded corners, making complex shapes is possible but involves hundreds of tiny tiny pixel-wide divs. Madness ensues.
Friday, September 16, 2022
 
salem
 
5

Firefox does not support drawing SVG images to canvas unless the svg file has width/height attributes on the root <svg> element and those width/height attributes are not percentages. This is a longstanding bug.

You will need to edit the icon.svg file so it meets the above criteria.

Tuesday, September 6, 2022
 
snowjon
 
2

When writing binary data to a file, such as is the case with an image, you cannot use text printing tools like IO#puts.

There's two things you need to ensure:

  • You need to be writing in binary mode to avoid any possible LF to CRLF expansion.
  • You must use write instead of puts as write can work on arbitrary data, but puts (literally "put string") is exclusively for text.

Combining these you get:

File.open('shipping_label.gif', 'wb') do |f|
  f.write(Base64.decode64(base_64_encoded_data))
end
Monday, November 7, 2022
 
rika
 
1

This should be possible with the webshot package (see question here: https://github.com/jbkunst/highcharter/issues/186)

library(webshot)
library(highcharter)
library(plyr)

data("citytemp")

plot <- highchart() %>% 
  hc_add_series(name = "London", data = citytemp$london, type = "area") %>% 
  hc_rm_series(name = "New York")

htmlwidgets::saveWidget(widget = plot, file = "~/plot.html")
setwd("~")
webshot::webshot(url = "plot.html", 
                 file = "plot.png")

Wednesday, September 21, 2022
 
Only authorized users can answer the search term. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :