3 #include <QDialogButtonBox>
9 #include <movit/resource_pool.h>
10 #include <movit/util.h>
12 #include "shared/context.h"
15 #include "ui_analyzer.h"
20 : ui(new Ui::Analyzer),
21 grabbed_image(global_flags.width, global_flags.height, QImage::Format_ARGB32_Premultiplied)
25 surface = create_surface(QSurfaceFormat::defaultFormat());
26 context = create_context(surface);
27 if (!make_current(context, surface)) {
32 grab_timer.setSingleShot(true);
33 connect(&grab_timer, &QTimer::timeout, bind(&Analyzer::grab_clicked, this));
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);
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);
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 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);
62 delete_context(context);
66 void Analyzer::update_channel_name(Mixer::Output output, const string &name)
68 if (output >= Mixer::OUTPUT_INPUT0) {
69 int index = (output - Mixer::OUTPUT_INPUT0) + 2;
70 ui->input_box->setItemText(index, QString::fromStdString(name));
74 void Analyzer::mixer_shutting_down()
76 ui->display->shutdown();
78 if (!make_current(context, surface)) {
82 glDeleteBuffers(1, &pbo);
84 if (resource_pool != nullptr) {
85 resource_pool->clean_context();
89 void Analyzer::grab_clicked()
91 Mixer::Output channel = static_cast<Mixer::Output>(ui->input_box->currentData().value<int>());
93 if (!make_current(context, surface)) {
98 Mixer::DisplayFrame frame;
99 if (!global_mixer->get_display_frame(channel, &frame)) {
104 // Set up an FBO to render into.
105 if (resource_pool == nullptr) {
106 resource_pool = frame.chain->get_resource_pool();
108 assert(resource_pool == frame.chain->get_resource_pool());
110 GLuint fbo_tex = resource_pool->create_2d_texture(GL_RGBA8, global_flags.width, global_flags.height);
112 GLuint fbo = resource_pool->create_fbo(fbo_tex);
115 glWaitSync(frame.ready_fence.get(), /*flags=*/0, GL_TIMEOUT_IGNORED);
119 glDisable(GL_FRAMEBUFFER_SRGB);
121 frame.chain->render_to_fbo(fbo, global_flags.width, global_flags.height);
124 // Read back to memory.
125 glBindFramebuffer(GL_FRAMEBUFFER, fbo);
127 glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo);
129 glReadPixels(0, 0, global_flags.width, global_flags.height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, BUFFER_OFFSET(0));
132 unsigned char *buf = (unsigned char *)glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY);
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);
142 snprintf(buf, sizeof(buf), "Grabbed frame (%dx%d)", global_flags.width, global_flags.height);
143 ui->grabbed_frame_sublabel->setText(buf);
147 pixmap.convertFromImage(grabbed_image);
148 ui->grabbed_frame_label->setPixmap(pixmap);
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) {
163 glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
165 glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
167 glBindFramebuffer(GL_FRAMEBUFFER, 0);
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);
177 ui->histogram->set_histograms(std::move(r_vec), std::move(g_vec), std::move(b_vec));
179 resource_pool->release_2d_texture(fbo_tex);
181 resource_pool->release_fbo(fbo);
184 if (last_x >= 0 && last_y >= 0) {
185 grab_pixel(last_x, last_y);
191 // Set up the next autograb if configured.
192 int delay = ui->grab_frequency_box->currentData().toInt(nullptr);
194 grab_timer.start(delay);
199 void Analyzer::signal_changed()
201 Mixer::Output channel = static_cast<Mixer::Output>(ui->input_box->currentData().value<int>());
202 ui->display->set_output(channel);
206 bool Analyzer::eventFilter(QObject *watched, QEvent *event)
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());
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"#—");
225 void Analyzer::grab_pixel(int x, int y)
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);
234 snprintf(buf, sizeof(buf), "Selected coordinate (x,y): (%d,%d)", x, y);
235 ui->coord_label->setText(buf);
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))));
242 snprintf(buf, sizeof(buf), "#%02x%02x%02x", qRed(pixel), qGreen(pixel), qBlue(pixel));
243 ui->hex_label->setText(buf);
246 void Analyzer::resizeEvent(QResizeEvent* event)
248 QMainWindow::resizeEvent(event);
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);
255 void Analyzer::showEvent(QShowEvent *event)
260 void Analyzer::relayout()
262 double aspect = double(global_flags.width) / global_flags.height;
264 // Left pane (2/5 of the width).
266 int width = ui->left_pane->geometry().width();
267 int height = ui->left_pane->geometry().height();
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);
272 remaining_height -= ui->input_box->geometry().height();
273 ui->left_pane->setStretch(2, ui->grab_btn->geometry().height());
275 remaining_height -= ui->grab_btn->geometry().height();
276 ui->left_pane->setStretch(3, ui->grab_btn->geometry().height());
278 remaining_height -= ui->histogram_label->geometry().height();
279 ui->left_pane->setStretch(5, ui->histogram_label->geometry().height());
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;
285 // Allocate so that the display is 16:9, if possible.
286 unsigned wanted_display_height = width / aspect;
287 unsigned display_height;
289 if (remaining_height >= int(wanted_display_height)) {
290 display_height = wanted_display_height;
292 display_height = remaining_height;
293 int display_width = lrint(display_height * aspect);
294 margin = (width - display_width) / 2;
296 ui->left_pane->setStretch(1, display_height);
297 ui->display_left_spacer->changeSize(margin, 1);
298 ui->display_right_spacer->changeSize(margin, 1);
300 remaining_height -= display_height;
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;
308 histogram_height = remaining_height;
310 remaining_height -= histogram_height;
311 ui->left_pane->setStretch(4, histogram_height);
313 ui->left_pane->setStretch(0, remaining_height / 2);
314 ui->left_pane->setStretch(6, remaining_height / 2);
317 // Right pane (remaining 3/5 of the width).
319 int width = ui->right_pane->geometry().width();
320 int height = ui->right_pane->geometry().height();
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();
328 // Allocate so that the display is 16:9, if possible.
329 unsigned wanted_display_height = width / aspect;
330 unsigned display_height;
332 if (remaining_height >= int(wanted_display_height)) {
333 display_height = wanted_display_height;
335 display_height = remaining_height;
336 int display_width = lrint(display_height * aspect);
337 margin = (width - display_width) / 2;
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;
344 if (remaining_height < 0) remaining_height = 0;
346 ui->right_pane->setStretch(0, remaining_height / 2);
347 ui->right_pane->setStretch(5, remaining_height / 2);