5 #include <QDialogButtonBox>
10 #include <QPushButton>
18 #include <movit/resource_pool.h>
19 #include <movit/util.h>
31 #include "shared/context.h"
34 #include "ui_analyzer.h"
39 : ui(new Ui::Analyzer),
40 grabbed_image(global_flags.width, global_flags.height, QImage::Format_ARGB32_Premultiplied)
44 surface = create_surface(QSurfaceFormat::defaultFormat());
45 context = create_context(surface);
46 if (!make_current(context, surface)) {
51 grab_timer.setSingleShot(true);
52 connect(&grab_timer, &QTimer::timeout, bind(&Analyzer::grab_clicked, this));
54 ui->input_box->addItem("Live", Mixer::OUTPUT_LIVE);
55 ui->input_box->addItem("Preview", Mixer::OUTPUT_PREVIEW);
56 unsigned num_channels = global_mixer->get_num_channels();
57 for (unsigned channel_idx = 0; channel_idx < num_channels; ++channel_idx) {
58 Mixer::Output channel = static_cast<Mixer::Output>(Mixer::OUTPUT_INPUT0 + channel_idx);
59 string name = global_mixer->get_channel_name(channel);
60 ui->input_box->addItem(QString::fromStdString(name), channel);
63 ui->grab_frequency_box->addItem("Never", 0);
64 ui->grab_frequency_box->addItem("100 ms", 100);
65 ui->grab_frequency_box->addItem("1 sec", 1000);
66 ui->grab_frequency_box->addItem("10 sec", 10000);
67 ui->grab_frequency_box->setCurrentIndex(2);
69 connect(ui->grab_btn, &QPushButton::clicked, bind(&Analyzer::grab_clicked, this));
70 connect(ui->input_box, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), bind(&Analyzer::signal_changed, this));
72 ui->grabbed_frame_label->installEventFilter(this);
74 glGenBuffers(1, &pbo);
75 glBindBuffer(GL_PIXEL_PACK_BUFFER_ARB, pbo);
76 glBufferData(GL_PIXEL_PACK_BUFFER_ARB, global_flags.width * global_flags.height * 4, nullptr, GL_STREAM_READ);
81 delete_context(context);
85 void Analyzer::update_channel_name(Mixer::Output output, const string &name)
87 if (output >= Mixer::OUTPUT_INPUT0) {
88 int index = (output - Mixer::OUTPUT_INPUT0) + 2;
89 ui->input_box->setItemText(index, QString::fromStdString(name));
93 void Analyzer::mixer_shutting_down()
95 ui->display->shutdown();
97 if (!make_current(context, surface)) {
101 glDeleteBuffers(1, &pbo);
103 if (resource_pool != nullptr) {
104 resource_pool->clean_context();
108 void Analyzer::grab_clicked()
110 Mixer::Output channel = static_cast<Mixer::Output>(ui->input_box->currentData().value<int>());
112 if (!make_current(context, surface)) {
117 Mixer::DisplayFrame frame;
118 if (!global_mixer->get_display_frame(channel, &frame)) {
123 // Set up an FBO to render into.
124 if (resource_pool == nullptr) {
125 resource_pool = frame.chain->get_resource_pool();
127 assert(resource_pool == frame.chain->get_resource_pool());
129 GLuint fbo_tex = resource_pool->create_2d_texture(GL_RGBA8, global_flags.width, global_flags.height);
131 GLuint fbo = resource_pool->create_fbo(fbo_tex);
134 glWaitSync(frame.ready_fence.get(), /*flags=*/0, GL_TIMEOUT_IGNORED);
138 glDisable(GL_FRAMEBUFFER_SRGB);
140 frame.chain->render_to_fbo(fbo, global_flags.width, global_flags.height);
143 // Read back to memory.
144 glBindFramebuffer(GL_FRAMEBUFFER, fbo);
146 glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo);
148 glReadPixels(0, 0, global_flags.width, global_flags.height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, BUFFER_OFFSET(0));
151 unsigned char *buf = (unsigned char *)glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY);
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);
161 snprintf(buf, sizeof(buf), "Grabbed frame (%dx%d)", global_flags.width, global_flags.height);
162 ui->grabbed_frame_sublabel->setText(buf);
166 pixmap.convertFromImage(grabbed_image);
167 ui->grabbed_frame_label->setPixmap(pixmap);
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) {
182 glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
184 glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
186 glBindFramebuffer(GL_FRAMEBUFFER, 0);
189 vector<double> r_vec(256), g_vec(256), b_vec(256);
190 for (unsigned i = 0; i < 256; ++i) {
191 r_vec[i] = log(r_hist[i] + 1.0);
192 g_vec[i] = log(g_hist[i] + 1.0);
193 b_vec[i] = log(b_hist[i] + 1.0);
196 ui->histogram->set_histograms(std::move(r_vec), std::move(g_vec), std::move(b_vec));
198 resource_pool->release_2d_texture(fbo_tex);
200 resource_pool->release_fbo(fbo);
203 if (last_x >= 0 && last_y >= 0) {
204 grab_pixel(last_x, last_y);
210 // Set up the next autograb if configured.
211 int delay = ui->grab_frequency_box->currentData().toInt(nullptr);
213 grab_timer.start(delay);
218 void Analyzer::signal_changed()
220 Mixer::Output channel = static_cast<Mixer::Output>(ui->input_box->currentData().value<int>());
221 ui->display->set_output(channel);
225 bool Analyzer::eventFilter(QObject *watched, QEvent *event)
227 if (event->type() == QEvent::MouseMove && watched->isWidgetType()) {
228 const QMouseEvent *mouse_event = (QMouseEvent *)event;
229 last_x = mouse_event->x();
230 last_y = mouse_event->y();
231 grab_pixel(mouse_event->x(), mouse_event->y());
233 if (event->type() == QEvent::Leave && watched->isWidgetType()) {
234 last_x = last_y = -1;
235 ui->coord_label->setText("Selected coordinate (x,y): (none)");
236 ui->red_label->setText(u8"—");
237 ui->green_label->setText(u8"—");
238 ui->blue_label->setText(u8"—");
239 ui->hex_label->setText(u8"#—");
244 void Analyzer::grab_pixel(int x, int y)
246 QPixmap pixmap = ui->grabbed_frame_label->pixmap(Qt::ReturnByValue);
247 x = lrint(x * double(pixmap.width()) / ui->grabbed_frame_label->width());
248 y = lrint(y * double(pixmap.height()) / ui->grabbed_frame_label->height());
249 x = std::min(x, pixmap.width() - 1);
250 y = std::min(y, pixmap.height() - 1);
253 snprintf(buf, sizeof(buf), "Selected coordinate (x,y): (%d,%d)", x, y);
254 ui->coord_label->setText(buf);
256 QRgb pixel = grabbed_image.pixel(x, y);
257 ui->red_label->setText(QString::fromStdString(to_string(qRed(pixel))));
258 ui->green_label->setText(QString::fromStdString(to_string(qGreen(pixel))));
259 ui->blue_label->setText(QString::fromStdString(to_string(qBlue(pixel))));
261 snprintf(buf, sizeof(buf), "#%02x%02x%02x", qRed(pixel), qGreen(pixel), qBlue(pixel));
262 ui->hex_label->setText(buf);
265 void Analyzer::resizeEvent(QResizeEvent* event)
267 QMainWindow::resizeEvent(event);
269 // Ask for a relayout, but only after the event loop is done doing relayout
270 // on everything else.
271 QMetaObject::invokeMethod(this, "relayout", Qt::QueuedConnection);
274 void Analyzer::showEvent(QShowEvent *event)
279 void Analyzer::relayout()
281 double aspect = double(global_flags.width) / global_flags.height;
283 // Left pane (2/5 of the width).
285 int width = ui->left_pane->geometry().width();
286 int height = ui->left_pane->geometry().height();
288 // Figure out how much space everything that's non-responsive needs.
289 int remaining_height = height - ui->left_pane->spacing() * (ui->left_pane->count() - 1);
291 remaining_height -= ui->input_box->geometry().height();
292 ui->left_pane->setStretch(2, ui->grab_btn->geometry().height());
294 remaining_height -= ui->grab_btn->geometry().height();
295 ui->left_pane->setStretch(3, ui->grab_btn->geometry().height());
297 remaining_height -= ui->histogram_label->geometry().height();
298 ui->left_pane->setStretch(5, ui->histogram_label->geometry().height());
300 // The histogram's minimumHeight returns 0, so let's set a reasonable minimum for it.
301 int min_histogram_height = 50;
302 remaining_height -= min_histogram_height;
304 // Allocate so that the display is 16:9, if possible.
305 unsigned wanted_display_height = width / aspect;
306 unsigned display_height;
308 if (remaining_height >= int(wanted_display_height)) {
309 display_height = wanted_display_height;
311 display_height = remaining_height;
312 int display_width = lrint(display_height * aspect);
313 margin = (width - display_width) / 2;
315 ui->left_pane->setStretch(1, display_height);
316 ui->display_left_spacer->changeSize(margin, 1);
317 ui->display_right_spacer->changeSize(margin, 1);
319 remaining_height -= display_height;
321 // Figure out if we can do the histogram at 16:9.
322 remaining_height += min_histogram_height;
323 unsigned histogram_height;
324 if (remaining_height >= int(wanted_display_height)) {
325 histogram_height = wanted_display_height;
327 histogram_height = remaining_height;
329 remaining_height -= histogram_height;
330 ui->left_pane->setStretch(4, histogram_height);
332 ui->left_pane->setStretch(0, remaining_height / 2);
333 ui->left_pane->setStretch(6, remaining_height / 2);
336 // Right pane (remaining 3/5 of the width).
338 int width = ui->right_pane->geometry().width();
339 int height = ui->right_pane->geometry().height();
341 // Figure out how much space everything that's non-responsive needs.
342 int remaining_height = height - ui->right_pane->spacing() * (ui->right_pane->count() - 1);
343 remaining_height -= ui->grabbed_frame_sublabel->geometry().height();
344 remaining_height -= ui->coord_label->geometry().height();
345 remaining_height -= ui->color_hbox->geometry().height();
347 // Allocate so that the display is 16:9, if possible.
348 unsigned wanted_display_height = width / aspect;
349 unsigned display_height;
351 if (remaining_height >= int(wanted_display_height)) {
352 display_height = wanted_display_height;
354 display_height = remaining_height;
355 int display_width = lrint(display_height * aspect);
356 margin = (width - display_width) / 2;
358 ui->right_pane->setStretch(1, display_height);
359 ui->grabbed_frame_left_spacer->changeSize(margin, 1);
360 ui->grabbed_frame_right_spacer->changeSize(margin, 1);
361 remaining_height -= display_height;
363 if (remaining_height < 0) remaining_height = 0;
365 ui->right_pane->setStretch(0, remaining_height / 2);
366 ui->right_pane->setStretch(5, remaining_height / 2);