3 #include <QDialogButtonBox>
8 #include <movit/resource_pool.h>
9 #include <movit/util.h>
14 #include "ui_analyzer.h"
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
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
34 : ui(new Ui::Analyzer),
35 grabbed_image(global_flags.width, global_flags.height, QImage::Format_ARGB32_Premultiplied)
39 //connect(ui->button_box, &QDialogButtonBox::accepted, [this]{ this->close(); });
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);
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));
53 ui->grabbed_frame_label->installEventFilter(this);
55 surface = create_surface(QSurfaceFormat::defaultFormat());
56 context = create_context(surface);
58 if (!make_current(context, surface)) {
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);
67 ui->histogram->xAxis->setVisible(true);
68 ui->histogram->yAxis->setVisible(false);
69 ui->histogram->xAxis->setRange(0, 255);
74 delete_context(context);
78 void Analyzer::mixer_shutting_down()
80 ui->display->shutdown();
82 if (!make_current(context, surface)) {
86 glDeleteBuffers(1, &pbo);
88 if (resource_pool != nullptr) {
89 resource_pool->clean_context();
93 void Analyzer::grab_clicked()
95 Mixer::Output channel = static_cast<Mixer::Output>(ui->input_box->currentData().value<int>());
97 if (!make_current(context, surface)) {
102 Mixer::DisplayFrame frame;
103 if (!global_mixer->get_display_frame(channel, &frame)) {
104 printf("Not ready yet\n");
108 // Set up an FBO to render into.
109 if (resource_pool == nullptr) {
110 resource_pool = frame.chain->get_resource_pool();
112 assert(resource_pool == frame.chain->get_resource_pool());
114 GLuint fbo_tex = resource_pool->create_2d_texture(GL_RGBA8, global_flags.width, global_flags.height);
116 GLuint fbo = resource_pool->create_fbo(fbo_tex);
119 glWaitSync(frame.ready_fence.get(), /*flags=*/0, GL_TIMEOUT_IGNORED);
123 glDisable(GL_FRAMEBUFFER_SRGB);
125 frame.chain->render_to_fbo(fbo, global_flags.width, global_flags.height);
128 // Read back to memory.
129 glBindFramebuffer(GL_FRAMEBUFFER, fbo);
131 glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo);
133 glReadPixels(0, 0, global_flags.width, global_flags.height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, BUFFER_OFFSET(0));
136 unsigned char *buf = (unsigned char *)glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY);
139 size_t pitch = global_flags.width * 4;
140 for (int y = 0; y < global_flags.height; ++y) {
141 memcpy(grabbed_image.scanLine(global_flags.height - y - 1), buf + y * pitch, pitch);
146 snprintf(buf, sizeof(buf), "Grabbed frame (%dx%d)", global_flags.width, global_flags.height);
147 ui->grabbed_frame_sublabel->setText(buf);
151 pixmap.convertFromImage(grabbed_image);
152 ui->grabbed_frame_label->setPixmap(pixmap);
154 int r_hist[256] = {0}, g_hist[256] = {0}, b_hist[256] = {0};
155 const unsigned char *ptr = buf;
156 for (int i = 0; i < global_flags.height * global_flags.width; ++i) {
167 glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
169 glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
171 glBindFramebuffer(GL_FRAMEBUFFER, 0);
174 QVector<double> r_vec(256), g_vec(256), b_vec(256), x_vec(256);
176 for (unsigned i = 0; i < 256; ++i) {
178 r_vec[i] = log(r_hist[i] + 1.0);
179 g_vec[i] = log(g_hist[i] + 1.0);
180 b_vec[i] = log(b_hist[i] + 1.0);
182 max = std::max(max, r_vec[i]);
183 max = std::max(max, g_vec[i]);
184 max = std::max(max, b_vec[i]);
187 ui->histogram->clearGraphs();
188 ui->histogram->addGraph();
189 ui->histogram->graph(0)->setData(x_vec, r_vec);
190 ui->histogram->graph(0)->setPen(QPen(Qt::red));
191 ui->histogram->graph(0)->setBrush(QBrush(QColor(255, 127, 127, 80)));
192 ui->histogram->addGraph();
193 ui->histogram->graph(1)->setData(x_vec, g_vec);
194 ui->histogram->graph(1)->setPen(QPen(Qt::green));
195 ui->histogram->graph(1)->setBrush(QBrush(QColor(127, 255, 127, 80)));
196 ui->histogram->addGraph();
197 ui->histogram->graph(2)->setData(x_vec, b_vec);
198 ui->histogram->graph(2)->setPen(QPen(Qt::blue));
199 ui->histogram->graph(2)->setBrush(QBrush(QColor(127, 127, 255, 80)));
201 ui->histogram->xAxis->setVisible(true);
202 ui->histogram->yAxis->setVisible(false);
203 ui->histogram->xAxis->setRange(0, 255);
204 ui->histogram->yAxis->setRange(0, max);
205 ui->histogram->replot();
207 resource_pool->release_2d_texture(fbo_tex);
209 resource_pool->release_fbo(fbo);
213 void Analyzer::signal_changed()
215 Mixer::Output channel = static_cast<Mixer::Output>(ui->input_box->currentData().value<int>());
216 ui->display->set_output(channel);
219 bool Analyzer::eventFilter(QObject *watched, QEvent *event)
221 if (event->type() == QEvent::MouseMove &&
222 watched->isWidgetType()) {
223 const QMouseEvent *mouse_event = (QMouseEvent *)event;
224 const QPixmap *pixmap = ui->grabbed_frame_label->pixmap();
225 if (pixmap != nullptr) {
226 int x = lrint(mouse_event->x() * double(pixmap->width()) / ui->grabbed_frame_label->width());
227 int y = lrint(mouse_event->y() * double(pixmap->height()) / ui->grabbed_frame_label->height());
228 x = std::min(x, pixmap->width() - 1);
229 y = std::min(y, pixmap->height() - 1);
230 QRgb pixel = grabbed_image.pixel(x, y);
231 ui->red_label->setText(QString::fromStdString(to_string(qRed(pixel))));
232 ui->green_label->setText(QString::fromStdString(to_string(qGreen(pixel))));
233 ui->blue_label->setText(QString::fromStdString(to_string(qBlue(pixel))));
236 snprintf(buf, sizeof(buf), "#%02x%02x%02x", qRed(pixel), qGreen(pixel), qBlue(pixel));
237 ui->hex_label->setText(buf);
243 void Analyzer::resizeEvent(QResizeEvent* event)
245 QMainWindow::resizeEvent(event);
247 // Ask for a relayout, but only after the event loop is done doing relayout
248 // on everything else.
249 QMetaObject::invokeMethod(this, "relayout", Qt::QueuedConnection);
252 void Analyzer::relayout()
254 double aspect = double(global_flags.width) / global_flags.height;
256 // Left pane (2/5 of the width).
258 int width = ui->left_pane->geometry().width();
259 int height = ui->left_pane->geometry().height();
261 // Figure out how much space everything that's non-responsive needs.
262 int remaining_height = height - ui->left_pane->spacing() * (ui->left_pane->count() - 1);
264 remaining_height -= ui->grab_btn->geometry().height();
265 ui->left_pane->setStretch(2, ui->grab_btn->geometry().height());
267 remaining_height -= ui->histogram_label->geometry().height();
268 ui->left_pane->setStretch(4, ui->histogram_label->geometry().height());
270 // The histogram's minimumHeight returns 0, so let's set a reasonable minimum for it.
271 int min_histogram_height = 50;
272 remaining_height -= min_histogram_height;
274 // Allocate so that the display is 16:9, if possible.
275 unsigned wanted_display_height = width / aspect;
276 unsigned display_height;
278 if (remaining_height >= int(wanted_display_height)) {
279 display_height = wanted_display_height;
281 display_height = remaining_height;
282 int display_width = lrint(display_height * aspect);
283 margin = (width - display_width) / 2;
285 ui->left_pane->setStretch(1, display_height);
286 ui->display_left_spacer->changeSize(margin, 1);
287 ui->display_right_spacer->changeSize(margin, 1);
289 remaining_height -= display_height;
291 // Figure out if we can do the histogram at 16:9.
292 remaining_height += min_histogram_height;
293 unsigned histogram_height;
294 if (remaining_height >= int(wanted_display_height)) {
295 histogram_height = wanted_display_height;
297 histogram_height = remaining_height;
299 remaining_height -= histogram_height;
300 ui->left_pane->setStretch(3, histogram_height);
302 ui->left_pane->setStretch(0, remaining_height / 2);
303 ui->left_pane->setStretch(5, remaining_height / 2);
306 // Right pane (remaining 3/5 of the width).
308 int width = ui->right_pane->geometry().width();
309 int height = ui->right_pane->geometry().height();
311 // Figure out how much space everything that's non-responsive needs.
312 int remaining_height = height - ui->right_pane->spacing() * (ui->right_pane->count() - 1);
313 remaining_height -= ui->grabbed_frame_sublabel->geometry().height();
314 remaining_height -= ui->coord_label->geometry().height();
315 remaining_height -= ui->color_hbox->geometry().height();
317 // Allocate so that the display is 16:9, if possible.
318 unsigned wanted_display_height = width / aspect;
319 unsigned display_height;
321 if (remaining_height >= int(wanted_display_height)) {
322 display_height = wanted_display_height;
324 display_height = remaining_height;
325 int display_width = lrint(display_height * aspect);
326 margin = (width - display_width) / 2;
328 ui->right_pane->setStretch(1, display_height);
329 ui->grabbed_frame_left_spacer->changeSize(margin, 1);
330 ui->grabbed_frame_right_spacer->changeSize(margin, 1);
331 remaining_height -= display_height;
333 if (remaining_height < 0) remaining_height = 0;
335 ui->right_pane->setStretch(0, remaining_height / 2);
336 ui->right_pane->setStretch(5, remaining_height / 2);