27

I want to download Canvas as PNG using fabric.js. While downloading I want to scale the image. So I use multiplier property of toDataURL() function. But I get Failed-Network Error

PS: If I dont give multiplier property,it is downloading but I do want to use multiplier property since I have to scale the image

This is what I am doing:

HTML Code:

<canvas width="400" height="500" id="canvas" ></canvas>
 <a id='downloadPreview' href="javascript:void(0)"> Download Image </a>

JS

document.getElementById("downloadPreview").addEventListener('click', downloadCanvas, false);

var _canvasObject = new fabric.Canvas('canvas');

var downloadCanvas =    function(){
    var link = document.createElement("a");

link.href = _canvasObject.toDataURL({format: 'png', multiplier: 4});
      link.download = "helloWorld.png";
     link.click();

}
Kaiido
  • 123,334
  • 13
  • 219
  • 285
Abhinav
  • 8,028
  • 12
  • 48
  • 89
  • Do you have this error only on chrome? if so this is probably related : http://stackoverflow.com/questions/36918075/is-it-possible-to-programmatically-detect-size-limit-for-data-url/36951356 but for your use case, instead of calling `toBlob` (which I don't think is available in fabricjs), you'll have to convert the dataURI to a blob before setting the anchor's href to the object URL of this blob. (you can check [MDN's polyfill for `toBlob`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob#Polyfill) and pick their way of converting a dataURI to a blob) – Kaiido May 10 '16 at 11:17
  • do i have to just set the href to blob value? – Abhinav May 10 '16 at 11:44
  • no to an object URL created from the blob (`URL.createObjectURL(blob)`). Check the answer on the proposed dupe. – Kaiido May 10 '16 at 11:47
  • Bro...It works, thank you so much,I have been scratching my head since yesterday. Whats with the blob and ObjectUrl? Why does it work whereas dataUri doesnt? – Abhinav May 10 '16 at 11:53
  • well when you do resize you canvas, the outputed dataURL is so big that it goes out of Chrome's limitation for anchors (``) with `download` attribute. I don't know exactly why they do have this limitation though. Also, if it did solve your issue, you can accept the duplicate ;-) – Kaiido May 10 '16 at 12:05
  • @K3N why the removal of [[tag:canvas] ]? – Kaiido May 10 '16 at 14:24
  • @Kaiido fabricjs code is a framework on top of canvas as well as other technologies. However, the code you use for fabricjs is not (always) compatible (and therefor not usable) with canvas context. –  May 10 '16 at 14:27
  • @K3N agreed but the specific problem exposed here is not directly related to fabricjs ( and not even canvas in some extent) but is definitely applicable to the canvas API too. Hence my close vote. – Kaiido May 10 '16 at 14:33
  • @Kaiido that may be, but I don't see how the root cause (solution) changes the context of the *question*. For example, if the problem turned out to be with say a specific version of a apache server, would you add the [apache] tag to the question? I wouldn't, but that's me.. –  May 10 '16 at 14:54
  • @K3N yes, if the problem is finally an apache one, and the solution involves some apache specific configuration, then I would **add** it. here the problem is the max-length for anchors HTML tags with download attribute set by some browsers. The only tag that could apply would be HTML but nor OP nor future readers will be able to know it at first. On the other hand, anyone can face this issue while dealing with canvas, or fabricjs canvas. But I'll stop arguing, And finally add my answer (even if there is already a dupe) which I hope will make things clearer. – Kaiido May 11 '16 at 01:57

5 Answers5

45

The problem you are facing is not directly related to fabricjs, (nor canvas and not even javascript btw), but comes from limitations some browsers (including Chrome) does have on the maximum length for the src attribute of an Anchor Element (<a>) with the donwload attribute.

When this limit is reached, then the only thing you've got is this uncatchable "Network Error" in the console ; the download as failed, but you as the developer can't know about it.

As proposed in this (you-refused-to-mark-as-) duplicate, the solution is either to directly get a Blob when available (for canvas, you may call its toBlob() method, or to first convert your dataURI to a Blob, and then create an object URL from this Blob.

Fabricjs doesn't seem to have a toBlob function implemented yet, so in your exact case, you'll have to do the later.
You can find many scripts to convert dataURI to Blob, one is available in MDN's polyfill to Canvas.toBlob() method.

Then it would look like this :

// edited from https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob#Polyfill
function dataURIToBlob(dataURI, callback) {
  var binStr = atob(dataURI.split(',')[1]),
    len = binStr.length,
    arr = new Uint8Array(len);

  for (var i = 0; i < len; i++) {
    arr[i] = binStr.charCodeAt(i);
  }

  callback(new Blob([arr]));
}

var callback = function(blob) {
    var a = document.createElement('a');
    a.download = fileName;
    a.innerHTML = 'download';
    // the string representation of the object URL will be small enough to workaround the browser's limitations
    a.href = URL.createObjectURL(blob);
    // you must revoke the object URL, 
    //   but since we can't know when the download occured, we have to attach it on the click handler..
    a.onclick = function() {
      // ..and to wait a frame
      requestAnimationFrame(function() {
          URL.revokeObjectURL(a.href);
        });
        a.removeAttribute('href')
      };
    };

dataURIToBlob(yourDataURL, callback);
Kaiido
  • 123,334
  • 13
  • 219
  • 285
  • 1
    Your dataURIToBlob isn't well formed. Where is type or quality defined? And `this` will reference the window most likely. – B T Jun 20 '17 at 01:07
  • 2
    @BT, you are completely right, I don't know how it could have stayed so long with no-one to notice it (I the first). Thanks. But I should probably just remove this dataURIToBlob function now that `toBlob` has made its way in most of the main browsers, it's way faster than converting twice the data. – Kaiido Jun 20 '17 at 01:14
  • Another thing should be noted about this: dataURIToBlob will only work for base64 encoded dataURIs. – B T Jun 20 '17 at 02:07
  • @BT do you know a lot of other ways to get a dataURI from an raster image file (or from an canvas)? Only text files (e.g. markup languages like svg) can be represented in a non b64 encoded way (URL_encoded), binary data can not be represented otherwise. This question is about exporting an fabricjs canvas as PNG (hence raster and binary). – Kaiido Jun 20 '17 at 02:11
  • This question is pointed to by more general questions, so it would have helped me to know that your answer isn't general. I'm sure its fine for canvas stuff! – B T Jun 20 '17 at 08:54
19

I got it. Worked it out as suggested by Kaiido

function dataURLtoBlob(dataurl) {
    var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1],
        bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
    while(n--){
        u8arr[n] = bstr.charCodeAt(n);
    }
    return new Blob([u8arr], {type:mime});
}

NOTE: Got the above function from HTML5 / Javascript - DataURL to Blob & Blob to DataURL

var downloadCanvas =    function(){
    var link = document.createElement("a");
      var imgData = _canvasObject.toDataURL({    format: 'png',
        multiplier: 4});
      var strDataURI = imgData.substr(22, imgData.length);
      var blob = dataURLtoBlob(imgData);
      var objurl = URL.createObjectURL(blob);

      link.download = "helloWorld.png";

      link.href = objurl;

     link.click();
} 
Community
  • 1
  • 1
Abhinav
  • 8,028
  • 12
  • 48
  • 89
  • I have this code working great in Chrome and Edge, but in FF nothing happens. Do you know if this worked for you in FF? – Ron Nov 26 '16 at 07:28
  • Its off today for me so I cant check and tell you now, but I guess it worked in FF, I ll check again anyway ASAP – Abhinav Nov 26 '16 at 07:41
  • 1
    Awesome. Had the same issue and solved it with your solution. Thanks so much! – Steevie Jan 10 '17 at 16:54
  • Thank you, i was receiving *414 (Request-URI Too Large)* to set a base64 image as background, after converting it to blob solved with: `var originalImg = URL.createObjectURL(printImg); fabric.Image.fromURL(originalImg, imgObj => {... ` – Renan Ribeiro Aug 03 '22 at 15:05
1

Since the previous two answers only work for dataURLs that have base64 data, and because this answer has been referenced by more general questions related to "network errors" because of href attributes that are too large, here's the code I'm using:

// must be called in a click handler or some other user action
var download = function(filename, dataUrl) {
    var element = document.createElement('a')

    var dataBlob = dataURLtoBlob(dataUrl)
    element.setAttribute('href', URL.createObjectURL(dataBlob))
    element.setAttribute('download', filename)

    element.style.display = 'none'
    document.body.appendChild(element)

    element.click()

    var clickHandler;
    element.addEventListener('click', clickHandler=function() {
        // ..and to wait a frame
        requestAnimationFrame(function() {
            URL.revokeObjectURL(element.href);
        })

        element.removeAttribute('href')
        element.removeEventListener('click', clickHandler)
    })

    document.body.removeChild(element)
}


// from Abhinav's answer at  https://stackoverflow.com/questions/37135417/download-canvas-as-png-in-fabric-js-giving-network-error/
var dataURLtoBlob = function(dataurl) {
    var parts = dataurl.split(','), mime = parts[0].match(/:(.*?);/)[1]
    if(parts[0].indexOf('base64') !== -1) {
        var bstr = atob(parts[1]), n = bstr.length, u8arr = new Uint8Array(n)
        while(n--){
            u8arr[n] = bstr.charCodeAt(n)
        }

        return new Blob([u8arr], {type:mime})
    } else {
        var raw = decodeURIComponent(parts[1])
        return new Blob([raw], {type: mime})
    }
}

With these functions you can change the code to this:

document.getElementById("downloadPreview").addEventListener('click', function() {
  var dataURL = _canvasObject.toDataURL({format: 'png', multiplier: 4})
  download("hellowWorld.png", dataURL)
})
B T
  • 57,525
  • 34
  • 189
  • 207
  • Shouldn't the linked question be reopened instead ? (at least if it has been closed) This doesn't provide too much helpful information on the current question : "Download Canvas as PNG in fabric.js". There is no other way to get an dataURI representing a PNG file than through base64 encoding its binary data. – Kaiido Jun 20 '17 at 02:23
0

I have multiple canvas on my website, and was struggling with same issue in One of them.

Canvas size 1 : 750px x 500px (Network Issue on Window Chrome, works fine on Chrome Macos)

Canvas Size 2: 540px x 1080px (No download issue, works with multiplier 4)

I simply reduced download size, changing multiplier 4 to 2 and it worked fine.

Then I changed multiplier to 3 and it wasn't again working.

then I tried 2.5 and it worked fine again.

Creating such a big file with 4 Multiplier wasn't my requirement, so I settled with 2.5.

This is the easiest and fastest solution to this problem.

0

In my case I just used this

 var callback = function(blob) {
var a = document.createElement('a');
var saveAs = $('input[name="extensionGrp"]:checked').val();


var link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = 'Image.' + saveAs;
document.body.appendChild(link);
link.click();

};
TAHA SULTAN TEMURI
  • 4,031
  • 2
  • 40
  • 66