]> git.sesse.net Git - nageru/blob - nageru/analyzer.cpp
Remove the QCustomPlot dependency.
[nageru] / nageru / analyzer.cpp
1 #include "analyzer.h"
2
3 #include <QDialogButtonBox>
4 #include <QMouseEvent>
5 #include <QPen>
6 #include <QSurface>
7 #include <QTimer>
8
9 #include <movit/resource_pool.h>
10 #include <movit/util.h>
11
12 #include "shared/context.h"
13 #include "flags.h"
14 #include "mixer.h"
15 #include "ui_analyzer.h"
16
17 using namespace std;
18
19 Analyzer::Analyzer()
20         : ui(new Ui::Analyzer),
21           grabbed_image(global_flags.width, global_flags.height, QImage::Format_ARGB32_Premultiplied)
22 {
23         ui->setupUi(this);
24
25         surface = create_surface(QSurfaceFormat::defaultFormat());
26         context = create_context(surface);
27         if (!make_current(context, surface)) {
28                 printf("oops\n");
29                 abort();
30         }
31
32         grab_timer.setSingleShot(true);
33         connect(&grab_timer, &QTimer::timeout, bind(&Analyzer::grab_clicked, this));
34
35         ui->input_box->addItem("Live", Mixer::OUTPUT_LIVE);
36         ui->input_box->addItem("Preview", Mixer::OUTPUT_PREVIEW);
37         unsigned num_channels = global_mixer->get_num_channels();
38         for (unsigned channel_idx = 0; channel_idx < num_channels; ++channel_idx) {
39                 Mixer::Output channel = static_cast<Mixer::Output>(Mixer::OUTPUT_INPUT0 + channel_idx); 
40                 string name = global_mixer->get_channel_name(channel);
41                 ui->input_box->addItem(QString::fromStdString(name), channel);
42         }
43
44         ui->grab_frequency_box->addItem("Never", 0);
45         ui->grab_frequency_box->addItem("100 ms", 100);
46         ui->grab_frequency_box->addItem("1 sec", 1000);
47         ui->grab_frequency_box->addItem("10 sec", 10000);
48         ui->grab_frequency_box->setCurrentIndex(2);
49
50         connect(ui->grab_btn, &QPushButton::clicked, bind(&Analyzer::grab_clicked, this));
51         connect(ui->input_box, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), bind(&Analyzer::signal_changed, this));
52         signal_changed();
53         ui->grabbed_frame_label->installEventFilter(this);
54
55         glGenBuffers(1, &pbo);
56         glBindBuffer(GL_PIXEL_PACK_BUFFER_ARB, pbo);
57         glBufferData(GL_PIXEL_PACK_BUFFER_ARB, global_flags.width * global_flags.height * 4, nullptr, GL_STREAM_READ);
58 }
59
60 Analyzer::~Analyzer()
61 {
62         delete_context(context);
63         delete surface;
64 }
65
66 void Analyzer::update_channel_name(Mixer::Output output, const string &name)
67 {
68         if (output >= Mixer::OUTPUT_INPUT0) {
69                 int index = (output - Mixer::OUTPUT_INPUT0) + 2;
70                 ui->input_box->setItemText(index, QString::fromStdString(name));
71         }
72 }
73
74 void Analyzer::mixer_shutting_down()
75 {
76         ui->display->shutdown();
77
78         if (!make_current(context, surface)) {
79                 printf("oops\n");
80                 abort();
81         }
82         glDeleteBuffers(1, &pbo);
83         check_error();
84         if (resource_pool != nullptr) {
85                 resource_pool->clean_context();
86         }
87 }
88
89 void Analyzer::grab_clicked()
90 {
91         Mixer::Output channel = static_cast<Mixer::Output>(ui->input_box->currentData().value<int>());
92
93         if (!make_current(context, surface)) {
94                 printf("oops\n");
95                 abort();
96         }
97
98         Mixer::DisplayFrame frame;
99         if (!global_mixer->get_display_frame(channel, &frame)) {
100                 // Not ready yet.
101                 return;
102         }
103
104         // Set up an FBO to render into.
105         if (resource_pool == nullptr) {
106                 resource_pool = frame.chain->get_resource_pool();
107         } else {
108                 assert(resource_pool == frame.chain->get_resource_pool());
109         }
110         GLuint fbo_tex = resource_pool->create_2d_texture(GL_RGBA8, global_flags.width, global_flags.height);
111         check_error();
112         GLuint fbo = resource_pool->create_fbo(fbo_tex);
113         check_error();
114
115         glWaitSync(frame.ready_fence.get(), /*flags=*/0, GL_TIMEOUT_IGNORED);
116         check_error();
117         frame.setup_chain();
118         check_error();
119         glDisable(GL_FRAMEBUFFER_SRGB);
120         check_error();
121         frame.chain->render_to_fbo(fbo, global_flags.width, global_flags.height);
122         check_error();
123
124         // Read back to memory.
125         glBindFramebuffer(GL_FRAMEBUFFER, fbo);
126         check_error();
127         glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo);
128         check_error();
129         glReadPixels(0, 0, global_flags.width, global_flags.height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, BUFFER_OFFSET(0));
130         check_error();
131
132         unsigned char *buf = (unsigned char *)glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY);
133         check_error();
134
135         size_t pitch = global_flags.width * 4;
136         for (int y = 0; y < global_flags.height; ++y) {
137                 memcpy(grabbed_image.scanLine(global_flags.height - y - 1), buf + y * pitch, pitch);
138         }
139
140         {
141                 char buf[256];
142                 snprintf(buf, sizeof(buf), "Grabbed frame (%dx%d)", global_flags.width, global_flags.height);
143                 ui->grabbed_frame_sublabel->setText(buf);
144         }
145
146         QPixmap pixmap;
147         pixmap.convertFromImage(grabbed_image);
148         ui->grabbed_frame_label->setPixmap(pixmap);
149
150         int r_hist[256] = {0}, g_hist[256] = {0}, b_hist[256] = {0};
151         const unsigned char *ptr = buf;
152         for (int i = 0; i < global_flags.height * global_flags.width; ++i) {
153                 uint8_t b = *ptr++;
154                 uint8_t g = *ptr++;
155                 uint8_t r = *ptr++;
156                 ++ptr;
157
158                 ++r_hist[r];
159                 ++g_hist[g];
160                 ++b_hist[b];
161         }
162
163         glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
164         check_error();
165         glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
166         check_error();
167         glBindFramebuffer(GL_FRAMEBUFFER, 0);
168         check_error();
169
170         vector<double> r_vec(256), g_vec(256), b_vec(256);
171         for (unsigned i = 0; i < 256; ++i) {
172                 r_vec[i] = log(r_hist[i] + 1.0);
173                 g_vec[i] = log(g_hist[i] + 1.0);
174                 b_vec[i] = log(b_hist[i] + 1.0);
175         }
176
177         ui->histogram->set_histograms(std::move(r_vec), std::move(g_vec), std::move(b_vec));
178
179         resource_pool->release_2d_texture(fbo_tex);
180         check_error();
181         resource_pool->release_fbo(fbo);
182         check_error();
183
184         if (last_x >= 0 && last_y >= 0) {
185                 grab_pixel(last_x, last_y);
186         }
187
188         if (isVisible()) {
189                 grab_timer.stop();
190
191                 // Set up the next autograb if configured.
192                 int delay = ui->grab_frequency_box->currentData().toInt(nullptr);
193                 if (delay > 0) {
194                         grab_timer.start(delay);
195                 }
196         }
197 }
198
199 void Analyzer::signal_changed()
200 {
201         Mixer::Output channel = static_cast<Mixer::Output>(ui->input_box->currentData().value<int>());
202         ui->display->set_output(channel);
203         grab_clicked();
204 }
205
206 bool Analyzer::eventFilter(QObject *watched, QEvent *event)
207 {
208         if (event->type() == QEvent::MouseMove && watched->isWidgetType()) {
209                 const QMouseEvent *mouse_event = (QMouseEvent *)event;
210                 last_x = mouse_event->x();
211                 last_y = mouse_event->y();
212                 grab_pixel(mouse_event->x(), mouse_event->y());
213         }
214         if (event->type() == QEvent::Leave && watched->isWidgetType()) {
215                 last_x = last_y = -1;
216                 ui->coord_label->setText("Selected coordinate (x,y): (none)");
217                 ui->red_label->setText(u8"—");
218                 ui->green_label->setText(u8"—");
219                 ui->blue_label->setText(u8"—");
220                 ui->hex_label->setText(u8"#—");
221         }
222         return false;
223 }
224
225 void Analyzer::grab_pixel(int x, int y)
226 {
227         QPixmap pixmap = ui->grabbed_frame_label->pixmap(Qt::ReturnByValue);
228         x = lrint(x * double(pixmap.width()) / ui->grabbed_frame_label->width());
229         y = lrint(y * double(pixmap.height()) / ui->grabbed_frame_label->height());
230         x = std::min(x, pixmap.width() - 1);
231         y = std::min(y, pixmap.height() - 1);
232
233         char buf[256];
234         snprintf(buf, sizeof(buf), "Selected coordinate (x,y): (%d,%d)", x, y);
235         ui->coord_label->setText(buf);
236
237         QRgb pixel = grabbed_image.pixel(x, y);
238         ui->red_label->setText(QString::fromStdString(to_string(qRed(pixel))));
239         ui->green_label->setText(QString::fromStdString(to_string(qGreen(pixel))));
240         ui->blue_label->setText(QString::fromStdString(to_string(qBlue(pixel))));
241
242         snprintf(buf, sizeof(buf), "#%02x%02x%02x", qRed(pixel), qGreen(pixel), qBlue(pixel));
243         ui->hex_label->setText(buf);
244 }
245
246 void Analyzer::resizeEvent(QResizeEvent* event)
247 {
248         QMainWindow::resizeEvent(event);
249
250         // Ask for a relayout, but only after the event loop is done doing relayout
251         // on everything else.
252         QMetaObject::invokeMethod(this, "relayout", Qt::QueuedConnection);
253 }
254
255 void Analyzer::showEvent(QShowEvent *event)
256 {
257         grab_clicked();
258 }
259
260 void Analyzer::relayout()
261 {
262         double aspect = double(global_flags.width) / global_flags.height;
263
264         // Left pane (2/5 of the width).
265         {
266                 int width = ui->left_pane->geometry().width();
267                 int height = ui->left_pane->geometry().height();
268
269                 // Figure out how much space everything that's non-responsive needs.
270                 int remaining_height = height - ui->left_pane->spacing() * (ui->left_pane->count() - 1);
271
272                 remaining_height -= ui->input_box->geometry().height();
273                 ui->left_pane->setStretch(2, ui->grab_btn->geometry().height());
274
275                 remaining_height -= ui->grab_btn->geometry().height();
276                 ui->left_pane->setStretch(3, ui->grab_btn->geometry().height());
277
278                 remaining_height -= ui->histogram_label->geometry().height();
279                 ui->left_pane->setStretch(5, ui->histogram_label->geometry().height());
280
281                 // The histogram's minimumHeight returns 0, so let's set a reasonable minimum for it.
282                 int min_histogram_height = 50;
283                 remaining_height -= min_histogram_height;
284
285                 // Allocate so that the display is 16:9, if possible.
286                 unsigned wanted_display_height = width / aspect;
287                 unsigned display_height;
288                 unsigned margin = 0;
289                 if (remaining_height >= int(wanted_display_height)) {
290                         display_height = wanted_display_height;
291                 } else {
292                         display_height = remaining_height;
293                         int display_width = lrint(display_height * aspect);
294                         margin = (width - display_width) / 2;
295                 }
296                 ui->left_pane->setStretch(1, display_height);
297                 ui->display_left_spacer->changeSize(margin, 1);
298                 ui->display_right_spacer->changeSize(margin, 1);
299
300                 remaining_height -= display_height;
301
302                 // Figure out if we can do the histogram at 16:9.
303                 remaining_height += min_histogram_height;
304                 unsigned histogram_height;
305                 if (remaining_height >= int(wanted_display_height)) {
306                         histogram_height = wanted_display_height;
307                 } else {
308                         histogram_height = remaining_height;
309                 }
310                 remaining_height -= histogram_height;
311                 ui->left_pane->setStretch(4, histogram_height);
312
313                 ui->left_pane->setStretch(0, remaining_height / 2);
314                 ui->left_pane->setStretch(6, remaining_height / 2);
315         }
316
317         // Right pane (remaining 3/5 of the width).
318         {
319                 int width = ui->right_pane->geometry().width();
320                 int height = ui->right_pane->geometry().height();
321
322                 // Figure out how much space everything that's non-responsive needs.
323                 int remaining_height = height - ui->right_pane->spacing() * (ui->right_pane->count() - 1);
324                 remaining_height -= ui->grabbed_frame_sublabel->geometry().height();
325                 remaining_height -= ui->coord_label->geometry().height();
326                 remaining_height -= ui->color_hbox->geometry().height();
327
328                 // Allocate so that the display is 16:9, if possible.
329                 unsigned wanted_display_height = width / aspect;
330                 unsigned display_height;
331                 unsigned margin = 0;
332                 if (remaining_height >= int(wanted_display_height)) {
333                         display_height = wanted_display_height;
334                 } else {
335                         display_height = remaining_height;
336                         int display_width = lrint(display_height * aspect);
337                         margin = (width - display_width) / 2;
338                 }
339                 ui->right_pane->setStretch(1, display_height);
340                 ui->grabbed_frame_left_spacer->changeSize(margin, 1);
341                 ui->grabbed_frame_right_spacer->changeSize(margin, 1);
342                 remaining_height -= display_height;
343
344                 if (remaining_height < 0) remaining_height = 0;
345
346                 ui->right_pane->setStretch(0, remaining_height / 2);
347                 ui->right_pane->setStretch(5, remaining_height / 2);
348         }
349 }