'use strict'; var utils = require('./utils'); /* jshint -W079 */ var Blob = require('blob'); var Promise = utils.Promise; // // PRIVATE // // From http://stackoverflow.com/questions/14967647/ (continues on next line) // encode-decode-image-with-base64-breaks-image (2013-04-21) function binaryStringToArrayBuffer(binary) { var length = binary.length; var buf = new ArrayBuffer(length); var arr = new Uint8Array(buf); var i = -1; while (++i < length) { arr[i] = binary.charCodeAt(i); } return buf; } // Can't find original post, but this is close // http://stackoverflow.com/questions/6965107/ (continues on next line) // converting-between-strings-and-arraybuffers function arrayBufferToBinaryString(buffer) { var binary = ''; var bytes = new Uint8Array(buffer); var length = bytes.byteLength; var i = -1; while (++i < length) { binary += String.fromCharCode(bytes[i]); } return binary; } // doesn't download the image more than once, because // browsers aren't dumb. uses the cache function loadImage(src, crossOrigin) { return new Promise(function (resolve, reject) { var img = new Image(); if (crossOrigin) { img.crossOrigin = crossOrigin; } img.onload = function () { resolve(img); }; img.onerror = reject; img.src = src; }); } function imgToCanvas(img) { var canvas = document.createElement('canvas'); canvas.width = img.width; canvas.height = img.height; // copy the image contents to the canvas var context = canvas.getContext('2d'); context.drawImage( img, 0, 0, img.width, img.height, 0, 0, img.width, img.height); return canvas; } // // PUBLIC // /** * Shim for * [new Blob()]{@link https://developer.mozilla.org/en-US/docs/Web/API/Blob.Blob} * to support * [older browsers that use the deprecated BlobBuilder API]{@link http://caniuse.com/blob}. * * @param {Array} parts - content of the Blob * @param {Object} options - usually just {type: myContentType} * @returns {Blob} */ function createBlob(parts, options) { options = options || {}; if (typeof options === 'string') { options = {type: options}; // do you a solid here } return new Blob(parts, options); } /** * Shim for * [URL.createObjectURL()]{@link https://developer.mozilla.org/en-US/docs/Web/API/URL.createObjectURL} * to support browsers that only have the prefixed * webkitURL (e.g. Android <4.4). * @param {Blob} blob * @returns {string} url */ function createObjectURL(blob) { return (window.URL || window.webkitURL).createObjectURL(blob); } /** * Shim for * [URL.revokeObjectURL()]{@link https://developer.mozilla.org/en-US/docs/Web/API/URL.revokeObjectURL} * to support browsers that only have the prefixed * webkitURL (e.g. Android <4.4). * @param {string} url */ function revokeObjectURL(url) { return (window.URL || window.webkitURL).revokeObjectURL(url); } /** * Convert a Blob to a binary string. Returns a Promise. * * @param {Blob} blob * @returns {Promise} Promise that resolves with the binary string */ function blobToBinaryString(blob) { return new Promise(function (resolve, reject) { var reader = new FileReader(); var hasBinaryString = typeof reader.readAsBinaryString === 'function'; reader.onloadend = function (e) { var result = e.target.result || ''; if (hasBinaryString) { return resolve(result); } resolve(arrayBufferToBinaryString(result)); }; reader.onerror = reject; if (hasBinaryString) { reader.readAsBinaryString(blob); } else { reader.readAsArrayBuffer(blob); } }); } /** * Convert a base64-encoded string to a Blob. Returns a Promise. * @param {string} base64 * @param {string|undefined} type - the content type (optional) * @returns {Promise} Promise that resolves with the Blob */ function base64StringToBlob(base64, type) { return Promise.resolve().then(function () { var parts = [binaryStringToArrayBuffer(atob(base64))]; return type ? createBlob(parts, {type: type}) : createBlob(parts); }); } /** * Convert a binary string to a Blob. Returns a Promise. * @param {string} binary * @param {string|undefined} type - the content type (optional) * @returns {Promise} Promise that resolves with the Blob */ function binaryStringToBlob(binary, type) { return Promise.resolve().then(function () { return base64StringToBlob(btoa(binary), type); }); } /** * Convert a Blob to a binary string. Returns a Promise. * @param {Blob} blob * @returns {Promise} Promise that resolves with the binary string */ function blobToBase64String(blob) { return blobToBinaryString(blob).then(function (binary) { return btoa(binary); }); } /** * Convert a data URL string * (e.g. 'data:image/png;base64,iVBORw0KG...') * to a Blob. Returns a Promise. * @param {string} dataURL * @returns {Promise} Promise that resolves with the Blob */ function dataURLToBlob(dataURL) { return Promise.resolve().then(function () { var type = dataURL.match(/data:([^;]+)/)[1]; var base64 = dataURL.replace(/^[^,]+,/, ''); var buff = binaryStringToArrayBuffer(atob(base64)); return createBlob([buff], {type: type}); }); } /** * Convert an image's src URL to a data URL by loading the image and painting * it to a canvas. Returns a Promise. * *

Note: this will coerce the image to the desired content type, and it * will only paint the first frame of an animated GIF. * * @param {string} src * @param {string|undefined} type - the content type (optional, defaults to 'image/png') * @param {string|undefined} crossOrigin - for CORS-enabled images, set this to * 'Anonymous' to avoid "tainted canvas" errors * @returns {Promise} Promise that resolves with the data URL string */ function imgSrcToDataURL(src, type, crossOrigin) { type = type || 'image/png'; return loadImage(src, crossOrigin).then(function (img) { return imgToCanvas(img); }).then(function (canvas) { return canvas.toDataURL(type); }); } /** * Convert a canvas to a Blob. Returns a Promise. * @param {string} canvas * @param {string|undefined} type - the content type (optional, defaults to 'image/png') * @returns {Promise} Promise that resolves with the Blob */ function canvasToBlob(canvas, type) { return Promise.resolve().then(function () { if (typeof canvas.toBlob === 'function') { return new Promise(function (resolve) { canvas.toBlob(resolve, type); }); } return dataURLToBlob(canvas.toDataURL(type)); }); } /** * Convert an image's src URL to a Blob by loading the image and painting * it to a canvas. Returns a Promise. * *

Note: this will coerce the image to the desired content type, and it * will only paint the first frame of an animated GIF. * * @param {string} src * @param {string|undefined} type - the content type (optional, defaults to 'image/png') * @param {string|undefined} crossOrigin - for CORS-enabled images, set this to * 'Anonymous' to avoid "tainted canvas" errors * @returns {Promise} Promise that resolves with the Blob */ function imgSrcToBlob(src, type, crossOrigin) { type = type || 'image/png'; return loadImage(src, crossOrigin).then(function (img) { return imgToCanvas(img); }).then(function (canvas) { return canvasToBlob(canvas, type); }); } /** * Convert an ArrayBuffer to a Blob. Returns a Promise. * * @param {ArrayBuffer} buffer * @param {string|undefined} type - the content type (optional) * @returns {Promise} Promise that resolves with the Blob */ function arrayBufferToBlob(buffer, type) { return Promise.resolve().then(function () { return createBlob([buffer], type); }); } /** * Convert a Blob to an ArrayBuffer. Returns a Promise. * @param {Blob} blob * @returns {Promise} Promise that resolves with the ArrayBuffer */ function blobToArrayBuffer(blob) { return blobToBinaryString(blob).then(function (binary) { return binaryStringToArrayBuffer(binary); }); } module.exports = { createBlob : createBlob, createObjectURL : createObjectURL, revokeObjectURL : revokeObjectURL, imgSrcToBlob : imgSrcToBlob, imgSrcToDataURL : imgSrcToDataURL, canvasToBlob : canvasToBlob, dataURLToBlob : dataURLToBlob, blobToBase64String : blobToBase64String, base64StringToBlob : base64StringToBlob, binaryStringToBlob : binaryStringToBlob, blobToBinaryString : blobToBinaryString, arrayBufferToBlob : arrayBufferToBlob, blobToArrayBuffer : blobToArrayBuffer };