diff --git a/.gitignore b/.gitignore index 318b19e..bea75e5 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ coverage test/test-bundle.js npm-debug.log dist +doc +api.md diff --git a/.jshintrc b/.jshintrc index 8b98e1a..6fa9ba0 100755 --- a/.jshintrc +++ b/.jshintrc @@ -15,7 +15,7 @@ "globals": { "Pouch": true}, "white": true, "indent": 2, - "maxlen": 100, + "maxlen": 120, "predef": [ "process", "global", diff --git a/README.md b/README.md index 52a39d5..1423d0c 100644 --- a/README.md +++ b/README.md @@ -3,21 +3,26 @@ blob-util [![Build Status](https://travis-ci.org/nolanlawson/blob-util.svg)](https://travis-ci.org/nolanlawson/blob-util) -You know what's cool? [HTML5 Blobs](https://developer.mozilla.org/en-US/docs/Web/API/Blob?redirectlocale=en-US&redirectslug=DOM%2FBlob). +`blob-util` is a [Blob](https://developer.mozilla.org/en-US/docs/Web/API/Blob?redirectlocale=en-US&redirectslug=DOM%2FBlob) library for busy people. -You know what's hard to work with? Yeah, you guessed it. +If you want an easy way to work with binary data in the browser, or you don't even know what a Blob is, then this is the library for you. -If you just want to work with binary data in the browser and not pull your hair out, then this is the library for you. +`blob-util` offers cross-browser utilities for translating Blobs to and from different formats: -`blob-util` offers various utilities for transforming Blobs between different formats (base 64, data URL, image), and it works -cross-browser. +* `` tags +* base 64 strings +* binary strings +* ArrayBuffers +* data URLs It's also a good pairing with the attachment API in [PouchDB](http://pouchdb.com). -Usage +`blob-util` is a browser library. If you need to work with binary data in Node.js, see [Buffers](http://nodejs.org/api/buffer.html). + +Install ------ -Grab it from the `dist/` folder above, or npm: +Grab it from the `dist/` folder above, or use NPM: ``` npm install blob-util @@ -39,19 +44,222 @@ Now you have a `window.blobUtil` object that contains the API. Or if you don't l globals, you can use Browserify. -Building +Quick Start +-------- + +Here's Kirby. He's a famous little Blob. + +Kirby + +So let's fulfill his manifest destiny, and convert him to a real `Blob` object. + +```js +var img = document.getElementById('kirby'); + +blobUtil.imgSrcToBlob(img.src).then(function (blob) { + // ladies and gents, we have a blob +}).catch(function (err) { + // image failed to load +}) +``` + +(Don't worry, this won't download the image twice, because browsers are smart.) + +Now that we have a `Blob`, we can convert it to a URL and use that as the source for another `` tag: + +```js +var blobURL = blobUtil.createObjectURL(blob); + +var newImg = document.createElement('img'); + +newImg.src = blobURL; +``` + +So now we have to Kirbys - one with a normal URL, and the other with a blob URL. Neato! + +API +------- + +Warning: this API uses [Promises](https://promisesaplus.com/), because it's not 2009 anymore. + +###Overview + +* [createBlob(parts, options)](#createBlob) +* [createObjectURL(blob)](#createObjectURL) +* [revokeObjectURL(url)](#revokeObjectURL) +* [blobToBinaryString(blob)](#blobToBinaryString) +* [base64StringToBlob(base64, type)](#base64StringToBlob) +* [binaryStringToBlob(binary, type)](#binaryStringToBlob) +* [blobToBase64String(blob)](#blobToBase64String) +* [dataURLToBlob(dataURL)](#dataURLToBlob) +* [imgSrcToDataURL(src, type)](#imgSrcToDataURL) +* [canvasToBlob(canvas, type)](#canvasToBlob) +* [imgSrcToBlob(src, type)](#imgSrcToBlob) +* [arrayBufferToBlob(buffer, type)](#arrayBufferToBlob) +* [blobToArrayBuffer(blob)](#blobToArrayBuffer) + + +###createBlob(parts, options) +Shim for +[new Blob()](https://developer.mozilla.org/en-US/docs/Web/API/Blob.Blob) +to support +[older browsers that use the deprecated BlobBuilder API](http://caniuse.com/blob). + +**Params** + +- parts `Array` - content of the Blob +- options `Object` - usually just {type: myContentType} + +**Returns**: `Blob` + +###createObjectURL(blob) +Shim for +[URL.createObjectURL()](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). + +**Params** + +- blob `Blob` + +**Returns**: `string` - url + +###revokeObjectURL(url) +Shim for +[URL.revokeObjectURL()](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). + +**Params** + +- url `string` + + +###blobToBinaryString(blob) +Convert a Blob to a binary string. Returns a Promise. + +**Params** + +- blob `Blob` + +**Returns**: `Promise` - Promise that resolves with the binary string + +###base64StringToBlob(base64, type) +Convert a base64-encoded string to a Blob. Returns a Promise. + +**Params** + +- base64 `string` +- type `string` | `undefined` - the content type (optional) + +**Returns**: `Promise` - Promise that resolves with the Blob + +###binaryStringToBlob(binary, type) +Convert a binary string to a Blob. Returns a Promise. + +**Params** + +- binary `string` +- type `string` | `undefined` - the content type (optional) + +**Returns**: `Promise` - Promise that resolves with the Blob + +###blobToBase64String(blob) +Convert a Blob to a binary string. Returns a Promise. + +**Params** + +- blob `Blob` + +**Returns**: `Promise` - Promise that resolves with the binary string + +###dataURLToBlob(dataURL) +Convert a data URL string +(e.g. 'data:image/png;base64,iVBORw0KG...') +to a Blob. Returns a Promise. + +**Params** + +- dataURL `string` + +**Returns**: `Promise` - Promise that resolves with the Blob + +###imgSrcToDataURL(src, 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. + +**Params** + +- src `string` +- type `string` | `undefined` - the content type (optional, defaults to 'image/png') + +**Returns**: `Promise` - Promise that resolves with the data URL string + +###canvasToBlob(canvas, type) +Convert a canvas to a Blob. Returns a Promise. + +**Params** + +- canvas `string` +- type `string` | `undefined` - the content type (optional, defaults to 'image/png') + +**Returns**: `Promise` - Promise that resolves with the Blob + +###imgSrcToBlob(src, 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. + +**Params** + +- src `string` +- type `string` | `undefined` - the content type (optional, defaults to 'image/png') + +**Returns**: `Promise` - Promise that resolves with the Blob + +###arrayBufferToBlob(buffer, type) +Convert an ArrayBuffer to a Blob. Returns a Promise. + +**Params** + +- buffer `ArrayBuffer` +- type `string` | `undefined` - the content type (optional) + +**Returns**: `Promise` - Promise that resolves with the Blob + +###blobToArrayBuffer(blob) +Convert a Blob to an ArrayBuffer. Returns a Promise. + +**Params** + +- blob `Blob` + +**Returns**: `Promise` - Promise that resolves with the ArrayBuffer + + + + + +Building the library ---- npm install npm run build Your plugin is now located at `dist/pouchdb.mypluginname.js` and `dist/pouchdb.mypluginname.min.js` and is ready for distribution. +To generate documentation: + + npm run jsdoc -Testing +Testing the library ---- - ### In the browser Run `npm run dev` and then point your favorite browser to [http://127.0.0.1:8001/test/index.html](http://127.0.0.1:8001/test/index.html). diff --git a/index.js b/index.js index a6c9646..fc396ab 100644 --- a/index.js +++ b/index.js @@ -1,26 +1,23 @@ 'use strict'; var utils = require('./utils'); -var blob = require('blob'); +/* jshint -W079 */ +var Blob = require('blob'); var Promise = utils.Promise; -function createBlob(parts, opts) { - opts = opts || {}; - if (typeof opts === 'string') { - opts = {type: opts}; // do you a solid here - } - return new blob(parts, opts); -} +// +// PRIVATE +// // From http://stackoverflow.com/questions/14967647/ (continues on next line) // encode-decode-image-with-base64-breaks-image (2013-04-21) -function binaryStringToArrayBuffer(bin) { - var length = bin.length; +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] = bin.charCodeAt(i); + arr[i] = binary.charCodeAt(i); } return buf; } @@ -39,46 +36,8 @@ function arrayBufferToBinaryString(buffer) { return binary; } -// shim for browsers that don't support it -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); - } - }); -} - -function base64StringToBlob(base64, type) { - return Promise.resolve().then(function () { - var parts = [binaryStringToArrayBuffer(atob(base64))]; - return type ? createBlob(parts, {type: type}) : createBlob(parts); - }); -} - -function binaryStringToBlob(binary, type) { - return Promise.resolve().then(function () { - return base64StringToBlob(btoa(binary), type); - }); -} - -function blobToBase64String(blob) { - return blobToBinaryString(blob).then(function (binary) { - return btoa(binary); - }); -} - +// doesn't download the image more than once, because +// browsers aren't dumb. uses the cache function loadImage(src) { return new Promise(function (resolve, reject) { var img = new Image(); @@ -91,25 +50,6 @@ function loadImage(src) { }); } -function dataURLToBlob(dataURL) { - return Promise.resolve().then(function () { - var type = dataURL.match(/data:([^;]+)/)[1]; - var base64 = dataURL.replace(/^[^,]+,/, ''); - - return createBlob([binaryStringToArrayBuffer(atob(base64))], {type: type}); - }); -} - -function createObjectURL(blob) { - var compatURL = window.URL || window.webkitURL; - return compatURL.createObjectURL(blob); -} - -function revokeObjectURL(url) { - var compatURL = window.URL || window.webkitURL; - return compatURL.revokeObjectURL(url); -} - function imgToCanvas(img) { var canvas = document.createElement('canvas'); @@ -128,6 +68,141 @@ function imgToCanvas(img) { 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') + * @returns {Promise} Promise that resolves with the data URL string + */ function imgSrcToDataURL(src, type) { type = type || 'image/png'; @@ -138,12 +213,14 @@ function imgSrcToDataURL(src, type) { }); } -function imgSrcToBlob(src, type) { - type = type || 'image/png'; - - return loadImage(src).then(function (img) { - return imgToCanvas(img); - }).then(function (canvas) { +/** + * 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); @@ -153,12 +230,45 @@ function imgSrcToBlob(src, 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') + * @returns {Promise} Promise that resolves with the Blob + */ +function imgSrcToBlob(src, type) { + type = type || 'image/png'; + + return loadImage(src).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); @@ -171,6 +281,7 @@ module.exports = { revokeObjectURL : revokeObjectURL, imgSrcToBlob : imgSrcToBlob, imgSrcToDataURL : imgSrcToDataURL, + canvasToBlob : canvasToBlob, dataURLToBlob : dataURLToBlob, blobToBase64String : blobToBase64String, base64StringToBlob : base64StringToBlob, diff --git a/package.json b/package.json index 1932677..52554f7 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "url": "https://github.com/nolanlawson/blob-util/issues" }, "scripts": { - "test-node": "istanbul test ./node_modules/mocha/bin/_mocha test/test.js", + "test-node": "echo 'node tests disabled'", "test-browser": "./bin/test-browser.js", "jshint": "jshint -c .jshintrc *.js test/test.js", "test": "npm run jshint && ./bin/run-test.sh", @@ -28,7 +28,8 @@ "min": "uglifyjs dist/blob-util.js -mc > dist/blob-util.min.js", "dev": "browserify -s blobUtil test/test.js > test/test-bundle.js && npm run dev-server", "dev-server": "./bin/dev-server.js", - "coverage": "npm test --coverage && istanbul check-coverage --lines 100 --function 100 --statements 100 --branches 100" + "coverage": "npm test --coverage && istanbul check-coverage --lines 100 --function 100 --statements 100 --branches 100", + "jsdoc": "jsdoc2md --heading-depth 3 ./index.js > api.md" }, "dependencies": { "blob": "0.0.4", @@ -42,6 +43,8 @@ "chai-as-promised": "~4.1.0", "http-server": "~0.5.5", "istanbul": "^0.2.7", + "jsdoc": "^3.3.0-alpha10", + "jsdoc-to-markdown": "^0.5.9", "jshint": "~2.3.0", "mocha": "~1.18", "phantomjs": "^1.9.7-5",