File indexing completed on 2024-04-28 15:39:57
0001 // SPDX-FileCopyrightText: 2003-2020 The KPhotoAlbum Development Team 0002 // SPDX-FileCopyrightText: 2021 Johannes Zarl-Zierl <johannes@zarl-zierl.at> 0003 // SPDX-FileCopyrightText: 2022 Johannes Zarl-Zierl <johannes@zarl-zierl.at> 0004 // 0005 // SPDX-License-Identifier: GPL-2.0-or-later 0006 0007 #include "ImageInfo.h" 0008 0009 #include "CategoryCollection.h" 0010 #include "FileInfo.h" 0011 #include "ImageDB.h" 0012 #include "MemberMap.h" 0013 0014 #include <kpabase/FileNameUtil.h> 0015 #include <kpabase/Logging.h> 0016 #include <kpabase/SettingsData.h> 0017 #include <kpabase/StringSet.h> 0018 #include <kpaexif/Database.h> 0019 #include <kpaexif/DatabaseElement.h> 0020 0021 #include <QFile> 0022 #include <QFileInfo> 0023 #include <QImageReader> 0024 #include <QStringList> 0025 0026 using namespace DB; 0027 0028 ImageInfo::ImageInfo() 0029 : m_angle(0) 0030 , m_imageOnDisk(OnDisk::Unchecked) 0031 , m_null(true) 0032 , m_type(anyMediaType) 0033 , m_rating(-1) 0034 , m_stackId(0) 0035 , m_stackOrder(0) 0036 , m_videoLength(-1) 0037 , m_isMatched(false) 0038 , m_matchGeneration(-1) 0039 , m_locked(false) 0040 , m_dirty(false) 0041 { 0042 } 0043 0044 ImageInfo::ImageInfo(const DB::FileName &fileName, MediaType type, FileInformation infoMode) 0045 : m_imageOnDisk(YesOnDisk) 0046 , m_null(false) 0047 , m_size(-1, -1) 0048 , m_type(type) 0049 , m_rating(-1) 0050 , m_stackId(0) 0051 , m_stackOrder(0) 0052 , m_videoLength(-1) 0053 , m_isMatched(false) 0054 , m_matchGeneration(-1) 0055 , m_locked(false) 0056 { 0057 QFileInfo fi(fileName.absolute()); 0058 m_label = fi.completeBaseName(); 0059 m_angle = 0; 0060 0061 setFileName(fileName); 0062 0063 // Read Exif information 0064 if (infoMode != FileInformation::Ignore) { 0065 ExifMode mode = EXIFMODE_INIT; 0066 if (infoMode == FileInformation::ReadAndUpdateExifDB) 0067 mode &= ~EXIFMODE_DATABASE_UPDATE; 0068 readExif(fileName, mode); 0069 } 0070 0071 m_dirty = false; 0072 } 0073 0074 ImageInfo::ImageInfo(const ImageInfo &other) 0075 : QSharedData(other) 0076 { 0077 *this = other; 0078 } 0079 0080 void ImageInfo::setIsMatched(bool isMatched) 0081 { 0082 m_isMatched = isMatched; 0083 } 0084 0085 bool ImageInfo::isMatched() const 0086 { 0087 return m_isMatched; 0088 } 0089 0090 void ImageInfo::setMatchGeneration(int matchGeneration) 0091 { 0092 m_matchGeneration = matchGeneration; 0093 } 0094 0095 int ImageInfo::matchGeneration() const 0096 { 0097 return m_matchGeneration; 0098 } 0099 0100 void ImageInfo::setLabel(const QString &desc) 0101 { 0102 if (desc != m_label) 0103 markDirty(); 0104 m_label = desc; 0105 } 0106 0107 QString ImageInfo::label() const 0108 { 0109 return m_label; 0110 } 0111 0112 void ImageInfo::setDescription(const QString &desc) 0113 { 0114 if (desc != m_description) 0115 markDirty(); 0116 m_description = desc.trimmed(); 0117 } 0118 0119 QString ImageInfo::description() const 0120 { 0121 return m_description; 0122 } 0123 0124 void ImageInfo::setCategoryInfo(const QString &key, const StringSet &value) 0125 { 0126 // Don't check if really changed, because it's too slow. 0127 markDirty(); 0128 m_categoryInfomation[key] = value; 0129 } 0130 0131 bool ImageInfo::hasCategoryInfo(const QString &key, const QString &value) const 0132 { 0133 return m_categoryInfomation[key].contains(value); 0134 } 0135 0136 bool DB::ImageInfo::hasCategoryInfo(const QString &key, const StringSet &values) const 0137 { 0138 return values.intersects(m_categoryInfomation[key]); 0139 } 0140 0141 StringSet ImageInfo::itemsOfCategory(const QString &key) const 0142 { 0143 return m_categoryInfomation[key]; 0144 } 0145 0146 void ImageInfo::renameItem(const QString &category, const QString &oldValue, const QString &newValue) 0147 { 0148 if (m_taggedAreas.contains(category)) { 0149 if (m_taggedAreas[category].contains(oldValue)) { 0150 m_taggedAreas[category][newValue] = m_taggedAreas[category][oldValue]; 0151 m_taggedAreas[category].remove(oldValue); 0152 } 0153 } 0154 0155 StringSet &set = m_categoryInfomation[category]; 0156 StringSet::iterator it = set.find(oldValue); 0157 if (it != set.end()) { 0158 markDirty(); 0159 set.erase(it); 0160 set.insert(newValue); 0161 } 0162 } 0163 0164 DB::FileName ImageInfo::fileName() const 0165 { 0166 return m_fileName; 0167 } 0168 0169 void ImageInfo::setFileName(const DB::FileName &fileName) 0170 { 0171 if (fileName != m_fileName) 0172 markDirty(); 0173 m_fileName = fileName; 0174 0175 m_imageOnDisk = Unchecked; 0176 DB::CategoryPtr folderCategory = DB::ImageDB::instance()->categoryCollection()->categoryForSpecial(DB::Category::FolderCategory); 0177 if (folderCategory) { 0178 DB::MemberMap &map = DB::ImageDB::instance()->memberMap(); 0179 createFolderCategoryItem(folderCategory, map); 0180 // ImageDB::instance()->setMemberMap( map ); 0181 } 0182 } 0183 0184 void ImageInfo::rotate(int degrees, RotationMode mode) 0185 { 0186 // ensure positive degrees: 0187 degrees += 360; 0188 degrees = degrees % 360; 0189 if (degrees == 0) 0190 return; 0191 0192 markDirty(); 0193 m_angle = (m_angle + degrees) % 360; 0194 0195 if (degrees == 90 || degrees == 270) { 0196 m_size.transpose(); 0197 } 0198 0199 // the AnnotationDialog manages this by itself and sets RotateImageInfoOnly: 0200 if (mode == RotateImageInfoAndAreas) { 0201 for (auto &areasOfCategory : m_taggedAreas) { 0202 for (auto &area : areasOfCategory) { 0203 QRect rotatedArea; 0204 0205 // parameter order for QRect::setCoords: 0206 // setCoords( left, top, right, bottom ) 0207 // keep in mind that _size is already transposed 0208 switch (degrees) { 0209 case 90: 0210 rotatedArea.setCoords( 0211 m_size.width() - area.bottom(), 0212 area.left(), 0213 m_size.width() - area.top(), 0214 area.right()); 0215 break; 0216 case 180: 0217 rotatedArea.setCoords( 0218 m_size.width() - area.right(), 0219 m_size.height() - area.bottom(), 0220 m_size.width() - area.left(), 0221 m_size.height() - area.top()); 0222 break; 0223 case 270: 0224 rotatedArea.setCoords( 0225 area.top(), 0226 m_size.height() - area.right(), 0227 area.bottom(), 0228 m_size.height() - area.left()); 0229 break; 0230 default: 0231 // degrees==0; "odd" values won't happen. 0232 rotatedArea = area; 0233 break; 0234 } 0235 0236 // update _taggedAreas[category][tag]: 0237 area = rotatedArea; 0238 } 0239 } 0240 } 0241 } 0242 0243 int ImageInfo::angle() const 0244 { 0245 return m_angle; 0246 } 0247 0248 void ImageInfo::setAngle(int angle) 0249 { 0250 if (angle != m_angle) 0251 markDirty(); 0252 m_angle = angle; 0253 } 0254 0255 short ImageInfo::rating() const 0256 { 0257 return m_rating; 0258 } 0259 0260 void ImageInfo::setRating(short rating) 0261 { 0262 Q_ASSERT((rating >= 0 && rating <= 10) || rating == -1); 0263 0264 if (rating > 10) 0265 rating = 10; 0266 if (rating < -1) 0267 rating = -1; 0268 if (m_rating != rating) 0269 markDirty(); 0270 0271 m_rating = rating; 0272 } 0273 0274 DB::StackID ImageInfo::stackId() const 0275 { 0276 return m_stackId; 0277 } 0278 0279 void ImageInfo::setStackId(const DB::StackID stackId) 0280 { 0281 if (stackId != m_stackId) 0282 markDirty(); 0283 m_stackId = stackId; 0284 } 0285 0286 unsigned int ImageInfo::stackOrder() const 0287 { 0288 return m_stackOrder; 0289 } 0290 0291 void ImageInfo::setStackOrder(const unsigned int stackOrder) 0292 { 0293 if (stackOrder != m_stackOrder) 0294 markDirty(); 0295 m_stackOrder = stackOrder; 0296 } 0297 0298 void ImageInfo::setVideoLength(int length) 0299 { 0300 if (m_videoLength != length) 0301 markDirty(); 0302 m_videoLength = length; 0303 } 0304 0305 int ImageInfo::videoLength() const 0306 { 0307 return m_videoLength; 0308 } 0309 0310 void ImageInfo::setDate(const ImageDate &date) 0311 { 0312 if (date != m_date) 0313 markDirty(); 0314 m_date = date; 0315 } 0316 0317 ImageDate &ImageInfo::date() 0318 { 0319 return m_date; 0320 } 0321 0322 ImageDate ImageInfo::date() const 0323 { 0324 return m_date; 0325 } 0326 0327 bool ImageInfo::operator!=(const ImageInfo &other) const 0328 { 0329 return !(*this == other); 0330 } 0331 0332 bool ImageInfo::operator==(const ImageInfo &other) const 0333 { 0334 bool changed = (m_fileName != other.m_fileName || m_label != other.m_label || (!m_description.isEmpty() && !other.m_description.isEmpty() && m_description != other.m_description) || // one might be isNull. 0335 m_date != other.m_date || m_angle != other.m_angle || m_rating != other.m_rating || (m_stackId != other.m_stackId || !((m_stackId == 0) ? true : (m_stackOrder == other.m_stackOrder)))); 0336 if (!changed) { 0337 QStringList keys = DB::ImageDB::instance()->categoryCollection()->categoryNames(); 0338 for (QStringList::ConstIterator it = keys.constBegin(); it != keys.constEnd(); ++it) 0339 changed |= m_categoryInfomation[*it] != other.m_categoryInfomation[*it]; 0340 } 0341 return !changed; 0342 } 0343 0344 void ImageInfo::renameCategory(const QString &oldName, const QString &newName) 0345 { 0346 markDirty(); 0347 0348 m_categoryInfomation[newName] = m_categoryInfomation[oldName]; 0349 m_categoryInfomation.remove(oldName); 0350 0351 m_taggedAreas[newName] = m_taggedAreas[oldName]; 0352 m_taggedAreas.remove(oldName); 0353 } 0354 0355 void ImageInfo::setMD5Sum(const MD5 &sum, bool storeEXIF) 0356 { 0357 if (sum != m_md5sum) { 0358 // if we make a QObject derived class out of imageinfo, we might invalidate thumbnails from here 0359 0360 // file changed -> reload/invalidate metadata: 0361 ExifMode mode = EXIFMODE_ORIENTATION | EXIFMODE_DATABASE_UPDATE; 0362 // fuzzy dates are usually set for a reason 0363 if (!m_date.isFuzzy()) 0364 mode |= EXIFMODE_DATE; 0365 // FIXME (ZaJ): the "right" thing to do would be to update the description 0366 // - if it is currently empty (done.) 0367 // - if it has been set from the exif info and not been changed (TODO) 0368 if (m_description.isEmpty()) 0369 mode |= EXIFMODE_DESCRIPTION; 0370 0371 if (!storeEXIF) 0372 mode &= ~EXIFMODE_DATABASE_UPDATE; 0373 readExif(fileName(), mode); 0374 if (storeEXIF) { 0375 // Size isn't really EXIF, but this is the most obvious 0376 // place to extract it 0377 QImageReader reader(m_fileName.absolute()); 0378 if (reader.canRead()) { 0379 m_size = reader.size(); 0380 if (m_angle == 90 || m_angle == 270) 0381 m_size.transpose(); 0382 } 0383 } 0384 0385 // FIXME (ZaJ): it *should* make sense to set the ImageDB::md5Map() from here, but I want 0386 // to make sure I fully understand everything first... 0387 // this could also be done as signal md5Changed(old,new) 0388 0389 // image size is invalidated by the thumbnail builder, if needed 0390 0391 markDirty(); 0392 } 0393 m_md5sum = sum; 0394 } 0395 0396 void ImageInfo::setLocked(bool locked) 0397 { 0398 m_locked = locked; 0399 } 0400 0401 bool ImageInfo::isLocked() const 0402 { 0403 return m_locked; 0404 } 0405 0406 void ImageInfo::readExif(const DB::FileName &fullPath, DB::ExifMode mode) 0407 { 0408 DB::FileInfo exifInfo = DB::FileInfo::read(fullPath, mode); 0409 0410 // Date 0411 if (updateDateInformation(mode)) { 0412 const ImageDate newDate(exifInfo.dateTime()); 0413 setDate(newDate); 0414 } 0415 0416 // Orientation 0417 if ((mode & EXIFMODE_ORIENTATION) && Settings::SettingsData::instance()->useEXIFRotate()) { 0418 setAngle(exifInfo.angle()); 0419 } 0420 0421 // Description 0422 if ((mode & EXIFMODE_DESCRIPTION) && Settings::SettingsData::instance()->useEXIFComments()) { 0423 bool doSetDescription = true; 0424 QString desc = exifInfo.description(); 0425 0426 if (Settings::SettingsData::instance()->stripEXIFComments()) { 0427 for (const auto &ignoredComment : Settings::SettingsData::instance()->EXIFCommentsToStrip()) { 0428 if (desc == ignoredComment) { 0429 doSetDescription = false; 0430 break; 0431 } 0432 } 0433 } 0434 0435 if (doSetDescription) { 0436 setDescription(desc); 0437 } 0438 } 0439 0440 // Database update 0441 if (mode & EXIFMODE_DATABASE_UPDATE) { 0442 DB::ImageDB::instance()->exifDB()->add(exifInfo.getFileName(), exifInfo.getExifData()); 0443 #ifdef HAVE_MARBLE 0444 // GPS coords might have changed... 0445 m_coordsIsSet = false; 0446 #endif 0447 } 0448 } 0449 0450 QStringList ImageInfo::availableCategories() const 0451 { 0452 return m_categoryInfomation.keys(); 0453 } 0454 0455 QSize ImageInfo::size() const 0456 { 0457 return m_size; 0458 } 0459 0460 void ImageInfo::setSize(const QSize &size) 0461 { 0462 if (size != m_size) 0463 markDirty(); 0464 m_size = size; 0465 } 0466 0467 bool ImageInfo::imageOnDisk(const DB::FileName &fileName) 0468 { 0469 return fileName.exists(); 0470 } 0471 0472 ImageInfo::ImageInfo(const DB::FileName &fileName, 0473 const QString &label, 0474 const QString &description, 0475 const ImageDate &date, 0476 int angle, 0477 const MD5 &md5sum, 0478 const QSize &size, 0479 MediaType type, 0480 short rating, 0481 unsigned int stackId, 0482 unsigned int stackOrder) 0483 : m_fileName(fileName) 0484 , m_label(label) 0485 , m_description(description) 0486 , m_date(date) 0487 , m_angle(angle) 0488 , m_md5sum(md5sum) 0489 , m_null(false) 0490 , m_size(size) 0491 , m_type(type) 0492 { 0493 markDirty(); 0494 0495 if (rating > 10) 0496 rating = 10; 0497 if (rating < -1) 0498 rating = -1; 0499 m_rating = rating; 0500 m_stackId = stackId; 0501 m_stackOrder = stackOrder; 0502 m_videoLength = -1; 0503 m_matchGeneration = -1; 0504 } 0505 0506 // Note: we need this operator because the base class QSharedData hides 0507 // its copy operator to make exclude the reference counting from being 0508 // copied. 0509 ImageInfo &ImageInfo::operator=(const ImageInfo &other) 0510 { 0511 m_fileName = other.m_fileName; 0512 m_label = other.m_label; 0513 m_description = other.m_description; 0514 m_date = other.m_date; 0515 m_categoryInfomation = other.m_categoryInfomation; 0516 m_taggedAreas = other.m_taggedAreas; 0517 m_angle = other.m_angle; 0518 m_imageOnDisk = other.m_imageOnDisk; 0519 m_md5sum = other.m_md5sum; 0520 m_null = other.m_null; 0521 m_size = other.m_size; 0522 m_type = other.m_type; 0523 m_rating = other.m_rating; 0524 m_stackId = other.m_stackId; 0525 m_stackOrder = other.m_stackOrder; 0526 m_videoLength = other.m_videoLength; 0527 m_isMatched = other.m_isMatched; 0528 m_matchGeneration = other.m_matchGeneration; 0529 #ifdef HAVE_KGEOMAP 0530 m_coordinates = other.m_coordinates; 0531 m_coordsIsSet = other.m_coordsIsSet; 0532 #endif 0533 m_locked = other.m_locked; 0534 m_dirty = other.m_dirty; 0535 0536 return *this; 0537 } 0538 0539 MediaType DB::ImageInfo::mediaType() const 0540 { 0541 return m_type; 0542 } 0543 0544 bool ImageInfo::isVideo() const 0545 { 0546 return m_type == Video; 0547 } 0548 0549 void DB::ImageInfo::createFolderCategoryItem(DB::CategoryPtr folderCategory, DB::MemberMap &memberMap) 0550 { 0551 QString folderName = Utilities::relativeFolderName(m_fileName.relative()); 0552 if (folderName.isEmpty()) 0553 return; 0554 0555 if (!memberMap.contains(folderCategory->name(), folderName)) { 0556 QStringList directories = folderName.split(QString::fromLatin1("/")); 0557 0558 QString curPath; 0559 for (QStringList::ConstIterator directoryIt = directories.constBegin(); directoryIt != directories.constEnd(); ++directoryIt) { 0560 if (curPath.isEmpty()) 0561 curPath = *directoryIt; 0562 else { 0563 QString oldPath = curPath; 0564 curPath = curPath + QString::fromLatin1("/") + *directoryIt; 0565 memberMap.addMemberToGroup(folderCategory->name(), oldPath, curPath); 0566 } 0567 } 0568 folderCategory->addItem(folderName); 0569 } 0570 0571 m_categoryInfomation.insert(folderCategory->name(), StringSet() << folderName); 0572 } 0573 0574 void DB::ImageInfo::copyExtraData(const DB::ImageInfo &from, bool copyAngle) 0575 { 0576 m_categoryInfomation = from.m_categoryInfomation; 0577 m_description = from.m_description; 0578 // Hmm... what should the date be? orig or modified? 0579 // _date = from._date; 0580 if (copyAngle) 0581 m_angle = from.m_angle; 0582 m_rating = from.m_rating; 0583 } 0584 0585 void DB::ImageInfo::removeExtraData() 0586 { 0587 m_categoryInfomation.clear(); 0588 m_description.clear(); 0589 m_rating = -1; 0590 } 0591 0592 void ImageInfo::merge(const ImageInfo &other) 0593 { 0594 // Merge date 0595 if (other.date() != m_date) { 0596 // a fuzzy date has been set by the user and therefore "wins" over an exact date. 0597 // two fuzzy dates can be merged 0598 // two exact dates should ideally be cross-checked with Exif information in the file. 0599 // Nevertheless, we merge them into a fuzzy date to avoid the complexity of checking the file. 0600 if (other.date().isFuzzy()) { 0601 if (m_date.isFuzzy()) 0602 m_date.extendTo(other.date()); 0603 else 0604 m_date = other.date(); 0605 } else if (!m_date.isFuzzy()) { 0606 m_date.extendTo(other.date()); 0607 } 0608 // else: keep m_date 0609 } 0610 0611 // Merge description 0612 if (!other.description().isEmpty()) { 0613 if (m_description.isEmpty()) 0614 m_description = other.description(); 0615 else if (m_description != other.description()) 0616 m_description += QString::fromUtf8("\n-----------\n") + other.m_description; 0617 } 0618 0619 // Clear untagged tag if only one of the images was untagged 0620 const QString untaggedCategory = Settings::SettingsData::instance()->untaggedCategory(); 0621 const QString untaggedTag = Settings::SettingsData::instance()->untaggedTag(); 0622 const bool isCompleted = !m_categoryInfomation[untaggedCategory].contains(untaggedTag) || !other.m_categoryInfomation[untaggedCategory].contains(untaggedTag); 0623 0624 // Merge tags 0625 const auto categoryInfomationKeys = m_categoryInfomation.keys(); 0626 QSet<QString> keys(categoryInfomationKeys.begin(), categoryInfomationKeys.end()); 0627 const auto otherCategoryInfomationKeys = other.m_categoryInfomation.keys(); 0628 const QSet<QString> otherCategoryInfomationKeysSet(otherCategoryInfomationKeys.begin(), otherCategoryInfomationKeys.end()); 0629 keys.unite(otherCategoryInfomationKeysSet); 0630 for (const QString &key : keys) { 0631 m_categoryInfomation[key].unite(other.m_categoryInfomation[key]); 0632 } 0633 0634 // Clear untagged tag if only one of the images was untagged 0635 if (isCompleted) 0636 m_categoryInfomation[untaggedCategory].remove(untaggedTag); 0637 0638 // merge stacks: 0639 if (isStacked() || other.isStacked()) { 0640 DB::FileNameList stackImages; 0641 if (!isStacked()) 0642 stackImages.append(fileName()); 0643 else 0644 stackImages.append(DB::ImageDB::instance()->getStackFor(fileName())); 0645 stackImages.append(DB::ImageDB::instance()->getStackFor(other.fileName())); 0646 0647 DB::ImageDB::instance()->unstack(stackImages); 0648 if (!DB::ImageDB::instance()->stack(stackImages)) 0649 qCWarning(DBLog, "Could not merge stacks!"); 0650 } 0651 } 0652 0653 void DB::ImageInfo::addCategoryInfo(const QString &category, const StringSet &values) 0654 { 0655 for (StringSet::const_iterator valueIt = values.constBegin(); valueIt != values.constEnd(); ++valueIt) { 0656 if (!m_categoryInfomation[category].contains(*valueIt)) { 0657 markDirty(); 0658 m_categoryInfomation[category].insert(*valueIt); 0659 } 0660 } 0661 } 0662 0663 void DB::ImageInfo::clearAllCategoryInfo() 0664 { 0665 m_categoryInfomation.clear(); 0666 m_taggedAreas.clear(); 0667 } 0668 0669 void DB::ImageInfo::removeCategoryInfo(const QString &category, const StringSet &values) 0670 { 0671 for (StringSet::const_iterator valueIt = values.constBegin(); valueIt != values.constEnd(); ++valueIt) { 0672 if (m_categoryInfomation[category].contains(*valueIt)) { 0673 markDirty(); 0674 m_categoryInfomation[category].remove(*valueIt); 0675 m_taggedAreas[category].remove(*valueIt); 0676 } 0677 } 0678 } 0679 0680 void DB::ImageInfo::addCategoryInfo(const QString &category, const QString &value, const QRect &area) 0681 { 0682 if (!m_categoryInfomation[category].contains(value)) { 0683 markDirty(); 0684 m_categoryInfomation[category].insert(value); 0685 0686 if (area.isValid()) { 0687 m_taggedAreas[category][value] = area; 0688 } 0689 } 0690 } 0691 0692 void DB::ImageInfo::removeCategoryInfo(const QString &category, const QString &value) 0693 { 0694 if (m_categoryInfomation[category].contains(value)) { 0695 markDirty(); 0696 m_categoryInfomation[category].remove(value); 0697 m_taggedAreas[category].remove(value); 0698 } 0699 } 0700 0701 void DB::ImageInfo::setPositionedTags(const QString &category, const PositionTags &positionedTags) 0702 { 0703 markDirty(); 0704 m_taggedAreas[category] = positionedTags; 0705 } 0706 0707 bool DB::ImageInfo::updateDateInformation(int mode) const 0708 { 0709 if ((mode & EXIFMODE_DATE) == 0) 0710 return false; 0711 0712 if ((mode & EXIFMODE_FORCE) != 0) 0713 return true; 0714 0715 return true; 0716 } 0717 0718 TaggedAreas DB::ImageInfo::taggedAreas() const 0719 { 0720 return m_taggedAreas; 0721 } 0722 0723 QRect DB::ImageInfo::areaForTag(QString category, QString tag) const 0724 { 0725 // QMap::value returns a default constructed value if the key is not found: 0726 return m_taggedAreas.value(category).value(tag); 0727 } 0728 0729 #ifdef HAVE_MARBLE 0730 Map::GeoCoordinates DB::ImageInfo::coordinates() const 0731 { 0732 if (m_coordsIsSet) { 0733 return m_coordinates; 0734 } 0735 0736 static const int EXIF_GPS_VERSIONID = 0; 0737 static const int EXIF_GPS_LATREF = 1; 0738 static const int EXIF_GPS_LAT = 2; 0739 static const int EXIF_GPS_LONREF = 3; 0740 static const int EXIF_GPS_LON = 4; 0741 static const int EXIF_GPS_ALTREF = 5; 0742 static const int EXIF_GPS_ALT = 6; 0743 0744 static const QString S = QString::fromUtf8("S"); 0745 static const QString W = QString::fromUtf8("W"); 0746 0747 static QList<Exif::DatabaseElement *> fields; 0748 if (fields.isEmpty()) { 0749 // the order here matters! we use the named int constants afterwards to refer to them: 0750 fields.append(new Exif::IntExifElement("Exif.GPSInfo.GPSVersionID")); // actually a byte value 0751 fields.append(new Exif::StringExifElement("Exif.GPSInfo.GPSLatitudeRef")); 0752 fields.append(new Exif::RationalExifElement("Exif.GPSInfo.GPSLatitude")); 0753 fields.append(new Exif::StringExifElement("Exif.GPSInfo.GPSLongitudeRef")); 0754 fields.append(new Exif::RationalExifElement("Exif.GPSInfo.GPSLongitude")); 0755 fields.append(new Exif::IntExifElement("Exif.GPSInfo.GPSAltitudeRef")); // actually a byte value 0756 fields.append(new Exif::RationalExifElement("Exif.GPSInfo.GPSAltitude")); 0757 } 0758 0759 // read field values from database: 0760 bool foundIt = DB::ImageDB::instance()->exifDB()->readFields(m_fileName, fields); 0761 0762 // if the Database query result doesn't contain exif GPS info (-> upgraded exifdb from DBVersion < 2), it is null 0763 // if the result is int 0, then there's no exif gps information in the image 0764 // otherwise we can proceed to parse the information 0765 if (foundIt && fields[EXIF_GPS_VERSIONID]->value().isNull()) { 0766 auto exifDB = DB::ImageDB::instance()->exifDB(); 0767 // update exif DB and repeat the search: 0768 exifDB->remove(fileName()); 0769 exifDB->add(fileName()); 0770 0771 exifDB->readFields(m_fileName, fields); 0772 Q_ASSERT(!fields[EXIF_GPS_VERSIONID]->value().isNull()); 0773 } 0774 0775 Map::GeoCoordinates coords; 0776 0777 // gps info set? 0778 // don't use the versionid field here, because some cameras use 0 as its value 0779 if (foundIt && fields[EXIF_GPS_LAT]->value().toInt() != -1.0 0780 && fields[EXIF_GPS_LON]->value().toInt() != -1.0) { 0781 // lat/lon/alt reference determines sign of float: 0782 double latr = (fields[EXIF_GPS_LATREF]->value().toString() == S) ? -1.0 : 1.0; 0783 double lat = fields[EXIF_GPS_LAT]->value().toFloat(); 0784 double lonr = (fields[EXIF_GPS_LONREF]->value().toString() == W) ? -1.0 : 1.0; 0785 double lon = fields[EXIF_GPS_LON]->value().toFloat(); 0786 double altr = (fields[EXIF_GPS_ALTREF]->value().toInt() == 1) ? -1.0 : 1.0; 0787 double alt = fields[EXIF_GPS_ALT]->value().toFloat(); 0788 0789 if (lat != -1.0 && lon != -1.0) { 0790 coords.setLatLon(latr * lat, lonr * lon); 0791 if (alt != 0.0f) { 0792 coords.setAlt(altr * alt); 0793 } 0794 } 0795 } 0796 0797 m_coordinates = coords; 0798 m_coordsIsSet = true; 0799 return m_coordinates; 0800 } 0801 0802 #endif 0803 0804 void ImageInfo::markDirty() 0805 { 0806 m_dirty = true; 0807 m_matchGeneration = -1; 0808 } 0809 0810 // vi:expandtab:tabstop=4 shiftwidth=4: