ResultListModel.cpp 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. #include "ResultListModel.hpp"
  2. #include "ImageHasher.hpp"
  3. #include <QImageReader>
  4. #include <QtConcurrent>
  5. #include <opencv2/opencv.hpp>
  6. #include <opencv2/imgproc.hpp>
  7. ResultListModel::ResultListModel(QObject *parent) : QAbstractListModel(parent) {
  8. }
  9. ResultListModel::~ResultListModel() {
  10. }
  11. int ResultListModel::rowCount(const QModelIndex &parent) const {
  12. if (parent.isValid()) return 0;
  13. return m_items.size();
  14. }
  15. QVariant ResultListModel::data(const QModelIndex &index, int role) const {
  16. // Custom roles not strictly used since Delegate casts and reads data manually.
  17. if (!index.isValid() || index.row() >= m_items.size()) return QVariant();
  18. return QVariant();
  19. }
  20. const ResultListItem& ResultListModel::getItem(int row) const {
  21. return m_items[row];
  22. }
  23. void ResultListModel::setGroups(const std::vector<DuplicateGroup>& groups, bool preserveState) {
  24. beginResetModel();
  25. std::unordered_map<std::string, bool> oldState;
  26. if (preserveState) {
  27. oldState = m_checkStates;
  28. }
  29. m_checkStates.clear();
  30. m_items.clear();
  31. int groupId = 0;
  32. for (const auto& group : groups) {
  33. if (group.images.empty()) continue;
  34. const ImageData* bestImage = &group.images[0];
  35. for (size_t i = 1; i < group.images.size(); ++i) {
  36. if (group.images[i].file_size > bestImage->file_size) {
  37. bestImage = &group.images[i];
  38. }
  39. }
  40. for (const auto& img : group.images) {
  41. if (preserveState && oldState.find(img.path) != oldState.end()) {
  42. m_checkStates[img.path] = oldState[img.path];
  43. } else {
  44. m_checkStates[img.path] = (&img != bestImage);
  45. }
  46. }
  47. ResultListItem header;
  48. header.type = ResultListItem::Header;
  49. header.groupId = groupId;
  50. header.headerText = tr("Duplicate Group - %1 images").arg(group.images.size());
  51. m_items.push_back(header);
  52. ResultListItem rowItem;
  53. rowItem.type = ResultListItem::ImageRow;
  54. rowItem.groupId = groupId;
  55. for (const auto& img : group.images) {
  56. rowItem.images.push_back(img);
  57. if (rowItem.images.size() == 4) {
  58. m_items.push_back(rowItem);
  59. rowItem.images.clear();
  60. }
  61. }
  62. if (!rowItem.images.empty()) {
  63. m_items.push_back(rowItem);
  64. }
  65. groupId++;
  66. }
  67. endResetModel();
  68. }
  69. bool ResultListModel::isChecked(const std::string& path) const {
  70. auto it = m_checkStates.find(path);
  71. if (it != m_checkStates.end()) return it->second;
  72. return false;
  73. }
  74. void ResultListModel::setChecked(const std::string& path, bool state) {
  75. if (m_checkStates[path] != state) {
  76. m_checkStates[path] = state;
  77. emitRowDataChangedForPath(path);
  78. }
  79. }
  80. void ResultListModel::clearAllChecks() {
  81. for (auto& pair : m_checkStates) {
  82. pair.second = false;
  83. }
  84. if (!m_items.empty()) {
  85. emit dataChanged(index(0, 0), index(m_items.size()-1, 0));
  86. }
  87. }
  88. void ResultListModel::clear() {
  89. beginResetModel();
  90. m_items.clear();
  91. m_checkStates.clear();
  92. m_thumbnails.clear();
  93. m_loadingPaths.clear();
  94. endResetModel();
  95. }
  96. const std::unordered_map<std::string, bool>& ResultListModel::getCheckStates() const {
  97. return m_checkStates;
  98. }
  99. QPixmap ResultListModel::getThumbnail(const std::string& path) const {
  100. auto it = m_thumbnails.find(path);
  101. if (it != m_thumbnails.end()) return it->second;
  102. requestThumbnail(path);
  103. return QPixmap();
  104. }
  105. void ResultListModel::requestThumbnail(const std::string& path) const {
  106. if (m_loadingPaths.contains(path)) return;
  107. m_loadingPaths.insert(path);
  108. QString qpath = QString::fromStdString(path);
  109. QtConcurrent::run([qpath, path]() -> QImage {
  110. QImageReader reader(qpath);
  111. reader.setAutoTransform(true);
  112. reader.setAllocationLimit(512);
  113. QSize imgSize = reader.size();
  114. if (imgSize.isValid()) {
  115. imgSize.scale(300, 300, Qt::KeepAspectRatio);
  116. reader.setScaledSize(imgSize);
  117. }
  118. QImage img = reader.read();
  119. if (!img.isNull()) {
  120. return img;
  121. } else {
  122. cv::Mat cvImg = ImageHasher::loadImage(path, 300);
  123. if (!cvImg.empty()) {
  124. cv::Mat rgb;
  125. cv::cvtColor(cvImg, rgb, cv::COLOR_BGR2RGB);
  126. QImage qimg(rgb.data, rgb.cols, rgb.rows, rgb.step, QImage::Format_RGB888);
  127. return qimg.copy();
  128. }
  129. }
  130. return QImage();
  131. }).then(const_cast<ResultListModel*>(this), [this, path](QImage img) {
  132. m_loadingPaths.erase(path);
  133. if (!img.isNull()) {
  134. m_thumbnails[path] = QPixmap::fromImage(img).scaled(150, 150, Qt::KeepAspectRatio, Qt::SmoothTransformation);
  135. } else {
  136. // エラー表示用ダミーピックスマップをセットしても良いが、ここでは空のまま
  137. m_thumbnails[path] = QPixmap();
  138. }
  139. const_cast<ResultListModel*>(this)->emitRowDataChangedForPath(path);
  140. });
  141. }
  142. void ResultListModel::emitRowDataChangedForPath(const std::string& path) {
  143. for (int i=0; i < static_cast<int>(m_items.size()); ++i) {
  144. if (m_items[i].type == ResultListItem::ImageRow) {
  145. for (const auto& img : m_items[i].images) {
  146. if (img.path == path) {
  147. emit dataChanged(index(i, 0), index(i, 0));
  148. return; // found it, rows don't repeat paths.
  149. }
  150. }
  151. }
  152. }
  153. }