6

I'm trying to put image in clipboard when user copies canvas selection:

canvas selection

So I thought the right way would be to convert canvas tu dataURL, dataURL to blob and blob to binary string.

Theoretically it should be possible to skip the blob, but I don't know why.

So this is what I did:

  function copy(event) {
    console.log("copy");
    console.log(event);

    //Get DataTransfer object
    var items = (event.clipboardData || event.originalEvent.clipboardData);
    //Canvas to blob
    var blob = Blob.fromDataURL(_this.editor.selection.getSelectedImage().toDataURL("image/png"));
    //File reader to convert blob to binary string
    var reader = new FileReader();
    //File reader is for some reason asynchronous
    reader.onloadend = function () {
      items.setData(reader.result, "image/png");
    }
    //This starts the conversion
    reader.readAsBinaryString(blob);

    //Prevent default copy operation
    event.preventDefault();
    event.cancelBubble = true;
    return false;
  }
  div.addEventListener('copy', copy);

But when the DataTransfer object is used out of the paste event thread the setData has no longer any chance to take effect.

How can I do the conversion in the same function thread?

Tomáš Zato
  • 50,171
  • 52
  • 268
  • 778
  • parameter order in setData should be reversed -> items.setData("image/png", reader.result) – belzebu Jul 16 '15 at 07:39
  • I have to admit that I'm really struggling to get the canvas into the clipboardData object and paste it let's say in MS Outlook message or elsewhere. Tomas, could you please outline what was your final solution to copy canvas into clipboard. I've tried nearly everything I could either using the setData("image/png"...) or items.filed.add(file) etc.. what should be the value to be passed to setData("image/url", ??) ? Thank you. – belzebu Jul 17 '15 at 02:31
  • @belzebu Unless something changes, I have bad news. I posted question subsequent to this one and it's still not answered because it's simply not possible: http://stackoverflow.com/q/27262879/607407 – Tomáš Zato Jul 21 '15 at 19:18

2 Answers2

8

Here is a hacky-way to get you synchronously from a blob to its bytes. I'm not sure how well this works for any binary data.

function blobToUint8Array(b) {
    var uri = URL.createObjectURL(b),
        xhr = new XMLHttpRequest(),
        i,
        ui8;
    
    xhr.open('GET', uri, false);
    xhr.send();
    
    URL.revokeObjectURL(uri);
    
    ui8 = new Uint8Array(xhr.response.length);
    
    for (i = 0; i < xhr.response.length; ++i) {
        ui8[i] = xhr.response.charCodeAt(i);
    }
    
    return ui8;
}

var b = new Blob(['abc'], {type: 'application/octet-stream'});
blobToUint8Array(b); // [97, 98, 99]

You should consider keeping it async but making it two-stage, though, as you may end up locking up the browser.

Additionally, you can skip Blobs entirely by including a binary-safe Base64 decoder, and you probably don't need to go via Base64 AND Blob, just one of them.

danronmoon
  • 3,814
  • 5
  • 34
  • 56
Paul S.
  • 64,864
  • 9
  • 122
  • 138
  • I think I'll just skip the blob phase. I'm curious though - how come that pseudo-xhr is synchronous? – Tomáš Zato Nov 30 '14 at 02:04
  • 2
    @TomášZato Passed `false` as the third parameter of the `open` method. This is not considered best practice. – Paul S. Nov 30 '14 at 03:42
  • For me, this is a clever solution. But a synchronously XMLHttpRequest just accept DOMString as response. That means, bytes higher then 127 are falsified, whenever the transmitted byte array is not UTF8 compatible. – Martin Wantke Feb 21 '18 at 12:13
  • I'm pretty sure you can just use `xhr.responseType="arraybuffer";` https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/responseType – Codesmith Jun 21 '18 at 02:57
  • @Codesmith Yep, if you only need to target IE10 or newer then that option is also valid – Paul S. Jun 23 '18 at 14:20
1

Blob can be converted to binary string by getting Blob as dataURI and then applying atob. This, however, again [requires FileReader][3]. In my case, it's best to skip the blob alltogether:

//Canvas to binary
var data = atob(
  _this.editor.selection.getSelectedImage()  //Canvas
  .toDataURL("image/png")                    //Base64 URI
  .split(',')[1]                             //Base64 code
);
Tomáš Zato
  • 50,171
  • 52
  • 268
  • 778
  • Omg there are so many duplicates of this question and tons of answers and none mentions the `toDataURL` that is exactly what's needed. – Nakilon Nov 15 '21 at 13:59