]> git.sesse.net Git - nageru/blob - nageru/analyzer.cpp
IWYU-fix nageru/*.cpp.
[nageru] / nageru / analyzer.cpp
1 #include "analyzer.h"
2
3 #include <QComboBox>
4 #include <QEvent>
5 #include <QDialogButtonBox>
6 #include <QImage>
7 #include <QMainWindow>
8 #include <QMouseEvent>
9 #include <QPixmap>
10 #include <QPushButton>
11 #include <QPen>
12 #include <QRgb>
13 #include <QSurface>
14 #include <QTimer>
15
16 #include <algorithm>
17 #include <functional>
18 #include <movit/resource_pool.h>
19 #include <movit/util.h>
20 #include <string>
21 #include <utility>
22 #include <vector>
23
24 #include <assert.h>
25 #include <math.h>
26 #include <stddef.h>
27 #include <stdint.h>
28 #include <stdio.h>
29 #include <string.h>
30
31 #include "shared/context.h"
32 #include "flags.h"
33 #include "mixer.h"
34 #include "ui_analyzer.h"
35
36 using namespace std;
37
38 Analyzer::Analyzer()
39         : ui(new Ui::Analyzer),
40           grabbed_image(global_flags.width, global_flags.height, QImage::Format_ARGB32_Premultiplied)
41 {
42         ui->setupUi(this);
43
44         surface = create_surface(QSurfaceFormat::defaultFormat());
45         context = create_context(surface);
46         if (!make_current(context, surface)) {
47                 printf("oops\n");
48                 abort();
49         }
50
51         grab_timer.setSingleShot(true);
52         connect(&grab_timer, &QTimer::timeout, bind(&Analyzer::grab_clicked, this));
53
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);
61         }
62
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);
68
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));
71         signal_changed();
72         ui->grabbed_frame_label->installEventFilter(this);
73
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);
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                 abort();
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                 abort();
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         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);
194         }
195
196         ui->histogram->set_histograms(std::move(r_vec), std::move(g_vec), std::move(b_vec));
197
198         resource_pool->release_2d_texture(fbo_tex);
199         check_error();
200         resource_pool->release_fbo(fbo);
201         check_error();
202
203         if (last_x >= 0 && last_y >= 0) {
204                 grab_pixel(last_x, last_y);
205         }
206
207         if (isVisible()) {
208                 grab_timer.stop();
209
210                 // Set up the next autograb if configured.
211                 int delay = ui->grab_frequency_box->currentData().toInt(nullptr);
212                 if (delay > 0) {
213                         grab_timer.start(delay);
214                 }
215         }
216 }
217
218 void Analyzer::signal_changed()
219 {
220         Mixer::Output channel = static_cast<Mixer::Output>(ui->input_box->currentData().value<int>());
221         ui->display->set_output(channel);
222         grab_clicked();
223 }
224
225 bool Analyzer::eventFilter(QObject *watched, QEvent *event)
226 {
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());
232         }
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"#—");
240         }
241         return false;
242 }
243
244 void Analyzer::grab_pixel(int x, int y)
245 {
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);
251
252         char buf[256];
253         snprintf(buf, sizeof(buf), "Selected coordinate (x,y): (%d,%d)", x, y);
254         ui->coord_label->setText(buf);
255
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))));
260
261         snprintf(buf, sizeof(buf), "#%02x%02x%02x", qRed(pixel), qGreen(pixel), qBlue(pixel));
262         ui->hex_label->setText(buf);
263 }
264
265 void Analyzer::resizeEvent(QResizeEvent* event)
266 {
267         QMainWindow::resizeEvent(event);
268
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);
272 }
273
274 void Analyzer::showEvent(QShowEvent *event)
275 {
276         grab_clicked();
277 }
278
279 void Analyzer::relayout()
280 {
281         double aspect = double(global_flags.width) / global_flags.height;
282
283         // Left pane (2/5 of the width).
284         {
285                 int width = ui->left_pane->geometry().width();
286                 int height = ui->left_pane->geometry().height();
287
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);
290
291                 remaining_height -= ui->input_box->geometry().height();
292                 ui->left_pane->setStretch(2, ui->grab_btn->geometry().height());
293
294                 remaining_height -= ui->grab_btn->geometry().height();
295                 ui->left_pane->setStretch(3, ui->grab_btn->geometry().height());
296
297                 remaining_height -= ui->histogram_label->geometry().height();
298                 ui->left_pane->setStretch(5, ui->histogram_label->geometry().height());
299
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;
303
304                 // Allocate so that the display is 16:9, if possible.
305                 unsigned wanted_display_height = width / aspect;
306                 unsigned display_height;
307                 unsigned margin = 0;
308                 if (remaining_height >= int(wanted_display_height)) {
309                         display_height = wanted_display_height;
310                 } else {
311                         display_height = remaining_height;
312                         int display_width = lrint(display_height * aspect);
313                         margin = (width - display_width) / 2;
314                 }
315                 ui->left_pane->setStretch(1, display_height);
316                 ui->display_left_spacer->changeSize(margin, 1);
317                 ui->display_right_spacer->changeSize(margin, 1);
318
319                 remaining_height -= display_height;
320
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;
326                 } else {
327                         histogram_height = remaining_height;
328                 }
329                 remaining_height -= histogram_height;
330                 ui->left_pane->setStretch(4, histogram_height);
331
332                 ui->left_pane->setStretch(0, remaining_height / 2);
333                 ui->left_pane->setStretch(6, remaining_height / 2);
334         }
335
336         // Right pane (remaining 3/5 of the width).
337         {
338                 int width = ui->right_pane->geometry().width();
339                 int height = ui->right_pane->geometry().height();
340
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();
346
347                 // Allocate so that the display is 16:9, if possible.
348                 unsigned wanted_display_height = width / aspect;
349                 unsigned display_height;
350                 unsigned margin = 0;
351                 if (remaining_height >= int(wanted_display_height)) {
352                         display_height = wanted_display_height;
353                 } else {
354                         display_height = remaining_height;
355                         int display_width = lrint(display_height * aspect);
356                         margin = (width - display_width) / 2;
357                 }
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;
362
363                 if (remaining_height < 0) remaining_height = 0;
364
365                 ui->right_pane->setStretch(0, remaining_height / 2);
366                 ui->right_pane->setStretch(5, remaining_height / 2);
367         }
368 }