diff --git a/rtengine/imagedata.cc b/rtengine/imagedata.cc index a46e07c33..869fd0775 100644 --- a/rtengine/imagedata.cc +++ b/rtengine/imagedata.cc @@ -119,7 +119,7 @@ void ImageData::extractInfo () { static const char *corp[] = { "Canon", "NIKON", "EPSON", "KODAK", "Kodak", "OLYMPUS", "PENTAX", "MINOLTA", "Minolta", "Konica", "CASIO", "Sinar", "Phase One", - "SAMSUNG", "Mamiya", "MOTOROLA" }; + "SAMSUNG", "Mamiya", "MOTOROLA", "Leaf" }; for (size_t i=0; i < (sizeof(corp)/sizeof(*corp)); i++) if ( make.find( corp[i] ) != std::string::npos ){ /* Simplify company names */ make = corp[i]; @@ -175,6 +175,7 @@ void ImageData::extractInfo () { if (sscanf ((const char*)exif->getTag("DateTimeOriginal")->getValue(), "%d:%d:%d %d:%d:%d", &time.tm_year, &time.tm_mon, &time.tm_mday, &time.tm_hour, &time.tm_min, &time.tm_sec) == 6) { time.tm_year -= 1900; time.tm_mon -= 1; + time.tm_isdst = -1; timeStamp = mktime(&time); } } diff --git a/rtengine/imageio.cc b/rtengine/imageio.cc index 0c8061a28..1aed31061 100644 --- a/rtengine/imageio.cc +++ b/rtengine/imageio.cc @@ -644,7 +644,7 @@ int ImageIO::saveJPEG (Glib::ustring fname, int quality) { jpeg_start_compress(&cinfo, TRUE); // buffer for exif and iptc markers - unsigned char* buffer = new unsigned char[165535]; //TODO: Is it really 165535... or 65535 ? + unsigned char* buffer = new unsigned char[165535]; //FIXME: no buffer size check so it can be overflowed in createJPEGMarker() for large tags, and then software will crash unsigned int size; // assemble and write exif marker if (exifRoot) { diff --git a/rtexif/rtexif.cc b/rtexif/rtexif.cc index 2de29d2b7..bf000b23d 100644 --- a/rtexif/rtexif.cc +++ b/rtexif/rtexif.cc @@ -1308,6 +1308,249 @@ void ExifManager::parseCIFF (FILE* f, int base, int length, TagDirectory* root) } } +static void +parse_leafdata(TagDirectory* root, ByteOrder order) { + + Tag *leafdata = root->getTag("LeafData"); + if (!leafdata) { + return; + } + unsigned char *value = leafdata->getValue(); + int valuesize = leafdata->getValueSize(); + + // parse LeafData tag, a tag specific to Leaf digital backs, and has a custom + // format with 52 byte tag headers starting with "PKTS". + char *hdr; + int pos = 0; + + // There are lots of sub-tags in here, but for now we only care about those directly + // useful to RT, which is ISO and rotation. Shutter speed and aperture is not + // available here. + int iso_speed = 0; + int rotation_angle = 0; + int found_count = 0; + while (pos + sizeof(hdr) <= valuesize && found_count < 2) { + hdr = (char *)&value[pos]; + if (strncmp(hdr, "PKTS", 4) != 0) { + // in a few cases the header can be offset a few bytes, don't know why + // it does not seem to be some sort of alignment, it appears random, + // this check takes care of it, restart if we find an offset match. + int offset = 1; + for (; offset <= 3; offset++) { + if (strncmp(&hdr[offset], "PKTS", 4) == 0) { + pos += offset; + break; + } + } + if (offset <= 3) { + continue; + } + break; + } + int size = sget4((unsigned char *)&hdr[48], order); + if (pos + size > valuesize) { + break; + } + pos += 52; + char *val = (char *)&value[pos]; + if (strncmp(&hdr[8], "CameraObj_ISO_speed", 19) == 0) { + iso_speed = 25 * (1 << (atoi(val) - 1)); + found_count++; + } else if (strncmp(&hdr[8], "ImgProf_rotation_angle", 22) == 0) { + rotation_angle = atoi(val); + found_count++; + } else { + // check if this is a sub-directory, include test for that strange offset of next header + if (size >= 8 && + (strncmp(val, "PKTS", 4) == 0 || + strncmp(&val[1], "PKTS", 4) == 0 || + strncmp(&val[2], "PKTS", 4) == 0 || + strncmp(&val[3], "PKTS", 4) == 0)) + { + // start of next hdr, this is a sub-directory, we skip those for now. + size = 0; + } + } + pos += size; + } + + // create standard tags from the custom Leaf tags + Tag* exif = root->getTag ("Exif"); + if (!exif) { + exif = new Tag (root, root->getAttrib ("Exif")); + exif->initSubDir(); + root->addTagFront (exif); + } + if (!exif->getDirectory()->getTag("ISOSpeedRatings")) { + Tag *t = new Tag (exif->getDirectory(), exif->getDirectory()->getAttrib ("ISOSpeedRatings")); + t->initInt (iso_speed, LONG); + exif->getDirectory()->addTagFront (t); + } + if (!root->getTag("Orientation")) { + int orientation; + switch (rotation_angle) { + case 0: orientation = 1; break; + case 90: orientation = 6; break; + case 180: orientation = 3; break; + case 270: orientation = 8; break; + default: orientation = 1; break; + } + Tag *t = new Tag (root, root->getAttrib ("Orientation")); + t->initInt (orientation, SHORT); + root->addTagFront (t); + } + + // now look in ApplicationNotes tag for additional information + Tag *appnotes = root->getTag("ApplicationNotes"); + if (!appnotes) { + return; + } + char *xmp = (char *)appnotes->getValue(); + char *end, *p; + // Quick-and-dirty value extractor, no real xml parsing. + // We could make it more generic, but we just get most important + // values we know use to be in there. + if ((p = strstr(xmp, "xmlns:tiff")) != NULL && + (end = strstr(p, "")) != NULL) + { + *end = '\0'; + while ((p = strstr(p, "')) == NULL) { + break; + } + *tagend = '\0'; + char *val = &tagend[1]; + if ((p = strstr(val, "getAttrib (tag) && !root->getTag (tag)) { + Tag *t = new Tag (root, root->getAttrib (tag)); + if (strcmp(tag, "Make") == 0 || + strcmp(tag, "Model") == 0) + { + t->initString (val); + root->addTagFront (t); + } else { + delete t; + } + } + *p = '<'; + *tagend = '>'; + } + *end = '<'; + } + if ((p = strstr(xmp, "xmlns:exif")) != NULL && + (end = strstr(p, "")) != NULL) + { + *end = '\0'; + while ((p = strstr(p, "')) == NULL) { + break; + } + *tagend = '\0'; + char *val = &tagend[1]; + if ((p = strstr(val, "getDirectory()->getAttrib (tag) && !exif->getDirectory()->getTag (tag)) { + Tag *t = new Tag (exif->getDirectory(), exif->getDirectory()->getAttrib (tag)); + int num, denom; + struct tm tm; + if (strcmp(tag, "ApertureValue") == 0 && sscanf(val, "%d/%d", &num, &denom) == 2) { + t->initRational (num, denom); + exif->getDirectory()->addTagFront (t); + // we also make an "FNumber" tag since many tools don't interpret ApertureValue + // according to Exif standard + t = new Tag (exif->getDirectory(), lookupAttrib(exifAttribs,"FNumber")); + double f = pow(sqrt(2.0), ((double)num / denom)); + if (f > 10.0) { + t->initRational ((int)floor(f), 1); + } else { + t->initRational ((int)floor(f * 10.0), 10); + } + exif->getDirectory()->addTagFront (t); + } else if (strcmp(tag, "ShutterSpeedValue") == 0 && sscanf(val, "%d/%d", &num, &denom) == 2) { + t->initRational (num, denom); + exif->getDirectory()->addTagFront (t); + // we also make an "ExposureTime" tag since many tools don't interpret ShutterSpeedValue + // according to Exif standard + t = new Tag (exif->getDirectory(), lookupAttrib(exifAttribs,"ExposureTime")); + double f = 1.0 / pow(2.0, ((double)num / denom)); + if (f > 10.0) { + t->initRational ((int)floor(f), 1); + } else if (f > 1.0) { + t->initRational ((int)floor(f * 10.0), 10); + } else if (f == 1.0) { + t->initRational (1, 1); + } else { + f = 1.0 / f; + static const double etimes[] = { + 10000, 8000, 6400, 6000, 5000, + 4000, 3200, 3000, 2500, + 2000, 1600, 1500, 1250, + 1000, 800, 750, 640, + 500, 400, 350, 320, + 250, 200, 180, 160, + 125, 100, 90, 80, + 60, 50, 45, 40, + 30, 25, 22, 20, + 15, 13, 11, 10, + 8, 6, 5, + 4, 3, 2.5, + 2, 1.6, 1.5, 1.3, + 1, -1 }; + double diff = etimes[0]; + int idx = -1; + for (int i = 1; etimes[i] > 0; i++) { + if (abs(etimes[i] - f) < diff) { + idx = i; + diff = abs(etimes[i] - f); + } + } + if (idx != -1 && f < etimes[0]) { + f = etimes[idx]; + } + if (f < 2) { + t->initRational (10, (int)(10 * f)); + } else { + t->initRational (1, (int)f); + } + } + exif->getDirectory()->addTagFront (t); + } else if (strcmp(tag, "FocalLength") == 0 && sscanf(val, "%d/%d", &num, &denom) == 2) { + t->initRational (num, denom); + exif->getDirectory()->addTagFront (t); + } else if (strcmp(tag, "ISOSpeedRatings") == 0) { + t->initInt (atoi(val), LONG); + exif->getDirectory()->addTagFront (t); + } else if (strcmp(tag, "DateTimeOriginal") == 0 && + sscanf(val, "%d-%d-%dT%d:%d:%dZ", + &tm.tm_year, &tm.tm_mon, + &tm.tm_mday, &tm.tm_hour, + &tm.tm_min, &tm.tm_sec) == 6) + { + char tstr[64]; + sprintf(tstr, "%02d:%02d:%02d %02d:%02d:%02d", tm.tm_year, tm.tm_mon, + tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); + t->initString (tstr); + exif->getDirectory()->addTagFront (t); + } else { + delete t; + } + } + *p = '<'; + *tagend = '>'; + } + *end = '<'; + } +} + TagDirectory* ExifManager::parse (FILE* f, int base, bool skipIgnored) { setlocale(LC_NUMERIC, "C"); // to set decimal point in sscanf // read tiff header @@ -1325,9 +1568,9 @@ TagDirectory* ExifManager::parse (FILE* f, int base, bool skipIgnored) { TagDirectory* root = new TagDirectory (NULL, f, base, ifdAttribs, order, skipIgnored); // fix ISO issue with nikon and panasonic cameras + Tag* make = root->getTag ("Make"); Tag* exif = root->getTag ("Exif"); if (exif && !exif->getDirectory()->getTag("ISOSpeedRatings")) { - Tag* make = root->getTag ("Make"); if (make && !strncmp((char*)make->getValue(), "NIKON", 5)) { Tag* mn = exif->getDirectory()->getTag("MakerNote"); if (mn) { @@ -1351,6 +1594,22 @@ TagDirectory* ExifManager::parse (FILE* f, int base, bool skipIgnored) { } } + parse_leafdata(root, order); + + if (!root->getTag("Orientation")) { + if (make && !strncmp((char*)make->getValue(), "Phase One", 9)) { + int orientation = 0; + Tag *iw = root->getTag("ImageWidth"); + if (iw) { + // from dcraw, derive orientation from image width + orientation = "0653"[iw->toInt()&3]-'0'; + } + Tag *t = new Tag (root, root->getAttrib ("Orientation")); + t->initInt (orientation, SHORT); + root->addTagFront (t); + } + } + // root->printAll (); return root; diff --git a/rtexif/stdattribs.cc b/rtexif/stdattribs.cc index 64c7d8249..06aacf9fd 100644 --- a/rtexif/stdattribs.cc +++ b/rtexif/stdattribs.cc @@ -540,6 +540,7 @@ const TagAttrib iopAttribs[] = { {0, 1, 0, 0, 0x828d, "CFAPatternDim", &stdInterpreter}, {0, 1, 0, 0, 0x828e, "CFAPattern", &cfaInterpreter}, {0, 1, 1, 0, 0x8298, "Copyright", &stdInterpreter}, + {0, 0, 0, 0, 0x8606, "LeafData", &stdInterpreter}, // is actually a subdir, but a proprietary format {0, 1, 0, exifAttribs, 0x8769, "Exif", &stdInterpreter}, {0, 2, 0, 0, 0x8773, "ICCProfile", &stdInterpreter}, {0, 2, 0, 0, 0x83BB, "IPTCData", &stdInterpreter},