/* * This file is part of RawTherapee. * * Copyright (c) 2004-2010 Gabor Horvath * * RawTherapee is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * RawTherapee is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with RawTherapee. If not, see . */ #include "imagedata.h" #include "iptcpairs.h" #include #include "safegtk.h" using namespace rtengine; extern "C" IptcData *iptc_data_new_from_jpeg_file (FILE* infile); ImageMetaData* ImageMetaData::fromFile (const Glib::ustring& fname, RawMetaDataLocation* rml) { return new ImageData (fname, rml); } ImageData::ImageData (Glib::ustring fname, RawMetaDataLocation* ri) { size_t dotpos = fname.find_last_of ('.'); root = NULL; iptc = NULL; if (ri && (ri->exifBase >= 0 || ri->ciffBase >= 0)) { FILE* f = safe_g_fopen (fname, "rb"); if (f) { if (ri->exifBase >= 0) { root = rtexif::ExifManager::parse (f, ri->exifBase); if (root) { rtexif::Tag* t = root->getTag (0x83BB); if (t) { iptc = iptc_data_new_from_data ((unsigned char*)t->getValue (), (unsigned)t->getValueSize ()); } } } else if (ri->ciffBase >= 0) { root = rtexif::ExifManager::parseCIFF (f, ri->ciffBase, ri->ciffLength); } fclose (f); extractInfo (); } } else if ((dotpos < fname.size() - 3 && !fname.casefold().compare (dotpos, 4, ".jpg")) || (dotpos < fname.size() - 4 && !fname.casefold().compare (dotpos, 5, ".jpeg"))) { FILE* f = safe_g_fopen (fname, "rb"); if (f) { root = rtexif::ExifManager::parseJPEG (f); extractInfo (); fclose (f); FILE* ff = safe_g_fopen (fname, "rb"); iptc = iptc_data_new_from_jpeg_file (ff); fclose (ff); } } else if ((dotpos < fname.size() - 3 && !fname.casefold().compare (dotpos, 4, ".tif")) || (dotpos < fname.size() - 4 && !fname.casefold().compare (dotpos, 5, ".tiff"))) { FILE* f = safe_g_fopen (fname, "rb"); if (f) { root = rtexif::ExifManager::parseTIFF (f); fclose (f); extractInfo (); if (root) { rtexif::Tag* t = root->getTag (0x83BB); if (t) { iptc = iptc_data_new_from_data ((unsigned char*)t->getValue (), (unsigned)t->getValueSize ()); } } } } else { root = new rtexif::TagDirectory (); shutter = 0; aperture = 0; iso_speed = 0; lens = "Unknown"; make = "Unknown"; model = "Unknown"; orientation = "Unknown"; expcomp = 0; focal_len = 0; memset (&time, 0, sizeof(time)); } } void ImageData::extractInfo () { if (!root) { return; } make = ""; model = ""; serial = ""; orientation = ""; expcomp = 0; shutter = 0; aperture = 0; focal_len = focal_len35mm = 0; focus_dist = 0; iso_speed = 0; memset (&time, 0, sizeof(time)); timeStamp = 0; if (root->getTag ("Make")) { make = root->getTag ("Make")->valueToString (); // same dcraw treatment static const char *corp[] = { "Canon", "NIKON", "EPSON", "KODAK", "Kodak", "OLYMPUS", "PENTAX", "RICOH", "MINOLTA", "Minolta", "Konica", "CASIO", "Sinar", "Phase One", "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]; break; } make.erase( make.find_last_not_of(' ') + 1 ); } if (root->getTag ("Model")) { model = root->getTag ("Model")->valueToString (); } if (!(model.size() == 0)) { std::size_t i = 0; if ( make.find("KODAK") != std::string::npos ) { if( (i = model.find(" DIGITAL CAMERA")) != std::string::npos || (i = model.find(" Digital Camera")) != std::string::npos || (i = model.find("FILE VERSION")) ) { model.resize( i ); } } model.erase( model.find_last_not_of(' ') + 1 ); //if( (i=model.find( make )) != std::string::npos ) if( !strncasecmp (model.c_str(), make.c_str(), make.size()) ) if( model.at( make.size() ) == ' ') { model.erase(0, make.size() + 1); } if( model.find( "Digital Camera ") != std::string::npos ) { model.erase(0, 15); } } else { model = "Unknown"; } if (root->getTag ("Orientation")) { orientation = root->getTag ("Orientation")->valueToString (); } rtexif::TagDirectory* exif = NULL; if (root->getTag ("Exif")) { exif = root->getTag ("Exif")->getDirectory (); } if (exif) { // standard exif tags if (exif->getTag ("ShutterSpeedValue")) { shutter = exif->getTag ("ShutterSpeedValue")->toDouble (); } if (exif->getTag ("ExposureTime")) { shutter = exif->getTag ("ExposureTime")->toDouble (); } if (exif->getTag ("ApertureValue")) { aperture = exif->getTag ("ApertureValue")->toDouble (); } if (exif->getTag ("FNumber")) { aperture = exif->getTag ("FNumber")->toDouble (); } if (exif->getTag ("ExposureBiasValue")) { expcomp = exif->getTag ("ExposureBiasValue")->toDouble (); } if (exif->getTag ("FocalLength")) { focal_len = exif->getTag ("FocalLength")->toDouble (); } if (exif->getTag ("FocalLengthIn35mmFilm")) { focal_len35mm = exif->getTag ("FocalLengthIn35mmFilm")->toDouble (); } // Focus distance from EXIF or XMP. MakerNote ones are scattered and partly encrypted int num = -3, denom = -3; // First try, offical EXIF. Set by Adobe on some DNGs rtexif::Tag* pDst = exif->getTag("SubjectDistance"); if (pDst) { int num, denom; pDst->toRational(num, denom); } else { // Second try, XMP data char sXMPVal[64]; if (root->getXMPTagValue("aux:ApproximateFocusDistance", sXMPVal)) { sscanf(sXMPVal, "%d/%d", &num, &denom); } } if (num != -3) { if ((denom == 1 && num >= 10000) || num < 0 || denom < 0) { focus_dist = 10000; // infinity } else if (denom > 0) { focus_dist = (float)num / denom; } } if (exif->getTag ("ISOSpeedRatings")) { iso_speed = exif->getTag ("ISOSpeedRatings")->toDouble (); } if (exif->getTag ("DateTimeOriginal")) { 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); } } rtexif::Tag *snTag = exif->findTag ("SerialNumber"); if(!snTag) { snTag = exif->findTag ("InternalSerialNumber"); } if ( snTag ) { serial = snTag->valueToString(); } // guess lens... lens = "Unknown"; // Sometimes (e.g. DNG) EXIF already contains lens data if(!make.compare (0, 8, "FUJIFILM")) { if(exif->getTag ("LensModel")) { lens = exif->getTag ("LensModel")->valueToString (); } } else if(!make.compare (0, 4, "SONY")) { if (iso_speed == 65535 || iso_speed == 0) { rtexif::Tag* isoTag = exif->getTag ("RecommendedExposureIndex"); if(isoTag) { iso_speed = isoTag->toDouble(); } } } if (lens == "Unknown") { rtexif::Tag* mnoteTag = root->findTag("MakerNote"); if (mnoteTag) { rtexif::TagDirectory* mnote = mnoteTag->getDirectory(); if (mnote && !make.compare (0, 5, "NIKON")) { // ISO at max value supported, check manufacturer specific if (iso_speed == 65535 || iso_speed == 0) { rtexif::Tag* isoTag = mnote->getTagP("ISOInfo/ISO"); if (isoTag) { iso_speed = isoTag->toInt(); } } bool lensOk = false; if (mnote->getTag ("LensData")) { std::string ldata = mnote->getTag ("LensData")->valueToString (); int pos; if (ldata.size() > 10 && (pos = ldata.find ("Lens = ")) != Glib::ustring::npos) { lens = ldata.substr (pos + 7); if (lens.compare (0, 7, "Unknown")) { lensOk = true; } else { int pos = lens.find("$FL$"); // is there a placeholder for focallength? if(pos != Glib::ustring::npos) { // then fill in focallength lens = lens.replace(pos, 4, exif->getTag ("FocalLength")->valueToString ()); if(mnote->getTag ("LensType")) { std::string ltype = mnote->getTag ("LensType")->valueToString (); if(ltype.find("MF = Yes") != Glib::ustring::npos) { // check, whether it's a MF lens, should be always lens = lens.replace(0, 7, "MF"); } lensOk = true; } } } } } if (!lensOk && mnote->getTag ("Lens")) { std::string ldata = mnote->getTag ("Lens")->valueToString (); size_t i = 0, j = 0; double n[4]; for (int m = 0; m < 4; m++) { while (i < ldata.size() && ldata[i] != '/') { i++; } int nom = atoi(ldata.substr(j, i).c_str()); j = i + 1; i++; while (i < ldata.size() && ldata[i] != ',') { i++; } int den = atoi(ldata.substr(j, i).c_str()); j = i + 2; i += 2; n[m] = (double) nom / den; } std::ostringstream str; if (n[0] == n[1]) { str << "Unknown " << n[0] << "mm F/" << n[2]; } else if (n[2] == n[3]) { str << "Unknown " << n[0] << "-" << n[1] << "mm F/" << n[2]; } else { str << "Unknown " << n[0] << "-" << n[1] << "mm F/" << n[2] << "-" << n[3]; } lens = str.str(); // Look whether it's MF or AF if(mnote->getTag ("LensType")) { std::string ltype = mnote->getTag ("LensType")->valueToString (); if(ltype.find("MF = Yes") != Glib::ustring::npos) { // check, whether it's a MF lens lens = lens.replace(0, 7, "MF"); // replace 'Unknwon' with 'MF' } else { lens = lens.replace(0, 7, "AF"); // replace 'Unknwon' with 'AF' } } } } else if (mnote && !make.compare (0, 5, "Canon")) { // ISO at max value supported, check manufacturer specific if (iso_speed == 65535 || iso_speed == 0) { rtexif::Tag* baseIsoTag = mnote->getTagP("CanonShotInfo/BaseISO"); if (baseIsoTag) { iso_speed = baseIsoTag->toInt(); } } int found = false; // canon EXIF have a string for lens model rtexif::Tag *lt = mnote->getTag("LensType"); if ( lt ) { std::string ldata = lt->valueToString (); if (ldata.size() > 1) { found = true; lens = "Canon " + ldata; } } if( !found || lens.substr(lens.find(' ')).length() < 7 ) { lt = mnote->findTag("LensID"); if ( lt ) { std::string ldata = lt->valueToString (); if (ldata.size() > 1) { lens = ldata; } } } } else if (mnote && (!make.compare (0, 6, "PENTAX") || (!make.compare (0, 5, "RICOH") && !model.compare (0, 6, "PENTAX")))) { if (mnote->getTag ("LensType")) { lens = mnote->getTag ("LensType")->valueToString (); } // Try to get the FocalLength from the LensInfo structure, where length below 10mm will be correctly set rtexif::Tag* flt = mnote->getTagP ("LensInfo/FocalLength"); if (flt) { // Don't replace Exif focal_len if Makernotes focal_len is 0 if (flt->toDouble() > 0) { focal_len = flt->toDouble (); } } else if ((flt = mnote->getTagP ("FocalLength"))) { rtexif::Tag* flt = mnote->getTag ("FocalLength"); focal_len = flt->toDouble (); } if (mnote->getTag ("FocalLengthIn35mmFilm")) { focal_len35mm = mnote->getTag ("FocalLengthIn35mmFilm")->toDouble (); } } else if (mnote && (!make.compare (0, 4, "SONY") || !make.compare (0, 6, "KONICA"))) { if (mnote->getTag ("LensID")) { lens = mnote->getTag ("LensID")->valueToString (); } } else if (mnote && !make.compare (0, 7, "OLYMPUS")) { if (mnote->getTag ("Equipment")) { rtexif::TagDirectory* eq = mnote->getTag ("Equipment")->getDirectory (); if (eq->getTag ("LensType")) { lens = eq->getTag ("LensType")->valueToString (); } } } } else if (exif->getTag ("DNGLensInfo")) { lens = exif->getTag ("DNGLensInfo")->valueToString (); } else if (exif->getTag ("LensModel")) { lens = exif->getTag ("LensModel")->valueToString (); } else if (exif->getTag ("LensInfo")) { lens = exif->getTag ("LensInfo")->valueToString (); } } } } ImageData::~ImageData () { delete root; if (iptc) { iptc_data_free (iptc); } } const procparams::IPTCPairs ImageData::getIPTCData () const { procparams::IPTCPairs iptcc; if (!iptc) { return iptcc; } unsigned char buffer[2100]; for (int i = 0; i < 16; i++) { IptcDataSet* ds = iptc_data_get_next_dataset (iptc, NULL, IPTC_RECORD_APP_2, strTags[i].tag); if (ds) { iptc_dataset_get_data (ds, buffer, 2100); std::vector icValues; icValues.push_back (safe_locale_to_utf8((char*)buffer)); iptcc[strTags[i].field] = icValues; iptc_dataset_unref (ds); } } IptcDataSet* ds = NULL; std::vector keywords; while ((ds = iptc_data_get_next_dataset (iptc, ds, IPTC_RECORD_APP_2, IPTC_TAG_KEYWORDS))) { iptc_dataset_get_data (ds, buffer, 2100); keywords.push_back (safe_locale_to_utf8((char*)buffer)); } iptcc["Keywords"] = keywords; ds = NULL; std::vector suppCategories; while ((ds = iptc_data_get_next_dataset (iptc, ds, IPTC_RECORD_APP_2, IPTC_TAG_SUPPL_CATEGORY))) { iptc_dataset_get_data (ds, buffer, 2100); suppCategories.push_back (safe_locale_to_utf8((char*)buffer)); iptc_dataset_unref (ds); } iptcc["SupplementalCategories"] = suppCategories; return iptcc; } //------inherited functions--------------// std::string ImageMetaData::apertureToString (double aperture) { char buffer[256]; sprintf (buffer, "%0.1f", aperture); return buffer; } std::string ImageMetaData::shutterToString (double shutter) { char buffer[256]; if (shutter > 0.0 && shutter < 0.9) { sprintf (buffer, "1/%0.0f", 1.0 / shutter); } else { sprintf (buffer, "%0.1f", shutter); } return buffer; } std::string ImageMetaData::expcompToString (double expcomp, bool maskZeroexpcomp) { char buffer[256]; if (maskZeroexpcomp == true) { if (expcomp != 0.0) { sprintf (buffer, "%0.2f", expcomp); return buffer; } else { return ""; } } else { sprintf (buffer, "%0.2f", expcomp); return buffer; } } double ImageMetaData::shutterFromString (std::string s) { size_t i = s.find_first_of ('/'); if (i == std::string::npos) { return atof (s.c_str()); } else { return atof (s.substr(0, i).c_str()) / atof (s.substr(i + 1).c_str()); } } double ImageMetaData::apertureFromString (std::string s) { return atof (s.c_str()); } extern "C" { #include #include struct _IptcDataPrivate { unsigned int ref_count; IptcLog *log; IptcMem *mem; }; IptcData * iptc_data_new_from_jpeg_file (FILE *infile) { IptcData *d; unsigned char * buf; int buf_len = 256 * 256; int len, offset; unsigned int iptc_len; if (!infile) { return NULL; } d = iptc_data_new (); if (!d) { return NULL; } buf = (unsigned char*)iptc_mem_alloc (d->priv->mem, buf_len); if (!buf) { iptc_data_unref (d); return NULL; } len = iptc_jpeg_read_ps3 (infile, buf, buf_len); if (len <= 0) { goto failure; } offset = iptc_jpeg_ps3_find_iptc (buf, len, &iptc_len); if (offset <= 0) { goto failure; } iptc_data_load (d, buf + offset, iptc_len); iptc_mem_free (d->priv->mem, buf); return d; failure: iptc_mem_free (d->priv->mem, buf); iptc_data_unref (d); return NULL; } }