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