Read binary data from an image and save it with JavaScript

Benny Neugebauer picture Benny Neugebauer · Feb 1, 2014 · Viewed 16.4k times · Source

I want to read the binary data of an image and then save it again to my local disk with JavaScript.

I wrote a small demo that shows this use-case. To read the file I use readAsBinaryString from the File Reader API (HTML5) to get the binary data.

I write the binary string into a textfield from which I then read the data again to write it to a file. If I save the file my images (I tested several JPEGs) are broken so you cannot see anything useful.

Can it be that "readAsBinaryString" makes a conversion which makes the binary data incorrect?

To have a look at my demo application I made a fiddle. The main part starts here:

reader.readAsBinaryString(file);

Answer

dbermudez picture dbermudez · Feb 1, 2014

I have tested this code on your fiddle and it has worked like a charm:

  var contentType = '';

  window.saveImage = function() {
    var textToWrite = document.getElementById("inputTextToSave").value;

    var splittedTextToWrite = textToWrite.split(",");

    var u16 = new Uint16Array(splittedTextToWrite.length);

      for(i=0; i<splittedTextToWrite.length; i++){
          u16[i]=splittedTextToWrite[i];
      }
    var textFileAsBlob = new Blob([u16], {type: contentType});          

    var fileNameToSaveAs = document.getElementById("inputFileNameToSaveAs").value;

    var downloadLink = document.createElement("a");
    downloadLink.download = fileNameToSaveAs;
    downloadLink.innerHTML = "Download File";
    if (window.webkitURL !== null) {
      // Chrome allows the link to be clicked
      // without actually adding it to the DOM.
      downloadLink.href = window.webkitURL.createObjectURL(textFileAsBlob);
    }
    else {
      // Firefox requires the link to be added to the DOM
      // before it can be clicked.
      downloadLink.href = window.URL.createObjectURL(textFileAsBlob);
      downloadLink.onclick = destroyClickedElement;
      downloadLink.style.display = "none";
      document.body.appendChild(downloadLink);
    }

    downloadLink.click();
  }

  function destroyClickedElement(event) {
    document.body.removeChild(event.target);
  }

  window.loadImage = function() {
    var file = document.getElementById("fileToLoad").files[0];

    var reader = new FileReader();
    reader.onload = function(event) {
      var data = event.target.result;

      var data16 = new Uint16Array(data);
      var text = [];
        for(i = 0; i<data16.length; i++){
            text.push(data16[i]);
        }

      document.getElementById("inputTextToSave").value = text;

      var imagePreview = document.getElementById("imagePreview");
      imagePreview.innerHTML = '';

      var dataURLReader = new FileReader();
      dataURLReader.onload = function(event) {
        // Parse image properties
        var dataURL = event.target.result;
        contentType = dataURL.split(",")[0].split(":")[1].split(";")[0];

        var image = new Image();
        image.src = dataURL;
        image.onload = function() {
          console.log("Image type: " + contentType);
          console.log("Image width: " + this.width);
          console.log("Image height: " + this.height);
          imagePreview.appendChild(this);
        };
      };
      dataURLReader.readAsDataURL(file);


    };
    //reader.readAsBinaryString(file);
    reader.readAsArrayBuffer(file);
  }

I'm not an expert on the new HTML5 APIs, but I will try to explain a bit what I have done.

1) I have saved a PNG to disk. (photo.png)

2) If you have Linux, you can see the contents of the file in hexadecimal format with this command od -cx photo.png. If not you need some hexadecimal editor.

The first lines of photo.png in hexadecimal show something like this:

       211   P   N   G  \r  \n 032  \n  \0  \0  \0  \r   I   H   D   R

       5089    474e    0a0d    0a1a    0000    0d00    4849    5244

Each pair of numbers in the second line represent the hexadecimal codification of the symbol above: 5089 is the codification of 211 P, 50 is the hex value for P and 89 for 211 (little endian codification, the first two bytes encodes the second symbol, the last two encodes the first symbol)

3) Instead of read the file as a binaryString, I read it as an ArrayBuffer (it doesn't make encoding conversions).

4) When the file is read, I transform the ArrayBuffer into a Uint16Array, and store each value in an array to show its decimal value on your text area. It shows the values as a list of decimal numbers, comma separated. The first decimal number will in this case will be 20617, which is the decimal equivalent for the 5089 hexadecimal.

5) Prior to saving the file, some simple code splits the decimal values and add them to a new Uint16Array.

It worked for me... It's a bit confusing and probably someone will get a better & more efficient approach using the APIs in another way.