diff --git a/cexif.cpp b/cexif.cpp new file mode 100644 index 0000000..c11a812 --- /dev/null +++ b/cexif.cpp @@ -0,0 +1,1169 @@ +/*! + \file cexif.cpp + +*/ + +#include "cexif.h" + +#include "common.h" + +#include +#include +#include +#include + +#include +#include + +#include + + +cEXIF::cEXIF(cEXIFTagList* lpEXIFTagList, cEXIFCompressionList* lpEXIFCompressionList, cEXIFLightSourceList* lpEXIFLightSourceList, cEXIFFlashList* lpEXIFFlashList, cIPTCTagList* lpIPTCTagList, cXMPTagList* lpXMPTagList) : + m_szMimeType(""), + m_iWidth(0), + m_iHeight(0), + m_szFileName(""), + m_lpEXIFTagList(lpEXIFTagList), + m_lpEXIFCompressionList(lpEXIFCompressionList), + m_lpEXIFLightSourceList(lpEXIFLightSourceList), + m_lpEXIFFlashList(lpEXIFFlashList), + m_lpIPTCTagList(lpIPTCTagList), + m_lpXMPTagList(lpXMPTagList) +{ +} + +bool cEXIF::fromFile(const QString& szFileName) +{ + if(!QFile::exists(szFileName)) + return(false); + + m_exifValueList.clear(); + m_previewList.clear(); + + m_szFileName = ""; + + try + { + Exiv2::Image::UniquePtr image = Exiv2::ImageFactory::open(szFileName.toLocal8Bit().toStdString()); + if(!image.get()) + return(false); + + image->readMetadata(); + + Exiv2::ExifData& exifData = image->exifData(); + Exiv2::IptcData& iptcData = image->iptcData(); + Exiv2::XmpData& xmpData = image->xmpData(); + + m_szMimeType = QString::fromStdString(image->mimeType()); + m_iWidth = image->pixelWidth(); + m_iHeight = image->pixelHeight(); + + m_szFileName = szFileName; + + if(!iptcData.empty()) + { + Exiv2::IptcData::const_iterator end = iptcData.end(); + + for(Exiv2::IptcData::const_iterator i = iptcData.begin(); i != end; ++i) + { + cIPTCTag* lpTag = m_lpIPTCTagList->find(i->tag()); + + if(lpTag) + { + cIPTCValue* lpValue = m_iptcValueList.add(lpTag); + if(lpValue) + lpValue->setValue(QString::fromStdString(i->value().toString()), i->typeId(), i->count()); + } + } + + Exiv2::PreviewManager previewManager(*image); + Exiv2::PreviewPropertiesList previewPropertiesList = previewManager.getPreviewProperties(); + + for(Exiv2::PreviewPropertiesList::const_iterator i = previewPropertiesList.begin();i != previewPropertiesList.end();i++) + { + Exiv2::PreviewImage previewImage = previewManager.getPreviewImage(*i); + QImage image; + image.loadFromData(static_cast(previewImage.pData()), static_cast(previewImage.size())); + + m_previewList.append(image); + } + } + + if(!exifData.empty()) + { + Exiv2::ExifData::const_iterator end = exifData.end(); + for(Exiv2::ExifData::const_iterator i = exifData.begin(); i != end; ++i) + { + cEXIFTag* lpTag = m_lpEXIFTagList->find(QString::fromUtf8(i->key().c_str())); + + if(lpTag) + { + cEXIFValue* lpValue = m_exifValueList.add(lpTag); + if(lpValue) + lpValue->setValue(QString::fromStdString(i->value().toString()), i->typeId(), i->count()); + } + } + + Exiv2::PreviewManager previewManager(*image); + Exiv2::PreviewPropertiesList previewPropertiesList = previewManager.getPreviewProperties(); + + for(Exiv2::PreviewPropertiesList::const_iterator i = previewPropertiesList.begin();i != previewPropertiesList.end();i++) + { + Exiv2::PreviewImage previewImage = previewManager.getPreviewImage(*i); + QImage image; + image.loadFromData(static_cast(previewImage.pData()), static_cast(previewImage.size())); + + m_previewList.append(image); + } + } + + if(!xmpData.empty()) + { + Exiv2::XmpData::const_iterator end = xmpData.end(); + + for(Exiv2::XmpData::const_iterator i = xmpData.begin(); i != end; ++i) + { + cXMPTag* lpTag = m_lpXMPTagList->find(QString::fromStdString(i->key())); + + if(lpTag) + { + cXMPValue* lpValue = m_xmpValueList.add(lpTag); + if(lpValue) + lpValue->setValue(QString::fromStdString(i->value().toString()), i->typeId(), i->count()); + } + } + + Exiv2::PreviewManager previewManager(*image); + Exiv2::PreviewPropertiesList previewPropertiesList = previewManager.getPreviewProperties(); + + for(Exiv2::PreviewPropertiesList::const_iterator i = previewPropertiesList.begin();i != previewPropertiesList.end();i++) + { + Exiv2::PreviewImage previewImage = previewManager.getPreviewImage(*i); + QImage image; + image.loadFromData(static_cast(previewImage.pData()), static_cast(previewImage.size())); + + m_previewList.append(image); + } + } + } + catch (Exiv2::AnyError& e) + { + qDebug() << "Caught Exiv2 exception '" << e.what(); + return(false); + } + + if(!m_previewList.count()) + { + QImage image; + if(image.load(szFileName)) + { + QTransform rotation; + int angle = 0; + + switch(imageOrientation()) + { + case 8: + angle = 270; + break; + case 3: + angle = 180; + break; + case 6: + angle = 90; + break; + } + + if(angle != 0) + { + rotation.rotate(angle); + image = image.transformed(rotation); + } + + m_thumbnail = image.scaled(THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT, Qt::KeepAspectRatio, Qt::SmoothTransformation); + } + } + else + { + qint32 index = -1; + qint32 widthDiff = std::numeric_limits::max(); + qint32 heightDiff = std::numeric_limits::max(); + + for(qint32 i = 0;i < m_previewList.count();i++) + { + qint32 wd = m_previewList[i].width() - THUMBNAIL_WIDTH; + qint32 hd = m_previewList[i].height() - THUMBNAIL_HEIGHT; + + if(wd >= 0 && hd >= 0) + { + if(wd <= widthDiff && hd <= heightDiff) + { + index = i; + widthDiff = wd; + heightDiff = hd; + } + } + } + + if(index == -1) + { + for(qint32 i = 0;i < m_previewList.count();i++) + { + qint32 wd = m_previewList[i].width() - THUMBNAIL_WIDTH; + qint32 hd = m_previewList[i].height() - THUMBNAIL_HEIGHT; + + if(abs(wd) <= widthDiff && abs(hd) <= heightDiff) + { + index = i; + widthDiff = abs(wd); + heightDiff = abs(hd); + } + } + } + + if(index != -1) + { + QTransform rotation; + int angle = 0; + + switch(imageOrientation()) + { + case 8: + angle = 270; + break; + case 3: + angle = 180; + break; + case 6: + angle = 90; + break; + } + + m_thumbnail = m_previewList[index].scaled(THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT, Qt::KeepAspectRatio, Qt::SmoothTransformation); + + if(angle != 0) + { + rotation.rotate(angle); + m_thumbnail = m_thumbnail.transformed(rotation); + } + } + } + + return(true); +} + +QString cEXIF::mimeType() +{ + return(m_szMimeType); +} + +qint32 cEXIF::imageWidth() +{ + if(m_iWidth) + return(m_iWidth); + + qint32 iWidth = getEXIFTag(0x0100, "Image").value(); + + if(iWidth) + return(iWidth); + + return(getXMPTag("Xmp.video.Width").value()); +} + +qint32 cEXIF::imageHeight() +{ + if(m_iHeight) + return(m_iHeight); + + qint32 iHeight = getEXIFTag(0x0101, "Image").value(); + + if(iHeight) + return(iHeight); + + return(getXMPTag("Xmp.video.Height").value()); +} + +qint16 cEXIF::imageOrientation() +{ + return(getEXIFTag(0x0112, "Image").value()); +} + +QString cEXIF::cameraMake() +{ + QString szCameraMake = getEXIFTag(0x010f, "Image").toString(); + + if(!szCameraMake.isEmpty()) + return(szCameraMake); + + return(getXMPTag("Xmp.video.MajorBrand").toString()); +} + +QString cEXIF::cameraModel() +{ + QString szCameraModel = getEXIFTag(0x0110, "Image").toString(); + + if(!szCameraModel.isEmpty()) + return(szCameraModel); + + return(getXMPTag("Xmp.video.MajorBrand").toString()); +} + +QDateTime cEXIF::dateTime() +{ + QDateTime dateTime = QDateTime::fromString(getEXIFTag(0x9004, "Image").value(), "yyyy:MM:dd hh:mm:ss"); + if(dateTime.isValid()) + return(dateTime); + + dateTime = QDateTime::fromString(getXMPTag("Xmp.video.DateTimeDigitized").toString(), "yyyy-MM-ddThh:mm:ssZ"); + if(dateTime.isValid()) + return(dateTime); + + dateTime = QDateTime::fromString(getEXIFTag(0x0132, "Image").value(), "yyyy:MM:dd hh:mm:ss"); + if(dateTime.isValid()) + return(dateTime); + + QFileInfo info(m_szFileName); + return(info.lastModified()); +} + +QString cEXIF::fNumber() +{ + return(getEXIFTag(0x829d, "Photo").value()); +} + +qint32 cEXIF::iso() +{ + return(getEXIFTag(0x8827, "Photo").value()); +} + +QString cEXIF::flash() +{ + return(m_lpEXIFFlashList->flash(getEXIFTag(0x9209, "Photo").value())); +} + +qint32 cEXIF::flashID() +{ + return(getEXIFTag(0x9209, "Photo").value()); +} + +qreal cEXIF::focalLength() +{ + return(getEXIFTag(0x920a, "Photo").value()); +} + +QString cEXIF::lensMake() +{ + return(getEXIFTag(0xa433, "Photo").value()); +} + +QString cEXIF::lensModel() +{ + return(getEXIFTag(0xa434, "Photo").value()); +} + +QString cEXIF::exposureTime() +{ + qreal value = getEXIFTag(0x829a, "Photo").value(); + + if(value == 0.0) + return("error"); + + if(value >= 1.0) + return(QString("%1 sec").arg(value)); + + return(QString("1/%1").arg(1/value)); +} + +qint32 cEXIF::exposureBias() +{ + return(getEXIFTag(0x9204, "Photo").value()); +} + +QString cEXIF::exifVersion() +{ + return(getEXIFTag(0x9000, "Photo").value()); +} + +QDateTime cEXIF::dateTimeOriginal() +{ + return(QDateTime::fromString(getEXIFTag(0x9003, "Photo").value(), "yyyy:MM:dd hh:mm:ss")); +} + +QDateTime cEXIF::dateTimeDigitized() +{ + return(QDateTime::fromString(getEXIFTag(0x9004, "Photo").value(), "yyyy:MM:dd hh:mm:ss")); +} + +qint32 cEXIF::whiteBalance() +{ + return(getEXIFTag(0xa403, "Photo").value()); +} + +qreal cEXIF::focalLength35() +{ + return(getEXIFTag(0xa405, "Photo").value()); +} + +QString cEXIF::gps() +{ + QList nList = getTagList(0x0002, "GPSInfo"); + QList eList = getTagList(0x0004, "GPSInfo"); + + if(nList.count() != 3) + return(""); + if(eList.count() != 3) + return(""); + + QString szGPS = QString("%1 %2° %3' %4\" %5 %6° %7' %8\"").arg(getEXIFTag(0x0001, "GPSInfo").value()).arg(nList[0].value()).arg(nList[1].value()).arg(nList[2].value()).arg(getEXIFTag(0x0001, "GPSInfo").value()).arg(eList[0].value()).arg(eList[1].value()).arg(eList[2].value()); + return(szGPS); +} + +QString cEXIF::duration() +{ + return(getXMPTag("Xmp.video.Duration").toString()); +} + +QString cEXIF::fileName() +{ + return(m_szFileName); +} + +QList cEXIF::previewList() +{ + return(m_previewList); +} + +QImage cEXIF::thumbnail() +{ + return(m_thumbnail); +} + +QVariant cEXIF::getEXIFTag(qint32 iTAGID, const QString& szIFD) +{ + cEXIFTag* lpTag = m_lpEXIFTagList->find(iTAGID, szIFD); + + if(!lpTag) + return(QVariant()); + + cEXIFValue* lpValue = m_exifValueList.find(lpTag); + + if(!lpValue) + return(QVariant()); + + return(lpValue->value()); +} + +QVariant cEXIF::getIPTCTag(qint32 iTAGID) +{ + cIPTCTag* lpTag = m_lpIPTCTagList->find(iTAGID); + + if(!lpTag) + return(QVariant()); + + cIPTCValue* lpValue = m_iptcValueList.find(lpTag); + + if(!lpValue) + return(QVariant()); + + return(lpValue->value()); +} + +QVariant cEXIF::getXMPTag(const QString& szTAGName) +{ + cXMPTag* lpTag = m_lpXMPTagList->find(szTAGName); + + if(!lpTag) + return(QVariant()); + + cXMPValue* lpValue = m_xmpValueList.find(lpTag); + + if(!lpValue) + return(QVariant()); + + return(lpValue->value()); +} + +QList cEXIF::getTagList(qint32 iTAGID, const QString& szIFD) +{ + cEXIFTag* lpTag = m_lpEXIFTagList->find(iTAGID, szIFD); + + if(!lpTag) + return(QList()); + + cEXIFValue* lpValue = m_exifValueList.find(lpTag); + + if(!lpValue) + return(QList()); + + return(lpValue->valueList()); +} + +bool cEXIF::copyTo(const QString& fileName) +{ + try + { + Exiv2::Image::UniquePtr image = Exiv2::ImageFactory::open(fileName.toLocal8Bit().toStdString()); + if(!image.get()) + return(false); + + image->readMetadata(); + + Exiv2::ExifData& exifData = image->exifData(); + + for(int x = 0;x < m_exifValueList.count();x++) + { + cEXIFValue* lpEXIFValue = m_exifValueList[x]; + +// if(!lpEXIFValue->exifTag()->m_szKey.startsWith("Exif.Image")) + exifData[lpEXIFValue->exifTag()->m_szKey.toUtf8().constData()] = lpEXIFValue->value().toString().toUtf8().constData(); + } + + image->setExifData(exifData); + image->writeMetadata(); + } + catch (Exiv2::AnyError& e) + { + qDebug() << "Caught Exiv2 exception '" << e.what(); + return(false); + } + + return(true); +} + +cEXIFCompression::cEXIFCompression(const qint32& iID, const QString& szCompression) : + m_iID(iID), + m_szCompression(szCompression) +{ +} + +cEXIFCompressionList::cEXIFCompressionList() +{ + QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE"); + db.setHostName("localhost"); + db.setDatabaseName("pictureConvert.db"); + + if(!db.open()) + return; + + QSqlQuery query; + + query.prepare("SELECT id, compression FROM exifCompression;"); + if(!query.exec()) + { + db.close(); + return; + } + + while(query.next()) + add(query.value("id").toInt(), query.value("compression").toString()); + + db.close(); +} + +cEXIFCompression* cEXIFCompressionList::add(const qint32& iID, const QString& szCompression) +{ + cEXIFCompression* lpNew = find(iID); + + if(lpNew) + return(nullptr); + + lpNew = new cEXIFCompression(iID, szCompression); + append(lpNew); + return(lpNew); +} + +cEXIFCompression* cEXIFCompressionList::find(const qint32& iID) +{ + for(int x = 0;x < count();x++) + { + cEXIFCompression* lpCompression= at(x); + + if(lpCompression->m_iID == iID) + return(lpCompression); + } + return(nullptr); +} + +QString cEXIFCompressionList::compression(const qint32& iID) +{ + cEXIFCompression* lpCompression = find(iID); + if(!lpCompression) + return(QObject::tr("unknown")); + return(lpCompression->m_szCompression); +} + +cEXIFLightSource::cEXIFLightSource(const qint32& iID, const QString& szLightSource) : + m_iID(iID), + m_szLightSource(szLightSource) +{ +} + +cEXIFLightSourceList::cEXIFLightSourceList() +{ + QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE"); + db.setHostName("localhost"); + db.setDatabaseName("pictureConvert.db"); + + if(!db.open()) + return; + + QSqlQuery query; + + query.prepare("SELECT id, lightsource FROM exifLightSource;"); + if(!query.exec()) + { + db.close(); + return; + } + + while(query.next()) + add(query.value("id").toInt(), query.value("lightsource").toString()); + + db.close(); +} + +cEXIFLightSource* cEXIFLightSourceList::add(const qint32& iID, const QString& szLightSource) +{ + cEXIFLightSource* lpNew = find(iID); + + if(lpNew) + return(nullptr); + + lpNew = new cEXIFLightSource(iID, szLightSource); + append(lpNew); + return(lpNew); +} + +cEXIFLightSource* cEXIFLightSourceList::find(const qint32& iID) +{ + for(int x = 0;x < count();x++) + { + cEXIFLightSource* lpLightSource = at(x); + + if(lpLightSource->m_iID == iID) + return(lpLightSource); + } + return(nullptr); +} + +QString cEXIFLightSourceList::lightSource(const qint32& iID) +{ + cEXIFLightSource* lpLightSource = find(iID); + if(!lpLightSource) + return(QObject::tr("unknown")); + return(lpLightSource->m_szLightSource); +} + +cEXIFFlash::cEXIFFlash(const qint32& iID, const QString& szFlash) : + m_iID(iID), + m_szFlash(szFlash) +{ +} + +cEXIFFlashList::cEXIFFlashList() +{ + QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE"); + db.setHostName("localhost"); + db.setDatabaseName("pictureConvert.db"); + + if(!db.open()) + return; + + QSqlQuery query; + + query.prepare("SELECT id, lightsource FROM exifLightSource;"); + if(!query.exec()) + { + db.close(); + return; + } + + while(query.next()) + add(query.value("id").toInt(), query.value("lightsource").toString()); + + db.close(); +} + +cEXIFFlash* cEXIFFlashList::add(const qint32& iID, const QString& szFlash) +{ + cEXIFFlash* lpNew = find(iID); + + if(lpNew) + return(nullptr); + + lpNew = new cEXIFFlash(iID, szFlash); + append(lpNew); + return(lpNew); +} + +cEXIFFlash* cEXIFFlashList::find(const qint32& iID) +{ + for(int x = 0;x < count();x++) + { + cEXIFFlash* lpFlash = at(x); + + if(lpFlash->m_iID == iID) + return(lpFlash); + } + return(nullptr); +} + +QString cEXIFFlashList::flash(const qint32& iID) +{ + cEXIFFlash* lpFlash = find(iID); + if(!lpFlash) + return(QObject::tr("unknown")); + return(lpFlash->m_szFlash); +} + +cEXIFTag::cEXIFTag(const qint32& iTAGID, const QString& szTAGName, const QString& szIFD, const QString& szKey, const QString& szType, const QString& szDescription) : + m_iTAGID(iTAGID), + m_szTAGName(szTAGName), + m_szIFD(szIFD), + m_szKey(szKey), + m_szType(szType), + m_szDescription(szDescription) +{ +} + +cEXIFTagList::cEXIFTagList() +{ + QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE"); + db.setHostName("localhost"); + db.setDatabaseName("pictureConvert.db"); + + if(!db.open()) + return; + + QSqlQuery query; + + query.prepare("SELECT tag, ifd, key, type, description FROM exifTags;"); + if(!query.exec()) + { + db.close(); + return; + } + + while(query.next()) + add(query.value("tag").toInt(), query.value("key").toString(), query.value("ifd").toString(), query.value("key").toString(), query.value("type").toString(), query.value("description").toString()); + + db.close(); +} + +cEXIFTag* cEXIFTagList::add(const qint32& iTAGID, const QString& szTAGName, const QString& szIFD, const QString& szKey, const QString& szType, const QString& szDescription) +{ + cEXIFTag* lpNew = find(iTAGID, szIFD); + + if(lpNew) + return(nullptr); + + lpNew = new cEXIFTag(iTAGID, szTAGName, szIFD, szKey, szType, szDescription); + append(lpNew); + return(lpNew); +} + +cEXIFTag* cEXIFTagList::find(const QString& szKey) +{ + for(int x = 0;x < count();x++) + { + cEXIFTag* lpTag = at(x); + + if(lpTag->m_szKey == szKey) + return(lpTag); + } + return(nullptr); +} + +cEXIFTag* cEXIFTagList::find(const qint32& iTAGID, const QString& szIFD) +{ + for(int x = 0;x < count();x++) + { + cEXIFTag* lpTag = at(x); + + if(lpTag->m_iTAGID == iTAGID && lpTag->m_szIFD== szIFD) + return(lpTag); + } + return(nullptr); +} + +cEXIFTag* cEXIFTagList::find(const QString& szTAG, const QString& szIFD) +{ + for(int x = 0;x < count();x++) + { + cEXIFTag* lpTag = at(x); + + if(lpTag->m_szTAGName == szTAG && lpTag->m_szIFD== szIFD) + return(lpTag); + } + return(nullptr); +} + +cEXIFTag* cEXIFValue::exifTag() +{ + return(m_lpEXIFTag); +} + +cEXIFValue::cEXIFValue(cEXIFTag* lpEXIFTag) : + m_lpEXIFTag(lpEXIFTag) +{ +} + +QVariant cEXIFValue::convertValue(const QString& szValue, qint32 iTypeId) +{ + QVariant variant; + + switch(iTypeId) + { + case 1: //byte + case 3: //short + case 4: //long + case 8: //signed short + variant = QVariant::fromValue(szValue.toLong()); + break; + case 5: //rational + case 10: //signed rational + case 11: //float + if(szValue.contains("/")) + { + QStringList tmp = szValue.split("/"); + variant = QVariant::fromValue(tmp[0].toDouble()/tmp[1].toDouble()); + } + else + variant = QVariant::fromValue(szValue.toDouble()); + break; + default: + variant = szValue; + } + return(variant); +} + +void cEXIFValue::setValue(const QString& szValue, qint32 iTypeId, qint32 iCount) +{ + if(iCount == 1 || iTypeId == 2 || iTypeId == 7) + { + m_valueList.append(convertValue(szValue, iTypeId)); + return; + } + + QStringList valueList = szValue.split(" "); + + if(valueList.count() != iCount) + return; + + for(int x = 0;x < iCount;x++) + m_valueList.append(convertValue(valueList[x], iTypeId)); +} + +QVariant cEXIFValue::value() +{ + if(m_valueList.count()) + return(m_valueList[0]); + + return(QVariant()); +} + +QList cEXIFValue::valueList() +{ + return(m_valueList); +} + +cEXIFValueList::cEXIFValueList() +{ +} + +cEXIFValue* cEXIFValueList::add(cEXIFTag* lpEXIFTag) +{ + cEXIFValue* lpNew = find(lpEXIFTag); + + if(lpNew) + return(nullptr); + + lpNew = new cEXIFValue(lpEXIFTag); + + append(lpNew); + return(lpNew); +} + +cEXIFValue* cEXIFValueList::find(cEXIFTag* lpEXIFTag) +{ + for(int x = 0;x < count();x++) + { + cEXIFValue* lpValue = at(x); + if(lpValue->exifTag() == lpEXIFTag) + return(lpValue); + } + return(nullptr); +} + +cIPTCTag::cIPTCTag(const qint32& iTAGID, const QString& szTAGName, const qint32& iTypeID, const QString& szDescription) : + m_iTAGID(iTAGID), + m_szTAGName(szTAGName), + m_iTypeID(iTypeID), + m_szDescription(szDescription) +{ +} + +cIPTCTagList::cIPTCTagList() +{ +// add(0x000b, QObject::tr("ProcessingSoftware"), 1, 2, QObject::tr("The name and version of the software used to post-process the picture.")); +} + +cIPTCTag* cIPTCTagList::add(const qint32& iTAGID, const QString& szTAGName, const qint32& iTypeID, const QString& szDescription) +{ + cIPTCTag* lpNew = find(iTAGID); + + if(lpNew) + return(nullptr); + + lpNew = new cIPTCTag(iTAGID, szTAGName, iTypeID, szDescription); + append(lpNew); + return(lpNew); +} + +cIPTCTag* cIPTCTagList::find(const qint32& iTAGID) +{ + for(int x = 0;x < count();x++) + { + cIPTCTag* lpTag = at(x); + + if(lpTag->m_iTAGID == iTAGID) + return(lpTag); + } + return(nullptr); +} + +cIPTCTag* cIPTCValue::iptcTag() +{ + return(m_lpIPTCTag); +} + +cIPTCValue::cIPTCValue(cIPTCTag* lpIPTCTag) : + m_lpIPTCTag(lpIPTCTag) +{ +} + +QVariant cIPTCValue::convertValue(const QString& szValue, qint32 iTypeId) +{ + QVariant variant; + + switch(iTypeId) + { + case 1: //byte + case 3: //short + case 4: //long + case 8: //signed short + variant = QVariant::fromValue(szValue.toLong()); + break; + case 5: //rational + case 10: //signed rational + case 11: //float + if(szValue.contains("/")) + { + QStringList tmp = szValue.split("/"); + variant = QVariant::fromValue(tmp[0].toDouble()/tmp[1].toDouble()); + } + else + variant = QVariant::fromValue(szValue.toDouble()); + break; + default: + variant = szValue; + } + return(variant); +} + +void cIPTCValue::setValue(const QString& szValue, qint32 iTypeId, qint32 iCount) +{ + if(iCount == 1 || iTypeId == 2 || iTypeId == 7) + { + m_valueList.append(convertValue(szValue, iTypeId)); + return; + } + + QStringList valueList = szValue.split(" "); + + if(valueList.count() != iCount) + return; + + for(int x = 0;x < iCount;x++) + m_valueList.append(convertValue(valueList[x], iTypeId)); +} + +QVariant cIPTCValue::value() +{ + if(m_valueList.count()) + return(m_valueList[0]); + + return(QVariant()); +} + +QList cIPTCValue::valueList() +{ + return(m_valueList); +} + +cIPTCValueList::cIPTCValueList() +{ +} + +cIPTCValue* cIPTCValueList::add(cIPTCTag* lpIPTCTag) +{ + cIPTCValue* lpNew = find(lpIPTCTag); + + if(lpNew) + return(nullptr); + + lpNew = new cIPTCValue(lpIPTCTag); + + append(lpNew); + return(lpNew); +} + +cIPTCValue* cIPTCValueList::find(cIPTCTag* lpIPTCTag) +{ + for(int x = 0;x < count();x++) + { + cIPTCValue* lpValue = at(x); + if(lpValue->iptcTag() == lpIPTCTag) + return(lpValue); + } + return(nullptr); +} + +cXMPTag::cXMPTag(const QString& szTAGName, const qint32& iTypeID, const QString& szDescription) : + m_szTAGName(szTAGName), + m_iTypeID(iTypeID), + m_szDescription(szDescription) +{ +} + +cXMPTagList::cXMPTagList() +{ + QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE"); + db.setHostName("localhost"); + db.setDatabaseName("pictureConvert.db"); + + if(!db.open()) + return; + + QSqlQuery query; + + query.prepare("SELECT tag, type, description FROM xmpTags;"); + if(!query.exec()) + { + db.close(); + return; + } + + while(query.next()) + add(query.value("tag").toString(), query.value("type").toInt(), query.value("description").toString()); + + db.close(); +} + +cXMPTag* cXMPTagList::add(const QString& szTAGName, const qint32& iTypeID, const QString& szDescription) +{ + cXMPTag* lpNew = find(szTAGName); + + if(lpNew) + return(nullptr); + + lpNew = new cXMPTag(szTAGName, iTypeID, szDescription); + append(lpNew); + return(lpNew); +} + +cXMPTag* cXMPTagList::find(const QString& szTAGName) +{ + for(int x = 0;x < count();x++) + { + cXMPTag* lpTag = at(x); + + if(lpTag->m_szTAGName == szTAGName) + return(lpTag); + } + return(nullptr); +} + +cXMPTag* cXMPValue::xmpTag() +{ + return(m_lpXMPTag); +} + +cXMPValue::cXMPValue(cXMPTag* lpXMPTag) : + m_lpXMPTag(lpXMPTag) +{ +} + +QVariant cXMPValue::convertValue(const QString& szValue, qint32 iTypeId) +{ + QVariant variant; + + switch(iTypeId) + { + case 1: //byte + case 3: //short + case 4: //long + case 8: //signed short + variant = QVariant::fromValue(szValue.toLong()); + break; + case 5: //rational + case 10: //signed rational + case 11: //float + if(szValue.contains("/")) + { + QStringList tmp = szValue.split("/"); + variant = QVariant::fromValue(tmp[0].toDouble()/tmp[1].toDouble()); + } + else + variant = QVariant::fromValue(szValue.toDouble()); + break; + default: + variant = szValue; + } + return(variant); +} + +void cXMPValue::setValue(const QString& szValue, qint32 iTypeId, qint32 /*iCount*/) +{ + m_valueList.append(convertValue(szValue, iTypeId)); +} + +QVariant cXMPValue::value() +{ + if(m_valueList.count()) + return(m_valueList[0]); + + return(QVariant()); +} + +QList cXMPValue::valueList() +{ + return(m_valueList); +} + +cXMPValueList::cXMPValueList() +{ +} + +cXMPValue* cXMPValueList::add(cXMPTag* lpXMPTag) +{ + cXMPValue* lpNew = find(lpXMPTag); + + if(lpNew) + return(nullptr); + + lpNew = new cXMPValue(lpXMPTag); + + append(lpNew); + return(lpNew); +} + +cXMPValue* cXMPValueList::find(cXMPTag* lpXMPTag) +{ + for(int x = 0;x < count();x++) + { + cXMPValue* lpValue = at(x); + if(lpValue->xmpTag() == lpXMPTag) + return(lpValue); + } + return(nullptr); +} diff --git a/cexif.h b/cexif.h new file mode 100644 index 0000000..11a0335 --- /dev/null +++ b/cexif.h @@ -0,0 +1,870 @@ +/*! + \file cexif.h + +*/ + +#ifndef CEXIF_H +#define CEXIF_H + +#include +#include +#include +#include + +#include +#include + +#include + + +/*! + \brief + + \class cEXIFCompression cexif.h "cexif.h" +*/ +class cEXIFCompression +{ +public: + cEXIFCompression(const qint32& iID, const QString& szCompression); + + qint32 m_iID; /*!< TODO: describe */ + QString m_szCompression; /*!< TODO: describe */ +}; + +Q_DECLARE_METATYPE(cEXIFCompression*) + +/*! + \brief + + \class cEXIFCompressionList cexif.h "cexif.h" +*/ +class cEXIFCompressionList : public QList +{ +public: + cEXIFCompressionList(); + + /*! + \brief + + \fn add + \param iID + \param szCompression + \return cEXIFCompression + */ + cEXIFCompression* add(const qint32& iID, const QString& szCompression); + /*! + \brief + + \fn find + \param iID + \return cEXIFCompression + */ + cEXIFCompression* find(const qint32& iID); + + /*! + \brief + + \fn compression + \param iID + \return QString + */ + QString compression(const qint32& iID); +}; + +Q_DECLARE_METATYPE(cEXIFCompressionList) + +/*! + \brief + + \class cEXIFLightSource cexif.h "cexif.h" +*/ +class cEXIFLightSource +{ +public: + cEXIFLightSource(const qint32& iID, const QString& szLightSource); + + qint32 m_iID; /*!< TODO: describe */ + QString m_szLightSource; /*!< TODO: describe */ +}; + +Q_DECLARE_METATYPE(cEXIFLightSource*) + +/*! + \brief + + \class cEXIFLightSourceList cexif.h "cexif.h" +*/ +class cEXIFLightSourceList : public QList +{ +public: + cEXIFLightSourceList(); + + /*! + \brief + + \fn add + \param iID + \param szLightSource + \return cEXIFLightSource + */ + cEXIFLightSource* add(const qint32& iID, const QString& szLightSource); + /*! + \brief + + \fn find + \param iID + \return cEXIFLightSource + */ + cEXIFLightSource* find(const qint32& iID); + + /*! + \brief + + \fn lightSource + \param iID + \return QString + */ + QString lightSource(const qint32& iID); +}; + +/*! + \brief + + \class cEXIFFlash cexif.h "cexif.h" +*/ +class cEXIFFlash +{ +public: + cEXIFFlash(const qint32& iID, const QString& szFlash); + + qint32 m_iID; /*!< TODO: describe */ + QString m_szFlash; /*!< TODO: describe */ +}; + +Q_DECLARE_METATYPE(cEXIFFlash*) + +/*! + \brief + + \class cEXIFFlashList cexif.h "cexif.h" +*/ +class cEXIFFlashList : public QList +{ +public: + cEXIFFlashList(); + + /*! + \brief + + \fn add + \param iID + \param szFlash + \return cEXIFFlash + */ + cEXIFFlash* add(const qint32& iID, const QString& szFlash); + /*! + \brief + + \fn find + \param iID + \return cEXIFFlash + */ + cEXIFFlash* find(const qint32& iID); + + /*! + \brief + + \fn flash + \param iID + \return QString + */ + QString flash(const qint32& iID); +}; + +/** + * @brief + * + */ +class cEXIFTag +{ +public: + cEXIFTag(const qint32& iTAGID, const QString& szTAGName, const QString& szIFD, const QString& szKey, const QString& szType, const QString& szDescription); + + qint32 m_iTAGID; /*!< TODO: describe */ + QString m_szTAGName; /*!< TODO: describe */ + QString m_szIFD; /*!< TODO: describe */ + QString m_szKey; /*!< TODO: describe */ + QString m_szType; /*!< TODO: describe */ + QString m_szDescription; /*!< TODO: describe */ +}; + +Q_DECLARE_METATYPE(cEXIFTag*) + +/*! + \brief + + \class cEXIFTagList cexif.h "cexif.h" +*/ +class cEXIFTagList : public QList +{ +public: + cEXIFTagList(); + + /*! + \brief + + \fn add + \param iTAGID + \param szTAGName + \param szIFD + \param szType + \param szDescription + \return cEXIFTag + */ + cEXIFTag* add(const qint32& iTAGID, const QString& szTAGName, const QString& szIFD, const QString& szKey, const QString& szType, const QString& szDescription); + /*! + \brief + + \fn find + \param szKey + \return cEXIFTag + */ + cEXIFTag* find(const QString& szKey); + /*! + \brief + + \fn find + \param iTAGID + \param szIFD + \return cEXIFTag + */ + cEXIFTag* find(const qint32& iTAGID, const QString& szIFD); + /*! + \brief + + \fn find + \param szTAG + \param szIFD + \return cEXIFTag + */ + cEXIFTag* find(const QString& szTAG, const QString& szIFD); +}; + +/*! + \brief + +*/ +class cEXIFValue +{ +public: + cEXIFValue(cEXIFTag* lpEXIFTag); + + /*! + \brief + + \fn exifTag + \return cEXIFTag + */ + cEXIFTag* exifTag(); + /*! + \brief + + \fn setValue + \param szValue + \param iTypeId + \param iCount + */ + void setValue(const QString& szValue, qint32 iTypeId, qint32 iCount = 1); + /*! + \brief + + \fn value + \return QVariant + */ + QVariant value(); + /*! + \brief + + \fn valueList + \return QList + */ + QList valueList(); + +private: + cEXIFTag* m_lpEXIFTag; /*!< TODO: describe */ + QList m_valueList; /*!< TODO: describe */ + + /*! + \brief + + \fn convertValue + \param szValue + \param iTypeId + \return QVariant + */ + QVariant convertValue(const QString& szValue, qint32 iTypeId); +}; + +Q_DECLARE_METATYPE(cEXIFValue*) + +/*! + \brief + + \class cEXIFValueList cexif.h "cexif.h" +*/ +class cEXIFValueList : public QList +{ +public: + cEXIFValueList(); + + /*! + \brief + + \fn add + \param lpEXIFTag + \return cEXIFValue + */ + cEXIFValue* add(cEXIFTag* lpEXIFTag); + /*! + \brief + + \fn find + \param lpEXIFTag + \return cEXIFValue + */ + cEXIFValue* find(cEXIFTag* lpEXIFTag); +}; + +/** + * @brief + * + */ +class cIPTCTag +{ +public: + cIPTCTag(const qint32& iTAGID, const QString& szTAGName, const qint32& iTypeID, const QString& szDescription); + + qint32 m_iTAGID; /*!< TODO: describe */ + QString m_szTAGName; /*!< TODO: describe */ + qint32 m_iTypeID; /*!< TODO: describe */ + QString m_szDescription; /*!< TODO: describe */ +}; + +Q_DECLARE_METATYPE(cIPTCTag*) + +/*! + \brief + + \class cIPTCTagList cexif.h "cexif.h" +*/ +class cIPTCTagList : public QList +{ +public: + cIPTCTagList(); + + /*! + \brief + + \fn add + \param iTAGID + \param szTAGName + \param iTypeID + \param szDescription + \return cIPTCTag + */ + cIPTCTag* add(const qint32& iTAGID, const QString& szTAGName, const qint32& iTypeID, const QString& szDescription); + /*! + \brief + + \fn find + \param iTAGID + \return cIPTCTag + */ + cIPTCTag* find(const qint32& iTAGID); +}; + +/*! + \brief + +*/ +class cIPTCValue +{ +public: + cIPTCValue(cIPTCTag* lpIPTCTag); + + /*! + \brief + + \fn iptcTag + \return cIPTCTag + */ + cIPTCTag* iptcTag(); + /*! + \brief + + \fn setValue + \param szValue + \param iTypeId + \param iCount + */ + void setValue(const QString& szValue, qint32 iTypeId, qint32 iCount = 1); + /*! + \brief + + \fn value + \return QVariant + */ + QVariant value(); + /*! + \brief + + \fn valueList + \return QList + */ + QList valueList(); + +private: + cIPTCTag* m_lpIPTCTag; /*!< TODO: describe */ + QList m_valueList; /*!< TODO: describe */ + + /*! + \brief + + \fn convertValue + \param szValue + \param iTypeId + \return QVariant + */ + QVariant convertValue(const QString& szValue, qint32 iTypeId); +}; + +Q_DECLARE_METATYPE(cIPTCValue*) + +/*! + \brief + + \class cIPTCValueList cexif.h "cexif.h" +*/ +class cIPTCValueList : public QList +{ +public: + cIPTCValueList(); + + /*! + \brief + + \fn add + \param lpIPTCTag + \return cIPTCValue + */ + cIPTCValue* add(cIPTCTag* lpIPTCTag); + /*! + \brief + + \fn find + \param lpIPTCTag + \return cIPTCValue + */ + cIPTCValue* find(cIPTCTag* lpIPTCTag); +}; + +/** + * @brief + * + */ +class cXMPTag +{ +public: + cXMPTag(const QString& szTAGName, const qint32& iTypeID, const QString& szDescription); + + QString m_szTAGName; /*!< TODO: describe */ + qint32 m_iTypeID; /*!< TODO: describe */ + QString m_szDescription; /*!< TODO: describe */ +}; + +Q_DECLARE_METATYPE(cXMPTag*) + +/*! + \brief + + \class cXMPTagList cexif.h "cexif.h" +*/ +class cXMPTagList : public QList +{ +public: + cXMPTagList(); + + /*! + \brief + + \fn add + \param szTAGName + \param iTypeID + \param szDescription + \return cXMPTag + */ + cXMPTag* add(const QString& szTAGName, const qint32& iTypeID, const QString& szDescription); + /*! + \brief + + \fn find + \param szTAGName + \return cXMPTag + */ + cXMPTag* find(const QString& szTAGName); +}; + +/*! + \brief + +*/ +class cXMPValue +{ +public: + cXMPValue(cXMPTag* lpXMPTag); + + /*! + \brief + + \fn exifTag + \return cXMPTag + */ + cXMPTag* xmpTag(); + /*! + \brief + + \fn setValue + \param szValue + \param iTypeId + \param iCount + */ + void setValue(const QString& szValue, qint32 iTypeId, qint32 iCount = 1); + /*! + \brief + + \fn value + \return QVariant + */ + QVariant value(); + /*! + \brief + + \fn valueList + \return QList + */ + QList valueList(); + +private: + cXMPTag* m_lpXMPTag; /*!< TODO: describe */ + QList m_valueList; /*!< TODO: describe */ + + /*! + \brief + + \fn convertValue + \param szValue + \param iTypeId + \return QVariant + */ + QVariant convertValue(const QString& szValue, qint32 iTypeId); +}; + +Q_DECLARE_METATYPE(cXMPValue*) + +/*! + \brief + + \class cXMPValueList cexif.h "cexif.h" +*/ +class cXMPValueList : public QList +{ +public: + cXMPValueList(); + + /*! + \brief + + \fn add + \param lpXMPTag + \return cXMPValue + */ + cXMPValue* add(cXMPTag* lpXMPTag); + /*! + \brief + + \fn find + \param lpXMPTag + \return cXMPValue + */ + cXMPValue* find(cXMPTag* lpXMPTag); +}; + +/*! + \brief + + \class cEXIF cexif.h "cexif.h" +*/ +class cEXIF +{ +public: + cEXIF(cEXIFTagList* lpEXIFTagList, cEXIFCompressionList* lpEXIFCompressionList, cEXIFLightSourceList* lpEXIFLightSourceList, cEXIFFlashList* lpEXIFFlashList, cIPTCTagList* lpIPTCTagList, cXMPTagList* lpXMPTagList); + + /*! + \brief + + \fn fromFile + \param szFileName + \return bool + */ + bool fromFile(const QString& szFileName); + + /*! + \brief + + \fn mimeType + \return QString + */ + QString mimeType(); + /*! + \brief + + \fn imageWidth + \return qint32 + */ + qint32 imageWidth(); + /*! + \brief + + \fn imageHeight + \return qint32 + */ + qint32 imageHeight(); + /*! + \brief + + \fn imageOrientation + \return qint16 + */ + qint16 imageOrientation(); + /*! + \brief + + \fn cameraMake + \return QString + */ + QString cameraMake(); + /*! + \brief + + \fn cameraModel + \return QString + */ + QString cameraModel(); + /*! + \brief + + \fn dateTime + \return QDateTime + */ + QDateTime dateTime(); + /*! + \brief + + \fn fNumber + \return QString + */ + QString fNumber(); + /*! + \brief + + \fn iso + \return qint32 + */ + qint32 iso(); + /*! + \brief + + \fn flash + \return QString + */ + QString flash(); + /*! + \brief + + \fn flashID + \return qint32 + */ + qint32 flashID(); + /*! + \brief + + \fn focalLength + \return qreal + */ + qreal focalLength(); + /*! + \brief + + \fn lensMake + \return QString + */ + QString lensMake(); + /*! + \brief + + \fn lensModel + \return QString + */ + QString lensModel(); + /*! + \brief + + \fn exposureTime + \return QString + */ + QString exposureTime(); + /*! + \brief + + \fn exposureBias + \return qint32 + */ + qint32 exposureBias(); + /*! + \brief + + \fn exifVersion + \return QString + */ + QString exifVersion(); + /*! + \brief + + \fn dateTimeOriginal + \return QDateTime + */ + QDateTime dateTimeOriginal(); + /*! + \brief + + \fn dateTimeDigitized + \return QDateTime + */ + QDateTime dateTimeDigitized(); + /*! + \brief + + \fn whiteBalance + \return qint32 + */ + qint32 whiteBalance(); + /*! + \brief + + \fn focalLength35 + \return qreal + */ + qreal focalLength35(); + /*! + \brief + + \fn gps + \return QString + */ + QString gps(); + /*! + \brief + + \fn duration + \return QString + */ + QString duration(); + /*! + \brief + + \fn fileName + \return QString + */ + QString fileName(); + /*! + \brief + + \fn previewList + \return QList + */ + QList previewList(); + /*! + \brief + + \fn thumbnail + \return QImage + */ + QImage thumbnail(); + + /*! + \brief + + \fn copyTo + \param fileName + */ + bool copyTo(const QString& fileName); + +private: + cEXIFValueList m_exifValueList; /*!< TODO: describe */ + cIPTCValueList m_iptcValueList; /*!< TODO: describe */ + cXMPValueList m_xmpValueList; /*!< TODO: describe */ + QString m_szMimeType; /*!< TODO: describe */ + qint32 m_iWidth; /*!< TODO: describe */ + qint32 m_iHeight; /*!< TODO: describe */ + QString m_szFileName; /*!< TODO: describe */ + QList m_previewList; /*!< TODO: describe */ + QImage m_thumbnail; /*!< TODO: describe */ + + cEXIFTagList* m_lpEXIFTagList; /*!< TODO: describe */ + cEXIFCompressionList* m_lpEXIFCompressionList; /*!< TODO: describe */ + cEXIFLightSourceList* m_lpEXIFLightSourceList; /*!< TODO: describe */ + cEXIFFlashList* m_lpEXIFFlashList; /*!< TODO: describe */ + + cIPTCTagList* m_lpIPTCTagList; /*!< TODO: describe */ + + cXMPTagList* m_lpXMPTagList; /*!< TODO: describe */ + + /*! + \brief + + \fn getEXIFTag + \param iTAGID + \param szIFD + \return QVariant + */ + QVariant getEXIFTag(qint32 iTAGID, const QString& szIFD); + /*! + \brief + + \fn getIPTCTag + \param iTAGID + \return QVariant + */ + QVariant getIPTCTag(qint32 iTAGID); + /*! + \brief + + \fn getXMPTag + \param szTAGName + \return QVariant + */ + QVariant getXMPTag(const QString& szTAGName); + /*! + \brief + + \fn getTagList + \param iTAGID + \param iIFDID + \return QList + */ + QList getTagList(qint32 iTAGID, const QString& szIFD); +}; + +Q_DECLARE_METATYPE(cEXIF*) + +#endif // CEXIF_H diff --git a/cfilebrowser.cpp b/cfilebrowser.cpp new file mode 100644 index 0000000..9b0d156 --- /dev/null +++ b/cfilebrowser.cpp @@ -0,0 +1,14 @@ +#include "cfilebrowser.h" +#include "ui_cfilebrowser.h" + +cFileBrowser::cFileBrowser(QWidget *parent) : + QWidget(parent), + ui(new Ui::cFileBrowser) +{ + ui->setupUi(this); +} + +cFileBrowser::~cFileBrowser() +{ + delete ui; +} diff --git a/cfilebrowser.h b/cfilebrowser.h new file mode 100644 index 0000000..0d70e42 --- /dev/null +++ b/cfilebrowser.h @@ -0,0 +1,22 @@ +#ifndef CFILEBROWSER_H +#define CFILEBROWSER_H + +#include + +namespace Ui { +class cFileBrowser; +} + +class cFileBrowser : public QWidget +{ + Q_OBJECT + +public: + explicit cFileBrowser(QWidget *parent = nullptr); + ~cFileBrowser(); + +private: + Ui::cFileBrowser *ui; +}; + +#endif // CFILEBROWSER_H diff --git a/cfilebrowser.ui b/cfilebrowser.ui new file mode 100644 index 0000000..c52210f --- /dev/null +++ b/cfilebrowser.ui @@ -0,0 +1,21 @@ + + + + + cFileBrowser + + + + 0 + 0 + 400 + 300 + + + + Form + + + + + diff --git a/cfileviewer.cpp b/cfileviewer.cpp new file mode 100644 index 0000000..40f4614 --- /dev/null +++ b/cfileviewer.cpp @@ -0,0 +1,14 @@ +#include "cfileviewer.h" +#include "ui_cfileviewer.h" + +cFileViewer::cFileViewer(QWidget *parent) : + QWidget(parent), + ui(new Ui::cFileViewer) +{ + ui->setupUi(this); +} + +cFileViewer::~cFileViewer() +{ + delete ui; +} diff --git a/cfileviewer.h b/cfileviewer.h new file mode 100644 index 0000000..6b20d77 --- /dev/null +++ b/cfileviewer.h @@ -0,0 +1,22 @@ +#ifndef CFILEVIEWER_H +#define CFILEVIEWER_H + +#include + +namespace Ui { +class cFileViewer; +} + +class cFileViewer : public QWidget +{ + Q_OBJECT + +public: + explicit cFileViewer(QWidget *parent = nullptr); + ~cFileViewer(); + +private: + Ui::cFileViewer *ui; +}; + +#endif // CFILEVIEWER_H diff --git a/cfileviewer.ui b/cfileviewer.ui new file mode 100644 index 0000000..7322fc3 --- /dev/null +++ b/cfileviewer.ui @@ -0,0 +1,21 @@ + + + + + cFileViewer + + + + 0 + 0 + 400 + 300 + + + + Form + + + + + diff --git a/cimage.cpp b/cimage.cpp new file mode 100644 index 0000000..42f7645 --- /dev/null +++ b/cimage.cpp @@ -0,0 +1,428 @@ +/*! + \file cimage.cpp + +*/ + +#include "cimage.h" + +#include "opencv2/imgproc/types_c.h" +#include "opencv2/imgproc.hpp" + +#include +#include + +#include +#include + + +cImage::cImage() : + QImage(), + m_isChromatic(true), + m_camType(camera_unknown), + m_raw(false) +{ +} + +cImage::cImage(const QSize &size, QImage::Format format) : + QImage(size, format), + m_isChromatic(true), + m_camType(camera_unknown), + m_raw(false) +{ +} + +cImage::cImage(int width, int height, QImage::Format format) : + QImage(width, height, format), + m_isChromatic(true), + m_camType(camera_unknown), + m_raw(false) +{ +} + +cImage::cImage(uchar *data, int width, int height, QImage::Format format, QImageCleanupFunction cleanupFunction, void *cleanupInfo) : + QImage(data, width, height, format, cleanupFunction, cleanupInfo), + m_isChromatic(true), + m_camType(camera_unknown), + m_raw(false) +{ +} + +cImage::cImage(const uchar *data, int width, int height, QImage::Format format, QImageCleanupFunction cleanupFunction, void *cleanupInfo) : + QImage(data, width, height, format, cleanupFunction, cleanupInfo), + m_isChromatic(true), + m_camType(camera_unknown), + m_raw(false) +{ +} + +cImage::cImage(uchar *data, int width, int height, int bytesPerLine, QImage::Format format, QImageCleanupFunction cleanupFunction, void *cleanupInfo) : + QImage(data, width, height, bytesPerLine, format, cleanupFunction, cleanupInfo), + m_isChromatic(true), + m_camType(camera_unknown), + m_raw(false) +{ +} + +cImage::cImage(const uchar *data, int width, int height, int bytesPerLine, QImage::Format format, QImageCleanupFunction cleanupFunction, void *cleanupInfo) : + QImage(data, width, height, bytesPerLine, format, cleanupFunction, cleanupInfo), + m_isChromatic(true), + m_camType(camera_unknown), + m_raw(false) +{ +} + +cImage::cImage(const QString &fileName, const char *format) : + QImage(), + m_isChromatic(true), + m_camType(camera_unknown), + m_raw(false) +{ + load(fileName, format); +} + +cImage::cImage(const QImage &image) : + QImage(image), + m_isChromatic(true), + m_camType(camera_unknown), + m_raw(false) +{ +} + +cImage::cImage(QImage &&other) : + QImage(other), + m_isChromatic(true), + m_camType(camera_unknown), + m_raw(false) +{ +} + +bool cImage::load(const QString &fileName, const char *format) +{ + QFileInfo fileInfo(fileName); + + if(!fileInfo.suffix().compare("NEF", Qt::CaseInsensitive)) + return(loadRAW(fileName)); + + if(QImage::load(fileName, format)) + return(true); + + return(loadRAW(fileName)); +} + +bool cImage::openBuffer(const QString &fileName, const QSharedPointer& ba, LibRaw& iProcessor) +{ + int error = LIBRAW_DATA_ERROR; + QFileInfo fi(fileName); + + if(fi.suffix().contains("iiq", Qt::CaseInsensitive) || !ba || ba->isEmpty()) + error = iProcessor.open_file(fileName.toStdString().c_str()); + else + { + if(ba->isEmpty() || ba->size() < 100) + return(false); + + error = iProcessor.open_buffer((void*)ba->constData(), static_cast(ba->size())); + } + + return (error == LIBRAW_SUCCESS); +} + +void cImage::detectSpecialCamera(const LibRaw & iProcessor) +{ + if(QString(iProcessor.imgdata.idata.model) == "IQ260 Achromatic") + m_isChromatic = false; + + if (QString(iProcessor.imgdata.idata.model).contains("IQ260")) + m_camType = camera_iiq; + else if (!QString(iProcessor.imgdata.idata.make).compare("Canon", Qt::CaseInsensitive)) + m_camType = camera_canon; +} + +cv::Mat cImage::demosaic(LibRaw & iProcessor) +{ + cv::Mat rawMat = cv::Mat(iProcessor.imgdata.sizes.height, iProcessor.imgdata.sizes.width, CV_16UC1); + double dynamicRange = static_cast(iProcessor.imgdata.color.maximum - iProcessor.imgdata.color.black); + + // normalize all image values + for(int rIdx = 0; rIdx < rawMat.rows; rIdx++) + { + unsigned short *ptrRaw = rawMat.ptr(rIdx); + + for (int cIdx = 0; cIdx < rawMat.cols; cIdx++) + { + int colIdx = iProcessor.COLOR(rIdx, cIdx); + double val = static_cast(iProcessor.imgdata.image[(rawMat.cols*rIdx) + cIdx][colIdx]); + + // normalize the value w.r.t the black point defined + val = (val - iProcessor.imgdata.color.black) / dynamicRange; + ptrRaw[cIdx] = clip(val * USHRT_MAX); // for conversion to 16U + } + } + + // no demosaicing + if(m_isChromatic) + { + unsigned long type = static_cast(iProcessor.imgdata.idata.filters); + type = type & 255; + + cv::Mat rgbImg; + + //define bayer pattern + if(type == 180) + cvtColor(rawMat, rgbImg, CV_BayerBG2RGB); //bitmask 10 11 01 00 -> 3(G) 2(B) 1(G) 0(R) -> RG RG RG + else if(type == 30) + cvtColor(rawMat, rgbImg, CV_BayerRG2RGB); //bitmask 00 01 11 10 -> 0 1 3 2 + else if(type == 225) + cvtColor(rawMat, rgbImg, CV_BayerGB2RGB); //bitmask 11 10 00 01 + else if(type == 75) + cvtColor(rawMat, rgbImg, CV_BayerGR2RGB); //bitmask 01 00 10 11 + else + { + qWarning() << "Wrong Bayer Pattern (not BG, RG, GB, GR)\n"; + return cv::Mat(); + } + + rawMat = rgbImg; + } + + // 16U (1 or 3 channeled) Mat + return(rawMat); +} + +cv::Mat cImage::prepareImg(const LibRaw & iProcessor) +{ + cv::Mat rawMat = cv::Mat(iProcessor.imgdata.sizes.height, iProcessor.imgdata.sizes.width, CV_16UC3, cv::Scalar(0)); + double dynamicRange = static_cast(iProcessor.imgdata.color.maximum - iProcessor.imgdata.color.black); + + // normalization function + auto normalize = [&](double val) + { + val = (val - iProcessor.imgdata.color.black) / dynamicRange; + return clip(val * USHRT_MAX); + }; + + for (int rIdx = 0; rIdx < rawMat.rows; rIdx++) + { + unsigned short *ptrI = rawMat.ptr(rIdx); + + for (int cIdx = 0; cIdx < rawMat.cols; cIdx++) + { + + *ptrI = normalize(iProcessor.imgdata.image[rawMat.cols*rIdx + cIdx][0]); + ptrI++; + *ptrI = normalize(iProcessor.imgdata.image[rawMat.cols*rIdx + cIdx][1]); + ptrI++; + *ptrI = normalize(iProcessor.imgdata.image[rawMat.cols*rIdx + cIdx][2]); + ptrI++; + } + } + + return rawMat; +} + +void cImage::whiteBalance(const LibRaw & iProcessor, cv::Mat & img) +{ + // white balance must not be empty at this point + cv::Mat wb = whiteMultipliers(iProcessor); + const float* wbp = wb.ptr(); + assert(wb.cols == 4); + + for(int rIdx = 0; rIdx < img.rows; rIdx++) + { + unsigned short *ptr = img.ptr(rIdx); + + for (int cIdx = 0; cIdx < img.cols; cIdx++) + { + //apply white balance correction + unsigned short r = clip(*ptr * wbp[0]); + unsigned short g = clip(*(ptr+1) * wbp[1]); + unsigned short b = clip(*(ptr+2) * wbp[2]); + + //apply color correction + int cr = qRound(iProcessor.imgdata.color.rgb_cam[0][0] * r + + iProcessor.imgdata.color.rgb_cam[0][1] * g + + iProcessor.imgdata.color.rgb_cam[0][2] * b); + int cg = qRound(iProcessor.imgdata.color.rgb_cam[1][0] * r + + iProcessor.imgdata.color.rgb_cam[1][1] * g + + iProcessor.imgdata.color.rgb_cam[1][2] * b); + int cb = qRound(iProcessor.imgdata.color.rgb_cam[2][0] * r + + iProcessor.imgdata.color.rgb_cam[2][1] * g + + iProcessor.imgdata.color.rgb_cam[2][2] * b); + + // clip & save color corrected values + *ptr = clip(cr); + ptr++; + *ptr = clip(cg); + ptr++; + *ptr = clip(cb); + ptr++; + } + } +} + +cv::Mat cImage::whiteMultipliers(const LibRaw & iProcessor) +{ + // get camera white balance multipliers + cv::Mat wm(1, 4, CV_32FC1); + + float* wmp = wm.ptr(); + + for(int idx = 0; idx < wm.cols; idx++) + wmp[idx] = iProcessor.imgdata.color.cam_mul[idx]; + + if(wmp[3] == 0.0f) + wmp[3] = wmp[1]; // take green (usually its RGBG) + + // normalize white balance multipliers + float w = static_cast(cv::sum(wm)[0] / 4.0f); + float maxW = 1.0f; + + //clipping according the camera model + //if w > 2.0 maxW is 256, otherwise 512 + //tested empirically + //check if it can be defined by some metadata settings? + if(w > 2.0f) + maxW = 255.0f; + if(w > 2.0f && m_camType == camera_canon) + maxW = 511.0f; // some cameras would even need ~800 - why? + + //normalize white point + wm /= maxW; + + // 1 x 4 32FC1 white balance vector + return wm; +} + +void cImage::gammaCorrection(const LibRaw & iProcessor, cv::Mat& img) +{ + // white balance must not be empty at this point + cv::Mat gt = gammaTable(iProcessor); + const unsigned short* gammaLookup = gt.ptr(); + assert(gt.cols == USHRT_MAX); + + for(int rIdx = 0; rIdx < img.rows; rIdx++) + { + unsigned short *ptr = img.ptr(rIdx); + + for(int cIdx = 0; cIdx < img.cols * img.channels(); cIdx++) + { + // values close to 0 are treated linear + if (ptr[cIdx] <= 5) // 0.018 * 255 + ptr[cIdx] = static_cast(qRound(ptr[cIdx] * static_cast(iProcessor.imgdata.params.gamm[1]) / 255.0)); + else + ptr[cIdx] = gammaLookup[ptr[cIdx]]; + } + } + +} + +cv::Mat cImage::gammaTable(const LibRaw & iProcessor) +{ + // OK this is an instance of reverse engineering: + // we found out that the values of (at least) the PhaseOne's achromatic back have to be doubled + // our images are no close to what their software (Capture One does) - only the gamma correction + // seems to be slightly different... -> now we can load compressed IIQs that are not supported by PS : ) + double cameraHackMlp = (QString(iProcessor.imgdata.idata.model) == "IQ260 Achromatic") ? 2.0 : 1.0; + + //read gamma value and create gamma table + double gamma = static_cast(iProcessor.imgdata.params.gamm[0]); + + cv::Mat gmt(1, USHRT_MAX, CV_16UC1); + unsigned short* gmtp = gmt.ptr(); + + for (int idx = 0; idx < gmt.cols; idx++) { + gmtp[idx] = clip(qRound((1.099*std::pow(static_cast(idx) / USHRT_MAX, gamma) - 0.099) * 255 * cameraHackMlp)); + } + + // a 1 x 65535 U16 gamma table + return gmt; +} + +QImage cImage::raw2Img(const LibRaw & iProcessor, cv::Mat & img) +{ + //check the pixel aspect ratio of the raw image + if(iProcessor.imgdata.sizes.pixel_aspect != 1.0f) + cv::resize(img, img, cv::Size(), static_cast(iProcessor.imgdata.sizes.pixel_aspect), 1.0f); + + // revert back to 8-bit image + img.convertTo(img, CV_8U); + + // TODO: for now - fix this! + if(img.channels() == 1) + cv::cvtColor(img, img, CV_GRAY2RGB); + + return(mat2QImage(img)); +} + +QImage cImage::mat2QImage(cv::Mat img) +{ + QImage qImg; + + // since Mat header is copied, a new buffer should be allocated (check this!) + if(img.depth() == CV_32F) + img.convertTo(img, CV_8U, 255); + + if(img.type() == CV_8UC1) + qImg = QImage(img.data, static_cast(img.cols), static_cast(img.rows), static_cast(img.step), QImage::Format_Indexed8); // opencv uses size_t for scaling in x64 applications + if(img.type() == CV_8UC3) + qImg = QImage(img.data, static_cast(img.cols), static_cast(img.rows), static_cast(img.step), QImage::Format_RGB888); + if(img.type() == CV_8UC4) + qImg = QImage(img.data, static_cast(img.cols), static_cast(img.rows), static_cast(img.step), QImage::Format_ARGB32); + + qImg = qImg.copy(); + + return qImg; +} + +bool cImage::loadRAW(const QString &fileName) +{ + QSharedPointer ba; + LibRaw iProcessor; + + if(!openBuffer(fileName, ba, iProcessor)) + { + qDebug() << "could not open buffer"; + return(false); + } + + detectSpecialCamera(iProcessor); + + int error = iProcessor.unpack(); + + if(strcmp(iProcessor.version(), "0.13.5") != 0) // fixes a bug specific to libraw 13 - version call is UNTESTED + iProcessor.raw2image(); + + if(error != LIBRAW_SUCCESS) + return(false); + + cv::Mat rawMat; + + if(iProcessor.imgdata.idata.filters) + rawMat = demosaic(iProcessor); + else + rawMat = prepareImg(iProcessor); + + // color correction + white balance + if(m_isChromatic) + whiteBalance(iProcessor, rawMat); + + gammaCorrection(iProcessor, rawMat); + + // reduce color noise +// if (DkSettingsManager::param().resources().filterRawImages && mIsChromatic) +// reduceColorNoise(iProcessor, rawMat); + + *this = raw2Img(iProcessor, rawMat); + + iProcessor.recycle(); + rawMat.release(); + + m_raw = true; + + return(true); +} + +bool cImage::isRaw() +{ + return(m_raw); +} diff --git a/cimage.h b/cimage.h new file mode 100644 index 0000000..103309b --- /dev/null +++ b/cimage.h @@ -0,0 +1,313 @@ +/*! + \file cimage.h + +*/ + +#ifndef CIMAGE_H +#define CIMAGE_H + + +#include "libraw/libraw.h" +#include + +#include + + +/*! + \brief + + \class cImage cimage.h "cimage.h" +*/ +class cImage : public QImage +{ +public: + cImage(); + /*! + \brief + + \fn cImage + \param size + \param format + */ + cImage(const QSize &size, QImage::Format format); + /*! + \brief + + \fn cImage + \param width + \param height + \param format + */ + cImage(int width, int height, QImage::Format format); + /*! + \brief + + \fn cImage + \param data + \param width + \param height + \param format + \param cleanupFunction + \param cleanupInfo + */ + cImage(uchar *data, int width, int height, QImage::Format format, QImageCleanupFunction cleanupFunction = nullptr, void *cleanupInfo = nullptr); + /*! + \brief + + \fn cImage + \param data + \param width + \param height + \param format + \param cleanupFunction + \param cleanupInfo + */ + cImage(const uchar *data, int width, int height, QImage::Format format, QImageCleanupFunction cleanupFunction = nullptr, void *cleanupInfo = nullptr); + /*! + \brief + + \fn cImage + \param data + \param width + \param height + \param bytesPerLine + \param format + \param cleanupFunction + \param cleanupInfo + */ + cImage(uchar *data, int width, int height, int bytesPerLine, QImage::Format format, QImageCleanupFunction cleanupFunction = nullptr, void *cleanupInfo = nullptr); + /*! + \brief + + \fn cImage + \param data + \param width + \param height + \param bytesPerLine + \param format + \param cleanupFunction + \param cleanupInfo + */ + cImage(const uchar *data, int width, int height, int bytesPerLine, QImage::Format format, QImageCleanupFunction cleanupFunction = nullptr, void *cleanupInfo = nullptr); + /*! + \brief + + \fn cImage + \param fileName + \param format + */ + cImage(const QString &fileName, const char *format = nullptr); + /*! + \brief + + \fn cImage + \param image + */ + cImage(const QImage &image); + /*! + \brief + + \fn cImage + \param other + */ + cImage(QImage &&other); + + /*! + \brief + + \fn load + \param fileName + \param format + \return bool + */ + bool load(const QString &fileName, const char *format = nullptr); + + /*! + \brief + + \fn isRaw + \return bool + */ + bool isRaw(); + +protected: + /*! + \brief + + \enum Cam + */ + enum Cam + { + camera_unknown = 0, + camera_iiq, + camera_canon, + camera_end + }; + + template + /*! + \brief + + \fn clip + \param val + \return num + */ + num clip(float val) const + { + int vr = qRound(val); + + // trust me I'm an engineer @ -2 + // with -2 we do not get pink in oversaturated areas + if (vr > std::numeric_limits::max()) + vr = std::numeric_limits::max()-2; + if (vr < 0) + vr = 0; + + return static_cast(vr); + } + + template + /*! + \brief + + \fn clip + \param val + \return num + */ + num clip(double val) const + { + int vr = qRound(val); + + // trust me I'm an engineer @ -2 + // with -2 we do not get pink in oversaturated areas + if (vr > std::numeric_limits::max()) + vr = std::numeric_limits::max()-2; + if (vr < 0) + vr = 0; + + return static_cast(vr); + } + + template + /*! + \brief + + \fn clip + \param val + \return num + */ + num clip(int val) const + { + int vr = qRound(static_cast(val)); + + // trust me I'm an engineer @ -2 + // with -2 we do not get pink in oversaturated areas + if (vr > std::numeric_limits::max()) + vr = std::numeric_limits::max()-2; + if (vr < 0) + vr = 0; + + return static_cast(vr); + } + +private: + bool m_isChromatic; /*!< TODO: describe */ + Cam m_camType; /*!< TODO: describe */ + bool m_raw; /*!< TODO: describe */ + + /*! + \brief + + \fn loadRAW + \param fileName + \return bool + */ + bool loadRAW(const QString &fileName); + + /*! + \brief + + \fn openBuffer + \param fileName + \param ba + \param iProcessor + \return bool + */ + bool openBuffer(const QString &fileName, const QSharedPointer& ba, LibRaw& iProcessor); + /*! + \brief + + \fn detectSpecialCamera + \param iProcessor + */ + void detectSpecialCamera(const LibRaw & iProcessor); + + /*! + \brief + + \fn demosaic + \param iProcessor + \return cv::Mat + */ + cv::Mat demosaic(LibRaw & iProcessor); + /*! + \brief + + \fn prepareImg + \param iProcessor + \return cv::Mat + */ + cv::Mat prepareImg(const LibRaw & iProcessor); + /*! + \brief + + \fn whiteBalance + \param iProcessor + \param img + */ + void whiteBalance(const LibRaw & iProcessor, cv::Mat & img); + /*! + \brief + + \fn whiteMultipliers + \param iProcessor + \return cv::Mat + */ + cv::Mat whiteMultipliers(const LibRaw & iProcessor); + /*! + \brief + + \fn gammaCorrection + \param iProcessor + \param img + */ + void gammaCorrection(const LibRaw & iProcessor, cv::Mat& img); + /*! + \brief + + \fn gammaTable + \param iProcessor + \return cv::Mat + */ + cv::Mat gammaTable(const LibRaw & iProcessor); + + /*! + \brief + + \fn raw2Img + \param iProcessor + \param img + \return QImage + */ + QImage raw2Img(const LibRaw & iProcessor, cv::Mat & img); + /*! + \brief + + \fn mat2QImage + \param img + \return QImage + */ + QImage mat2QImage(cv::Mat img); +}; + +#endif // CIMAGE_H