javascriptfilereader

Specify the type returned from FileReader readAsDataURL() as PNG and not TIFF?


I'm getting an image that has been pasted into the content editable div and during paste the types in listed in the Clipboard include "image/png" and "image/tiff".

When I get that image using a FileReader and readAsDataURL the base64 image string it returns is a TIFF, "data:image/tiff,".

Is there a way to specify to the FileReader to return a PNG, "data:image/png"?

Related question here.


Solution

  • You don't.
    FileReader.readAsDataURL() only converts the binary data held in the passed Blob to a base64 string, it doesn't do any other conversion.
    It will only prepend the corresponding MIME header to whatever it thinks this data is (this check is first done against the Blob's type, and then probably against magic-numbers), but you can't force it to convert the data to something else.

    const fr = new FileReader();
    const fakeGif = new Blob(['foo'], {type:'image/gif'});
    fr.onload = onload;
    fr.readAsDataURL(fakeGif);
    
    function onload(){
      console.log(fr.result); // with image/gif MIME
      console.log(atob(fr.result.split(',')[1])) // still just "foo"...
    }


    Now to your case, since Safari still doesn't support the DataTransferItem interface, you are out of luck.

    Even though Grab utility does save a png File in the clipboard, Safari chose to display the tiff raw data.

    You can check this assertion in your dev's Network panel, where you will see that the image is fetched as image/tiff.

    Browsers that do support DataTransferItem interface can retrieve the png file attached in the clipboard though, so let's hope Safari implement this interface soon enough.

    So one way to convert this TIFF image to a png one, is to go through a canvas element:

    // an Array to hold pasted Files
    var files = [];
    
    // We start by checking if the browser supports the 
    // DataTransferItem interface. If not, we need to create a 
    // contenteditable element that catches all pasted data 
    if (!window.DataTransferItem) {
      var pasteCatcher = document.createElement("div");
      pasteCatcher.setAttribute("contenteditable", "");
    
      // We can hide the element and append it to the body,
      pasteCatcher.style.opacity = 0.5;
      document.body.appendChild(pasteCatcher);
    
      // as long as we make sure it is always in focus
      pasteCatcher.focus();
      document.addEventListener("click", function() {
        pasteCatcher.focus();
      });
    }
    // Add the paste event listener
    window.addEventListener("paste", pasteHandler);
    
    /* Handle paste events */
    
    function pasteHandler(e) {
      // We need to check if event.clipboardData is supported (Chrome)
      if (e.clipboardData) {
        // Get the items from the clipboard
        var items = e.clipboardData.items || e.clipboardData.files;
        itemcount = items.length;
        if (itemcount) {
          // Loop through all items, looking for any kind of image
          for (var i = 0; i < items.length; i++) {
            getItem(items[i]);
          }
        } else {
          // This is a cheap trick to make sure we read the data
          // AFTER it has been inserted.
          setTimeout(checkInput, 1);
        }
        // If we can't handle clipboard data directly (Firefox), 
        // we need to read what was pasted from the contenteditable element
      } else {
    
        console.log("checking input");
    
        // This is a cheap trick to make sure we read the data
        // AFTER it has been inserted.
        setTimeout(checkInput, 1);
      }
    }
    /* For browsers that support DataTransferItem interface */
    function getItem(item)  {
      if (item.type.indexOf("image") !== -1) {
        // We need to represent the image as a file,
        var blob = item instanceof Blob ? item : item.getAsFile();
        // save the File for later use if needed
        files.push(blob);
        // and use a URL or webkitURL (whichever is available to the browser)
        // to create a temporary URL to the object
        var URLObj = window.URL || window.webkitURL;
        var source = URLObj.createObjectURL(blob);
        // The URL can then be used as the source of an image
        createImage(source);
      }
    }
    /* For older browsers */
    /* Parse the input in the paste catcher element */
    function checkInput() {
    
      // Store the pasted content in a variable
      var child = pasteCatcher.childNodes[0];
    
      if (child) {
        // If the user pastes an image, the src attribute
        // will represent the image as a base64 encoded string.
        if (child.tagName === "IMG") {
          getPngFromIMG(child, function(blob) {
            // Clear the inner html to make sure we're always
            // getting the latest inserted content
            pasteCatcher.innerHTML = "";
            // save the png blob in our list
            files.push(blob);
            createImage(URL.createObjectURL(blob));
          });
        }
      }
    }
    
    function getPngFromIMG(img, callback) {
      if (img.naturalWidth) // if already loaded
        draw();
      else
        img.onload = draw;
    
      function draw() {
        var canvas = document.createElement('canvas');
        canvas.width = img.naturalWidth;
        canvas.height = img.naturalHeight;
        canvas.getContext('2d').drawImage(img, 0, 0);
        canvas.toBlob(callback);
      }
    }
    /* Creates a new image from a given source */
    function createImage(source) {
      var pastedImage = new Image();
      pastedImage.onload = function(e) {
        loadImage.src = e.target.src;
      }
      pastedImage.src = source;
    }
    
    btn.onclick = function() {
      console.log(files);
    }
    <textarea id="pasteArea" placeholder="Paste Image Here"></textarea>
    <img id="loadImage" />
    <button id="btn">do something with pasted files</button>