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