text: 
  /*
   * Javascript EXIF Reader - jQuery plugin 0.1.3
   * Copyright (c) 2008 Jacob Seidelin, cupboy@gmail.com, http://blog.nihilogic.dk/
   * Licensed under the MPL License [http://www.nihilogic.dk/licenses/mpl-license.txt]
   */
  
  /*
   * I added three functions for read EXIF from dataURL
   * - getImageDataFromDataURL
   * - getDataFromDataURL
   * - jQuery.fn.exifLoadFromDataURL
   * 
   * http://orientation.gokercebeci.com 
   * @gokercebeci
   */
  
  (function() {
  
  
      var BinaryFile = function(strData, iDataOffset, iDataLength) {
          var data = strData;
          var dataOffset = iDataOffset || 0;
          var dataLength = 0;
  
          this.getRawData = function() {
              return data;
          }
  
          if (typeof strData == "string") {
              dataLength = iDataLength || data.length;
  
              this.getByteAt = function(iOffset) {
                  return data.charCodeAt(iOffset + dataOffset) & 0xFF;
              }
          } else if (typeof strData == "unknown") {
              dataLength = iDataLength || IEBinary_getLength(data);
  
              this.getByteAt = function(iOffset) {
                  return IEBinary_getByteAt(data, iOffset + dataOffset);
              }
          }
  
          this.getLength = function() {
              return dataLength;
          }
  
          this.getSByteAt = function(iOffset) {
              var iByte = this.getByteAt(iOffset);
              if (iByte > 127)
                  return iByte - 256;
              else
                  return iByte;
          }
  
          this.getShortAt = function(iOffset, bBigEndian) {
              var iShort = bBigEndian ?
                      (this.getByteAt(iOffset) << 8) + this.getByteAt(iOffset + 1)
                      : (this.getByteAt(iOffset + 1) << 8) + this.getByteAt(iOffset)
              if (iShort < 0)
                  iShort += 65536;
              return iShort;
          }
          this.getSShortAt = function(iOffset, bBigEndian) {
              var iUShort = this.getShortAt(iOffset, bBigEndian);
              if (iUShort > 32767)
                  return iUShort - 65536;
              else
                  return iUShort;
          }
          this.getLongAt = function(iOffset, bBigEndian) {
              var iByte1 = this.getByteAt(iOffset),
                      iByte2 = this.getByteAt(iOffset + 1),
                      iByte3 = this.getByteAt(iOffset + 2),
                      iByte4 = this.getByteAt(iOffset + 3);
  
              var iLong = bBigEndian ?
                      (((((iByte1 << 8) + iByte2) << 8) + iByte3) << 8) + iByte4
                      : (((((iByte4 << 8) + iByte3) << 8) + iByte2) << 8) + iByte1;
              if (iLong < 0)
                  iLong += 4294967296;
              return iLong;
          }
          this.getSLongAt = function(iOffset, bBigEndian) {
              var iULong = this.getLongAt(iOffset, bBigEndian);
              if (iULong > 2147483647)
                  return iULong - 4294967296;
              else
                  return iULong;
          }
          this.getStringAt = function(iOffset, iLength) {
              var aStr = [];
              for (var i = iOffset, j = 0; i < iOffset + iLength; i++, j++) {
                  aStr[j] = String.fromCharCode(this.getByteAt(i));
              }
              return aStr.join("");
          }
  
          this.getCharAt = function(iOffset) {
              return String.fromCharCode(this.getByteAt(iOffset));
          }
          this.toBase64 = function() {
              return window.btoa(data);
          }
          this.fromBase64 = function(strBase64) {
              data = window.atob(strBase64);
          }
      }
  
  
      var BinaryAjax = (function() {
  
          function createRequest() {
              var oHTTP = null;
              if (window.XMLHttpRequest) {
                  oHTTP = new XMLHttpRequest();
              } else if (window.ActiveXObject) {
                  oHTTP = new ActiveXObject("Microsoft.XMLHTTP");
              }
              return oHTTP;
          }
  
          function getHead(strURL, fncCallback, fncError) {
              var oHTTP = createRequest();
              if (oHTTP) {
                  if (fncCallback) {
                      if (typeof(oHTTP.onload) != "undefined") {
                          oHTTP.onload = function() {
                              if (oHTTP.status == "200") {
                                  fncCallback(this);
                              } else {
                                  if (fncError)
                                      fncError();
                              }
                              oHTTP = null;
                          };
                      } else {
                          oHTTP.onreadystatechange = function() {
                              if (oHTTP.readyState == 4) {
                                  if (oHTTP.status == "200") {
                                      fncCallback(this);
                                  } else {
                                      if (fncError)
                                          fncError();
                                  }
                                  oHTTP = null;
                              }
                          };
                      }
                  }
                  oHTTP.open("HEAD", strURL, true);
                  oHTTP.send(null);
              } else {
                  if (fncError)
                      fncError();
              }
          }
  
          function sendRequest(strURL, fncCallback, fncError, aRange, bAcceptRanges, iFileSize) {
              var oHTTP = createRequest();
              if (oHTTP) {
  
                  var iDataOffset = 0;
                  if (aRange && !bAcceptRanges) {
                      iDataOffset = aRange[0];
                  }
                  var iDataLen = 0;
                  if (aRange) {
                      iDataLen = aRange[1] - aRange[0] + 1;
                  }
  
                  if (fncCallback) {
                      if (typeof(oHTTP.onload) != "undefined") {
                          oHTTP.onload = function() {
  
                              if (oHTTP.status == "200" || oHTTP.status == "206" || oHTTP.status == "0") {
                                  this.binaryResponse = new BinaryFile(this.responseText, iDataOffset, iDataLen);
                                  this.fileSize = iFileSize || this.getResponseHeader("Content-Length");
                                  fncCallback(this);
                              } else {
                                  if (fncError)
                                      fncError();
                              }
                              oHTTP = null;
                          };
                      } else {
                          oHTTP.onreadystatechange = function() {
                              if (oHTTP.readyState == 4) {
                                  if (oHTTP.status == "200" || oHTTP.status == "206" || oHTTP.status == "0") {
                                      this.binaryResponse = new BinaryFile(oHTTP.responseBody, iDataOffset, iDataLen);
                                      this.fileSize = iFileSize || this.getResponseHeader("Content-Length");
                                      fncCallback(this);
                                  } else {
                                      if (fncError)
                                          fncError();
                                  }
                                  oHTTP = null;
                              }
                          };
                      }
                  }
                  oHTTP.open("GET", strURL, true);
  
                  if (oHTTP.overrideMimeType)
                      oHTTP.overrideMimeType('text/plain; charset=x-user-defined');
  
                  if (aRange && bAcceptRanges) {
                      oHTTP.setRequestHeader("Range", "bytes=" + aRange[0] + "-" + aRange[1]);
                  }
  
                  oHTTP.setRequestHeader("If-Modified-Since", "Sat, 1 Jan 1970 00:00:00 GMT");
  
                  oHTTP.send(null);
              } else {
                  if (fncError)
                      fncError();
              }
          }
  
          return function(strURL, fncCallback, fncError, aRange) {
  
              if (aRange) {
                  getHead(
                          strURL,
                          function(oHTTP) {
                              var iLength = parseInt(oHTTP.getResponseHeader("Content-Length"), 10);
                              var strAcceptRanges = oHTTP.getResponseHeader("Accept-Ranges");
  
                              var iStart, iEnd;
                              iStart = aRange[0];
                              if (aRange[0] < 0)
                                  iStart += iLength;
                              iEnd = iStart + aRange[1] - 1;
  
                              sendRequest(strURL, fncCallback, fncError, [iStart, iEnd], (strAcceptRanges == "bytes"), iLength);
                          }
                  );
  
              } else {
                  sendRequest(strURL, fncCallback, fncError);
              }
          }
  
      }());
  
      var script = document.createElement("script");
      script.type = 'text/vbscript';
      script.innerHTML = 
  		"Function IEBinary_getByteAt(strBinary, iOffset)\r\n"
              + "	IEBinary_getByteAt = AscB(MidB(strBinary,iOffset+1,1))\r\n"
              + "End Function\r\n"
              + "Function IEBinary_getLength(strBinary)\r\n"
              + "	IEBinary_getLength = LenB(strBinary)\r\n"
              + "End Function";
      document.head.appendChild(script);
  
      var EXIF = {};
  
      (function() {
  
          var bDebug = false;
  
          EXIF.Tags = {
  
              // version tags
              0x9000: "ExifVersion", // EXIF version
              0xA000: "FlashpixVersion", // Flashpix format version
  
              // colorspace tags
              0xA001: "ColorSpace", // Color space information tag
  
              // image configuration
              0xA002: "PixelXDimension", // Valid width of meaningful image
              0xA003: "PixelYDimension", // Valid height of meaningful image
              0x9101: "ComponentsConfiguration", // Information about channels
              0x9102: "CompressedBitsPerPixel", // Compressed bits per pixel
  
              // user information
              0x927C: "MakerNote", // Any desired information written by the manufacturer
              0x9286: "UserComment", // Comments by user
  
              // related file
              0xA004: "RelatedSoundFile", // Name of related sound file
  
              // date and time
              0x9003: "DateTimeOriginal", // Date and time when the original image was generated
              0x9004: "DateTimeDigitized", // Date and time when the image was stored digitally
              0x9290: "SubsecTime", // Fractions of seconds for DateTime
              0x9291: "SubsecTimeOriginal", // Fractions of seconds for DateTimeOriginal
              0x9292: "SubsecTimeDigitized", // Fractions of seconds for DateTimeDigitized
  
              // picture-taking conditions
              0x829A: "ExposureTime", // Exposure time (in seconds)
              0x829D: "FNumber", // F number
              0x8822: "ExposureProgram", // Exposure program
              0x8824: "SpectralSensitivity", // Spectral sensitivity
              0x8827: "ISOSpeedRatings", // ISO speed rating
              0x8828: "OECF", // Optoelectric conversion factor
              0x9201: "ShutterSpeedValue", // Shutter speed
              0x9202: "ApertureValue", // Lens aperture
              0x9203: "BrightnessValue", // Value of brightness
              0x9204: "ExposureBias", // Exposure bias
              0x9205: "MaxApertureValue", // Smallest F number of lens
              0x9206: "SubjectDistance", // Distance to subject in meters
              0x9207: "MeteringMode", // Metering mode
              0x9208: "LightSource", // Kind of light source
              0x9209: "Flash", // Flash status
              0x9214: "SubjectArea", // Location and area of main subject
              0x920A: "FocalLength", // Focal length of the lens in mm
              0xA20B: "FlashEnergy", // Strobe energy in BCPS
              0xA20C: "SpatialFrequencyResponse", // 
              0xA20E: "FocalPlaneXResolution", // Number of pixels in width direction per FocalPlaneResolutionUnit
              0xA20F: "FocalPlaneYResolution", // Number of pixels in height direction per FocalPlaneResolutionUnit
              0xA210: "FocalPlaneResolutionUnit", // Unit for measuring FocalPlaneXResolution and FocalPlaneYResolution
              0xA214: "SubjectLocation", // Location of subject in image
              0xA215: "ExposureIndex", // Exposure index selected on camera
              0xA217: "SensingMethod", // Image sensor type
              0xA300: "FileSource", // Image source (3 == DSC)
              0xA301: "SceneType", // Scene type (1 == directly photographed)
              0xA302: "CFAPattern", // Color filter array geometric pattern
              0xA401: "CustomRendered", // Special processing
              0xA402: "ExposureMode", // Exposure mode
              0xA403: "WhiteBalance", // 1 = auto white balance, 2 = manual
              0xA404: "DigitalZoomRation", // Digital zoom ratio
              0xA405: "FocalLengthIn35mmFilm", // Equivalent foacl length assuming 35mm film camera (in mm)
              0xA406: "SceneCaptureType", // Type of scene
              0xA407: "GainControl", // Degree of overall image gain adjustment
              0xA408: "Contrast", // Direction of contrast processing applied by camera
              0xA409: "Saturation", // Direction of saturation processing applied by camera
              0xA40A: "Sharpness", // Direction of sharpness processing applied by camera
              0xA40B: "DeviceSettingDescription", // 
              0xA40C: "SubjectDistanceRange", // Distance to subject
  
              // other tags
              0xA005: "InteroperabilityIFDPointer",
              0xA420: "ImageUniqueID"		// Identifier assigned uniquely to each image
          };
  
          EXIF.TiffTags = {
              0x0100: "ImageWidth",
              0x0101: "ImageHeight",
              0x8769: "ExifIFDPointer",
              0x8825: "GPSInfoIFDPointer",
              0xA005: "InteroperabilityIFDPointer",
              0x0102: "BitsPerSample",
              0x0103: "Compression",
              0x0106: "PhotometricInterpretation",
              0x0112: "Orientation",
              0x0115: "SamplesPerPixel",
              0x011C: "PlanarConfiguration",
              0x0212: "YCbCrSubSampling",
              0x0213: "YCbCrPositioning",
              0x011A: "XResolution",
              0x011B: "YResolution",
              0x0128: "ResolutionUnit",
              0x0111: "StripOffsets",
              0x0116: "RowsPerStrip",
              0x0117: "StripByteCounts",
              0x0201: "JPEGInterchangeFormat",
              0x0202: "JPEGInterchangeFormatLength",
              0x012D: "TransferFunction",
              0x013E: "WhitePoint",
              0x013F: "PrimaryChromaticities",
              0x0211: "YCbCrCoefficients",
              0x0214: "ReferenceBlackWhite",
              0x0132: "DateTime",
              0x010E: "ImageDescription",
              0x010F: "Make",
              0x0110: "Model",
              0x0131: "Software",
              0x013B: "Artist",
              0x8298: "Copyright"
          }
  
          EXIF.GPSTags = {
              0x0000: "GPSVersionID",
              0x0001: "GPSLatitudeRef",
              0x0002: "GPSLatitude",
              0x0003: "GPSLongitudeRef",
              0x0004: "GPSLongitude",
              0x0005: "GPSAltitudeRef",
              0x0006: "GPSAltitude",
              0x0007: "GPSTimeStamp",
              0x0008: "GPSSatellites",
              0x0009: "GPSStatus",
              0x000A: "GPSMeasureMode",
              0x000B: "GPSDOP",
              0x000C: "GPSSpeedRef",
              0x000D: "GPSSpeed",
              0x000E: "GPSTrackRef",
              0x000F: "GPSTrack",
              0x0010: "GPSImgDirectionRef",
              0x0011: "GPSImgDirection",
              0x0012: "GPSMapDatum",
              0x0013: "GPSDestLatitudeRef",
              0x0014: "GPSDestLatitude",
              0x0015: "GPSDestLongitudeRef",
              0x0016: "GPSDestLongitude",
              0x0017: "GPSDestBearingRef",
              0x0018: "GPSDestBearing",
              0x0019: "GPSDestDistanceRef",
              0x001A: "GPSDestDistance",
              0x001B: "GPSProcessingMethod",
              0x001C: "GPSAreaInformation",
              0x001D: "GPSDateStamp",
              0x001E: "GPSDifferential"
          }
  
          EXIF.StringValues = {
              ExposureProgram: {
                  0: "Not defined",
                  1: "Manual",
                  2: "Normal program",
                  3: "Aperture priority",
                  4: "Shutter priority",
                  5: "Creative program",
                  6: "Action program",
                  7: "Portrait mode",
                  8: "Landscape mode"
              },
              MeteringMode: {
                  0: "Unknown",
                  1: "Average",
                  2: "CenterWeightedAverage",
                  3: "Spot",
                  4: "MultiSpot",
                  5: "Pattern",
                  6: "Partial",
                  255: "Other"
              },
              LightSource: {
                  0: "Unknown",
                  1: "Daylight",
                  2: "Fluorescent",
                  3: "Tungsten (incandescent light)",
                  4: "Flash",
                  9: "Fine weather",
                  10: "Cloudy weather",
                  11: "Shade",
                  12: "Daylight fluorescent (D 5700 - 7100K)",
                  13: "Day white fluorescent (N 4600 - 5400K)",
                  14: "Cool white fluorescent (W 3900 - 4500K)",
                  15: "White fluorescent (WW 3200 - 3700K)",
                  17: "Standard light A",
                  18: "Standard light B",
                  19: "Standard light C",
                  20: "D55",
                  21: "D65",
                  22: "D75",
                  23: "D50",
                  24: "ISO studio tungsten",
                  255: "Other"
              },
              Flash: {
                  0x0000: "Flash did not fire",
                  0x0001: "Flash fired",
                  0x0005: "Strobe return light not detected",
                  0x0007: "Strobe return light detected",
                  0x0009: "Flash fired, compulsory flash mode",
                  0x000D: "Flash fired, compulsory flash mode, return light not detected",
                  0x000F: "Flash fired, compulsory flash mode, return light detected",
                  0x0010: "Flash did not fire, compulsory flash mode",
                  0x0018: "Flash did not fire, auto mode",
                  0x0019: "Flash fired, auto mode",
                  0x001D: "Flash fired, auto mode, return light not detected",
                  0x001F: "Flash fired, auto mode, return light detected",
                  0x0020: "No flash function",
                  0x0041: "Flash fired, red-eye reduction mode",
                  0x0045: "Flash fired, red-eye reduction mode, return light not detected",
                  0x0047: "Flash fired, red-eye reduction mode, return light detected",
                  0x0049: "Flash fired, compulsory flash mode, red-eye reduction mode",
                  0x004D: "Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected",
                  0x004F: "Flash fired, compulsory flash mode, red-eye reduction mode, return light detected",
                  0x0059: "Flash fired, auto mode, red-eye reduction mode",
                  0x005D: "Flash fired, auto mode, return light not detected, red-eye reduction mode",
                  0x005F: "Flash fired, auto mode, return light detected, red-eye reduction mode"
              },
              SensingMethod: {
                  1: "Not defined",
                  2: "One-chip color area sensor",
                  3: "Two-chip color area sensor",
                  4: "Three-chip color area sensor",
                  5: "Color sequential area sensor",
                  7: "Trilinear sensor",
                  8: "Color sequential linear sensor"
              },
              SceneCaptureType: {
                  0: "Standard",
                  1: "Landscape",
                  2: "Portrait",
                  3: "Night scene"
              },
              SceneType: {
                  1: "Directly photographed"
              },
              CustomRendered: {
                  0: "Normal process",
                  1: "Custom process"
              },
              WhiteBalance: {
                  0: "Auto white balance",
                  1: "Manual white balance"
              },
              GainControl: {
                  0: "None",
                  1: "Low gain up",
                  2: "High gain up",
                  3: "Low gain down",
                  4: "High gain down"
              },
              Contrast: {
                  0: "Normal",
                  1: "Soft",
                  2: "Hard"
              },
              Saturation: {
                  0: "Normal",
                  1: "Low saturation",
                  2: "High saturation"
              },
              Sharpness: {
                  0: "Normal",
                  1: "Soft",
                  2: "Hard"
              },
              SubjectDistanceRange: {
                  0: "Unknown",
                  1: "Macro",
                  2: "Close view",
                  3: "Distant view"
              },
              FileSource: {
                  3: "DSC"
              },
              Components: {
                  0: "",
                  1: "Y",
                  2: "Cb",
                  3: "Cr",
                  4: "R",
                  5: "G",
                  6: "B"
              }
          }
  
          function addEvent(oElement, strEvent, fncHandler)
          {
              if (oElement.addEventListener) {
                  oElement.addEventListener(strEvent, fncHandler, false);
              } else if (oElement.attachEvent) {
                  oElement.attachEvent("on" + strEvent, fncHandler);
              }
          }
  
  
          function imageHasData(oImg)
          {
              return !!(oImg.exifdata);
          }
  
          function getImageData(oImg, fncCallback)
          {
              BinaryAjax(
                      oImg.src,
                      function(oHTTP) {
                          console.log('BINARY', oHTTP.binaryResponse);
                          var oEXIF = findEXIFinJPEG(oHTTP.binaryResponse);
                          oImg.exifdata = oEXIF || {};
                          if (fncCallback)
                      fncCallback();
                      }
              )
          }
  
          function getImageDataFromDataURL(oImg, fncCallback)
          {
              var byteString = atob(oImg.src.split(',')[1]);
              var f = new BinaryFile(byteString, 0, byteString.length)
              var oEXIF = findEXIFinJPEG(f);
              oImg.exifdata = oEXIF || {};
              if (fncCallback)
                  fncCallback();
          }
  
          function findEXIFinJPEG(oFile) {
              var aMarkers = [];
  
              if (oFile.getByteAt(0) != 0xFF || oFile.getByteAt(1) != 0xD8) {
                  return false; // not a valid jpeg
              }
  
              var iOffset = 2;
              var iLength = oFile.getLength();
              while (iOffset < iLength) {
                  if (oFile.getByteAt(iOffset) != 0xFF) {
                      if (bDebug)
                          console.log("Not a valid marker at offset " + iOffset + ", found: " + oFile.getByteAt(iOffset));
                      return false; // not a valid marker, something is wrong
                  }
  
                  var iMarker = oFile.getByteAt(iOffset + 1);
  
                  // we could implement handling for other markers here, 
                  // but we're only looking for 0xFFE1 for EXIF data
  
                  if (iMarker == 22400) {
                      if (bDebug)
                          console.log("Found 0xFFE1 marker");
                      return readEXIFData(oFile, iOffset + 4, oFile.getShortAt(iOffset + 2, true) - 2);
                      iOffset += 2 + oFile.getShortAt(iOffset + 2, true);
  
                  } else if (iMarker == 225) {
                      // 0xE1 = Application-specific 1 (for EXIF)
                      if (bDebug)
                          console.log("Found 0xFFE1 marker");
                      return readEXIFData(oFile, iOffset + 4, oFile.getShortAt(iOffset + 2, true) - 2);
  
                  } else {
                      iOffset += 2 + oFile.getShortAt(iOffset + 2, true);
                  }
  
              }
  
          }
  
  
          function readTags(oFile, iTIFFStart, iDirStart, oStrings, bBigEnd)
          {
              var iEntries = oFile.getShortAt(iDirStart, bBigEnd);
              var oTags = {};
              for (var i = 0; i < iEntries; i++) {
                  var iEntryOffset = iDirStart + i * 12 + 2;
                  var strTag = oStrings[oFile.getShortAt(iEntryOffset, bBigEnd)];
                  if (!strTag && bDebug)
                      console.log("Unknown tag: " + oFile.getShortAt(iEntryOffset, bBigEnd));
                  oTags[strTag] = readTagValue(oFile, iEntryOffset, iTIFFStart, iDirStart, bBigEnd);
              }
              return oTags;
          }
  
  
          function readTagValue(oFile, iEntryOffset, iTIFFStart, iDirStart, bBigEnd)
          {
              var iType = oFile.getShortAt(iEntryOffset + 2, bBigEnd);
              var iNumValues = oFile.getLongAt(iEntryOffset + 4, bBigEnd);
              var iValueOffset = oFile.getLongAt(iEntryOffset + 8, bBigEnd) + iTIFFStart;
  
              switch (iType) {
                  case 1: // byte, 8-bit unsigned int
                  case 7: // undefined, 8-bit byte, value depending on field
                      if (iNumValues == 1) {
                          return oFile.getByteAt(iEntryOffset + 8, bBigEnd);
                      } else {
                          var iValOffset = iNumValues > 4 ? iValueOffset : (iEntryOffset + 8);
                          var aVals = [];
                          for (var n = 0; n < iNumValues; n++) {
                              aVals[n] = oFile.getByteAt(iValOffset + n);
                          }
                          return aVals;
                      }
                      break;
  
                  case 2: // ascii, 8-bit byte
                      var iStringOffset = iNumValues > 4 ? iValueOffset : (iEntryOffset + 8);
                      return oFile.getStringAt(iStringOffset, iNumValues - 1);
                      break;
  
                  case 3: // short, 16 bit int
                      if (iNumValues == 1) {
                          return oFile.getShortAt(iEntryOffset + 8, bBigEnd);
                      } else {
                          var iValOffset = iNumValues > 2 ? iValueOffset : (iEntryOffset + 8);
                          var aVals = [];
                          for (var n = 0; n < iNumValues; n++) {
                              aVals[n] = oFile.getShortAt(iValOffset + 2 * n, bBigEnd);
                          }
                          return aVals;
                      }
                      break;
  
                  case 4: // long, 32 bit int
                      if (iNumValues == 1) {
                          return oFile.getLongAt(iEntryOffset + 8, bBigEnd);
                      } else {
                          var aVals = [];
                          for (var n = 0; n < iNumValues; n++) {
                              aVals[n] = oFile.getLongAt(iValueOffset + 4 * n, bBigEnd);
                          }
                          return aVals;
                      }
                      break;
                  case 5:	// rational = two long values, first is numerator, second is denominator
                      if (iNumValues == 1) {
                          return oFile.getLongAt(iValueOffset, bBigEnd) / oFile.getLongAt(iValueOffset + 4, bBigEnd);
                      } else {
                          var aVals = [];
                          for (var n = 0; n < iNumValues; n++) {
                              aVals[n] = oFile.getLongAt(iValueOffset + 8 * n, bBigEnd) / oFile.getLongAt(iValueOffset + 4 + 8 * n, bBigEnd);
                          }
                          return aVals;
                      }
                      break;
                  case 9: // slong, 32 bit signed int
                      if (iNumValues == 1) {
                          return oFile.getSLongAt(iEntryOffset + 8, bBigEnd);
                      } else {
                          var aVals = [];
                          for (var n = 0; n < iNumValues; n++) {
                              aVals[n] = oFile.getSLongAt(iValueOffset + 4 * n, bBigEnd);
                          }
                          return aVals;
                      }
                      break;
                  case 10: // signed rational, two slongs, first is numerator, second is denominator
                      if (iNumValues == 1) {
                          return oFile.getSLongAt(iValueOffset, bBigEnd) / oFile.getSLongAt(iValueOffset + 4, bBigEnd);
                      } else {
                          var aVals = [];
                          for (var n = 0; n < iNumValues; n++) {
                              aVals[n] = oFile.getSLongAt(iValueOffset + 8 * n, bBigEnd) / oFile.getSLongAt(iValueOffset + 4 + 8 * n, bBigEnd);
                          }
                          return aVals;
                      }
                      break;
              }
          }
  
  
          function readEXIFData(oFile, iStart, iLength)
          {
              if (oFile.getStringAt(iStart, 4) != "Exif") {
                  if (bDebug)
                      console.log("Not valid EXIF data! " + oFile.getStringAt(iStart, 4));
                  return false;
              }
  
              var bBigEnd;
  
              var iTIFFOffset = iStart + 6;
  
              // test for TIFF validity and endianness
              if (oFile.getShortAt(iTIFFOffset) == 0x4949) {
                  bBigEnd = false;
              } else if (oFile.getShortAt(iTIFFOffset) == 0x4D4D) {
                  bBigEnd = true;
              } else {
                  if (bDebug)
                      console.log("Not valid TIFF data! (no 0x4949 or 0x4D4D)");
                  return false;
              }
  
              if (oFile.getShortAt(iTIFFOffset + 2, bBigEnd) != 0x002A) {
                  if (bDebug)
                      console.log("Not valid TIFF data! (no 0x002A)");
                  return false;
              }
  
              if (oFile.getLongAt(iTIFFOffset + 4, bBigEnd) != 0x00000008) {
                  if (bDebug)
                      console.log("Not valid TIFF data! (First offset not 8)", oFile.getShortAt(iTIFFOffset + 4, bBigEnd));
                  return false;
              }
  
              var oTags = readTags(oFile, iTIFFOffset, iTIFFOffset + 8, EXIF.TiffTags, bBigEnd);
  
              if (oTags.ExifIFDPointer) {
                  var oEXIFTags = readTags(oFile, iTIFFOffset, iTIFFOffset + oTags.ExifIFDPointer, EXIF.Tags, bBigEnd);
                  for (var strTag in oEXIFTags) {
                      switch (strTag) {
                          case "LightSource" :
                          case "Flash" :
                          case "MeteringMode" :
                          case "ExposureProgram" :
                          case "SensingMethod" :
                          case "SceneCaptureType" :
                          case "SceneType" :
                          case "CustomRendered" :
                          case "WhiteBalance" :
                          case "GainControl" :
                          case "Contrast" :
                          case "Saturation" :
                          case "Sharpness" :
                          case "SubjectDistanceRange" :
                          case "FileSource" :
                              oEXIFTags[strTag] = EXIF.StringValues[strTag][oEXIFTags[strTag]];
                              break;
  
                          case "ExifVersion" :
                          case "FlashpixVersion" :
                              oEXIFTags[strTag] = String.fromCharCode(oEXIFTags[strTag][0], oEXIFTags[strTag][1], oEXIFTags[strTag][2], oEXIFTags[strTag][3]);
                              break;
  
                          case "ComponentsConfiguration" :
                              oEXIFTags[strTag] =
                                      EXIF.StringValues.Components[oEXIFTags[strTag][0]]
                                      + EXIF.StringValues.Components[oEXIFTags[strTag][1]]
                                      + EXIF.StringValues.Components[oEXIFTags[strTag][2]]
                                      + EXIF.StringValues.Components[oEXIFTags[strTag][3]];
                              break;
                      }
                      oTags[strTag] = oEXIFTags[strTag];
                  }
              }
  
              if (oTags.GPSInfoIFDPointer) {
                  var oGPSTags = readTags(oFile, iTIFFOffset, iTIFFOffset + oTags.GPSInfoIFDPointer, EXIF.GPSTags, bBigEnd);
                  for (var strTag in oGPSTags) {
                      switch (strTag) {
                          case "GPSVersionID" :
                              oGPSTags[strTag] = oGPSTags[strTag][0]
                                      + "." + oGPSTags[strTag][1]
                                      + "." + oGPSTags[strTag][2]
                                      + "." + oGPSTags[strTag][3];
                              break;
                      }
                      oTags[strTag] = oGPSTags[strTag];
                  }
              }
  
              return oTags;
          }
  
  
          EXIF.getData = function(oImg, fncCallback)
          {
              if (!oImg.complete)
                  return false;
              if (!imageHasData(oImg)) {
                  getImageData(oImg, fncCallback);
              } else {
                  if (fncCallback)
                      fncCallback();
              }
              return true;
          }
  
          EXIF.getDataFromDataURL = function(oImg, fncCallback)
          {
              if (!oImg.complete)
                  return false;
              if (!imageHasData(oImg)) {
                  getImageDataFromDataURL(oImg, fncCallback);
              } else {
                  if (fncCallback)
                      fncCallback();
              }
              return true;
          }
  
          EXIF.getTag = function(oImg, strTag)
          {
              if (!imageHasData(oImg))
                  return;
              return oImg.exifdata[strTag];
          }
  
          EXIF.getAllTags = function(oImg)
          {
              if (!imageHasData(oImg))
                  return {};
              var oData = oImg.exifdata;
              var oAllTags = {};
              for (var a in oData) {
                  if (oData.hasOwnProperty(a)) {
                      oAllTags[a] = oData[a];
                  }
              }
              return oAllTags;
          }
  
          EXIF.pretty = function(oImg)
          {
              if (!imageHasData(oImg))
                  return "";
              var oData = oImg.exifdata;
              var strPretty = "";
              for (var a in oData) {
                  if (oData.hasOwnProperty(a)) {
                      if (typeof oData[a] == "object") {
                          strPretty += a + " : [" + oData[a].length + " values]\r\n";
                      } else {
                          strPretty += a + " : " + oData[a] + "\r\n";
                      }
                  }
              }
              return strPretty;
          }
  
          EXIF.readFromBinaryFile = function(oFile) {
              return findEXIFinJPEG(oFile);
          }
  
          function loadAllImages()
          {
              var aImages = document.getElementsByTagName("img");
              for (var i = 0; i < aImages.length; i++) {
                  if (aImages[i].getAttribute("exif") == "true") {
                      if (!aImages[i].complete) {
                          addEvent(aImages[i], "load",
                                  function() {
                                      EXIF.getData(this);
                                  }
                          );
                      } else {
                          EXIF.getData(aImages[i]);
                      }
                  }
              }
          }
  
          // automatically load exif data for all images with exif=true when doc is ready
          jQuery(document).ready(loadAllImages);
  
          // load data for images manually
          jQuery.fn.exifLoad = function(fncCallback) {
              return this.each(function() {
                  EXIF.getData(this, fncCallback)
              });
          }
  
          // load data for images manually
          jQuery.fn.exifLoadFromDataURL = function(fncCallback) {
              return this.each(function() {
                  EXIF.getDataFromDataURL(this, fncCallback)
                  return true;
              });
          }
  
          jQuery.fn.exif = function(strTag) {
              var aStrings = [];
              this.each(function() {
                  aStrings.push(EXIF.getTag(this, strTag));
              });
              return aStrings;
          }
  
          jQuery.fn.exifAll = function() {
              var aStrings = [];
              this.each(function() {
                  aStrings.push(EXIF.getAllTags(this));
              });
              return aStrings;
          }
  
          jQuery.fn.exifPretty = function() {
              var aStrings = [];
              this.each(function() {
                  aStrings.push(EXIF.pretty(this));
              });
              return aStrings;
          }
  
  
      })();
  
  
  })();
  