简体   繁体   中英

Why does canvas.toDataURL() not produce the same base64 as in Ruby for an image?

I'm trying to produce the same base64 data for an image file in both JavaScript and in Ruby. Unfortunately both are outputting two very different values.

In Ruby I do this:

Base64.encode64(File.binread('test.png'));

And then in JavaScript:

var image = new Image();
image.src = 'http://localhost:8000/test.png';

$(image).load(function() {
    var canvas, context, base64ImageData;

    canvas = document.createElement('canvas');
    context = canvas.getContext('2d');
    canvas.width = this.width;
    canvas.height = this.height;
    context.drawImage(this, 0, 0);
    imageData = canvas.toDataURL('image/png').replace(/data:image\/[a-z]+;base64,/, '');

    console.log(imageData);
});

Any idea why these outputs are different?

When you load the image in Ruby the binary file without any modifications will be encoded directly to base-64.

When you load an image in the browser it will apply some processing to the image before you will be able to use it with canvas:

  • ICC profile will be applied (if the image file contains that)
  • Gamma correction (where supported)

By the time you draw the image to canvas, the bitmap values has already been changed and won't necessarily be identical to the bitmap that was encoded before loading it as image (if you have an alpha channel in the file this may affect the color values when drawn to canvas - canvas is a little peculiar at this..).

As the color values are changed the resulting string from canvas will naturally also be different, before you even get to the stage of re-encoding the bitmap (as PNG is loss-less the encoding/compressing should be fairly identical, but factors may exist depending on the browser implementation that will influence that as well. to test, save out a black unprocessed canvas as PNG and compare with a similar image from your application - all values should be 0 incl. alpha and at the same size of course).

The only way to avoid this is to deal with the binary data directly. This is of course a bit overkill (in general at least) and a relative slow process in a browser.

A possible solution that works in some cases, is to remove any ICC profile from the image file. To save an image from Photoshop without ICC choose "Save for web.." in the file menu.

The browser is re-encoding the image as you save the canvas.

It does not generate an identical encoding to the file you rendered.

So I actually ended up solving this...

Fortunately I am using imgcache.js to cache images in the local filesystem using the FileSystem API. My solution is to use this API (and imgcache.js makes it easy) to get the base64 data from the actual cached copy of the file. The code looks like this:

var imageUrl = 'http://localhost:8000/test.png';

ImgCache.init(function() {
    ImgCache.cacheFile(imageUrl, function() {
        ImgCache.getCachedFile(imageUrl, function(url, fileEntry) {
            fileEntry.file(function(file) {
                var reader = new FileReader();
                reader.onloadend = function(e) {
                    console.log($.md5(this.result.replace(/data:image\/[a-z]+;base64,/, '')));
                };
                reader.readAsDataURL(file);
            });
        });
    });
});

Also, and very importantly, I had to remove line breaks from the base64 in Ruby:

Base64.encode64(File.binread('test.png')).gsub("\n", '');

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM