1 // SPDX-FileCopyrightText: 2003-2020 The KPhotoAlbum Development Team
2 // SPDX-FileCopyrightText: 2021 Johannes Zarl-Zierl <johannes@zarl-zierl.at>
3 //
4 // SPDX-License-Identifier: GPL-2.0-or-later
5 
6 #include "ImageInfo.h"
7 
8 #include "CategoryCollection.h"
9 #include "FileInfo.h"
10 #include "ImageDB.h"
11 #include "MemberMap.h"
12 
13 #include <kpabase/FileNameUtil.h>
14 #include <kpabase/Logging.h>
15 #include <kpabase/SettingsData.h>
16 #include <kpabase/StringSet.h>
17 #include <kpaexif/Database.h>
18 #include <kpaexif/DatabaseElement.h>
19 
20 #include <QFile>
21 #include <QFileInfo>
22 #include <QImageReader>
23 #include <QStringList>
24 
25 using namespace DB;
26 
ImageInfo()27 ImageInfo::ImageInfo()
28     : m_null(true)
29     , m_rating(-1)
30     , m_stackId(0)
31     , m_stackOrder(0)
32     , m_videoLength(-1)
33     , m_isMatched(false)
34     , m_matchGeneration(-1)
35     , m_locked(false)
36     , m_dirty(false)
37 {
38 }
39 
ImageInfo(const DB::FileName & fileName,MediaType type,FileInformation infoMode)40 ImageInfo::ImageInfo(const DB::FileName &fileName, MediaType type, FileInformation infoMode)
41     : m_imageOnDisk(YesOnDisk)
42     , m_null(false)
43     , m_size(-1, -1)
44     , m_type(type)
45     , m_rating(-1)
46     , m_stackId(0)
47     , m_stackOrder(0)
48     , m_videoLength(-1)
49     , m_isMatched(false)
50     , m_matchGeneration(-1)
51     , m_locked(false)
52 {
53     QFileInfo fi(fileName.absolute());
54     m_label = fi.completeBaseName();
55     m_angle = 0;
56 
57     setFileName(fileName);
58 
59     // Read Exif information
60     if (infoMode != FileInformation::Ignore) {
61         ExifMode mode = EXIFMODE_INIT;
62         if (infoMode == FileInformation::ReadAndUpdateExifDB)
63             mode &= ~EXIFMODE_DATABASE_UPDATE;
64         readExif(fileName, mode);
65     }
66 
67     m_dirty = false;
68 }
69 
ImageInfo(const ImageInfo & other)70 ImageInfo::ImageInfo(const ImageInfo &other)
71     : QSharedData(other)
72 {
73     *this = other;
74 }
75 
setIsMatched(bool isMatched)76 void ImageInfo::setIsMatched(bool isMatched)
77 {
78     m_isMatched = isMatched;
79 }
80 
isMatched() const81 bool ImageInfo::isMatched() const
82 {
83     return m_isMatched;
84 }
85 
setMatchGeneration(int matchGeneration)86 void ImageInfo::setMatchGeneration(int matchGeneration)
87 {
88     m_matchGeneration = matchGeneration;
89 }
90 
matchGeneration() const91 int ImageInfo::matchGeneration() const
92 {
93     return m_matchGeneration;
94 }
95 
setLabel(const QString & desc)96 void ImageInfo::setLabel(const QString &desc)
97 {
98     if (desc != m_label)
99         markDirty();
100     m_label = desc;
101 }
102 
label() const103 QString ImageInfo::label() const
104 {
105     return m_label;
106 }
107 
setDescription(const QString & desc)108 void ImageInfo::setDescription(const QString &desc)
109 {
110     if (desc != m_description)
111         markDirty();
112     m_description = desc.trimmed();
113 }
114 
description() const115 QString ImageInfo::description() const
116 {
117     return m_description;
118 }
119 
setCategoryInfo(const QString & key,const StringSet & value)120 void ImageInfo::setCategoryInfo(const QString &key, const StringSet &value)
121 {
122     // Don't check if really changed, because it's too slow.
123     markDirty();
124     m_categoryInfomation[key] = value;
125 }
126 
hasCategoryInfo(const QString & key,const QString & value) const127 bool ImageInfo::hasCategoryInfo(const QString &key, const QString &value) const
128 {
129     return m_categoryInfomation[key].contains(value);
130 }
131 
hasCategoryInfo(const QString & key,const StringSet & values) const132 bool DB::ImageInfo::hasCategoryInfo(const QString &key, const StringSet &values) const
133 {
134     return values.intersects(m_categoryInfomation[key]);
135 }
136 
itemsOfCategory(const QString & key) const137 StringSet ImageInfo::itemsOfCategory(const QString &key) const
138 {
139     return m_categoryInfomation[key];
140 }
141 
renameItem(const QString & category,const QString & oldValue,const QString & newValue)142 void ImageInfo::renameItem(const QString &category, const QString &oldValue, const QString &newValue)
143 {
144     if (m_taggedAreas.contains(category)) {
145         if (m_taggedAreas[category].contains(oldValue)) {
146             m_taggedAreas[category][newValue] = m_taggedAreas[category][oldValue];
147             m_taggedAreas[category].remove(oldValue);
148         }
149     }
150 
151     StringSet &set = m_categoryInfomation[category];
152     StringSet::iterator it = set.find(oldValue);
153     if (it != set.end()) {
154         markDirty();
155         set.erase(it);
156         set.insert(newValue);
157     }
158 }
159 
fileName() const160 DB::FileName ImageInfo::fileName() const
161 {
162     return m_fileName;
163 }
164 
setFileName(const DB::FileName & fileName)165 void ImageInfo::setFileName(const DB::FileName &fileName)
166 {
167     if (fileName != m_fileName)
168         markDirty();
169     m_fileName = fileName;
170 
171     m_imageOnDisk = Unchecked;
172     DB::CategoryPtr folderCategory = DB::ImageDB::instance()->categoryCollection()->categoryForSpecial(DB::Category::FolderCategory);
173     if (folderCategory) {
174         DB::MemberMap &map = DB::ImageDB::instance()->memberMap();
175         createFolderCategoryItem(folderCategory, map);
176         //ImageDB::instance()->setMemberMap( map );
177     }
178 }
179 
rotate(int degrees,RotationMode mode)180 void ImageInfo::rotate(int degrees, RotationMode mode)
181 {
182     // ensure positive degrees:
183     degrees += 360;
184     degrees = degrees % 360;
185     if (degrees == 0)
186         return;
187 
188     markDirty();
189     m_angle = (m_angle + degrees) % 360;
190 
191     if (degrees == 90 || degrees == 270) {
192         m_size.transpose();
193     }
194 
195     // the AnnotationDialog manages this by itself and sets RotateImageInfoOnly:
196     if (mode == RotateImageInfoAndAreas) {
197         for (auto &areasOfCategory : m_taggedAreas) {
198             for (auto &area : areasOfCategory) {
199                 QRect rotatedArea;
200 
201                 // parameter order for QRect::setCoords:
202                 // setCoords( left, top, right, bottom )
203                 // keep in mind that _size is already transposed
204                 switch (degrees) {
205                 case 90:
206                     rotatedArea.setCoords(
207                         m_size.width() - area.bottom(),
208                         area.left(),
209                         m_size.width() - area.top(),
210                         area.right());
211                     break;
212                 case 180:
213                     rotatedArea.setCoords(
214                         m_size.width() - area.right(),
215                         m_size.height() - area.bottom(),
216                         m_size.width() - area.left(),
217                         m_size.height() - area.top());
218                     break;
219                 case 270:
220                     rotatedArea.setCoords(
221                         area.top(),
222                         m_size.height() - area.right(),
223                         area.bottom(),
224                         m_size.height() - area.left());
225                     break;
226                 default:
227                     // degrees==0; "odd" values won't happen.
228                     rotatedArea = area;
229                     break;
230                 }
231 
232                 // update _taggedAreas[category][tag]:
233                 area = rotatedArea;
234             }
235         }
236     }
237 }
238 
angle() const239 int ImageInfo::angle() const
240 {
241     return m_angle;
242 }
243 
setAngle(int angle)244 void ImageInfo::setAngle(int angle)
245 {
246     if (angle != m_angle)
247         markDirty();
248     m_angle = angle;
249 }
250 
rating() const251 short ImageInfo::rating() const
252 {
253     return m_rating;
254 }
255 
setRating(short rating)256 void ImageInfo::setRating(short rating)
257 {
258     Q_ASSERT((rating >= 0 && rating <= 10) || rating == -1);
259 
260     if (rating > 10)
261         rating = 10;
262     if (rating < -1)
263         rating = -1;
264     if (m_rating != rating)
265         markDirty();
266 
267     m_rating = rating;
268 }
269 
stackId() const270 DB::StackID ImageInfo::stackId() const
271 {
272     return m_stackId;
273 }
274 
setStackId(const DB::StackID stackId)275 void ImageInfo::setStackId(const DB::StackID stackId)
276 {
277     if (stackId != m_stackId)
278         markDirty();
279     m_stackId = stackId;
280 }
281 
stackOrder() const282 unsigned int ImageInfo::stackOrder() const
283 {
284     return m_stackOrder;
285 }
286 
setStackOrder(const unsigned int stackOrder)287 void ImageInfo::setStackOrder(const unsigned int stackOrder)
288 {
289     if (stackOrder != m_stackOrder)
290         markDirty();
291     m_stackOrder = stackOrder;
292 }
293 
setVideoLength(int length)294 void ImageInfo::setVideoLength(int length)
295 {
296     if (m_videoLength != length)
297         markDirty();
298     m_videoLength = length;
299 }
300 
videoLength() const301 int ImageInfo::videoLength() const
302 {
303     return m_videoLength;
304 }
305 
setDate(const ImageDate & date)306 void ImageInfo::setDate(const ImageDate &date)
307 {
308     if (date != m_date)
309         markDirty();
310     m_date = date;
311 }
312 
date()313 ImageDate &ImageInfo::date()
314 {
315     return m_date;
316 }
317 
date() const318 ImageDate ImageInfo::date() const
319 {
320     return m_date;
321 }
322 
operator !=(const ImageInfo & other) const323 bool ImageInfo::operator!=(const ImageInfo &other) const
324 {
325     return !(*this == other);
326 }
327 
operator ==(const ImageInfo & other) const328 bool ImageInfo::operator==(const ImageInfo &other) const
329 {
330     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.
331                     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))));
332     if (!changed) {
333         QStringList keys = DB::ImageDB::instance()->categoryCollection()->categoryNames();
334         for (QStringList::ConstIterator it = keys.constBegin(); it != keys.constEnd(); ++it)
335             changed |= m_categoryInfomation[*it] != other.m_categoryInfomation[*it];
336     }
337     return !changed;
338 }
339 
renameCategory(const QString & oldName,const QString & newName)340 void ImageInfo::renameCategory(const QString &oldName, const QString &newName)
341 {
342     markDirty();
343 
344     m_categoryInfomation[newName] = m_categoryInfomation[oldName];
345     m_categoryInfomation.remove(oldName);
346 
347     m_taggedAreas[newName] = m_taggedAreas[oldName];
348     m_taggedAreas.remove(oldName);
349 }
350 
setMD5Sum(const MD5 & sum,bool storeEXIF)351 void ImageInfo::setMD5Sum(const MD5 &sum, bool storeEXIF)
352 {
353     if (sum != m_md5sum) {
354         // if we make a QObject derived class out of imageinfo, we might invalidate thumbnails from here
355 
356         // file changed -> reload/invalidate metadata:
357         ExifMode mode = EXIFMODE_ORIENTATION | EXIFMODE_DATABASE_UPDATE;
358         // fuzzy dates are usually set for a reason
359         if (!m_date.isFuzzy())
360             mode |= EXIFMODE_DATE;
361         // FIXME (ZaJ): the "right" thing to do would be to update the description
362         //              - if it is currently empty (done.)
363         //              - if it has been set from the exif info and not been changed (TODO)
364         if (m_description.isEmpty())
365             mode |= EXIFMODE_DESCRIPTION;
366 
367         if (!storeEXIF)
368             mode &= ~EXIFMODE_DATABASE_UPDATE;
369         readExif(fileName(), mode);
370         if (storeEXIF) {
371             // Size isn't really EXIF, but this is the most obvious
372             // place to extract it
373             QImageReader reader(m_fileName.absolute());
374             if (reader.canRead()) {
375                 m_size = reader.size();
376                 if (m_angle == 90 || m_angle == 270)
377                     m_size.transpose();
378             }
379         }
380 
381         // FIXME (ZaJ): it *should* make sense to set the ImageDB::md5Map() from here, but I want
382         //              to make sure I fully understand everything first...
383         //              this could also be done as signal md5Changed(old,new)
384 
385         // image size is invalidated by the thumbnail builder, if needed
386 
387         markDirty();
388     }
389     m_md5sum = sum;
390 }
391 
setLocked(bool locked)392 void ImageInfo::setLocked(bool locked)
393 {
394     m_locked = locked;
395 }
396 
isLocked() const397 bool ImageInfo::isLocked() const
398 {
399     return m_locked;
400 }
401 
readExif(const DB::FileName & fullPath,DB::ExifMode mode)402 void ImageInfo::readExif(const DB::FileName &fullPath, DB::ExifMode mode)
403 {
404     DB::FileInfo exifInfo = DB::FileInfo::read(fullPath, mode);
405 
406     // Date
407     if (updateDateInformation(mode)) {
408         const ImageDate newDate(exifInfo.dateTime());
409         setDate(newDate);
410     }
411 
412     // Orientation
413     if ((mode & EXIFMODE_ORIENTATION) && Settings::SettingsData::instance()->useEXIFRotate()) {
414         setAngle(exifInfo.angle());
415     }
416 
417     // Description
418     if ((mode & EXIFMODE_DESCRIPTION) && Settings::SettingsData::instance()->useEXIFComments()) {
419         bool doSetDescription = true;
420         QString desc = exifInfo.description();
421 
422         if (Settings::SettingsData::instance()->stripEXIFComments()) {
423             for (const auto &ignoredComment : Settings::SettingsData::instance()->EXIFCommentsToStrip()) {
424                 if (desc == ignoredComment) {
425                     doSetDescription = false;
426                     break;
427                 }
428             }
429         }
430 
431         if (doSetDescription) {
432             setDescription(desc);
433         }
434     }
435 
436     // Database update
437     if (mode & EXIFMODE_DATABASE_UPDATE) {
438         DB::ImageDB::instance()->exifDB()->add(exifInfo.getFileName(), exifInfo.getExifData());
439 #ifdef HAVE_MARBLE
440         // GPS coords might have changed...
441         m_coordsIsSet = false;
442 #endif
443     }
444 }
445 
availableCategories() const446 QStringList ImageInfo::availableCategories() const
447 {
448     return m_categoryInfomation.keys();
449 }
450 
size() const451 QSize ImageInfo::size() const
452 {
453     return m_size;
454 }
455 
setSize(const QSize & size)456 void ImageInfo::setSize(const QSize &size)
457 {
458     if (size != m_size)
459         markDirty();
460     m_size = size;
461 }
462 
imageOnDisk(const DB::FileName & fileName)463 bool ImageInfo::imageOnDisk(const DB::FileName &fileName)
464 {
465     return fileName.exists();
466 }
467 
ImageInfo(const DB::FileName & fileName,const QString & label,const QString & description,const ImageDate & date,int angle,const MD5 & md5sum,const QSize & size,MediaType type,short rating,unsigned int stackId,unsigned int stackOrder)468 ImageInfo::ImageInfo(const DB::FileName &fileName,
469                      const QString &label,
470                      const QString &description,
471                      const ImageDate &date,
472                      int angle,
473                      const MD5 &md5sum,
474                      const QSize &size,
475                      MediaType type,
476                      short rating,
477                      unsigned int stackId,
478                      unsigned int stackOrder)
479 {
480     m_fileName = fileName;
481     m_label = label;
482     m_description = description;
483     m_date = date;
484     m_angle = angle;
485     m_md5sum = md5sum;
486     m_size = size;
487     m_imageOnDisk = Unchecked;
488     m_locked = false;
489     m_null = false;
490     m_type = type;
491     markDirty();
492 
493     if (rating > 10)
494         rating = 10;
495     if (rating < -1)
496         rating = -1;
497     m_rating = rating;
498     m_stackId = stackId;
499     m_stackOrder = stackOrder;
500     m_videoLength = -1;
501     m_matchGeneration = -1;
502 }
503 
504 // Note: we need this operator because the base class QSharedData hides
505 // its copy operator to make exclude the reference counting from being
506 // copied.
operator =(const ImageInfo & other)507 ImageInfo &ImageInfo::operator=(const ImageInfo &other)
508 {
509     m_fileName = other.m_fileName;
510     m_label = other.m_label;
511     m_description = other.m_description;
512     m_date = other.m_date;
513     m_categoryInfomation = other.m_categoryInfomation;
514     m_taggedAreas = other.m_taggedAreas;
515     m_angle = other.m_angle;
516     m_imageOnDisk = other.m_imageOnDisk;
517     m_md5sum = other.m_md5sum;
518     m_null = other.m_null;
519     m_size = other.m_size;
520     m_type = other.m_type;
521     m_rating = other.m_rating;
522     m_stackId = other.m_stackId;
523     m_stackOrder = other.m_stackOrder;
524     m_videoLength = other.m_videoLength;
525     m_isMatched = other.m_isMatched;
526     m_matchGeneration = other.m_matchGeneration;
527 #ifdef HAVE_KGEOMAP
528     m_coordinates = other.m_coordinates;
529     m_coordsIsSet = other.m_coordsIsSet;
530 #endif
531     m_locked = other.m_locked;
532     m_dirty = other.m_dirty;
533 
534     return *this;
535 }
536 
mediaType() const537 MediaType DB::ImageInfo::mediaType() const
538 {
539     return m_type;
540 }
541 
isVideo() const542 bool ImageInfo::isVideo() const
543 {
544     return m_type == Video;
545 }
546 
createFolderCategoryItem(DB::CategoryPtr folderCategory,DB::MemberMap & memberMap)547 void DB::ImageInfo::createFolderCategoryItem(DB::CategoryPtr folderCategory, DB::MemberMap &memberMap)
548 {
549     QString folderName = Utilities::relativeFolderName(m_fileName.relative());
550     if (folderName.isEmpty())
551         return;
552 
553     if (!memberMap.contains(folderCategory->name(), folderName)) {
554         QStringList directories = folderName.split(QString::fromLatin1("/"));
555 
556         QString curPath;
557         for (QStringList::ConstIterator directoryIt = directories.constBegin(); directoryIt != directories.constEnd(); ++directoryIt) {
558             if (curPath.isEmpty())
559                 curPath = *directoryIt;
560             else {
561                 QString oldPath = curPath;
562                 curPath = curPath + QString::fromLatin1("/") + *directoryIt;
563                 memberMap.addMemberToGroup(folderCategory->name(), oldPath, curPath);
564             }
565         }
566         folderCategory->addItem(folderName);
567     }
568 
569     m_categoryInfomation.insert(folderCategory->name(), StringSet() << folderName);
570 }
571 
copyExtraData(const DB::ImageInfo & from,bool copyAngle)572 void DB::ImageInfo::copyExtraData(const DB::ImageInfo &from, bool copyAngle)
573 {
574     m_categoryInfomation = from.m_categoryInfomation;
575     m_description = from.m_description;
576     // Hmm...  what should the date be?  orig or modified?
577     // _date = from._date;
578     if (copyAngle)
579         m_angle = from.m_angle;
580     m_rating = from.m_rating;
581 }
582 
removeExtraData()583 void DB::ImageInfo::removeExtraData()
584 {
585     m_categoryInfomation.clear();
586     m_description.clear();
587     m_rating = -1;
588 }
589 
merge(const ImageInfo & other)590 void ImageInfo::merge(const ImageInfo &other)
591 {
592     // Merge date
593     if (other.date() != m_date) {
594         // a fuzzy date has been set by the user and therefore "wins" over an exact date.
595         // two fuzzy dates can be merged
596         // two exact dates should ideally be cross-checked with Exif information in the file.
597         // Nevertheless, we merge them into a fuzzy date to avoid the complexity of checking the file.
598         if (other.date().isFuzzy()) {
599             if (m_date.isFuzzy())
600                 m_date.extendTo(other.date());
601             else
602                 m_date = other.date();
603         } else if (!m_date.isFuzzy()) {
604             m_date.extendTo(other.date());
605         }
606         // else: keep m_date
607     }
608 
609     // Merge description
610     if (!other.description().isEmpty()) {
611         if (m_description.isEmpty())
612             m_description = other.description();
613         else if (m_description != other.description())
614             m_description += QString::fromUtf8("\n-----------\n") + other.m_description;
615     }
616 
617     // Clear untagged tag if only one of the images was untagged
618     const QString untaggedCategory = Settings::SettingsData::instance()->untaggedCategory();
619     const QString untaggedTag = Settings::SettingsData::instance()->untaggedTag();
620     const bool isCompleted = !m_categoryInfomation[untaggedCategory].contains(untaggedTag) || !other.m_categoryInfomation[untaggedCategory].contains(untaggedTag);
621 
622     // Merge tags
623 #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
624     const auto categoryInfomationKeys = m_categoryInfomation.keys();
625     QSet<QString> keys(categoryInfomationKeys.begin(), categoryInfomationKeys.end());
626     const auto otherCategoryInfomationKeys = other.m_categoryInfomation.keys();
627     const QSet<QString> otherCategoryInfomationKeysSet(otherCategoryInfomationKeys.begin(), otherCategoryInfomationKeys.end());
628 #else
629     QSet<QString> keys = QSet<QString>::fromList(m_categoryInfomation.keys());
630     const auto otherCategoryInfomationKeysSet = QSet<QString>::fromList(other.m_categoryInfomation.keys());
631 #endif
632     keys.unite(otherCategoryInfomationKeysSet);
633     for (const QString &key : keys) {
634         m_categoryInfomation[key].unite(other.m_categoryInfomation[key]);
635     }
636 
637     // Clear untagged tag if only one of the images was untagged
638     if (isCompleted)
639         m_categoryInfomation[untaggedCategory].remove(untaggedTag);
640 
641     // merge stacks:
642     if (isStacked() || other.isStacked()) {
643         DB::FileNameList stackImages;
644         if (!isStacked())
645             stackImages.append(fileName());
646         else
647             stackImages.append(DB::ImageDB::instance()->getStackFor(fileName()));
648         stackImages.append(DB::ImageDB::instance()->getStackFor(other.fileName()));
649 
650         DB::ImageDB::instance()->unstack(stackImages);
651         if (!DB::ImageDB::instance()->stack(stackImages))
652             qCWarning(DBLog, "Could not merge stacks!");
653     }
654 }
655 
addCategoryInfo(const QString & category,const StringSet & values)656 void DB::ImageInfo::addCategoryInfo(const QString &category, const StringSet &values)
657 {
658     for (StringSet::const_iterator valueIt = values.constBegin(); valueIt != values.constEnd(); ++valueIt) {
659         if (!m_categoryInfomation[category].contains(*valueIt)) {
660             markDirty();
661             m_categoryInfomation[category].insert(*valueIt);
662         }
663     }
664 }
665 
clearAllCategoryInfo()666 void DB::ImageInfo::clearAllCategoryInfo()
667 {
668     m_categoryInfomation.clear();
669     m_taggedAreas.clear();
670 }
671 
removeCategoryInfo(const QString & category,const StringSet & values)672 void DB::ImageInfo::removeCategoryInfo(const QString &category, const StringSet &values)
673 {
674     for (StringSet::const_iterator valueIt = values.constBegin(); valueIt != values.constEnd(); ++valueIt) {
675         if (m_categoryInfomation[category].contains(*valueIt)) {
676             markDirty();
677             m_categoryInfomation[category].remove(*valueIt);
678             m_taggedAreas[category].remove(*valueIt);
679         }
680     }
681 }
682 
addCategoryInfo(const QString & category,const QString & value,const QRect & area)683 void DB::ImageInfo::addCategoryInfo(const QString &category, const QString &value, const QRect &area)
684 {
685     if (!m_categoryInfomation[category].contains(value)) {
686         markDirty();
687         m_categoryInfomation[category].insert(value);
688 
689         if (area.isValid()) {
690             m_taggedAreas[category][value] = area;
691         }
692     }
693 }
694 
removeCategoryInfo(const QString & category,const QString & value)695 void DB::ImageInfo::removeCategoryInfo(const QString &category, const QString &value)
696 {
697     if (m_categoryInfomation[category].contains(value)) {
698         markDirty();
699         m_categoryInfomation[category].remove(value);
700         m_taggedAreas[category].remove(value);
701     }
702 }
703 
setPositionedTags(const QString & category,const PositionTags & positionedTags)704 void DB::ImageInfo::setPositionedTags(const QString &category, const PositionTags &positionedTags)
705 {
706     markDirty();
707     m_taggedAreas[category] = positionedTags;
708 }
709 
updateDateInformation(int mode) const710 bool DB::ImageInfo::updateDateInformation(int mode) const
711 {
712     if ((mode & EXIFMODE_DATE) == 0)
713         return false;
714 
715     if ((mode & EXIFMODE_FORCE) != 0)
716         return true;
717 
718     return true;
719 }
720 
taggedAreas() const721 TaggedAreas DB::ImageInfo::taggedAreas() const
722 {
723     return m_taggedAreas;
724 }
725 
areaForTag(QString category,QString tag) const726 QRect DB::ImageInfo::areaForTag(QString category, QString tag) const
727 {
728     // QMap::value returns a default constructed value if the key is not found:
729     return m_taggedAreas.value(category).value(tag);
730 }
731 
732 #ifdef HAVE_MARBLE
coordinates() const733 Map::GeoCoordinates DB::ImageInfo::coordinates() const
734 {
735     if (m_coordsIsSet) {
736         return m_coordinates;
737     }
738 
739     static const int EXIF_GPS_VERSIONID = 0;
740     static const int EXIF_GPS_LATREF = 1;
741     static const int EXIF_GPS_LAT = 2;
742     static const int EXIF_GPS_LONREF = 3;
743     static const int EXIF_GPS_LON = 4;
744     static const int EXIF_GPS_ALTREF = 5;
745     static const int EXIF_GPS_ALT = 6;
746 
747     static const QString S = QString::fromUtf8("S");
748     static const QString W = QString::fromUtf8("W");
749 
750     static QList<Exif::DatabaseElement *> fields;
751     if (fields.isEmpty()) {
752         // the order here matters! we use the named int constants afterwards to refer to them:
753         fields.append(new Exif::IntExifElement("Exif.GPSInfo.GPSVersionID")); // actually a byte value
754         fields.append(new Exif::StringExifElement("Exif.GPSInfo.GPSLatitudeRef"));
755         fields.append(new Exif::RationalExifElement("Exif.GPSInfo.GPSLatitude"));
756         fields.append(new Exif::StringExifElement("Exif.GPSInfo.GPSLongitudeRef"));
757         fields.append(new Exif::RationalExifElement("Exif.GPSInfo.GPSLongitude"));
758         fields.append(new Exif::IntExifElement("Exif.GPSInfo.GPSAltitudeRef")); // actually a byte value
759         fields.append(new Exif::RationalExifElement("Exif.GPSInfo.GPSAltitude"));
760     }
761 
762     // read field values from database:
763     bool foundIt = DB::ImageDB::instance()->exifDB()->readFields(m_fileName, fields);
764 
765     // if the Database query result doesn't contain exif GPS info (-> upgraded exifdb from DBVersion < 2), it is null
766     // if the result is int 0, then there's no exif gps information in the image
767     // otherwise we can proceed to parse the information
768     if (foundIt && fields[EXIF_GPS_VERSIONID]->value().isNull()) {
769         auto exifDB = DB::ImageDB::instance()->exifDB();
770         // update exif DB and repeat the search:
771         exifDB->remove(fileName());
772         exifDB->add(fileName());
773 
774         exifDB->readFields(m_fileName, fields);
775         Q_ASSERT(!fields[EXIF_GPS_VERSIONID]->value().isNull());
776     }
777 
778     Map::GeoCoordinates coords;
779 
780     // gps info set?
781     // don't use the versionid field here, because some cameras use 0 as its value
782     if (foundIt && fields[EXIF_GPS_LAT]->value().toInt() != -1.0
783         && fields[EXIF_GPS_LON]->value().toInt() != -1.0) {
784         // lat/lon/alt reference determines sign of float:
785         double latr = (fields[EXIF_GPS_LATREF]->value().toString() == S) ? -1.0 : 1.0;
786         double lat = fields[EXIF_GPS_LAT]->value().toFloat();
787         double lonr = (fields[EXIF_GPS_LONREF]->value().toString() == W) ? -1.0 : 1.0;
788         double lon = fields[EXIF_GPS_LON]->value().toFloat();
789         double altr = (fields[EXIF_GPS_ALTREF]->value().toInt() == 1) ? -1.0 : 1.0;
790         double alt = fields[EXIF_GPS_ALT]->value().toFloat();
791 
792         if (lat != -1.0 && lon != -1.0) {
793             coords.setLatLon(latr * lat, lonr * lon);
794             if (alt != 0.0f) {
795                 coords.setAlt(altr * alt);
796             }
797         }
798     }
799 
800     m_coordinates = coords;
801     m_coordsIsSet = true;
802     return m_coordinates;
803 }
804 
805 #endif
806 
markDirty()807 void ImageInfo::markDirty()
808 {
809     m_dirty = true;
810     m_matchGeneration = -1;
811 }
812 
813 // vi:expandtab:tabstop=4 shiftwidth=4:
814