diff --git a/toonz/sources/common/tiio/tiio_jpg.cpp b/toonz/sources/common/tiio/tiio_jpg.cpp
index ec3917c..ee31928 100644
--- a/toonz/sources/common/tiio/tiio_jpg.cpp
+++ b/toonz/sources/common/tiio/tiio_jpg.cpp
@@ -9,6 +9,7 @@
 //#include "../compatibility/tnz4.h"
 
 #include "tiio_jpg.h"
+#include "tiio_jpg_exif.h"
 #include "tproperty.h"
 #include "tpixel.h"
 
@@ -76,10 +77,36 @@ void JpgReader::open(FILE *file) {
   m_cinfo.err->error_exit = tnz_error_exit;
 
   jpeg_create_decompress(&m_cinfo);
+
   m_chan = file;
   jpeg_stdio_src(&m_cinfo, m_chan);
+  jpeg_save_markers(&m_cinfo, JPEG_APP0 + 1, 0xffff);  // EXIF
   bool ret = jpeg_read_header(&m_cinfo, TRUE);
-  ret      = ret && jpeg_start_decompress(&m_cinfo);
+
+  bool resolutionFoundInExif = false;
+  jpeg_saved_marker_ptr mark;
+  for (mark = m_cinfo.marker_list; NULL != mark; mark = mark->next) {
+    switch (mark->marker) {
+    case JPEG_APP0 + 1:  // EXIF
+      JpgExifReader exifReader;
+      exifReader.process_EXIF(mark->data - 2, mark->data_length);
+      if (exifReader.containsResolution()) {
+        int resUnit           = exifReader.getResolutionUnit();
+        resolutionFoundInExif = true;
+        if (resUnit == 1 || resUnit == 2) {  // no unit(1) or inch(2)
+          m_info.m_dpix = (double)exifReader.getXResolution();
+          m_info.m_dpiy = (double)exifReader.getYResolution();
+        } else if (resUnit == 3) {  // centimeter(3);
+          m_info.m_dpix = (double)exifReader.getXResolution() * 2.54;
+          m_info.m_dpiy = (double)exifReader.getYResolution() * 2.54;
+        } else  // ignore millimeter(4) and micrometer(5) cases for now
+          resolutionFoundInExif = false;
+      }
+      break;
+    }
+  }
+
+  ret = ret && jpeg_start_decompress(&m_cinfo);
   if (!ret) return;
 
   int row_stride = m_cinfo.output_width * m_cinfo.output_components;
@@ -91,6 +118,17 @@ void JpgReader::open(FILE *file) {
   m_info.m_samplePerPixel = 3;
   m_info.m_valid          = true;
   m_isOpen                = true;
+
+  if (!resolutionFoundInExif && (m_cinfo.saw_JFIF_marker != 0) &&
+      (m_cinfo.X_density != 1) && (m_cinfo.Y_density != 1)) {
+    if (m_cinfo.density_unit == 1) {
+      m_info.m_dpix = (double)m_cinfo.X_density;
+      m_info.m_dpiy = (double)m_cinfo.Y_density;
+    } else if (m_cinfo.density_unit == 2) {
+      m_info.m_dpix = (double)m_cinfo.X_density * 2.54;
+      m_info.m_dpiy = (double)m_cinfo.Y_density * 2.54;
+    }
+  }
 }
 
 void JpgReader::readLine(char *buffer, int x0, int x1, int shrink) {
@@ -164,6 +202,16 @@ public:
     m_cinfo.in_color_space   = JCS_RGB;
 
     jpeg_set_defaults(&m_cinfo);
+
+    // save dpi always in JFIF header, instead of EXIF
+    m_cinfo.write_JFIF_header  = 1;
+    m_cinfo.JFIF_major_version = 1;
+    m_cinfo.JFIF_minor_version = 2;
+    m_cinfo.X_density          = (UINT16)info.m_dpix;
+    m_cinfo.Y_density          = (UINT16)info.m_dpiy;
+    m_cinfo.density_unit       = 1;  // dot per inch
+    m_cinfo.write_Adobe_marker = 0;
+
     if (!m_properties) m_properties = new Tiio::JpgWriterProperties();
 
     jpeg_set_quality(
diff --git a/toonz/sources/common/tiio/tiio_jpg_exif.cpp b/toonz/sources/common/tiio/tiio_jpg_exif.cpp
new file mode 100644
index 0000000..c7613dd
--- /dev/null
+++ b/toonz/sources/common/tiio/tiio_jpg_exif.cpp
@@ -0,0 +1,1057 @@
+/*-------------------------------------------------------------
+tiio_jpg_exif.cpp
+Based on source code of a public domain software "Exif Jpeg header manipulation
+tool (jhead)" by Matthias Wandel.
+For now it is used only for obtaining resolution values.
+http://www.sentex.net/~mwandel/jhead/
+-------------------------------------------------------------*/
+#include "tiio_jpg_exif.h"
+#include <iostream>
+#include <string.h>
+
+// for debug
+#define ShowTags 0
+#define DumpExifMap 0
+
+namespace {
+
+typedef unsigned char uchar;
+const int BytesPerFormat[] = {0, 1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4, 8};
+//--------------------------------------------------------------------------
+// Describes tag values
+const int TAG_X_RESOLUTION    = 0x011A;
+const int TAG_Y_RESOLUTION    = 0x011B;
+const int TAG_RESOLUTION_UNIT = 0x0128;
+#ifdef ReadAllTags
+const int TAG_INTEROP_INDEX          = 0x0001;
+const int TAG_INTEROP_VERSION        = 0x0002;
+const int TAG_IMAGE_WIDTH            = 0x0100;
+const int TAG_IMAGE_LENGTH           = 0x0101;
+const int TAG_BITS_PER_SAMPLE        = 0x0102;
+const int TAG_COMPRESSION            = 0x0103;
+const int TAG_PHOTOMETRIC_INTERP     = 0x0106;
+const int TAG_FILL_ORDER             = 0x010A;
+const int TAG_DOCUMENT_NAME          = 0x010D;
+const int TAG_IMAGE_DESCRIPTION      = 0x010E;
+const int TAG_MAKE                   = 0x010F;
+const int TAG_MODEL                  = 0x0110;
+const int TAG_SRIP_OFFSET            = 0x0111;
+const int TAG_ORIENTATION            = 0x0112;
+const int TAG_SAMPLES_PER_PIXEL      = 0x0115;
+const int TAG_ROWS_PER_STRIP         = 0x0116;
+const int TAG_STRIP_BYTE_COUNTS      = 0x0117;
+const int TAG_PLANAR_CONFIGURATION   = 0x011C;
+const int TAG_TRANSFER_FUNCTION      = 0x012D;
+const int TAG_SOFTWARE               = 0x0131;
+const int TAG_DATETIME               = 0x0132;
+const int TAG_ARTIST                 = 0x013B;
+const int TAG_WHITE_POINT            = 0x013E;
+const int TAG_PRIMARY_CHROMATICITIES = 0x013F;
+const int TAG_TRANSFER_RANGE         = 0x0156;
+const int TAG_JPEG_PROC              = 0x0200;
+const int TAG_THUMBNAIL_OFFSET       = 0x0201;
+const int TAG_THUMBNAIL_LENGTH       = 0x0202;
+const int TAG_Y_CB_CR_COEFFICIENTS   = 0x0211;
+const int TAG_Y_CB_CR_SUB_SAMPLING   = 0x0212;
+const int TAG_Y_CB_CR_POSITIONING    = 0x0213;
+const int TAG_REFERENCE_BLACK_WHITE  = 0x0214;
+const int TAG_RELATED_IMAGE_WIDTH    = 0x1001;
+const int TAG_RELATED_IMAGE_LENGTH   = 0x1002;
+const int TAG_CFA_REPEAT_PATTERN_DIM = 0x828D;
+const int TAG_CFA_PATTERN1           = 0x828E;
+const int TAG_BATTERY_LEVEL          = 0x828F;
+const int TAG_COPYRIGHT              = 0x8298;
+const int TAG_EXPOSURETIME           = 0x829A;
+const int TAG_FNUMBER                = 0x829D;
+const int TAG_IPTC_NAA               = 0x83BB;
+const int TAG_EXIF_OFFSET            = 0x8769;
+const int TAG_INTER_COLOR_PROFILE    = 0x8773;
+const int TAG_EXPOSURE_PROGRAM       = 0x8822;
+const int TAG_SPECTRAL_SENSITIVITY   = 0x8824;
+const int TAG_GPSINFO                = 0x8825;
+const int TAG_ISO_EQUIVALENT         = 0x8827;
+const int TAG_OECF                   = 0x8828;
+const int TAG_EXIF_VERSION           = 0x9000;
+const int TAG_DATETIME_ORIGINAL      = 0x9003;
+const int TAG_DATETIME_DIGITIZED     = 0x9004;
+const int TAG_COMPONENTS_CONFIG      = 0x9101;
+const int TAG_CPRS_BITS_PER_PIXEL    = 0x9102;
+const int TAG_SHUTTERSPEED           = 0x9201;
+const int TAG_APERTURE               = 0x9202;
+const int TAG_BRIGHTNESS_VALUE       = 0x9203;
+const int TAG_EXPOSURE_BIAS          = 0x9204;
+const int TAG_MAXAPERTURE            = 0x9205;
+const int TAG_SUBJECT_DISTANCE       = 0x9206;
+const int TAG_METERING_MODE          = 0x9207;
+const int TAG_LIGHT_SOURCE           = 0x9208;
+const int TAG_FLASH                  = 0x9209;
+const int TAG_FOCALLENGTH            = 0x920A;
+const int TAG_SUBJECTAREA            = 0x9214;
+const int TAG_MAKER_NOTE             = 0x927C;
+const int TAG_USERCOMMENT            = 0x9286;
+const int TAG_SUBSEC_TIME            = 0x9290;
+const int TAG_SUBSEC_TIME_ORIG       = 0x9291;
+const int TAG_SUBSEC_TIME_DIG        = 0x9292;
+
+const int TAG_WINXP_TITLE = 0x9c9b;  // Windows XP - not part of exif standard.
+const int TAG_WINXP_COMMENT =
+    0x9c9c;                           // Windows XP - not part of exif standard.
+const int TAG_WINXP_AUTHOR = 0x9c9d;  // Windows XP - not part of exif standard.
+const int TAG_WINXP_KEYWORDS =
+    0x9c9e;  // Windows XP - not part of exif standard.
+const int TAG_WINXP_SUBJECT =
+    0x9c9f;  // Windows XP - not part of exif standard.
+
+const int TAG_FLASH_PIX_VERSION  = 0xA000;
+const int TAG_COLOR_SPACE        = 0xA001;
+const int TAG_PIXEL_X_DIMENSION  = 0xA002;
+const int TAG_PIXEL_Y_DIMENSION  = 0xA003;
+const int TAG_RELATED_AUDIO_FILE = 0xA004;
+const int TAG_INTEROP_OFFSET     = 0xA005;
+const int TAG_FLASH_ENERGY       = 0xA20B;
+const int TAG_SPATIAL_FREQ_RESP  = 0xA20C;
+const int TAG_FOCAL_PLANE_XRES   = 0xA20E;
+const int TAG_FOCAL_PLANE_YRES   = 0xA20F;
+const int TAG_FOCAL_PLANE_UNITS  = 0xA210;
+const int TAG_SUBJECT_LOCATION   = 0xA214;
+const int TAG_EXPOSURE_INDEX     = 0xA215;
+const int TAG_SENSING_METHOD     = 0xA217;
+const int TAG_FILE_SOURCE        = 0xA300;
+const int TAG_SCENE_TYPE         = 0xA301;
+const int TAG_CFA_PATTERN        = 0xA302;
+const int TAG_CUSTOM_RENDERED    = 0xA401;
+const int TAG_EXPOSURE_MODE      = 0xA402;
+const int TAG_WHITEBALANCE       = 0xA403;
+const int TAG_DIGITALZOOMRATIO   = 0xA404;
+const int TAG_FOCALLENGTH_35MM   = 0xA405;
+const int TAG_SCENE_CAPTURE_TYPE = 0xA406;
+const int TAG_GAIN_CONTROL       = 0xA407;
+const int TAG_CONTRAST           = 0xA408;
+const int TAG_SATURATION         = 0xA409;
+const int TAG_SHARPNESS          = 0xA40A;
+const int TAG_DISTANCE_RANGE     = 0xA40C;
+const int TAG_IMAGE_UNIQUE_ID    = 0xA420;
+#endif
+
+typedef struct {
+  unsigned short Tag;
+  char *Desc;
+} TagTable_t;
+
+const TagTable_t TagTable[] = {
+    {TAG_X_RESOLUTION, "XResolution"},
+    {TAG_Y_RESOLUTION, "YResolution"},
+    {TAG_RESOLUTION_UNIT, "ResolutionUnit"},
+#ifdef ReadAllTags
+    {TAG_INTEROP_INDEX, "InteropIndex"},
+    {TAG_INTEROP_VERSION, "InteropVersion"},
+    {TAG_IMAGE_WIDTH, "ImageWidth"},
+    {TAG_IMAGE_LENGTH, "ImageLength"},
+    {TAG_BITS_PER_SAMPLE, "BitsPerSample"},
+    {TAG_COMPRESSION, "Compression"},
+    {TAG_PHOTOMETRIC_INTERP, "PhotometricInterpretation"},
+    {TAG_FILL_ORDER, "FillOrder"},
+    {TAG_DOCUMENT_NAME, "DocumentName"},
+    {TAG_IMAGE_DESCRIPTION, "ImageDescription"},
+    {TAG_MAKE, "Make"},
+    {TAG_MODEL, "Model"},
+    {TAG_SRIP_OFFSET, "StripOffsets"},
+    {TAG_ORIENTATION, "Orientation"},
+    {TAG_SAMPLES_PER_PIXEL, "SamplesPerPixel"},
+    {TAG_ROWS_PER_STRIP, "RowsPerStrip"},
+    {TAG_STRIP_BYTE_COUNTS, "StripByteCounts"},
+    {TAG_PLANAR_CONFIGURATION, "PlanarConfiguration"},
+    {TAG_TRANSFER_FUNCTION, "TransferFunction"},
+    {TAG_SOFTWARE, "Software"},
+    {TAG_DATETIME, "DateTime"},
+    {TAG_ARTIST, "Artist"},
+    {TAG_WHITE_POINT, "WhitePoint"},
+    {TAG_PRIMARY_CHROMATICITIES, "PrimaryChromaticities"},
+    {TAG_TRANSFER_RANGE, "TransferRange"},
+    {TAG_JPEG_PROC, "JPEGProc"},
+    {TAG_THUMBNAIL_OFFSET, "ThumbnailOffset"},
+    {TAG_THUMBNAIL_LENGTH, "ThumbnailLength"},
+    {TAG_Y_CB_CR_COEFFICIENTS, "YCbCrCoefficients"},
+    {TAG_Y_CB_CR_SUB_SAMPLING, "YCbCrSubSampling"},
+    {TAG_Y_CB_CR_POSITIONING, "YCbCrPositioning"},
+    {TAG_REFERENCE_BLACK_WHITE, "ReferenceBlackWhite"},
+    {TAG_RELATED_IMAGE_WIDTH, "RelatedImageWidth"},
+    {TAG_RELATED_IMAGE_LENGTH, "RelatedImageLength"},
+    {TAG_CFA_REPEAT_PATTERN_DIM, "CFARepeatPatternDim"},
+    {TAG_CFA_PATTERN1, "CFAPattern"},
+    {TAG_BATTERY_LEVEL, "BatteryLevel"},
+    {TAG_COPYRIGHT, "Copyright"},
+    {TAG_EXPOSURETIME, "ExposureTime"},
+    {TAG_FNUMBER, "FNumber"},
+    {TAG_IPTC_NAA, "IPTC/NAA"},
+    {TAG_EXIF_OFFSET, "ExifOffset"},
+    {TAG_INTER_COLOR_PROFILE, "InterColorProfile"},
+    {TAG_EXPOSURE_PROGRAM, "ExposureProgram"},
+    {TAG_SPECTRAL_SENSITIVITY, "SpectralSensitivity"},
+    {TAG_GPSINFO, "GPS Dir offset"},
+    {TAG_ISO_EQUIVALENT, "ISOSpeedRatings"},
+    {TAG_OECF, "OECF"},
+    {TAG_EXIF_VERSION, "ExifVersion"},
+    {TAG_DATETIME_ORIGINAL, "DateTimeOriginal"},
+    {TAG_DATETIME_DIGITIZED, "DateTimeDigitized"},
+    {TAG_COMPONENTS_CONFIG, "ComponentsConfiguration"},
+    {TAG_CPRS_BITS_PER_PIXEL, "CompressedBitsPerPixel"},
+    {TAG_SHUTTERSPEED, "ShutterSpeedValue"},
+    {TAG_APERTURE, "ApertureValue"},
+    {TAG_BRIGHTNESS_VALUE, "BrightnessValue"},
+    {TAG_EXPOSURE_BIAS, "ExposureBiasValue"},
+    {TAG_MAXAPERTURE, "MaxApertureValue"},
+    {TAG_SUBJECT_DISTANCE, "SubjectDistance"},
+    {TAG_METERING_MODE, "MeteringMode"},
+    {TAG_LIGHT_SOURCE, "LightSource"},
+    {TAG_FLASH, "Flash"},
+    {TAG_FOCALLENGTH, "FocalLength"},
+    {TAG_MAKER_NOTE, "MakerNote"},
+    {TAG_USERCOMMENT, "UserComment"},
+    {TAG_SUBSEC_TIME, "SubSecTime"},
+    {TAG_SUBSEC_TIME_ORIG, "SubSecTimeOriginal"},
+    {TAG_SUBSEC_TIME_DIG, "SubSecTimeDigitized"},
+    {TAG_WINXP_TITLE, "Windows-XP Title"},
+    {TAG_WINXP_COMMENT, "Windows-XP comment"},
+    {TAG_WINXP_AUTHOR, "Windows-XP author"},
+    {TAG_WINXP_KEYWORDS, "Windows-XP keywords"},
+    {TAG_WINXP_SUBJECT, "Windows-XP subject"},
+    {TAG_FLASH_PIX_VERSION, "FlashPixVersion"},
+    {TAG_COLOR_SPACE, "ColorSpace"},
+    {TAG_PIXEL_X_DIMENSION, "ExifImageWidth"},
+    {TAG_PIXEL_Y_DIMENSION, "ExifImageLength"},
+    {TAG_RELATED_AUDIO_FILE, "RelatedAudioFile"},
+    {TAG_INTEROP_OFFSET, "InteroperabilityOffset"},
+    {TAG_FLASH_ENERGY, "FlashEnergy"},
+    {TAG_SPATIAL_FREQ_RESP, "SpatialFrequencyResponse"},
+    {TAG_FOCAL_PLANE_XRES, "FocalPlaneXResolution"},
+    {TAG_FOCAL_PLANE_YRES, "FocalPlaneYResolution"},
+    {TAG_FOCAL_PLANE_UNITS, "FocalPlaneResolutionUnit"},
+    {TAG_SUBJECT_LOCATION, "SubjectLocation"},
+    {TAG_EXPOSURE_INDEX, "ExposureIndex"},
+    {TAG_SENSING_METHOD, "SensingMethod"},
+    {TAG_FILE_SOURCE, "FileSource"},
+    {TAG_SCENE_TYPE, "SceneType"},
+    {TAG_CFA_PATTERN, "CFA Pattern"},
+    {TAG_CUSTOM_RENDERED, "CustomRendered"},
+    {TAG_EXPOSURE_MODE, "ExposureMode"},
+    {TAG_WHITEBALANCE, "WhiteBalance"},
+    {TAG_DIGITALZOOMRATIO, "DigitalZoomRatio"},
+    {TAG_FOCALLENGTH_35MM, "FocalLengthIn35mmFilm"},
+    {TAG_SUBJECTAREA, "SubjectArea"},
+    {TAG_SCENE_CAPTURE_TYPE, "SceneCaptureType"},
+    {TAG_GAIN_CONTROL, "GainControl"},
+    {TAG_CONTRAST, "Contrast"},
+    {TAG_SATURATION, "Saturation"},
+    {TAG_SHARPNESS, "Sharpness"},
+    {TAG_DISTANCE_RANGE, "SubjectDistanceRange"},
+    {TAG_IMAGE_UNIQUE_ID, "ImageUniqueId"},
+#endif
+};
+
+const int TAG_TABLE_SIZE = (sizeof(TagTable) / sizeof(TagTable_t));
+
+const int TRUE  = 1;
+const int FALSE = 0;
+}
+
+//--------------------------------------------------------------------------
+// Convert a 16 bit unsigned value from file's native byte order
+//--------------------------------------------------------------------------
+int JpgExifReader::Get16u(void *Short) {
+  if (MotorolaOrder) {
+    return (((uchar *)Short)[0] << 8) | ((uchar *)Short)[1];
+  } else {
+    return (((uchar *)Short)[1] << 8) | ((uchar *)Short)[0];
+  }
+}
+
+//--------------------------------------------------------------------------
+// Convert a 32 bit signed value from file's native byte order
+//--------------------------------------------------------------------------
+int JpgExifReader::Get32s(void *Long) {
+  if (MotorolaOrder) {
+    return (((char *)Long)[0] << 24) | (((uchar *)Long)[1] << 16) |
+           (((uchar *)Long)[2] << 8) | (((uchar *)Long)[3] << 0);
+  } else {
+    return (((char *)Long)[3] << 24) | (((uchar *)Long)[2] << 16) |
+           (((uchar *)Long)[1] << 8) | (((uchar *)Long)[0] << 0);
+  }
+}
+
+//--------------------------------------------------------------------------
+// Convert a 32 bit unsigned value from file's native byte order
+//--------------------------------------------------------------------------
+unsigned JpgExifReader::Get32u(void *Long) {
+  return (unsigned)Get32s(Long) & 0xffffffff;
+}
+
+//--------------------------------------------------------------------------
+// Display a number as one of its many formats
+//--------------------------------------------------------------------------
+void JpgExifReader::PrintFormatNumber(void *ValuePtr, int Format,
+                                      int ByteCount) {
+  int s, n;
+
+  for (n = 0; n < 16; n++) {
+    switch (Format) {
+    case FMT_SBYTE:
+    case FMT_BYTE:
+      printf("%02x", *(uchar *)ValuePtr);
+      s = 1;
+      break;
+    case FMT_USHORT:
+      printf("%d", Get16u(ValuePtr));
+      s = 2;
+      break;
+    case FMT_ULONG:
+    case FMT_SLONG:
+      printf("%d", Get32s(ValuePtr));
+      s = 4;
+      break;
+    case FMT_SSHORT:
+      printf("%hd", (signed short)Get16u(ValuePtr));
+      s = 2;
+      break;
+    case FMT_URATIONAL:
+      printf("%u/%u", Get32s(ValuePtr), Get32s(4 + (char *)ValuePtr));
+      s = 8;
+      break;
+
+    case FMT_SRATIONAL:
+      printf("%d/%d", Get32s(ValuePtr), Get32s(4 + (char *)ValuePtr));
+      s = 8;
+      break;
+
+    case FMT_SINGLE:
+      printf("%f", (double)*(float *)ValuePtr);
+      s = 8;
+      break;
+    case FMT_DOUBLE:
+      printf("%f", *(double *)ValuePtr);
+      s = 8;
+      break;
+    default:
+      printf("Unknown format %d:", Format);
+      return;
+    }
+    ByteCount -= s;
+    if (ByteCount <= 0) break;
+    printf(", ");
+    ValuePtr = (void *)((char *)ValuePtr + s);
+  }
+  if (n >= 16) printf("...");
+}
+
+//--------------------------------------------------------------------------
+// Evaluate number, be it int, rational, or float from directory.
+//--------------------------------------------------------------------------
+double JpgExifReader::ConvertAnyFormat(void *ValuePtr, int Format) {
+  double Value;
+  Value = 0;
+
+  switch (Format) {
+  case FMT_SBYTE:
+    Value = *(signed char *)ValuePtr;
+    break;
+  case FMT_BYTE:
+    Value = *(uchar *)ValuePtr;
+    break;
+
+  case FMT_USHORT:
+    Value = Get16u(ValuePtr);
+    break;
+  case FMT_ULONG:
+    Value = Get32u(ValuePtr);
+    break;
+
+  case FMT_URATIONAL:
+  case FMT_SRATIONAL: {
+    int Num, Den;
+    Num = Get32s(ValuePtr);
+    Den = Get32s(4 + (char *)ValuePtr);
+    if (Den == 0) {
+      Value = 0;
+    } else {
+      if (Format == FMT_SRATIONAL) {
+        Value = (double)Num / Den;
+      } else {
+        Value = (double)(unsigned)Num / (double)(unsigned)Den;
+      }
+    }
+    break;
+  }
+
+  case FMT_SSHORT:
+    Value = (signed short)Get16u(ValuePtr);
+    break;
+  case FMT_SLONG:
+    Value = Get32s(ValuePtr);
+    break;
+
+  // Not sure if this is correct (never seen float used in Exif format)
+  case FMT_SINGLE:
+    Value = (double)*(float *)ValuePtr;
+    break;
+  case FMT_DOUBLE:
+    Value = *(double *)ValuePtr;
+    break;
+
+  default:
+    std::cout << "Illegal format code " << Format << " in Exif header"
+              << std::endl;
+  }
+  return Value;
+}
+
+//--------------------------------------------------------------------------
+
+void JpgExifReader::process_EXIF(unsigned char *ExifSection,
+                                 unsigned int length) {
+  unsigned int FirstOffset;
+
+#ifdef ReadAllTags
+  FocalplaneXRes       = 0;
+  FocalplaneUnits      = 0;
+  ExifImageWidth       = 0;
+  NumOrientations      = 0;
+  DirWithThumbnailPtrs = NULL;
+#endif
+
+  if (ShowTags) printf("Exif header %u bytes long\n", length);
+
+  {  // Check the EXIF header component
+    static uchar ExifHeader[] = "Exif\0\0";
+    if (memcmp(ExifSection + 2, ExifHeader, 6)) {
+      std::cout << "Incorrect Exif header" << std::endl;
+      return;
+    }
+  }
+
+  if (memcmp(ExifSection + 8, "II", 2) == 0) {
+    if (ShowTags) printf("Exif section in Intel order\n");
+    MotorolaOrder = 0;
+  } else {
+    if (memcmp(ExifSection + 8, "MM", 2) == 0) {
+      if (ShowTags) printf("Exif section in Motorola order\n");
+      MotorolaOrder = 1;
+    } else {
+      std::cout << "Invalid Exif alignment marker." << std::endl;
+      return;
+    }
+  }
+
+  // Check the next value for correctness.
+  if (Get16u(ExifSection + 10) != 0x2a) {
+    std::cout << "Invalid Exif start (1)" << std::endl;
+    return;
+  }
+
+  FirstOffset = Get32u(ExifSection + 12);
+  if (FirstOffset < 8 || FirstOffset > 16) {
+    if (FirstOffset < 16 || FirstOffset > length - 16) {
+      std::cout << "invalid offset for first Exif IFD value" << std::endl;
+      return;
+    }
+    // Usually set to 8, but other values valid too.
+    std::cout << "Suspicious offset of first Exif IFD value" << std::endl;
+  }
+
+  // First directory starts 16 bytes in.  All offset are relative to 8 bytes in.
+  ProcessExifDir(ExifSection + 8 + FirstOffset, ExifSection + 8, length - 8, 0);
+
+#ifdef ReadAllTags
+  ImageInfo.ThumbnailAtEnd =
+      ImageInfo.ThumbnailOffset >= ImageInfo.LargestExifOffset ? TRUE : FALSE;
+
+  if (DumpExifMap) {
+    unsigned a, b;
+    printf("Map: %05d- End of exif\n", length - 8);
+    for (a = 0; a < length - 8; a += 10) {
+      printf("Map: %05d ", a);
+      for (b = 0; b < 10; b++) printf(" %02x", *(ExifSection + 8 + a + b));
+      printf("\n");
+    }
+  }
+
+  // Compute the CCD width, in millimeters.
+  if (FocalplaneXRes != 0 && ExifImageWidth != 0) {
+    // Note: With some cameras, its not possible to compute this correctly
+    // because
+    // they don't adjust the indicated focal plane resolution units when using
+    // less
+    // than maximum resolution, so the CCDWidth value comes out too small.
+    // Nothing
+    // that Jhad can do about it - its a camera problem.
+    ImageInfo.CCDWidth =
+        (float)(ExifImageWidth * FocalplaneUnits / FocalplaneXRes);
+
+    if (ImageInfo.FocalLength && ImageInfo.FocalLength35mmEquiv == 0) {
+      // Compute 35 mm equivalent focal length based on sensor geometry if we
+      // haven't
+      // already got it explicitly from a tag.
+      ImageInfo.FocalLength35mmEquiv =
+          (int)(ImageInfo.FocalLength / ImageInfo.CCDWidth * 36 + 0.5);
+    }
+  }
+#endif
+}
+
+//--------------------------------------------------------------------------
+// Process one of the nested EXIF directories.
+//--------------------------------------------------------------------------
+void JpgExifReader::ProcessExifDir(unsigned char *DirStart,
+                                   unsigned char *OffsetBase,
+                                   unsigned ExifLength, int NestingLevel) {
+  int de;
+  int a;
+  int NumDirEntries;
+  unsigned ThumbnailOffset = 0;
+  unsigned ThumbnailSize   = 0;
+  char IndentString[25];
+
+  if (NestingLevel > 4) {
+    std::cout << "Maximum Exif directory nesting exceeded (corrupt Exif header)"
+              << std::endl;
+    return;
+  }
+
+  memset(IndentString, ' ', 25);
+  IndentString[NestingLevel * 4] = '\0';
+
+  NumDirEntries = Get16u(DirStart);
+#define DIR_ENTRY_ADDR(Start, Entry) (Start + 2 + 12 * (Entry))
+
+  {
+    unsigned char *DirEnd;
+    DirEnd = DIR_ENTRY_ADDR(DirStart, NumDirEntries);
+    if (DirEnd + 4 > (OffsetBase + ExifLength)) {
+      if (DirEnd + 2 == OffsetBase + ExifLength ||
+          DirEnd == OffsetBase + ExifLength) {
+        // Version 1.3 of jhead would truncate a bit too much.
+        // This also caught later on as well.
+      } else {
+        std::cout << "Illegally sized Exif subdirectory (" << NumDirEntries
+                  << "entries)" << std::endl;
+        return;
+      }
+    }
+    if (DumpExifMap) {
+      printf("Map: %05u-%05u: Directory\n", (int)(DirStart - OffsetBase),
+             (int)(DirEnd + 4 - OffsetBase));
+    }
+  }
+
+  if (ShowTags) {
+    printf("(dir has %d entries)\n", NumDirEntries);
+  }
+
+  for (de = 0; de < NumDirEntries; de++) {
+    int Tag, Format, Components;
+    unsigned char *ValuePtr;
+    int ByteCount;
+    unsigned char *DirEntry;
+    DirEntry = DIR_ENTRY_ADDR(DirStart, de);
+
+    Tag        = Get16u(DirEntry);
+    Format     = Get16u(DirEntry + 2);
+    Components = Get32u(DirEntry + 4);
+
+    if ((Format - 1) >= NUM_FORMATS) {
+      // (-1) catches illegal zero case as unsigned underflows to positive
+      // large.
+      std::cout << "Illegal number format " << Format << " for tag " << Tag
+                << " in Exif" << std::endl;
+      continue;
+    }
+
+    if ((unsigned)Components > 0x10000) {
+      std::cout << "Too many components " << Components << " for tag " << Tag
+                << " in Exif";
+      continue;
+    }
+
+    ByteCount = Components * BytesPerFormat[Format];
+
+    if (ByteCount > 4) {
+      unsigned OffsetVal;
+      OffsetVal = Get32u(DirEntry + 8);
+      // If its bigger than 4 bytes, the dir entry contains an offset.
+      if (OffsetVal + ByteCount > ExifLength) {
+        // Bogus pointer offset and / or bytecount value
+        std::cout << "Illegal value pointer for tag " << Tag << " in Exif";
+        continue;
+      }
+      ValuePtr = OffsetBase + OffsetVal;
+
+#ifdef ReadAllTags
+      if (OffsetVal > ImageInfo.LargestExifOffset) {
+        ImageInfo.LargestExifOffset = OffsetVal;
+      }
+#endif
+
+      if (DumpExifMap) {
+        printf("Map: %05u-%05u:   Data for tag %04x\n", OffsetVal,
+               OffsetVal + ByteCount, Tag);
+      }
+    } else {
+      // 4 bytes or less and value is in the dir entry itself
+      ValuePtr = DirEntry + 8;
+    }
+
+#ifdef ReadAllTags
+    if (Tag == TAG_MAKER_NOTE) {
+      if (ShowTags) {
+        printf("%s    Maker note: ", IndentString);
+      }
+      // ProcessMakerNote(ValuePtr, ByteCount, OffsetBase, ExifLength);
+      continue;
+    }
+
+    if (ShowTags) {
+      // Show tag name
+      for (a = 0;; a++) {
+        if (a >= TAG_TABLE_SIZE) {
+          printf("%s    Unknown Tag %04x Value = ", IndentString, Tag);
+          break;
+        }
+        if (TagTable[a].Tag == Tag) {
+          printf("%s    %s = ", IndentString, TagTable[a].Desc);
+          break;
+        }
+      }
+
+      // Show tag value.
+      switch (Format) {
+      case FMT_BYTE:
+        if (ByteCount > 1) {
+          printf("%.*ls\n", ByteCount / 2, (wchar_t *)ValuePtr);
+        } else {
+          PrintFormatNumber(ValuePtr, Format, ByteCount);
+          printf("\n");
+        }
+        break;
+
+      case FMT_UNDEFINED:
+      // Undefined is typically an ascii string.
+
+      case FMT_STRING:
+        // String arrays printed without function call (different from int
+        // arrays)
+        {
+          int NoPrint = 0;
+          printf("\"");
+          for (a = 0; a < ByteCount; a++) {
+            if (ValuePtr[a] >= 32) {
+              putchar(ValuePtr[a]);
+              NoPrint = 0;
+            } else {
+              // Avoiding indicating too many unprintable characters of
+              // proprietary
+              // bits of binary information this program may not know how to
+              // parse.
+              if (!NoPrint && a != ByteCount - 1) {
+                putchar('?');
+                NoPrint = 1;
+              }
+            }
+          }
+          printf("\"\n");
+        }
+        break;
+
+      default:
+        // Handle arrays of numbers later (will there ever be?)
+        PrintFormatNumber(ValuePtr, Format, ByteCount);
+        printf("\n");
+      }
+    }
+#endif
+    // Extract useful components of tag
+    switch (Tag) {
+#ifdef ReadAllTags
+    case TAG_MAKE:
+      strncpy(ImageInfo.CameraMake, (char *)ValuePtr,
+              ByteCount < 31 ? ByteCount : 31);
+      break;
+
+    case TAG_MODEL:
+      strncpy(ImageInfo.CameraModel, (char *)ValuePtr,
+              ByteCount < 39 ? ByteCount : 39);
+      break;
+
+    case TAG_DATETIME_ORIGINAL:
+      // If we get a DATETIME_ORIGINAL, we use that one.
+      strncpy(ImageInfo.DateTime, (char *)ValuePtr, 19);
+    // Fallthru...
+
+    case TAG_DATETIME_DIGITIZED:
+    case TAG_DATETIME:
+      if (!isdigit(static_cast<unsigned char>(ImageInfo.DateTime[0]))) {
+        // If we don't already have a DATETIME_ORIGINAL, use whatever
+        // time fields we may have.
+        strncpy(ImageInfo.DateTime, (char *)ValuePtr, 19);
+      }
+
+      if (ImageInfo.numDateTimeTags >= MAX_DATE_COPIES) {
+        std::cout << "More than " << MAX_DATE_COPIES
+                  << " date fields in Exif.  This is nuts" << std::endl;
+        break;
+      }
+      ImageInfo.DateTimeOffsets[ImageInfo.numDateTimeTags++] =
+          (char *)ValuePtr - (char *)OffsetBase;
+      break;
+
+    case TAG_WINXP_COMMENT:
+      if (ImageInfo.Comments[0]) {  // We already have a jpeg comment.
+        // Already have a comment (probably windows comment), skip this one.
+        if (ShowTags)
+          printf("Windows XP commend and other comment in header\n");
+        break;  // Already have a windows comment, skip this one.
+      }
+
+      if (ByteCount > 1) {
+        if (ByteCount > MAX_COMMENT_SIZE) ByteCount = MAX_COMMENT_SIZE;
+        memcpy(ImageInfo.Comments, ValuePtr, ByteCount);
+        ImageInfo.CommentWidthchars = ByteCount / 2;
+      }
+      break;
+
+    case TAG_USERCOMMENT:
+      if (ImageInfo.Comments[0]) {  // We already have a jpeg comment.
+        // Already have a comment (probably windows comment), skip this one.
+        if (ShowTags) printf("Multiple comments in exif header\n");
+        break;  // Already have a windows comment, skip this one.
+      }
+
+      // Comment is often padded with trailing spaces.  Remove these first.
+      for (a = ByteCount;;) {
+        a--;
+        if ((ValuePtr)[a] == ' ') {
+          (ValuePtr)[a] = '\0';
+        } else {
+          break;
+        }
+        if (a == 0) break;
+      }
+
+      // Copy the comment
+      {
+        int msiz                   = ExifLength - (ValuePtr - OffsetBase);
+        if (msiz > ByteCount) msiz = ByteCount;
+        if (msiz > MAX_COMMENT_SIZE - 1) msiz = MAX_COMMENT_SIZE - 1;
+        if (msiz > 5 && memcmp(ValuePtr, "ASCII", 5) == 0) {
+          for (a = 5; a < 10 && a < msiz; a++) {
+            int c = (ValuePtr)[a];
+            if (c != '\0' && c != ' ') {
+              strncpy(ImageInfo.Comments, (char *)ValuePtr + a, msiz - a);
+              break;
+            }
+          }
+        } else {
+          strncpy(ImageInfo.Comments, (char *)ValuePtr, msiz);
+        }
+      }
+      break;
+
+    case TAG_FNUMBER:
+      // Simplest way of expressing aperture, so I trust it the most.
+      // (overwrite previously computd value if there is one)
+      ImageInfo.ApertureFNumber = (float)ConvertAnyFormat(ValuePtr, Format);
+      break;
+
+    case TAG_APERTURE:
+    case TAG_MAXAPERTURE:
+      // More relevant info always comes earlier, so only use this field if we
+      // don't
+      // have appropriate aperture information yet.
+      if (ImageInfo.ApertureFNumber == 0) {
+        ImageInfo.ApertureFNumber =
+            (float)exp(ConvertAnyFormat(ValuePtr, Format) * log(2) * 0.5);
+      }
+      break;
+
+    case TAG_FOCALLENGTH:
+      // Nice digital cameras actually save the focal length as a function
+      // of how farthey are zoomed in.
+      ImageInfo.FocalLength = (float)ConvertAnyFormat(ValuePtr, Format);
+      break;
+
+    case TAG_SUBJECT_DISTANCE:
+      // Inidcates the distacne the autofocus camera is focused to.
+      // Tends to be less accurate as distance increases.
+      ImageInfo.Distance = (float)ConvertAnyFormat(ValuePtr, Format);
+      break;
+
+    case TAG_EXPOSURETIME:
+      // Simplest way of expressing exposure time, so I trust it most.
+      // (overwrite previously computd value if there is one)
+      ImageInfo.ExposureTime = (float)ConvertAnyFormat(ValuePtr, Format);
+      break;
+
+    case TAG_SHUTTERSPEED:
+      // More complicated way of expressing exposure time, so only use
+      // this value if we don't already have it from somewhere else.
+      if (ImageInfo.ExposureTime == 0) {
+        ImageInfo.ExposureTime =
+            (float)(1 / exp(ConvertAnyFormat(ValuePtr, Format) * log(2)));
+      }
+      break;
+
+    case TAG_FLASH:
+      ImageInfo.FlashUsed = (int)ConvertAnyFormat(ValuePtr, Format);
+      break;
+
+    case TAG_ORIENTATION:
+      if (NumOrientations >= 2) {
+        // Can have another orientation tag for the thumbnail, but if there's
+        // a third one, things are strange.
+        std::cout << "More than two orientation in Exif" << std::endl;
+        break;
+      }
+      OrientationPtr[NumOrientations]       = ValuePtr;
+      OrientationNumFormat[NumOrientations] = Format;
+      if (NumOrientations == 0) {
+        ImageInfo.Orientation = (int)ConvertAnyFormat(ValuePtr, Format);
+      }
+      if (ImageInfo.Orientation < 0 || ImageInfo.Orientation > 8) {
+        std::cout << "Undefined rotation value " << ImageInfo.Orientation
+                  << " in Exif" << std::endl;
+      }
+      NumOrientations += 1;
+      break;
+
+    case TAG_PIXEL_Y_DIMENSION:
+    case TAG_PIXEL_X_DIMENSION:
+      // Use largest of height and width to deal with images that have been
+      // rotated to portrait format.
+      a = (int)ConvertAnyFormat(ValuePtr, Format);
+      if (ExifImageWidth < a) ExifImageWidth = a;
+      break;
+
+    case TAG_FOCAL_PLANE_XRES:
+      FocalplaneXRes = ConvertAnyFormat(ValuePtr, Format);
+      break;
+
+    case TAG_FOCAL_PLANE_UNITS:
+      switch ((int)ConvertAnyFormat(ValuePtr, Format)) {
+      case 1:
+        FocalplaneUnits = 25.4;
+        break;  // inch
+      case 2:
+        // According to the information I was using, 2 means meters.
+        // But looking at the Cannon powershot's files, inches is the only
+        // sensible value.
+        FocalplaneUnits = 25.4;
+        break;
+
+      case 3:
+        FocalplaneUnits = 10;
+        break;  // centimeter
+      case 4:
+        FocalplaneUnits = 1;
+        break;  // millimeter
+      case 5:
+        FocalplaneUnits = .001;
+        break;  // micrometer
+      }
+      break;
+
+    case TAG_EXPOSURE_BIAS:
+      ImageInfo.ExposureBias = (float)ConvertAnyFormat(ValuePtr, Format);
+      break;
+
+    case TAG_WHITEBALANCE:
+      ImageInfo.Whitebalance = (int)ConvertAnyFormat(ValuePtr, Format);
+      break;
+
+    case TAG_LIGHT_SOURCE:
+      ImageInfo.LightSource = (int)ConvertAnyFormat(ValuePtr, Format);
+      break;
+
+    case TAG_METERING_MODE:
+      ImageInfo.MeteringMode = (int)ConvertAnyFormat(ValuePtr, Format);
+      break;
+
+    case TAG_EXPOSURE_PROGRAM:
+      ImageInfo.ExposureProgram = (int)ConvertAnyFormat(ValuePtr, Format);
+      break;
+
+    case TAG_EXPOSURE_INDEX:
+      if (ImageInfo.ISOequivalent == 0) {
+        // Exposure index and ISO equivalent are often used interchangeably,
+        // so we will do the same in jhead.
+        // http://photography.about.com/library/glossary/bldef_ei.htm
+        ImageInfo.ISOequivalent = (int)ConvertAnyFormat(ValuePtr, Format);
+      }
+      break;
+
+    case TAG_EXPOSURE_MODE:
+      ImageInfo.ExposureMode = (int)ConvertAnyFormat(ValuePtr, Format);
+      break;
+
+    case TAG_ISO_EQUIVALENT:
+      ImageInfo.ISOequivalent = (int)ConvertAnyFormat(ValuePtr, Format);
+      break;
+
+    case TAG_DIGITALZOOMRATIO:
+      ImageInfo.DigitalZoomRatio = (float)ConvertAnyFormat(ValuePtr, Format);
+      break;
+
+    case TAG_THUMBNAIL_OFFSET:
+      ThumbnailOffset      = (unsigned)ConvertAnyFormat(ValuePtr, Format);
+      DirWithThumbnailPtrs = DirStart;
+      break;
+
+    case TAG_THUMBNAIL_LENGTH:
+      ThumbnailSize = (unsigned)ConvertAnyFormat(ValuePtr, Format);
+      ImageInfo.ThumbnailSizeOffset = ValuePtr - OffsetBase;
+      break;
+
+    case TAG_EXIF_OFFSET:
+      if (ShowTags) printf("%s    Exif Dir:", IndentString);
+
+    case TAG_INTEROP_OFFSET:
+      if (Tag == TAG_INTEROP_OFFSET && ShowTags)
+        printf("%s    Interop Dir:", IndentString);
+      {
+        unsigned char *SubdirStart;
+        SubdirStart = OffsetBase + Get32u(ValuePtr);
+        if (SubdirStart < OffsetBase || SubdirStart > OffsetBase + ExifLength) {
+          std::cout << "Illegal Exif or interop ofset directory link"
+                    << std::endl;
+        } else {
+          ProcessExifDir(SubdirStart, OffsetBase, ExifLength, NestingLevel + 1);
+        }
+        continue;
+      }
+      break;
+
+    case TAG_GPSINFO:
+      if (ShowTags) printf("%s    GPS info dir:", IndentString);
+      {
+        unsigned char *SubdirStart;
+        SubdirStart = OffsetBase + Get32u(ValuePtr);
+        if (SubdirStart < OffsetBase || SubdirStart > OffsetBase + ExifLength) {
+          std::cout << "Illegal GPS directory link in Exif" << std::endl;
+        } else {
+          // ProcessGpsInfo(SubdirStart, OffsetBase, ExifLength);
+        }
+        continue;
+      }
+      break;
+
+    case TAG_FOCALLENGTH_35MM:
+      // The focal length equivalent 35 mm is a 2.2 tag (defined as of April
+      // 2002)
+      // if its present, use it to compute equivalent focal length instead of
+      // computing it from sensor geometry and actual focal length.
+      ImageInfo.FocalLength35mmEquiv =
+          (unsigned)ConvertAnyFormat(ValuePtr, Format);
+      break;
+
+    case TAG_DISTANCE_RANGE:
+      // Three possible standard values:
+      //   1 = macro, 2 = close, 3 = distant
+      ImageInfo.DistanceRange = (int)ConvertAnyFormat(ValuePtr, Format);
+      break;
+
+#endif
+
+    case TAG_X_RESOLUTION:
+      if (NestingLevel ==
+          0) {  // Only use the values from the top level directory
+        ImageInfo.xResolution = (float)ConvertAnyFormat(ValuePtr, Format);
+        // if yResolution has not been set, use the value of xResolution
+        if (ImageInfo.yResolution == 0.0)
+          ImageInfo.yResolution = ImageInfo.xResolution;
+      }
+      break;
+
+    case TAG_Y_RESOLUTION:
+      if (NestingLevel ==
+          0) {  // Only use the values from the top level directory
+        ImageInfo.yResolution = (float)ConvertAnyFormat(ValuePtr, Format);
+        // if xResolution has not been set, use the value of yResolution
+        if (ImageInfo.xResolution == 0.0)
+          ImageInfo.xResolution = ImageInfo.yResolution;
+      }
+      break;
+
+    case TAG_RESOLUTION_UNIT:
+      if (NestingLevel ==
+          0) {  // Only use the values from the top level directory
+        ImageInfo.ResolutionUnit = (int)ConvertAnyFormat(ValuePtr, Format);
+      }
+      break;
+    }
+  }
+
+#ifdef ReadAllTags
+  {
+    // In addition to linking to subdirectories via exif tags,
+    // there's also a potential link to another directory at the end of each
+    // directory.  this has got to be the result of a committee!
+    unsigned char *SubdirStart;
+    unsigned Offset;
+
+    if (DIR_ENTRY_ADDR(DirStart, NumDirEntries) + 4 <=
+        OffsetBase + ExifLength) {
+      Offset = Get32u(DirStart + 2 + 12 * NumDirEntries);
+      if (Offset) {
+        SubdirStart = OffsetBase + Offset;
+        if (SubdirStart > OffsetBase + ExifLength || SubdirStart < OffsetBase) {
+          if (SubdirStart > OffsetBase &&
+              SubdirStart < OffsetBase + ExifLength + 20) {
+            // Jhead 1.3 or earlier would crop the whole directory!
+            // As Jhead produces this form of format incorrectness,
+            // I'll just let it pass silently
+            if (ShowTags)
+              printf("Thumbnail removed with Jhead 1.3 or earlier\n");
+          } else {
+            std::cout << "Illegal subdirectory link in Exif header"
+                      << std::endl;
+          }
+        } else {
+          if (SubdirStart <= OffsetBase + ExifLength) {
+            if (ShowTags) printf("%s    Continued directory ", IndentString);
+            ProcessExifDir(SubdirStart, OffsetBase, ExifLength,
+                           NestingLevel + 1);
+          }
+        }
+        if (Offset > ImageInfo.LargestExifOffset) {
+          ImageInfo.LargestExifOffset = Offset;
+        }
+      }
+    } else {
+      // The exif header ends before the last next directory pointer.
+    }
+  }
+
+  if (ThumbnailOffset) {
+    ImageInfo.ThumbnailAtEnd = FALSE;
+
+    if (DumpExifMap) {
+      printf("Map: %05d-%05d: Thumbnail\n", ThumbnailOffset,
+             ThumbnailOffset + ThumbnailSize);
+    }
+
+    if (ThumbnailOffset <= ExifLength) {
+      if (ThumbnailSize > ExifLength - ThumbnailOffset) {
+        // If thumbnail extends past exif header, only save the part that
+        // actually exists.  Canon's EOS viewer utility will do this - the
+        // thumbnail extracts ok with this hack.
+        ThumbnailSize = ExifLength - ThumbnailOffset;
+        if (ShowTags) printf("Thumbnail incorrectly placed in header\n");
+      }
+      // The thumbnail pointer appears to be valid.  Store it.
+      ImageInfo.ThumbnailOffset = ThumbnailOffset;
+      ImageInfo.ThumbnailSize   = ThumbnailSize;
+
+      if (ShowTags) {
+        printf("Thumbnail size: %u bytes\n", ThumbnailSize);
+      }
+    }
+  }
+#endif
+}
diff --git a/toonz/sources/include/tiio_jpg_exif.h b/toonz/sources/include/tiio_jpg_exif.h
new file mode 100644
index 0000000..1d619d8
--- /dev/null
+++ b/toonz/sources/include/tiio_jpg_exif.h
@@ -0,0 +1,139 @@
+/*-------------------------------------------------------------
+tiio_jpg_exif.h
+Based on source code of a public domain software "Exif Jpeg header manipulation
+tool (jhead)" by Matthias Wandel.
+For now it is used only for obtaining resolution values.
+http://www.sentex.net/~mwandel/jhead/
+-------------------------------------------------------------*/
+#pragma once
+#ifndef TIIO_JPG_EXIF_H
+#define TIIO_JPG_EXIF_H
+
+#include <stdlib.h>
+
+// undefining ReadAllTags will load only resolution infomation
+#define ReadAllTags
+#undef ReadAllTags
+
+#define MAX_COMMENT_SIZE 16000
+#define MAX_DATE_COPIES 10
+
+#ifdef _WIN32
+#define PATH_MAX _MAX_PATH
+#define SLASH '\\'
+#else
+#ifndef PATH_MAX
+#define PATH_MAX 1024
+#endif
+#define SLASH '/'
+#endif
+
+class JpgExifReader {
+  enum FORMATS {
+    FMT_BYTE = 1,
+    FMT_STRING,
+    FMT_USHORT,
+    FMT_ULONG,
+    FMT_URATIONAL,
+    FMT_SBYTE,
+    FMT_UNDEFINED,
+    FMT_SSHORT,
+    FMT_SLONG,
+    FMT_SRATIONAL,
+    FMT_SINGLE,
+    FMT_DOUBLE
+  };
+
+  int NUM_FORMATS = FMT_DOUBLE;
+
+  struct ExifImageInfo {
+    float xResolution = 0.0;
+    float yResolution = 0.0;
+    int ResolutionUnit;
+#ifdef ReadAllTags
+    char FileName[PATH_MAX + 1];
+    time_t FileDateTime;
+
+    unsigned FileSize;
+    char CameraMake[32];
+    char CameraModel[40];
+    char DateTime[20];
+    unsigned Height, Width;
+    int Orientation;
+    int IsColor;
+    int Process;
+    int FlashUsed;
+    float FocalLength;
+    float ExposureTime;
+    float ApertureFNumber;
+    float Distance;
+    float CCDWidth;
+    float ExposureBias;
+    float DigitalZoomRatio;
+    int FocalLength35mmEquiv;  // Exif 2.2 tag - usually not present.
+    int Whitebalance;
+    int MeteringMode;
+    int ExposureProgram;
+    int ExposureMode;
+    int ISOequivalent;
+    int LightSource;
+    int DistanceRange;
+
+    char Comments[MAX_COMMENT_SIZE];
+    int CommentWidthchars;  // If nonzero, widechar comment, indicates number of
+                            // chars.
+
+    unsigned ThumbnailOffset;    // Exif offset to thumbnail
+    unsigned ThumbnailSize;      // Size of thumbnail.
+    unsigned LargestExifOffset;  // Last exif data referenced (to check if
+                                 // thumbnail is at end)
+
+    char ThumbnailAtEnd;  // Exif header ends with the thumbnail
+    // (we can only modify the thumbnail if its at the end)
+    int ThumbnailSizeOffset;
+
+    int DateTimeOffsets[MAX_DATE_COPIES];
+    int numDateTimeTags = 0;
+
+    int GpsInfoPresent;
+    char GpsLat[31];
+    char GpsLong[31];
+    char GpsAlt[20];
+
+    int QualityGuess;
+#endif
+  } ImageInfo;
+
+#ifdef ReadAllTags
+  unsigned char* DirWithThumbnailPtrs;
+  double FocalplaneXRes;
+  double FocalplaneUnits;
+  int ExifImageWidth;
+
+  // for fixing the rotation.
+  void* OrientationPtr[2];
+  int OrientationNumFormat[2];
+  int NumOrientations = 0;
+#endif
+
+  int MotorolaOrder = 0;
+
+  int Get16u(void* Short);
+  unsigned Get32u(void* Long);
+  int Get32s(void* Long);
+  void PrintFormatNumber(void* ValuePtr, int Format, int ByteCount);
+  double ConvertAnyFormat(void* ValuePtr, int Format);
+
+public:
+  void process_EXIF(unsigned char* ExifSection, unsigned int length);
+  void ProcessExifDir(unsigned char* DirStart, unsigned char* OffsetBase,
+                      unsigned ExifLength, int NestingLevel);
+
+  // obtaining resolution info
+  bool containsResolution() { return ImageInfo.xResolution != 0.0; }
+  float getXResolution() { return ImageInfo.xResolution; }
+  float getYResolution() { return ImageInfo.yResolution; }
+  int getResolutionUnit() { return ImageInfo.ResolutionUnit; }
+};
+
+#endif
\ No newline at end of file
diff --git a/toonz/sources/tnzcore/CMakeLists.txt b/toonz/sources/tnzcore/CMakeLists.txt
index 16603f9..658c70e 100644
--- a/toonz/sources/tnzcore/CMakeLists.txt
+++ b/toonz/sources/tnzcore/CMakeLists.txt
@@ -112,6 +112,7 @@ set(HEADERS ${MOC_HEADERS}
     ../include/tiio_bmp.h
     ../include/tiio_jpg.h
     ../include/tiio_jpg_util.h
+    ../include/tiio_jpg_exif.h
     ../include/tiio_std.h
     ../include/ttoonzimage.h
     ../include/tipc.h
@@ -226,6 +227,7 @@ set(SOURCES
     ../common/tiio/tiio_bmp.cpp
     ../common/tiio/tiio_jpg.cpp
     ../common/tiio/tiio_jpg_util.cpp
+    ../common/tiio/tiio_jpg_exif.cpp
     ../common/tiio/tiio_std.cpp
     ../common/tiio/movsettings.cpp
     ../common/tiio/compatibility/tfile_io.c