OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 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 var EXIF_MARK_SOI = 0xffd8; // Start of image data. | 5 var EXIF_MARK_SOI = 0xffd8; // Start of image data. |
6 var EXIF_MARK_SOS = 0xffda; // Start of "stream" (the actual image data). | 6 var EXIF_MARK_SOS = 0xffda; // Start of "stream" (the actual image data). |
7 var EXIF_MARK_SOF = 0xffc0; // Start of "frame" | 7 var EXIF_MARK_SOF = 0xffc0; // Start of "frame" |
8 var EXIF_MARK_EXIF = 0xffe1; // Start of exif block. | 8 var EXIF_MARK_EXIF = 0xffe1; // Start of exif block. |
9 | 9 |
10 var EXIF_ALIGN_LITTLE = 0x4949; // Indicates little endian exif data. | 10 var EXIF_ALIGN_LITTLE = 0x4949; // Indicates little endian exif data. |
(...skipping 10 matching lines...) Expand all Loading... |
21 var EXIF_TAG_ORIENTATION = 0x0112; | 21 var EXIF_TAG_ORIENTATION = 0x0112; |
22 var EXIF_TAG_X_DIMENSION = 0xA002; | 22 var EXIF_TAG_X_DIMENSION = 0xA002; |
23 var EXIF_TAG_Y_DIMENSION = 0xA003; | 23 var EXIF_TAG_Y_DIMENSION = 0xA003; |
24 | 24 |
25 function ExifParser(parent) { | 25 function ExifParser(parent) { |
26 ImageParser.call(this, parent, 'jpeg', /\.jpe?g$/i); | 26 ImageParser.call(this, parent, 'jpeg', /\.jpe?g$/i); |
27 } | 27 } |
28 | 28 |
29 ExifParser.prototype = {__proto__: ImageParser.prototype}; | 29 ExifParser.prototype = {__proto__: ImageParser.prototype}; |
30 | 30 |
| 31 /** |
| 32 * @param {File} file //TODO(JSDOC). |
| 33 * @param {Object} metadata //TODO(JSDOC). |
| 34 * @param {function} callback //TODO(JSDOC). |
| 35 * @param {function} errorCallback //TODO(JSDOC). |
| 36 */ |
31 ExifParser.prototype.parse = function(file, metadata, callback, errorCallback) { | 37 ExifParser.prototype.parse = function(file, metadata, callback, errorCallback) { |
32 this.requestSlice(file, callback, errorCallback, metadata, 0); | 38 this.requestSlice(file, callback, errorCallback, metadata, 0); |
33 }; | 39 }; |
34 | 40 |
| 41 /** |
| 42 * @param {File} file //TODO(JSDOC). |
| 43 * @param {function} callback //TODO(JSDOC). |
| 44 * @param {function} errorCallback //TODO(JSDOC). |
| 45 * @param {Object} metadata //TODO(JSDOC). |
| 46 * @param {number} filePos //TODO(JSDOC). |
| 47 * @param {number=} opt_length //TODO(JSDOC). |
| 48 */ |
35 ExifParser.prototype.requestSlice = function( | 49 ExifParser.prototype.requestSlice = function( |
36 file, callback, errorCallback, metadata, filePos, opt_length) { | 50 file, callback, errorCallback, metadata, filePos, opt_length) { |
37 // Read at least 1Kb so that we do not issue too many read requests. | 51 // Read at least 1Kb so that we do not issue too many read requests. |
38 opt_length = Math.max(1024, opt_length || 0); | 52 opt_length = Math.max(1024, opt_length || 0); |
39 | 53 |
40 var self = this; | 54 var self = this; |
41 var reader = new FileReader(); | 55 var reader = new FileReader(); |
42 reader.onerror = errorCallback; | 56 reader.onerror = errorCallback; |
43 reader.onload = function() { self.parseSlice( | 57 reader.onload = function() { self.parseSlice( |
44 file, callback, errorCallback, metadata, filePos, reader.result); | 58 file, callback, errorCallback, metadata, filePos, reader.result); |
45 }; | 59 }; |
46 reader.readAsArrayBuffer(file.slice(filePos, filePos + opt_length)); | 60 reader.readAsArrayBuffer(file.slice(filePos, filePos + opt_length)); |
47 }; | 61 }; |
48 | 62 |
| 63 /** |
| 64 * @param {File} file //TODO(JSDOC). |
| 65 * @param {function} callback //TODO(JSDOC). |
| 66 * @param {function} errorCallback //TODO(JSDOC). |
| 67 * @param {Object} metadata //TODO(JSDOC). |
| 68 * @param {number} filePos //TODO(JSDOC). |
| 69 * @param {ArrayBuffer} buf //TODO(JSDOC). |
| 70 */ |
49 ExifParser.prototype.parseSlice = function( | 71 ExifParser.prototype.parseSlice = function( |
50 file, callback, errorCallback, metadata, filePos, buf) { | 72 file, callback, errorCallback, metadata, filePos, buf) { |
51 try { | 73 try { |
52 var br = new ByteReader(buf); | 74 var br = new ByteReader(buf); |
53 | 75 |
54 if (!br.canRead(4)) { | 76 if (!br.canRead(4)) { |
55 // We never ask for less than 4 bytes. This can only mean we reached EOF. | 77 // We never ask for less than 4 bytes. This can only mean we reached EOF. |
56 throw new Error('Unexpected EOF @' + (filePos + buf.byteLength)); | 78 throw new Error('Unexpected EOF @' + (filePos + buf.byteLength)); |
57 } | 79 } |
58 | 80 |
(...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
105 return; | 127 return; |
106 } | 128 } |
107 | 129 |
108 br.seek(nextSectionStart, ByteReader.SEEK_BEG); | 130 br.seek(nextSectionStart, ByteReader.SEEK_BEG); |
109 } | 131 } |
110 } catch (e) { | 132 } catch (e) { |
111 errorCallback(e.toString()); | 133 errorCallback(e.toString()); |
112 } | 134 } |
113 }; | 135 }; |
114 | 136 |
| 137 /** |
| 138 * @private |
| 139 * @param {number} mark //TODO(JSDOC). |
| 140 * @return {boolean} //TODO(JSDOC). |
| 141 */ |
115 ExifParser.isSOF_ = function(mark) { | 142 ExifParser.isSOF_ = function(mark) { |
116 // There are 13 variants of SOF fragment format distinguished by the last | 143 // There are 13 variants of SOF fragment format distinguished by the last |
117 // hex digit of the mark, but the part we want is always the same. | 144 // hex digit of the mark, but the part we want is always the same. |
118 if ((mark & ~0xF) != EXIF_MARK_SOF) return false; | 145 if ((mark & ~0xF) != EXIF_MARK_SOF) return false; |
119 | 146 |
120 // If the last digit is 4, 8 or 12 it is not really a SOF. | 147 // If the last digit is 4, 8 or 12 it is not really a SOF. |
121 var type = mark & 0xF; | 148 var type = mark & 0xF; |
122 return (type != 4 && type != 8 && type != 12); | 149 return (type != 4 && type != 8 && type != 12); |
123 }; | 150 }; |
124 | 151 |
| 152 /** |
| 153 * @param {Object} metadata //TODO(JSDOC). |
| 154 * @param {ArrayBuffer} buf //TODO(JSDOC). |
| 155 * @param {ByteReader} br //TODO(JSDOC). |
| 156 */ |
125 ExifParser.prototype.parseExifSection = function(metadata, buf, br) { | 157 ExifParser.prototype.parseExifSection = function(metadata, buf, br) { |
126 var magic = br.readString(6); | 158 var magic = br.readString(6); |
127 if (magic != 'Exif\0\0') { | 159 if (magic != 'Exif\0\0') { |
128 // Some JPEG files may have sections marked with EXIF_MARK_EXIF | 160 // Some JPEG files may have sections marked with EXIF_MARK_EXIF |
129 // but containing something else (e.g. XML text). Ignore such sections. | 161 // but containing something else (e.g. XML text). Ignore such sections. |
130 this.vlog('Invalid EXIF magic: ' + magic + br.readString(100)); | 162 this.vlog('Invalid EXIF magic: ' + magic + br.readString(100)); |
131 return; | 163 return; |
132 } | 164 } |
133 | 165 |
134 // Offsets inside the EXIF block are based after the magic string. | 166 // Offsets inside the EXIF block are based after the magic string. |
(...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
198 EXIF_TAG_JPG_THUMB_LENGTH in metadata.ifd.thumbnail) { | 230 EXIF_TAG_JPG_THUMB_LENGTH in metadata.ifd.thumbnail) { |
199 this.vlog('Read thumbnail image.'); | 231 this.vlog('Read thumbnail image.'); |
200 br.seek(metadata.ifd.thumbnail[EXIF_TAG_JPG_THUMB_OFFSET].value); | 232 br.seek(metadata.ifd.thumbnail[EXIF_TAG_JPG_THUMB_OFFSET].value); |
201 metadata.thumbnailURL = br.readImage( | 233 metadata.thumbnailURL = br.readImage( |
202 metadata.ifd.thumbnail[EXIF_TAG_JPG_THUMB_LENGTH].value); | 234 metadata.ifd.thumbnail[EXIF_TAG_JPG_THUMB_LENGTH].value); |
203 } else { | 235 } else { |
204 this.vlog('Image has EXIF data, but no JPG thumbnail.'); | 236 this.vlog('Image has EXIF data, but no JPG thumbnail.'); |
205 } | 237 } |
206 }; | 238 }; |
207 | 239 |
| 240 /** |
| 241 * @param {Object} metadata //TODO(JSDOC). |
| 242 * @param {number} width //TODO(JSDOC). |
| 243 * @param {number} height //TODO(JSDOC). |
| 244 */ |
208 ExifParser.setImageSize = function(metadata, width, height) { | 245 ExifParser.setImageSize = function(metadata, width, height) { |
209 if (metadata.imageTransform && metadata.imageTransform.rotate90) { | 246 if (metadata.imageTransform && metadata.imageTransform.rotate90) { |
210 metadata.width = height; | 247 metadata.width = height; |
211 metadata.height = width; | 248 metadata.height = width; |
212 } else { | 249 } else { |
213 metadata.width = width; | 250 metadata.width = width; |
214 metadata.height = height; | 251 metadata.height = height; |
215 } | 252 } |
216 }; | 253 }; |
217 | 254 |
| 255 /** |
| 256 * @param {ByteReader} br //TODO(JSDOC). |
| 257 * @return {number} //TODO(JSDOC). |
| 258 */ |
218 ExifParser.prototype.readMark = function(br) { | 259 ExifParser.prototype.readMark = function(br) { |
219 return br.readScalar(2); | 260 return br.readScalar(2); |
220 }; | 261 }; |
221 | 262 |
| 263 /** |
| 264 * @param {ByteReader} br //TODO(JSDOC). |
| 265 * @return {number} //TODO(JSDOC). |
| 266 */ |
222 ExifParser.prototype.readMarkLength = function(br) { | 267 ExifParser.prototype.readMarkLength = function(br) { |
223 // Length includes the 2 bytes used to store the length. | 268 // Length includes the 2 bytes used to store the length. |
224 return br.readScalar(2) - 2; | 269 return br.readScalar(2) - 2; |
225 }; | 270 }; |
226 | 271 |
| 272 /** |
| 273 * @param {ByteReader} br //TODO(JSDOC). |
| 274 * @param {Array.<Object>} tags //TODO(JSDOC). |
| 275 * @return {number} //TODO(JSDOC). |
| 276 */ |
227 ExifParser.prototype.readDirectory = function(br, tags) { | 277 ExifParser.prototype.readDirectory = function(br, tags) { |
228 var entryCount = br.readScalar(2); | 278 var entryCount = br.readScalar(2); |
229 for (var i = 0; i < entryCount; i++) { | 279 for (var i = 0; i < entryCount; i++) { |
230 var tagId = br.readScalar(2); | 280 var tagId = br.readScalar(2); |
231 var tag = tags[tagId] = {id: tagId}; | 281 var tag = tags[tagId] = {id: tagId}; |
232 tag.format = br.readScalar(2); | 282 tag.format = br.readScalar(2); |
233 tag.componentCount = br.readScalar(4); | 283 tag.componentCount = br.readScalar(4); |
234 this.readTagValue(br, tag); | 284 this.readTagValue(br, tag); |
235 } | 285 } |
236 | 286 |
237 return br.readScalar(4); | 287 return br.readScalar(4); |
238 }; | 288 }; |
239 | 289 |
| 290 /** |
| 291 * @param {ByteReader} br //TODO(JSDOC). |
| 292 * @param {Object} tag //TODO(JSDOC). |
| 293 */ |
240 ExifParser.prototype.readTagValue = function(br, tag) { | 294 ExifParser.prototype.readTagValue = function(br, tag) { |
241 var self = this; | 295 var self = this; |
242 | 296 |
243 function safeRead(size, readFunction, signed) { | 297 function safeRead(size, readFunction, signed) { |
244 try { | 298 try { |
245 unsafeRead(size, readFunction, signed); | 299 unsafeRead(size, readFunction, signed); |
246 } catch (ex) { | 300 } catch (ex) { |
247 self.log('error reading tag 0x' + tag.id.toString(16) + '/' + | 301 self.log('error reading tag 0x' + tag.id.toString(16) + '/' + |
248 tag.format + ', size ' + tag.componentCount + '*' + size + ' ' + | 302 tag.format + ', size ' + tag.componentCount + '*' + size + ' ' + |
249 (ex.stack || '<no stack>') + ': ' + ex); | 303 (ex.stack || '<no stack>') + ': ' + ex); |
(...skipping 83 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
333 this.vlog('Unknown tag format 0x' + Number(tag.id).toString(16) + | 387 this.vlog('Unknown tag format 0x' + Number(tag.id).toString(16) + |
334 ': ' + tag.format); | 388 ': ' + tag.format); |
335 safeRead(4); | 389 safeRead(4); |
336 break; | 390 break; |
337 } | 391 } |
338 | 392 |
339 this.vlog('Read tag: 0x' + tag.id.toString(16) + '/' + tag.format + ': ' + | 393 this.vlog('Read tag: 0x' + tag.id.toString(16) + '/' + tag.format + ': ' + |
340 tag.value); | 394 tag.value); |
341 }; | 395 }; |
342 | 396 |
343 //TODO(JSDOC) | 397 /** |
| 398 * TODO(JSDOC) |
| 399 * @const |
| 400 * @type {Array.<number>} |
| 401 */ |
344 ExifParser.SCALEX = [1, -1, -1, 1, 1, 1, -1, -1]; | 402 ExifParser.SCALEX = [1, -1, -1, 1, 1, 1, -1, -1]; |
345 | 403 |
346 //TODO(JSDOC) | 404 /** |
| 405 * TODO(JSDOC) |
| 406 * @const |
| 407 * @type {Array.<number>} |
| 408 */ |
347 ExifParser.SCALEY = [1, 1, -1, -1, -1, 1, 1, -1]; | 409 ExifParser.SCALEY = [1, 1, -1, -1, -1, 1, 1, -1]; |
348 | 410 |
349 //TODO(JSDOC) | 411 /** |
| 412 * TODO(JSDOC) |
| 413 * @const |
| 414 * @type {Array.<number>} |
| 415 */ |
350 ExifParser.ROTATE90 = [0, 0, 0, 0, 1, 1, 1, 1]; | 416 ExifParser.ROTATE90 = [0, 0, 0, 0, 1, 1, 1, 1]; |
351 | 417 |
352 /** | 418 /** |
353 * Transform exif-encoded orientation into a set of parameters compatible with | 419 * Transform exif-encoded orientation into a set of parameters compatible with |
354 * CSS and canvas transforms (scaleX, scaleY, rotation). | 420 * CSS and canvas transforms (scaleX, scaleY, rotation). |
355 * | 421 * |
356 * @param {Object} ifd exif property dictionary (image or thumbnail). | 422 * @param {Object} ifd exif property dictionary (image or thumbnail). |
357 * @return {Object} //TODO(JSDOC). | 423 * @return {Object} //TODO(JSDOC). |
358 */ | 424 */ |
359 ExifParser.prototype.parseOrientation = function(ifd) { | 425 ExifParser.prototype.parseOrientation = function(ifd) { |
360 if (ifd[EXIF_TAG_ORIENTATION]) { | 426 if (ifd[EXIF_TAG_ORIENTATION]) { |
361 var index = (ifd[EXIF_TAG_ORIENTATION].value || 1) - 1; | 427 var index = (ifd[EXIF_TAG_ORIENTATION].value || 1) - 1; |
362 return { | 428 return { |
363 scaleX: ExifParser.SCALEX[index], | 429 scaleX: ExifParser.SCALEX[index], |
364 scaleY: ExifParser.SCALEY[index], | 430 scaleY: ExifParser.SCALEY[index], |
365 rotate90: ExifParser.ROTATE90[index] | 431 rotate90: ExifParser.ROTATE90[index] |
366 }; | 432 }; |
367 } | 433 } |
368 return null; | 434 return null; |
369 }; | 435 }; |
370 | 436 |
371 MetadataDispatcher.registerParserClass(ExifParser); | 437 MetadataDispatcher.registerParserClass(ExifParser); |
OLD | NEW |