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