0

So Im trying to build an app using electron, the program is pretty basic, the user should upload a couple of pictures, they press a button, I draw them into a <canvas> element and they should be able to download it, the problem that Im having is that when I try to make a downloadable button, a security error is thrown, I did some research and find out that it's a feature of HTML5, the think is that i can't add the crossOrigin = 'Anonymous' because the user is going to load the picture from its own computer, so my question is...

a)- is there a way to "cheat the system" and be able to download the content of the canvas?,

b)- if (a) is not posible...is there an alternative that could work similary to a canvas element?

I'm already using FileReader() to get the picture the user selected, and i'm already drawing that into the canvas, and it looks great, the problem that I have is that i can't download the content that I created for that security error. the .toDataURL can't be called because that's the one that throws the error.

The code to read the pictures from the inputs:

 function handleBox1(evt) {
    var files = evt.target.files; // FileList object

    for (var i = 0, f; f = files[i]; i++) {
      // Only process image files.
      if (!f.type.match('image.*')) {
        continue;
      }
      var reader = new FileReader();

      reader.onload = (function(theFile) {
        return function(e) {

            $('#imgBox1').attr("src",e.target.result);              

        };
      })(f);
      // Read in the image file as a data URL.
      reader.readAsDataURL(f);
    }
}

this is a simple function to see if I could get the Url from the canvas:

descargar = function(){
  var w=window.open('about:blank','image from canvas');
  w.document.write("<img src='"+ totem.toDataURL("image/png")+ "' alt='from canvas'/>");
}
  • can you post your code and errors? An yes cross-origin is not related to the user uploads at all. It will be caused due to third party websites or links. – Stack learner Apr 27 '16 at 04:59
  • "...cheat the system": No, Satisfy the security requirements: Yes. If the user has a modern browser you can use FileReader to get local images without triggering security restrictions. The key is that the user must explicitly select the desired file and thereby explicitly accept responsibility for uploading a cross-domain image. – markE Apr 27 '16 at 05:53
  • @markE The linked Question does not appear draw multiple images onto single `` element? – guest271314 Apr 27 '16 at 06:10
  • @markE Yes, `FileReader()` is important portion of returning expected result. If not mistaken, no `` element appears at `html` at linked Question? Not necessarily "easily" done to add and substract image heights before drawing to `canvas`; was more difficult than initially anticipated. In particular, using `canvas.toDataURL()` where one of the images had substantial `size` could freeze browser as `data URI` was written to `a.href` ; using `canvas.toBlob()` polyfill did not cause any frezing of browser, even though method actually calls `.toDataURL()` – guest271314 Apr 27 '16 at 06:35
  • _"Where in the question did Gabriel Tortomano say their app needed to "sort image with greatest width, add that images width to next image width, then subtract last image width before drawing last image"?"_ When attempting to resolve Question, if the images have different widths, if the image having the greatest `width` is not used to set `canvas` `width` the canvas could render with clipped images. While `FileReader()` is used in linked Question, linked Question does not ask to merge two images into same `canvas`. Attempted to address exact present Question; did not even try with three images – guest271314 Apr 27 '16 at 06:51
  • ok...let me clarify something... i'm already using FileReader() to get the picture the user selected, and i'm already drawing that into the canvas, and it looks great, the problem that I have is that i can't download the content that I created for that security error. the .toDataURL can't be called because that's the one that throws the error. But in this application there's not going to be any remote server pictures related, so..maybe a diferent aproach?, the pictures are always going to be selected by the user from it's own computer. – Gabriel Tortomano Apr 27 '16 at 13:39
  • @GabrielTortomano Where at `js` at updated Question is content attempted to be downloaded? What is `totem` ? Have you tried `js` at post? – guest271314 Apr 27 '16 at 14:00
  • no in that code i wasn't attempting to download it was a test to see if i could get the sorce of the canvas, and as you can see the line totem.toDataURL("image/png") breaks imediatlly. totem is the name of my canvas and yeah, I tried that code and didn't work , there has to be a way to download it without trigering any security alert!. – Gabriel Tortomano Apr 27 '16 at 19:05
  • the problem is being cause by 2 or 3 pictures that i have to load without the user having to select them, is there a way to do that without triggering anything?, i tested it without the those pictures and it worked...but they need those pictures to load by touching or not the corresponding button... – Gabriel Tortomano Apr 27 '16 at 19:46

1 Answers1

1

the user should upload a couple of pictures, I draw them into a element and they should be able to download it

You can utilize for loop containing an IIFE to process files object from input type="file" element, new Image, onload event of <img> element, URL.createObjectURL(), Array.prototype.sort() to determine greatest width of the two uploaded images; Promise.all(), Array.prototype.forEach() to process img elements; canvas.toBlob() polyfill to create an objectURL of of image; <a> element having download attribute set to objectURL representing single image created from two images; revoke objectURL at click of a element after download of image completes.

// if (!HTMLCanvasElement.prototype.toBlob) {
Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', {
  value: function(callback, type, quality) {

    var binStr = atob(this.toDataURL(type, quality).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], {
      type: type || 'image/png'
    }));
  }
});
//}

var input = document.querySelector("input");
var canvas = document.querySelector("canvas");
var a = document.querySelector("a");
var ctx = canvas.getContext("2d");
var urls = [];

input.onchange = function(e) {
  var images = [];

  for (var i = 0; i < e.target.files.length; i++) {
    (function(j) {
      // var reader = new FileReader();
      // reader.onload = function(event) {
        var img = new Image;
        img.onload = function() {
          images.push(Promise.resolve(this));
          if (images.length === e.target.files.length) {
            Promise.all(images)
              .then(function(data) {
                data.sort(function(a, b) {
                  return a.naturalHeight < b.naturalHeight;
                })
                data.forEach(function(el, index) {
                  if (index === 0) {
                    canvas.width = el.naturalWidth;
                    canvas.height = el.naturalHeight 
                                    + data[index + 1].naturalHeight;
                    ctx.drawImage(el, 0, 0);
                  } else {
                    ctx.drawImage(el, 0, canvas.height - el.naturalHeight);
                  }

                });
                canvas.toBlob(function(blob) {
                  var url = URL.createObjectURL(blob);
                  console.log(blob);
                  a.href = url;
                  a.style.display = "block";
                  urls.push(url);
                });
              })
          }
        }
        img.src = URL.createObjectURL(e.target.files[j]);
      // }
      // reader.readAsDataURL(e.target.files[j]);
    }(i))
  }
}

a.onclick = function() {
  setTimeout(function() {
    urls.forEach(function(obj) {
      URL.revokeObjectURL(obj)
    })
  })
}
<a download="" href="" style="display:none">download</a>
<input type="file" accept="image/*" multiple />
<br>
<canvas></canvas>
guest271314
  • 1
  • 15
  • 104
  • 177
  • 1
    Your code fails in IE and Edge because the `download` attribute is not supported in those browsers. But an upvote for using FileReader to satisfy cross-origin restrictions. – markE Apr 27 '16 at 06:51
  • @markE See http://stackoverflow.com/questions/38711803/how-to-download-a-file-without-using-a-element-with-download-attribute-or-a-se – guest271314 Aug 09 '16 at 20:28
  • Actually why the over complication of FileReader here ? You are already relying on the existence of URL, so use it all the way, it will remove one level of nesting. – Kaiido May 15 '17 at 04:55
  • @Kaiido Do you mean just use `.toBlob()`? – guest271314 May 15 '17 at 05:02
  • 1
    No I mean `(function(j) { var img =new Image(); img.onload = ... ; img.src = URL.createObjectURL(e.target.files[j])})(i);` i.e don't use a FileReader here, it's useless, overcomplicates your code and eats unnecessary memory (`createObjectURL` called with user inputted files only create a direct pointer to the file on the user's disk). – Kaiido May 15 '17 at 05:17