Load an Image from an Arbitrary URL into HTML Canvas

While working on Pixie we wanted to be able to import any image from the web into the editor. Ideally this would all occur in client-side JavaScript, but due to a “security” restriction* I believe that it is not possible without extensive workarounds. Fortunately loading an image on the server is actually much easier than working with the Canvas ImageData API (no joke), so though a loss from an efficiency standpoint, from a simplicity standpoint it may be win.

This particular implementation requires RMagick, though any image library should be about as easy. The first step is to read the image from the user-supplied URL, next gather the width and height meta-data.

  def data_from_url(url)
    image_data = Magick::Image.read(url).first
    width = image_data.columns
    height = image_data.rows
 
    data = image_data.get_pixels(0, 0, width, height).map do |pixel|
      hex_color_to_rgba(pixel.to_color(Magick::AllCompliance, false, 8, true), pixel.opacity)
    end
 
    return {
      :width => width,
      :height => height,
      :data => data,
    }
  end

The only tricky part is converting all the pixel data into a format that can be used by JavaScritpt. I decided on rgba format as that is simple to read, implement, and test. The one downside that I can see is that for large images it will be somewhat inefficient to convert each pixel into a large text string, but since I mostly plan on dealing with images < 100×100 it shouldn’t be a big deal. The hex_color_to_rgba helper method takes care of all the dirty work of converting the moderately unwieldy output from RMagick. It turns "#FAFAFA", 65535 into "rgba(250, 250, 25, 1)" a format that browsers respect.

  def hex_color_to_rgba(color, opacity)
    int_opacity = (Magick::QuantumRange - opacity) / Magick::QuantumRange.to_f
 
    match_data = /^#([A-Fa-f0-9]{2})([A-Fa-f0-9]{2})([A-Fa-f0-9]{2})/.match(color)[1..3].map(&amp;:hex)
 
    "rgba(#{match_data.join(',')},#{int_opacity})"
  end

In the end we return a hash containing the the width, height, and pixel data. This can easily be converted to json via the to_json method, or whatever other format tickles your fancy.

The JavaScript code is pretty dumb, it reads the width and height, then iterates over the data and paints each pixel individually. Not super exciting, but simple and effective. That’s that for loading arbitrary images into HTML Canvas, though I hope one day to develop an entirely JS solution.

* I do not understand the reasoning why you are allowed to load an image from an arbitrary url and display that image, but not have access to the pixel data from it. It sounds pretty silly, especially considering that you can load scripts from an arbitrary URL and run them, or load json from an arbitrary URL. I have no idea what “security” that this restriction provides, other than the “security” of having to load images through your server and hinder the user experience. Here’s a related discussion, but I still don’t get it. This is a hole in my personal understanding, so if anyone knows the reasoning behind the restriction I’d love to hear it.

Leave a Reply

Your email address will not be published. Required fields are marked *