diff --git a/msvc/exiv2.sln b/msvc/exiv2.sln index a51fb452..886b0bcb 100644 --- a/msvc/exiv2.sln +++ b/msvc/exiv2.sln @@ -73,6 +73,16 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "write-test", "write-test\wr {831EF580-92C8-4CA8-B0CE-3D906280A54D} = {831EF580-92C8-4CA8-B0CE-3D906280A54D} EndProjectSection EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "iotest", "iotest\iotest.vcproj", "{5D43ECB3-681D-4732-9395-AB81CD283F6C}" + ProjectSection(ProjectDependencies) = postProject + {831EF580-92C8-4CA8-B0CE-3D906280A54D} = {831EF580-92C8-4CA8-B0CE-3D906280A54D} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "iptceasy", "iptceasy\iptceasy.vcproj", "{D8B36F3A-34BB-4540-A731-EEABF1DC2E05}" + ProjectSection(ProjectDependencies) = postProject + {831EF580-92C8-4CA8-B0CE-3D906280A54D} = {831EF580-92C8-4CA8-B0CE-3D906280A54D} + EndProjectSection +EndProject Global GlobalSection(SolutionConfiguration) = preSolution Debug = Debug @@ -139,6 +149,14 @@ Global {94A7505B-3A53-40F0-95A2-2ECB1CEC7C57}.Debug.Build.0 = Debug|Win32 {94A7505B-3A53-40F0-95A2-2ECB1CEC7C57}.Release.ActiveCfg = Release|Win32 {94A7505B-3A53-40F0-95A2-2ECB1CEC7C57}.Release.Build.0 = Release|Win32 + {5D43ECB3-681D-4732-9395-AB81CD283F6C}.Debug.ActiveCfg = Debug|Win32 + {5D43ECB3-681D-4732-9395-AB81CD283F6C}.Debug.Build.0 = Debug|Win32 + {5D43ECB3-681D-4732-9395-AB81CD283F6C}.Release.ActiveCfg = Release|Win32 + {5D43ECB3-681D-4732-9395-AB81CD283F6C}.Release.Build.0 = Release|Win32 + {D8B36F3A-34BB-4540-A731-EEABF1DC2E05}.Debug.ActiveCfg = Debug|Win32 + {D8B36F3A-34BB-4540-A731-EEABF1DC2E05}.Debug.Build.0 = Debug|Win32 + {D8B36F3A-34BB-4540-A731-EEABF1DC2E05}.Release.ActiveCfg = Release|Win32 + {D8B36F3A-34BB-4540-A731-EEABF1DC2E05}.Release.Build.0 = Release|Win32 EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution EndGlobalSection diff --git a/msvc/exiv2lib/exiv2lib.vcproj b/msvc/exiv2lib/exiv2lib.vcproj index 95c55bd7..4a32f0cd 100644 --- a/msvc/exiv2lib/exiv2lib.vcproj +++ b/msvc/exiv2lib/exiv2lib.vcproj @@ -1,351 +1,363 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/msvc/exivsimple/exivsimple.cpp b/msvc/exivsimple/exivsimple.cpp index f000115d..0f3ab695 100644 --- a/msvc/exivsimple/exivsimple.cpp +++ b/msvc/exivsimple/exivsimple.cpp @@ -28,61 +28,60 @@ #include "stdafx.h" #include "exivsimple.h" +#include "image.hpp" #include "exif.hpp" #include "iptc.hpp" #include -struct ExivImage +struct ImageWrapper { - Exiv2::IptcData iptcData; - Exiv2::ExifData exifData; - std::string fileName; + Exiv2::Image::AutoPtr image; }; // Returns 0 if failed. EXIVSIMPLE_API HIMAGE OpenImage(const char *file) { assert(file); + ImageWrapper *imgWrap = new ImageWrapper; - ExivImage *image = new ExivImage; - image->fileName = file; + // See if file exists. Sorry for very bad error handling + if (INVALID_FILE_ATTRIBUTES == GetFileAttributes(file)) { + return 0; + } - // rc of 3 just means no iptc or exif data (not a real error) - int rc = image->iptcData.read( file ); - if (rc==0 || rc==3) - rc = image->exifData.read( file ); + imgWrap->image = Exiv2::ImageFactory::open(file); + if (imgWrap->image.get() == 0) { + return 0; + } - if (rc!=0 && rc!=3) { - delete image; - image = 0; + // Load existing metadata + if (imgWrap->image->readMetadata()) { + delete imgWrap; + imgWrap = 0; } - return (HIMAGE)image; + return (HIMAGE)imgWrap; } EXIVSIMPLE_API void FreeImage(HIMAGE img) { if (img) { - ExivImage *image = (ExivImage*)img; - delete image; + ImageWrapper *imgWrap = (ImageWrapper*)img; + delete imgWrap; } } EXIVSIMPLE_API int SaveImage(HIMAGE img) { assert(img); - ExivImage *image = (ExivImage*)img; - - int rc = image->iptcData.write(image->fileName); - if (rc==0) - rc = image->exifData.write(image->fileName); - - return rc; + ImageWrapper *imgWrap = (ImageWrapper*)img; + return imgWrap->image->writeMetadata(); } // This is weird because iptc and exif have not been "unified". Once -// the API is turned inside out, this DLL should not have to know +// they are unified, this DLL should not have to know // about either... just generic images, keys, values, etc. +// // buffsize should be the total size of *buff (including space for null) // Note that if there is more than one entry (for some IPTC datasets) this // returns the first one found. Currently no way to get the others. @@ -90,14 +89,18 @@ EXIVSIMPLE_API int ReadMeta(HIMAGE img, const char *key, char *buff, int buffsiz { assert(img && key && buff); if (img==0 || key==0 || buff==0 || buffsize==0) return -1; - ExivImage *image = (ExivImage*)img; + ImageWrapper *imgWrap = (ImageWrapper*)img; int rc = 2; + Exiv2::IptcData &iptcData = imgWrap->image->iptcData(); + Exiv2::ExifData &exifData = imgWrap->image->exifData(); + try { + // First try iptc Exiv2::IptcKey iptcKey(key); rc = 1; - Exiv2::IptcData::const_iterator iter = image->iptcData.findKey(iptcKey); - if (iter != image->iptcData.end()) { + Exiv2::IptcData::const_iterator iter = iptcData.findKey(iptcKey); + if (iter != iptcData.end()) { strncpy(buff, iter->value().toString().c_str(), buffsize); buff[buffsize-1] = 0; rc = 0; @@ -111,8 +114,8 @@ EXIVSIMPLE_API int ReadMeta(HIMAGE img, const char *key, char *buff, int buffsiz try { Exiv2::ExifKey exifKey(key); rc = 1; - Exiv2::ExifData::const_iterator iter = image->exifData.findKey(exifKey); - if (iter != image->exifData.end()) { + Exiv2::ExifData::const_iterator iter = exifData.findKey(exifKey); + if (iter != exifData.end()) { strncpy(buff, iter->value().toString().c_str(), buffsize); buff[buffsize-1] = 0; rc = 0; @@ -132,9 +135,12 @@ EXIVSIMPLE_API int ModifyMeta(HIMAGE img, const char *key, const char *val, DllT { assert(img && key && val); if (img==0 || key==0 || val==0) return -1; - ExivImage *image = (ExivImage*)img; + ImageWrapper *imgWrap = (ImageWrapper*)img; int rc = 2; + Exiv2::IptcData &iptcData = imgWrap->image->iptcData(); + Exiv2::ExifData &exifData = imgWrap->image->exifData(); + std::string data(val); // if data starts and ends with quotes, remove them if (data.at(0) == '\"' && data.at(data.size()-1) == '\"') { @@ -150,13 +156,13 @@ EXIVSIMPLE_API int ModifyMeta(HIMAGE img, const char *key, const char *val, DllT Exiv2::Value::AutoPtr value = Exiv2::Value::create((Exiv2::TypeId)type); value->read(data); - Exiv2::IptcData::iterator iter = image->iptcData.findKey(iptcKey); - if (iter != image->iptcData.end()) { + Exiv2::IptcData::iterator iter = iptcData.findKey(iptcKey); + if (iter != iptcData.end()) { iter->setValue(value.get()); rc = 0; } else { - rc = image->iptcData.add(iptcKey, value.get()); + rc = iptcData.add(iptcKey, value.get()); } } catch(const Exiv2::Error&) { @@ -174,13 +180,13 @@ EXIVSIMPLE_API int ModifyMeta(HIMAGE img, const char *key, const char *val, DllT Exiv2::Value::AutoPtr value = Exiv2::Value::create((Exiv2::TypeId)type); value->read(data); - Exiv2::ExifData::iterator iter = image->exifData.findKey(exifKey); - if (iter != image->exifData.end()) { + Exiv2::ExifData::iterator iter = exifData.findKey(exifKey); + if (iter != exifData.end()) { iter->setValue(value.get()); rc = 0; } else { - image->exifData.add(exifKey, value.get()); + exifData.add(exifKey, value.get()); rc = 0; } } @@ -198,9 +204,12 @@ EXIVSIMPLE_API int AddMeta(HIMAGE img, const char *key, const char *val, DllType { assert(img && key && val); if (img==0 || key==0 || val==0) return -1; - ExivImage *image = (ExivImage*)img; + ImageWrapper *imgWrap = (ImageWrapper*)img; int rc = 2; + Exiv2::IptcData &iptcData = imgWrap->image->iptcData(); + Exiv2::ExifData &exifData = imgWrap->image->exifData(); + std::string data(val); // if data starts and ends with quotes, remove them if (data.at(0) == '\"' && data.at(data.size()-1) == '\"') { @@ -216,7 +225,7 @@ EXIVSIMPLE_API int AddMeta(HIMAGE img, const char *key, const char *val, DllType Exiv2::Value::AutoPtr value = Exiv2::Value::create((Exiv2::TypeId)type); value->read(data); - rc = image->iptcData.add(iptcKey, value.get()); + rc = iptcData.add(iptcKey, value.get()); } catch(const Exiv2::Error&) { } @@ -233,7 +242,7 @@ EXIVSIMPLE_API int AddMeta(HIMAGE img, const char *key, const char *val, DllType Exiv2::Value::AutoPtr value = Exiv2::Value::create((Exiv2::TypeId)type); value->read(data); - image->exifData.add(exifKey, value.get()); + exifData.add(exifKey, value.get()); rc = 0; } catch(const Exiv2::Error&) { @@ -249,15 +258,18 @@ EXIVSIMPLE_API int RemoveMeta(HIMAGE img, const char *key) { assert(img && key); if (img==0 || key==0) return -1; - ExivImage *image = (ExivImage*)img; + ImageWrapper *imgWrap = (ImageWrapper*)img; int rc = 2; + Exiv2::IptcData &iptcData = imgWrap->image->iptcData(); + Exiv2::ExifData &exifData = imgWrap->image->exifData(); + try { Exiv2::IptcKey iptcKey(key); rc = 1; - Exiv2::IptcData::iterator iter = image->iptcData.findKey(iptcKey); - if (iter != image->iptcData.end()) { - image->iptcData.erase(iter); + Exiv2::IptcData::iterator iter = iptcData.findKey(iptcKey); + if (iter != iptcData.end()) { + iptcData.erase(iter); rc = 0; } } @@ -269,9 +281,9 @@ EXIVSIMPLE_API int RemoveMeta(HIMAGE img, const char *key) try { Exiv2::ExifKey exifKey(key); rc = 1; - Exiv2::ExifData::iterator iter = image->exifData.findKey(exifKey); - if (iter != image->exifData.end()) { - image->exifData.erase(iter); + Exiv2::ExifData::iterator iter = exifData.findKey(exifKey); + if (iter != exifData.end()) { + exifData.erase(iter); rc = 0; } } @@ -286,17 +298,20 @@ EXIVSIMPLE_API int EnumMeta(HIMAGE img, METAENUMPROC proc, void *user) { assert(img && proc); if (img==0 || proc==0) return -1; - ExivImage *image = (ExivImage*)img; + ImageWrapper *imgWrap = (ImageWrapper*)img; bool more = true; - Exiv2::IptcData::const_iterator iend = image->iptcData.end(); - for (Exiv2::IptcData::const_iterator i = image->iptcData.begin(); + Exiv2::IptcData &iptcData = imgWrap->image->iptcData(); + Exiv2::ExifData &exifData = imgWrap->image->exifData(); + + Exiv2::IptcData::const_iterator iend = iptcData.end(); + for (Exiv2::IptcData::const_iterator i = iptcData.begin(); i != iend && more; ++i) { more = proc(i->key().c_str(), i->value().toString().c_str(), user); } - Exiv2::ExifData::const_iterator eend = image->exifData.end(); - for (Exiv2::ExifData::const_iterator e = image->exifData.begin(); + Exiv2::ExifData::const_iterator eend = exifData.end(); + for (Exiv2::ExifData::const_iterator e = exifData.begin(); e != eend && more; ++e) { more = proc(e->key().c_str(), e->value().toString().c_str(), user); } diff --git a/msvc/iotest/iotest.vcproj b/msvc/iotest/iotest.vcproj new file mode 100644 index 00000000..568a0f54 --- /dev/null +++ b/msvc/iotest/iotest.vcproj @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/msvc/iptceasy/iptceasy.vcproj b/msvc/iptceasy/iptceasy.vcproj new file mode 100644 index 00000000..366778ae --- /dev/null +++ b/msvc/iptceasy/iptceasy.vcproj @@ -0,0 +1,144 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Makefile b/src/Makefile index 4992beae..508c7e44 100644 --- a/src/Makefile +++ b/src/Makefile @@ -51,14 +51,14 @@ include $(top_srcdir)/config.mk CCHDR = rcsid.hpp error.hpp # Add library C++ source files to this list -CCSRC = canonmn.cpp datasets.cpp exif.cpp fujimn.cpp ifd.cpp image.cpp iptc.cpp \ +CCSRC = canonmn.cpp datasets.cpp exif.cpp fujimn.cpp ifd.cpp basicio.cpp iptc.cpp \ makernote.cpp metadatum.cpp nikonmn.cpp sigmamn.cpp tags.cpp types.cpp \ - value.cpp + image.cpp jpgimage.cpp value.cpp # Add source files of simple applications to this list BINSRC = addmoddel.cpp exifcomment.cpp exifprint.cpp ifd-test.cpp iptcprint.cpp \ iptctest.cpp key-test.cpp makernote-test.cpp taglist.cpp write-test.cpp \ - write2-test.cpp dataarea-test.cpp iptceasy.cpp + iotest.cpp write2-test.cpp dataarea-test.cpp iptceasy.cpp # State the main source file of the Exiv2 application here EXIV2MAIN = exiv2.cpp diff --git a/src/actions.cpp b/src/actions.cpp index e2cd7af1..337497bc 100644 --- a/src/actions.cpp +++ b/src/actions.cpp @@ -39,6 +39,7 @@ EXIV2_RCSID("@(#) $Id$"); #endif #include "actions.hpp" +#include "image.hpp" #include "exiv2.hpp" #include "utils.hpp" #include "types.hpp" @@ -167,13 +168,24 @@ namespace Action { int Print::printSummary() { - Exiv2::ExifData exifData; - int rc = exifData.read(path_); + if (!Util::fileExists(path_, true)) { + std::cerr << path_ + << ": Failed to open the file\n"; + return -1; + } + Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open(path_); + if (image.get() == 0) { + std::cerr << path_ + << ": The file contains data of an unknown image type\n"; + return -2; + } + int rc = image->readMetadata(); if (rc) { - std::cerr << Exiv2::ExifData::strError(rc, path_) << "\n"; + std::cerr << Exiv2::Image::strError(rc, path_) << "\n"; return rc; } + Exiv2::ExifData &exifData = image->exifData(); align_ = 16; // Filename @@ -267,7 +279,7 @@ namespace Action { // Subject distance std::cout << std::setw(align_) << std::setfill(' ') << std::left << "Subject distance" << ": "; - if (0 == printTag(exifData, "Exif.Photo.SubjectDistance")) { + if (0 == printTag(exifData, "Exif.Photo.SubjectDistance")) { md = exifData.findKey( Exiv2::ExifKey("Exif.Canon.CameraSettings2")); if (md != exifData.end() && md->count() >= 19) { @@ -446,13 +458,24 @@ namespace Action { int Print::printInterpreted() { - Exiv2::ExifData exifData; - int rc = exifData.read(path_); + if (!Util::fileExists(path_, true)) { + std::cerr << path_ + << ": Failed to open the file\n"; + return -1; + } + Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open(path_); + if (image.get() == 0) { + std::cerr << path_ + << ": The file contains data of an unknown image type\n"; + return -2; + } + int rc = image->readMetadata(); if (rc) { - std::cerr << Exiv2::ExifData::strError(rc, path_) << "\n"; + std::cerr << Exiv2::Image::strError(rc, path_) << "\n"; return rc; } + Exiv2::ExifData &exifData = image->exifData(); Exiv2::ExifData::const_iterator md; for (md = exifData.begin(); md != exifData.end(); ++md) { std::cout << "0x" << std::setw(4) << std::setfill('0') << std::right @@ -469,13 +492,24 @@ namespace Action { int Print::printValues() { - Exiv2::ExifData exifData; - int rc = exifData.read(path_); + if (!Util::fileExists(path_, true)) { + std::cerr << path_ + << ": Failed to open the file\n"; + return -1; + } + Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open(path_); + if (image.get() == 0) { + std::cerr << path_ + << ": The file contains data of an unknown image type\n"; + return -2; + } + int rc = image->readMetadata(); if (rc) { - std::cerr << Exiv2::ExifData::strError(rc, path_) << "\n"; + std::cerr << Exiv2::Image::strError(rc, path_) << "\n"; return rc; } + Exiv2::ExifData &exifData = image->exifData(); Exiv2::ExifData::const_iterator end = exifData.end(); Exiv2::ExifData::const_iterator md; for (md = exifData.begin(); md != end; ++md) { @@ -499,13 +533,24 @@ namespace Action { int Print::printIptc() { - Exiv2::IptcData iptcData; - int rc = iptcData.read(path_); + if (!Util::fileExists(path_, true)) { + std::cerr << path_ + << ": Failed to open the file\n"; + return -1; + } + Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open(path_); + if (image.get() == 0) { + std::cerr << path_ + << ": The file contains data of an unknown image type\n"; + return -2; + } + int rc = image->readMetadata(); if (rc) { - std::cerr << Exiv2::IptcData::strError(rc, path_) << "\n"; + std::cerr << Exiv2::Image::strError(rc, path_) << "\n"; return rc; } + Exiv2::IptcData &iptcData = image->iptcData(); Exiv2::IptcData::const_iterator end = iptcData.end(); Exiv2::IptcData::const_iterator md; for (md = iptcData.begin(); md != end; ++md) { @@ -529,13 +574,24 @@ namespace Action { int Print::printHexdump() { - Exiv2::ExifData exifData; - int rc = exifData.read(path_); + if (!Util::fileExists(path_, true)) { + std::cerr << path_ + << ": Failed to open the file\n"; + return -1; + } + Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open(path_); + if (image.get() == 0) { + std::cerr << path_ + << ": The file contains data of an unknown image type\n"; + return -2; + } + int rc = image->readMetadata(); if (rc) { - std::cerr << Exiv2::ExifData::strError(rc, path_) << "\n"; + std::cerr << Exiv2::Image::strError(rc, path_) << "\n"; return rc; } + Exiv2::ExifData &exifData = image->exifData(); Exiv2::ExifData::const_iterator md; for (md = exifData.begin(); md != exifData.end(); ++md) { std::cout << std::setw(4) << std::setfill(' ') << std::left @@ -567,7 +623,7 @@ namespace Action { << ": Failed to open the file\n"; return -1; } - Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::instance().open(path_); + Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open(path_); if (image.get() == 0) { std::cerr << path_ << ": The file contains data of an unknown image type\n"; @@ -598,12 +654,24 @@ namespace Action { int Rename::run(const std::string& path) try { - Exiv2::ExifData exifData; - int rc = exifData.read(path); + if (!Util::fileExists(path, true)) { + std::cerr << path + << ": Failed to open the file\n"; + return -1; + } + Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open(path); + if (image.get() == 0) { + std::cerr << path + << ": The file contains data of an unknown image type\n"; + return -2; + } + int rc = image->readMetadata(); if (rc) { - std::cerr << Exiv2::ExifData::strError(rc, path) << "\n"; + std::cerr << Exiv2::Image::strError(rc, path) << "\n"; return rc; } + + Exiv2::ExifData &exifData = image->exifData(); Exiv2::ExifKey key("Exif.Photo.DateTimeOriginal"); Exiv2::ExifData::iterator md = exifData.findKey(key); if (md == exifData.end()) { @@ -683,8 +751,13 @@ namespace Action { try { path_ = path; + if (!Util::fileExists(path_, true)) { + std::cerr << path_ + << ": Failed to open the file\n"; + return -1; + } Exiv2::Image::AutoPtr image - = Exiv2::ImageFactory::instance().open(path_); + = Exiv2::ImageFactory::open(path_); if (image.get() == 0) { std::cerr << path_ << ": The file contains data of an unknown image type\n"; @@ -710,7 +783,7 @@ namespace Action { if (0 == rc) { rc = image->writeMetadata(); if (rc) { - std::cerr << Exiv2::ExifData::strError(rc, path_) << "\n"; + std::cerr << Exiv2::Image::strError(rc, path_) << "\n"; } } @@ -725,33 +798,22 @@ namespace Action { int Erase::eraseThumbnail(Exiv2::Image* image) const { - if (image->sizeExifData() == 0) { + Exiv2::ExifData &exifData = image->exifData(); + std::string thumbExt = exifData.thumbnailExtension(); + if (thumbExt.empty()) { return 0; } - int rc = 0; - Exiv2::ExifData exifData; - rc = exifData.read(image->exifData(), image->sizeExifData()); - if (rc) { - std::cerr << Exiv2::ExifData::strError(rc, path_) << "\n"; - } - if (0 == rc) { - std::string thumbExt = exifData.thumbnailExtension(); - if (!thumbExt.empty()) { - long delta = exifData.eraseThumbnail(); - if (Params::instance().verbose_) { - std::cout << "Erasing " << delta - << " Bytes of thumbnail data" << std::endl; - } - Exiv2::DataBuf buf(exifData.copy()); - image->setExifData(buf.pData_, buf.size_); - } + long delta = exifData.eraseThumbnail(); + if (Params::instance().verbose_) { + std::cout << "Erasing " << delta + << " Bytes of thumbnail data" << std::endl; } - return rc; + return 0; } int Erase::eraseExifData(Exiv2::Image* image) const { - if (Params::instance().verbose_ && image->sizeExifData() > 0) { + if (Params::instance().verbose_ && image->exifData().count() > 0) { std::cout << "Erasing Exif data from the file" << std::endl; } image->clearExifData(); @@ -760,7 +822,7 @@ namespace Action { int Erase::eraseIptcData(Exiv2::Image* image) const { - if (Params::instance().verbose_ && image->sizeIptcData() > 0) { + if (Params::instance().verbose_ && image->iptcData().count() > 0) { std::cout << "Erasing Iptc data from the file" << std::endl; } image->clearIptcData(); @@ -816,12 +878,24 @@ namespace Action { int Extract::writeThumbnail() const { - Exiv2::ExifData exifData; - int rc = exifData.read(path_); + if (!Util::fileExists(path_, true)) { + std::cerr << path_ + << ": Failed to open the file\n"; + return -1; + } + Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open(path_); + if (image.get() == 0) { + std::cerr << path_ + << ": The file contains data of an unknown image type\n"; + return -2; + } + int rc = image->readMetadata(); if (rc) { - std::cerr << Exiv2::ExifData::strError(rc, path_) << "\n"; + std::cerr << Exiv2::Image::strError(rc, path_) << "\n"; return rc; } + Exiv2::ExifData &exifData = image->exifData(); + std::string thumb = Util::dirname(path_) + SEPERATOR_STR + Util::basename(path_, true) + "-thumb"; std::string thumbExt = exifData.thumbnailExtension(); @@ -898,14 +972,26 @@ namespace Action { << ": Failed to open the file\n"; return -1; } - Exiv2::ExifData exifData; - int rc = exifData.read(path); + if (!Util::fileExists(path, true)) { + std::cerr << path + << ": Failed to open the file\n"; + return -1; + } + Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open(path); + if (image.get() == 0) { + std::cerr << path + << ": The file contains data of an unknown image type\n"; + return -2; + } + int rc = image->readMetadata(); if (rc) { - std::cerr << Exiv2::ExifData::strError(rc, path) << "\n"; + std::cerr << Exiv2::Image::strError(rc, path) << "\n"; return rc; } + + Exiv2::ExifData &exifData = image->exifData(); exifData.setJpegThumbnail(thumbPath); - return exifData.write(path); + return image->writeMetadata(); } // Insert::insertThumbnail @@ -926,10 +1012,17 @@ namespace Action { << ": Failed to open the file\n"; return -1; } - - // Read both exif and iptc metadata (ignore return code) - exifData_.read(path); - iptcData_.read(path); + image_ = Exiv2::ImageFactory::open(path); + if (image_.get() == 0) { + std::cerr << path + << ": The file contains data of an unknown image type\n"; + return -2; + } + int rc = image_->readMetadata(); + if (rc) { + std::cerr << Exiv2::Image::strError(rc, path) << "\n"; + return rc; + } // loop through command table and apply each command ModifyCmds& modifyCmds = Params::instance().modifyCmds_; @@ -953,13 +1046,9 @@ namespace Action { } // Save both exif and iptc metadata - int rc = exifData_.write(path); - if (rc) { - std::cerr << Exiv2::ExifData::strError(rc, path) << "\n"; - } - rc = iptcData_.write(path); + rc = image_->writeMetadata(); if (rc) { - std::cerr << Exiv2::IptcData::strError(rc, path) << "\n"; + std::cerr << Exiv2::Image::strError(rc, path) << "\n"; } return rc; } @@ -981,10 +1070,10 @@ namespace Action { Exiv2::Value::AutoPtr value = Exiv2::Value::create(modifyCmd.typeId_); value->read(modifyCmd.value_); if (modifyCmd.metadataId_ == exif) { - exifData_.add(Exiv2::ExifKey(modifyCmd.key_), value.get()); + image_->exifData().add(Exiv2::ExifKey(modifyCmd.key_), value.get()); } if (modifyCmd.metadataId_ == iptc) { - iptcData_.add(Exiv2::IptcKey(modifyCmd.key_), value.get()); + image_->iptcData().add(Exiv2::IptcKey(modifyCmd.key_), value.get()); } } @@ -996,12 +1085,15 @@ namespace Action { << Exiv2::TypeInfo::typeName(modifyCmd.typeId_) << ")" << std::endl; } + + Exiv2::ExifData &exifData = image_->exifData(); + Exiv2::IptcData &iptcData = image_->iptcData(); Exiv2::Metadatum* metadatum = 0; if (modifyCmd.metadataId_ == exif) { - metadatum = &exifData_[modifyCmd.key_]; + metadatum = &exifData[modifyCmd.key_]; } if (modifyCmd.metadataId_ == iptc) { - metadatum = &iptcData_[modifyCmd.key_]; + metadatum = &iptcData[modifyCmd.key_]; } assert(metadatum); Exiv2::Value::AutoPtr value = metadatum->getValue(); @@ -1020,15 +1112,18 @@ namespace Action { if (Params::instance().verbose_) { std::cout << "Del " << modifyCmd.key_ << std::endl; } + + Exiv2::ExifData &exifData = image_->exifData(); + Exiv2::IptcData &iptcData = image_->iptcData(); if (modifyCmd.metadataId_ == exif) { Exiv2::ExifData::iterator pos = - exifData_.findKey(Exiv2::ExifKey(modifyCmd.key_)); - if (pos != exifData_.end()) exifData_.erase(pos); + exifData.findKey(Exiv2::ExifKey(modifyCmd.key_)); + if (pos != exifData.end()) exifData.erase(pos); } if (modifyCmd.metadataId_ == iptc) { Exiv2::IptcData::iterator pos = - iptcData_.findKey(Exiv2::IptcKey(modifyCmd.key_)); - if (pos != iptcData_.end()) iptcData_.erase(pos); + iptcData.findKey(Exiv2::IptcKey(modifyCmd.key_)); + if (pos != iptcData.end()) iptcData.erase(pos); } } @@ -1046,19 +1141,31 @@ namespace Action { try { adjustment_ = Params::instance().adjustment_; - Exiv2::ExifData exifData; - int rc = exifData.read(path); + if (!Util::fileExists(path, true)) { + std::cerr << path + << ": Failed to open the file\n"; + return -1; + } + Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open(path); + if (image.get() == 0) { + std::cerr << path + << ": The file contains data of an unknown image type\n"; + return -2; + } + int rc = image->readMetadata(); if (rc) { - std::cerr << Exiv2::ExifData::strError(rc, path) << "\n"; + std::cerr << Exiv2::Image::strError(rc, path) << "\n"; return rc; } + + Exiv2::ExifData &exifData = image->exifData(); rc = adjustDateTime(exifData, "Exif.Image.DateTime", path); rc += adjustDateTime(exifData, "Exif.Photo.DateTimeOriginal", path); rc += adjustDateTime(exifData, "Exif.Photo.DateTimeDigitized", path); if (rc) return 1; - rc = exifData.write(path); + rc = image->writeMetadata(); if (rc) { - std::cerr << Exiv2::ExifData::strError(rc, path) << "\n"; + std::cerr << Exiv2::Image::strError(rc, path) << "\n"; } return rc; } @@ -1182,7 +1289,7 @@ namespace { return -1; } Exiv2::Image::AutoPtr sourceImage - = Exiv2::ImageFactory::instance().open(source); + = Exiv2::ImageFactory::open(source); if (sourceImage.get() == 0) { std::cerr << source << ": The file contains data of an unknown image type\n"; @@ -1195,7 +1302,7 @@ namespace { return 1; } Exiv2::Image::AutoPtr targetImage - = Exiv2::ImageFactory::instance().open(target); + = Exiv2::ImageFactory::open(target); if (preserve && targetImage.get() != 0) { if (targetImage->readMetadata()) { std::cerr << target @@ -1205,7 +1312,7 @@ namespace { } if (targetImage.get() == 0) { targetImage - = Exiv2::ImageFactory::instance().create(Exiv2::Image::exv, target); + = Exiv2::ImageFactory::create(Exiv2::Image::exv, target); } if (targetImage.get() == 0) { std::cerr << target @@ -1213,22 +1320,20 @@ namespace { return 2; } if ( Params::instance().target_ & Params::ctExif - && sourceImage->sizeExifData() > 0) { + && sourceImage->exifData().count() > 0) { if (Params::instance().verbose_) { std::cout << "Writing Exif data from " << source << " to " << target << std::endl; } - targetImage->setExifData(sourceImage->exifData(), - sourceImage->sizeExifData()); + targetImage->setExifData(sourceImage->exifData()); } if ( Params::instance().target_ & Params::ctIptc - && sourceImage->sizeIptcData() > 0) { + && sourceImage->iptcData().count() > 0) { if (Params::instance().verbose_) { std::cout << "Writing Iptc data from " << source << " to " << target << std::endl; } - targetImage->setIptcData(sourceImage->iptcData(), - sourceImage->sizeIptcData()); + targetImage->setIptcData(sourceImage->iptcData()); } if ( Params::instance().target_ & Params::ctComment && !sourceImage->comment().empty()) { diff --git a/src/actions.hpp b/src/actions.hpp index ad4e279e..f19a9cf2 100644 --- a/src/actions.hpp +++ b/src/actions.hpp @@ -303,9 +303,12 @@ namespace Action { virtual int run(const std::string& path); typedef std::auto_ptr AutoPtr; AutoPtr clone() const; + Modify() {} private: virtual Modify* clone_() const; + //! Copy contructor needed because of AutoPtr memeber + Modify(const Modify& src) {} //! Add a metadatum according to \em modifyCmd void addMetadatum(const ModifyCmd& modifyCmd); @@ -314,8 +317,7 @@ namespace Action { //! Delete a metadatum according to \em modifyCmd void delMetadatum(const ModifyCmd& modifyCmd); - Exiv2::ExifData exifData_; //!< Exif metadata - Exiv2::IptcData iptcData_; //!< Iptc metadata + Exiv2::Image::AutoPtr image_; //!< Image to modify }; // class Modify } // namespace Action diff --git a/src/addmoddel.cpp b/src/addmoddel.cpp index 68036d41..aef33d3c 100644 --- a/src/addmoddel.cpp +++ b/src/addmoddel.cpp @@ -2,6 +2,7 @@ // addmoddel.cpp, $Rev$ // Sample program showing how to add, modify and delete Exif metadata. +#include "image.hpp" #include "exif.hpp" #include #include @@ -14,7 +15,9 @@ try { } std::string file(argv[1]); - // Container for all metadata + // Container for exif metadata. This is an example of creating + // exif metadata from scratch. If you want to add, modify, delete + // metadata that exists in an image, start with ImageFactory::open Exiv2::ExifData exifData; // ************************************************************************* @@ -90,10 +93,18 @@ try { std::cout << "Deleted key \"" << key << "\"\n"; // ************************************************************************* - // Finally, write the remaining Exif data to an image file - int rc = exifData.write(file); + // Finally, write the remaining Exif data to the image file + Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open(file); + if (image.get() == 0) { + std::string error(file); + error += " : Could not read file or unknown image type"; + throw Exiv2::Error(error); + } + + image->setExifData(exifData); + int rc = image->writeMetadata(); if (rc) { - std::string error = Exiv2::ExifData::strError(rc, file); + std::string error = Exiv2::Image::strError(rc, file); throw Exiv2::Error(error); } diff --git a/src/basicio.cpp b/src/basicio.cpp new file mode 100644 index 00000000..bf43123c --- /dev/null +++ b/src/basicio.cpp @@ -0,0 +1,427 @@ +// ***************************************************************** -*- C++ -*- +/* + * Copyright (C) 2004 Andreas Huggel + * + * This program is part of the Exiv2 distribution. + * + * This program 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 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +/* + File: basicio.cpp + Version: $Rev$ + Author(s): Brad Schick (brad) + History: 04-Dec-04, brad: created + */ +// ***************************************************************************** +#include "rcsid.hpp" +EXIV2_RCSID("@(#) $Id$"); + +// Define DEBUG_MAKERNOTE to output debug information to std::cerr +#undef DEBUG_MAKERNOTE + +// ***************************************************************************** +// included header files +#include "basicio.hpp" +#include "types.hpp" + +// + standard includes +#include +#include // for stat() +#include // for stat() +#ifdef HAVE_PROCESS_H +# include +#endif +#ifdef HAVE_UNISTD_H +# include // for getpid, stat +#endif + +// ***************************************************************************** +// class member definitions +namespace Exiv2 { + + FileIo::FileIo(const std::string& path) : + path_(path), fp_(0), opMode_(opSeek) + { + } + + FileIo::~FileIo() + { + close(); + } + + BasicIo::AutoPtr FileIo::temporary() const + { + BasicIo::AutoPtr basicIo; + + struct stat buf; + int ret = stat(path_.c_str(), &buf); + + // If file is > 1MB then use a file, otherwise use memory buffer + if (buf.st_size > 1048576 || ret != 0) { + pid_t pid = getpid(); + std::string tmpname = path_ + toString(pid); + FileIo *fileIo = new FileIo(tmpname); + if (fileIo->open("w+b") != 0 ) { + delete fileIo; + } + else { + basicIo.reset(fileIo); + } + } + else { + basicIo.reset(new MemIo); + } + + return basicIo; + } + + long FileIo::write(const byte* data, long wcount ) + { + assert(fp_ != 0); + + // ANSI C requires a flush or seek when switching + // between read and write modes. + if (opMode_ == opRead) { + // on msvcrt fflush does not do the job + fseek(fp_, 0, SEEK_CUR); + } + opMode_ = opWrite; + return (long)fwrite(data, 1, wcount, fp_); + } + + long FileIo::write(BasicIo& src) + { + assert(fp_ != 0); + if (static_cast(this)==&src) return 0; + if (!src.isopen()) return 0; + + // ANSI C requires a flush or seek when switching + // between read and write modes. + if (opMode_ == opRead) { + // on msvcrt fflush does not do the job + fseek(fp_, 0, SEEK_CUR); + } + opMode_ = opWrite; + + byte buf[4096]; + long readCount = 0; + long writeCount = 0; + long writeTotal = 0; + while ((readCount = src.read(buf, sizeof(buf)))) { + writeTotal += writeCount = (long)fwrite(buf, 1, readCount, fp_); + if (writeCount != readCount) { + // try to reset back to where write stopped + src.seek(writeCount-readCount, BasicIo::cur); + break; + } + } + + return writeTotal; + } + + int FileIo::transfer(BasicIo& src) + { + const bool wasOpen = (fp_ != 0); + const std::string lastMode(openMode_); + + FileIo *fileIo = dynamic_cast(&src); + if (fileIo) { + // Optimization if this is another instance of FileIo + close(); + fileIo->close(); + // MSVCRT rename that does not overwrite existing files + if (remove(path_.c_str()) != 0) return -4; + if (rename(fileIo->path_.c_str(), path_.c_str()) == -1) return -4; + remove(fileIo->path_.c_str()); + } + else{ + // Generic handling, reopen both to reset to start + open("w+b"); + if (src.open() !=0) return 1; + write(src); + src.close(); + } + + if (wasOpen) open(lastMode); + else close(); + + return error() || src.error(); + } + + int FileIo::putb(byte data) + { + assert(fp_ != 0); + if (opMode_ == opRead) { + // on msvcrt fflush does not do the job + fseek(fp_, 0, SEEK_CUR); + } + opMode_ = opWrite; + return putc(data, fp_); + } + + int FileIo::seek(long offset, Position pos) + { + assert(fp_ != 0); + int fileSeek; + if (pos == BasicIo::cur) { + fileSeek = SEEK_CUR; + } + else if (pos == BasicIo::beg) { + fileSeek = SEEK_SET; + } + else { + assert(pos == BasicIo::end); + fileSeek = SEEK_END; + } + + opMode_ = opSeek; + return fseek(fp_, offset, fileSeek); + } + + long FileIo::tell() const + { + assert(fp_ != 0); + return ftell(fp_); + } + + int FileIo::open() + { + // Default open is in read-write binary mode + return open("r+b"); + } + + int FileIo::open(const std::string& mode) + { + if (fp_ != 0) { + fclose(fp_); + } + + openMode_ = mode; + opMode_ = opSeek; + fp_ = fopen(path_.c_str(), mode.c_str()); + if (!fp_) return 1; + return 0; + } + + bool FileIo::isopen() const + { + return fp_ != 0; + } + + int FileIo::close() + { + if (fp_ != 0) { + fclose(fp_); + fp_= 0; + } + return 0; + } + + DataBuf FileIo::read(long rcount) + { + assert(fp_ != 0); + DataBuf buf(rcount); + long readCount = read(buf.pData_, buf.size_); + buf.size_ = readCount; + return buf; + } + + long FileIo::read(byte* buf, long rcount) + { + assert(fp_ != 0); + + if (opMode_ == opWrite) { + // on msvcrt fflush does not do the job + fseek(fp_, 0, SEEK_CUR); + } + opMode_ = opRead; + return (long)fread(buf, 1, rcount, fp_); + } + + int FileIo::getb() + { + assert(fp_ != 0); + + if (opMode_ == opWrite) { + // on msvcrt fflush does not do the job + fseek(fp_, 0, SEEK_CUR); + } + opMode_ = opRead; + return getc(fp_); + } + + int FileIo::error() const + { + return fp_ != 0 ? ferror(fp_) : 0; + } + + bool FileIo::eof() const + { + assert(fp_ != 0); + return feof(fp_) != 0; + } + + + MemIo::MemIo(const byte* data, long size) + { + // If copying data is too slow it might be worth + // creating a readonly MemIo variant + data_.reserve(size); + data_.assign(data, data+size); + idx_ = 0; + } + + BasicIo::AutoPtr MemIo::temporary() const + { + return BasicIo::AutoPtr(new MemIo); + } + + void MemIo::checkSize(long wcount) + { + ByteVector::size_type need = wcount + idx_; + if (need > data_.size()) { + data_.resize(need); + } + } + + long MemIo::write(const byte* data, long wcount ) + { + checkSize(wcount); + // According to Josuttis 6.2.3 this is safe + memcpy(&data_[idx_], data, wcount); + idx_ += wcount; + return wcount; + } + + int MemIo::transfer(BasicIo& src) + { + MemIo *memIo = dynamic_cast(&src); + if (memIo) { + // Optimization if this is another instance of MemIo + data_.swap(memIo->data_); + idx_ = 0; + } + else{ + // Generic reopen to reset position to start + data_.clear(); + idx_ = 0; + if (src.open() != 0) return 1; + write(src); + src.close(); + } + return error() || src.error(); + } + + long MemIo::write(BasicIo& src) + { + if (static_cast(this)==&src) return 0; + if (!src.isopen()) return 0; + + byte buf[4096]; + long readCount = 0; + long writeTotal = 0; + while ((readCount = src.read(buf, sizeof(buf)))) { + write(buf, readCount); + writeTotal += readCount; + } + + return writeTotal; + } + + int MemIo::putb(byte data) + { + checkSize(1); + data_[idx_++] = data; + return data; + } + + int MemIo::seek(long offset, Position pos) + { + ByteVector::size_type newIdx; + + if (pos == BasicIo::cur ) { + newIdx = idx_ + offset; + } + else if (pos == BasicIo::beg) { + newIdx = offset; + } + else { + assert(pos == BasicIo::end); + newIdx = data_.size() + offset; + } + + if (newIdx < 0 || newIdx > data_.size()) return 1; + idx_ = newIdx; + return 0; + } + + long MemIo::tell() const + { + return (long)idx_; + } + + int MemIo::open() + { + idx_ = 0; + return 0; + } + + bool MemIo::isopen() const + { + return true; + } + + int MemIo::close() + { + return 0; + } + + DataBuf MemIo::read(long rcount) + { + DataBuf buf(rcount); + long readCount = read(buf.pData_, buf.size_); + buf.size_ = readCount; + return buf; + } + + long MemIo::read(byte* buf, long rcount) + { + long avail = (long)(data_.size() - idx_); + long allow = std::min(rcount, avail); + + // According to Josuttis 6.2.3 this is safe + memcpy(buf, &data_[idx_], allow); + idx_ += allow; + return allow; + } + + int MemIo::getb() + { + if (idx_ == data_.size()) + return EOF; + return data_[idx_++]; + } + + int MemIo::error() const + { + return 0; + } + + bool MemIo::eof() const + { + return idx_ == data_.size(); + } + +} // namespace Exiv2 diff --git a/src/basicio.hpp b/src/basicio.hpp new file mode 100644 index 00000000..47385764 --- /dev/null +++ b/src/basicio.hpp @@ -0,0 +1,605 @@ +// ***************************************************************** -*- C++ -*- +/* + * Copyright (C) 2004 Andreas Huggel + * + * This program is part of the Exiv2 distribution. + * + * This program 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 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +/*! + @file basicio.hpp + @brief Simple binary IO abstraction + @version $Rev$ + @author Brad Schick (brad) + brad@robotbattle.com + @date 04-Dec-04, brad: created + */ +#ifndef BASICIO_HPP_ +#define BASICIO_HPP_ + +// ***************************************************************************** +// included header files +#include "types.hpp" +#include "error.hpp" + +// + standard includes +#include +#include +#include + +// ***************************************************************************** +// namespace extensions +namespace Exiv2 { + +// ***************************************************************************** +// class definitions + + /*! + @brief An interface for simple binary IO. + + Designed to have semantics + and names similar to those of C style FILE* operations. Subclasses + should all behave the same so that they can be interchanged. + */ + class BasicIo + { + public: + //! BasicIo auto_ptr type + typedef std::auto_ptr AutoPtr; + + //! Seek starting positions + enum Position { beg, cur, end }; + + //! @name Creators + //@{ + //! Destructor + virtual ~BasicIo() {} + //@} + + //! @name Manipulators + //@{ + /*! + @brief Open the IO source using the default access mode. The + default mode should allow for reading and writing. + + This method can also be used to "reopen" an IO source which will + flush any unwritten data and reset the IO position to the start. + Subclasses may provide custom methods to allow for + opening IO sources differently. + + @return 0 if successful;
+ Nonzero if failure; + */ + virtual int open() = 0; + /*! + @brief Close the IO source. After closing a BasicIo instance can not + be read or written. Closing flushes any unwritten data. It is + safe to call close on a closed instance. + @return 0 if successful;
+ Nonzero if failure; + */ + virtual int close() = 0; + /*! + @brief Write data to the IO source. Current IO position is advanced + by the number of bytes written. + @param data Pointer to data. Data must be at least \em wcount + bytes long + @param wcount Number of bytes to be written. + @return Number of bytes written to IO source successfully;
+ 0 if failure; + */ + virtual long write(const byte* data, long wcount) = 0; + /*! + @brief Write data that is read from another BasicIo instance to + the IO source. Current IO position is advanced by the number + of bytes written. + @param src Reference to another BasicIo instance. Reading start + at the source's current IO position + @return Number of bytes written to IO source successfully;
+ 0 if failure; + */ + virtual long write(BasicIo& src) = 0; + /*! + @brief Write one byte to the IO source. Current IO position is + advanced by one byte. + @param data The single byte to be written. + @return The value of the byte written if successful;
+ EOF if failure; + */ + virtual int putb(byte data) = 0; + /*! + @brief Read data from the IO source. Reading starts at the current + IO position and the position is advanced by the number of bytes + read. + @param rcount Maximum number of bytes to read. Fewer bytes may be + read if \em rcount bytes are not available. + @return DataBuf instance containing the bytes read. Use the + DataBuf::size_ member to find the number of bytes read. + DataBuf::size_ will be 0 on failure. + */ + virtual DataBuf read(long rcount) = 0; + /*! + @brief Read data from the IO source. Reading starts at the current + IO position and the position is advanced by the number of bytes + read. + @param buf Pointer to a block of memory into which the read data + is stored. The memory block must be at least \em rcount bytes + long. + @param rcount Maximum number of bytes to read. Fewer bytes may be + read if \em rcount bytes are not available. + @return Number of bytes read from IO source successfully;
+ 0 if failure; + */ + virtual long read(byte *buf, long rcount) = 0; + /*! + @brief Read one byte from the IO source. Current IO position is + advanced by one byte. + @return The byte read from the IO source if successful;
+ EOF if failure; + */ + virtual int getb() = 0; + /*! + @brief Remove all data from this object's IO source and then transfer + data from the \em src BasicIo object into this object. + + The source object is invalidated by this operation and should not be + used after this method returns. This method exists primarily to + be used with the BasicIo::temporary() method. + + @param src Reference to another BasicIo instance. The entire contents + of src are transferred to this object. The \em src object is + invalidated by the method. + @return 0 if successful;
+ Nonzero if failure; + */ + virtual int transfer(BasicIo& src) = 0; + /*! + @brief Move the current IO position. + @param offset Number of bytes to move the position relative + to the starting position specified by \em pos + @param pos Position from which the seek should start + @return 0 if successful;
+ Nonzero if failure; + */ + virtual int seek(long offset, Position pos) = 0; + //@} + + //! @name Accessors + //@{ + /*! + @brief Get the current IO position. + @return Offset from the start of IO if successful;
+ -l if failure; + */ + virtual long tell() const = 0; + //!Returns true if the IO source is open, otherwise false. + virtual bool isopen() const = 0; + //!Returns 0 if the IO source is in a valid state, otherwise nonzero. + virtual int error() const = 0; + //!Returns true if the IO position has reach the end, otherwise false. + virtual bool eof() const = 0; + /*! + @brief Returns a temporary data storage location. This is often + needed to rewrite an IO source. + + For example, data may be read from the original IO source, modified + in some way, and then saved to the temporary instance. After the + operation is complete, the BasicIo::transfer method can be used to + replace the original IO source with the modified version. Subclasses + are free to return any class that derives from BasicIo. + + @return An instance of BasicIo on success;
+ Null pointer on failure; + */ + virtual BasicIo::AutoPtr temporary() const = 0; + //@} + + protected: + //! @name Creators + //@{ + //! Default Constructor + BasicIo() {} + //@} + }; // class BasicIo + + /*! + @brief Utility class that closes a BasicIo instance upon destruction. + Meant to be used as a stack variable in functions that need to + ensure BasicIo instances get closed. Useful when functions return + errors from many locations. + */ + class IoCloser { + public: + //! @name Creators + //@{ + //! Constructor, takes a BasicIo reference + IoCloser(BasicIo &bio) : bio_(bio) {} + //! Destructor, closes the BasicIo reference + ~IoCloser() { close(); } + //@} + + //! @name Manipulators + //@{ + //! Close the BasicIo if it is open + void close() { if (bio_.isopen()) bio_.close(); } + //@} + + // DATA + //! The BasicIo reference + BasicIo &bio_; + private: + // Not implemented + //! Copy constructor + IoCloser(const IoCloser&); + //! Assignment operator + IoCloser& operator=(const IoCloser&); + }; // class IoCloser + + + /*! + @brief Provides binary file IO by implementing the BasicIo + interface. + */ + class FileIo : public BasicIo + { + public: + //! @name Creators + //@{ + /*! + @brief Constructor that accepts the file path on which IO will be + performed. The constructor does not open the file, and + therefore never failes. + @param path The full path of a file + */ + FileIo(const std::string& path); + //! Destructor. Flushes and closes an open file. + virtual ~FileIo(); + //@} + + //! @name Manipulators + //@{ + /*! + @brief Open the file using using the specified mode. + + This method can also be used to "reopen" a file which will flush any + unwritten data and reset the IO position to the start. Although + files can be opened in binary or text mode, this class has + only been tested carefully in binary mode. + + @param mode Specified that type of access allowed on the file. + Valid values match those of the C fopen command exactly. + @return 0 if successful;
+ Nonzero if failure; + */ + int open( const std::string& mode); + /*! + @brief Open the file using using the default access mode of "r+b". + This method can also be used to "reopen" a file which will flush + any unwritten data and reset the IO position to the start. + @return 0 if successful;
+ Nonzero if failure; + */ + virtual int open(); + /*! + @brief Flush and unwritten data and close the file . It is + safe to call close on an already closed instance. + @return 0 if successful;
+ Nonzero if failure; + */ + virtual int close(); + /*! + @brief Write data to the file. The file position is advanced + by the number of bytes written. + @param data Pointer to data. Data must be at least \em wcount + bytes long + @param wcount Number of bytes to be written. + @return Number of bytes written to the file successfully;
+ 0 if failure; + */ + virtual long write(const byte* data, long wcount); + /*! + @brief Write data that is read from another BasicIo instance to + the file. The file position is advanced by the number + of bytes written. + @param src Reference to another BasicIo instance. Reading start + at the source's current IO position + @return Number of bytes written to the file successfully;
+ 0 if failure; + */ + virtual long write(BasicIo& src); + /*! + @brief Write one byte to the file. The file position is + advanced by one byte. + @param data The single byte to be written. + @return The value of the byte written if successful;
+ EOF if failure; + */ + virtual int putb(byte data); + /*! + @brief Read data from the file. Reading starts at the current + file position and the position is advanced by the number of + bytes read. + @param rcount Maximum number of bytes to read. Fewer bytes may be + read if \em rcount bytes are not available. + @return DataBuf instance containing the bytes read. Use the + DataBuf::size_ member to find the number of bytes read. + DataBuf::size_ will be 0 on failure. + */ + virtual DataBuf read(long rcount); + /*! + @brief Read data from the file. Reading starts at the current + file position and the position is advanced by the number of + bytes read. + @param buf Pointer to a block of memory into which the read data + is stored. The memory block must be at least \em rcount bytes + long. + @param rcount Maximum number of bytes to read. Fewer bytes may be + read if \em rcount bytes are not available. + @return Number of bytes read from the file successfully;
+ 0 if failure; + */ + virtual long read(byte *buf, long rcount); + /*! + @brief Read one byte from the file. The file position is + advanced by one byte. + @return The byte read from the file if successful;
+ EOF if failure; + */ + virtual int getb(); + /*! + @brief Remove the contents of the file and then transfer data from + the \em src BasicIo object into the empty file. + + This method is optimized to simply rename the source file if the + source object is another FileIo instance. The source BasicIo object + is invalidated by this operation and should not be used after this + method returns. This method exists primarily to be used with + the BasicIo::temporary() method. + + @param src Reference to another BasicIo instance. The entire contents + of src are transferred to this object. The \em src object is + invalidated by the method. + @return 0 if successful;
+ Nonzero if failure; + */ + virtual int transfer(BasicIo& src); + /*! + @brief Move the current file position. + @param offset Number of bytes to move the file position + relative to the starting position specified by \em pos + @param pos Position from which the seek should start + @return 0 if successful;
+ Nonzero if failure; + */ + virtual int seek(long offset, Position pos); + //@} + + //! @name Accessors + //@{ + /*! + @brief Get the current file position. + @return Offset from the start of the file if successful;
+ -l if failure; + */ + virtual long tell() const; + //!Returns true if the file is open, otherwise false. + virtual bool isopen() const; + //!Returns 0 if the file is in a valid state, otherwise nonzero. + virtual int error() const; + //!Returns true if the file position has reach the end, otherwise false. + virtual bool eof() const; + /*! + @brief Returns a temporary data storage location. The actual type + returned depends upon the size of the file represented a FileIo + object. For small files, a MemIo is returned while for large files + a FileIo is returned. Callers should not rely on this behavior, + however, since it may change. + @return An instance of BasicIo on success;
+ Null pointer on failure; + */ + virtual BasicIo::AutoPtr temporary() const; + //@} + + private: + // NOT IMPLEMENTED + //! Copy constructor + FileIo(FileIo& rhs); + //! Assignment operator + FileIo& operator=(const FileIo& rhs); + + // Enumeration + enum OpMode { opRead, opWrite, opSeek }; + + // DATA + std::string path_; + std::string openMode_; + FILE *fp_; + OpMode opMode_; + }; // class FileIo + + /*! + @brief Provides binary IO on blocks of memory by implementing the + BasicIo interface. The current implementation makes a copy of + any data passed to its constructors. If writes are performed, the + changed data can be retrieved using the read methods (since the + data used in construction is never modified). + + @note If read only usage of this class is common, it might be worth + creating a specialized readonly class or changing this one to + have a readonly mode. + */ + class MemIo : public BasicIo + { + public: + //! @name Creators + //@{ + //! Default constructor that results in an empty object + MemIo() { idx_ = 0; } + /*! + @brief Constructor that accepts a block of memory to be copied. + IO operations are performed on the copied memory. + @param data Pointer to data. Data must be at least \em size + bytes long + @param size Number of bytes to copy. + */ + MemIo(const byte* data, long size); + //! Destructor. Releases all managed memory + virtual ~MemIo() {} + //@} + + //! @name Manipulators + //@{ + /*! + @brief Memory IO is always open for reading and writing. This method + therefore only resets the IO position to the start. + @return 0 + */ + virtual int open(); + /*! + @brief Does nothing on MemIo objects. + @return 0 + */ + virtual int close(); + /*! + @brief Write data to the memory block. If needed, the size of the + internal memory block is expanded. The IO position is advanced + by the number of bytes written. + @param data Pointer to data. Data must be at least \em wcount + bytes long + @param wcount Number of bytes to be written. + @return Number of bytes written to the memory block successfully;
+ 0 if failure; + */ + virtual long write(const byte* data, long wcount); + /*! + @brief Write data that is read from another BasicIo instance to + the memory block. If needed, the size of the internal memory + block is expanded. The IO position is advanced by the number + of bytes written. + @param src Reference to another BasicIo instance. Reading start + at the source's current IO position + @return Number of bytes written to the memory block successfully;
+ 0 if failure; + */ + virtual long write(BasicIo& src); + /*! + @brief Write one byte to the memory block. The IO position is + advanced by one byte. + @param data The single byte to be written. + @return The value of the byte written if successful;
+ EOF if failure; + */ + virtual int putb(byte data); + /*! + @brief Read data from the memory block. Reading starts at the current + IO position and the position is advanced by the number of + bytes read. + @param rcount Maximum number of bytes to read. Fewer bytes may be + read if \em rcount bytes are not available. + @return DataBuf instance containing the bytes read. Use the + DataBuf::size_ member to find the number of bytes read. + DataBuf::size_ will be 0 on failure. + */ + virtual DataBuf read(long rcount); + /*! + @brief Read data from the memory block. Reading starts at the current + IO position and the position is advanced by the number of + bytes read. + @param buf Pointer to a block of memory into which the read data + is stored. The memory block must be at least \em rcount bytes + long. + @param rcount Maximum number of bytes to read. Fewer bytes may be + read if \em rcount bytes are not available. + @return Number of bytes read from the memory block successfully;
+ 0 if failure; + */ + virtual long read(byte *buf, long rcount); + /*! + @brief Read one byte from the memory block. The IO position is + advanced by one byte. + @return The byte read from the memory block if successful;
+ EOF if failure; + */ + virtual int getb(); + /*! + @brief Clear the memory clock and then transfer data from + the \em src BasicIo object into a new block of memory. + + This method is optimized to simply swap memory block if the source + object is another MemIo instance. The source BasicIo instance + is invalidated by this operation and should not be used after this + method returns. This method exists primarily to be used with + the BasicIo::temporary() method. + + @param src Reference to another BasicIo instance. The entire contents + of src are transferred to this object. The \em src object is + invalidated by the method. + @return 0 if successful;
+ Nonzero if failure; + */ + virtual int transfer(BasicIo& src); + /*! + @brief Move the current IO position. + @param offset Number of bytes to move the IO position + relative to the starting position specified by \em pos + @param pos Position from which the seek should start + @return 0 if successful;
+ Nonzero if failure; + */ + virtual int seek(long offset, Position pos); + //@} + + //! @name Accessors + //@{ + /*! + @brief Get the current IO position. + @return Offset from the start of the memory block + */ + virtual long tell() const; + //!Always returns true + virtual bool isopen() const; + //!Always returns 0 + virtual int error() const; + //!Returns true if the IO position has reach the end, otherwise false. + virtual bool eof() const; + /*! + @brief Returns a temporary data storage location. Currently returns + an empty MemIo object, but callers should not rely on this + behavior since it may change. + @return An instance of BasicIo on success;
+ Null pointer on failure; + */ + virtual BasicIo::AutoPtr temporary() const; + //@} + private: + // NOT IMPLEMENTED + //! Copy constructor + MemIo(MemIo& rhs); + //! Assignment operator + MemIo& operator=(const MemIo& rhs); + + // Typedefs + typedef std::vector ByteVector; + + // DATA + ByteVector data_; + ByteVector::size_type idx_; + + //METHODS + void checkSize(long wcount); + }; // class MemIo +} // namespace Exiv2 + +#endif // #ifndef BASICIO_HPP_ diff --git a/src/dataarea-test.cpp b/src/dataarea-test.cpp index df46f560..23529789 100644 --- a/src/dataarea-test.cpp +++ b/src/dataarea-test.cpp @@ -78,22 +78,30 @@ catch (Exiv2::Error& e) { void write(const std::string& file, Exiv2::ExifData& ed) { - int rc = ed.writeExifData(file); + Image::AutoPtr image = ImageFactory::create(Image::exv, file); + assert(image.get() != 0); + + image->setExifData(ed); + int rc = image->writeMetadata(); if (rc) { - std::string error = Exiv2::ExifData::strError(rc, file); + std::string error = Exiv2::Image::strError(rc, file); throw Exiv2::Error(error); } } void print(const std::string& file) { - Exiv2::ExifData ed; - int rc = ed.read(file); + Image::AutoPtr image = ImageFactory::open(file); + assert(image.get() != 0); + + // Load existing metadata + int rc = image->readMetadata(); if (rc) { - std::string error = Exiv2::ExifData::strError(rc, file); + std::string error = Exiv2::Image::strError(rc, file); throw Exiv2::Error(error); } + Exiv2::ExifData &ed = image->exifData(); Exiv2::ExifData::const_iterator end = ed.end(); for (Exiv2::ExifData::const_iterator i = ed.begin(); i != end; ++i) { std::cout << std::setw(35) << std::setfill(' ') << std::left @@ -109,29 +117,28 @@ void print(const std::string& file) << i->count() << " " << std::dec << i->value() << "\n"; - } } int read(const std::string& path) { - Image::AutoPtr image = ImageFactory::instance().open(path); + Image::AutoPtr image = ImageFactory::open(path); assert(image.get() != 0); int rc = image->readMetadata(); if (rc) return rc; - if (image->sizeExifData() > 0) { - const byte *pData = image->exifData(); - long size = image->sizeExifData(); + if (image->exifData().count() > 0) { + DataBuf exifData = image->exifData().copy(); + long size = exifData.size_; // Read the TIFF header TiffHeader tiffHeader; - rc = tiffHeader.read(pData); + rc = tiffHeader.read(exifData.pData_); if (rc) return rc; // Read IFD0 Ifd ifd0(ifd0Id); - rc = ifd0.read(pData + tiffHeader.offset(), + rc = ifd0.read(exifData.pData_ + tiffHeader.offset(), size - tiffHeader.offset(), tiffHeader.byteOrder(), tiffHeader.offset()); @@ -143,7 +150,7 @@ int read(const std::string& path) Value::AutoPtr v = Value::create(TypeId(i->type())); v->read(i->data(), i->count() * i->typeSize(), tiffHeader.byteOrder()); - v->setDataArea(pData + v->toLong(), 32); + v->setDataArea(exifData.pData_ + v->toLong(), 32); std::cout << "Value of tag 0x8298: " << std::hex; v->write(std::cout); @@ -162,7 +169,7 @@ int read(const std::string& path) v = Value::create(TypeId(i->type())); v->read(i->data(), i->count() * i->typeSize(), tiffHeader.byteOrder()); - v->setDataArea(pData + v->toLong(), 16); + v->setDataArea(exifData.pData_ + v->toLong(), 16); std::cout << "Value of tag 0x013b: "; v->write(std::cout); diff --git a/src/exif.cpp b/src/exif.cpp index 0424036b..712f696c 100644 --- a/src/exif.cpp +++ b/src/exif.cpp @@ -36,11 +36,12 @@ EXIV2_RCSID("@(#) $Id$"); // included header files #include "exif.hpp" #include "types.hpp" +#include "basicio.hpp" #include "error.hpp" #include "value.hpp" #include "ifd.hpp" #include "tags.hpp" -#include "image.hpp" +#include "jpgimage.hpp" #include "makernote.hpp" // + standard includes @@ -355,28 +356,7 @@ namespace Exiv2 { return *pos; } - int ExifData::read(const std::string& path) - { - if (!fileExists(path, true)) return -1; - Image::AutoPtr image = ImageFactory::instance().open(path); - if (image.get() == 0) { - // We don't know this type of file - return -2; - } - - int rc = image->readMetadata(); - if (rc == 0) { - if (image->sizeExifData() > 0) { - rc = read(image->exifData(), image->sizeExifData()); - } - else { - rc = 3; - } - } - return rc; - } - - int ExifData::read(const byte* buf, long len) + int ExifData::load(const byte* buf, long len) { // Copy the data buffer delete[] pData_; @@ -476,38 +456,6 @@ namespace Exiv2 { return ret; } // ExifData::read - int ExifData::erase(const std::string& path) const - { - if (!fileExists(path, true)) return -1; - Image::AutoPtr image = ImageFactory::instance().open(path); - if (image.get() == 0) return -2; - - // Read all metadata then erase only Exif data - int rc = image->readMetadata(); - if (rc == 0) { - image->clearExifData(); - rc = image->writeMetadata(); - } - return rc; - } // ExifData::erase - - int ExifData::write(const std::string& path) - { - // Remove the Exif section from the file if there is no metadata - if (count() == 0) return erase(path); - - if (!fileExists(path, true)) return -1; - Image::AutoPtr image = ImageFactory::instance().open(path); - if (image.get() == 0) return -2; - DataBuf buf(copy()); - // Read all metadata to preserve non-Exif data - int rc = image->readMetadata(); - if (rc == 0) { - image->setExifData(buf.pData_, buf.size_); - rc = image->writeMetadata(); - } - return rc; - } // ExifData::write DataBuf ExifData::copy() { @@ -660,15 +608,6 @@ namespace Exiv2 { return buf; } // ExifData::copyFromMetadata - int ExifData::writeExifData(const std::string& path) - { - DataBuf buf(copy()); - ExvImage exvImage(path, true); - if (!exvImage.good()) return -1; - exvImage.setExifData(buf.pData_, buf.size_); - return exvImage.writeMetadata(); - } // ExifData::writeExifData - void ExifData::add(Entries::const_iterator begin, Entries::const_iterator end, ByteOrder byteOrder) @@ -846,11 +785,11 @@ namespace Exiv2 { if (thumbnail.get() == 0) return 8; std::string name = path + thumbnail->extension(); - FileCloser file(fopen(name.c_str(), "wb")); - if (!file.fp_) return -1; + FileIo file(name); + if (file.open("wb") != 0) return -1; DataBuf buf(thumbnail->copy(*this)); - if (fwrite(buf.pData_, 1, buf.size_, file.fp_) != (size_t)buf.size_) { + if (file.write(buf.pData_, buf.size_) != buf.size_) { return 4; } return 0; @@ -1197,14 +1136,14 @@ namespace { Exiv2::DataBuf readFile(const std::string& path) { - Exiv2::FileCloser file(fopen(path.c_str(), "rb")); - if (!file.fp_) + Exiv2::FileIo file(path); + if (file.open("rb") != 0) throw Exiv2::Error("Couldn't open input file"); struct stat st; if (0 != stat(path.c_str(), &st)) throw Exiv2::Error("Couldn't stat input file"); Exiv2::DataBuf buf(st.st_size); - long len = (long)fread(buf.pData_, 1, buf.size_, file.fp_); + long len = file.read(buf.pData_, buf.size_); if (len != buf.size_) throw Exiv2::Error("Couldn't read input file"); return buf; diff --git a/src/exif.hpp b/src/exif.hpp index ec632dd0..6fcea025 100644 --- a/src/exif.hpp +++ b/src/exif.hpp @@ -480,9 +480,6 @@ namespace Exiv2 { - extract and delete Exif thumbnail (JPEG and TIFF thumbnails) */ class ExifData { - //! @name Not implemented - //@{ - //@} public: //! ExifMetadata iterator type typedef ExifMetadata::iterator iterator; @@ -504,43 +501,13 @@ namespace Exiv2 { //! Assignment operator (Todo: assign image data also) ExifData& operator=(const ExifData& rhs); /*! - @brief Read the Exif data from file \em path. - @param path Path to the file - @return 0 if successful;
- 3 if the file contains no Exif data;
- the return code of Image::readMetadata() - if the call to this function fails
- the return code of read(const char* buf, long len) - if the call to this function fails - */ - int read(const std::string& path); - /*! - @brief Read the Exif data from a byte buffer. The data buffer + @brief Load the Exif data from a byte buffer. The data buffer must start with the TIFF header. @param buf Pointer to the data buffer to read from @param len Number of bytes in the data buffer @return 0 if successful. */ - int read(const byte* buf, long len); - /*! - @brief Write the Exif data to file \em path. If an Exif data section - already exists in the file, it is replaced. If there is no - metadata and no thumbnail to write, the Exif data section is - deleted from the file. Otherwise, an Exif data section is - created. See copy(byte* buf) for further details. - - @return 0 if successful. - */ - int write(const std::string& path); - /*! - @brief Write the Exif data to a binary file. By convention, the - filename extension should be ".exv". This file format contains - the Exif data as it is found in a JPEG file, starting with the - APP1 marker 0xffe1, the size of the data and the string - "Exif\0\0". Exv files can be read with - int read(const std::string& path) just like image Exif data. - */ - int writeExifData(const std::string& path); + int load(const byte* buf, long len); /*! @brief Write the Exif data to a data buffer, which is returned. The caller owns this copy and %DataBuf ensures that it will be @@ -603,6 +570,11 @@ namespace Exiv2 { by this call. */ iterator erase(iterator pos); + /*! + @brief Delete all Exifdatum instances resulting in an empty container. + Note that this also removes thumbnails. + */ + void clear() { eraseThumbnail(); exifMetadata_.clear(); } //! Sort metadata by key void sortByKey(); //! Sort metadata by tag @@ -709,12 +681,6 @@ namespace Exiv2 { //! @name Accessors //@{ - /*! - @brief Erase the Exif data section from file \em path. - @param path Path to the file. - @return 0 if successful. - */ - int erase(const std::string& path) const; //! Begin of the metadata const_iterator begin() const { return exifMetadata_.begin(); } //! End of the metadata @@ -780,14 +746,13 @@ namespace Exiv2 { /*! @brief Convert the return code \em rc from \n - int read(const std::string& path); \n - int write(const std::string& path); \n - int writeExifData(const std::string& path); \n - int writeThumbnail(const std::string& path) const; and \n - int erase(const std::string& path) const \n + int read(const byte* buf, long len), \n into an error message. + @param rc Error code. + @param path %Image file or other identifying string. + @return String containing error message. - Todo: Implement global handling of error messages + Todo: Implement global handling of error messages */ static std::string strError(int rc, const std::string& path); diff --git a/src/exifcomment.cpp b/src/exifcomment.cpp index 2bf8862f..a3562ac2 100644 --- a/src/exifcomment.cpp +++ b/src/exifcomment.cpp @@ -9,6 +9,7 @@ */ // ***************************************************************************** // included header files +#include "image.hpp" #include "exif.hpp" #include #include @@ -24,13 +25,22 @@ try { return 1; } - Exiv2::ExifData exifData; - int rc = exifData.read(argv[1]); + Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open(argv[1]); + if (image.get() == 0) { + std::string error(argv[1]); + error += " : Could not read file or unknown image type"; + throw Exiv2::Error(error); + } + + // Load existing metadata + int rc = image->readMetadata(); if (rc) { - std::string error = Exiv2::ExifData::strError(rc, argv[1]); + std::string error = Exiv2::Image::strError(rc, argv[1]); throw Exiv2::Error(error); } + Exiv2::ExifData &exifData = image->exifData(); + /* There are two pitfalls that we need to consider when setting the Exif user comment (Exif.Photo.UserComment) of an image: @@ -76,13 +86,13 @@ try { // output operator to print the formatted value std::cout << "Writing user comment '" << *pos << "' back to the image\n"; - rc = exifData.write(argv[1]); + rc = image->writeMetadata(); if (rc) { - std::string error = Exiv2::ExifData::strError(rc, argv[1]); + std::string error = Exiv2::Image::strError(rc, argv[1]); throw Exiv2::Error(error); } - return rc; + return rc; } catch (Exiv2::Error& e) { std::cout << "Caught Exiv2 exception '" << e << "'\n"; diff --git a/src/exifprint.cpp b/src/exifprint.cpp index 034392db..3a0809f1 100644 --- a/src/exifprint.cpp +++ b/src/exifprint.cpp @@ -2,6 +2,7 @@ // exifprint.cpp, $Rev$ // Sample program to print the Exif metadata of an image +#include "image.hpp" #include "exif.hpp" #include #include @@ -14,13 +15,21 @@ try { return 1; } - Exiv2::ExifData exifData; - int rc = exifData.read(argv[1]); + Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open(argv[1]); + if (image.get() == 0) { + std::string error(argv[1]); + error += " : Could not read file or unknown image type"; + throw Exiv2::Error(error); + } + + // Load existing metadata + int rc = image->readMetadata(); if (rc) { - std::string error = Exiv2::ExifData::strError(rc, argv[1]); + std::string error = Exiv2::Image::strError(rc, argv[1]); throw Exiv2::Error(error); } + Exiv2::ExifData &exifData = image->exifData(); Exiv2::ExifData::const_iterator end = exifData.end(); for (Exiv2::ExifData::const_iterator i = exifData.begin(); i != end; ++i) { std::cout << std::setw(53) << std::setfill(' ') << std::left diff --git a/src/image.cpp b/src/image.cpp index c369ac83..084847e5 100644 --- a/src/image.cpp +++ b/src/image.cpp @@ -26,6 +26,7 @@ History: 26-Jan-04, ahu: created 11-Feb-04, ahu: isolated as a component 19-Jul-04, brad: revamped to be more flexible and support Iptc + 15-Jan-05, brad: inside-out design changes */ // ***************************************************************************** #include "rcsid.hpp" @@ -42,9 +43,11 @@ EXIV2_RCSID("@(#) $Id$"); #endif #include "image.hpp" -#include "types.hpp" #include "error.hpp" +// Ensure registration with factory +#include "jpgimage.hpp" + // + standard includes #include #include @@ -55,71 +58,54 @@ EXIV2_RCSID("@(#) $Id$"); #ifdef _MSC_VER # define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) #endif -#ifdef HAVE_PROCESS_H -# include -#endif #ifdef HAVE_UNISTD_H -# include // for getpid, stat +# include // stat #endif // ***************************************************************************** // class member definitions namespace Exiv2 { - // Local functions. These could be static private functions on Image - // subclasses but then ImageFactory needs to be made a friend. - /*! - @brief Create a new ExvImage instance and return an auto-pointer to it. - Caller owns the returned object and the auto-pointer ensures that - it will be deleted. - */ - Image::AutoPtr newExvInstance(const std::string& path, bool create); - //! Check if the file ifp is an EXV file. - bool isExvType(FILE* ifp, bool advance); - /*! - @brief Create a new JpegImage instance and return an auto-pointer to it. - Caller owns the returned object and the auto-pointer ensures that - it will be deleted. - */ - Image::AutoPtr newJpegInstance(const std::string& path, bool create); - //! Check if the file ifp is a JPEG image. - bool isJpegType(FILE* ifp, bool advance); - - ImageFactory* ImageFactory::pInstance_ = 0; + ImageFactory::Registry* ImageFactory::registry_ = 0; - ImageFactory& ImageFactory::instance() + void ImageFactory::init() { - if (0 == pInstance_) { - pInstance_ = new ImageFactory; + if (0 == registry_) { + registry_ = new Registry; } - return *pInstance_; - } // ImageFactory::instance + } void ImageFactory::registerImage(Image::Type type, NewInstanceFct newInst, IsThisTypeFct isType) { + init(); assert (newInst && isType); - registry_[type] = ImageFcts(newInst, isType); - } // ImageFactory::registerImage + (*registry_)[type] = ImageFcts(newInst, isType); + } - ImageFactory::ImageFactory() + Image::Type ImageFactory::getType(const std::string& path) { - // Register a prototype of each known image - registerImage(Image::jpeg, newJpegInstance, isJpegType); - registerImage(Image::exv, newExvInstance, isExvType); - } // ImageFactory c'tor + FileIo fileIo(path); + return getType(fileIo); + } - Image::Type ImageFactory::getType(const std::string& path) const + Image::Type ImageFactory::getType(const byte* data, long size) { - FileCloser closer(fopen(path.c_str(), "rb")); - if (!closer.fp_) return Image::none; + MemIo memIo(data, size); + return getType(memIo); + } + Image::Type ImageFactory::getType(BasicIo& io) + { + IoCloser closer(io); + if (io.open() != 0) return Image::none; + Image::Type type = Image::none; - Registry::const_iterator b = registry_.begin(); - Registry::const_iterator e = registry_.end(); + Registry::const_iterator b = registry_->begin(); + Registry::const_iterator e = registry_->end(); for (Registry::const_iterator i = b; i != e; ++i) { - if (i->second.isThisType(closer.fp_, false)) { + if (i->second.isThisType(io, false)) { type = i->first; break; } @@ -127,657 +113,96 @@ namespace Exiv2 { return type; } // ImageFactory::getType - Image::AutoPtr ImageFactory::open(const std::string& path) const - { - Image::AutoPtr image; - FileCloser closer(fopen(path.c_str(), "rb")); - if (!closer.fp_) return image; - - Registry::const_iterator b = registry_.begin(); - Registry::const_iterator e = registry_.end(); - for (Registry::const_iterator i = b; i != e; ++i) - { - if (i->second.isThisType(closer.fp_, false)) { - image = i->second.newInstance(path, false); - break; - } - } - return image; - } // ImageFactory::open - - Image::AutoPtr ImageFactory::create(Image::Type type, - const std::string& path) const - { - Registry::const_iterator i = registry_.find(type); - if (i != registry_.end()) { - return i->second.newInstance(path, true); - } - return Image::AutoPtr(); - } // ImageFactory::create - - - const byte JpegBase::sos_ = 0xda; - const byte JpegBase::eoi_ = 0xd9; - const byte JpegBase::app0_ = 0xe0; - const byte JpegBase::app1_ = 0xe1; - const byte JpegBase::app13_ = 0xed; - const byte JpegBase::com_ = 0xfe; - const uint16_t JpegBase::iptc_ = 0x0404; - const char JpegBase::exifId_[] = "Exif\0\0"; - const char JpegBase::jfifId_[] = "JFIF\0"; - const char JpegBase::ps3Id_[] = "Photoshop 3.0\0"; - const char JpegBase::bimId_[] = "8BIM"; - - JpegBase::JpegBase(const std::string& path, bool create, - const byte initData[], size_t dataSize) - : path_(path), sizeExifData_(0), pExifData_(0), - sizeIptcData_(0), pIptcData_(0) - { - if (create) { - FILE* fp = fopen(path.c_str(), "w+b"); - if (fp) { - initFile(fp, initData, dataSize); - fclose(fp); - } - } - } - - int JpegBase::initFile(FILE* fp, const byte initData[], size_t dataSize) - { - if (!fp || ferror(fp)) return 4; - if (fwrite(initData, 1, dataSize, fp) != dataSize) { - return 4; - } - return 0; - } - - JpegBase::~JpegBase() - { - delete[] pExifData_; - delete[] pIptcData_; - } - - bool JpegBase::good() const - { - FileCloser closer(fopen(path_.c_str(), "rb")); - if (closer.fp_ == 0 ) return false; - return isThisType(closer.fp_, false); - } - - void JpegBase::clearMetadata() - { - clearIptcData(); - clearExifData(); - clearComment(); - } - - void JpegBase::clearIptcData() - { - delete[] pIptcData_; - pIptcData_ = 0; - sizeIptcData_ = 0; - } - - void JpegBase::clearExifData() - { - delete[] pExifData_; - pExifData_ = 0; - sizeExifData_ = 0; - } - - void JpegBase::clearComment() - { - comment_.erase(); - } - - void JpegBase::setExifData(const byte* buf, long size) + Image::AutoPtr ImageFactory::open(const std::string& path) { - if (size > 0xfffd) throw Error("Exif data too large"); - clearExifData(); - if (size) { - sizeExifData_ = size; - pExifData_ = new byte[size]; - memcpy(pExifData_, buf, size); - } + BasicIo::AutoPtr io(new FileIo(path)); + return open(io); } - void JpegBase::setIptcData(const byte* buf, long size) + Image::AutoPtr ImageFactory::open(const byte* data, long size) { - clearIptcData(); - if (size) { - sizeIptcData_ = size; - pIptcData_ = new byte[size]; - memcpy(pIptcData_, buf, size); - } + BasicIo::AutoPtr io(new MemIo(data, size)); + return open(io); } - void JpegBase::setComment(const std::string& comment) - { - comment_ = comment; - } - - void JpegBase::setMetadata(const Image& image) + Image::AutoPtr ImageFactory::open(BasicIo::AutoPtr io) { - setIptcData(image.iptcData(), image.sizeIptcData()); - setExifData(image.exifData(), image.sizeExifData()); - setComment(image.comment()); - } - - int JpegBase::advanceToMarker(FILE *fp) const - { - int c = -1; - // Skips potential padding between markers - while ((c=fgetc(fp)) != 0xff) { - if (c == EOF) return -1; - } - - // Markers can start with any number of 0xff - while ((c=fgetc(fp)) == 0xff) { - if (c == EOF) return -1; - } - return c; - } - - int JpegBase::readMetadata() - { - FileCloser closer(fopen(path_.c_str(), "rb")); - if (!closer.fp_) return 1; - - // Ensure that this is the correct image type - if (!isThisType(closer.fp_, true)) { - if (ferror(closer.fp_) || feof(closer.fp_)) return 1; - return 2; - } - clearMetadata(); - int search = 3; - const long bufMinSize = 16; - long bufRead = 0; - DataBuf buf(bufMinSize); - - // Read section marker - int marker = advanceToMarker(closer.fp_); - if (marker < 0) return 2; - - while (marker != sos_ && marker != eoi_ && search > 0) { - // Read size and signature (ok if this hits EOF) - bufRead = (long)fread(buf.pData_, 1, bufMinSize, closer.fp_); - if (ferror(closer.fp_)) return 1; - uint16_t size = getUShort(buf.pData_, bigEndian); - - if (marker == app1_ && memcmp(buf.pData_ + 2, exifId_, 6) == 0) { - if (size < 8) return 2; - // Seek to begining and read the Exif data - fseek(closer.fp_, 8-bufRead, SEEK_CUR); - long sizeExifData = size - 8; - pExifData_ = new byte[sizeExifData]; - fread(pExifData_, 1, sizeExifData, closer.fp_); - if (ferror(closer.fp_) || feof(closer.fp_)) { - delete[] pExifData_; - pExifData_ = 0; - return 1; - } - // Set the size and offset of the Exif data buffer - sizeExifData_ = sizeExifData; - --search; - } - else if (marker == app13_ && memcmp(buf.pData_ + 2, ps3Id_, 14) == 0) { - if (size < 16) return 2; - // Read the rest of the APP13 segment - // needed if bufMinSize!=16: fseek(closer.fp_, 16-bufRead, SEEK_CUR); - DataBuf psData(size - 16); - fread(psData.pData_, 1, psData.size_, closer.fp_); - if (ferror(closer.fp_) || feof(closer.fp_)) return 1; - const byte *record = 0; - uint16_t sizeIptc = 0; - uint16_t sizeHdr = 0; - // Find actual Iptc data within the APP13 segment - if (!locateIptcData(psData.pData_, psData.size_, &record, - &sizeHdr, &sizeIptc)) { - assert(sizeIptc); - sizeIptcData_ = sizeIptc; - pIptcData_ = new byte[sizeIptc]; - memcpy( pIptcData_, record + sizeHdr, sizeIptc ); - } - --search; - } - else if (marker == com_ && comment_.empty()) - { - if (size < 2) return 2; - // Jpegs can have multiple comments, but for now only read - // the first one (most jpegs only have one anyway). Comments - // are simple single byte ISO-8859-1 strings. - fseek(closer.fp_, 2-bufRead, SEEK_CUR); - buf.alloc(size-2); - fread(buf.pData_, 1, size-2, closer.fp_); - if (ferror(closer.fp_) || feof(closer.fp_)) return 1; - comment_.assign(reinterpret_cast(buf.pData_), size-2); - while ( comment_.length() - && comment_.at(comment_.length()-1) == '\0') { - comment_.erase(comment_.length()-1); - } - --search; - } - else { - if (size < 2) return 2; - // Skip the remainder of the unknown segment - if (fseek(closer.fp_, size-bufRead, SEEK_CUR)) return 2; - } - // Read the beginning of the next segment - marker = advanceToMarker(closer.fp_); - if (marker < 0) return 2; - } - return 0; - } // JpegBase::readMetadata - - - // Operates on raw data (rather than file streams) to simplify reuse - int JpegBase::locateIptcData(const byte *pPsData, - long sizePsData, - const byte **record, - uint16_t *const sizeHdr, - uint16_t *const sizeIptc) const - { - assert(record); - assert(sizeHdr); - assert(sizeIptc); - // Used for error checking - long position = 0; - - // Data should follow Photoshop format, if not exit - while (position <= (sizePsData - 14) && - memcmp(pPsData + position, bimId_, 4)==0) { - const byte *hrd = pPsData + position; - position += 4; - uint16_t type = getUShort(pPsData+ position, bigEndian); - position += 2; - - // Pascal string is padded to have an even size (including size byte) - byte psSize = pPsData[position] + 1; - psSize += (psSize & 1); - position += psSize; - if (position >= sizePsData) return -2; - - // Data is also padded to be even - long dataSize = getULong(pPsData + position, bigEndian); - position += 4; - if (dataSize > sizePsData - position) return -2; - - if (type == iptc_) { - *sizeIptc = static_cast(dataSize); - *sizeHdr = psSize + 10; - *record = hrd; - return 0; - } - position += dataSize + (dataSize & 1); - } - return 3; - } // JpegBase::locateIptcData - - int JpegBase::writeMetadata() - { - FileCloser reader(fopen(path_.c_str(), "rb")); - if (!reader.fp_) return 1; - - // Write the output to a temporary file - pid_t pid = getpid(); - std::string tmpname = path_ + toString(pid); - FileCloser writer(fopen(tmpname.c_str(), "wb")); - if (!writer.fp_) return -3; - - int rc = doWriteMetadata(reader.fp_, writer.fp_); - writer.close(); - reader.close(); - if (rc == 0) { - // Workaround for MSVCRT rename that does not overwrite existing files - if (remove(path_.c_str()) != 0) rc = -4; - } - if (rc == 0) { - // rename temporary file - if (rename(tmpname.c_str(), path_.c_str()) == -1) rc = -4; - } - if (rc != 0) { - // remove temporary file - remove(tmpname.c_str()); - } - return rc; - } // JpegBase::writeMetadata - - int JpegBase::doWriteMetadata(FILE *ifp, FILE* ofp) const - { - if (!ifp) return 1; - if (!ofp) return 4; - - // Ensure that this is the correct image type - if (!isThisType(ifp, true)) { - if (ferror(ifp) || feof(ifp)) return 1; - return 2; - } - - const long bufMinSize = 16; - long bufRead = 0; - DataBuf buf(bufMinSize); - const long seek = ftell(ifp); - int count = 0; - int search = 0; - int insertPos = 0; - int skipApp1Exif = -1; - int skipApp13Ps3 = -1; - int skipCom = -1; - DataBuf psData; - - // Write image header - if (writeHeader(ofp)) return 4; - - // Read section marker - int marker = advanceToMarker(ifp); - if (marker < 0) return 2; - - // First find segments of interest. Normally app0 is first and we want - // to insert after it. But if app0 comes after com, app1 and app13 then - // don't bother. - while (marker != sos_ && marker != eoi_ && search < 3) { - // Read size and signature (ok if this hits EOF) - bufRead = (long)fread(buf.pData_, 1, bufMinSize, ifp); - if (ferror(ifp)) return 1; - uint16_t size = getUShort(buf.pData_, bigEndian); - - if (marker == app0_) { - if (size < 2) return 2; - insertPos = count + 1; - if (fseek(ifp, size-bufRead, SEEK_CUR)) return 2; - } - else if (marker == app1_ && memcmp(buf.pData_ + 2, exifId_, 6) == 0) { - if (size < 8) return 2; - skipApp1Exif = count; - ++search; - if (fseek(ifp, size-bufRead, SEEK_CUR)) return 2; - } - else if (marker == app13_ && memcmp(buf.pData_ + 2, ps3Id_, 14) == 0) { - if (size < 16) return 2; - skipApp13Ps3 = count; - ++search; - // needed if bufMinSize!=16: fseek(ifp, 16-bufRead, SEEK_CUR); - psData.alloc(size - 16); - // Load PS data now to allow reinsertion at any point - fread(psData.pData_, 1, psData.size_, ifp); - if (ferror(ifp) || feof(ifp)) return 1; - } - else if (marker == com_ && skipCom == -1) { - if (size < 2) return 2; - // Jpegs can have multiple comments, but for now only handle - // the first one (most jpegs only have one anyway). - skipCom = count; - ++search; - if (fseek(ifp, size-bufRead, SEEK_CUR)) return 2; - } - else { - if (size < 2) return 2; - if (fseek(ifp, size-bufRead, SEEK_CUR)) return 2; - } - marker = advanceToMarker(ifp); - if (marker < 0) return 2; - ++count; - } - - if (pExifData_) ++search; - if (pIptcData_) ++search; - if (!comment_.empty()) ++search; - - fseek(ifp, seek, SEEK_SET); - count = 0; - marker = advanceToMarker(ifp); - if (marker < 0) return 2; - - // To simplify this a bit, new segments are inserts at either the start - // or right after app0. This is standard in most jpegs, but has the - // potential to change segment ordering (which is allowed). - // Segments are erased if there is no assigned metadata. - while (marker != sos_ && search > 0) { - // Read size and signature (ok if this hits EOF) - bufRead = (long)fread(buf.pData_, 1, bufMinSize, ifp); - if (ferror(ifp)) return 1; - // Careful, this can be a meaningless number for empty - // images with only an eoi_ marker - uint16_t size = getUShort(buf.pData_, bigEndian); - - if (insertPos == count) { - byte tmpBuf[18]; - if (!comment_.empty()) { - // Write COM marker, size of comment, and string - tmpBuf[0] = 0xff; - tmpBuf[1] = com_; - us2Data(tmpBuf + 2, - static_cast(comment_.length()+3), bigEndian); - if (fwrite(tmpBuf, 1, 4, ofp) != 4) return 4; - if ( fwrite(comment_.data(), 1, comment_.length(), ofp) - != comment_.length()) return 4; - if (fputc(0, ofp)==EOF) return 4; - if (ferror(ofp)) return 4; - --search; - } - if (pExifData_) { - // Write APP1 marker, size of APP1 field, Exif id and Exif data - tmpBuf[0] = 0xff; - tmpBuf[1] = app1_; - us2Data(tmpBuf + 2, - static_cast(sizeExifData_+8), - bigEndian); - memcpy(tmpBuf + 4, exifId_, 6); - if (fwrite(tmpBuf, 1, 10, ofp) != 10) return 4; - if ( fwrite(pExifData_, 1, sizeExifData_, ofp) - != (size_t)sizeExifData_) return 4; - if (ferror(ofp)) return 4; - --search; - } - - const byte *record = psData.pData_; - uint16_t sizeIptc = 0; - uint16_t sizeHdr = 0; - // Safe to call with zero psData.size_ - locateIptcData(psData.pData_, psData.size_, &record, &sizeHdr, &sizeIptc); - - // Data is rounded to be even - const int sizeOldData = sizeHdr + sizeIptc + (sizeIptc & 1); - if (psData.size_ > sizeOldData || pIptcData_) { - // write app13 marker, new size, and ps3Id - tmpBuf[0] = 0xff; - tmpBuf[1] = app13_; - const int sizeNewData = sizeIptcData_ ? - sizeIptcData_+(sizeIptcData_&1)+12 : 0; - us2Data(tmpBuf + 2, - static_cast(psData.size_-sizeOldData+sizeNewData+16), - bigEndian); - memcpy(tmpBuf + 4, ps3Id_, 14); - if (fwrite(tmpBuf, 1, 18, ofp) != 18) return 4; - if (ferror(ofp)) return 4; - - const long sizeFront = (long)(record - psData.pData_); - const long sizeEnd = psData.size_ - sizeFront - sizeOldData; - // write data before old record. - if (fwrite(psData.pData_, 1, sizeFront, ofp) != (size_t)sizeFront) return 4; + Image::AutoPtr image; + IoCloser closer(*io); + if (io->open() != 0) return image; - // write new iptc record if we have it - if (pIptcData_) { - memcpy(tmpBuf, bimId_, 4); - us2Data(tmpBuf+4, iptc_, bigEndian); - tmpBuf[6] = 0; - tmpBuf[7] = 0; - ul2Data(tmpBuf + 8, sizeIptcData_, bigEndian); - if (fwrite(tmpBuf, 1, 12, ofp) != 12) return 4; - if ( fwrite(pIptcData_, 1, sizeIptcData_ , ofp) - != (size_t)sizeIptcData_) return 4; - // data is padded to be even (but not included in size) - if (sizeIptcData_ & 1) { - if (fputc(0, ofp)==EOF) return 4; - } - if (ferror(ofp)) return 4; - --search; - } - - // write existing stuff after record - if ( fwrite(record+sizeOldData, 1, sizeEnd, ofp) - != (size_t)sizeEnd) return 4; - if (ferror(ofp)) return 4; - } - } - if (marker == eoi_) { + Registry::const_iterator b = registry_->begin(); + Registry::const_iterator e = registry_->end(); + for (Registry::const_iterator i = b; i != e; ++i) + { + if (i->second.isThisType(*io, false)) { + image = i->second.newInstance(io, false); break; } - else if (skipApp1Exif==count || skipApp13Ps3==count || skipCom==count) { - --search; - fseek(ifp, size-bufRead, SEEK_CUR); - } - else { - if (size < 2) return 2; - buf.alloc(size+2); - fseek(ifp, -bufRead-2, SEEK_CUR); - fread(buf.pData_, 1, size+2, ifp); - if (ferror(ifp) || feof(ifp)) return 1; - if (fwrite(buf.pData_, 1, size+2, ofp) != (size_t)size+2) return 4; - if (ferror(ofp)) return 4; - } - - // Next marker - marker = advanceToMarker(ifp); - if (marker < 0) return 2; - ++count; - } - - // Copy rest of the stream - fseek(ifp, -2, SEEK_CUR); - fflush( ofp ); - buf.alloc(4096); - size_t readSize = 0; - while ((readSize=fread(buf.pData_, 1, buf.size_, ifp))) { - if (fwrite(buf.pData_, 1, readSize, ofp) != readSize) return 4; - } - if (ferror(ofp)) return 4; - - return 0; - }// JpegBase::doWriteMetadata - - - const byte JpegImage::soi_ = 0xd8; - const byte JpegImage::blank_[] = { - 0xFF,0xD8,0xFF,0xDB,0x00,0x84,0x00,0x10,0x0B,0x0B,0x0B,0x0C,0x0B,0x10,0x0C,0x0C, - 0x10,0x17,0x0F,0x0D,0x0F,0x17,0x1B,0x14,0x10,0x10,0x14,0x1B,0x1F,0x17,0x17,0x17, - 0x17,0x17,0x1F,0x1E,0x17,0x1A,0x1A,0x1A,0x1A,0x17,0x1E,0x1E,0x23,0x25,0x27,0x25, - 0x23,0x1E,0x2F,0x2F,0x33,0x33,0x2F,0x2F,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40, - 0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x01,0x11,0x0F,0x0F,0x11,0x13,0x11,0x15,0x12, - 0x12,0x15,0x14,0x11,0x14,0x11,0x14,0x1A,0x14,0x16,0x16,0x14,0x1A,0x26,0x1A,0x1A, - 0x1C,0x1A,0x1A,0x26,0x30,0x23,0x1E,0x1E,0x1E,0x1E,0x23,0x30,0x2B,0x2E,0x27,0x27, - 0x27,0x2E,0x2B,0x35,0x35,0x30,0x30,0x35,0x35,0x40,0x40,0x3F,0x40,0x40,0x40,0x40, - 0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0xFF,0xC0,0x00,0x11,0x08,0x00,0x01,0x00, - 0x01,0x03,0x01,0x22,0x00,0x02,0x11,0x01,0x03,0x11,0x01,0xFF,0xC4,0x00,0x4B,0x00, - 0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x07,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x10,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x11,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xDA,0x00,0x0C,0x03,0x01,0x00,0x02, - 0x11,0x03,0x11,0x00,0x3F,0x00,0xA0,0x00,0x0F,0xFF,0xD9 }; - - JpegImage::JpegImage(const std::string& path, bool create) - : JpegBase(path, create, blank_, sizeof(blank_)) - { - } - - int JpegImage::writeHeader(FILE* ofp) const - { - // Jpeg header - byte tmpBuf[2]; - tmpBuf[0] = 0xff; - tmpBuf[1] = soi_; - if (fwrite(tmpBuf, 1, 2, ofp) != 2) return 4; - if (ferror(ofp)) return 4; - return 0; - } - - bool JpegImage::isThisType(FILE* ifp, bool advance) const - { - return isJpegType(ifp, advance); - } - - Image::AutoPtr newJpegInstance(const std::string& path, bool create) - { - Image::AutoPtr image; - if (create) { - image = Image::AutoPtr(new JpegImage(path, true)); - } - else { - image = Image::AutoPtr(new JpegImage(path, false)); - } - if (!image->good()) { - image.reset(); } return image; - } + } // ImageFactory::open - bool isJpegType(FILE* ifp, bool advance) - { - bool result = true; - byte tmpBuf[2]; - fread(tmpBuf, 1, 2, ifp); - if (ferror(ifp) || feof(ifp)) return false; - if (0xff!=tmpBuf[0] || JpegImage::soi_!=tmpBuf[1]) { - result = false; - } - if (!advance || !result ) fseek(ifp, -2, SEEK_CUR); - return result; + Image::AutoPtr ImageFactory::create(Image::Type type, + const std::string& path) + { + FileIo *fileIo = new FileIo(path); + BasicIo::AutoPtr io(fileIo); + // Create or overwrite the file, then close it + if (fileIo->open("w+b") != 0) return Image::AutoPtr(); + fileIo->close(); + return create(type, io); } - - const char ExvImage::exiv2Id_[] = "Exiv2"; - const byte ExvImage::blank_[] = { 0xff,0x01,'E','x','i','v','2',0xff,0xd9 }; - ExvImage::ExvImage(const std::string& path, bool create) - : JpegBase(path, create, blank_, sizeof(blank_)) + Image::AutoPtr ImageFactory::create(Image::Type type) { + BasicIo::AutoPtr io(new MemIo); + return create(type, io); } - int ExvImage::writeHeader(FILE* ofp) const - { - // Exv header - byte tmpBuf[7]; - tmpBuf[0] = 0xff; - tmpBuf[1] = 0x01; - memcpy(tmpBuf + 2, exiv2Id_, 5); - if (fwrite(tmpBuf, 1, 7, ofp) != 7) return 4; - if (ferror(ofp)) return 4; - return 0; - } - bool ExvImage::isThisType(FILE* ifp, bool advance) const + Image::AutoPtr ImageFactory::create(Image::Type type, + BasicIo::AutoPtr io) { - return isExvType(ifp, advance); - } + // BasicIo instance does not need to be open + Registry::const_iterator i = registry_->find(type); + if (i != registry_->end()) { + return i->second.newInstance(io, true); + } + return Image::AutoPtr(); + } // ImageFactory::create - Image::AutoPtr newExvInstance(const std::string& path, bool create) + std::string Image::strError(int rc, const std::string& path) { - Image::AutoPtr image; - if (create) { - image = Image::AutoPtr(new ExvImage(path, true)); - } - else { - image = Image::AutoPtr(new ExvImage(path, false)); + std::string error = path + ": "; + switch (rc) { + case -1: + error += "Failed to open the file"; + break; + case -2: + error += "The file contains data of an unknown image type"; + break; + case -3: + error += "Couldn't open temporary file"; + break; + case -4: + error += "Renaming temporary file failed"; + break; + case 1: + error += "Couldn't read from the input file"; + break; + case 2: + error += "This does not look like a JPEG image"; + break; + default: + error += "Accessing image data failed, rc = " + toString(rc); + break; } - if (!image->good()) image.reset(); - return image; - } + return error; + } // Image::strError - bool isExvType(FILE* ifp, bool advance) - { - bool result = true; - byte tmpBuf[7]; - fread(tmpBuf, 1, 7, ifp); - if (ferror(ifp) || feof(ifp)) return false; - if (0xff!=tmpBuf[0] || 0x01!=tmpBuf[1] || - memcmp(tmpBuf + 2, ExvImage::exiv2Id_, 5) != 0) { - result = false; - } - if (!advance || !result ) fseek(ifp, -7, SEEK_CUR); - return result; - } TiffHeader::TiffHeader(ByteOrder byteOrder) : byteOrder_(byteOrder), tag_(0x002a), offset_(0x00000008) diff --git a/src/image.hpp b/src/image.hpp index 8bb1f278..d8f11412 100644 --- a/src/image.hpp +++ b/src/image.hpp @@ -29,6 +29,7 @@ @date 09-Jan-04, ahu: created
11-Feb-04, ahu: isolated as a component
19-Jul-04, brad: revamped to be more flexible and support Iptc + 15-Jan-05, brad: inside-out design changes */ #ifndef IMAGE_HPP_ #define IMAGE_HPP_ @@ -36,29 +37,39 @@ // ***************************************************************************** // included header files #include "types.hpp" +#include "basicio.hpp" // + standard includes #include #include -#include // ***************************************************************************** // namespace extensions namespace Exiv2 { - + // ***************************************************************************** -// class definitions +// class declarations + class ExifData; + class IptcData; + +// ***************************************************************************** +// class definitions /*! - @brief Abstract base class defining the interface for an image. + @brief Abstract base class defining the interface for an image. This is + the top-level interface to the Exiv2 library. + + Most client apps will obtain an Image instance by calling a static + ImageFactory method. The Image class can then be used to to + read, write, and save metadata. */ class Image { public: - //! Image auto_ptr type - typedef std::auto_ptr AutoPtr; - //! Supported image formats enum Type { none, jpeg, exv }; + + //! Image auto_ptr type + typedef std::auto_ptr AutoPtr; //! @name Creators //@{ @@ -69,61 +80,67 @@ namespace Exiv2 { //! @name Manipulators //@{ /*! - @brief Read metadata from assigned image file into internal - buffers. + @brief Read metadata from assigned image. Before this method + is called, the various metadata types (Iptc, Exif) will be empty. @return 0 if successful. */ virtual int readMetadata() =0; /*! - @brief Write metadata from internal buffers into to the image fle. + @brief Write metadata back to the image. + + All existing metadata sections in the image are either created, + replaced, or erased. If values for a given metadata type have been + assigned, a section for that metadata type will either be created or + replaced. If no values have been assigned to a given metadata type, + any exists section for that metadata type will be removed from the + image. + @return 0 if successful. */ virtual int writeMetadata() =0; /*! - @brief Set the Exif data. The data is copied into an internal data - buffer and is not written until writeMetadata is called. - @param buf Pointer to the new Exif data. - @param size Size in bytes of new Exif data. + @brief Assign new exif data. The new exif data is not written + to the image until the writeMetadata() method is called. + @param exifData An ExifData instance holding exif data to be copied */ - virtual void setExifData(const byte* buf, long size) =0; + virtual void setExifData(const ExifData& exifData) =0; /*! - @brief Erase any buffered Exif data. Exif data is not removed - from the actual file until writeMetadata is called. + @brief Erase any buffered Exif data. Exif data is not removed from + the actual image until the writeMetadata() method is called. */ virtual void clearExifData() =0; /*! - @brief Set the Iptc data. The data is copied into an internal data - buffer and is not written until writeMetadata is called. - @param buf Pointer to the new Iptc data. - @param size Size in bytes of new Iptc data. + @brief Assign new iptc data. The new iptc data is not written + to the image until the writeMetadata() method is called. + @param iptcData An IptcData instance holding iptc data to be copied */ - virtual void setIptcData(const byte* buf, long size) =0; + virtual void setIptcData(const IptcData& iptcData) =0; /*! - @brief Erase any buffered Iptc data. Iptc data is not removed - from the actual file until writeMetadata is called. + @brief Erase any buffered Iptc data. Iptc data is not removed from + the actual image until the writeMetadata() method is called. */ virtual void clearIptcData() =0; /*! - @brief Set the image comment. The data is copied into an internal data - buffer and is not written until writeMetadata is called. + @brief Set the image comment. The new comment is not written + to the image until the writeMetadata() method is called. @param comment String containing comment. */ virtual void setComment(const std::string& comment) =0; /*! @brief Erase any buffered comment. Comment is not removed - from the actual file until writeMetadata is called. + from the actual image until the writeMetadata() method is called. */ virtual void clearComment() =0; /*! - @brief Copy all existing metadata from source %Image. The data is - copied into internal buffers and is not written until - writeMetadata is called. + @brief Copy all existing metadata from source Image. The data is + copied into internal buffers and is not written to the image + until the writeMetadata() method is called. @param image Metadata source. All metadata types are copied. */ virtual void setMetadata(const Image& image) =0; /*! @brief Erase all buffered metadata. Metadata is not removed - from the actual file until writeMetadata is called. + from the actual image until the writeMetadata() method is called. */ virtual void clearMetadata() =0; //@} @@ -131,31 +148,91 @@ namespace Exiv2 { //! @name Accessors //@{ /*! - @brief Check if the %Image instance is valid. Use after object + @brief Check if the Image instance is valid. Use after object construction. - @return true if the %Image is in a valid state. + @return true if the Image is in a valid state. */ virtual bool good() const =0; - //! Return the size of the Exif data in bytes. - virtual long sizeExifData() const =0; /*! - @brief Return a read-only pointer to an Exif data buffer. Do not - attempt to write to this buffer. + @brief Returns an ExifData instance containing currently buffered + exif data. + + The exif data may have been read from the image by + a previous call to readMetadata() or added directly. The exif + data in the returned instance will be written to the image when + writeMetadata() is called. + + @return read only ExifData instance containing exif values + */ + virtual const ExifData& exifData() const =0; + /*! + @brief Returns an ExifData instance containing currently buffered + exif data. + + The contained exif data may have been read from the image by + a previous call to readMetadata() or added directly. The exif + data in the returned instance will be written to the image when + writeMetadata() is called. + + @return modifiable ExifData instance containing exif values */ - virtual const byte* exifData() const =0; - //! Return the size of the Iptc data in bytes. - virtual long sizeIptcData() const =0; + virtual ExifData& exifData() =0; /*! - @brief Return a read-only pointer to an Iptc data buffer. Do not - attempt to write to this buffer. + @brief Returns an IptcData instance containing currently buffered + iptc data. + + The contained iptc data may have been read from the image by + a previous call to readMetadata() or added directly. The iptc + data in the returned instance will be written to the image when + writeMetadata() is called. + + @return modifiable IptcData instance containing iptc values */ - virtual const byte* iptcData() const =0; + virtual const IptcData& iptcData() const =0; + /*! + @brief Returns an ExifData instance containing currently buffered + exif data. + + The contained iptc data may have been read from the image by + a previous call to readMetadata() or added directly. The iptc + data in the returned instance will be written to the image when + writeMetadata() is called. + + @return modifiable IptcData instance containing iptc values + */ + virtual IptcData& iptcData() =0; /*! @brief Return a copy of the image comment. May be an empty string. */ virtual std::string comment() const =0; + /*! + @brief Return a reference to the BasicIo instance being used for Io. + + This refence is particularly useful to reading the results of + operations on a MemIo instance. For example after metadata has + been modified and the writeMetadata() method has been called, + this method can be used to get access to the modified image. + + @return BasicIo instance that can be used to read or write image + data directly. + @note If the returned BasicIo is used to write to the image, the + Image class will not see those changes until the readMetadata() + method is called. + */ + virtual BasicIo& io() const = 0; + /*! + @brief Convert the return code \em rc from various methods + in this class to string messages. + @param rc Error code. + @param path %Image file or other identifying string. + @return String containing error message. + + Todo: Implement global handling of error messages + */ + static std::string strError(int rc, const std::string& path); //@} + protected: //! @name Creators //@{ @@ -173,17 +250,15 @@ namespace Exiv2 { }; // class Image //! Type for function pointer that creates new Image instances - typedef Image::AutoPtr (*NewInstanceFct)(const std::string& path, - bool create); + typedef Image::AutoPtr (*NewInstanceFct)(BasicIo::AutoPtr io, bool create); //! Type for function pointer that checks image types - typedef bool (*IsThisTypeFct)(FILE* ifp, bool advance); + typedef bool (*IsThisTypeFct)(BasicIo& iIo, bool advance); /*! - @brief Image factory. + @brief Returns an Image instance of the specified type. - Creates an instance of the image of the requested type. The factory is - implemented as a singleton, which can be accessed only through the static - member function instance(). + The factory is implemented as a singleton, which can be accessed + through static member functions. */ class ImageFactory { public: @@ -192,16 +267,16 @@ namespace Exiv2 { /*! @brief Register image type together with its function pointers. - The image factory creates new images calling their associated + The image factory creates new images by calling their associated function pointer. Additional images can be added by registering new type and function pointers. If called for a type that already - exists in the list, the corresponding prototype is replaced. + exists in the list, the corresponding functions are replaced. @param type Image type. @param newInst Function pointer for creating image instances. @param isType Function pointer to test for matching image types. */ - void registerImage(Image::Type type, + static void registerImage(Image::Type type, NewInstanceFct newInst, IsThisTypeFct isType); //@} @@ -209,41 +284,101 @@ namespace Exiv2 { //! @name Accessors //@{ /*! - @brief Create an %Image of the appropriate type by opening the - specified file. File type is derived from the contents of the - file. - @param path %Image file. The contents of the file are tested to - determine the image type to open. File extension is ignored. - @return An auto-pointer that owns an %Image of the type derived from - the file. If no image type could be determined, the pointer is 0. - */ - Image::AutoPtr open(const std::string& path) const; - /*! - @brief Create an %Image of the requested type by creating a new - file. If the file already exists, it will be overwritten. - @param type Type of the image to be created. + @brief Create an Image subclass of the appropriate type by reading + the specified file. %Image type is derived from the file + contents. @param path %Image file. The contents of the file are tested to - determine the image type to open. File extension is ignored. - @return An auto-pointer that owns an %Image of the requested type. - If the image type is not supported, the pointer is 0. - */ - Image::AutoPtr create(Image::Type type, const std::string& path) const; - /*! - @brief Returns the image type of the provided file. - @param path %Image file. The contents of the file are tested to - determine the image type. File extension is ignored. - @return %Image type of Image::none if the type is not recognized. - */ - Image::Type getType(const std::string& path) const; + determine the image type. File extension is ignored. + @return An auto-pointer that owns an Image instance whose type + matches that of the file. If no image type could be determined, + the pointer is 0. + */ + static Image::AutoPtr open(const std::string& path); + /*! + @brief Create an Image subclass of the appropriate type by reading + the provided memory. %Image type is derived from the memory + contents. + @param data Pointer to a data buffer containing an image. The contents + of the memory are tested to determine the image type. + @param size Number of bytes pointed to by \em data. + @return An auto-pointer that owns an Image instance whose type + matches that of the data buffer. If no image type could be + determined, the pointer is 0. + */ + static Image::AutoPtr open(const byte* data, long size); + /*! + @brief Create an Image subclass of the appropriate type by reading + the provided BasicIo instance. %Image type is derived from the + data provided by \em io. The passed in \em io instance is + (re)opened by this method. + @param io An auto-pointer that owns a BasicIo instance that provides + image data. The contents of the image data are tested to determine + the type. \b Important: This method takes ownership of the passed + in BasicIo instance through the auto-pointer. Callers should not + continue to use the BasicIo instance after it is passed to this method. + Use theImage::io() method to get a temporary reference. + @return An auto-pointer that owns an Image instance whose type + matches that of the \em io data. If no image type could be + determined, the pointer is 0. + */ + static Image::AutoPtr open(BasicIo::AutoPtr io); + /*! + @brief Create an Image subclass of the requested type by creating a + new image file. If the file already exists, it will be overwritten. + @param type Type of the image to be created. + @param path %Image file to create. File extension is ignored. + @return An auto-pointer that owns an Image instance of the requested + type. If the image type is not supported, the pointer is 0. + */ + static Image::AutoPtr create(Image::Type type, const std::string& path); + /*! + @brief Create an Image subclass of the requested type by creating a + new image in memory. + @param type Type of the image to be created. + @return An auto-pointer that owns an Image instance of the requested + type. If the image type is not supported, the pointer is 0. + */ + static Image::AutoPtr create(Image::Type type); + /*! + @brief Create an Image subclass of the requested type by writing a + new image to a BasicIo instance. If the BasicIo instance already + contains data, it will be overwritten. + @param type Type of the image to be created. + @param io An auto-pointer that owns a BasicIo instance that will + be written to when creating a new image. \b Important: This + method takes ownership of the passed in BasicIo instance through + the auto-pointer. Callers should not continue to use the BasicIo + instance after it is passed to this method. Use theImage::io() + method to get a temporary reference. + @return An auto-pointer that owns an Image instance of the requested + type. If the image type is not supported, the pointer is 0. + */ + static Image::AutoPtr create(Image::Type type, BasicIo::AutoPtr io); + /*! + @brief Returns the image type of the provided file. + @param path %Image file. The contents of the file are tested to + determine the image type. File extension is ignored. + @return %Image type or Image::none if the type is not recognized. + */ + static Image::Type getType(const std::string& path); + /*! + @brief Returns the image type of the provided data buffer. + @param data Pointer to a data buffer containing an image. The contents + of the memory are tested to determine the image type. + @param size Number of bytes pointed to by \em data. + @return %Image type or Image::none if the type is not recognized. + */ + static Image::Type getType(const byte* data, long size); + /*! + @brief Returns the image type of data provided by a BasicIo instance. + The passed in \em io instance is (re)opened by this method. + @param io A BasicIo instance that provides image data. The contents + of the image data are tested to determine the type. + @return %Image type or Image::none if the type is not recognized. + */ + static Image::Type getType(BasicIo& io); //@} - /*! - @brief Get access to the image factory. - - Clients access the image factory exclusively through - this method. - */ - static ImageFactory& instance(); private: //! @name Creators @@ -252,6 +387,8 @@ namespace Exiv2 { ImageFactory(); //! Prevent copy construction: not implemented. ImageFactory(const ImageFactory& rhs); + //! Creates the private static instance + static void init(); //@} //! Struct for storing image function pointers. @@ -265,331 +402,12 @@ namespace Exiv2 { }; // DATA - //! Pointer to the one and only instance of this class. - static ImageFactory* pInstance_; //! Type used to store Image creation functions typedef std::map Registry; //! List of image types and corresponding creation functions. - Registry registry_; - + static Registry* registry_; }; // class ImageFactory - /*! - @brief Abstract helper base class to access JPEG images - */ - class JpegBase : public Image { - public: - //! @name Creators - //@{ - //! Virtual destructor. - virtual ~JpegBase(); - //@} - //! @name Manipulators - //@{ - /*! - @brief Read all metadata from the file into the internal - data buffers. This method returns success even when - no metadata is found in the image. Callers must therefore - check the size of indivdual metadata types before - accessing the data. - @return 0 if successful;
- 1 if reading from the file failed - (could be caused by invalid image);
- 2 if the file does not contain a valid image;
- */ - int readMetadata(); - /*! - @brief Write all buffered metadata to associated file. All existing - metadata sections in the file are either replaced or erased. - If data for a given metadata type has not been assigned, - then that metadata type will be erased from the file. - @return 0 if successful;
- 1 if reading from the file failed;
- 2 if the file does not contain a valid image;
- 4 if the temporary output file can not be written to;
- -1 if the newly created file could not be reopened;
- -3 if the temporary output file can not be opened;
- -4 if renaming the temporary file fails;
- */ - int writeMetadata(); - /*! - @brief Set the Exif data. The data is copied into an internal data - buffer and is not written until writeMetadata is called. - @param buf Pointer to the new Exif data. - @param size Size in bytes of new Exif data. - - @throw Error ("Exif data too large") if the exif data is larger than - 65535 bytes (the maximum size of JPEG APP segments) - */ - void setExifData(const byte* buf, long size); - void clearExifData(); - void setIptcData(const byte* buf, long size); - void clearIptcData(); - void setComment(const std::string& comment); - void clearComment(); - void setMetadata(const Image& image); - void clearMetadata(); - //@} - - //! @name Accessors - //@{ - bool good() const; - long sizeExifData() const { return sizeExifData_; } - const byte* exifData() const { return pExifData_; } - long sizeIptcData() const { return sizeIptcData_; } - const byte* iptcData() const { return pIptcData_; } - std::string comment() const { return comment_; } - //@} - - protected: - //! @name Creators - //@{ - /*! - @brief Constructor that can either open an existing image or create - a new image from scratch. If a new image is to be created, any - existing file is overwritten - @param path Full path to image file. - @param create Specifies if an existing file should be opened (false) - or if a new file should be created (true). - @param initData Data to initialize newly created files. Only used - when %create is true. Should contain the data for the smallest - valid image of the calling subclass. - @param dataSize Size of initData in bytes. - */ - JpegBase(const std::string& path, bool create, - const byte initData[], size_t dataSize); - //@} - //! @name Accessors - //@{ - /*! - @brief Writes the image header (aka signature) to the file stream. - @param ofp File stream that the header is written to. - @return 0 if successful;
- 4 if the output file can not be written to;
- */ - virtual int writeHeader(FILE* ofp) const =0; - /*! - @brief Determine if the content of the stream is of the type of this - class. - - The advance flag determines if the read position in the stream is - moved (see below). This applies only if the type matches and the - function returns true. If the type does not match, the stream - position is not changed. However, if reading from the stream fails, - the stream position is undefined. Consult the stream state to obtain - more information in this case. - - @param ifp Input file stream. - @param advance Flag indicating whether the read position in the stream - should be advanced by the number of characters read to - analyse the stream (true) or left at its original - position (false). This applies only if the type matches. - @return true if the stream data matches the type of this class;
- false if the stream data does not match;
- */ - virtual bool isThisType(FILE* ifp, bool advance) const =0; - //@} - - // Constant Data - static const byte sos_; //!< JPEG SOS marker - static const byte eoi_; //!< JPEG EOI marker - static const byte app0_; //!< JPEG APP0 marker - static const byte app1_; //!< JPEG APP1 marker - static const byte app13_; //!< JPEG APP13 marker - static const byte com_; //!< JPEG Comment marker - static const char exifId_[]; //!< Exif identifier - static const char jfifId_[]; //!< JFIF identifier - static const char ps3Id_[]; //!< Photoshop marker - static const char bimId_[]; //!< Photoshop marker - static const uint16_t iptc_; //!< Photoshop Iptc marker - - private: - // DATA - const std::string path_; //!< Image file name - long sizeExifData_; //!< Size of the Exif data buffer - byte* pExifData_; //!< Exif data buffer - long sizeIptcData_; //!< Size of the Iptc data buffer - byte* pIptcData_; //!< Iptc data buffer - std::string comment_; //!< JPEG comment - - // METHODS - /*! - @brief Advances file stream to one byte past the next Jpeg marker - and returns the marker. This method should be called when the - file stream is positioned one byte past the end of a Jpeg segment. - @param fp File stream to advance - @return the next Jpeg segment marker if successful;
- -1 if a maker was not found before EOF;
- */ - int advanceToMarker(FILE *fp) const; - /*! - @brief Locates Photoshop formated Iptc data in a memory buffer. - Operates on raw data (rather than file streams) to simplify reuse. - @param pPsData Pointer to buffer containing entire payload of - Photoshop formated APP13 Jpeg segment. - @param sizePsData Size in bytes of pPsData. - @param record Output value that is set to the start of the Iptc - data block within pPsData (may not be null). - @param sizeHdr Output value that is set to the size of the header - within the Iptc data block pointed to by record (may not - be null). - @param sizeIptc Output value that is set to the size of the actual - Iptc data within the Iptc data block pointed to by record - (may not be null). - @return 0 if successful;
- 3 if no Iptc data was found in pPsData;
- -2 if the pPsData buffer does not contain valid data;
- */ - int locateIptcData(const byte *pPsData, - long sizePsData, - const byte **record, - uint16_t *const sizeHdr, - uint16_t *const sizeIptc) const; - /*! - @brief Write to the specified file stream with the provided data. - @param fp File stream to be written to (should be "w+b" mode) - @param initData Data to be written to the associated file - @param dataSize Size in bytes of data to be written - @return 0 if successful;
- 4 if the output file can not be written to;
- */ - int initFile(FILE* fp, const byte initData[], size_t dataSize); - /*! - @brief Provides the main implementation of writeMetadata by - writing all buffered metadata to associated file. - @param ifp Input file stream. Non-metadata is copied to output file. - @param ofp Output file stream to write to (e.g., a temporary file). - @return 0 if successful;
- 1 if reading from input file failed;
- 2 if the input file does not contain a valid image;
- 4 if the output file can not be written to;
- */ - int doWriteMetadata(FILE *ifp, FILE* ofp) const; - - // NOT Implemented - //! Default constructor. - JpegBase(); - //! Copy constructor - JpegBase(const JpegBase& rhs); - //! Assignment operator - JpegBase& operator=(const JpegBase& rhs); - }; // class JpegBase - - /*! - @brief Helper class to access JPEG images - */ - class JpegImage : public JpegBase { - friend bool isJpegType(FILE* ifp, bool advance); - public: - //! @name Creators - //@{ - /*! - @brief Constructor that can either open an existing Jpeg image or create - a new image from scratch. If a new image is to be created, any - existing file is overwritten. Since the constructor can not return - a result, callers should check the %good method after object - construction to determine success or failure. - @param path Full path to image file. - @param create Specifies if an existing file should be opened (false) - or if a new file should be created (true). - */ - JpegImage(const std::string& path, bool create); - //! Destructor - ~JpegImage() {} - //@} - protected: - //! @name Accessors - //@{ - /*! - @brief Writes a Jpeg header (aka signature) to the file stream. - @param ofp File stream that the header is written to. - @return 0 if successful;
- 4 if the output file can not be written to;
- */ - int writeHeader(FILE* ofp) const; - /*! - @brief Determine if the content of the file stream is a Jpeg image. - See base class for more details. - @param ifp Input file stream. - @param advance Flag indicating whether the read position in the stream - should be advanced by the number of characters read to - analyse the stream (true) or left at its original - position (false). This applies only if the type matches. - @return true if the file stream data matches a Jpeg image;
- false if the stream data does not match;
- */ - bool isThisType(FILE* ifp, bool advance) const; - //@} - private: - // Constant data - static const byte soi_; // SOI marker - static const byte blank_[]; // Minimal Jpeg image - - // NOT Implemented - //! Default constructor - JpegImage(); - //! Copy constructor - JpegImage(const JpegImage& rhs); - //! Assignment operator - JpegImage& operator=(const JpegImage& rhs); - }; // class JpegImage - - //! Helper class to access %Exiv2 files - class ExvImage : public JpegBase { - friend bool isExvType(FILE* ifp, bool advance); - public: - //! @name Creators - //@{ - /*! - @brief Constructor that can either open an existing Exv image or create - a new image from scratch. If a new image is to be created, any - existing file is overwritten. Since the constructor can not return - a result, callers should check the %good method after object - construction to determine success or failure. - @param path Full path to image file. - @param create Specifies if an existing file should be opened (false) - or if a new file should be created (true). - */ - ExvImage(const std::string& path, bool create); - //! Destructor - ~ExvImage() {} - //@} - protected: - //! @name Accessors - //@{ - /*! - @brief Writes an Exv header (aka signature) to the file stream. - @param ofp File stream that the header is written to. - @return 0 if successful;
- 4 if the output file can not be written to;
- */ - int writeHeader(FILE* ofp) const; - /*! - @brief Determine if the content of the file stream is a Exv image. - See base class for more details. - @param ifp Input file stream. - @param advance Flag indicating whether the read position in the stream - should be advanced by the number of characters read to - analyse the stream (true) or left at its original - position (false). This applies only if the type matches. - @return true if the file stream data matches a Exv image;
- false if the stream data does not match;
- */ - virtual bool isThisType(FILE* ifp, bool advance) const; - //@} - private: - // Constant data - static const char exiv2Id_[]; // Exv identifier - static const byte blank_[]; // Minimal exiv file - - // NOT Implemented - //! Default constructor - ExvImage(); - //! Copy constructor - ExvImage(const ExvImage& rhs); - //! Assignment operator - ExvImage& operator=(const ExvImage& rhs); - }; // class ExvImage //! Helper class modelling the TIFF header structure. class TiffHeader { diff --git a/src/iotest.cpp b/src/iotest.cpp new file mode 100644 index 00000000..6c476792 --- /dev/null +++ b/src/iotest.cpp @@ -0,0 +1,217 @@ +// ***************************************************************** -*- C++ -*- +/* + * Copyright (C) 2004 Andreas Huggel + * + * This program is part of the Exiv2 distribution. + * + * This program 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 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +/* + Abstract : Tester application for BasicIo functions. Tests MemIo primarily + since FileIo just sits atop of FILE* streams. + + File : iotest.cpp + Version : $Rev$ + Author(s): Brad Schick (brad) + History : 13-Jul-04, brad: created + */ +// ***************************************************************************** +// included header files +#include "types.hpp" +#include "error.hpp" +#include "basicio.hpp" +#include + +using Exiv2::byte; +using Exiv2::BasicIo; +using Exiv2::MemIo; +using Exiv2::FileIo; +using Exiv2::IoCloser; + +int WriteReadSeek(BasicIo &io); + +// ***************************************************************************** +// Main +int main(int argc, char* const argv[]) +{ +try { + if (argc != 4) { + std::cout << "Usage: " << argv[0] << " filein fileout1 fileout2\n"; + std::cout << "fileouts are overwritten and should match filein exactly\n"; + return 1; + } + + FileIo fileIn(argv[1]); + if (fileIn.open() != 0) { + std::cerr << argv[0] << + ": Could not open input file (" << argv[1] << ")\n"; + return 1; + } + + FileIo fileOut1(argv[2]); + if (fileOut1.open("w+b") != 0) { + std::cerr << argv[0] << + ": Could not open output file 1 (" << argv[2] << ")\n"; + return 1; + } + + MemIo memIo1; + + // Copy to output file through memIo + memIo1.write(fileIn); + memIo1.seek(0, BasicIo::beg); + fileOut1.write(memIo1); + + // Read writereadseek test on MemIo + MemIo memIo2; + int rc = WriteReadSeek(memIo2); + if (rc != 0) return rc; + + // Read writereadseek test on FileIo + // Create or overwrite the file, then close it + FileIo fileTest("iotest.txt"); + if (fileTest.open("w+b") != 0) { + std::cerr << argv[0] << + ": Could not create test file iotest.txt\n"; + return 1; + } + fileTest.close(); + rc = WriteReadSeek(fileTest); + if (rc != 0) return rc; + + // Another test of reading and writing + fileOut1.seek(0, BasicIo::beg); + memIo2.seek(0, BasicIo::beg); + FileIo fileOut2(argv[3]); + if (fileOut2.open("w+b") != 0) { + std::cerr << argv[0] << + ": Could not open output file 2 (" << argv[3] << ")\n"; + return 1; + } + + long readCount = 0; + byte buf[32]; + while ((readCount=fileOut1.read(buf, sizeof(buf)))) { + if (memIo2.write(buf, readCount) != readCount) { + std::cerr << argv[0] << + ": MemIo bad write 2\n"; + return 13; + } + if (fileOut2.write(buf, readCount) != readCount) { + std::cerr << argv[0] << + ": FileIo bad write 2\n"; + return 14; + } + } + + return 0; +} +catch (Exiv2::Error& e) { + std::cerr << "Caught Exiv2 exception '" << e << "'\n"; + return 20; +} +} + + +int WriteReadSeek(BasicIo &io) +{ + byte buf[4096]; + const char tester1[] = "this is a little test of MemIo"; + const char tester2[] = "Appending this on the end"; + const char expect[] = "this is a little teAppending this on the end"; + const long insert = 19; + const long len1 = (long)strlen(tester1) + 1; + const long len2 = (long)strlen(tester2) + 1; + + IoCloser closer(io); + if (io.open() != 0) { + std::cerr << ": WRS could not open IO\n"; + return 2; + } + + if (io.write((byte*)tester1, len1) != len1) { + std::cerr << ": WRS initial write failed\n"; + return 2; + } + io.seek(-len1, BasicIo::cur); + + int c = EOF; + memset(buf, -1, sizeof(buf)); + for (int i = 0; (c=io.getb()) != EOF; ++i) { + buf[i] = (byte)c; + } + + // Make sure we got the null back + if(buf[len1-1] != 0) { + std::cerr << ": WRS missing null terminator 1\n"; + return 3; + } + + if (strcmp(tester1, (char*)buf) != 0 ) { + std::cerr << ": WRS strings don't match 1\n"; + return 4; + } + + io.seek(-2, BasicIo::end); + if (io.getb() != 'o') { + std::cerr << ": WRS bad getb o\n"; + return 5; + } + + io.seek(-2, BasicIo::cur); + if (io.getb() != 'I') { + std::cerr << ": WRS bad getb I\n"; + return 6; + } + + if (io.putb('O') != 'O') { + std::cerr << ": WRS bad putb\n"; + return 7; + } + + io.seek(-1, BasicIo::cur); + if (io.getb() != 'O') { + std::cerr << ": WRS bad getb O\n"; + return 8; + } + + io.seek(insert, BasicIo::beg); + if(io.write((byte*)tester2, len2) != len2) { + std::cerr << ": WRS bad write 1\n"; + return 9; + } + + // open should seek to beginning + io.open(); + memset(buf, -1, sizeof(buf)); + if (io.read(buf, sizeof(buf)) != insert + len2) { + std::cerr << ": WRS something went wrong\n"; + return 10; + } + + // Make sure we got the null back + if(buf[insert + len2 - 1] != 0) { + std::cerr << ": WRS missing null terminator 2\n"; + return 11; + } + + if (strcmp(expect, (char*)buf) != 0 ) { + std::cerr << ": WRS strings don't match 2\n"; + return 12; + } + + return 0; +} + diff --git a/src/iptc.cpp b/src/iptc.cpp index 21a81052..c85d4c58 100644 --- a/src/iptc.cpp +++ b/src/iptc.cpp @@ -38,7 +38,7 @@ EXIV2_RCSID("@(#) $Id$"); #include "error.hpp" #include "value.hpp" #include "datasets.hpp" -#include "image.hpp" +#include "jpgimage.hpp" // + standard includes #include @@ -128,28 +128,7 @@ namespace Exiv2 { return *pos; } - int IptcData::read(const std::string& path) - { - if (!fileExists(path, true)) return -1; - Image::AutoPtr image = ImageFactory::instance().open(path); - if (image.get() == 0) { - // We don't know this type of file - return -2; - } - - int rc = image->readMetadata(); - if (rc == 0) { - if (image->sizeIptcData() > 0) { - rc = read(image->iptcData(), image->sizeIptcData()); - } - else { - rc = 3; - } - } - return rc; - } - - int IptcData::read(const byte* buf, long len) + int IptcData::load(const byte* buf, long len) { const byte* pRead = buf; iptcMetadata_.clear(); @@ -201,41 +180,6 @@ namespace Exiv2 { return 0; } - int IptcData::erase(const std::string& path) const - { - if (!fileExists(path, true)) return -1; - Image::AutoPtr image = ImageFactory::instance().open(path); - if (image.get() == 0) return -2; - - // Read all metadata then erase only Iptc data - int rc = image->readMetadata(); - if (rc == 0) { - image->clearIptcData(); - rc = image->writeMetadata(); - } - return rc; - } // IptcData::erase - - int IptcData::write(const std::string& path) - { - // Remove the Iptc section from the file if there is no metadata - if (count() == 0) return erase(path); - - if (!fileExists(path, true)) return -1; - Image::AutoPtr image = ImageFactory::instance().open(path); - if (image.get() == 0) return -2; - - DataBuf buf(copy()); - - // Read all metadata to preserve non-Iptc data - int rc = image->readMetadata(); - if (rc == 0) { - image->setIptcData(buf.pData_, buf.size_); - rc = image->writeMetadata(); - } - return rc; - } // IptcData::write - DataBuf IptcData::copy() { DataBuf buf(size()); @@ -268,7 +212,7 @@ namespace Exiv2 { } return buf; - } + } // IptcData::updateBuffer long IptcData::size() const { @@ -288,15 +232,6 @@ namespace Exiv2 { return newSize; } // IptcData::size - int IptcData::writeIptcData(const std::string& path) - { - DataBuf buf(copy()); - ExvImage exvImage(path, true); - if (!exvImage.good()) return -1; - exvImage.setIptcData(buf.pData_, buf.size_); - return exvImage.writeMetadata(); - } // IptcData::writeIptcData - int IptcData::add(const IptcKey& key, Value* value) { return add(Iptcdatum(key, value)); diff --git a/src/iptc.hpp b/src/iptc.hpp index 97e3a79d..d5875cab 100644 --- a/src/iptc.hpp +++ b/src/iptc.hpp @@ -290,48 +290,14 @@ namespace Exiv2 { //! @name Manipulators //@{ /*! - @brief Read the Iptc data from file path. - @param path Path to the file - @return 0 if successful;
- 3 if the file contains no Iptc data;
- the return code of Image::readMetadata() - if the call to this function fails;
- the return code of read(const char* buf, long len) - if the call to this function fails;
- */ - int read(const std::string& path); - /*! - @brief Read the Iptc data from a byte buffer. The format must follow + @brief Load the Iptc data from a byte buffer. The format must follow the IPTC IIM4 standard. @param buf Pointer to the data buffer to read from @param len Number of bytes in the data buffer @return 0 if successful;
5 if Iptc data is invalid or corrupt;
*/ - int read(const byte* buf, long len); - /*! - @brief Write the Iptc data to file path. If an Iptc data section - already exists in the file, it is replaced. If there is no - metadata to write, the Iptc data section is - deleted from the file. Otherwise, an Iptc data section is - created. - @return 0 if successful;
- -2 if the file contains an unknown image type;
- the return code of Image::writeMetadata() - if the call to this function fails;
- */ - int write(const std::string& path); - /*! - @brief Write the Iptc data to a binary file. By convention, the - filename extension should be ".exv". This file format contains - the Iptc data as it is found in a JPEG file. Exv files can - be read with int read(const std::string& path) just like - normal image files. - @return 0 if successful;
- the return code of Image::writeMetadata() - if the call to this function fails;
- */ - int writeIptcData(const std::string& path); + int load(const byte* buf, long len); /*! @brief Write the Iptc data to a data buffer and return the data buffer. Caller owns this buffer. The copied data follows the IPTC IIM4 @@ -370,6 +336,10 @@ namespace Exiv2 { by this call. */ iterator erase(iterator pos); + /*! + @brief Delete all Iptcdatum instances resulting in an empty container. + */ + void clear() { iptcMetadata_.clear(); } //! Sort metadata by key void sortByKey(); //! Sort metadata by tag (aka dataset) @@ -396,15 +366,6 @@ namespace Exiv2 { //! @name Accessors //@{ - /*! - @brief Erase the Iptc data from file path. - @param path Path to the file. - @return 0 if successful;
- -2 if the file contains an unknown image type;
- the return code of Image::writeMetadata() - if the call to this function fails;
- */ - int erase(const std::string& path) const; //! Begin of the metadata const_iterator begin() const { return iptcMetadata_.begin(); } //! End of the metadata @@ -432,15 +393,14 @@ namespace Exiv2 { //@} /*! - @brief Convert the return code from - int read(const std::string& path), - int read(const byte* buf, long len), - int write(const std::string& path), - int writeIptcData(const std::string& path), - int erase(const std::string& path) const + @brief Convert the return code from \n + int read(const byte* buf, long len), \n into an error message. + @param rc Error code. + @param path %Image file or other identifying string. + @return String containing error message. - Todo: Implement global handling of error messages + Todo: Implement global handling of error messages */ static std::string strError(int rc, const std::string& path); diff --git a/src/iptceasy.cpp b/src/iptceasy.cpp index 1b6ac19b..572d2dda 100644 --- a/src/iptceasy.cpp +++ b/src/iptceasy.cpp @@ -3,6 +3,7 @@ // The quickest way to access, set or modify Iptc metadata. #include "iptc.hpp" +#include "image.hpp" #include #include @@ -31,7 +32,24 @@ try { std::cout << "Time sent: " << iptcData["Iptc.Envelope.TimeSent"] << "\n"; - return iptcData.write(file); // Write IPTC data to file and exit + // Open image file + Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open(file); + if (image.get() == 0) { + std::string error(file); + error += " : Could not read file or unknown image type"; + throw Exiv2::Error(error); + } + + // Read existing metdata (so that exif will be preserved) + int rc = image->readMetadata(); + if (rc) { + std::string error = Exiv2::Image::strError(rc, file); + throw Exiv2::Error(error); + } + + // Replace Iptc data and write it back to the file + image->setIptcData(iptcData); + return image->writeMetadata(); } catch (Exiv2::Error& e) { std::cout << "Caught Exiv2 exception '" << e << "'\n"; diff --git a/src/iptcprint.cpp b/src/iptcprint.cpp index fb22103f..3b637347 100644 --- a/src/iptcprint.cpp +++ b/src/iptcprint.cpp @@ -2,6 +2,7 @@ // iptcprint.cpp, $Rev$ // Sample program to print the Iptc metadata of an image +#include "image.hpp" #include "iptc.hpp" #include #include @@ -14,13 +15,21 @@ try { return 1; } - Exiv2::IptcData iptcData; - int rc = iptcData.read(argv[1]); + Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open(argv[1]); + if (image.get() == 0) { + std::string error(argv[1]); + error += " : Could not read file or unknown image type"; + throw Exiv2::Error(error); + } + + // Load existing metadata + int rc = image->readMetadata(); if (rc) { - std::string error = Exiv2::IptcData::strError(rc, argv[1]); + std::string error = Exiv2::Image::strError(rc, argv[1]); throw Exiv2::Error(error); } + Exiv2::IptcData &iptcData = image->iptcData(); Exiv2::IptcData::iterator end = iptcData.end(); for (Exiv2::IptcData::iterator md = iptcData.begin(); md != end; ++md) { std::cout << std::setw(36) << std::setfill(' ') << std::left diff --git a/src/iptctest.cpp b/src/iptctest.cpp index e1071970..076ca916 100644 --- a/src/iptctest.cpp +++ b/src/iptctest.cpp @@ -10,6 +10,7 @@ */ // ***************************************************************************** // included header files +#include "image.hpp" #include "iptc.hpp" #include "datasets.hpp" #include "value.hpp" @@ -18,12 +19,10 @@ using namespace Exiv2; -bool processLine(const std::string& line, int num); -void processAdd(const std::string& line, int num); -void processRemove(const std::string& line, int num); -void processModify(const std::string& line, int num); - -IptcData g_iptcData; +bool processLine(const std::string& line, int num, IptcData &iptcData); +void processAdd(const std::string& line, int num, IptcData &iptcData); +void processRemove(const std::string& line, int num, IptcData &iptcData); +void processModify(const std::string& line, int num, IptcData &iptcData); // ***************************************************************************** // Main @@ -37,22 +36,30 @@ int main(int argc, char* const argv[]) return 1; } - int rc = g_iptcData.read(argv[1]); + Image::AutoPtr image = ImageFactory::open(argv[1]); + if (image.get() == 0) { + throw Error("Could not read file"); + } + + // Load existing metadata + int rc = image->readMetadata(); if (rc) { - std::string error = IptcData::strError(rc, argv[1]); + std::string error = Image::strError(rc, argv[1]); throw Error(error); } + // Process commands std::string line; int num = 0; std::getline(std::cin, line); - while (line.length() && processLine(line, ++num)) { + while (line.length() && processLine(line, ++num, image->iptcData())) { std::getline(std::cin, line); } - - rc = g_iptcData.write(argv[1]); + + // Save any changes + rc = image->writeMetadata(); if (rc) { - std::string error = IptcData::strError(rc, argv[1]); + std::string error = Image::strError(rc, argv[1]); throw Error(error); } @@ -64,20 +71,20 @@ int main(int argc, char* const argv[]) } } -bool processLine(const std::string& line, int num ) +bool processLine(const std::string& line, int num, IptcData &iptcData) { switch (line.at(0)) { case 'a': case 'A': - processAdd(line, num); + processAdd(line, num, iptcData); break; case 'r': case 'R': - processRemove(line, num); + processRemove(line, num, iptcData); break; case 'm': case 'M': - processModify(line, num); + processModify(line, num, iptcData); break; case 'q': case 'Q': @@ -90,7 +97,7 @@ bool processLine(const std::string& line, int num ) return true; } -void processAdd(const std::string& line, int num) +void processAdd(const std::string& line, int num, IptcData &iptcData) { std::string::size_type keyStart = line.find_first_not_of(" \t", 1); std::string::size_type keyEnd = line.find_first_of(" \t", keyStart+1); @@ -116,7 +123,7 @@ void processAdd(const std::string& line, int num) Value::AutoPtr value = Value::create(type); value->read(data); - int rc = g_iptcData.add(iptcKey, value.get()); + int rc = iptcData.add(iptcKey, value.get()); if (rc) { std::string error = IptcData::strError(rc, "Input file"); throw Error(error); @@ -124,7 +131,7 @@ void processAdd(const std::string& line, int num) } -void processRemove(const std::string& line, int num) +void processRemove(const std::string& line, int num, IptcData &iptcData) { std::string::size_type keyStart = line.find_first_not_of(" \t", 1); @@ -137,13 +144,13 @@ void processRemove(const std::string& line, int num) const std::string key( line.substr(keyStart) ); IptcKey iptcKey(key); - IptcData::iterator iter = g_iptcData.findKey(iptcKey); - if (iter != g_iptcData.end()) { - g_iptcData.erase(iter); + IptcData::iterator iter = iptcData.findKey(iptcKey); + if (iter != iptcData.end()) { + iptcData.erase(iter); } } -void processModify(const std::string& line, int num) +void processModify(const std::string& line, int num, IptcData &iptcData) { std::string::size_type keyStart = line.find_first_not_of(" \t", 1); std::string::size_type keyEnd = line.find_first_of(" \t", keyStart+1); @@ -169,12 +176,12 @@ void processModify(const std::string& line, int num) Value::AutoPtr value = Value::create(type); value->read(data); - IptcData::iterator iter = g_iptcData.findKey(iptcKey); - if (iter != g_iptcData.end()) { + IptcData::iterator iter = iptcData.findKey(iptcKey); + if (iter != iptcData.end()) { iter->setValue(value.get()); } else { - int rc = g_iptcData.add(iptcKey, value.get()); + int rc = iptcData.add(iptcKey, value.get()); if (rc) { std::string error = IptcData::strError(rc, "Input file"); throw Error(error); diff --git a/src/jpgimage.cpp b/src/jpgimage.cpp new file mode 100644 index 00000000..9e7c425e --- /dev/null +++ b/src/jpgimage.cpp @@ -0,0 +1,670 @@ +// ***************************************************************** -*- C++ -*- +/* + * Copyright (C) 2004 Andreas Huggel + * + * This program is part of the Exiv2 distribution. + * + * This program 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 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +/* + File: jpgimage.cpp + Version: $Rev$ + Author(s): Andreas Huggel (ahu) + Brad Schick (brad) + History: 15-Jan-05, brad: split out from image.cpp + + */ +// ***************************************************************************** +#include "rcsid.hpp" +EXIV2_RCSID("@(#) $Id$"); + +// ***************************************************************************** +// included header files +#ifdef HAVE_CONFIG_H +# include +#else +# ifdef _MSC_VER +# include +# endif +#endif + +#include "jpgimage.hpp" +#include "error.hpp" + +// + standard includes +#include +#include + +// ***************************************************************************** +// class member definitions +namespace Exiv2 { + + // Local functions. These could be static private functions on Image + // subclasses but then ImageFactory needs to be made a friend. + /*! + @brief Create a new ExvImage instance and return an auto-pointer to it. + Caller owns the returned object and the auto-pointer ensures that + it will be deleted. + */ + Image::AutoPtr newExvInstance(BasicIo::AutoPtr io, bool create); + //! Check if the file iIo is an EXV file + bool isExvType(BasicIo& iIo, bool advance); + /*! + @brief Create a new JpegImage instance and return an auto-pointer to it. + Caller owns the returned object and the auto-pointer ensures that + it will be deleted. + */ + Image::AutoPtr newJpegInstance(BasicIo::AutoPtr io, bool create); + //! Check if the file iIo is a JPEG image. + bool isJpegType(BasicIo& iIo, bool advance); + + const byte JpegBase::sos_ = 0xda; + const byte JpegBase::eoi_ = 0xd9; + const byte JpegBase::app0_ = 0xe0; + const byte JpegBase::app1_ = 0xe1; + const byte JpegBase::app13_ = 0xed; + const byte JpegBase::com_ = 0xfe; + const uint16_t JpegBase::iptc_ = 0x0404; + const char JpegBase::exifId_[] = "Exif\0\0"; + const char JpegBase::jfifId_[] = "JFIF\0"; + const char JpegBase::ps3Id_[] = "Photoshop 3.0\0"; + const char JpegBase::bimId_[] = "8BIM"; + + JpegBase::JpegBase(BasicIo::AutoPtr io, bool create, + const byte initData[], long dataSize) + : io_(io) + { + if (create) { + initImage(initData, dataSize); + } + } + + int JpegBase::initImage(const byte initData[], long dataSize) + { + IoCloser closer(*io_); + if (io_->open() != 0) return 4; + if (io_->write(initData, dataSize) != dataSize) { + return 4; + } + return 0; + } + + bool JpegBase::good() const + { + IoCloser closer(*io_); + if (io_->open() != 0) return false; + return isThisType(*io_, false); + } + + void JpegBase::clearMetadata() + { + clearIptcData(); + clearExifData(); + clearComment(); + } + + void JpegBase::clearIptcData() + { + iptcData_.clear(); + } + + void JpegBase::clearExifData() + { + exifData_.clear(); + } + + void JpegBase::clearComment() + { + comment_.erase(); + } + + void JpegBase::setExifData(const ExifData& exifData) + { + exifData_ = exifData; + } + + void JpegBase::setIptcData(const IptcData& iptcData) + { + iptcData_ = iptcData; + } + + void JpegBase::setComment(const std::string& comment) + { + comment_ = comment; + } + + void JpegBase::setMetadata(const Image& image) + { + setIptcData(image.iptcData()); + setExifData(image.exifData()); + setComment(image.comment()); + } + + int JpegBase::advanceToMarker() const + { + int c = -1; + // Skips potential padding between markers + while ((c=io_->getb()) != 0xff) { + if (c == EOF) return -1; + } + + // Markers can start with any number of 0xff + while ((c=io_->getb()) == 0xff) { + if (c == EOF) return -1; + } + return c; + } + + int JpegBase::readMetadata() + { + IoCloser closer(*io_); + if (io_->open() != 0) return 1; + + // Ensure that this is the correct image type + if (!isThisType(*io_, true)) { + if (io_->error() || io_->eof()) return 1; + return 2; + } + clearMetadata(); + int search = 3; + const long bufMinSize = 16; + long bufRead = 0; + DataBuf buf(bufMinSize); + + // Read section marker + int marker = advanceToMarker(); + if (marker < 0) return 2; + + while (marker != sos_ && marker != eoi_ && search > 0) { + // Read size and signature (ok if this hits EOF) + bufRead = io_->read(buf.pData_, bufMinSize); + if (io_->error()) return 1; + uint16_t size = getUShort(buf.pData_, bigEndian); + + if (marker == app1_ && memcmp(buf.pData_ + 2, exifId_, 6) == 0) { + if (size < 8) return 2; + // Seek to begining and read the Exif data + io_->seek(8-bufRead, BasicIo::cur); + long sizeExifData = size - 8; + DataBuf rawExif(sizeExifData); + io_->read(rawExif.pData_, sizeExifData); + if (io_->error() || io_->eof()) { + return 1; + } + if (exifData_.load(rawExif.pData_, sizeExifData)) return 2; + --search; + } + else if (marker == app13_ && memcmp(buf.pData_ + 2, ps3Id_, 14) == 0) { + if (size < 16) return 2; + // Read the rest of the APP13 segment + // needed if bufMinSize!=16: io_->seek(16-bufRead, BasicIo::cur); + DataBuf psData(size - 16); + io_->read(psData.pData_, psData.size_); + if (io_->error() || io_->eof()) return 1; + const byte *record = 0; + uint16_t sizeIptc = 0; + uint16_t sizeHdr = 0; + // Find actual Iptc data within the APP13 segment + if (!locateIptcData(psData.pData_, psData.size_, &record, + &sizeHdr, &sizeIptc)) { + assert(sizeIptc); + if (iptcData_.load(record + sizeHdr, sizeIptc)) return 2; + } + --search; + } + else if (marker == com_ && comment_.empty()) + { + if (size < 2) return 2; + // Jpegs can have multiple comments, but for now only read + // the first one (most jpegs only have one anyway). Comments + // are simple single byte ISO-8859-1 strings. + io_->seek(2-bufRead, BasicIo::cur); + buf.alloc(size-2); + io_->read(buf.pData_, size-2); + if (io_->error() || io_->eof()) return 1; + comment_.assign(reinterpret_cast(buf.pData_), size-2); + while ( comment_.length() + && comment_.at(comment_.length()-1) == '\0') { + comment_.erase(comment_.length()-1); + } + --search; + } + else { + if (size < 2) return 2; + // Skip the remainder of the unknown segment + if (io_->seek(size-bufRead, BasicIo::cur)) return 2; + } + // Read the beginning of the next segment + marker = advanceToMarker(); + if (marker < 0) return 2; + } + return 0; + } // JpegBase::readMetadata + + + // Operates on raw data (rather than file streams) to simplify reuse + int JpegBase::locateIptcData(const byte *pPsData, + long sizePsData, + const byte **record, + uint16_t *const sizeHdr, + uint16_t *const sizeIptc) const + { + assert(record); + assert(sizeHdr); + assert(sizeIptc); + // Used for error checking + long position = 0; + + // Data should follow Photoshop format, if not exit + while (position <= (sizePsData - 14) && + memcmp(pPsData + position, bimId_, 4)==0) { + const byte *hrd = pPsData + position; + position += 4; + uint16_t type = getUShort(pPsData+ position, bigEndian); + position += 2; + + // Pascal string is padded to have an even size (including size byte) + byte psSize = pPsData[position] + 1; + psSize += (psSize & 1); + position += psSize; + if (position >= sizePsData) return -2; + + // Data is also padded to be even + long dataSize = getULong(pPsData + position, bigEndian); + position += 4; + if (dataSize > sizePsData - position) return -2; + + if (type == iptc_) { + *sizeIptc = static_cast(dataSize); + *sizeHdr = psSize + 10; + *record = hrd; + return 0; + } + position += dataSize + (dataSize & 1); + } + return 3; + } // JpegBase::locateIptcData + + int JpegBase::writeMetadata() + { + IoCloser closer(*io_); + if (io_->open() != 0) return 1; + BasicIo::AutoPtr tempIo(io_->temporary()); + if (!tempIo.get()) return -3; + + int rc = doWriteMetadata(*tempIo); + io_->close(); + if( rc == 0 ) { + if (io_->transfer(*tempIo) != 0) return -3; + } + return rc; + } // JpegBase::writeMetadata + + int JpegBase::doWriteMetadata(BasicIo& outIo) + { + if (!io_->isopen()) return 1; + if (!outIo.isopen()) return 4; + + // Ensure that this is the correct image type + if (!isThisType(*io_, true)) { + if (io_->error() || io_->eof()) return 1; + return 2; + } + + const long bufMinSize = 16; + long bufRead = 0; + DataBuf buf(bufMinSize); + const long seek = io_->tell(); + int count = 0; + int search = 0; + int insertPos = 0; + int skipApp1Exif = -1; + int skipApp13Ps3 = -1; + int skipCom = -1; + DataBuf psData; + + // Write image header + if (writeHeader(outIo)) return 4; + + // Read section marker + int marker = advanceToMarker(); + if (marker < 0) return 2; + + // First find segments of interest. Normally app0 is first and we want + // to insert after it. But if app0 comes after com, app1 and app13 then + // don't bother. + while (marker != sos_ && marker != eoi_ && search < 3) { + // Read size and signature (ok if this hits EOF) + bufRead = io_->read(buf.pData_, bufMinSize); + if (io_->error()) return 1; + uint16_t size = getUShort(buf.pData_, bigEndian); + + if (marker == app0_) { + if (size < 2) return 2; + insertPos = count + 1; + if (io_->seek(size-bufRead, BasicIo::cur)) return 2; + } + else if (marker == app1_ && memcmp(buf.pData_ + 2, exifId_, 6) == 0) { + if (size < 8) return 2; + skipApp1Exif = count; + ++search; + if (io_->seek(size-bufRead, BasicIo::cur)) return 2; + } + else if (marker == app13_ && memcmp(buf.pData_ + 2, ps3Id_, 14) == 0) { + if (size < 16) return 2; + skipApp13Ps3 = count; + ++search; + // needed if bufMinSize!=16: io_->seek(16-bufRead, BasicIo::cur); + psData.alloc(size - 16); + // Load PS data now to allow reinsertion at any point + io_->read(psData.pData_, psData.size_); + if (io_->error() || io_->eof()) return 1; + } + else if (marker == com_ && skipCom == -1) { + if (size < 2) return 2; + // Jpegs can have multiple comments, but for now only handle + // the first one (most jpegs only have one anyway). + skipCom = count; + ++search; + if (io_->seek(size-bufRead, BasicIo::cur)) return 2; + } + else { + if (size < 2) return 2; + if (io_->seek(size-bufRead, BasicIo::cur)) return 2; + } + marker = advanceToMarker(); + if (marker < 0) return 2; + ++count; + } + + if (exifData_.count() > 0) ++search; + if (iptcData_.count() > 0) ++search; + if (!comment_.empty()) ++search; + + io_->seek(seek, BasicIo::beg); + count = 0; + marker = advanceToMarker(); + if (marker < 0) return 2; + + // To simplify this a bit, new segments are inserts at either the start + // or right after app0. This is standard in most jpegs, but has the + // potential to change segment ordering (which is allowed). + // Segments are erased if there is no assigned metadata. + while (marker != sos_ && search > 0) { + // Read size and signature (ok if this hits EOF) + bufRead = io_->read(buf.pData_, bufMinSize); + if (io_->error()) return 1; + // Careful, this can be a meaningless number for empty + // images with only an eoi_ marker + uint16_t size = getUShort(buf.pData_, bigEndian); + + if (insertPos == count) { + byte tmpBuf[18]; + if (!comment_.empty()) { + // Write COM marker, size of comment, and string + tmpBuf[0] = 0xff; + tmpBuf[1] = com_; + us2Data(tmpBuf + 2, + static_cast(comment_.length()+3), bigEndian); + if (outIo.write(tmpBuf, 4) != 4) return 4; + if (outIo.write((byte*)comment_.data(), (long)comment_.length()) + != (long)comment_.length()) return 4; + if (outIo.putb(0)==EOF) return 4; + if (outIo.error()) return 4; + --search; + } + if (exifData_.count() > 0) { + // Write APP1 marker, size of APP1 field, Exif id and Exif data + DataBuf rawExif(exifData_.copy()); + tmpBuf[0] = 0xff; + tmpBuf[1] = app1_; + us2Data(tmpBuf + 2, + static_cast(rawExif.size_+8), + bigEndian); + memcpy(tmpBuf + 4, exifId_, 6); + if (outIo.write(tmpBuf, 10) != 10) return 4; + if (outIo.write(rawExif.pData_, rawExif.size_) + != rawExif.size_) return 4; + if (outIo.error()) return 4; + --search; + } + + const byte *record = psData.pData_; + uint16_t sizeIptc = 0; + uint16_t sizeHdr = 0; + // Safe to call with zero psData.size_ + locateIptcData(psData.pData_, psData.size_, &record, &sizeHdr, &sizeIptc); + + // Data is rounded to be even + const int sizeOldData = sizeHdr + sizeIptc + (sizeIptc & 1); + if (psData.size_ > sizeOldData || iptcData_.count() > 0) { + // rawIptc may have size of zero. + DataBuf rawIptc(iptcData_.copy()); + // write app13 marker, new size, and ps3Id + tmpBuf[0] = 0xff; + tmpBuf[1] = app13_; + const int sizeNewData = rawIptc.size_ ? + rawIptc.size_ + (rawIptc.size_ & 1) + 12 : 0; + us2Data(tmpBuf + 2, + static_cast(psData.size_-sizeOldData+sizeNewData+16), + bigEndian); + memcpy(tmpBuf + 4, ps3Id_, 14); + if (outIo.write(tmpBuf, 18) != 18) return 4; + if (outIo.error()) return 4; + + const long sizeFront = (long)(record - psData.pData_); + const long sizeEnd = psData.size_ - sizeFront - sizeOldData; + // write data before old record. + if (outIo.write(psData.pData_, sizeFront) != sizeFront) return 4; + + // write new iptc record if we have it + if (iptcData_.count() > 0) { + memcpy(tmpBuf, bimId_, 4); + us2Data(tmpBuf+4, iptc_, bigEndian); + tmpBuf[6] = 0; + tmpBuf[7] = 0; + ul2Data(tmpBuf + 8, rawIptc.size_, bigEndian); + if (outIo.write(tmpBuf, 12) != 12) return 4; + if (outIo.write(rawIptc.pData_, rawIptc.size_) + != rawIptc.size_) return 4; + // data is padded to be even (but not included in size) + if (rawIptc.size_ & 1) { + if (outIo.putb(0)==EOF) return 4; + } + if (outIo.error()) return 4; + --search; + } + + // write existing stuff after record + if (outIo.write(record+sizeOldData, sizeEnd) + != sizeEnd) return 4; + if (outIo.error()) return 4; + } + } + if (marker == eoi_) { + break; + } + else if (skipApp1Exif==count || skipApp13Ps3==count || skipCom==count) { + --search; + io_->seek(size-bufRead, BasicIo::cur); + } + else { + if (size < 2) return 2; + buf.alloc(size+2); + io_->seek(-bufRead-2, BasicIo::cur); + io_->read(buf.pData_, size+2); + if (io_->error() || io_->eof()) return 1; + if (outIo.write(buf.pData_, size+2) != size+2) return 4; + if (outIo.error()) return 4; + } + + // Next marker + marker = advanceToMarker(); + if (marker < 0) return 2; + ++count; + } + + // Copy rest of the Io + io_->seek(-2, BasicIo::cur); + buf.alloc(4096); + long readSize = 0; + while ((readSize=io_->read(buf.pData_, buf.size_))) { + if (outIo.write(buf.pData_, readSize) != readSize) return 4; + } + if (outIo.error()) return 4; + + return 0; + }// JpegBase::doWriteMetadata + + + const byte JpegImage::soi_ = 0xd8; + const byte JpegImage::blank_[] = { + 0xFF,0xD8,0xFF,0xDB,0x00,0x84,0x00,0x10,0x0B,0x0B,0x0B,0x0C,0x0B,0x10,0x0C,0x0C, + 0x10,0x17,0x0F,0x0D,0x0F,0x17,0x1B,0x14,0x10,0x10,0x14,0x1B,0x1F,0x17,0x17,0x17, + 0x17,0x17,0x1F,0x1E,0x17,0x1A,0x1A,0x1A,0x1A,0x17,0x1E,0x1E,0x23,0x25,0x27,0x25, + 0x23,0x1E,0x2F,0x2F,0x33,0x33,0x2F,0x2F,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40, + 0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x01,0x11,0x0F,0x0F,0x11,0x13,0x11,0x15,0x12, + 0x12,0x15,0x14,0x11,0x14,0x11,0x14,0x1A,0x14,0x16,0x16,0x14,0x1A,0x26,0x1A,0x1A, + 0x1C,0x1A,0x1A,0x26,0x30,0x23,0x1E,0x1E,0x1E,0x1E,0x23,0x30,0x2B,0x2E,0x27,0x27, + 0x27,0x2E,0x2B,0x35,0x35,0x30,0x30,0x35,0x35,0x40,0x40,0x3F,0x40,0x40,0x40,0x40, + 0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0xFF,0xC0,0x00,0x11,0x08,0x00,0x01,0x00, + 0x01,0x03,0x01,0x22,0x00,0x02,0x11,0x01,0x03,0x11,0x01,0xFF,0xC4,0x00,0x4B,0x00, + 0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x07,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x10,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x11,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xDA,0x00,0x0C,0x03,0x01,0x00,0x02, + 0x11,0x03,0x11,0x00,0x3F,0x00,0xA0,0x00,0x0F,0xFF,0xD9 }; + + JpegImage::JpegImage(BasicIo::AutoPtr io, bool create) + : JpegBase(io, create, blank_, sizeof(blank_)) + { + } + + //! @cond IGNORE + JpegImage::JpegRegister::JpegRegister() + { + ImageFactory::registerImage( + Image::jpeg, newJpegInstance, isJpegType); + } + //! @endcond + + int JpegImage::writeHeader(BasicIo& outIo) const + { + // Jpeg header + byte tmpBuf[2]; + tmpBuf[0] = 0xff; + tmpBuf[1] = soi_; + if (outIo.write(tmpBuf, 2) != 2) return 4; + if (outIo.error()) return 4; + return 0; + } + + bool JpegImage::isThisType(BasicIo& iIo, bool advance) const + { + return isJpegType(iIo, advance); + } + + Image::AutoPtr newJpegInstance(BasicIo::AutoPtr io, bool create) + { + Image::AutoPtr image; + if (create) { + image = Image::AutoPtr(new JpegImage(io, true)); + } + else { + image = Image::AutoPtr(new JpegImage(io, false)); + } + if (!image->good()) { + image.reset(); + } + return image; + } + + bool isJpegType(BasicIo& iIo, bool advance) + { + bool result = true; + byte tmpBuf[2]; + iIo.read(tmpBuf, 2); + if (iIo.error() || iIo.eof()) return false; + + if (0xff!=tmpBuf[0] || JpegImage::soi_!=tmpBuf[1]) { + result = false; + } + if (!advance || !result ) iIo.seek(-2, BasicIo::cur); + return result; + } + + const char ExvImage::exiv2Id_[] = "Exiv2"; + const byte ExvImage::blank_[] = { 0xff,0x01,'E','x','i','v','2',0xff,0xd9 }; + + ExvImage::ExvImage(BasicIo::AutoPtr io, bool create) + : JpegBase(io, create, blank_, sizeof(blank_)) + { + } + + //! @cond IGNORE + ExvImage::ExvRegister::ExvRegister() + { + ImageFactory::registerImage( + Image::exv, newExvInstance, isExvType); + } + //! @endcond + + int ExvImage::writeHeader(BasicIo& outIo) const + { + // Exv header + byte tmpBuf[7]; + tmpBuf[0] = 0xff; + tmpBuf[1] = 0x01; + memcpy(tmpBuf + 2, exiv2Id_, 5); + if (outIo.write(tmpBuf, 7) != 7) return 4; + if (outIo.error()) return 4; + return 0; + } + + bool ExvImage::isThisType(BasicIo& iIo, bool advance) const + { + return isExvType(iIo, advance); + } + + Image::AutoPtr newExvInstance(BasicIo::AutoPtr io, bool create) + { + Image::AutoPtr image; + if (create) { + image = Image::AutoPtr(new ExvImage(io, true)); + } + else { + image = Image::AutoPtr(new ExvImage(io, false)); + } + if (!image->good()) image.reset(); + return image; + } + + bool isExvType(BasicIo& iIo, bool advance) + { + bool result = true; + byte tmpBuf[7]; + iIo.read(tmpBuf, 7); + if (iIo.error() || iIo.eof()) return false; + + if (0xff!=tmpBuf[0] || 0x01!=tmpBuf[1] || + memcmp(tmpBuf + 2, ExvImage::exiv2Id_, 5) != 0) { + result = false; + } + if (!advance || !result ) iIo.seek(-7, BasicIo::cur); + return result; + } + +} // namespace Exiv2 diff --git a/src/jpgimage.hpp b/src/jpgimage.hpp new file mode 100644 index 00000000..2977eb5d --- /dev/null +++ b/src/jpgimage.hpp @@ -0,0 +1,418 @@ +// ***************************************************************** -*- C++ -*- +/* + * Copyright (C) 2004 Andreas Huggel + * + * This program is part of the Exiv2 distribution. + * + * This program 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 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +/*! + @file jpgimage.hpp + @brief Class JpegImage to access JPEG images + @version $Rev$ + @author Andreas Huggel (ahu) + ahuggel@gmx.net + @author Brad Schick (brad) + brad@robotbattle.com + @date 15-Jan-05, brad: split out from image.cpp + */ +#ifndef JPGIMAGE_HPP_ +#define JPGIMAGE_HPP_ + +// ***************************************************************************** +// included header files +#include "types.hpp" +#include "image.hpp" +#include "basicio.hpp" +#include "exif.hpp" +#include "iptc.hpp" + +// + standard includes +#include + +// ***************************************************************************** +// namespace extensions +namespace Exiv2 { + + +// ***************************************************************************** +// class definitions + + /*! + @brief Abstract helper base class to access JPEG images. + */ + class JpegBase : public Image { + public: + //! @name Creators + //@{ + //! Virtual destructor. + virtual ~JpegBase() {} + //@} + //! @name Manipulators + //@{ + /*! + @brief Read all metadata from the image. Before this method + is called, the various metadata types (Iptc, Exif) will be empty. + + This method returns success even when no metadata is found in + the image. Callers must therefore check the size of indivdual + metadata types before accessing the data. + + @return 0 if successful;
+ 1 if reading from the file failed + (could be caused by invalid image);
+ 2 if the file does not contain a valid image;
+ */ + int readMetadata(); + /*! + @brief Write metadata back to the image. + + All existing metadata sections in the image are either created, + replaced, or erased. If values for a given metadata type have been + assigned, a section for that metadata type will either be created or + replaced. If no values have been assigned to a given metadata type, + any exists section for that metadata type will be removed from the + image. + + @return 0 if successful;
+ 1 if reading from the file failed;
+ 2 if the file does not contain a valid image;
+ 4 if the temporary output file can not be written to;
+ -1 if the newly created file could not be reopened;
+ -3 if the temporary output file can not be opened;
+ -4 if renaming the temporary file fails;
+ */ + int writeMetadata(); + /*! + @brief Assign new exif data. The new exif data is not written + to the image until the writeMetadata() method is called. + @param exifData An ExifData instance holding exif data to be copied + + @throw Error ("Exif data too large") if the exif data is larger than + 65535 bytes (the maximum size of JPEG APP segments) + */ + void setExifData(const ExifData& exifData); + void clearExifData(); + void setIptcData(const IptcData& iptcData); + void clearIptcData(); + void setComment(const std::string& comment); + void clearComment(); + void setMetadata(const Image& image); + void clearMetadata(); + //@} + + //! @name Accessors + //@{ + bool good() const; + const ExifData& exifData() const { return exifData_; } + ExifData& exifData() { return exifData_; } + const IptcData& iptcData() const { return iptcData_; } + IptcData& iptcData() { return iptcData_; } + std::string comment() const { return comment_; } + BasicIo& io() const { return *io_; } + //@} + protected: + //! @name Creators + //@{ + /*! + @brief Constructor that can either open an existing image or create + a new image from scratch. If a new image is to be created, any + existing data is overwritten. + @param io An auto-pointer that owns a BasicIo instance used for + reading and writing image metadata. \b Important: The constructor + takes ownership of the passed in BasicIo instance through the + auto-pointer. Callers should not continue to use the BasicIo + instance after it is passed to this method. Use the Image::io() + method to get a temporary reference. + @param create Specifies if an existing image should be read (false) + or if a new image should be created (true). + @param initData Data to initialize newly created images. Only used + when \em create is true. Should contain data for the smallest + valid image of the calling subclass. + @param dataSize Size of initData in bytes. + */ + JpegBase(BasicIo::AutoPtr io, bool create, + const byte initData[], long dataSize); + //@} + //! @name Manipulators + //@{ + /*! + @brief Writes the image header (aka signature) to the BasicIo instance. + @param oIo BasicIo instance that the header is written to. + @return 0 if successful;
+ 4 if the output file can not be written to;
+ */ + virtual int writeHeader(BasicIo& oIo) const =0; + //@} + //! @name Accessors + //@{ + /*! + @brief Determine if the content of the BasicIo instance is of the + type supported by this class. + + The advance flag determines if the read position in the stream is + moved (see below). This applies only if the type matches and the + function returns true. If the type does not match, the stream + position is not changed. However, if reading from the stream fails, + the stream position is undefined. Consult the stream state to obtain + more information in this case. + + @param iIo BasicIo instance to read from. + @param advance Flag indicating whether the position of the io + should be advanced by the number of characters read to + analyse the data (true) or left at its original + position (false). This applies only if the type matches. + @return true if the data matches the type of this class;
+ false if the data does not match;
+ */ + virtual bool isThisType(BasicIo& iIo, bool advance) const =0; + //@} + + // Constant Data + static const byte sos_; //!< JPEG SOS marker + static const byte eoi_; //!< JPEG EOI marker + static const byte app0_; //!< JPEG APP0 marker + static const byte app1_; //!< JPEG APP1 marker + static const byte app13_; //!< JPEG APP13 marker + static const byte com_; //!< JPEG Comment marker + static const char exifId_[]; //!< Exif identifier + static const char jfifId_[]; //!< JFIF identifier + static const char ps3Id_[]; //!< Photoshop marker + static const char bimId_[]; //!< Photoshop marker + static const uint16_t iptc_; //!< Photoshop Iptc marker + + private: + // DATA + BasicIo::AutoPtr io_; //!< Image data io pointer + ExifData exifData_; //!< Exif data container + IptcData iptcData_; //!< Iptc data container + std::string comment_; //!< JPEG comment + + // METHODS + /*! + @brief Advances associated io instance to one byte past the next + Jpeg marker and returns the marker. This method should be called + when the BasicIo instance is positioned one byte past the end of a + Jpeg segment. + @return the next Jpeg segment marker if successful;
+ -1 if a maker was not found before EOF;
+ */ + int advanceToMarker() const; + /*! + @brief Locates Photoshop formated Iptc data in a memory buffer. + Operates on raw data to simplify reuse. + @param pPsData Pointer to buffer containing entire payload of + Photoshop formated APP13 Jpeg segment. + @param sizePsData Size in bytes of pPsData. + @param record Output value that is set to the start of the Iptc + data block within pPsData (may not be null). + @param sizeHdr Output value that is set to the size of the header + within the Iptc data block pointed to by record (may not + be null). + @param sizeIptc Output value that is set to the size of the actual + Iptc data within the Iptc data block pointed to by record + (may not be null). + @return 0 if successful;
+ 3 if no Iptc data was found in pPsData;
+ -2 if the pPsData buffer does not contain valid data;
+ */ + int locateIptcData(const byte *pPsData, + long sizePsData, + const byte **record, + uint16_t *const sizeHdr, + uint16_t *const sizeIptc) const; + /*! + @brief Initialize the image with the provided data. + @param initData Data to be written to the associated BasicIo + @param dataSize Size in bytes of data to be written + @return 0 if successful;
+ 4 if the output file can not be written to;
+ */ + int initImage(const byte initData[], long dataSize); + /*! + @brief Provides the main implementation of writeMetadata() by + writing all buffered metadata to the provided BasicIo. + @param oIo BasicIo instance to write to (a temporary location). + @return 0 if successful;
+ 1 if reading from input file failed;
+ 2 if the input file does not contain a valid image;
+ 4 if the output file can not be written to;
+ */ + int doWriteMetadata(BasicIo& oIo); + + // NOT Implemented + //! Default constructor. + JpegBase(); + //! Copy constructor + JpegBase(const JpegBase& rhs); + //! Assignment operator + JpegBase& operator=(const JpegBase& rhs); + }; // class JpegBase + + /*! + @brief Class to access JPEG images + */ + class JpegImage : public JpegBase { + friend bool isJpegType(BasicIo& iIo, bool advance); + public: + //! @name Creators + //@{ + /*! + @brief Constructor that can either open an existing Jpeg image or create + a new image from scratch. If a new image is to be created, any + existing data is overwritten. Since the constructor can not return + a result, callers should check the good() method after object + construction to determine success or failure. + @param io An auto-pointer that owns a BasicIo instance used for + reading and writing image metadata. \b Important: The constructor + takes ownership of the passed in BasicIo instance through the + auto-pointer. Callers should not continue to use the BasicIo + instance after it is passed to this method. Use the Image::io() + method to get a temporary reference. + @param create Specifies if an existing image should be read (false) + or if a new file should be created (true). + */ + JpegImage(BasicIo::AutoPtr io, bool create); + //! Destructor + ~JpegImage() {} + //@} + + //! @cond IGNORE + // Public only so that we can create a static instance + struct JpegRegister{ + JpegRegister(); + }; + //! @endcond + protected: + //! @name Accessors + //@{ + /*! + @brief Determine if the content of the BasicIo instance is a Jpeg image. + See base class for more details. + @param iIo BasicIo instance to read from. + @param advance Flag indicating whether the position of the io + should be advanced by the number of characters read to + analyse the data (true) or left at its original + position (false). This applies only if the type matches. + @return true if the data matches a Jpeg image;
+ false if the data does not match;
+ */ + bool isThisType(BasicIo& iIo, bool advance) const; + //@} + //! @name Manipulators + //@{ + /*! + @brief Writes a Jpeg header (aka signature) to the BasicIo instance. + @param oIo BasicIo instance that the header is written to. + @return 0 if successful;
+ 2 if the input image is invalid or can not be read;
+ 4 if the temporary image can not be written to;
+ -3 other temporary errors;
+ */ + int writeHeader(BasicIo& oIo) const; + //@} + private: + // Constant data + static const byte soi_; // SOI marker + static const byte blank_[]; // Minimal Jpeg image + + // NOT Implemented + //! Default constructor + JpegImage(); + //! Copy constructor + JpegImage(const JpegImage& rhs); + //! Assignment operator + JpegImage& operator=(const JpegImage& rhs); + }; // class JpegImage + + static JpegImage::JpegRegister jpegReg; + + //! Helper class to access %Exiv2 files + class ExvImage : public JpegBase { + friend bool isExvType(BasicIo& iIo, bool advance); + public: + //! @name Creators + //@{ + /*! + @brief Constructor that can either open an existing Exv image or create + a new image from scratch. If a new image is to be created, any + existing data is overwritten. Since the constructor can not return + a result, callers should check the good() method after object + construction to determine success or failure. + @param io An auto-pointer that owns a BasicIo instance used for + reading and writing image metadata. \b Important: The constructor + takes ownership of the passed in BasicIo instance through the + auto-pointer. Callers should not continue to use the BasicIo + instance after it is passed to this method. Use the Image::io() + method to get a temporary reference. + @param create Specifies if an existing image should be read (false) + or if a new file should be created (true). + */ + ExvImage(BasicIo::AutoPtr io, bool create); + //! Destructor + ~ExvImage() {} + //@} + + //! @cond IGNORE + // Public only so that we can create a static instance + struct ExvRegister{ + ExvRegister(); + }; + //! @endcond + protected: + //! @name Accessors + //@{ + /*! + @brief Determine if the content of the BasicIo instance is an Exv + image. See base class for more details. + @param iIo BasicIo instance to read from. + @param advance Flag indicating whether the position of the io + should be advanced by the number of characters read to + analyse the data (true) or left at its original + position (false). This applies only if the type matches. + @return true if the data matches a Jpeg image;
+ false if the data does not match;
+ */ + virtual bool isThisType(BasicIo& iIo, bool advance) const; + //@} + //! @name Manipulators + //@{ + /*! + @brief Writes an Exv header (aka signature) to the BasicIo instance. + @param oIo BasicIo instance that the header is written to. + @return 0 if successful;
+ 4 if the output file can not be written to;
+ */ + int writeHeader(BasicIo& oIo) const; + //@} + private: + // Constant data + static const char exiv2Id_[]; // Exv identifier + static const byte blank_[]; // Minimal exiv file + + // NOT Implemented + //! Default constructor + ExvImage(); + //! Copy constructor + ExvImage(const ExvImage& rhs); + //! Assignment operator + ExvImage& operator=(const ExvImage& rhs); + }; // class ExvImage + + static ExvImage::ExvRegister exvReg; +} // namespace Exiv2 + + +#endif // #ifndef JPGIMAGE_HPP_ diff --git a/src/metacopy.cpp b/src/metacopy.cpp index 29edefb7..cfd5927f 100644 --- a/src/metacopy.cpp +++ b/src/metacopy.cpp @@ -28,6 +28,8 @@ */ // ***************************************************************************** // included header files +#include "image.hpp" +#include "iptc.hpp" #include "exif.hpp" #include "types.hpp" #include "metacopy.hpp" @@ -50,8 +52,18 @@ try { return 2; } + // Use MemIo to increase test coverage. + Exiv2::BasicIo::AutoPtr fileIo(new Exiv2::FileIo(params.read_)); + Exiv2::BasicIo::AutoPtr memIo(new Exiv2::MemIo); + + if (memIo->transfer(*fileIo) != 0) { + std::cerr << params.progname() << + ": Could not read file (" << params.read_ << ")\n"; + return 4; + } + Exiv2::Image::AutoPtr readImg - = Exiv2::ImageFactory::instance().open(params.read_); + = Exiv2::ImageFactory::open(memIo); if (readImg.get() == 0) { std::cerr << params.progname() << ": Could not read file (" << params.read_ << ")\n"; @@ -64,7 +76,7 @@ try { } Exiv2::Image::AutoPtr writeImg - = Exiv2::ImageFactory::instance().open(params.write_); + = Exiv2::ImageFactory::open(params.write_); if (writeImg.get() == 0) { std::cerr << params.progname() << ": Could not read file (" << params.write_ << ")\n"; @@ -79,10 +91,10 @@ try { } } if (params.iptc_) { - writeImg->setIptcData(readImg->iptcData(), readImg->sizeIptcData()); + writeImg->setIptcData(readImg->iptcData()); } if (params.exif_) { - writeImg->setExifData(readImg->exifData(), readImg->sizeExifData()); + writeImg->setExifData(readImg->exifData()); } if (params.comment_) { writeImg->setComment(readImg->comment()); diff --git a/src/types.hpp b/src/types.hpp index 13b086c9..f4767740 100644 --- a/src/types.hpp +++ b/src/types.hpp @@ -203,39 +203,6 @@ namespace Exiv2 { long size_; }; // class DataBuf - /*! - @brief Utility class that closes a file stream pointer upon destruction. - Its primary use is to be a stack variable in functions that need to - ensure files get closed. Useful when functions return errors from - many locations. - */ - class FileCloser { - // Not implemented - //! Copy constructor - FileCloser(const FileCloser&); - //! Assignment operator - FileCloser& operator=(const FileCloser&); - public: - //! @name Creators - //@{ - //! Default constructor - FileCloser() : fp_(0) {} - //! Constructor, takes a file stream pointer - FileCloser(FILE *fp) : fp_(fp) {} - //! Destructor, closes the file - ~FileCloser() { close(); } - //@} - - //! @name Manipulators - //@{ - //! Close the file - void close() { if (fp_) fclose(fp_); fp_ = 0; } - //@} - - // DATA - //! The file stream pointer - FILE *fp_; - }; // class FileCloser // ***************************************************************************** // free functions diff --git a/src/write-test.cpp b/src/write-test.cpp index 60905889..2304ef66 100644 --- a/src/write-test.cpp +++ b/src/write-test.cpp @@ -15,6 +15,7 @@ */ // ***************************************************************************** // included header files +#include "image.hpp" #include "exif.hpp" #include "makernote.hpp" @@ -160,15 +161,24 @@ void testCase(const std::string& file1, const std::string& value) { ExifKey ek(key); - ExifData ed1; + //Open first image + Image::AutoPtr image1 = ImageFactory::open(file1); + if (image1.get() == 0) { + std::string error(file1); + error += " : Could not read file or unknown image type"; + throw Exiv2::Error(error); + } + + // Load existing metadata std::cerr << "---> Reading file " << file1 << "\n"; - int rc = ed1.read(file1); + int rc = image1->readMetadata(); if (rc) { - std::string error = ExifData::strError(rc, file1.c_str()); - throw Error(error); + std::string error = Exiv2::Image::strError(rc, file1); + throw Exiv2::Error(error); } + Exiv2::ExifData &ed1 = image1->exifData(); std::cerr << "---> Modifying Exif data\n"; Exiv2::ExifData::iterator pos = ed1.findKey(ek); if (pos == ed1.end()) { @@ -176,27 +186,35 @@ void testCase(const std::string& file1, } pos->setValue(value); + // Open second image + Image::AutoPtr image2 = ImageFactory::open(file2); + if (image2.get() == 0) { + std::string error(file2); + error += " : Could not read file or unknown image type"; + throw Exiv2::Error(error); + } + + image2->setExifData(image1->exifData()); + std::cerr << "---> Writing Exif data to file " << file2 << "\n"; - rc = ed1.write(file2); + rc = image2->writeMetadata(); if (rc) { - std::string error = ExifData::strError(rc, file2.c_str()); + std::string error = Image::strError(rc, file2.c_str()); throw Error(error); } - ExifData ed2; - std::cerr << "---> Reading file " << file2 << "\n"; - rc = ed2.read(file2); + rc = image2->readMetadata(); if (rc) { - std::string error = ExifData::strError(rc, file2.c_str()); - throw Error(error); + std::string error = Exiv2::Image::strError(rc, file2); + throw Exiv2::Error(error); } + Exiv2::ExifData &ed2 = image2->exifData(); exifPrint(ed2); std::cerr << "---> Writing Exif thumbnail to file " << thumb << ".*\n"; ed2.writeThumbnail(thumb); - } // ***************************************************************************** diff --git a/src/write2-test.cpp b/src/write2-test.cpp index 77eabde6..e8ccefe6 100644 --- a/src/write2-test.cpp +++ b/src/write2-test.cpp @@ -10,6 +10,7 @@ */ // ***************************************************************************** // included header files +#include "image.hpp" #include "exif.hpp" #include #include @@ -150,22 +151,37 @@ catch (Exiv2::Error& e) { void write(const std::string& file, Exiv2::ExifData& ed) { - int rc = ed.write(file); + Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open(file); + if (image.get() == 0) { + std::string error(file); + error += " : Could not read file or unknown image type"; + throw Exiv2::Error(error); + } + + image->setExifData(ed); + int rc = image->writeMetadata(); if (rc) { - std::string error = Exiv2::ExifData::strError(rc, file); + std::string error = Exiv2::Image::strError(rc, file); throw Exiv2::Error(error); } } void print(const std::string& file) { - Exiv2::ExifData ed; - int rc = ed.read(file); + Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open(file); + if (image.get() == 0) { + std::string error(file); + error += " : Could not read file or unknown image type"; + throw Exiv2::Error(error); + } + + int rc = image->readMetadata(); if (rc) { - std::string error = Exiv2::ExifData::strError(rc, file); + std::string error = Exiv2::Image::strError(rc, file); throw Exiv2::Error(error); } + Exiv2::ExifData &ed = image->exifData(); Exiv2::ExifData::const_iterator end = ed.end(); for (Exiv2::ExifData::const_iterator i = ed.begin(); i != end; ++i) { std::cout << std::setw(45) << std::setfill(' ') << std::left diff --git a/test/data/glider.exv.irgd b/test/data/glider.exv.irgd index 447ee918..e69de29b 100644 --- a/test/data/glider.exv.irgd +++ b/test/data/glider.exv.irgd @@ -1 +0,0 @@ -Caught Exiv2 exception 'temp: No Iptc data found in the file' diff --git a/test/imagetest.sh b/test/imagetest.sh index 20c8cae7..2a35a737 100755 --- a/test/imagetest.sh +++ b/test/imagetest.sh @@ -1,4 +1,4 @@ -#! /bin/sh +#! /bin/bash # Test driver for image file i/o eraseTest() diff --git a/test/iotest.sh b/test/iotest.sh new file mode 100644 index 00000000..bb4bf50a --- /dev/null +++ b/test/iotest.sh @@ -0,0 +1,59 @@ +#! /bin/bash +# Test driver for image file i/o + +ioTest() +{ + src=$datapath/$1 + out1=${1}.1 + out2=${1}.2 + + #run tests + $binpath/iotest $src $out1 $out2 + if [ $? -ne 0 ]; then + let ++errors + return + fi + + #check results + diffCheck $out1 $src + diffCheck $out2 $src + echo -n "." +} + +# Make sure to pass the test file first and the known good file second +diffCheck() +{ + test=$1 + good=$2 + + #run diff and check results + diff -q --binary $test $good + if [ $? -ne 0 ]; then + let ++errors + else + rm $test + fi +} + +# ********************************************************************** +# main + +LD_LIBRARY_PATH=../../src:$LD_LIBRARY_PATH +binpath="../../src" +datapath="../data" + +test_files="table.jpg smiley2.jpg ext.dat" + +let errors=0 +cd ./tmp +echo + +echo -n "Io tests" +for i in $test_files; do ioTest $i; done + +echo -e "\n---------------------------------------------------------" +if [ $errors -eq 0 ]; then + echo 'All test cases passed' +else + echo $errors 'test case(s) failed!' +fi diff --git a/test/iptctest.sh b/test/iptctest.sh index 90eb83c5..ab2460a7 100755 --- a/test/iptctest.sh +++ b/test/iptctest.sh @@ -1,4 +1,4 @@ -#! /bin/sh +#! /bin/bash # Test driver for Iptc metadata printTest()