Advanced topics

Access to Canvas Pixels

It is a great idea to use images from our CDN on HTML canvas. Unfortunately browsers restrict access to canvas if an image was downloaded from untrusted source. By default only images from the same origin are treated as trusted. If you’ll draw the image from other origins, the canvas will be marked as «dirty» and no pixel read will be possible from the canvas.

Calling getImageData() or toDataURL() will lead to SecurityError:

canvas.toDataURL();
// Uncaught DOMException: Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported.
canvas.getContext('2d').getImageData(0, 0, 100, 100);
// Uncaught DOMException: Failed to execute 'getImageData' on 'CanvasRenderingContext2D': The canvas has been tainted by cross-origin data.

One of solutions is using crossOrigin image property to ask browser to request the image in a safe way.

$(function() {
  var imageUrl = 'https://ucarecdn.com/c5b7dd84-c0e2-48ff-babc-d23939f2c6b4/-/preview/240x240/-/quality/best/';
   
  var canvas = document.getElementById('s1-canvas');
  var img = new Image();
   
  img.crossOrigin = "Anonymous";
   
  img.onload = function() {
    canvas.width = this.width;
    canvas.height = this.height;
    canvas.getContext('2d').drawImage(this, 0, 0);
   
    canvas.toDataURL();
    canvas.getContext('2d').getImageData(0, 0, 100, 100);
  };
  img.src = imageUrl;
});
  
<canvas id="s1-canvas"></canvas>
  

Unfortunately this property is available only in Chrome and Firefox. But it is very easy to make such request manually with the help of XMLHttpRequest.

$(function() 
  function requestImage(imageUrl, callback) {
    var req = new XMLHttpRequest();
    req.onload = function() {
      var img = new Image();
      img.onload = function() {
        URL.revokeObjectURL(this.src);
        callback(img);
      };
      img.src = URL.createObjectURL(req.response);
    };
    req.open("get", imageUrl, true);
    req.responseType = 'blob';
    req.send();
  }
 
  function getAverageColor(canvas) {
    var ctx = canvas.getContext('2d');
    var data = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
    var r = 0, g = 0, b = 0, a = 0;
    for (i = 0; i < data.length; i += 4) {
      r += data[i+0] * data[i+3];
      g += data[i+1] * data[i+3];
      b += data[i+2] * data[i+3];
      a += data[i+3];
    }
    return [r / a, g / a, b / a];
  }
 
  var canvas = document.getElementById('s2-canvas');
  var URL = window.URL || window.webkitURL;
 
  $('#s2-average-color').on('click', function() {
    var imageUrl = $('#s2-source').val();
    requestImage(imageUrl, function(img) {
      canvas.width = img.width;
      canvas.height = img.height;
      canvas.getContext('2d').drawImage(img, 0, 0);
 
      var colors = $.map(getAverageColor(canvas), function(x) {
        return Math.round(x).toString(16);
      });
      alert("The average color is #" + colors.join(''));
    })
  });
});
  
<input type="text" size="80" id="s2-source" value="https://ucarecdn.com/c5b7dd84-c0e2-48ff-babc-d23939f2c6b4/-/preview/250x250/"><br>
<button id="s2-average-color">Get average color</button><br>
<canvas id="s2-canvas"></canvas>
  


This works well in Chrome 19+, Firefox 6+, IE 10+, Safari 6+ and Opera 15+.

Uploaded Files Browser

Often you may want to allow user to choose previously uploaded files again. Uploadcare doesn’t store relations between users and files and also doesn’t allow the widget to list all files in the project for security reasons.

There are two ways to handle the files-to-user relations:

  1. You can store this information on your side and load list of user’s files using AJAX requests to your server. This is relatively complex way because you’ll need to implement some storage and endpoint which will return file list from this storage.
  2. List of previously uploaded files can be stored locally on user’s computer in browser’s local storage. This way user will not be able to choose files uploaded from other devices, but this doesn’t require any support on server side.

Following example shows how to add local storage history tab with following features:

  • Lists are separated by public key. If you are using widgets with two or more public keys, only files from current project will be shown.
  • List of last 50 files are shown.
  • If widget has images-only flag, only image files will be shown.
  • User can remove files from history.
uploadcare.registerTab('localhistory', function(container, button, dialogApi, settings, name) {
 
  function loadItems() {
    // Format: UUID isImage size filename
    var items = localStorage.getItem(localKey);
    if ( ! items) {
      return [];
    }
    items = items.split("\n");
    for (var i = 0; i < items.length; i++) {
      var v = items[i].split(' ');
      v.splice(3, v.length, v.slice(3, v.length).join(' '));
      v[1] = !!parseInt(v[1]);
      v[2] = parseInt(v[2]);
      items[i] = v;
    }
    return items;
  }
 
  function saveItems(items) {
    items = items.slice(0, 100);
    for (var i = 0; i < items.length; i++) {
      var v = items[i].slice();
      v[1] = 0 + v[1]; // bool → int
      items[i] = v.join(" ");
    }
    localStorage.setItem(localKey, items.join("\n"));
  }
 
  function addItem(fileInfo) {
    var items = loadItems();
    items = $.grep(items, function(v) {
      return v[0] !== fileInfo.uuid;
    });
    var item = [fileInfo.uuid, fileInfo.isImage, fileInfo.size, fileInfo.name];
    items.unshift(item);
    saveItems(items);
  }
 
  function removeItem(item) {
    var items = loadItems();
    items = $.grep(items, function(v) {
      return v[0] !== item[0];
    });
    saveItems(items);
  }
 
  function makeItem(data) {
    var html = $('<div class="uploadcare-file-item"></div>').append([
      $('<div class="uploadcare-file-item__preview"></div>').append(
        data[1]
        ?
        $('<img/>', {
          src: settings.cdnBase + "/" + data[0] + "/-/quality/lightest/" + (
            settings.imagesOnly
              ? "-/preview/340x340/"
              : "-/scale_crop/110x110/center/"
          )
        })
        :
        $('<div class="uploadcare-file-icon"></div>')
      ),
      $('<div class="uploadcare-file-item__name"></div>').text(data[3]),
      $('<div class="uploadcare-file-item__size"></div>').text(uc.utils.readableFileSize(data[2])),
      $('<div class="uploadcare-file-item__remove"></div>').append(
        $('<div class="uploadcare-remove"></div>')
          .on('click', function() {
            html.remove();
            removeItem(data);
          })
      )
    ]).on('click', function(e) {
        dialogApi.addFiles('uploaded', [data[0]]);
        e.preventDefault();
      });
    return html;
  }
 
  function populate(container) {
    var items = loadItems();
    for (var i = items.length - 1; i >= 0; i--) {
      var item = items[i];
      if (settings.imagesOnly && ! item[1]) {
        continue;
      }
      container.prepend(makeItem(item));
    }
  }
 
  var localStorage = window.localStorage;
  var localKey = "uploadcare_" + settings.publicKey;
  var $ = uploadcare.jQuery;
  var uc;
  if ( ! localStorage) {
    button.hide();
    return;
  }
  uploadcare.plugin(function(uploadcare) {
    uc = uploadcare;
  });
  dialogApi.fileColl.onAdd.add(function(file) {
    file.done(function(fileInfo) {
      addItem(fileInfo);
    })
  });
  populate($('<div class="uploadcare-file-list"></div>')
    .toggleClass('uploadcare-file-list_table', !settings.imagesOnly)
    .toggleClass('uploadcare-file-list_tiles', settings.imagesOnly)
    .appendTo($('<div class="uploadcare-dialog-padding">\
      <div class="uploadcare-dialog-title">Previous uploaded files</div>\
    </div>'
)
    .appendTo(container)));
});
 
UPLOADCARE_LOCALE_TRANSLATIONS = {
  dialog: {tabs: {names: {
    localhistory: 'History'
  }}}
};
.uploadcare-dialog-tab-localhistory:before {
  background-image: url(data:image/png;base64,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) !important;
  background-position: 0 0;
}
.uploadcare-dialog-tab_current.uploadcare-dialog-tab-localhistory:before {
  background-position: 0 50px;
}
.uploadcare-dialog-tabs-panel-localhistory .uploadcare-file-item {
  cursor: pointer;
}
<p>
  Images only with crop:
  <input type="hidden" role="uploadcare-uploader" data-tabs="file camera dropbox localhistory" data-images-only="" data-crop="">
</p>
<p>
  Multiple widget:
  <input type="hidden" role="uploadcare-uploader" data-tabs="file camera dropbox localhistory" data-multiple="">
</p>
  

Images only with crop:

Multiple widget:

Questions?

We're always happy to help with code or other questions you might have! Search our site for more information or send us an email!