• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Javascript EXIF Reader 0.1.2
3 * Copyright (c) 2008 Jacob Seidelin, cupboy@gmail.com, http://blog.nihilogic.dk/
4 * MIT License [http://www.opensource.org/licenses/mit-license.php]
5 */
6
7
8var EXIF = {};
9
10(function() {
11
12var bDebug = false;
13
14EXIF.Tags = {
15
16  // version tags
17  0x9000 : "ExifVersion",			// EXIF version
18  0xA000 : "FlashpixVersion",		// Flashpix format version
19
20  // colorspace tags
21  0xA001 : "ColorSpace",			// Color space information tag
22
23  // image configuration
24  0xA002 : "PixelXDimension",		// Valid width of meaningful image
25  0xA003 : "PixelYDimension",		// Valid height of meaningful image
26  0x9101 : "ComponentsConfiguration",	// Information about channels
27  0x9102 : "CompressedBitsPerPixel",	// Compressed bits per pixel
28
29  // user information
30  0x927C : "MakerNote",			// Any desired information written by the manufacturer
31  0x9286 : "UserComment",			// Comments by user
32
33  // related file
34  0xA004 : "RelatedSoundFile",		// Name of related sound file
35
36  // date and time
37  0x9003 : "DateTimeOriginal",		// Date and time when the original image was generated
38  0x9004 : "DateTimeDigitized",		// Date and time when the image was stored digitally
39  0x9290 : "SubsecTime",			// Fractions of seconds for DateTime
40  0x9291 : "SubsecTimeOriginal",		// Fractions of seconds for DateTimeOriginal
41  0x9292 : "SubsecTimeDigitized",		// Fractions of seconds for DateTimeDigitized
42
43  // picture-taking conditions
44  0x829A : "ExposureTime",		// Exposure time (in seconds)
45  0x829D : "FNumber",			// F number
46  0x8822 : "ExposureProgram",		// Exposure program
47  0x8824 : "SpectralSensitivity",		// Spectral sensitivity
48  0x8827 : "ISOSpeedRatings",		// ISO speed rating
49  0x8828 : "OECF",			// Optoelectric conversion factor
50  0x9201 : "ShutterSpeedValue",		// Shutter speed
51  0x9202 : "ApertureValue",		// Lens aperture
52  0x9203 : "BrightnessValue",		// Value of brightness
53  0x9204 : "ExposureBias",		// Exposure bias
54  0x9205 : "MaxApertureValue",		// Smallest F number of lens
55  0x9206 : "SubjectDistance",		// Distance to subject in meters
56  0x9207 : "MeteringMode", 		// Metering mode
57  0x9208 : "LightSource",			// Kind of light source
58  0x9209 : "Flash",			// Flash status
59  0x9214 : "SubjectArea",			// Location and area of main subject
60  0x920A : "FocalLength",			// Focal length of the lens in mm
61  0xA20B : "FlashEnergy",			// Strobe energy in BCPS
62  0xA20C : "SpatialFrequencyResponse",	//
63  0xA20E : "FocalPlaneXResolution", 	// Number of pixels in width direction per FocalPlaneResolutionUnit
64  0xA20F : "FocalPlaneYResolution", 	// Number of pixels in height direction per FocalPlaneResolutionUnit
65  0xA210 : "FocalPlaneResolutionUnit", 	// Unit for measuring FocalPlaneXResolution and FocalPlaneYResolution
66  0xA214 : "SubjectLocation",		// Location of subject in image
67  0xA215 : "ExposureIndex",		// Exposure index selected on camera
68  0xA217 : "SensingMethod", 		// Image sensor type
69  0xA300 : "FileSource", 			// Image source (3 == DSC)
70  0xA301 : "SceneType", 			// Scene type (1 == directly photographed)
71  0xA302 : "CFAPattern",			// Color filter array geometric pattern
72  0xA401 : "CustomRendered",		// Special processing
73  0xA402 : "ExposureMode",		// Exposure mode
74  0xA403 : "WhiteBalance",		// 1 = auto white balance, 2 = manual
75  0xA404 : "DigitalZoomRation",		// Digital zoom ratio
76  0xA405 : "FocalLengthIn35mmFilm",	// Equivalent foacl length assuming 35mm film camera (in mm)
77  0xA406 : "SceneCaptureType",		// Type of scene
78  0xA407 : "GainControl",			// Degree of overall image gain adjustment
79  0xA408 : "Contrast",			// Direction of contrast processing applied by camera
80  0xA409 : "Saturation", 			// Direction of saturation processing applied by camera
81  0xA40A : "Sharpness",			// Direction of sharpness processing applied by camera
82  0xA40B : "DeviceSettingDescription",	//
83  0xA40C : "SubjectDistanceRange",	// Distance to subject
84
85  // other tags
86  0xA005 : "InteroperabilityIFDPointer",
87  0xA420 : "ImageUniqueID"		// Identifier assigned uniquely to each image
88};
89
90EXIF.TiffTags = {
91  0x0100 : "ImageWidth",
92  0x0101 : "ImageHeight",
93  0x8769 : "ExifIFDPointer",
94  0x8825 : "GPSInfoIFDPointer",
95  0xA005 : "InteroperabilityIFDPointer",
96  0x0102 : "BitsPerSample",
97  0x0103 : "Compression",
98  0x0106 : "PhotometricInterpretation",
99  0x0112 : "Orientation",
100  0x0115 : "SamplesPerPixel",
101  0x011C : "PlanarConfiguration",
102  0x0212 : "YCbCrSubSampling",
103  0x0213 : "YCbCrPositioning",
104  0x011A : "XResolution",
105  0x011B : "YResolution",
106  0x0128 : "ResolutionUnit",
107  0x0111 : "StripOffsets",
108  0x0116 : "RowsPerStrip",
109  0x0117 : "StripByteCounts",
110  0x0201 : "JPEGInterchangeFormat",
111  0x0202 : "JPEGInterchangeFormatLength",
112  0x012D : "TransferFunction",
113  0x013E : "WhitePoint",
114  0x013F : "PrimaryChromaticities",
115  0x0211 : "YCbCrCoefficients",
116  0x0214 : "ReferenceBlackWhite",
117  0x0132 : "DateTime",
118  0x010E : "ImageDescription",
119  0x010F : "Make",
120  0x0110 : "Model",
121  0x0131 : "Software",
122  0x013B : "Artist",
123  0x8298 : "Copyright"
124}
125
126EXIF.GPSTags = {
127  0x0000 : "GPSVersionID",
128  0x0001 : "GPSLatitudeRef",
129  0x0002 : "GPSLatitude",
130  0x0003 : "GPSLongitudeRef",
131  0x0004 : "GPSLongitude",
132  0x0005 : "GPSAltitudeRef",
133  0x0006 : "GPSAltitude",
134  0x0007 : "GPSTimeStamp",
135  0x0008 : "GPSSatellites",
136  0x0009 : "GPSStatus",
137  0x000A : "GPSMeasureMode",
138  0x000B : "GPSDOP",
139  0x000C : "GPSSpeedRef",
140  0x000D : "GPSSpeed",
141  0x000E : "GPSTrackRef",
142  0x000F : "GPSTrack",
143  0x0010 : "GPSImgDirectionRef",
144  0x0011 : "GPSImgDirection",
145  0x0012 : "GPSMapDatum",
146  0x0013 : "GPSDestLatitudeRef",
147  0x0014 : "GPSDestLatitude",
148  0x0015 : "GPSDestLongitudeRef",
149  0x0016 : "GPSDestLongitude",
150  0x0017 : "GPSDestBearingRef",
151  0x0018 : "GPSDestBearing",
152  0x0019 : "GPSDestDistanceRef",
153  0x001A : "GPSDestDistance",
154  0x001B : "GPSProcessingMethod",
155  0x001C : "GPSAreaInformation",
156  0x001D : "GPSDateStamp",
157  0x001E : "GPSDifferential"
158}
159
160EXIF.StringValues = {
161  ExposureProgram : {
162    0 : "Not defined",
163    1 : "Manual",
164    2 : "Normal program",
165    3 : "Aperture priority",
166    4 : "Shutter priority",
167    5 : "Creative program",
168    6 : "Action program",
169    7 : "Portrait mode",
170    8 : "Landscape mode"
171  },
172  MeteringMode : {
173    0 : "Unknown",
174    1 : "Average",
175    2 : "CenterWeightedAverage",
176    3 : "Spot",
177    4 : "MultiSpot",
178    5 : "Pattern",
179    6 : "Partial",
180    255 : "Other"
181  },
182  LightSource : {
183    0 : "Unknown",
184    1 : "Daylight",
185    2 : "Fluorescent",
186    3 : "Tungsten (incandescent light)",
187    4 : "Flash",
188    9 : "Fine weather",
189    10 : "Cloudy weather",
190    11 : "Shade",
191    12 : "Daylight fluorescent (D 5700 - 7100K)",
192    13 : "Day white fluorescent (N 4600 - 5400K)",
193    14 : "Cool white fluorescent (W 3900 - 4500K)",
194    15 : "White fluorescent (WW 3200 - 3700K)",
195    17 : "Standard light A",
196    18 : "Standard light B",
197    19 : "Standard light C",
198    20 : "D55",
199    21 : "D65",
200    22 : "D75",
201    23 : "D50",
202    24 : "ISO studio tungsten",
203    255 : "Other"
204  },
205  Flash : {
206    0x0000 : "Flash did not fire",
207    0x0001 : "Flash fired",
208    0x0005 : "Strobe return light not detected",
209    0x0007 : "Strobe return light detected",
210    0x0009 : "Flash fired, compulsory flash mode",
211    0x000D : "Flash fired, compulsory flash mode, return light not detected",
212    0x000F : "Flash fired, compulsory flash mode, return light detected",
213    0x0010 : "Flash did not fire, compulsory flash mode",
214    0x0018 : "Flash did not fire, auto mode",
215    0x0019 : "Flash fired, auto mode",
216    0x001D : "Flash fired, auto mode, return light not detected",
217    0x001F : "Flash fired, auto mode, return light detected",
218    0x0020 : "No flash function",
219    0x0041 : "Flash fired, red-eye reduction mode",
220    0x0045 : "Flash fired, red-eye reduction mode, return light not detected",
221    0x0047 : "Flash fired, red-eye reduction mode, return light detected",
222    0x0049 : "Flash fired, compulsory flash mode, red-eye reduction mode",
223    0x004D : "Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected",
224    0x004F : "Flash fired, compulsory flash mode, red-eye reduction mode, return light detected",
225    0x0059 : "Flash fired, auto mode, red-eye reduction mode",
226    0x005D : "Flash fired, auto mode, return light not detected, red-eye reduction mode",
227    0x005F : "Flash fired, auto mode, return light detected, red-eye reduction mode"
228  },
229  SensingMethod : {
230    1 : "Not defined",
231    2 : "One-chip color area sensor",
232    3 : "Two-chip color area sensor",
233    4 : "Three-chip color area sensor",
234    5 : "Color sequential area sensor",
235    7 : "Trilinear sensor",
236    8 : "Color sequential linear sensor"
237  },
238  SceneCaptureType : {
239    0 : "Standard",
240    1 : "Landscape",
241    2 : "Portrait",
242    3 : "Night scene"
243  },
244  SceneType : {
245    1 : "Directly photographed"
246  },
247  CustomRendered : {
248    0 : "Normal process",
249    1 : "Custom process"
250  },
251  WhiteBalance : {
252    0 : "Auto white balance",
253    1 : "Manual white balance"
254  },
255  GainControl : {
256    0 : "None",
257    1 : "Low gain up",
258    2 : "High gain up",
259    3 : "Low gain down",
260    4 : "High gain down"
261  },
262  Contrast : {
263    0 : "Normal",
264    1 : "Soft",
265    2 : "Hard"
266  },
267  Saturation : {
268    0 : "Normal",
269    1 : "Low saturation",
270    2 : "High saturation"
271  },
272  Sharpness : {
273    0 : "Normal",
274    1 : "Soft",
275    2 : "Hard"
276  },
277  SubjectDistanceRange : {
278    0 : "Unknown",
279    1 : "Macro",
280    2 : "Close view",
281    3 : "Distant view"
282  },
283  FileSource : {
284    3 : "DSC"
285  },
286
287  Components : {
288    0 : "",
289    1 : "Y",
290    2 : "Cb",
291    3 : "Cr",
292    4 : "R",
293    5 : "G",
294    6 : "B"
295  }
296}
297
298function addEvent(oElement, strEvent, fncHandler)
299{
300  if (oElement.addEventListener) {
301    oElement.addEventListener(strEvent, fncHandler, false);
302  } else if (oElement.attachEvent) {
303    oElement.attachEvent("on" + strEvent, fncHandler);
304  }
305}
306
307
308function imageHasData(oImg)
309{
310  return !!(oImg.exifdata);
311}
312
313function getImageData(oImg, fncCallback)
314{
315  BinaryAjax(
316    oImg.src,
317    function(oHTTP) {
318      var oEXIF = findEXIFinJPEG(oHTTP.binaryResponse);
319      oImg.exifdata = oEXIF || {};
320      if (fncCallback) fncCallback();
321    }
322  )
323}
324
325function findEXIFinJPEG(oFile) {
326  var aMarkers = [];
327
328  if (oFile.getByteAt(0) != 0xFF || oFile.getByteAt(1) != 0xD8) {
329    return false; // not a valid jpeg
330  }
331
332  var iOffset = 2;
333  var iLength = oFile.getLength();
334  while (iOffset < iLength) {
335    if (oFile.getByteAt(iOffset) != 0xFF) {
336      if (bDebug) console.log("Not a valid marker at offset " + iOffset + ", found: " + oFile.getByteAt(iOffset));
337      return false; // not a valid marker, something is wrong
338    }
339
340    var iMarker = oFile.getByteAt(iOffset+1);
341
342    // we could implement handling for other markers here,
343    // but we're only looking for 0xFFE1 for EXIF data
344
345    if (iMarker == 22400) {
346      if (bDebug) console.log("Found 0xFFE1 marker");
347      return readEXIFData(oFile, iOffset + 4, oFile.getShortAt(iOffset+2, true)-2);
348      iOffset += 2 + oFile.getShortAt(iOffset+2, true);
349
350    } else if (iMarker == 225) {
351      // 0xE1 = Application-specific 1 (for EXIF)
352      if (bDebug) console.log("Found 0xFFE1 marker");
353      return readEXIFData(oFile, iOffset + 4, oFile.getShortAt(iOffset+2, true)-2);
354
355    } else {
356      iOffset += 2 + oFile.getShortAt(iOffset+2, true);
357    }
358
359  }
360
361}
362
363
364function readTags(oFile, iTIFFStart, iDirStart, oStrings, bBigEnd)
365{
366  var iEntries = oFile.getShortAt(iDirStart, bBigEnd);
367  var oTags = {};
368  for (var i=0;i<iEntries;i++) {
369    var iEntryOffset = iDirStart + i*12 + 2;
370    var strTag = oStrings[oFile.getShortAt(iEntryOffset, bBigEnd)];
371    if (!strTag && bDebug) console.log("Unknown tag: " + oFile.getShortAt(iEntryOffset, bBigEnd));
372    oTags[strTag] = readTagValue(oFile, iEntryOffset, iTIFFStart, iDirStart, bBigEnd);
373  }
374  return oTags;
375}
376
377
378function readTagValue(oFile, iEntryOffset, iTIFFStart, iDirStart, bBigEnd)
379{
380  var iType = oFile.getShortAt(iEntryOffset+2, bBigEnd);
381  var iNumValues = oFile.getLongAt(iEntryOffset+4, bBigEnd);
382  var iValueOffset = oFile.getLongAt(iEntryOffset+8, bBigEnd) + iTIFFStart;
383
384  switch (iType) {
385    case 1: // byte, 8-bit unsigned int
386    case 7: // undefined, 8-bit byte, value depending on field
387      if (iNumValues == 1) {
388        return oFile.getByteAt(iEntryOffset + 8, bBigEnd);
389      } else {
390        var iValOffset = iNumValues > 4 ? iValueOffset : (iEntryOffset + 8);
391        var aVals = [];
392        for (var n=0;n<iNumValues;n++) {
393          aVals[n] = oFile.getByteAt(iValOffset + n);
394        }
395        return aVals;
396      }
397      break;
398
399    case 2: // ascii, 8-bit byte
400      var iStringOffset = iNumValues > 4 ? iValueOffset : (iEntryOffset + 8);
401      return oFile.getStringAt(iStringOffset, iNumValues-1);
402      break;
403
404    case 3: // short, 16 bit int
405      if (iNumValues == 1) {
406        return oFile.getShortAt(iEntryOffset + 8, bBigEnd);
407      } else {
408        var iValOffset = iNumValues > 2 ? iValueOffset : (iEntryOffset + 8);
409        var aVals = [];
410        for (var n=0;n<iNumValues;n++) {
411          aVals[n] = oFile.getShortAt(iValOffset + 2*n, bBigEnd);
412        }
413        return aVals;
414      }
415      break;
416
417    case 4: // long, 32 bit int
418      if (iNumValues == 1) {
419        return oFile.getLongAt(iEntryOffset + 8, bBigEnd);
420      } else {
421        var aVals = [];
422        for (var n=0;n<iNumValues;n++) {
423          aVals[n] = oFile.getLongAt(iValueOffset + 4*n, bBigEnd);
424        }
425        return aVals;
426      }
427      break;
428    case 5:	// rational = two long values, first is numerator, second is denominator
429      if (iNumValues == 1) {
430        return oFile.getLongAt(iValueOffset, bBigEnd) / oFile.getLongAt(iValueOffset+4, bBigEnd);
431      } else {
432        var aVals = [];
433        for (var n=0;n<iNumValues;n++) {
434          aVals[n] = oFile.getLongAt(iValueOffset + 8*n, bBigEnd) / oFile.getLongAt(iValueOffset+4 + 8*n, bBigEnd);
435        }
436        return aVals;
437      }
438      break;
439    case 9: // slong, 32 bit signed int
440      if (iNumValues == 1) {
441        return oFile.getSLongAt(iEntryOffset + 8, bBigEnd);
442      } else {
443        var aVals = [];
444        for (var n=0;n<iNumValues;n++) {
445          aVals[n] = oFile.getSLongAt(iValueOffset + 4*n, bBigEnd);
446        }
447        return aVals;
448      }
449      break;
450    case 10: // signed rational, two slongs, first is numerator, second is denominator
451      if (iNumValues == 1) {
452        return oFile.getSLongAt(iValueOffset, bBigEnd) / oFile.getSLongAt(iValueOffset+4, bBigEnd);
453      } else {
454        var aVals = [];
455        for (var n=0;n<iNumValues;n++) {
456          aVals[n] = oFile.getSLongAt(iValueOffset + 8*n, bBigEnd) / oFile.getSLongAt(iValueOffset+4 + 8*n, bBigEnd);
457        }
458        return aVals;
459      }
460      break;
461  }
462}
463
464
465function readEXIFData(oFile, iStart, iLength)
466{
467  if (oFile.getStringAt(iStart, 4) != "Exif") {
468    if (bDebug) console.log("Not valid EXIF data! " + oFile.getStringAt(iStart, 4));
469    return false;
470  }
471
472  var bBigEnd;
473
474  var iTIFFOffset = iStart + 6;
475
476  // test for TIFF validity and endianness
477  if (oFile.getShortAt(iTIFFOffset) == 0x4949) {
478    bBigEnd = false;
479  } else if (oFile.getShortAt(iTIFFOffset) == 0x4D4D) {
480    bBigEnd = true;
481  } else {
482    if (bDebug) console.log("Not valid TIFF data! (no 0x4949 or 0x4D4D)");
483    return false;
484  }
485
486  if (oFile.getShortAt(iTIFFOffset+2, bBigEnd) != 0x002A) {
487    if (bDebug) console.log("Not valid TIFF data! (no 0x002A)");
488    return false;
489  }
490
491  if (oFile.getLongAt(iTIFFOffset+4, bBigEnd) != 0x00000008) {
492    if (bDebug) console.log("Not valid TIFF data! (First offset not 8)", oFile.getShortAt(iTIFFOffset+4, bBigEnd));
493    return false;
494  }
495
496  var oTags = readTags(oFile, iTIFFOffset, iTIFFOffset+8, EXIF.TiffTags, bBigEnd);
497
498  if (oTags.ExifIFDPointer) {
499    var oEXIFTags = readTags(oFile, iTIFFOffset, iTIFFOffset + oTags.ExifIFDPointer, EXIF.Tags, bBigEnd);
500    for (var strTag in oEXIFTags) {
501      switch (strTag) {
502        case "LightSource" :
503        case "Flash" :
504        case "MeteringMode" :
505        case "ExposureProgram" :
506        case "SensingMethod" :
507        case "SceneCaptureType" :
508        case "SceneType" :
509        case "CustomRendered" :
510        case "WhiteBalance" :
511        case "GainControl" :
512        case "Contrast" :
513        case "Saturation" :
514        case "Sharpness" :
515        case "SubjectDistanceRange" :
516        case "FileSource" :
517          oEXIFTags[strTag] = EXIF.StringValues[strTag][oEXIFTags[strTag]];
518          break;
519
520        case "ExifVersion" :
521        case "FlashpixVersion" :
522          oEXIFTags[strTag] = String.fromCharCode(oEXIFTags[strTag][0], oEXIFTags[strTag][1], oEXIFTags[strTag][2], oEXIFTags[strTag][3]);
523          break;
524
525        case "ComponentsConfiguration" :
526          oEXIFTags[strTag] =
527            EXIF.StringValues.Components[oEXIFTags[strTag][0]]
528            + EXIF.StringValues.Components[oEXIFTags[strTag][1]]
529            + EXIF.StringValues.Components[oEXIFTags[strTag][2]]
530            + EXIF.StringValues.Components[oEXIFTags[strTag][3]];
531          break;
532      }
533      oTags[strTag] = oEXIFTags[strTag];
534    }
535  }
536
537  if (oTags.GPSInfoIFDPointer) {
538    var oGPSTags = readTags(oFile, iTIFFOffset, iTIFFOffset + oTags.GPSInfoIFDPointer, EXIF.GPSTags, bBigEnd);
539    for (var strTag in oGPSTags) {
540      switch (strTag) {
541        case "GPSVersionID" :
542          oGPSTags[strTag] = oGPSTags[strTag][0]
543            + "." + oGPSTags[strTag][1]
544            + "." + oGPSTags[strTag][2]
545            + "." + oGPSTags[strTag][3];
546          break;
547      }
548      oTags[strTag] = oGPSTags[strTag];
549    }
550  }
551
552  return oTags;
553}
554
555
556EXIF.getData = function(oImg, fncCallback)
557{
558  if (!oImg.complete) return false;
559  if (!imageHasData(oImg)) {
560    getImageData(oImg, fncCallback);
561  } else {
562    if (fncCallback) fncCallback();
563  }
564  return true;
565}
566
567EXIF.getTag = function(oImg, strTag)
568{
569  if (!imageHasData(oImg)) return;
570  return oImg.exifdata[strTag];
571}
572
573EXIF.pretty = function(oImg)
574{
575  if (!imageHasData(oImg)) return "";
576  var oData = oImg.exifdata;
577  var strPretty = "";
578  for (var a in oData) {
579    if (oData.hasOwnProperty(a)) {
580      if (typeof oData[a] == "object") {
581        strPretty += a + " : [" + oData[a].length + " values]\r\n";
582      } else {
583        strPretty += a + " : " + oData[a] + "\r\n";
584      }
585    }
586  }
587  return strPretty;
588}
589
590EXIF.readFromBinaryFile = function(oFile) {
591  return findEXIFinJPEG(oFile);
592}
593
594function loadAllImages()
595{
596  var aImages = document.getElementsByTagName("img");
597  for (var i=0;i<aImages.length;i++) {
598    if (aImages[i].getAttribute("exif") == "true") {
599      if (!aImages[i].complete) {
600        addEvent(aImages[i], "load",
601          function() {
602            EXIF.getData(this);
603          }
604        );
605      } else {
606        EXIF.getData(aImages[i]);
607      }
608    }
609  }
610}
611
612addEvent(window, "load", loadAllImages);
613
614})();
615
616