| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 'use strict'; | |
| 6 | |
| 7 /** | |
| 8 * A namespace class for image encoding functions. All methods are static. | |
| 9 */ | |
| 10 function ImageEncoder() {} | |
| 11 | |
| 12 /** | |
| 13 * @type {Array.<Object>} | |
| 14 */ | |
| 15 ImageEncoder.metadataEncoders = {}; | |
| 16 | |
| 17 /** | |
| 18 * @param {function(new:ImageEncoder.MetadataEncoder)} constructor | |
| 19 * // TODO(JSDOC). | |
| 20 * @param {string} mimeType // TODO(JSDOC). | |
| 21 */ | |
| 22 ImageEncoder.registerMetadataEncoder = function(constructor, mimeType) { | |
| 23 ImageEncoder.metadataEncoders[mimeType] = constructor; | |
| 24 }; | |
| 25 | |
| 26 /** | |
| 27 * Create a metadata encoder. | |
| 28 * | |
| 29 * The encoder will own and modify a copy of the original metadata. | |
| 30 * | |
| 31 * @param {Object} metadata Original metadata. | |
| 32 * @return {ImageEncoder.MetadataEncoder} Created metadata encoder. | |
| 33 */ | |
| 34 ImageEncoder.createMetadataEncoder = function(metadata) { | |
| 35 var constructor = ImageEncoder.metadataEncoders[metadata.mimeType] || | |
| 36 ImageEncoder.MetadataEncoder; | |
| 37 return new constructor(metadata); | |
| 38 }; | |
| 39 | |
| 40 | |
| 41 /** | |
| 42 * Create a metadata encoder object holding a copy of metadata | |
| 43 * modified according to the properties of the supplied image. | |
| 44 * | |
| 45 * @param {Object} metadata Original metadata. | |
| 46 * @param {HTMLCanvasElement} canvas Canvas to use for metadata. | |
| 47 * @param {number} quality Encoding quality (defaults to 1). | |
| 48 * @return {ImageEncoder.MetadataEncoder} Encoder with encoded metadata. | |
| 49 */ | |
| 50 ImageEncoder.encodeMetadata = function(metadata, canvas, quality) { | |
| 51 var encoder = ImageEncoder.createMetadataEncoder(metadata); | |
| 52 encoder.setImageData(canvas); | |
| 53 encoder.setThumbnailData(ImageEncoder.createThumbnail(canvas), quality || 1); | |
| 54 return encoder; | |
| 55 }; | |
| 56 | |
| 57 | |
| 58 /** | |
| 59 * Return a blob with the encoded image with metadata inserted. | |
| 60 * @param {HTMLCanvasElement} canvas The canvas with the image to be encoded. | |
| 61 * @param {ImageEncoder.MetadataEncoder} metadataEncoder Encoder to use. | |
| 62 * @param {number} quality (0..1], Encoding quality, defaults to 0.9. | |
| 63 * @return {Blob} encoded data. | |
| 64 */ | |
| 65 ImageEncoder.getBlob = function(canvas, metadataEncoder, quality) { | |
| 66 // Contrary to what one might think 1.0 is not a good default. Opening and | |
| 67 // saving an typical photo taken with consumer camera increases its file size | |
| 68 // by 50-100%. | |
| 69 // Experiments show that 0.9 is much better. It shrinks some photos a bit, | |
| 70 // keeps others about the same size, but does not visibly lower the quality. | |
| 71 quality = quality || 0.9; | |
| 72 | |
| 73 ImageUtil.trace.resetTimer('dataurl'); | |
| 74 // WebKit does not support canvas.toBlob yet so canvas.toDataURL is | |
| 75 // the only way to use the Chrome built-in image encoder. | |
| 76 var dataURL = | |
| 77 canvas.toDataURL(metadataEncoder.getMetadata().mimeType, quality); | |
| 78 ImageUtil.trace.reportTimer('dataurl'); | |
| 79 | |
| 80 var encodedImage = ImageEncoder.decodeDataURL(dataURL); | |
| 81 | |
| 82 var encodedMetadata = metadataEncoder.encode(); | |
| 83 | |
| 84 var slices = []; | |
| 85 | |
| 86 // TODO(kaznacheev): refactor |stringToArrayBuffer| and |encode| to return | |
| 87 // arrays instead of array buffers. | |
| 88 function appendSlice(arrayBuffer) { | |
| 89 slices.push(new DataView(arrayBuffer)); | |
| 90 } | |
| 91 | |
| 92 ImageUtil.trace.resetTimer('blob'); | |
| 93 if (encodedMetadata.byteLength != 0) { | |
| 94 var metadataRange = metadataEncoder.findInsertionRange(encodedImage); | |
| 95 appendSlice(ImageEncoder.stringToArrayBuffer( | |
| 96 encodedImage, 0, metadataRange.from)); | |
| 97 | |
| 98 appendSlice(metadataEncoder.encode()); | |
| 99 | |
| 100 appendSlice(ImageEncoder.stringToArrayBuffer( | |
| 101 encodedImage, metadataRange.to, encodedImage.length)); | |
| 102 } else { | |
| 103 appendSlice(ImageEncoder.stringToArrayBuffer( | |
| 104 encodedImage, 0, encodedImage.length)); | |
| 105 } | |
| 106 var blob = new Blob(slices, {type: metadataEncoder.getMetadata().mimeType}); | |
| 107 ImageUtil.trace.reportTimer('blob'); | |
| 108 return blob; | |
| 109 }; | |
| 110 | |
| 111 /** | |
| 112 * Decode a dataURL into a binary string containing the encoded image. | |
| 113 * | |
| 114 * Why return a string? Calling atob and having the rest of the code deal | |
| 115 * with a string is several times faster than decoding base64 in Javascript. | |
| 116 * | |
| 117 * @param {string} dataURL Data URL to decode. | |
| 118 * @return {string} A binary string (char codes are the actual byte values). | |
| 119 */ | |
| 120 ImageEncoder.decodeDataURL = function(dataURL) { | |
| 121 // Skip the prefix ('data:image/<type>;base64,') | |
| 122 var base64string = dataURL.substring(dataURL.indexOf(',') + 1); | |
| 123 return atob(base64string); | |
| 124 }; | |
| 125 | |
| 126 /** | |
| 127 * Return a thumbnail for an image. | |
| 128 * @param {HTMLCanvasElement} canvas Original image. | |
| 129 * @param {number=} opt_shrinkage Thumbnail should be at least this much smaller | |
| 130 * than the original image (in each dimension). | |
| 131 * @return {HTMLCanvasElement} Thumbnail canvas. | |
| 132 */ | |
| 133 ImageEncoder.createThumbnail = function(canvas, opt_shrinkage) { | |
| 134 var MAX_THUMBNAIL_DIMENSION = 320; | |
| 135 | |
| 136 opt_shrinkage = Math.max(opt_shrinkage || 4, | |
| 137 canvas.width / MAX_THUMBNAIL_DIMENSION, | |
| 138 canvas.height / MAX_THUMBNAIL_DIMENSION); | |
| 139 | |
| 140 var thumbnailCanvas = canvas.ownerDocument.createElement('canvas'); | |
| 141 thumbnailCanvas.width = Math.round(canvas.width / opt_shrinkage); | |
| 142 thumbnailCanvas.height = Math.round(canvas.height / opt_shrinkage); | |
| 143 | |
| 144 var context = thumbnailCanvas.getContext('2d'); | |
| 145 context.drawImage(canvas, | |
| 146 0, 0, canvas.width, canvas.height, | |
| 147 0, 0, thumbnailCanvas.width, thumbnailCanvas.height); | |
| 148 | |
| 149 return thumbnailCanvas; | |
| 150 }; | |
| 151 | |
| 152 /** | |
| 153 * TODO(JSDOC) | |
| 154 * @param {string} string // TODO(JSDOC). | |
| 155 * @param {number} from // TODO(JSDOC). | |
| 156 * @param {number} to // TODO(JSDOC). | |
| 157 * @return {ArrayBuffer} // TODO(JSDOC). | |
| 158 */ | |
| 159 ImageEncoder.stringToArrayBuffer = function(string, from, to) { | |
| 160 var size = to - from; | |
| 161 var array = new Uint8Array(size); | |
| 162 for (var i = 0; i != size; i++) { | |
| 163 array[i] = string.charCodeAt(from + i); | |
| 164 } | |
| 165 return array.buffer; | |
| 166 }; | |
| 167 | |
| 168 /** | |
| 169 * A base class for a metadata encoder. | |
| 170 * | |
| 171 * Serves as a default metadata encoder for images that none of the metadata | |
| 172 * parsers recognized. | |
| 173 * | |
| 174 * @param {Object} original_metadata Starting metadata. | |
| 175 * @constructor | |
| 176 */ | |
| 177 ImageEncoder.MetadataEncoder = function(original_metadata) { | |
| 178 this.metadata_ = MetadataCache.cloneMetadata(original_metadata) || {}; | |
| 179 if (this.metadata_.mimeType != 'image/jpeg') { | |
| 180 // Chrome can only encode JPEG and PNG. Force PNG mime type so that we | |
| 181 // can save to file and generate a thumbnail. | |
| 182 this.metadata_.mimeType = 'image/png'; | |
| 183 } | |
| 184 }; | |
| 185 | |
| 186 /** | |
| 187 * TODO(JSDOC) | |
| 188 * @return {Object} // TODO(JSDOC). | |
| 189 */ | |
| 190 ImageEncoder.MetadataEncoder.prototype.getMetadata = function() { | |
| 191 return this.metadata_; | |
| 192 }; | |
| 193 | |
| 194 /** | |
| 195 * @param {HTMLCanvasElement|Object} canvas Canvas or or anything with | |
| 196 * width and height properties. | |
| 197 */ | |
| 198 ImageEncoder.MetadataEncoder.prototype.setImageData = function(canvas) { | |
| 199 this.metadata_.width = canvas.width; | |
| 200 this.metadata_.height = canvas.height; | |
| 201 }; | |
| 202 | |
| 203 /** | |
| 204 * @param {HTMLCanvasElement} canvas Canvas to use as thumbnail. | |
| 205 * @param {number} quality Thumbnail quality. | |
| 206 */ | |
| 207 ImageEncoder.MetadataEncoder.prototype.setThumbnailData = | |
| 208 function(canvas, quality) { | |
| 209 this.metadata_.thumbnailURL = | |
| 210 canvas.toDataURL(this.metadata_.mimeType, quality); | |
| 211 }; | |
| 212 | |
| 213 /** | |
| 214 * Return a range where the metadata is (or should be) located. | |
| 215 * @param {string} encodedImage // TODO(JSDOC). | |
| 216 * @return {Object} An object with from and to properties. | |
| 217 */ | |
| 218 ImageEncoder.MetadataEncoder.prototype. | |
| 219 findInsertionRange = function(encodedImage) { return {from: 0, to: 0} }; | |
| 220 | |
| 221 /** | |
| 222 * Return serialized metadata ready to write to an image file. | |
| 223 * The return type is optimized for passing to Blob.append. | |
| 224 * @return {ArrayBuffer} // TODO(JSDOC). | |
| 225 */ | |
| 226 ImageEncoder.MetadataEncoder.prototype.encode = function() { | |
| 227 return new Uint8Array(0).buffer; | |
| 228 }; | |
| OLD | NEW |