2 * This is JsJpegMeta v1.0
3 * From: https://code.google.com/p/jsjpegmeta/downloads/list
4 * From: https://github.com/bennoleslie/jsjpegmeta/blob/v1.0.0/jpegmeta.js
6 * Ported to MediaWiki ResourceLoader by Bryan Tong Minh
9 * - Add this.JpegMeta assignment to expose it as global.
14 Copyright (c) 2009 Ben Leslie
16 Permission is hereby granted, free of charge, to any person obtaining a copy
17 of this software and associated documentation files (the "Software"), to deal
18 in the Software without restriction, including without limitation the rights
19 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
20 copies of the Software, and to permit persons to whom the Software is
21 furnished to do so, subject to the following conditions:
23 The above copyright notice and this permission notice shall be included in
24 all copies or substantial portions of the Software.
26 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
27 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
28 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
29 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
30 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
31 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
36 This JavaScript library is used to parse meta-data from files
37 with mime-type image/jpeg.
39 Include it with something like:
41 <script type="text/javascript" src="jpegmeta.js"></script>
43 This adds a single 'module' object called 'JpegMeta' to the global
48 JpegMeta.parseNum - parse unsigned integers from binary data
49 JpegMeta.parseSnum - parse signed integers from binary data
53 JpegMeta.Rational - A rational number class
56 JpegMeta.JpegFile - Primary class for Javascript parsing
60 // MediaWiki: Expose as global
61 this.JpegMeta = JpegMeta;
64 parse an unsigned number of size bytes at offset in some binary string data.
66 is "<" parse the data as little endian, if endian
67 is ">" parse as big-endian.
69 JpegMeta.parseNum = function parseNum(endian, data, offset, size) {
72 var big_endian = (endian === ">");
73 if (offset === undefined) offset = 0;
74 if (size === undefined) size = data.length - offset;
75 for (big_endian ? i = offset : i = offset + size - 1;
76 big_endian ? i < offset + size : i >= offset;
77 big_endian ? i++ : i--) {
79 ret += data.charCodeAt(i);
85 parse an signed number of size bytes at offset in some binary string data.
87 is "<" parse the data as little endian, if endian
88 is ">" parse as big-endian.
90 JpegMeta.parseSnum = function parseSnum(endian, data, offset, size) {
94 var big_endian = (endian === ">");
95 if (offset === undefined) offset = 0;
96 if (size === undefined) size = data.length - offset;
97 for (big_endian ? i = offset : i = offset + size - 1;
98 big_endian ? i < offset + size : i >= offset;
99 big_endian ? i++ : i--) {
100 if (neg === undefined) {
101 /* Negative if top bit is set */
102 neg = (data.charCodeAt(i) & 0x80) === 0x80;
105 /* If it is negative we invert the bits */
106 ret += neg ? ~data.charCodeAt(i) & 0xff: data.charCodeAt(i);
109 /* If it is negative we do two's complement */
116 /* Rational number class */
117 JpegMeta.Rational = function Rational(num, den)
124 /* Rational number methods */
125 JpegMeta.Rational.prototype.toString = function toString() {
126 if (this.num === 0) {
127 return "" + this.num;
129 if (this.den === 1) {
130 return "" + this.num;
132 if (this.num === 1) {
133 return this.num + " / " + this.den;
135 return this.num / this.den; // + "/" + this.den;
138 JpegMeta.Rational.prototype.asFloat = function asFloat() {
139 return this.num / this.den;
142 /* MetaGroup class */
143 JpegMeta.MetaGroup = function MetaGroup(fieldName, description) {
144 this.fieldName = fieldName;
145 this.description = description;
150 JpegMeta.MetaGroup.prototype._addProperty = function _addProperty(fieldName, description, value) {
151 var property = new JpegMeta.MetaProp(fieldName, description, value);
152 this[property.fieldName] = property;
153 this.metaProps[property.fieldName] = property;
156 JpegMeta.MetaGroup.prototype.toString = function toString() {
157 return "[MetaGroup " + this.description + "]";
161 JpegMeta.MetaProp = function MetaProp(fieldName, description, value) {
162 this.fieldName = fieldName;
163 this.description = description;
168 JpegMeta.MetaProp.prototype.toString = function toString() {
169 return "" + this.value;
173 JpegMeta.JpegFile = function JpegFile(binary_data, filename) {
174 /* Change this to EOI if we want to parse. */
175 var break_segment = this._SOS;
177 this.metaGroups = {};
178 this._binary_data = binary_data;
179 this.filename = filename;
181 /* Go through and parse. */
183 var pos_start_of_segment = 0;
192 /* Check to see if this looks like a JPEG file */
193 if (this._binary_data.slice(0, 2) !== this._SOI_MARKER) {
194 throw new Error("Doesn't look like a JPEG file. First two bytes are " +
195 this._binary_data.charCodeAt(0) + "," +
196 this._binary_data.charCodeAt(1) + ".");
201 while (pos < this._binary_data.length) {
202 delim = this._binary_data.charCodeAt(pos++);
203 mark = this._binary_data.charCodeAt(pos++);
205 pos_start_of_segment = pos;
207 if (delim != this._DELIM) {
211 if (mark === break_segment) {
215 headersize = JpegMeta.parseNum(">", this._binary_data, pos, 2);
219 while (pos < this._binary_data.length) {
220 delim = this._binary_data.charCodeAt(pos++);
221 if (delim == this._DELIM) {
222 _mark = this._binary_data.charCodeAt(pos++);
230 segsize = pos - pos_start_of_segment;
232 if (this._markers[mark]) {
233 mark_code = this._markers[mark][0];
234 mark_fn = this._markers[mark][1];
241 this[mark_fn](mark, pos_start_of_segment + 2);
246 if (this.general === undefined) {
247 throw Error("Invalid JPEG file.");
253 this.JpegMeta.JpegFile.prototype.toString = function () {
254 return "[JpegFile " + this.filename + " " +
255 this.general.type + " " +
256 this.general.pixelWidth + "x" +
257 this.general.pixelHeight +
258 " Depth: " + this.general.depth + "]";
261 /* Some useful constants */
262 this.JpegMeta.JpegFile.prototype._SOI_MARKER = '\xff\xd8';
263 this.JpegMeta.JpegFile.prototype._DELIM = 0xff;
264 this.JpegMeta.JpegFile.prototype._EOI = 0xd9;
265 this.JpegMeta.JpegFile.prototype._SOS = 0xda;
267 this.JpegMeta.JpegFile.prototype._sofHandler = function _sofHandler (mark, pos) {
268 if (this.general !== undefined) {
269 throw Error("Unexpected multiple-frame image");
272 this._addMetaGroup("general", "General");
273 this.general._addProperty("depth", "Depth", JpegMeta.parseNum(">", this._binary_data, pos, 1));
274 this.general._addProperty("pixelHeight", "Pixel Height", JpegMeta.parseNum(">", this._binary_data, pos + 1, 2));
275 this.general._addProperty("pixelWidth", "Pixel Width",JpegMeta.parseNum(">", this._binary_data, pos + 3, 2));
276 this.general._addProperty("type", "Type", this._markers[mark][2]);
280 this.JpegMeta.JpegFile.prototype._JFIF_IDENT = "JFIF\x00";
281 this.JpegMeta.JpegFile.prototype._JFXX_IDENT = "JFXX\x00";
284 this.JpegMeta.JpegFile.prototype._EXIF_IDENT = "Exif\x00";
287 this.JpegMeta.JpegFile.prototype._types = {
288 /* The format is identifier : ["type name", type_size_in_bytes ] */
295 7 : ["UNDEFINED", 1],
298 10 : ["SRATIONAL", 8],
303 this.JpegMeta.JpegFile.prototype._tifftags = {
304 /* A. Tags relating to image data structure */
305 256 : ["Image width", "ImageWidth"],
306 257 : ["Image height", "ImageLength"],
307 258 : ["Number of bits per component", "BitsPerSample"],
308 259 : ["Compression scheme", "Compression",
309 {1 : "uncompressed", 6 : "JPEG compression" }],
310 262 : ["Pixel composition", "PhotmetricInerpretation",
311 {2 : "RGB", 6 : "YCbCr"}],
312 274 : ["Orientation of image", "Orientation",
313 /* FIXME: Check the mirror-image / reverse encoding and rotation */
314 {1 : "Normal", 2 : "Reverse?",
315 3 : "Upside-down", 4 : "Upside-down Reverse",
316 5 : "90 degree CW", 6 : "90 degree CW reverse",
317 7 : "90 degree CCW", 8 : "90 degree CCW reverse"}],
318 277 : ["Number of components", "SamplesPerPixel"],
319 284 : ["Image data arrangement", "PlanarConfiguration",
320 {1 : "chunky format", 2 : "planar format"}],
321 530 : ["Subsampling ratio of Y to C", "YCbCrSubSampling"],
322 531 : ["Y and C positioning", "YCbCrPositioning",
323 {1 : "centered", 2 : "co-sited"}],
324 282 : ["X Resolution", "XResolution"],
325 283 : ["Y Resolution", "YResolution"],
326 296 : ["Resolution Unit", "ResolutionUnit",
327 {2 : "inches", 3 : "centimeters"}],
328 /* B. Tags realting to recording offset */
329 273 : ["Image data location", "StripOffsets"],
330 278 : ["Number of rows per strip", "RowsPerStrip"],
331 279 : ["Bytes per compressed strip", "StripByteCounts"],
332 513 : ["Offset to JPEG SOI", "JPEGInterchangeFormat"],
333 514 : ["Bytes of JPEG Data", "JPEGInterchangeFormatLength"],
334 /* C. Tags relating to image data characteristics */
335 301 : ["Transfer function", "TransferFunction"],
336 318 : ["White point chromaticity", "WhitePoint"],
337 319 : ["Chromaticities of primaries", "PrimaryChromaticities"],
338 529 : ["Color space transformation matrix coefficients", "YCbCrCoefficients"],
339 532 : ["Pair of black and white reference values", "ReferenceBlackWhite"],
341 306 : ["Date and time", "DateTime"],
342 270 : ["Image title", "ImageDescription"],
343 271 : ["Make", "Make"],
344 272 : ["Model", "Model"],
345 305 : ["Software", "Software"],
346 315 : ["Person who created the image", "Artist"],
347 316 : ["Host Computer", "HostComputer"],
348 33432 : ["Copyright holder", "Copyright"],
350 34665 : ["Exif tag", "ExifIfdPointer"],
351 34853 : ["GPS tag", "GPSInfoIfdPointer"]
354 this.JpegMeta.JpegFile.prototype._exiftags = {
355 /* Tag Support Levels (2) - 0th IFX Exif Private Tags */
356 /* A. Tags Relating to Version */
357 36864 : ["Exif Version", "ExifVersion"],
358 40960 : ["FlashPix Version", "FlashpixVersion"],
360 /* B. Tag Relating to Image Data Characteristics */
361 40961 : ["Color Space", "ColorSpace"],
363 /* C. Tags Relating to Image Configuration */
364 37121 : ["Meaning of each component", "ComponentsConfiguration"],
365 37122 : ["Compressed Bits Per Pixel", "CompressedBitsPerPixel"],
366 40962 : ["Pixel X Dimension", "PixelXDimension"],
367 40963 : ["Pixel Y Dimension", "PixelYDimension"],
369 /* D. Tags Relating to User Information */
370 37500 : ["Manufacturer notes", "MakerNote"],
371 37510 : ["User comments", "UserComment"],
373 /* E. Tag Relating to Related File Information */
374 40964 : ["Related audio file", "RelatedSoundFile"],
376 /* F. Tags Relating to Date and Time */
377 36867 : ["Date Time Original", "DateTimeOriginal"],
378 36868 : ["Date Time Digitized", "DateTimeDigitized"],
379 37520 : ["DateTime subseconds", "SubSecTime"],
380 37521 : ["DateTimeOriginal subseconds", "SubSecTimeOriginal"],
381 37522 : ["DateTimeDigitized subseconds", "SubSecTimeDigitized"],
383 /* G. Tags Relating to Picture-Taking Conditions */
384 33434 : ["Exposure time", "ExposureTime"],
385 33437 : ["FNumber", "FNumber"],
386 34850 : ["Exposure program", "ExposureProgram"],
387 34852 : ["Spectral sensitivity", "SpectralSensitivity"],
388 34855 : ["ISO Speed Ratings", "ISOSpeedRatings"],
389 34856 : ["Optoelectric coefficient", "OECF"],
390 37377 : ["Shutter Speed", "ShutterSpeedValue"],
391 37378 : ["Aperture Value", "ApertureValue"],
392 37379 : ["Brightness", "BrightnessValue"],
393 37380 : ["Exposure Bias Value", "ExposureBiasValue"],
394 37381 : ["Max Aperture Value", "MaxApertureValue"],
395 37382 : ["Subject Distance", "SubjectDistance"],
396 37383 : ["Metering Mode", "MeteringMode"],
397 37384 : ["Light Source", "LightSource"],
398 37385 : ["Flash", "Flash"],
399 37386 : ["Focal Length", "FocalLength"],
400 37396 : ["Subject Area", "SubjectArea"],
401 41483 : ["Flash Energy", "FlashEnergy"],
402 41484 : ["Spatial Frequency Response", "SpatialFrequencyResponse"],
403 41486 : ["Focal Plane X Resolution", "FocalPlaneXResolution"],
404 41487 : ["Focal Plane Y Resolution", "FocalPlaneYResolution"],
405 41488 : ["Focal Plane Resolution Unit", "FocalPlaneResolutionUnit"],
406 41492 : ["Subject Location", "SubjectLocation"],
407 41493 : ["Exposure Index", "ExposureIndex"],
408 41495 : ["Sensing Method", "SensingMethod"],
409 41728 : ["File Source", "FileSource"],
410 41729 : ["Scene Type", "SceneType"],
411 41730 : ["CFA Pattern", "CFAPattern"],
412 41985 : ["Custom Rendered", "CustomRendered"],
413 41986 : ["Exposure Mode", "Exposure Mode"],
414 41987 : ["White Balance", "WhiteBalance"],
415 41988 : ["Digital Zoom Ratio", "DigitalZoomRatio"],
416 41990 : ["Scene Capture Type", "SceneCaptureType"],
417 41991 : ["Gain Control", "GainControl"],
418 41992 : ["Contrast", "Contrast"],
419 41993 : ["Saturation", "Saturation"],
420 41994 : ["Sharpness", "Sharpness"],
421 41995 : ["Device settings description", "DeviceSettingDescription"],
422 41996 : ["Subject distance range", "SubjectDistanceRange"],
425 42016 : ["Unique image ID", "ImageUniqueID"],
427 40965 : ["Interoperability tag", "InteroperabilityIFDPointer"]
430 this.JpegMeta.JpegFile.prototype._gpstags = {
431 /* A. Tags Relating to GPS */
432 0 : ["GPS tag version", "GPSVersionID"],
433 1 : ["North or South Latitude", "GPSLatitudeRef"],
434 2 : ["Latitude", "GPSLatitude"],
435 3 : ["East or West Longitude", "GPSLongitudeRef"],
436 4 : ["Longitude", "GPSLongitude"],
437 5 : ["Altitude reference", "GPSAltitudeRef"],
438 6 : ["Altitude", "GPSAltitude"],
439 7 : ["GPS time (atomic clock)", "GPSTimeStamp"],
440 8 : ["GPS satellites usedd for measurement", "GPSSatellites"],
441 9 : ["GPS receiver status", "GPSStatus"],
442 10 : ["GPS mesaurement mode", "GPSMeasureMode"],
443 11 : ["Measurement precision", "GPSDOP"],
444 12 : ["Speed unit", "GPSSpeedRef"],
445 13 : ["Speed of GPS receiver", "GPSSpeed"],
446 14 : ["Reference for direction of movement", "GPSTrackRef"],
447 15 : ["Direction of movement", "GPSTrack"],
448 16 : ["Reference for direction of image", "GPSImgDirectionRef"],
449 17 : ["Direction of image", "GPSImgDirection"],
450 18 : ["Geodetic survey data used", "GPSMapDatum"],
451 19 : ["Reference for latitude of destination", "GPSDestLatitudeRef"],
452 20 : ["Latitude of destination", "GPSDestLatitude"],
453 21 : ["Reference for longitude of destination", "GPSDestLongitudeRef"],
454 22 : ["Longitude of destination", "GPSDestLongitude"],
455 23 : ["Reference for bearing of destination", "GPSDestBearingRef"],
456 24 : ["Bearing of destination", "GPSDestBearing"],
457 25 : ["Reference for distance to destination", "GPSDestDistanceRef"],
458 26 : ["Distance to destination", "GPSDestDistance"],
459 27 : ["Name of GPS processing method", "GPSProcessingMethod"],
460 28 : ["Name of GPS area", "GPSAreaInformation"],
461 29 : ["GPS Date", "GPSDateStamp"],
462 30 : ["GPS differential correction", "GPSDifferential"]
465 this.JpegMeta.JpegFile.prototype._markers = {
466 /* Start Of Frame markers, non-differential, Huffman coding */
467 0xc0: ["SOF0", "_sofHandler", "Baseline DCT"],
468 0xc1: ["SOF1", "_sofHandler", "Extended sequential DCT"],
469 0xc2: ["SOF2", "_sofHandler", "Progressive DCT"],
470 0xc3: ["SOF3", "_sofHandler", "Lossless (sequential)"],
472 /* Start Of Frame markers, differential, Huffman coding */
473 0xc5: ["SOF5", "_sofHandler", "Differential sequential DCT"],
474 0xc6: ["SOF6", "_sofHandler", "Differential progressive DCT"],
475 0xc7: ["SOF7", "_sofHandler", "Differential lossless (sequential)"],
477 /* Start Of Frame markers, non-differential, arithmetic coding */
478 0xc8: ["JPG", null, "Reserved for JPEG extensions"],
479 0xc9: ["SOF9", "_sofHandler", "Extended sequential DCT"],
480 0xca: ["SOF10", "_sofHandler", "Progressive DCT"],
481 0xcb: ["SOF11", "_sofHandler", "Lossless (sequential)"],
483 /* Start Of Frame markers, differential, arithmetic coding */
484 0xcd: ["SOF13", "_sofHandler", "Differential sequential DCT"],
485 0xce: ["SOF14", "_sofHandler", "Differential progressive DCT"],
486 0xcf: ["SOF15", "_sofHandler", "Differential lossless (sequential)"],
488 /* Huffman table specification */
489 0xc4: ["DHT", null, "Define Huffman table(s)"],
490 0xcc: ["DAC", null, "Define arithmetic coding conditioning(s)"],
492 /* Restart interval termination" */
493 0xd0: ["RST0", null, "Restart with modulo 8 count “0”"],
494 0xd1: ["RST1", null, "Restart with modulo 8 count “1”"],
495 0xd2: ["RST2", null, "Restart with modulo 8 count “2”"],
496 0xd3: ["RST3", null, "Restart with modulo 8 count “3”"],
497 0xd4: ["RST4", null, "Restart with modulo 8 count “4”"],
498 0xd5: ["RST5", null, "Restart with modulo 8 count “5”"],
499 0xd6: ["RST6", null, "Restart with modulo 8 count “6”"],
500 0xd7: ["RST7", null, "Restart with modulo 8 count “7”"],
503 0xd8: ["SOI", null, "Start of image"],
504 0xd9: ["EOI", null, "End of image"],
505 0xda: ["SOS", null, "Start of scan"],
506 0xdb: ["DQT", null, "Define quantization table(s)"],
507 0xdc: ["DNL", null, "Define number of lines"],
508 0xdd: ["DRI", null, "Define restart interval"],
509 0xde: ["DHP", null, "Define hierarchical progression"],
510 0xdf: ["EXP", null, "Expand reference component(s)"],
511 0xe0: ["APP0", "_app0Handler", "Reserved for application segments"],
512 0xe1: ["APP1", "_app1Handler"],
513 0xe2: ["APP2", null],
514 0xe3: ["APP3", null],
515 0xe4: ["APP4", null],
516 0xe5: ["APP5", null],
517 0xe6: ["APP6", null],
518 0xe7: ["APP7", null],
519 0xe8: ["APP8", null],
520 0xe9: ["APP9", null],
521 0xea: ["APP10", null],
522 0xeb: ["APP11", null],
523 0xec: ["APP12", null],
524 0xed: ["APP13", null],
525 0xee: ["APP14", null],
526 0xef: ["APP15", null],
527 0xf0: ["JPG0", null], /* Reserved for JPEG extensions */
528 0xf1: ["JPG1", null],
529 0xf2: ["JPG2", null],
530 0xf3: ["JPG3", null],
531 0xf4: ["JPG4", null],
532 0xf5: ["JPG5", null],
533 0xf6: ["JPG6", null],
534 0xf7: ["JPG7", null],
535 0xf8: ["JPG8", null],
536 0xf9: ["JPG9", null],
537 0xfa: ["JPG10", null],
538 0xfb: ["JPG11", null],
539 0xfc: ["JPG12", null],
540 0xfd: ["JPG13", null],
541 0xfe: ["COM", null], /* Comment */
543 /* Reserved markers */
544 0x01: ["JPG13", null] /* For temporary private use in arithmetic coding */
545 /* 02 -> bf are reserverd */
548 /* Private methods */
549 this.JpegMeta.JpegFile.prototype._addMetaGroup = function _addMetaGroup(name, description) {
550 var group = new JpegMeta.MetaGroup(name, description);
551 this[group.fieldName] = group;
552 this.metaGroups[group.fieldName] = group;
556 this.JpegMeta.JpegFile.prototype._parseIfd = function _parseIfd(endian, _binary_data, base, ifd_offset, tags, name, description) {
557 var num_fields = JpegMeta.parseNum(endian, _binary_data, base + ifd_offset, 2);
558 /* Per tag variables */
562 var type, type_field, type_size;
572 group = this._addMetaGroup(name, description);
574 for (var i = 0; i < num_fields; i++) {
575 /* parse the field */
576 tag_base = base + ifd_offset + 2 + (i * 12);
577 tag_field = JpegMeta.parseNum(endian, _binary_data, tag_base, 2);
578 type_field = JpegMeta.parseNum(endian, _binary_data, tag_base + 2, 2);
579 num_values = JpegMeta.parseNum(endian, _binary_data, tag_base + 4, 4);
580 value_offset = JpegMeta.parseNum(endian, _binary_data, tag_base + 8, 4);
581 if (this._types[type_field] === undefined) {
584 type = this._types[type_field][0];
585 type_size = this._types[type_field][1];
587 if (type_size * num_values <= 4) {
588 /* Data is in-line */
589 value_offset = tag_base + 8;
591 value_offset = base + value_offset;
595 if (type == "UNDEFINED") {
596 value = _binary_data.slice(value_offset, value_offset + num_values);
597 } else if (type == "ASCII") {
598 value = _binary_data.slice(value_offset, value_offset + num_values);
599 value = value.split('\x00')[0];
600 /* strip trail nul */
603 for (j = 0; j < num_values; j++, value_offset += type_size) {
604 if (type == "BYTE" || type == "SHORT" || type == "LONG") {
605 value.push(JpegMeta.parseNum(endian, _binary_data, value_offset, type_size));
607 if (type == "SBYTE" || type == "SSHORT" || type == "SLONG") {
608 value.push(JpegMeta.parseSnum(endian, _binary_data, value_offset, type_size));
610 if (type == "RATIONAL") {
611 num = JpegMeta.parseNum(endian, _binary_data, value_offset, 4);
612 den = JpegMeta.parseNum(endian, _binary_data, value_offset + 4, 4);
613 value.push(new JpegMeta.Rational(num, den));
615 if (type == "SRATIONAL") {
616 num = JpegMeta.parseSnum(endian, _binary_data, value_offset, 4);
617 den = JpegMeta.parseSnum(endian, _binary_data, value_offset + 4, 4);
618 value.push(new JpegMeta.Rational(num, den));
622 if (num_values === 1) {
626 if (tags[tag_field] !== undefined) {
627 group._addProperty(tags[tag_field][1], tags[tag_field][0], value);
632 this.JpegMeta.JpegFile.prototype._jfifHandler = function _jfifHandler(mark, pos) {
633 if (this.jfif !== undefined) {
634 throw Error("Multiple JFIF segments found");
636 this._addMetaGroup("jfif", "JFIF");
637 this.jfif._addProperty("version_major", "Version Major", this._binary_data.charCodeAt(pos + 5));
638 this.jfif._addProperty("version_minor", "Version Minor", this._binary_data.charCodeAt(pos + 6));
639 this.jfif._addProperty("version", "JFIF Version", this.jfif.version_major.value + "." + this.jfif.version_minor.value);
640 this.jfif._addProperty("units", "Density Unit", this._binary_data.charCodeAt(pos + 7));
641 this.jfif._addProperty("Xdensity", "X density", JpegMeta.parseNum(">", this._binary_data, pos + 8, 2));
642 this.jfif._addProperty("Ydensity", "Y Density", JpegMeta.parseNum(">", this._binary_data, pos + 10, 2));
643 this.jfif._addProperty("Xthumbnail", "X Thumbnail", JpegMeta.parseNum(">", this._binary_data, pos + 12, 1));
644 this.jfif._addProperty("Ythumbnail", "Y Thumbnail", JpegMeta.parseNum(">", this._binary_data, pos + 13, 1));
647 /* Handle app0 segments */
648 this.JpegMeta.JpegFile.prototype._app0Handler = function app0Handler(mark, pos) {
649 var ident = this._binary_data.slice(pos, pos + 5);
650 if (ident == this._JFIF_IDENT) {
651 this._jfifHandler(mark, pos);
652 } else if (ident == this._JFXX_IDENT) {
653 /* Don't handle JFXX Ident yet */
655 /* Don't know about other idents */
659 /* Handle app1 segments */
660 this.JpegMeta.JpegFile.prototype._app1Handler = function _app1Handler(mark, pos) {
661 var ident = this._binary_data.slice(pos, pos + 5);
662 if (ident == this._EXIF_IDENT) {
663 this._exifHandler(mark, pos + 6);
665 /* Don't know about other idents */
669 /* Handle exif segments */
670 JpegMeta.JpegFile.prototype._exifHandler = function _exifHandler(mark, pos) {
671 if (this.exif !== undefined) {
672 throw new Error("Multiple JFIF segments found");
675 /* Parse this TIFF header */
679 var primary_ifd, exif_ifd, gps_ifd;
680 var endian_field = this._binary_data.slice(pos, pos + 2);
682 /* Trivia: This 'I' is for Intel, the 'M' is for Motorola */
683 if (endian_field === "II") {
685 } else if (endian_field === "MM") {
688 throw new Error("Malformed TIFF meta-data. Unknown endianess: " + endian_field);
691 magic_field = JpegMeta.parseNum(endian, this._binary_data, pos + 2, 2);
693 if (magic_field !== 42) {
694 throw new Error("Malformed TIFF meta-data. Bad magic: " + magic_field);
697 ifd_offset = JpegMeta.parseNum(endian, this._binary_data, pos + 4, 4);
700 this._parseIfd(endian, this._binary_data, pos, ifd_offset, this._tifftags, "tiff", "TIFF");
702 if (this.tiff.ExifIfdPointer) {
703 this._parseIfd(endian, this._binary_data, pos, this.tiff.ExifIfdPointer.value, this._exiftags, "exif", "Exif");
706 if (this.tiff.GPSInfoIfdPointer) {
707 this._parseIfd(endian, this._binary_data, pos, this.tiff.GPSInfoIfdPointer.value, this._gpstags, "gps", "GPS");
708 if (this.gps.GPSLatitude) {
710 latitude = this.gps.GPSLatitude.value[0].asFloat() +
711 (1 / 60) * this.gps.GPSLatitude.value[1].asFloat() +
712 (1 / 3600) * this.gps.GPSLatitude.value[2].asFloat();
713 if (this.gps.GPSLatitudeRef.value === "S") {
714 latitude = -latitude;
716 this.gps._addProperty("latitude", "Dec. Latitude", latitude);
718 if (this.gps.GPSLongitude) {
720 longitude = this.gps.GPSLongitude.value[0].asFloat() +
721 (1 / 60) * this.gps.GPSLongitude.value[1].asFloat() +
722 (1 / 3600) * this.gps.GPSLongitude.value[2].asFloat();
723 if (this.gps.GPSLongitudeRef.value === "W") {
724 longitude = -longitude;
726 this.gps._addProperty("longitude", "Dec. Longitude", longitude);