17

Thought I had this, but no. The goal: snap a photo (insurance card), save it locally, and retrieve it later.

// Get a reference to the image element
var elephant = document.getElementById("SnapIt_mobileimage_5");

var imgCanvas = document.createElement("canvas"),
imgContext = imgCanvas.getContext("2d");

// Make sure canvas is as big as the picture
imgCanvas.width = elephant.width;
imgCanvas.height = elephant.height;

// Draw image into canvas element

imgContext.drawImage(elephant, 0, 0, elephant.width, elephant.height );
 console.log( 'Did that' );
// Get canvas contents as a data URL
var imgAsDataURL = imgCanvas.toDataURL("data:image/jpg;base64,");

// Save image into localStorage
try {
localStorage.setItem("elephant", imgAsDataURL);
}
catch (e) {
console.log("Storage failed: " + e);
}; 

//Did it work?
var pic = localStorage.getItem("elephant");

console.log( elephant );
console.log( pic );

Each step succeeds, the final output is:

<img id="SnapIt_mobileimage_5" class=" SnapIt_mobileimage_5" name="mobileimage_5" dsid="mobileimage_5" src="files/views/assets/image/IMG_0590.JPG">
 data:image/png;base64,iVBORw0KGgoAAAANSUhEUgA

On a new page, when I ask

var policy_shot = localStorage.getItem( 'elephant' );
console.log( policy_shot );

$('#TestScreen_mobileimage_1').src = policy_shot ;

It logs the binary:

data:image/png;base64,iVBORw0KGgoAAAANSUhEUg ....

But the image doesn't appear.

  1. Is there a simpler approach?
  2. Why is the getItem (binary) preceded by data:image/png; instead of data:image/jpg ?
  3. Is that why it doesn't display, or am I doing something else wrong?
Ed Jones
  • 653
  • 1
  • 4
  • 19
  • 1
    Is the image you draw loaded from the same server or does it resides on another server? –  Sep 26 '13 at 00:36
  • 1
    It was taken with the phone camera. This is for a mobile app. – Ed Jones Sep 26 '13 at 00:49
  • 1
    If you use local referenced paths browser will prevent the image from being loaded. Please see my answer. You can test the data-uri by pasting the complete string into a new tab as url and hit enter. Now you can check if there is an empty image or if you get the actual image. –  Sep 26 '13 at 01:01

2 Answers2

15

1) This is the only way you can convert an image into a string locally (with the excpetion of files loaded with FileReader, see below). Optionally you need to use the server to do it.

2) To get a JPEG image you need to use the argument like this:

var datauri = imgCanvas.toDataURL("image/jpeg", 0.5); //0.5 = optional quality

don't use data:... in the argument as that will be invalid and it will produce the default PNG as you can see in your result. toDataURL() can only take a type, ie. image/png, image/jpeg etc.

3)

External files

If your image was loaded from a different origin (scheme, server ...) or by using local referenced files (file://, /my/path/ etc.) CORS kicks in and will prevent you from creating a data-uri, that is: the image will be empty (and therefor invisible).

For external servers you can request permission to use the image from a cross-origin by supplying the crossOrigin property:

<img src="http://extrernalserver/...." crossOrigin="anonymous" />

or by code (img.crossOrigin = 'anonymous';) before setting the src.

It's however up to the server to allow or deny the request.

If it still doesn't work you will need to load your image through a proxy (f.ex. a page on your server that can load and the image externally and serve it from your own server).

Or if you have access to the server's configuration you can add special access allow headers (Access-Control-Allow-Origin: *, see link below for more details).

CORS (Cross-Origin Resource Sharing) is a security mechanism and can't be worked around other than these ways.

Local files

For local files you will need to use FileReader - this can turn out to be an advantage as FileReader comes with a handy method: readAsDataURL(). This allow you to "upload" the image file directly as a data-uri without going by canvas.

See examples here:
https://developer.mozilla.org/en-US/docs/Web/API/FileReader

Unfortunately you can't just pick the files from code - you will need to provide an input element or a drop zone so the user can pick the files (s)he want to store.

Conclusion

If all these steps are fulfilled to the degree that you actually do get an image the problem possibly is in the other end, the string being truncated being too long to store.

You can verify by checking the length before and after storing the string to see if it has been cut:

console.log(imgAsDataURL.length);
... set / get ...
console.log(pic.length);

Other possibilities:

  • The image element is not properly defined.
  • A bug in the browser
  • A bug in the framework

(I think I covered most of the typical pitfalls?)

Update (missed one, sort of.. ;-p)

OP found a specific in the framework which I'll include here for future reference:

In the end, the issue was with $('#TestScreen_mobileimage_1').src = policy_shot ; I'm using Appery.io and they don't support .src.

It's $('#TestScreen_mobileimage_1').attr("src", policy_shot ) ;

A final note: localStorage is very limited in regards to storing images. Typical storage space is 2.5 - 5 mb. Each char stored takes 2 bytes and storing a data-uri encoded as base-64 is 33% larger than the original - so space will be scarce. Look into Indexed DB, Web SQL or File API for good alternatives.

  • Thanks! I changed the toDataUrl argument, and it yielded: data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODd Then I added the cross-origin, but still no display. $('#TestScreen_mobileimage_1').crossOrigin = 'anonymous'; $('#TestScreen_mobileimage_1').src = policy_shot ; – Ed Jones Sep 26 '13 at 01:17
  • Yes, pasting the string into a new tab produced an image. ;-) – Ed Jones Sep 26 '13 at 01:20
  • @EdJones I updated my answer with a possible work-around using the FileReader API. This will enable you to "upload" files directly as data-uris. The drawback is that the user need to specify the file by selecting them as you need to provide an input element or drop-zone for this to work. –  Sep 26 '13 at 01:21
  • I was hoping to make it pretty simple for user; two buttons: "Take Picture" and "Save Your Pic". – Ed Jones Sep 26 '13 at 01:28
  • Lengths were both 20203. ? – Ed Jones Sep 26 '13 at 01:35
  • @EdJones Then we are left with only how the image is set. Is this element the actual element and it is defined? `TestScreen_mobileimage_1` –  Sep 26 '13 at 01:42
  • In the end, the issue was with $('#TestScreen_mobileimage_1').src = policy_shot ; I'm using Appery.io and they don't support .src. It's $('#TestScreen_mobileimage_1').attr("src", policy_shot ) ; – Ed Jones Sep 26 '13 at 15:13
  • Ken, if you want to add my solution to your answer, I will mark it as the accepted solution. You've given so much helpful info here, that others will surely find it useful! Thanks so much. – Ed Jones Sep 26 '13 at 16:53
  • @EdJones No problem, I added your solution for future references. –  Sep 26 '13 at 18:08
2

Here the complete solution using File Api


    <html>
    <body>
    <input type="file" id="image-input" />
    <img id="image-container" />
    <script type="text/javascript">
    (function(){
          /** @type {Node} */
      var imgInput = document.getElementById( "image-input" ),
          /** @type {Node} */
          imgContainer = document.getElementById( "image-container" ),
          /** Restore image src from local storage */
          updateUi = function() {
            imgContainer.src = window.localStorage.getItem( "image-base64" );
          },
          /** Register event listeners */
          bindUi = function(){
            imgInput.addEventListener( "change", function(){
              if ( this.files.length ) {
                var reader = new FileReader();
                reader.onload = function( e ){
                  window.localStorage.setItem( "image-base64", e.target.result );
                  updateUi();
                };
                reader.readAsDataURL( this.files[ 0 ] );
              }
            }, false );
          };

    updateUi();
    bindUi();
    }());
    </script>
    </body>
    </html>

Dmitry Sheiko
  • 2,130
  • 1
  • 25
  • 28