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 |