| OLD | NEW |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 /** | 5 /** |
| 6 * A namespace class for image encoding functions. All methods are static. | 6 * A namespace class for image encoding functions. All methods are static. |
| 7 */ | 7 */ |
| 8 function ImageEncoder() {} | 8 function ImageEncoder() {} |
| 9 | 9 |
| 10 /** | 10 /** |
| 11 * The value 360 px is enough in Files.app grid view for HiDPI devices. | 11 * The value 360 px is enough in Files.app grid view for HiDPI devices. |
| 12 * @const {number} | 12 * @const {number} |
| 13 */ | 13 */ |
| 14 ImageEncoder.MAX_THUMBNAIL_DIMENSION = 360; | 14 ImageEncoder.MAX_THUMBNAIL_DIMENSION = 360; |
| 15 | 15 |
| 16 /** | 16 /** |
| 17 * Tries to create thumbnail if the image width or height longer than the size. | 17 * Tries to create thumbnail if the image width or height longer than the size. |
| 18 * @const {number} | 18 * @const {number} |
| 19 */ | 19 */ |
| 20 ImageEncoder.MIN_IMAGE_DIMENSION_FOR_THUMBNAIL = | 20 ImageEncoder.MIN_IMAGE_DIMENSION_FOR_THUMBNAIL = |
| 21 ImageEncoder.MAX_THUMBNAIL_DIMENSION * 4; | 21 ImageEncoder.MAX_THUMBNAIL_DIMENSION * 4; |
| 22 | 22 |
| 23 /** | 23 /** |
| 24 * Metadata encoders. | 24 * Metadata encoders. |
| 25 * @type {!Object.<string,function(new:ImageEncoder.MetadataEncoder,!Object)>} | 25 * @type {!Object.<string,function( |
| 26 * new:ImageEncoder.MetadataEncoder,!MetadataItem)>} |
| 26 * @const | 27 * @const |
| 27 */ | 28 */ |
| 28 ImageEncoder.metadataEncoders = {}; | 29 ImageEncoder.metadataEncoders = {}; |
| 29 | 30 |
| 30 /** | 31 /** |
| 31 * Registers metadata encoder. | 32 * Registers metadata encoder. |
| 32 * @param {function(new:ImageEncoder.MetadataEncoder,!Object)} constructor | 33 * @param {function(new:ImageEncoder.MetadataEncoder,!MetadataItem)} constructor |
| 33 * Constructor of a metadata encoder. | 34 * Constructor of a metadata encoder. |
| 34 * @param {string} mimeType Mime type of the metadata encoder. | 35 * @param {string} mimeType Mime type of the metadata encoder. |
| 35 */ | 36 */ |
| 36 ImageEncoder.registerMetadataEncoder = function(constructor, mimeType) { | 37 ImageEncoder.registerMetadataEncoder = function(constructor, mimeType) { |
| 37 ImageEncoder.metadataEncoders[mimeType] = constructor; | 38 ImageEncoder.metadataEncoders[mimeType] = constructor; |
| 38 }; | 39 }; |
| 39 | 40 |
| 40 /** | 41 /** |
| 41 * Create a metadata encoder. | 42 * Create a metadata encoder. |
| 42 * | 43 * |
| 43 * The encoder will own and modify a copy of the original metadata. | 44 * The encoder will own and modify a copy of the original metadata. |
| 44 * | 45 * |
| 45 * @param {!Object} metadata Original metadata. | 46 * @param {!MetadataItem} metadata Original metadata. |
| 46 * @return {!ImageEncoder.MetadataEncoder} Created metadata encoder. | 47 * @return {!ImageEncoder.MetadataEncoder} Created metadata encoder. |
| 47 */ | 48 */ |
| 48 ImageEncoder.createMetadataEncoder = function(metadata) { | 49 ImageEncoder.createMetadataEncoder = function(metadata) { |
| 49 var constructor = | 50 var constructor = |
| 50 (metadata && ImageEncoder.metadataEncoders[metadata.media.mimeType]) || | 51 ImageEncoder.metadataEncoders[metadata.mediaMimeType || ""] || |
| 51 ImageEncoder.MetadataEncoder; | 52 ImageEncoder.MetadataEncoder; |
| 52 return new constructor(metadata); | 53 return new constructor(metadata); |
| 53 }; | 54 }; |
| 54 | 55 |
| 55 /** | 56 /** |
| 56 * Create a metadata encoder object holding a copy of metadata | 57 * Create a metadata encoder object holding a copy of metadata |
| 57 * modified according to the properties of the supplied image. | 58 * modified according to the properties of the supplied image. |
| 58 * | 59 * |
| 59 * @param {!Object} metadata Original metadata. | 60 * @param {!MetadataItem} metadata Original metadata. |
| 60 * @param {!HTMLCanvasElement} canvas Canvas to use for metadata. | 61 * @param {!HTMLCanvasElement} canvas Canvas to use for metadata. |
| 61 * @param {number} thumbnailQuality Encoding quality of a thumbnail. | 62 * @param {number} thumbnailQuality Encoding quality of a thumbnail. |
| 62 * @param {Date=} opt_modificationDateTime Modification date time of an image. | |
| 63 * @return {!ImageEncoder.MetadataEncoder} Encoder with encoded metadata. | 63 * @return {!ImageEncoder.MetadataEncoder} Encoder with encoded metadata. |
| 64 */ | 64 */ |
| 65 ImageEncoder.encodeMetadata = function( | 65 ImageEncoder.encodeMetadata = function(metadata, canvas, thumbnailQuality) { |
| 66 metadata, canvas, thumbnailQuality, opt_modificationDateTime) { | |
| 67 var encoder = ImageEncoder.createMetadataEncoder(metadata); | 66 var encoder = ImageEncoder.createMetadataEncoder(metadata); |
| 68 encoder.setImageData(canvas, opt_modificationDateTime); | 67 encoder.setImageData(canvas); |
| 69 encoder.setThumbnailData(ImageEncoder.createThumbnail(canvas), | 68 encoder.setThumbnailData(ImageEncoder.createThumbnail(canvas), |
| 70 thumbnailQuality); | 69 thumbnailQuality); |
| 71 return encoder; | 70 return encoder; |
| 72 }; | 71 }; |
| 73 | 72 |
| 74 /** | 73 /** |
| 75 * Return a blob with the encoded image with metadata inserted. | 74 * Return a blob with the encoded image with metadata inserted. |
| 76 * @param {!HTMLCanvasElement} canvas The canvas with the image to be encoded. | 75 * @param {!HTMLCanvasElement} canvas The canvas with the image to be encoded. |
| 77 * @param {!ImageEncoder.MetadataEncoder} metadataEncoder Encoder to use. | 76 * @param {!ImageEncoder.MetadataEncoder} metadataEncoder Encoder to use. |
| 78 * @param {number} imageQuality (0..1], Encoding quality of an image. | 77 * @param {number} imageQuality (0..1], Encoding quality of an image. |
| 79 * @return {!Blob} encoded data. | 78 * @return {!Blob} encoded data. |
| 80 */ | 79 */ |
| 81 ImageEncoder.getBlob = function(canvas, metadataEncoder, imageQuality) { | 80 ImageEncoder.getBlob = function(canvas, metadataEncoder, imageQuality) { |
| 82 ImageUtil.trace.resetTimer('dataurl'); | 81 ImageUtil.trace.resetTimer('dataurl'); |
| 83 // WebKit does not support canvas.toBlob yet so canvas.toDataURL is | 82 // WebKit does not support canvas.toBlob yet so canvas.toDataURL is |
| 84 // the only way to use the Chrome built-in image encoder. | 83 // the only way to use the Chrome built-in image encoder. |
| 85 var dataURL = canvas.toDataURL(metadataEncoder.getMetadata().media.mimeType, | 84 var dataURL = canvas.toDataURL(metadataEncoder.mimeType, imageQuality); |
| 86 imageQuality); | |
| 87 ImageUtil.trace.reportTimer('dataurl'); | 85 ImageUtil.trace.reportTimer('dataurl'); |
| 88 | 86 |
| 89 var encodedImage = ImageEncoder.decodeDataURL(dataURL); | 87 var encodedImage = ImageEncoder.decodeDataURL(dataURL); |
| 90 | 88 |
| 91 var encodedMetadata = metadataEncoder.encode(); | 89 var encodedMetadata = metadataEncoder.encode(); |
| 92 | 90 |
| 93 var slices = []; | 91 var slices = []; |
| 94 | 92 |
| 95 // TODO(kaznacheev): refactor |stringToArrayBuffer| and |encode| to return | 93 // TODO(kaznacheev): refactor |stringToArrayBuffer| and |encode| to return |
| 96 // arrays instead of array buffers. | 94 // arrays instead of array buffers. |
| 97 function appendSlice(arrayBuffer) { | 95 function appendSlice(arrayBuffer) { |
| 98 slices.push(new DataView(arrayBuffer)); | 96 slices.push(new DataView(arrayBuffer)); |
| 99 } | 97 } |
| 100 | 98 |
| 101 ImageUtil.trace.resetTimer('blob'); | 99 ImageUtil.trace.resetTimer('blob'); |
| 102 if (encodedMetadata.byteLength != 0) { | 100 if (encodedMetadata.byteLength != 0) { |
| 103 var metadataRange = metadataEncoder.findInsertionRange(encodedImage); | 101 var metadataRange = metadataEncoder.findInsertionRange(encodedImage); |
| 104 appendSlice(ImageEncoder.stringToArrayBuffer( | 102 appendSlice(ImageEncoder.stringToArrayBuffer( |
| 105 encodedImage, 0, metadataRange.from)); | 103 encodedImage, 0, metadataRange.from)); |
| 106 | 104 |
| 107 appendSlice(metadataEncoder.encode()); | 105 appendSlice(metadataEncoder.encode()); |
| 108 | 106 |
| 109 appendSlice(ImageEncoder.stringToArrayBuffer( | 107 appendSlice(ImageEncoder.stringToArrayBuffer( |
| 110 encodedImage, metadataRange.to, encodedImage.length)); | 108 encodedImage, metadataRange.to, encodedImage.length)); |
| 111 } else { | 109 } else { |
| 112 appendSlice(ImageEncoder.stringToArrayBuffer( | 110 appendSlice(ImageEncoder.stringToArrayBuffer( |
| 113 encodedImage, 0, encodedImage.length)); | 111 encodedImage, 0, encodedImage.length)); |
| 114 } | 112 } |
| 115 var blob = new Blob(slices, | 113 var blob = new Blob(slices, {type: metadataEncoder.mimeType}); |
| 116 {type: metadataEncoder.getMetadata().media.mimeType}); | |
| 117 ImageUtil.trace.reportTimer('blob'); | 114 ImageUtil.trace.reportTimer('blob'); |
| 118 return blob; | 115 return blob; |
| 119 }; | 116 }; |
| 120 | 117 |
| 121 /** | 118 /** |
| 122 * Decode a dataURL into a binary string containing the encoded image. | 119 * Decode a dataURL into a binary string containing the encoded image. |
| 123 * | 120 * |
| 124 * Why return a string? Calling atob and having the rest of the code deal | 121 * Why return a string? Calling atob and having the rest of the code deal |
| 125 * with a string is several times faster than decoding base64 in Javascript. | 122 * with a string is several times faster than decoding base64 in Javascript. |
| 126 * | 123 * |
| (...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 174 } | 171 } |
| 175 return array.buffer; | 172 return array.buffer; |
| 176 }; | 173 }; |
| 177 | 174 |
| 178 /** | 175 /** |
| 179 * A base class for a metadata encoder. | 176 * A base class for a metadata encoder. |
| 180 * | 177 * |
| 181 * Serves as a default metadata encoder for images that none of the metadata | 178 * Serves as a default metadata encoder for images that none of the metadata |
| 182 * parsers recognized. | 179 * parsers recognized. |
| 183 * | 180 * |
| 184 * @param {!Object} original_metadata Starting metadata. | 181 * @param {!MetadataItem} originalMetadata Starting metadata. |
| 185 * @constructor | 182 * @constructor |
| 186 * @struct | 183 * @struct |
| 187 */ | 184 */ |
| 188 ImageEncoder.MetadataEncoder = function(original_metadata) { | 185 ImageEncoder.MetadataEncoder = function(originalMetadata) { |
| 189 this.metadata_ = MetadataCache.cloneMetadata(original_metadata) || {}; | 186 var mimeType = ImageEncoder.MetadataEncoder.getMimeType_(originalMetadata); |
| 190 if (ImageEncoder.MetadataEncoder.getMimeType_(this.metadata_) !== | 187 |
| 191 'image/jpeg') { | 188 /** |
| 192 // Chrome can only encode JPEG and PNG. Force PNG mime type so that we | 189 * Chrome can only encode JPEG and PNG. Force PNG mime type so that we |
| 193 // can save to file and generate a thumbnail. | 190 * can save to file and generate a thumbnail. |
| 194 // TODO(yawano) Change this not to modify metadata. Mime type comes from | 191 * @public {string} |
| 195 // different fields depending on the conditions. Just overriding | 192 */ |
| 196 // media.mimeType and use the modified metadata could cause a problem. | 193 this.mimeType = mimeType === 'image/jpeg' ? 'image/jpeg' : 'image/png'; |
| 197 this.metadata_.media.mimeType = 'image/png'; | 194 |
| 198 } | 195 /** |
| 196 * @protected {string} |
| 197 */ |
| 198 this.thumbnailDataUrl = ''; |
| 199 |
| 200 /** |
| 201 * @protected {number} |
| 202 */ |
| 203 this.imageWidth = 0; |
| 204 |
| 205 /** |
| 206 * @protected {number} |
| 207 */ |
| 208 this.imageHeight = 0; |
| 199 }; | 209 }; |
| 200 | 210 |
| 201 /** | 211 /** |
| 202 * Gets mime type from metadata. It reads media.mimeType at first, and if it | 212 * Gets mime type from metadata. It reads media.mimeType at first, and if it |
| 203 * fails, it falls back to external.contentMimeType. If both fields are | 213 * fails, it falls back to external.contentMimeType. If both fields are |
| 204 * undefined, it means that metadata is broken. Then it throws an exception. | 214 * undefined, it means that metadata is broken. Then it throws an exception. |
| 205 * | 215 * |
| 206 * @param {!Object} metadata Metadata. | 216 * @param {!MetadataItem} metadata Metadata. |
| 207 * @return {string} Mime type. | 217 * @return {string} Mime type. |
| 208 * @private | 218 * @private |
| 209 */ | 219 */ |
| 210 ImageEncoder.MetadataEncoder.getMimeType_ = function(metadata) { | 220 ImageEncoder.MetadataEncoder.getMimeType_ = function(metadata) { |
| 211 if (metadata.media.mimeType) | 221 if (metadata.mediaMimeType) |
| 212 return metadata.media.mimeType; | 222 return metadata.mediaMimeType; |
| 213 else if (metadata.external.contentMimeType) | 223 else if (metadata.contentMimeType) |
| 214 return metadata.external.contentMimeType; | 224 return metadata.contentMimeType; |
| 215 | 225 |
| 216 assertNotReached(); | 226 assertNotReached(); |
| 217 }; | 227 }; |
| 218 | 228 |
| 219 /** | 229 /** |
| 220 * Returns metadata. | |
| 221 * @return {!Object} A metadata. | |
| 222 * | |
| 223 * TODO(yawano): MetadataEncoder.getMetadata seems not to be used anymore. | |
| 224 * Investigate this, and remove if possible. Should not modify a metadata by | |
| 225 * using an encoder. | |
| 226 */ | |
| 227 ImageEncoder.MetadataEncoder.prototype.getMetadata = function() { | |
| 228 return this.metadata_; | |
| 229 }; | |
| 230 | |
| 231 /** | |
| 232 * Sets an image data. | 230 * Sets an image data. |
| 233 * @param {!HTMLCanvasElement} canvas Canvas or anything with width and height | 231 * @param {!HTMLCanvasElement} canvas Canvas or anything with width and height |
| 234 * properties. | 232 * properties. |
| 235 * @param {Date=} opt_modificationDateTime Modification date time of an image. | |
| 236 */ | 233 */ |
| 237 ImageEncoder.MetadataEncoder.prototype.setImageData = | 234 ImageEncoder.MetadataEncoder.prototype.setImageData = function(canvas) { |
| 238 function(canvas, opt_modificationDateTime) { | 235 this.imageWidth = canvas.width; |
| 239 this.metadata_.width = canvas.width; | 236 this.imageHeight = canvas.height; |
| 240 this.metadata_.height = canvas.height; | |
| 241 }; | 237 }; |
| 242 | 238 |
| 243 /** | 239 /** |
| 244 * @param {HTMLCanvasElement} canvas Canvas to use as thumbnail. Note that it | 240 * @param {HTMLCanvasElement} canvas Canvas to use as thumbnail. Note that it |
| 245 * can be null. | 241 * can be null. |
| 246 * @param {number} quality Thumbnail quality. | 242 * @param {number} quality Thumbnail quality. |
| 247 */ | 243 */ |
| 248 ImageEncoder.MetadataEncoder.prototype.setThumbnailData = | 244 ImageEncoder.MetadataEncoder.prototype.setThumbnailData = |
| 249 function(canvas, quality) { | 245 function(canvas, quality) { |
| 250 this.metadata_.thumbnailURL = | 246 this.thumbnailDataUrl = |
| 251 canvas ? canvas.toDataURL(this.metadata_.media.mimeType, quality) : ''; | 247 canvas ? canvas.toDataURL(this.mimeType, quality) : ''; |
| 252 delete this.metadata_.thumbnailTransform; | |
| 253 }; | 248 }; |
| 254 | 249 |
| 255 /** | 250 /** |
| 256 * Returns a range where the metadata is (or should be) located. | 251 * Returns a range where the metadata is (or should be) located. |
| 257 * @param {string} encodedImage An encoded image. | 252 * @param {string} encodedImage An encoded image. |
| 258 * @return {{from:number, to:number}} An object with from and to properties. | 253 * @return {{from:number, to:number}} An object with from and to properties. |
| 259 */ | 254 */ |
| 260 ImageEncoder.MetadataEncoder.prototype. | 255 ImageEncoder.MetadataEncoder.prototype. |
| 261 findInsertionRange = function(encodedImage) { return {from: 0, to: 0}; }; | 256 findInsertionRange = function(encodedImage) { return {from: 0, to: 0}; }; |
| 262 | 257 |
| 263 /** | 258 /** |
| 264 * Returns serialized metadata ready to write to an image file. | 259 * Returns serialized metadata ready to write to an image file. |
| 265 * The return type is optimized for passing to Blob.append. | 260 * The return type is optimized for passing to Blob.append. |
| 266 * @return {!ArrayBuffer} Serialized metadata. | 261 * @return {!ArrayBuffer} Serialized metadata. |
| 267 */ | 262 */ |
| 268 ImageEncoder.MetadataEncoder.prototype.encode = function() { | 263 ImageEncoder.MetadataEncoder.prototype.encode = function() { |
| 269 return new Uint8Array(0).buffer; | 264 return new Uint8Array(0).buffer; |
| 270 }; | 265 }; |
| OLD | NEW |