X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=analyzer.cpp;h=d48176036a1df67c3b9e132757351c4c9fb2e726;hb=96cb6414f85e0ef4d660b7bd56267303e80fcd05;hp=eb1f5170be9b617ffe69173988e1f87b7ce812af;hpb=a03af0e6a77a14eb9c93e2fd1cf94a28302bdb6e;p=nageru diff --git a/analyzer.cpp b/analyzer.cpp index eb1f517..d481760 100644 --- a/analyzer.cpp +++ b/analyzer.cpp @@ -1,7 +1,10 @@ #include "analyzer.h" #include +#include +#include #include +#include #include #include @@ -11,14 +14,38 @@ #include "mixer.h" #include "ui_analyzer.h" +// QCustomPlot includes qopenglfunctions.h, which #undefs all of the epoxy +// definitions (ugh) and doesn't put back any others (ugh). Add the ones we +// need back. + +#define glBindBuffer epoxy_glBindBuffer +#define glBindFramebuffer epoxy_glBindFramebuffer +#define glBufferData epoxy_glBufferData +#define glDeleteBuffers epoxy_glDeleteBuffers +#define glDisable epoxy_glDisable +#define glGenBuffers epoxy_glGenBuffers +#define glGetError epoxy_glGetError +#define glReadPixels epoxy_glReadPixels +#define glUnmapBuffer epoxy_glUnmapBuffer +#define glWaitSync epoxy_glWaitSync + using namespace std; Analyzer::Analyzer() - : ui(new Ui::Analyzer) + : ui(new Ui::Analyzer), + grabbed_image(global_flags.width, global_flags.height, QImage::Format_ARGB32_Premultiplied) { ui->setupUi(this); - //connect(ui->button_box, &QDialogButtonBox::accepted, [this]{ this->close(); }); + surface = create_surface(QSurfaceFormat::defaultFormat()); + context = create_context(surface); + if (!make_current(context, surface)) { + printf("oops\n"); + exit(1); + } + + grab_timer.setSingleShot(true); + connect(&grab_timer, &QTimer::timeout, bind(&Analyzer::grab_clicked, this)); ui->input_box->addItem("Live", Mixer::OUTPUT_LIVE); ui->input_box->addItem("Preview", Mixer::OUTPUT_PREVIEW); @@ -29,25 +56,44 @@ Analyzer::Analyzer() ui->input_box->addItem(QString::fromStdString(name), channel); } + ui->grab_frequency_box->addItem("Never", 0); + ui->grab_frequency_box->addItem("100 ms", 100); + ui->grab_frequency_box->addItem("1 sec", 1000); + ui->grab_frequency_box->addItem("10 sec", 10000); + ui->grab_frequency_box->setCurrentIndex(2); + connect(ui->grab_btn, &QPushButton::clicked, bind(&Analyzer::grab_clicked, this)); connect(ui->input_box, static_cast(&QComboBox::currentIndexChanged), bind(&Analyzer::signal_changed, this)); signal_changed(); - - surface = create_surface(QSurfaceFormat::defaultFormat()); - context = create_context(surface); - - if (!make_current(context, surface)) { - printf("oops\n"); - exit(1); - } + ui->grabbed_frame_label->installEventFilter(this); glGenBuffers(1, &pbo); glBindBuffer(GL_PIXEL_PACK_BUFFER_ARB, pbo); glBufferData(GL_PIXEL_PACK_BUFFER_ARB, global_flags.width * global_flags.height * 4, NULL, GL_STREAM_READ); + + ui->histogram->xAxis->setVisible(true); + ui->histogram->yAxis->setVisible(false); + ui->histogram->xAxis->setRange(0, 255); } Analyzer::~Analyzer() { + delete_context(context); + delete surface; +} + +void Analyzer::update_channel_name(Mixer::Output output, const string &name) +{ + if (output >= Mixer::OUTPUT_INPUT0) { + int index = (output - Mixer::OUTPUT_INPUT0) + 2; + ui->input_box->setItemText(index, QString::fromStdString(name)); + } +} + +void Analyzer::mixer_shutting_down() +{ + ui->display->shutdown(); + if (!make_current(context, surface)) { printf("oops\n"); exit(1); @@ -57,8 +103,6 @@ Analyzer::~Analyzer() if (resource_pool != nullptr) { resource_pool->clean_context(); } - delete_context(context); - delete surface; // TODO? } void Analyzer::grab_clicked() @@ -72,7 +116,7 @@ void Analyzer::grab_clicked() Mixer::DisplayFrame frame; if (!global_mixer->get_display_frame(channel, &frame)) { - printf("Not ready yet\n"); + // Not ready yet. return; } @@ -107,14 +151,19 @@ void Analyzer::grab_clicked() unsigned char *buf = (unsigned char *)glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY); check_error(); - QImage img(global_flags.width, global_flags.height, QImage::Format_ARGB32_Premultiplied); size_t pitch = global_flags.width * 4; for (int y = 0; y < global_flags.height; ++y) { - memcpy(img.scanLine(global_flags.height - y - 1), buf + y * pitch, pitch); + memcpy(grabbed_image.scanLine(global_flags.height - y - 1), buf + y * pitch, pitch); + } + + { + char buf[256]; + snprintf(buf, sizeof(buf), "Grabbed frame (%dx%d)", global_flags.width, global_flags.height); + ui->grabbed_frame_sublabel->setText(buf); } QPixmap pixmap; - pixmap.convertFromImage(QImage(img)); + pixmap.convertFromImage(grabbed_image); ui->grabbed_frame_label->setPixmap(pixmap); int r_hist[256] = {0}, g_hist[256] = {0}, b_hist[256] = {0}; @@ -137,24 +186,209 @@ void Analyzer::grab_clicked() glBindFramebuffer(GL_FRAMEBUFFER, 0); check_error(); - printf("R hist:"); - for (unsigned i = 0; i < 256; ++i) { printf(" %d", r_hist[i]); } - printf("\n"); - printf("G hist:"); - for (unsigned i = 0; i < 256; ++i) { printf(" %d", g_hist[i]); } - printf("\n"); - printf("B hist:"); - for (unsigned i = 0; i < 256; ++i) { printf(" %d", b_hist[i]); } - printf("\n"); + QVector r_vec(256), g_vec(256), b_vec(256), x_vec(256); + double max = 0.0; + for (unsigned i = 0; i < 256; ++i) { + x_vec[i] = i; + r_vec[i] = log(r_hist[i] + 1.0); + g_vec[i] = log(g_hist[i] + 1.0); + b_vec[i] = log(b_hist[i] + 1.0); + + max = std::max(max, r_vec[i]); + max = std::max(max, g_vec[i]); + max = std::max(max, b_vec[i]); + } + + ui->histogram->clearGraphs(); + ui->histogram->addGraph(); + ui->histogram->graph(0)->setData(x_vec, r_vec); + ui->histogram->graph(0)->setPen(QPen(Qt::red)); + ui->histogram->graph(0)->setBrush(QBrush(QColor(255, 127, 127, 80))); + ui->histogram->addGraph(); + ui->histogram->graph(1)->setData(x_vec, g_vec); + ui->histogram->graph(1)->setPen(QPen(Qt::green)); + ui->histogram->graph(1)->setBrush(QBrush(QColor(127, 255, 127, 80))); + ui->histogram->addGraph(); + ui->histogram->graph(2)->setData(x_vec, b_vec); + ui->histogram->graph(2)->setPen(QPen(Qt::blue)); + ui->histogram->graph(2)->setBrush(QBrush(QColor(127, 127, 255, 80))); + + ui->histogram->xAxis->setVisible(true); + ui->histogram->yAxis->setVisible(false); + ui->histogram->xAxis->setRange(0, 255); + ui->histogram->yAxis->setRange(0, max); + ui->histogram->replot(); resource_pool->release_2d_texture(fbo_tex); check_error(); resource_pool->release_fbo(fbo); check_error(); + + if (last_x >= 0 && last_y >= 0) { + grab_pixel(last_x, last_y); + } + + if (isVisible()) { + grab_timer.stop(); + + // Set up the next autograb if configured. + int delay = ui->grab_frequency_box->currentData().toInt(nullptr); + if (delay > 0) { + grab_timer.start(delay); + } + } } void Analyzer::signal_changed() { Mixer::Output channel = static_cast(ui->input_box->currentData().value()); ui->display->set_output(channel); + grab_clicked(); +} + +bool Analyzer::eventFilter(QObject *watched, QEvent *event) +{ + if (event->type() == QEvent::MouseMove && watched->isWidgetType()) { + const QMouseEvent *mouse_event = (QMouseEvent *)event; + last_x = mouse_event->x(); + last_y = mouse_event->y(); + grab_pixel(mouse_event->x(), mouse_event->y()); + } + if (event->type() == QEvent::Leave && watched->isWidgetType()) { + last_x = last_y = -1; + ui->coord_label->setText("Selected coordinate (x,y): (none)"); + ui->red_label->setText(u8"—"); + ui->green_label->setText(u8"—"); + ui->blue_label->setText(u8"—"); + ui->hex_label->setText(u8"#—"); + } + return false; +} + +void Analyzer::grab_pixel(int x, int y) +{ + const QPixmap *pixmap = ui->grabbed_frame_label->pixmap(); + if (pixmap != nullptr) { + x = lrint(x * double(pixmap->width()) / ui->grabbed_frame_label->width()); + y = lrint(y * double(pixmap->height()) / ui->grabbed_frame_label->height()); + x = std::min(x, pixmap->width() - 1); + y = std::min(y, pixmap->height() - 1); + + char buf[256]; + snprintf(buf, sizeof(buf), "Selected coordinate (x,y): (%d,%d)", x, y); + ui->coord_label->setText(buf); + + QRgb pixel = grabbed_image.pixel(x, y); + ui->red_label->setText(QString::fromStdString(to_string(qRed(pixel)))); + ui->green_label->setText(QString::fromStdString(to_string(qGreen(pixel)))); + ui->blue_label->setText(QString::fromStdString(to_string(qBlue(pixel)))); + + snprintf(buf, sizeof(buf), "#%02x%02x%02x", qRed(pixel), qGreen(pixel), qBlue(pixel)); + ui->hex_label->setText(buf); + } +} + +void Analyzer::resizeEvent(QResizeEvent* event) +{ + QMainWindow::resizeEvent(event); + + // Ask for a relayout, but only after the event loop is done doing relayout + // on everything else. + QMetaObject::invokeMethod(this, "relayout", Qt::QueuedConnection); +} + +void Analyzer::showEvent(QShowEvent *event) +{ + grab_clicked(); +} + +void Analyzer::relayout() +{ + double aspect = double(global_flags.width) / global_flags.height; + + // Left pane (2/5 of the width). + { + int width = ui->left_pane->geometry().width(); + int height = ui->left_pane->geometry().height(); + + // Figure out how much space everything that's non-responsive needs. + int remaining_height = height - ui->left_pane->spacing() * (ui->left_pane->count() - 1); + + remaining_height -= ui->input_box->geometry().height(); + ui->left_pane->setStretch(2, ui->grab_btn->geometry().height()); + + remaining_height -= ui->grab_btn->geometry().height(); + ui->left_pane->setStretch(3, ui->grab_btn->geometry().height()); + + remaining_height -= ui->histogram_label->geometry().height(); + ui->left_pane->setStretch(5, ui->histogram_label->geometry().height()); + + // The histogram's minimumHeight returns 0, so let's set a reasonable minimum for it. + int min_histogram_height = 50; + remaining_height -= min_histogram_height; + + // Allocate so that the display is 16:9, if possible. + unsigned wanted_display_height = width / aspect; + unsigned display_height; + unsigned margin = 0; + if (remaining_height >= int(wanted_display_height)) { + display_height = wanted_display_height; + } else { + display_height = remaining_height; + int display_width = lrint(display_height * aspect); + margin = (width - display_width) / 2; + } + ui->left_pane->setStretch(1, display_height); + ui->display_left_spacer->changeSize(margin, 1); + ui->display_right_spacer->changeSize(margin, 1); + + remaining_height -= display_height; + + // Figure out if we can do the histogram at 16:9. + remaining_height += min_histogram_height; + unsigned histogram_height; + if (remaining_height >= int(wanted_display_height)) { + histogram_height = wanted_display_height; + } else { + histogram_height = remaining_height; + } + remaining_height -= histogram_height; + ui->left_pane->setStretch(4, histogram_height); + + ui->left_pane->setStretch(0, remaining_height / 2); + ui->left_pane->setStretch(6, remaining_height / 2); + } + + // Right pane (remaining 3/5 of the width). + { + int width = ui->right_pane->geometry().width(); + int height = ui->right_pane->geometry().height(); + + // Figure out how much space everything that's non-responsive needs. + int remaining_height = height - ui->right_pane->spacing() * (ui->right_pane->count() - 1); + remaining_height -= ui->grabbed_frame_sublabel->geometry().height(); + remaining_height -= ui->coord_label->geometry().height(); + remaining_height -= ui->color_hbox->geometry().height(); + + // Allocate so that the display is 16:9, if possible. + unsigned wanted_display_height = width / aspect; + unsigned display_height; + unsigned margin = 0; + if (remaining_height >= int(wanted_display_height)) { + display_height = wanted_display_height; + } else { + display_height = remaining_height; + int display_width = lrint(display_height * aspect); + margin = (width - display_width) / 2; + } + ui->right_pane->setStretch(1, display_height); + ui->grabbed_frame_left_spacer->changeSize(margin, 1); + ui->grabbed_frame_right_spacer->changeSize(margin, 1); + remaining_height -= display_height; + + if (remaining_height < 0) remaining_height = 0; + + ui->right_pane->setStretch(0, remaining_height / 2); + ui->right_pane->setStretch(5, remaining_height / 2); + } }