Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1435)

Side by Side Diff: chrome/browser/resources/file_manager/js/exif_parser.js

Issue 9583009: [File Manager] Cleanup: Moving js/css/html files to dedicated directories (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: 2011->2012 Created 8 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(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 const EXIF_MARK_SOI = 0xffd8; // Start of image data.
6 const EXIF_MARK_SOS = 0xffda; // Start of "stream" (the actual image data).
7 const EXIF_MARK_SOF = 0xffc0; // Start of "frame"
8 const EXIF_MARK_EXIF = 0xffe1; // Start of exif block.
9
10 const EXIF_ALIGN_LITTLE = 0x4949; // Indicates little endian exif data.
11 const EXIF_ALIGN_BIG = 0x4d4d; // Indicates big endian exif data.
12
13 const EXIF_TAG_TIFF = 0x002a; // First directory containing TIFF data.
14 const EXIF_TAG_GPSDATA = 0x8825; // Pointer from TIFF to the GPS directory.
15 const EXIF_TAG_EXIFDATA = 0x8769; // Pointer from TIFF to the EXIF IFD.
16 const EXIF_TAG_SUBIFD = 0x014a; // Pointer from TIFF to "Extra" IFDs.
17
18 const EXIF_TAG_JPG_THUMB_OFFSET = 0x0201; // Pointer from TIFF to thumbnail.
19 const EXIF_TAG_JPG_THUMB_LENGTH = 0x0202; // Length of thumbnail data.
20
21 const EXIF_TAG_ORIENTATION = 0x0112;
22 const EXIF_TAG_X_DIMENSION = 0xA002;
23 const EXIF_TAG_Y_DIMENSION = 0xA003;
24
25 function ExifParser(parent) {
26 ImageParser.call(this, parent, 'jpeg', /\.jpe?g$/i);
27 }
28
29 ExifParser.prototype = {__proto__: ImageParser.prototype};
30
31 ExifParser.prototype.parse = function(file, metadata, callback, errorCallback) {
32 this.requestSlice(file, callback, errorCallback, metadata, 0);
33 };
34
35 ExifParser.prototype.requestSlice = function (
36 file, callback, errorCallback, metadata, filePos, opt_length) {
37 // Read at least 1Kb so that we do not issue too many read requests.
38 opt_length = Math.max(1024, opt_length || 0);
39
40 var self = this;
41 var reader = new FileReader();
42 reader.onerror = errorCallback;
43 reader.onload = function() { self.parseSlice(
44 file, callback, errorCallback, metadata, filePos, reader.result);
45 };
46 reader.readAsArrayBuffer(file.webkitSlice(filePos, filePos + opt_length));
47 };
48
49 ExifParser.prototype.parseSlice = function(
50 file, callback, errorCallback, metadata, filePos, buf) {
51 try {
52 var br = new ByteReader(buf);
53
54 if (!br.canRead(4)) {
55 // We never ask for less than 4 bytes. This can only mean we reached EOF.
56 throw new Error('Unexpected EOF @' + (filePos + buf.byteLength));
57 }
58
59 if (filePos == 0) {
60 // First slice, check for the SOI mark.
61 var firstMark = this.readMark(br);
62 if (firstMark != EXIF_MARK_SOI)
63 throw new Error('Invalid file header: ' + firstMark.toString(16));
64 }
65
66 var self = this;
67 function reread(opt_offset, opt_bytes) {
68 self.requestSlice(file, callback, errorCallback, metadata,
69 filePos + br.tell() + (opt_offset || 0), opt_bytes);
70 }
71
72 while (true) {
73 if (!br.canRead(4)) {
74 // Cannot read the mark and the length, request a minimum-size slice.
75 reread();
76 return;
77 }
78
79 var mark = this.readMark(br);
80 if (mark == EXIF_MARK_SOS)
81 throw new Error('SOS marker found before SOF');
82
83 var markLength = this.readMarkLength(br);
84
85 var nextSectionStart = br.tell() + markLength;
86 if (!br.canRead(markLength)) {
87 // Get the entire section.
88 if (filePos + br.tell() + markLength > file.size) {
89 throw new Error(
90 'Invalid section length @' + (filePos + br.tell() - 2));
91 }
92 reread(-4, markLength + 4);
93 return;
94 }
95
96 if (mark == EXIF_MARK_EXIF) {
97 this.parseExifSection(metadata, buf, br);
98 } else if (ExifParser.isSOF_(mark)) {
99 // The most reliable size information is encoded in the SOF section.
100 br.seek(1, ByteReader.SEEK_CUR); // Skip the precision byte.
101 var height = br.readScalar(2);
102 var width = br.readScalar(2);
103 ExifParser.setImageSize(metadata, width, height);
104 callback(metadata); // We are done!
105 return;
106 }
107
108 br.seek(nextSectionStart, ByteReader.SEEK_BEG);
109 }
110 } catch (e) {
111 errorCallback(e.toString());
112 }
113 };
114
115 ExifParser.isSOF_ = function(mark) {
116 // 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.
118 if ((mark & ~0xF) != EXIF_MARK_SOF) return false;
119
120 // If the last digit is 4, 8 or 12 it is not really a SOF.
121 var type = mark & 0xF;
122 return (type != 4 && type != 8 && type != 12);
123 };
124
125 ExifParser.prototype.parseExifSection = function(metadata, buf, br) {
126 var magic = br.readString(6);
127 if (magic != 'Exif\0\0') {
128 // Some JPEG files may have sections marked with EXIF_MARK_EXIF
129 // but containing something else (e.g. XML text). Ignore such sections.
130 this.vlog('Invalid EXIF magic: ' + magic + br.readString(100));
131 return;
132 }
133
134 // Offsets inside the EXIF block are based after the magic string.
135 // Create a new ByteReader based on the current position to make offset
136 // calculations simpler.
137 br = new ByteReader(buf, br.tell());
138
139 var order = br.readScalar(2);
140 if (order == EXIF_ALIGN_LITTLE) {
141 br.setByteOrder(ByteReader.LITTLE_ENDIAN);
142 } else if (order != EXIF_ALIGN_BIG) {
143 this.log('Invalid alignment value: ' + order.toString(16));
144 return;
145 }
146
147 var tag = br.readScalar(2);
148 if (tag != EXIF_TAG_TIFF) {
149 this.log('Invalid TIFF tag: ' + tag.toString(16));
150 return;
151 }
152
153 metadata.littleEndian = (order == EXIF_ALIGN_LITTLE);
154 metadata.ifd = {
155 image: {},
156 thumbnail: {}
157 };
158 var directoryOffset = br.readScalar(4);
159
160 // Image directory.
161 this.vlog('Read image directory.');
162 br.seek(directoryOffset);
163 directoryOffset = this.readDirectory(br, metadata.ifd.image);
164 metadata.imageTransform = this.parseOrientation(metadata.ifd.image);
165
166 // Thumbnail Directory chained from the end of the image directory.
167 if (directoryOffset) {
168 this.vlog('Read thumbnail directory.');
169 br.seek(directoryOffset);
170 this.readDirectory(br, metadata.ifd.thumbnail);
171 // If no thumbnail orientation is encoded, assume same orientation as
172 // the primary image.
173 metadata.thumbnailTransform =
174 this.parseOrientation(metadata.ifd.thumbnail) ||
175 metadata.imageTransform;
176 }
177
178 // EXIF Directory may be specified as a tag in the image directory.
179 if (EXIF_TAG_EXIFDATA in metadata.ifd.image) {
180 this.vlog('Read EXIF directory.');
181 directoryOffset = metadata.ifd.image[EXIF_TAG_EXIFDATA].value;
182 br.seek(directoryOffset);
183 metadata.ifd.exif = {};
184 this.readDirectory(br, metadata.ifd.exif);
185 }
186
187 // GPS Directory may also be linked from the image directory.
188 if (EXIF_TAG_GPSDATA in metadata.ifd.image) {
189 this.vlog('Read GPS directory.');
190 directoryOffset = metadata.ifd.image[EXIF_TAG_GPSDATA].value;
191 br.seek(directoryOffset);
192 metadata.ifd.gps = {};
193 this.readDirectory(br, metadata.ifd.gps);
194 }
195
196 // Thumbnail may be linked from the image directory.
197 if (EXIF_TAG_JPG_THUMB_OFFSET in metadata.ifd.thumbnail &&
198 EXIF_TAG_JPG_THUMB_LENGTH in metadata.ifd.thumbnail) {
199 this.vlog('Read thumbnail image.');
200 br.seek(metadata.ifd.thumbnail[EXIF_TAG_JPG_THUMB_OFFSET].value);
201 metadata.thumbnailURL = br.readImage(
202 metadata.ifd.thumbnail[EXIF_TAG_JPG_THUMB_LENGTH].value);
203 } else {
204 this.vlog('Image has EXIF data, but no JPG thumbnail.');
205 }
206 };
207
208 ExifParser.setImageSize = function(metadata, width, height) {
209 if (metadata.imageTransform && metadata.imageTransform.rotate90) {
210 metadata.width = height;
211 metadata.height = width;
212 } else {
213 metadata.width = width;
214 metadata.height = height;
215 }
216 };
217
218 ExifParser.prototype.readMark = function(br) {
219 return br.readScalar(2);
220 };
221
222 ExifParser.prototype.readMarkLength = function(br) {
223 // Length includes the 2 bytes used to store the length.
224 return br.readScalar(2) - 2;
225 };
226
227 ExifParser.prototype.readDirectory = function(br, tags) {
228 var entryCount = br.readScalar(2);
229 for (var i = 0; i < entryCount; i++) {
230 var tagId = br.readScalar(2);
231 var tag = tags[tagId] = {id: tagId};
232 tag.format = br.readScalar(2);
233 tag.componentCount = br.readScalar(4);
234 this.readTagValue(br, tag);
235 }
236
237 return br.readScalar(4);
238 };
239
240 ExifParser.prototype.readTagValue = function(br, tag) {
241 var self = this;
242
243 function safeRead(size, readFunction, signed) {
244 try {
245 unsafeRead(size, readFunction, signed);
246 } catch (ex) {
247 self.log('error reading tag 0x' + tag.id.toString(16) + '/' +
248 tag.format + ', size ' + tag.componentCount + '*' + size + ' ' +
249 (ex.stack || '<no stack>') + ': ' + ex);
250 tag.value = null;
251 }
252 }
253
254 function unsafeRead(size, readFunction, signed) {
255 if (!readFunction)
256 readFunction = function(size) { return br.readScalar(size, signed) };
257
258 var totalSize = tag.componentCount * size;
259 if (totalSize < 1) {
260 // This is probably invalid exif data, skip it.
261 tag.componentCount = 1;
262 tag.value = br.readScalar(4);
263 return;
264 }
265
266 if (totalSize > 4) {
267 // If the total size is > 4, the next 4 bytes will be a pointer to the
268 // actual data.
269 br.pushSeek(br.readScalar(4));
270 }
271
272 if (tag.componentCount == 1) {
273 tag.value = readFunction(size);
274 } else {
275 // Read multiple components into an array.
276 tag.value = [];
277 for (var i = 0; i < tag.componentCount; i++)
278 tag.value[i] = readFunction(size);
279 }
280
281 if (totalSize > 4) {
282 // Go back to the previous position if we had to jump to the data.
283 br.popSeek();
284 } else if (totalSize < 4) {
285 // Otherwise, if the value wasn't exactly 4 bytes, skip over the
286 // unread data.
287 br.seek(4 - totalSize, ByteReader.SEEK_CUR);
288 }
289 }
290
291 switch (tag.format) {
292 case 1: // Byte
293 case 7: // Undefined
294 safeRead(1);
295 break;
296
297 case 2: // String
298 safeRead(1);
299 if (tag.componentCount == 0) {
300 tag.value = '';
301 } else if (tag.componentCount == 1) {
302 tag.value = String.fromCharCode(tag.value);
303 } else {
304 tag.value = String.fromCharCode.apply(null, tag.value);
305 }
306 break;
307
308 case 3: // Short
309 safeRead(2);
310 break;
311
312 case 4: // Long
313 safeRead(4);
314 break;
315
316 case 9: // Signed Long
317 safeRead(4, null, true);
318 break;
319
320 case 5: // Rational
321 safeRead(8, function() {
322 return [ br.readScalar(4), br.readScalar(4) ];
323 });
324 break;
325
326 case 10: // Signed Rational
327 safeRead(8, function() {
328 return [ br.readScalar(4, true), br.readScalar(4, true) ];
329 });
330 break;
331
332 default: // ???
333 this.vlog('Unknown tag format 0x' + Number(tag.id).toString(16) +
334 ': ' + tag.format);
335 safeRead(4);
336 break;
337 }
338
339 this.vlog('Read tag: 0x' + tag.id.toString(16) + '/' + tag.format + ': ' +
340 tag.value);
341 };
342
343 ExifParser.SCALEX = [1, -1, -1, 1, 1, 1, -1, -1];
344 ExifParser.SCALEY = [1, 1, -1, -1, -1, 1, 1, -1];
345 ExifParser.ROTATE90 = [0, 0, 0, 0, 1, 1, 1, 1];
346
347 /**
348 * Transform exif-encoded orientation into a set of parameters compatible with
349 * CSS and canvas transforms (scaleX, scaleY, rotation).
350 *
351 * @param {Object} ifd exif property dictionary (image or thumbnail)
352 */
353 ExifParser.prototype.parseOrientation = function(ifd) {
354 if (ifd[EXIF_TAG_ORIENTATION]) {
355 var index = (ifd[EXIF_TAG_ORIENTATION].value || 1) - 1;
356 return {
357 scaleX: ExifParser.SCALEX[index],
358 scaleY: ExifParser.SCALEY[index],
359 rotate90: ExifParser.ROTATE90[index]
360 }
361 }
362 return null;
363 };
364
365 MetadataDispatcher.registerParserClass(ExifParser);
OLDNEW
« no previous file with comments | « chrome/browser/resources/file_manager/js/byte_reader.js ('k') | chrome/browser/resources/file_manager/js/file_manager.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698