| 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 |