]> git.sesse.net Git - nageru/blob - analyzer.cpp
Implement the RGB histograms in the frame 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
68 Analyzer::~Analyzer()
69 {
70         if (!make_current(context, surface)) {
71                 printf("oops\n");
72                 exit(1);
73         }
74         glDeleteBuffers(1, &pbo);
75         check_error();
76         if (resource_pool != nullptr) {
77                 resource_pool->clean_context();
78         }
79         delete_context(context);
80         delete surface;
81 }
82
83 void Analyzer::grab_clicked()
84 {
85         Mixer::Output channel = static_cast<Mixer::Output>(ui->input_box->currentData().value<int>());
86
87         if (!make_current(context, surface)) {
88                 printf("oops\n");
89                 exit(1);
90         }
91
92         Mixer::DisplayFrame frame;
93         if (!global_mixer->get_display_frame(channel, &frame)) {
94                 printf("Not ready yet\n");
95                 return;
96         }
97
98         // Set up an FBO to render into.
99         if (resource_pool == nullptr) {
100                 resource_pool = frame.chain->get_resource_pool();
101         } else {
102                 assert(resource_pool == frame.chain->get_resource_pool());
103         }
104         GLuint fbo_tex = resource_pool->create_2d_texture(GL_RGBA8, global_flags.width, global_flags.height);
105         check_error();
106         GLuint fbo = resource_pool->create_fbo(fbo_tex);
107         check_error();
108
109         glWaitSync(frame.ready_fence.get(), /*flags=*/0, GL_TIMEOUT_IGNORED);
110         check_error();
111         frame.setup_chain();
112         check_error();
113         glDisable(GL_FRAMEBUFFER_SRGB);
114         check_error();
115         frame.chain->render_to_fbo(fbo, global_flags.width, global_flags.height);
116         check_error();
117
118         // Read back to memory.
119         glBindFramebuffer(GL_FRAMEBUFFER, fbo);
120         check_error();
121         glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo);
122         check_error();
123         glReadPixels(0, 0, global_flags.width, global_flags.height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, BUFFER_OFFSET(0));
124         check_error();
125
126         unsigned char *buf = (unsigned char *)glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY);
127         check_error();
128
129         size_t pitch = global_flags.width * 4;
130         for (int y = 0; y < global_flags.height; ++y) {
131                 memcpy(grabbed_image.scanLine(global_flags.height - y - 1), buf + y * pitch, pitch);
132         }
133
134         {
135                 char buf[256];
136                 snprintf(buf, sizeof(buf), "Grabbed frame (%dx%d)", global_flags.width, global_flags.height);
137                 ui->grabbed_frame_sublabel->setText(buf);
138         }
139
140         QPixmap pixmap;
141         pixmap.convertFromImage(grabbed_image);
142         ui->grabbed_frame_label->setPixmap(pixmap);
143
144         int r_hist[256] = {0}, g_hist[256] = {0}, b_hist[256] = {0};
145         const unsigned char *ptr = buf;
146         for (int i = 0; i < global_flags.height * global_flags.width; ++i) {
147                 uint8_t b = *ptr++;
148                 uint8_t g = *ptr++;
149                 uint8_t r = *ptr++;
150                 ++ptr;
151
152                 ++r_hist[r];
153                 ++g_hist[g];
154                 ++b_hist[b];
155         }
156
157         glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
158         check_error();
159         glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
160         check_error();
161         glBindFramebuffer(GL_FRAMEBUFFER, 0);
162         check_error();
163
164         QVector<double> r_vec(256), g_vec(256), b_vec(256), x_vec(256);
165         double max = 0.0;
166         for (unsigned i = 0; i < 256; ++i) {
167                 x_vec[i] = i;
168                 r_vec[i] = log(r_hist[i] + 1.0);
169                 g_vec[i] = log(g_hist[i] + 1.0);
170                 b_vec[i] = log(b_hist[i] + 1.0);
171
172                 max = std::max(max, r_vec[i]);
173                 max = std::max(max, g_vec[i]);
174                 max = std::max(max, b_vec[i]);
175         }
176
177         ui->histogram->clearGraphs();
178         ui->histogram->addGraph();
179         ui->histogram->graph(0)->setData(x_vec, r_vec);
180         ui->histogram->graph(0)->setPen(QPen(Qt::red));
181         ui->histogram->graph(0)->setBrush(QBrush(QColor(255, 127, 127, 80)));
182         ui->histogram->addGraph();
183         ui->histogram->graph(1)->setData(x_vec, g_vec);
184         ui->histogram->graph(1)->setPen(QPen(Qt::green));
185         ui->histogram->graph(1)->setBrush(QBrush(QColor(127, 255, 127, 80)));
186         ui->histogram->addGraph();
187         ui->histogram->graph(2)->setData(x_vec, b_vec);
188         ui->histogram->graph(2)->setPen(QPen(Qt::blue));
189         ui->histogram->graph(2)->setBrush(QBrush(QColor(127, 127, 255, 80)));
190
191         ui->histogram->xAxis->setVisible(true);
192         ui->histogram->yAxis->setVisible(false);
193         ui->histogram->xAxis->setRange(0, 255);
194         ui->histogram->yAxis->setRange(0, max);
195         ui->histogram->replot();
196
197         resource_pool->release_2d_texture(fbo_tex);
198         check_error();
199         resource_pool->release_fbo(fbo);
200         check_error();
201 }
202
203 void Analyzer::signal_changed()
204 {
205         Mixer::Output channel = static_cast<Mixer::Output>(ui->input_box->currentData().value<int>());
206         ui->display->set_output(channel);
207 }
208
209 bool Analyzer::eventFilter(QObject *watched, QEvent *event)
210 {
211         if (event->type() == QEvent::MouseMove &&
212             watched->isWidgetType()) {
213                 const QMouseEvent *mouse_event = (QMouseEvent *)event;
214                 const QPixmap *pixmap = ui->grabbed_frame_label->pixmap();
215                 if (pixmap != nullptr) {
216                         int x = lrint(mouse_event->x() * double(pixmap->width()) / ui->grabbed_frame_label->width());
217                         int y = lrint(mouse_event->y() * double(pixmap->height()) / ui->grabbed_frame_label->height());
218                         x = std::min(x, pixmap->width() - 1);
219                         y = std::min(y, pixmap->height() - 1);
220                         QRgb pixel = grabbed_image.pixel(x, y);
221                         ui->red_label->setText(QString::fromStdString(to_string(qRed(pixel))));
222                         ui->green_label->setText(QString::fromStdString(to_string(qGreen(pixel))));
223                         ui->blue_label->setText(QString::fromStdString(to_string(qBlue(pixel))));
224
225                         char buf[256];
226                         snprintf(buf, sizeof(buf), "#%02x%02x%02x", qRed(pixel), qGreen(pixel), qBlue(pixel));
227                         ui->hex_label->setText(buf);
228                 }
229         }
230         return false;
231 }