Skip to content

Commit 614877a

Browse files
author
Gin
committed
add overlay image UI
1 parent 8b583be commit 614877a

File tree

13 files changed

+315
-102
lines changed

13 files changed

+315
-102
lines changed

SerialPrograms/Source/CommonFramework/VideoPipeline/UI/VideoOverlayWidget.cpp

Lines changed: 55 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@
99
#include "CommonFramework/GlobalServices.h"
1010
#include "VideoOverlayWidget.h"
1111

12-
//#include <iostream>
13-
//using std::cout;
14-
//using std::endl;
12+
// #include <iostream>
13+
// using std::cout;
14+
// using std::endl;
1515

1616
namespace PokemonAutomation{
1717

@@ -33,6 +33,7 @@ VideoOverlayWidget::VideoOverlayWidget(QWidget& parent, VideoOverlaySession& ses
3333
, m_session(session)
3434
, m_boxes(std::make_shared<std::vector<OverlayBox>>(session.boxes()))
3535
, m_texts(std::make_shared<std::vector<OverlayText>>(session.texts()))
36+
, m_images(std::make_shared<std::vector<OverlayImage>>(session.images()))
3637
, m_log(std::make_shared<std::vector<OverlayLogLine>>(session.log_texts()))
3738
, m_stats(nullptr)
3839
{
@@ -50,38 +51,37 @@ VideoOverlayWidget::VideoOverlayWidget(QWidget& parent, VideoOverlaySession& ses
5051
}
5152
}
5253

53-
void VideoOverlayWidget::enabled_boxes(bool enabled){
54-
QMetaObject::invokeMethod(this, [this]{ this->update(); });
55-
}
56-
void VideoOverlayWidget::enabled_text(bool enabled){
57-
QMetaObject::invokeMethod(this, [this]{ this->update(); });
58-
}
59-
void VideoOverlayWidget::enabled_log(bool enabled){
60-
QMetaObject::invokeMethod(this, [this]{ this->update(); });
61-
}
62-
void VideoOverlayWidget::enabled_stats(bool enabled){
54+
void VideoOverlayWidget::async_update(){
55+
// by using QMetaObject::invokeMethod, we can call this->update() on a non-main thread
56+
// without trigger Qt crash, as normally this->update() can only be called on the main
57+
// thread.
6358
QMetaObject::invokeMethod(this, [this]{ this->update(); });
6459
}
6560

66-
void VideoOverlayWidget::update_boxes(const std::shared_ptr<const std::vector<OverlayBox>>& boxes){
61+
void VideoOverlayWidget::on_overlay_update_boxes(const std::shared_ptr<const std::vector<OverlayBox>>& boxes){
6762
WriteSpinLock lg(m_lock, "VideoOverlay::update_boxes()");
6863
m_boxes = boxes;
6964
}
70-
void VideoOverlayWidget::update_text(const std::shared_ptr<const std::vector<OverlayText>>& texts){
65+
void VideoOverlayWidget::on_overlay_update_text(const std::shared_ptr<const std::vector<OverlayText>>& texts){
7166
WriteSpinLock lg(m_lock, "VideoOverlay::update_text()");
7267
m_texts = texts;
7368
}
74-
void VideoOverlayWidget::update_log(const std::shared_ptr<const std::vector<OverlayLogLine>>& texts){
69+
void VideoOverlayWidget::on_overlay_update_images(const std::shared_ptr<const std::vector<OverlayImage>>& images){
70+
WriteSpinLock lg(m_lock, "VideoOverlay::update_images()");
71+
m_images = images;
72+
}
73+
74+
void VideoOverlayWidget::on_overlay_update_log(const std::shared_ptr<const std::vector<OverlayLogLine>>& logs){
7575
WriteSpinLock lg(m_lock, "VideoOverlay::update_log_text()");
76-
m_log = texts;
76+
m_log = logs;
7777
}
7878
#if 0
7979
void VideoOverlayWidget::update_log_background(const std::shared_ptr<const std::vector<VideoOverlaySession::Box>>& bg_boxes){
8080
WriteSpinLock lg(m_lock, "VideoOverlay::update_log_background()");
8181
m_log_text_bg_boxes = bg_boxes;
8282
}
8383
#endif
84-
void VideoOverlayWidget::update_stats(const std::list<OverlayStat*>* stats){
84+
void VideoOverlayWidget::on_overlay_update_stats(const std::list<OverlayStat*>* stats){
8585
WriteSpinLock lg(m_lock, "VideoOverlay::update_stats()");
8686
m_stats = stats;
8787
}
@@ -94,33 +94,37 @@ void VideoOverlayWidget::on_watchdog_timeout(){
9494

9595

9696
void VideoOverlayWidget::resizeEvent(QResizeEvent* event){}
97+
9798
void VideoOverlayWidget::paintEvent(QPaintEvent*){
9899
QPainter painter(this);
99100

100101
{
101102
WriteSpinLock lg(m_lock, "VideoOverlay::paintEvent()");
102103

103104
if (m_session.enabled_boxes()){
104-
update_boxes(painter);
105+
render_boxes(painter);
105106
}
106107
if (m_session.enabled_text()){
107-
update_text(painter);
108+
render_text(painter);
109+
}
110+
if (m_session.enabled_images()){
111+
render_images(painter);
108112
}
109113
if (m_session.enabled_log()){
110-
update_log(painter);
114+
render_log(painter);
111115
}
112116
if (m_session.enabled_stats() && m_stats){
113-
update_stats(painter);
117+
render_stats(painter);
114118
}
115119
}
116120

117121
global_watchdog().delay(*this);
118122
}
119123

120124

121-
void VideoOverlayWidget::update_boxes(QPainter& painter){
122-
int width = this->width();
123-
int height = this->height();
125+
void VideoOverlayWidget::render_boxes(QPainter& painter){
126+
const int width = this->width();
127+
const int height = this->height();
124128
for (const auto& item : *m_boxes){
125129
QColor color = QColor((uint32_t)item.color);
126130
painter.setPen(color);
@@ -196,9 +200,9 @@ void VideoOverlayWidget::update_boxes(QPainter& painter){
196200
painter.drawText(QPoint(xmin + padding_width, ymin - 2*padding_height), text);
197201
}
198202
}
199-
void VideoOverlayWidget::update_text(QPainter& painter){
200-
int width = this->width();
201-
int height = this->height();
203+
void VideoOverlayWidget::render_text(QPainter& painter){
204+
const int width = this->width();
205+
const int height = this->height();
202206
for (const auto& item: *m_texts){
203207
painter.setPen(QColor((uint32_t)item.color));
204208
QFont text_font = this->font();
@@ -211,7 +215,25 @@ void VideoOverlayWidget::update_text(QPainter& painter){
211215
painter.drawText(QPoint(xmin, ymin), QString::fromStdString(item.message));
212216
}
213217
}
214-
void VideoOverlayWidget::update_log(QPainter& painter){
218+
void VideoOverlayWidget::render_images(QPainter& painter){
219+
const double width = static_cast<double>(this->width());
220+
const double height = static_cast<double>(this->height());
221+
222+
for(const auto& image_overlay: *m_images){
223+
QImage q_image = image_overlay.image.to_QImage_ref();
224+
// source rect is the entire portion of the q_image, in pixel units
225+
QRectF source_rect(0.0, 0.0, static_cast<double>(q_image.width()), static_cast<double>(q_image.height()));
226+
// build a target_rect. target_rect is what region the overlay image should appear inside the overlay viewport.
227+
// target_rect is in pixel units of the viewport
228+
const double target_start_x = width * image_overlay.x;
229+
const double target_start_y = height * image_overlay.y;
230+
const double target_width = width * image_overlay.width;
231+
const double target_height = height * image_overlay.height;
232+
QRectF target_rect(target_start_x, target_start_y, target_width, target_height);
233+
painter.drawImage(target_rect, q_image, source_rect);
234+
}
235+
}
236+
void VideoOverlayWidget::render_log(QPainter& painter){
215237
if (m_log->empty()){
216238
return;
217239
}
@@ -231,7 +253,9 @@ void VideoOverlayWidget::update_log(QPainter& painter){
231253

232254
// Draw the box.
233255
{
234-
QColor box_color(10, 10, 10, 200);
256+
// set a semi-transparent dark color so that user can see the log lines while can also see
257+
// vaguely the video stream content behind it
258+
QColor box_color(10, 10, 10, 200); // r=g=b=10, alpha=200
235259
painter.setPen(box_color);
236260
const int xmin = std::max((int)(width * LOG_MIN_X + 0.5), 1);
237261
const int ymin = std::max((int)(height * (LOG_MAX_Y - log_bg_height) + 0.5), 1);
@@ -257,7 +281,7 @@ void VideoOverlayWidget::update_log(QPainter& painter){
257281
y -= LOG_LINE_SPACING;
258282
}
259283
}
260-
void VideoOverlayWidget::update_stats(QPainter& painter){
284+
void VideoOverlayWidget::render_stats(QPainter& painter){
261285
const double TEXT_SIZE = 0.02;
262286
const double ROW_HEIGHT = 0.03;
263287

SerialPrograms/Source/CommonFramework/VideoPipeline/UI/VideoOverlayWidget.h

Lines changed: 42 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ class VideoOverlayWidget : public QWidget, private VideoOverlaySession::ContentL
2020
public:
2121
static constexpr bool DEFAULT_ENABLE_BOXES = true;
2222
static constexpr bool DEFAULT_ENABLE_TEXT = true;
23+
static constexpr bool DEFAULT_ENABLE_IMAGES = true;
2324
static constexpr bool DEFAULT_ENABLE_LOG = false;
2425
static constexpr bool DEFAULT_ENABLE_STATS = true;
2526

@@ -32,33 +33,61 @@ class VideoOverlayWidget : public QWidget, private VideoOverlaySession::ContentL
3233

3334
// Asynchronous changes to the overlays.
3435

35-
virtual void enabled_boxes(bool enabled) override;
36-
virtual void enabled_text (bool enabled) override;
37-
virtual void enabled_log (bool enabled) override;
38-
virtual void enabled_stats(bool enabled) override;
39-
40-
virtual void update_boxes(const std::shared_ptr<const std::vector<OverlayBox>>& boxes) override;
41-
virtual void update_text (const std::shared_ptr<const std::vector<OverlayText>>& texts) override;
42-
virtual void update_log (const std::shared_ptr<const std::vector<OverlayLogLine>>& texts) override;
43-
virtual void update_stats(const std::list<OverlayStat*>* stats) override;
36+
// callback function from VideoOverlaySession on overlay boxes enabled
37+
virtual void on_overlay_enabled_boxes (bool enabled) override{async_update();}
38+
// callback function from VideoOverlaySession on overlay text enabled
39+
virtual void on_overlay_enabled_text (bool enabled) override{async_update();}
40+
// callback function from VideoOverlaySession on overlay images enabled
41+
virtual void on_overlay_enabled_images(bool enabled) override{async_update();}
42+
// callback function from VideoOverlaySession on overlay log enabled
43+
virtual void on_overlay_enabled_log (bool enabled) override{async_update();}
44+
// callback function from VideoOverlaySession on overlay stats enabled
45+
virtual void on_overlay_enabled_stats (bool enabled) override{async_update();}
46+
47+
// callback function from VideoOverlaySession on overlay boxes updated
48+
virtual void on_overlay_update_boxes (const std::shared_ptr<const std::vector<OverlayBox>>& boxes) override;
49+
// callback function from VideoOverlaySession on overlay text updated
50+
virtual void on_overlay_update_text (const std::shared_ptr<const std::vector<OverlayText>>& texts) override;
51+
// callback function from VideoOverlaySession on overlay images updated
52+
virtual void on_overlay_update_images(const std::shared_ptr<const std::vector<OverlayImage>>& images) override;
53+
// callback function from VideoOverlaySession on overlay images updated
54+
virtual void on_overlay_update_log (const std::shared_ptr<const std::vector<OverlayLogLine>>& logs) override;
55+
// callback function from VideoOverlaySession on overlay stats updated
56+
virtual void on_overlay_update_stats (const std::list<OverlayStat*>* stats) override;
4457

4558
virtual void on_watchdog_timeout() override;
4659

4760
virtual void resizeEvent(QResizeEvent* event) override;
61+
// render video overlay, override QWidget::paintEvent()
4862
virtual void paintEvent(QPaintEvent*) override;
4963

5064
private:
51-
void update_boxes(QPainter& painter);
52-
void update_text (QPainter& painter);
53-
void update_log (QPainter& painter);
54-
void update_stats(QPainter& painter);
65+
// Call QWidget::update() to notify Qt to schedule a re-rendering of the overlay widget.
66+
//
67+
// Threads other than the main thread may change video overlay, causing this VideoOverlayWidget, which
68+
// listens to video overlay change, to get called on the non-main thread.
69+
// Since QWidget::update() must be called on the main thread to not crash, we have to use this
70+
// async_update() to avoid that.
71+
void async_update();
72+
73+
// render overlay boxes
74+
void render_boxes (QPainter& painter);
75+
// render overlay texts
76+
void render_text (QPainter& painter);
77+
// render overlay images
78+
void render_images (QPainter& painter);
79+
// render overlay log lines
80+
void render_log (QPainter& painter);
81+
// render overlay stats
82+
void render_stats (QPainter& painter);
5583

5684
private:
5785
VideoOverlaySession& m_session;
5886

5987
SpinLock m_lock;
6088
std::shared_ptr<const std::vector<OverlayBox>> m_boxes;
6189
std::shared_ptr<const std::vector<OverlayText>> m_texts;
90+
std::shared_ptr<const std::vector<OverlayImage>> m_images;
6291
std::shared_ptr<const std::vector<OverlayLogLine>> m_log;
6392
const std::list<OverlayStat*>* m_stats;
6493
};

SerialPrograms/Source/CommonFramework/VideoPipeline/VideoOverlay.h

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,11 @@
1515

1616
namespace PokemonAutomation{
1717

18-
18+
// Interface to add overlay objects (e.g. bounding boxes and texts) on top of rendered
19+
// video stream
20+
// The implementation is in CommonFramework/VideoPipeline/VideoOverlaySession.h:VideoOverlaySession
21+
// The reason to create this interface is to reduce the header dependency on other code that relies
22+
// on the overlay.
1923
class VideoOverlay{
2024
public:
2125
VideoOverlay();
@@ -38,8 +42,8 @@ class VideoOverlay{
3842

3943
// Asychronously, add a text, `OverlayText` as part of the video overlay.
4044
// Once added, `text` cannot be destroyed until after `VideoOverlay::remove_text()` is called to remove it.
41-
// If a `text` with the same address is added, it will override the old text's position, content and color. You only need
42-
// to call `remove_text()` once to remove it.
45+
// If a `text` with the same address is added, it will override the old text's position, content and color.
46+
// You only need to call `remove_text()` once to remove it.
4347
//
4448
// Can use `OverlayTextScope: public OverlayText` to handle text removal automatically when it's destroyed.
4549
virtual void add_text(const OverlayText& text) = 0;
@@ -49,6 +53,17 @@ class VideoOverlay{
4953
// See `add_text()` for more info on managing texts.
5054
virtual void remove_text(const OverlayText& text) = 0;
5155

56+
// Asychronously, add an image, `OverlayImage` as part of the video overlay.
57+
// Allow transparency to create mask overlay.
58+
// Once added, `image` cannot be destroyed until after `VideoOverlay::remove_image()` is called to remove it.
59+
// If an `image` with the same address is added, it will override the old content. You only need
60+
// to call `remove_text()` once to remove it.
61+
virtual void add_image(const OverlayImage& image) = 0;
62+
// Asychronously, remove an added `OverlayImage`.
63+
// The OverlayImage must be already added.
64+
// See `add_image()` for more info on managing overlay images.
65+
virtual void remove_image(const OverlayImage& image) = 0;
66+
5267
// Asynchronously, add a log message to the screen. The older messages added via `add_log_text()`
5368
// will be placed higher like in the logging window.
5469
// Use `OverlayLogTextScope` to remove the log messages automatically.

SerialPrograms/Source/CommonFramework/VideoPipeline/VideoOverlayOption.cpp

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ namespace PokemonAutomation{
1313

1414
const std::string VideoOverlayOption::JSON_BOXES = "Boxes";
1515
const std::string VideoOverlayOption::JSON_TEXT = "Text";
16+
const std::string VideoOverlayOption::JSON_IMAGES = "Images";
1617
const std::string VideoOverlayOption::JSON_LOG = "Log";
1718
const std::string VideoOverlayOption::JSON_STATS = "Stats";
1819

@@ -37,6 +38,12 @@ void VideoOverlayOption::load_json(const JsonValue& json){
3738
if (obj->read_boolean(tmp, JSON_TEXT)){
3839
text.store(tmp, std::memory_order_relaxed);
3940
}
41+
if (obj->read_boolean(tmp, JSON_IMAGES)){
42+
images.store(tmp, std::memory_order_relaxed);
43+
} else{
44+
// by default the image overlay is on
45+
images.store(true, std::memory_order_relaxed);
46+
}
4047
if (obj->read_boolean(tmp, JSON_LOG)){
4148
log.store(tmp, std::memory_order_relaxed);
4249
}
@@ -46,10 +53,11 @@ void VideoOverlayOption::load_json(const JsonValue& json){
4653
}
4754
JsonValue VideoOverlayOption::to_json() const{
4855
JsonObject root;
49-
root[JSON_BOXES] = boxes.load(std::memory_order_relaxed);
50-
root[JSON_TEXT] = text.load(std::memory_order_relaxed);
51-
root[JSON_LOG] = log.load(std::memory_order_relaxed);
52-
root[JSON_STATS] = stats.load(std::memory_order_relaxed);
56+
root[JSON_BOXES] = boxes.load(std::memory_order_relaxed);
57+
root[JSON_TEXT] = text.load(std::memory_order_relaxed);
58+
root[JSON_IMAGES] = images.load(std::memory_order_relaxed);
59+
root[JSON_LOG] = log.load(std::memory_order_relaxed);
60+
root[JSON_STATS] = stats.load(std::memory_order_relaxed);
5361
return root;
5462
}
5563

SerialPrograms/Source/CommonFramework/VideoPipeline/VideoOverlayOption.h

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,12 @@ namespace PokemonAutomation{
1515
class JsonValue;
1616

1717

18+
// store data related to the checkerboxes on UI: whether user enabled box overlay,
19+
// text overlay, etc.
1820
class VideoOverlayOption{
1921
static const std::string JSON_BOXES;
2022
static const std::string JSON_TEXT;
23+
static const std::string JSON_IMAGES;
2124
static const std::string JSON_LOG;
2225
static const std::string JSON_STATS;
2326

@@ -28,10 +31,11 @@ class VideoOverlayOption{
2831
JsonValue to_json() const;
2932

3033
public:
31-
std::atomic<bool> boxes;
32-
std::atomic<bool> text;
33-
std::atomic<bool> log;
34-
std::atomic<bool> stats;
34+
std::atomic<bool> boxes; // whether user enabled box overlay
35+
std::atomic<bool> text; // whether user enabled text overlay
36+
std::atomic<bool> images; // whether user enabled image overlay
37+
std::atomic<bool> log; // whether user enabled log overlay
38+
std::atomic<bool> stats; // whether user enabled stats overlay
3539
};
3640

3741

0 commit comments

Comments
 (0)