1
0

ThumbnailManager.cpp 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. /******************************************************************************
  2. Copyright (C) 2025 by Taylor Giampaolo <[email protected]>
  3. Lain Bailey <[email protected]>
  4. This program is free software: you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation, either version 2 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with this program. If not, see <http://www.gnu.org/licenses/>.
  14. ******************************************************************************/
  15. #include "ThumbnailManager.hpp"
  16. #include <utility/ScreenshotObj.hpp>
  17. #include <widgets/OBSBasic.hpp>
  18. #include "display-helpers.hpp"
  19. #include <QImageWriter>
  20. constexpr int MIN_THUMBNAIL_UPDATE_INTERVAL_MS = 100;
  21. constexpr int MIN_SOURCE_UPDATE_INTERVAL_MS = 5000;
  22. ThumbnailItem::ThumbnailItem(std::string uuid, OBSSource source) : uuid(uuid), weakSource(OBSGetWeakRef(source)) {}
  23. void ThumbnailItem::init(std::weak_ptr<ThumbnailItem> weakActiveItem)
  24. {
  25. auto thumbnailManager = OBSBasic::Get()->thumbnails();
  26. if (!thumbnailManager) {
  27. return;
  28. }
  29. auto it = thumbnailManager->cachedThumbnails.find(uuid);
  30. if (it != thumbnailManager->cachedThumbnails.end()) {
  31. auto &cachedItem = it->second;
  32. pixmap = cachedItem.pixmap.value_or(QPixmap());
  33. cachedItem.pixmap.reset();
  34. cachedItem.weakActiveItem = std::move(weakActiveItem);
  35. }
  36. }
  37. ThumbnailItem::~ThumbnailItem()
  38. {
  39. auto thumbnailManager = OBSBasic::Get()->thumbnails();
  40. if (!thumbnailManager) {
  41. return;
  42. }
  43. auto &cachedItem = thumbnailManager->cachedThumbnails[uuid];
  44. cachedItem.pixmap = pixmap;
  45. cachedItem.weakActiveItem.reset();
  46. }
  47. void ThumbnailItem::imageUpdated(QImage image)
  48. {
  49. QPixmap newPixmap;
  50. if (!image.isNull()) {
  51. newPixmap = QPixmap::fromImage(image);
  52. }
  53. pixmap = newPixmap;
  54. emit updateThumbnail(pixmap);
  55. }
  56. void Thumbnail::thumbnailUpdated(QPixmap pixmap)
  57. {
  58. emit updateThumbnail(pixmap);
  59. }
  60. ThumbnailManager::ThumbnailManager(QObject *parent) : QObject(parent)
  61. {
  62. connect(&updateTimer, &QTimer::timeout, this, &ThumbnailManager::updateTick);
  63. }
  64. ThumbnailManager::~ThumbnailManager() {}
  65. std::shared_ptr<Thumbnail> ThumbnailManager::getThumbnail(OBSSource source)
  66. {
  67. std::string uuid = obs_source_get_uuid(source);
  68. for (auto it = thumbnails.begin(); it != thumbnails.end(); ++it) {
  69. auto item = it->lock();
  70. if (item && item->uuid == uuid) {
  71. return std::make_shared<Thumbnail>(item);
  72. }
  73. }
  74. std::shared_ptr<Thumbnail> thumbnail;
  75. if ((obs_source_get_output_flags(source) & OBS_SOURCE_VIDEO) != 0) {
  76. auto item = std::make_shared<ThumbnailItem>(uuid, source);
  77. item->init(std::weak_ptr<ThumbnailItem>(item));
  78. thumbnail = std::make_shared<Thumbnail>(item);
  79. connect(item.get(), &ThumbnailItem::updateThumbnail, thumbnail.get(), &Thumbnail::thumbnailUpdated);
  80. newThumbnails.push_back(std::weak_ptr<ThumbnailItem>(item));
  81. }
  82. updateIntervalChanged(thumbnails.size());
  83. return thumbnail;
  84. }
  85. bool ThumbnailManager::updatePixmap(std::shared_ptr<ThumbnailItem> &sharedPointerItem)
  86. {
  87. ThumbnailItem *item = sharedPointerItem.get();
  88. OBSSource source = OBSGetStrongRef(item->weakSource);
  89. if (!source) {
  90. return true;
  91. }
  92. QPixmap pixmap;
  93. item->pixmap = pixmap;
  94. if (source) {
  95. uint32_t sourceWidth = obs_source_get_width(source);
  96. uint32_t sourceHeight = obs_source_get_height(source);
  97. if (sourceWidth == 0 || sourceHeight == 0) {
  98. return true;
  99. }
  100. auto obj = new ScreenshotObj(source);
  101. obj->setSaveToFile(false);
  102. obj->setSize(Thumbnail::size);
  103. connect(obj, &ScreenshotObj::imageReady, item, &ThumbnailItem::imageUpdated);
  104. }
  105. return true;
  106. }
  107. void ThumbnailManager::updateIntervalChanged(size_t newCount)
  108. {
  109. int intervalMS = MIN_THUMBNAIL_UPDATE_INTERVAL_MS;
  110. if (newThumbnails.size() == 0 && newCount > 0) {
  111. int count = (int)newCount;
  112. intervalMS = MIN_SOURCE_UPDATE_INTERVAL_MS / count;
  113. if (intervalMS < MIN_THUMBNAIL_UPDATE_INTERVAL_MS) {
  114. intervalMS = MIN_THUMBNAIL_UPDATE_INTERVAL_MS;
  115. }
  116. }
  117. updateTimer.start(intervalMS);
  118. }
  119. void ThumbnailManager::updateTick()
  120. {
  121. std::shared_ptr<ThumbnailItem> item;
  122. bool changed = false;
  123. bool newThumbnail = false;
  124. while (newThumbnails.size() > 0) {
  125. changed = true;
  126. item = newThumbnails.front().lock();
  127. newThumbnails.pop_front();
  128. if (item) {
  129. newThumbnail = true;
  130. break;
  131. }
  132. }
  133. if (!item) {
  134. while (thumbnails.size() > 0) {
  135. item = thumbnails.front().lock();
  136. thumbnails.pop_front();
  137. if (item) {
  138. break;
  139. } else {
  140. changed = true;
  141. }
  142. }
  143. }
  144. if (changed && newThumbnails.size() == 0) {
  145. updateIntervalChanged(thumbnails.size() + (item ? 1 : 0));
  146. }
  147. if (!item) {
  148. return;
  149. }
  150. if (updatePixmap(item)) {
  151. thumbnails.push_back(std::weak_ptr<ThumbnailItem>(item));
  152. } else {
  153. thumbnails.push_front(std::weak_ptr<ThumbnailItem>(item));
  154. }
  155. }
  156. std::optional<QPixmap> ThumbnailManager::getCachedThumbnail(OBSSource source)
  157. {
  158. if (!source) {
  159. return std::nullopt;
  160. }
  161. std::string uuid = obs_source_get_uuid(source);
  162. auto it = cachedThumbnails.find(uuid);
  163. if (it != cachedThumbnails.end()) {
  164. auto &cachedItem = it->second;
  165. if (cachedItem.pixmap.has_value()) {
  166. return cachedItem.pixmap;
  167. }
  168. auto activeItem = cachedItem.weakActiveItem.lock();
  169. return activeItem ? std::make_optional(activeItem->pixmap) : std::nullopt;
  170. }
  171. return std::nullopt;
  172. }
  173. void ThumbnailManager::preloadThumbnail(OBSSource source, QObject *object, std::function<void(QPixmap)> callback)
  174. {
  175. if (!source) {
  176. return;
  177. }
  178. std::string uuid = obs_source_get_uuid(source);
  179. if (cachedThumbnails.find(uuid) == cachedThumbnails.end()) {
  180. uint32_t sourceWidth = obs_source_get_width(source);
  181. uint32_t sourceHeight = obs_source_get_height(source);
  182. cachedThumbnails[uuid].pixmap = QPixmap();
  183. if (sourceWidth == 0 || sourceHeight == 0) {
  184. return;
  185. }
  186. auto obj = new ScreenshotObj(source);
  187. obj->setSaveToFile(false);
  188. obj->setSize(Thumbnail::size);
  189. QPointer<QObject> safeObject = qobject_cast<QObject *>(object);
  190. connect(obj, &ScreenshotObj::imageReady, this, [=](QImage image) {
  191. QPixmap pixmap;
  192. if (!image.isNull()) {
  193. pixmap = QPixmap::fromImage(image);
  194. }
  195. cachedThumbnails[uuid].pixmap = pixmap;
  196. QMetaObject::invokeMethod(
  197. safeObject,
  198. [safeObject, callback, pixmap]() {
  199. if (safeObject) {
  200. callback(pixmap);
  201. }
  202. },
  203. Qt::QueuedConnection);
  204. });
  205. }
  206. }