From: Steinar H. Gunderson Date: Tue, 3 Nov 2015 00:10:44 +0000 (+0100) Subject: Hook up white balance into the theme. X-Git-Tag: 1.0.0~171 X-Git-Url: https://git.sesse.net/?p=nageru;a=commitdiff_plain;h=9f1e8fb59e1b68b68b4bb1a05e1f4ee37ea47471 Hook up white balance into the theme. --- diff --git a/README b/README index 723ec4b..3324310 100644 --- a/README +++ b/README @@ -7,7 +7,7 @@ Features (those marked with * are still in progress or not started yet): on my Thinkpad X240[1]); almost all pixel processing is done on the GPU. - High output quality; Lanczos3 scaling, subpixel precision everywhere, - white balance adjustment (*), mix of 16- and 32-bit floating point + white balance adjustment, mix of 16- and 32-bit floating point for intermediate calculations, dithered output. - Proper sound support: Syncing of multiple unrelated sources through diff --git a/main.cpp b/main.cpp index 3969634..53ba77d 100644 --- a/main.cpp +++ b/main.cpp @@ -43,6 +43,8 @@ int main(int argc, char *argv[]) mainWindow.resize(QSize(1500, 685)); mainWindow.show(); + app.installEventFilter(&mainWindow); // For white balance color picking. + int rc = app.exec(); global_mixer->quit(); delete global_mixer; diff --git a/mainwindow.cpp b/mainwindow.cpp index 2324996..be46ded 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -74,7 +74,6 @@ void MainWindow::mixer_created(Mixer *mixer) Ui::Display *ui_display = new Ui::Display; ui_display->setupUi(preview); ui_display->label->setText(mixer->get_channel_name(output).c_str()); - ui_display->wb_button->setVisible(mixer->get_supports_set_wb(output)); ui_display->display->set_output(output); ui->preview_displays->insertWidget(previews.size(), preview, 1); previews.push_back(ui_display); @@ -85,6 +84,10 @@ void MainWindow::mixer_created(Mixer *mixer) // Hook up the keyboard key. QShortcut *shortcut = new QShortcut(QKeySequence(Qt::Key_1 + i), this); connect(shortcut, &QShortcut::activated, std::bind(&MainWindow::channel_clicked, this, i)); + + // Hook up the white balance button (irrelevant if invisible). + ui_display->wb_button->setVisible(mixer->get_supports_set_wb(output)); + connect(ui_display->wb_button, &QPushButton::clicked, std::bind(&MainWindow::wb_button_clicked, this, i)); } mixer->set_audio_level_callback([this](float level_lufs, float peak_db, float global_level_lufs, float range_low_lufs, float range_high_lufs){ @@ -163,5 +166,58 @@ void MainWindow::transition_clicked(int transition_number) void MainWindow::channel_clicked(int channel_number) { - global_mixer->channel_clicked(channel_number); + if (current_wb_pick_display == channel_number) { + // The picking was already done from eventFilter(), since we don't get + // the mouse pointer here. + } else { + global_mixer->channel_clicked(channel_number); + } +} + +void MainWindow::wb_button_clicked(int channel_number) +{ + current_wb_pick_display = channel_number; + QApplication::setOverrideCursor(Qt::CrossCursor); +} + +bool MainWindow::eventFilter(QObject *watched, QEvent *event) +{ + if (current_wb_pick_display != -1 && + event->type() == QEvent::MouseButtonRelease && + watched->isWidgetType()) { + QApplication::restoreOverrideCursor(); + if (watched == previews[current_wb_pick_display]->display) { + const QMouseEvent *mouse_event = (QMouseEvent *)event; + set_white_balance(current_wb_pick_display, mouse_event->x(), mouse_event->y()); + } else { + // The user clicked on something else, give up. + // (The click goes through, which might not be ideal, but, yes.) + current_wb_pick_display = -1; + } + } + return false; +} + +namespace { + +double srgb_to_linear(double x) +{ + if (x < 0.04045) { + return x / 12.92; + } else { + return pow((x + 0.055) / 1.055, 2.4); + } +} + +} // namespace + +void MainWindow::set_white_balance(int channel_number, int x, int y) +{ + // FIXME: This is wrong. We should really reset the white balance and then + // re-render, but renderToPixmap() crashes for me. + QRgb reference_color = previews[channel_number]->display->grabFrameBuffer().pixel(x, y); + double r = srgb_to_linear(qRed(reference_color) / 255.0); + double g = srgb_to_linear(qGreen(reference_color) / 255.0); + double b = srgb_to_linear(qBlue(reference_color) / 255.0); + global_mixer->set_wb(Mixer::OUTPUT_INPUT0 + channel_number, r, g, b); } diff --git a/mainwindow.h b/mainwindow.h index 6a8e9db..28c4705 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -28,13 +28,18 @@ public: public slots: void transition_clicked(int transition_number); void channel_clicked(int channel_number); + void wb_button_clicked(int channel_number); void set_transition_names(std::vector transition_names); void relayout(); private: + bool eventFilter(QObject *watched, QEvent *event) override; + void set_white_balance(int channel_number, int x, int y); + Ui::MainWindow *ui; QPushButton *transition_btn1, *transition_btn2, *transition_btn3; std::vector previews; + int current_wb_pick_display = -1; }; extern MainWindow *global_mainwindow; diff --git a/mixer.h b/mixer.h index b02b997..02c06ed 100644 --- a/mixer.h +++ b/mixer.h @@ -121,6 +121,11 @@ public: return theme->get_supports_set_wb(channel); } + void set_wb(unsigned channel, double r, double g, double b) const + { + theme->set_wb(channel, r, g, b); + } + private: void bm_frame(unsigned card_index, uint16_t timecode, FrameAllocator::Frame video_frame, size_t video_offset, uint16_t video_format, diff --git a/theme.cpp b/theme.cpp index f8e0d8e..4ea8cd7 100644 --- a/theme.cpp +++ b/theme.cpp @@ -249,6 +249,24 @@ int Effect_set_int(lua_State *L) return 0; } +int Effect_set_vec3(lua_State *L) +{ + assert(lua_gettop(L) == 5); + Effect *effect = (Effect *)get_effect(L, 1); + size_t len; + const char* cstr = lua_tolstring(L, 2, &len); + std::string key(cstr, len); + float v[3]; + v[0] = luaL_checknumber(L, 3); + v[1] = luaL_checknumber(L, 4); + v[2] = luaL_checknumber(L, 5); + if (!effect->set_vec3(key, v)) { + luaL_error(L, "Effect refused set_vec3(\"%s\", %f, %f, %f) (invalid key?)", cstr, + v[0], v[1], v[2]); + } + return 0; +} + int Effect_set_vec4(lua_State *L) { assert(lua_gettop(L) == 6); @@ -285,6 +303,7 @@ const luaL_Reg WhiteBalanceEffect_funcs[] = { { "new", WhiteBalanceEffect_new }, { "set_float", Effect_set_float }, { "set_int", Effect_set_int }, + { "set_vec3", Effect_set_vec3 }, { "set_vec4", Effect_set_vec4 }, { NULL, NULL } }; @@ -293,6 +312,7 @@ const luaL_Reg ResampleEffect_funcs[] = { { "new", ResampleEffect_new }, { "set_float", Effect_set_float }, { "set_int", Effect_set_int }, + { "set_vec3", Effect_set_vec3 }, { "set_vec4", Effect_set_vec4 }, { NULL, NULL } }; @@ -301,6 +321,7 @@ const luaL_Reg PaddingEffect_funcs[] = { { "new", PaddingEffect_new }, { "set_float", Effect_set_float }, { "set_int", Effect_set_int }, + { "set_vec3", Effect_set_vec3 }, { "set_vec4", Effect_set_vec4 }, { NULL, NULL } }; @@ -309,6 +330,7 @@ const luaL_Reg IntegralPaddingEffect_funcs[] = { { "new", IntegralPaddingEffect_new }, { "set_float", Effect_set_float }, { "set_int", Effect_set_int }, + { "set_vec3", Effect_set_vec3 }, { "set_vec4", Effect_set_vec4 }, { NULL, NULL } }; @@ -317,6 +339,7 @@ const luaL_Reg OverlayEffect_funcs[] = { { "new", OverlayEffect_new }, { "set_float", Effect_set_float }, { "set_int", Effect_set_int }, + { "set_vec3", Effect_set_vec3 }, { "set_vec4", Effect_set_vec4 }, { NULL, NULL } }; @@ -325,6 +348,7 @@ const luaL_Reg ResizeEffect_funcs[] = { { "new", ResizeEffect_new }, { "set_float", Effect_set_float }, { "set_int", Effect_set_int }, + { "set_vec3", Effect_set_vec3 }, { "set_vec4", Effect_set_vec4 }, { NULL, NULL } }; @@ -333,6 +357,7 @@ const luaL_Reg MixEffect_funcs[] = { { "new", MixEffect_new }, { "set_float", Effect_set_float }, { "set_int", Effect_set_int }, + { "set_vec3", Effect_set_vec3 }, { "set_vec4", Effect_set_vec4 }, { NULL, NULL } }; @@ -501,6 +526,22 @@ bool Theme::get_supports_set_wb(unsigned channel) return ret; } +void Theme::set_wb(unsigned channel, double r, double g, double b) +{ + unique_lock lock(m); + lua_getglobal(L, "set_wb"); + lua_pushnumber(L, channel); + lua_pushnumber(L, r); + lua_pushnumber(L, g); + lua_pushnumber(L, b); + if (lua_pcall(L, 4, 0, 0) != 0) { + fprintf(stderr, "error running function `set_wb': %s\n", lua_tostring(L, -1)); + exit(1); + } + + assert(lua_gettop(L) == 0); +} + std::vector Theme::get_transition_names(float t) { unique_lock lock(m); diff --git a/theme.h b/theme.h index 5752590..f00c7a2 100644 --- a/theme.h +++ b/theme.h @@ -48,6 +48,7 @@ public: int get_num_channels() { return num_channels; } std::string get_channel_name(unsigned channel); bool get_supports_set_wb(unsigned channel); + void set_wb(unsigned channel, double r, double g, double b); std::vector get_transition_names(float t); diff --git a/theme.lua b/theme.lua index b91e0fa..e5f902c 100644 --- a/theme.lua +++ b/theme.lua @@ -15,6 +15,9 @@ local zoom_poi = 0 -- which input to zoom in on local fade_src = 0.0 local fade_dst = 1.0 +local input0_neutral_color = {0.5, 0.5, 0.5} +local input1_neutral_color = {0.5, 0.5, 0.5} + local live_signal_num = 0 local preview_signal_num = 1 @@ -23,15 +26,17 @@ function make_sbs_chain(hq) local chain = EffectChain.new(16, 9) local input0 = chain:add_live_input(true) input0:connect_signal(0) + local input0_wb_effect = chain:add_effect(WhiteBalanceEffect.new()) local input1 = chain:add_live_input(true) input1:connect_signal(1) + local input1_wb_effect = chain:add_effect(WhiteBalanceEffect.new()) local resample_effect = nil local resize_effect = nil if (hq) then - resample_effect = chain:add_effect(ResampleEffect.new(), input0) + resample_effect = chain:add_effect(ResampleEffect.new(), input0_wb_effect) else - resize_effect = chain:add_effect(ResizeEffect.new(), input0) + resize_effect = chain:add_effect(ResizeEffect.new(), input0_wb_effect) end local padding_effect = chain:add_effect(IntegralPaddingEffect.new()) @@ -40,14 +45,10 @@ function make_sbs_chain(hq) local resample2_effect = nil local resize2_effect = nil if (hq) then - resample2_effect = chain:add_effect(ResampleEffect.new(), input1) + resample2_effect = chain:add_effect(ResampleEffect.new(), input1_wb_effect) else - resize2_effect = chain:add_effect(ResizeEffect.new(), input1) + resize2_effect = chain:add_effect(ResizeEffect.new(), input1_wb_effect) end - -- Effect *saturation_effect = chain->add_effect(new SaturationEffect()) - -- CHECK(saturation_effect->set_float("saturation", 0.3f)) - local wb_effect = chain:add_effect(WhiteBalanceEffect.new()) - wb_effect:set_float("output_color_temperature", 3500.0) local padding2_effect = chain:add_effect(IntegralPaddingEffect.new()) chain:add_effect(OverlayEffect.new(), padding_effect, padding2_effect) @@ -57,12 +58,14 @@ function make_sbs_chain(hq) chain = chain, input0 = { input = input0, + white_balance_effect = input0_wb_effect, resample_effect = resample_effect, resize_effect = resize_effect, padding_effect = padding_effect }, input1 = { input = input1, + white_balance_effect = input1_wb_effect, resample_effect = resample2_effect, resize_effect = resize2_effect, padding_effect = padding2_effect @@ -76,22 +79,26 @@ local main_chain_lq = make_sbs_chain(false) -- A chain to fade between two inputs (live chain only) local fade_chain_hq = EffectChain.new(16, 9) local fade_chain_hq_input0 = fade_chain_hq:add_live_input(true) +local fade_chain_hq_wb0_effect = fade_chain_hq:add_effect(WhiteBalanceEffect.new()) local fade_chain_hq_input1 = fade_chain_hq:add_live_input(true) +local fade_chain_hq_wb1_effect = fade_chain_hq:add_effect(WhiteBalanceEffect.new()) fade_chain_hq_input0:connect_signal(0) fade_chain_hq_input1:connect_signal(1) -local fade_chain_mix_effect = fade_chain_hq:add_effect(MixEffect.new(), fade_chain_hq_input0, fade_chain_hq_input1) +local fade_chain_mix_effect = fade_chain_hq:add_effect(MixEffect.new(), fade_chain_hq_wb0_effect, fade_chain_hq_wb1_effect) fade_chain_hq:finalize(true) -- A chain to show a single input on screen (HQ version). local simple_chain_hq = EffectChain.new(16, 9) local simple_chain_hq_input = simple_chain_hq:add_live_input(true) simple_chain_hq_input:connect_signal(0) -- First input card. Can be changed whenever you want. +local simple_chain_hq_wb_effect = simple_chain_hq:add_effect(WhiteBalanceEffect.new()) simple_chain_hq:finalize(true) -- A chain to show a single input on screen (LQ version). local simple_chain_lq = EffectChain.new(16, 9) local simple_chain_lq_input = simple_chain_lq:add_live_input(true) simple_chain_lq_input:connect_signal(0) -- First input card. Can be changed whenever you want. +local simple_chain_lq_wb_effect = simple_chain_lq:add_effect(WhiteBalanceEffect.new()) simple_chain_lq:finalize(false) -- Returns the number of outputs in addition to the live (0) and preview (1). @@ -118,6 +125,16 @@ function supports_set_wb(channel) return channel == 2 or channel == 3 end +-- Gets called with a new gray point when the white balance is changing. +-- The color is in linear light (not sRGB gamma). +function set_wb(channel, red, green, blue) + if channel == 2 then + input0_neutral_color = { red, green, blue } + elseif channel == 3 then + input1_neutral_color = { red, green, blue } + end +end + function finish_transitions(t) -- If live is 2 (SBS) but de-facto single, make it so. if live_signal_num == 2 and t >= transition_end and zoom_dst == 1.0 then @@ -269,12 +286,19 @@ function get_chain(num, t, width, height) if live_signal_num == 0 or live_signal_num == 1 then -- Plain inputs. prepare = function() simple_chain_hq_input:connect_signal(live_signal_num) + if live_signal_num == 0 then + set_neutral_color(simple_chain_hq_wb_effect, input0_neutral_color) + else + set_neutral_color(simple_chain_hq_wb_effect, input1_neutral_color) + end end return simple_chain_hq, prepare elseif live_signal_num == 3 then -- Fade. prepare = function() fade_chain_hq_input0:connect_signal(0) + set_neutral_color(fade_chain_hq_wb0_effect, input0_neutral_color) fade_chain_hq_input1:connect_signal(1) + set_neutral_color(fade_chain_hq_wb1_effect, input1_neutral_color) local tt = (t - transition_start) / (transition_end - transition_start) if tt < 0.0 then tt = 0.0 @@ -300,6 +324,7 @@ function get_chain(num, t, width, height) -- Special case: Show only the single image on screen. prepare = function() simple_chain_hq_input:connect_signal(0) + set_neutral_color(simple_chain_hq_wb_effect, input0_neutral_color) end return simple_chain_hq, prepare end @@ -323,12 +348,14 @@ function get_chain(num, t, width, height) if num == 2 then prepare = function() simple_chain_lq_input:connect_signal(0) + set_neutral_color(simple_chain_lq_wb_effect, input0_neutral_color) end return simple_chain_lq, prepare end if num == 3 then prepare = function() simple_chain_lq_input:connect_signal(1) + set_neutral_color(simple_chain_lq_wb_effect, input1_neutral_color) end return simple_chain_lq, prepare end @@ -434,6 +461,8 @@ end function prepare_sbs_chain(chain, t, screen_width, screen_height) chain.input0.input:connect_signal(0) chain.input1.input:connect_signal(1) + set_neutral_color(chain.input0.white_balance_effect, input0_neutral_color) + set_neutral_color(chain.input1.white_balance_effect, input1_neutral_color) -- First input is positioned (16,48) from top-left. local width0 = round(848 * screen_width/1280.0) @@ -487,3 +516,7 @@ function prepare_sbs_chain(chain, t, screen_width, screen_height) place_rectangle(chain.input0.resample_effect, chain.input0.resize_effect, chain.input0.padding_effect, left0, top0, right0, bottom0, screen_width, screen_height, 1280, 720) place_rectangle(chain.input1.resample_effect, chain.input1.resize_effect, chain.input1.padding_effect, left1, top1, right1, bottom1, screen_width, screen_height, 1280, 720) end + +function set_neutral_color(effect, color) + effect:set_vec3("neutral_color", color[1], color[2], color[3]) +end