]> git.sesse.net Git - nageru/commitdiff
Support auto white balance (ie., not controlled by the theme).
authorSteinar H. Gunderson <sgunderson@bigfoot.com>
Sun, 9 Feb 2020 13:43:17 +0000 (14:43 +0100)
committerSteinar H. Gunderson <sgunderson@bigfoot.com>
Sun, 9 Feb 2020 13:46:25 +0000 (14:46 +0100)
This is added as a regular (optional) white balance effect, but it automatically
intercepts set_wb() calls and stores it locally. (You can still set neutral_color
manually, but it's probably sort of pointless.)

This simplifies the themes somewhat and allows in some cases for fewer different
scene instantiations (since image inputs don't get white balance), but more
importantly, it will allow exporting the white balance setting in the MJPEG
export.

nageru/scene.cpp
nageru/scene.h
nageru/theme.cpp
nageru/theme.h
nageru/theme.lua

index 178755a0782ff38960c500a436932282e7de9ce2..06392c031e9f7c133547396c1d8caa619825e579 100644 (file)
@@ -176,6 +176,14 @@ bool Scene::is_noncanonical_chain(size_t chain_idx) const
                                        return true;
                                }
                        }
+
+                       // Auto white balance is always disabled for image inputs.
+                       if (block->root_input_block != nullptr) {
+                               const Block *input = block->root_input_block;
+                               if (input->alternatives[input->chosen_alternative(chain_idx)]->effect_type == IMAGE_INPUT) {
+                                       return true;
+                               }
+                       }
                }
        }
        return false;
@@ -227,16 +235,16 @@ int Scene::add_input(lua_State* L)
        return wrap_lua_existing_object_nonowned<Block>(L, "Block", block);
 }
 
-void Scene::find_inputs_for_block(lua_State *L, Scene *scene, Block *block)
+void Scene::find_inputs_for_block(lua_State *L, Scene *scene, Block *block, int first_input_idx)
 {
-       if (lua_gettop(L) == 2) {
+       if (lua_gettop(L) == first_input_idx - 1) {
                // Implicitly the last added effect.
                assert(!scene->blocks.empty());
                block->inputs.push_back(scene->blocks.size() - 1);
                return;
        }
 
-       for (int idx = 3; idx <= lua_gettop(L); ++idx) {
+       for (int idx = first_input_idx; idx <= lua_gettop(L); ++idx) {
                Block *input_block = nullptr;
                if (luaL_testudata(L, idx, "Block")) {
                        input_block = *(Block **)luaL_checkudata(L, idx, "Block");
@@ -318,6 +326,55 @@ int Scene::add_optional_effect(lua_State* L)
        return wrap_lua_existing_object_nonowned<Block>(L, "Block", block);
 }
 
+const Block *Scene::find_root_input_block(lua_State *L, const Block *block)
+{
+       if (block->is_input) {
+               assert(block->inputs.size() == 0);
+               return block;
+       }
+
+       const Block *ret = nullptr;
+       for (size_t input_idx : block->inputs) {
+               const Block *parent = find_root_input_block(L, blocks[input_idx]);
+               if (parent != nullptr) {
+                       if (ret != nullptr) {
+                               luaL_error(L, "add_auto_white_balance() was connected to more than one input");
+                       }
+                       ret = parent;
+               }
+       }
+       return ret;
+}
+
+int Scene::add_auto_white_balance(lua_State* L)
+{
+       assert(lua_gettop(L) >= 1);
+       Scene *scene = (Scene *)luaL_checkudata(L, 1, "Scene");
+
+       Block *block = new Block;
+       block->declaration_point = get_declaration_point(L);
+       block->idx = scene->blocks.size();
+
+       block->alternatives.push_back(new EffectBlueprint(WHITE_BALANCE_EFFECT));
+       block->alternatives.push_back(new EffectBlueprint(IDENTITY_EFFECT));
+
+       block->canonical_alternative = 1;
+
+       find_inputs_for_block(L, scene, block, /*first_input_idx=*/2);
+
+       if (block->inputs.size() != 1) {
+               luaL_error(L, "add_auto_white_balance() needs exactly one input");
+       }
+       block->root_input_block = scene->find_root_input_block(L, block);
+       if (block->root_input_block == nullptr) {
+               luaL_error(L, "add_auto_white_balance() was not connected to an input");
+       }
+
+       scene->blocks.push_back(block);
+
+       return wrap_lua_existing_object_nonowned<Block>(L, "Block", block);
+}
+
 Effect *Scene::instantiate_effects(const Block *block, size_t chain_idx, Scene::Instantiation *instantiation)
 {
        // Find the chosen alternative for this block in this instance.
@@ -463,6 +520,38 @@ Scene::get_chain(Theme *theme, lua_State *L, unsigned num, const InputState &inp
                }
        }
 
+       // Find all auto white balance blocks, turn on and off the effect as needed,
+       // and fetch the actual white balance set (it is stored in Theme).
+       map<Block *, array<float, 3>> white_balance;
+       for (size_t block_idx = 0; block_idx < blocks.size(); ++block_idx) {
+               Block *block = blocks[block_idx];
+               const Block *input = block->root_input_block;
+               if (input == nullptr) {
+                       continue;  // Not an auto white balance block.
+               }
+
+               EffectType chosen_type = current_type(input);
+               if (chosen_type == IMAGE_INPUT) {
+                       // Image inputs never get white balance applied.
+                       block->currently_chosen_alternative = find_index_of(block, IDENTITY_EFFECT);
+                       continue;
+               }
+
+               assert(chosen_type == LIVE_INPUT_YCBCR ||
+                      chosen_type == LIVE_INPUT_YCBCR_WITH_DEINTERLACE ||
+                      chosen_type == LIVE_INPUT_YCBCR_PLANAR ||
+                      chosen_type == LIVE_INPUT_BGRA);
+               int signal = find_signal_to_connect(L, input);
+               Theme::WhiteBalance wb = theme->get_white_balance_for_signal(signal);
+               if (fabs(wb.r - 1.0) < 1e-3 && fabs(wb.g - 1.0) < 1e-3 && fabs(wb.b - 1.0) < 1e-3) {
+                       // Neutral white balance.
+                       block->currently_chosen_alternative = find_index_of(block, IDENTITY_EFFECT);
+               } else {
+                       block->currently_chosen_alternative = find_index_of(block, WHITE_BALANCE_EFFECT);
+                       white_balance.emplace(block, array<float, 3>{ wb.r, wb.g, wb.b });
+               }
+       }
+
        // Pick out the right chain based on the current selections,
        // and snapshot all the set variables so that we can set them
        // in the prepare function even if they're being changed by
@@ -536,6 +625,9 @@ Scene::get_chain(Theme *theme, lua_State *L, unsigned num, const InputState &inp
                        for (const auto &key_and_tuple : block->float_parameters) {
                                float_to_set.emplace(make_pair(effect, key_and_tuple.first), key_and_tuple.second);
                        }
+                       if (white_balance.count(block)) {
+                               vec3_to_set.emplace(make_pair(effect, "neutral_color"), white_balance[block]);
+                       }
                        for (const auto &key_and_tuple : block->vec3_parameters) {
                                vec3_to_set.emplace(make_pair(effect, key_and_tuple.first), key_and_tuple.second);
                        }
index 8db75540559cfa29bf45611319e19380bb56fc1c..8cff06a8d3e53946af18ad4e41aedfe56e5236b0 100644 (file)
@@ -131,6 +131,10 @@ struct Block {
        std::map<std::string, std::array<float, 4>> vec4_parameters;
 
        std::string declaration_point;  // For error messages.
+
+       // Only for AUTO_WHITE_BALANCE_EFFECT. Points to the parent block with is_input = true,
+       // so that we know which signal to get the white balance from.
+       const Block *root_input_block = nullptr;
 };
 
 int Block_display(lua_State* L);
@@ -162,7 +166,7 @@ private:
 
        movit::Effect *instantiate_effects(const Block *block, size_t chain_idx, Instantiation *instantiation);
        size_t compute_chain_number_for_block(size_t block_idx, const std::bitset<256> &disabled) const;
-       static void find_inputs_for_block(lua_State *L, Scene *scene, Block *block);
+       static void find_inputs_for_block(lua_State *L, Scene *scene, Block *block, int first_input_idx = 3);
 
        // Find out which blocks (indexed by position in the “blocks” array),
        // if any, are disabled in a given instantiation. A disabled block is
@@ -182,6 +186,11 @@ private:
        // it returns true.
        bool is_noncanonical_chain(size_t chain_idx) const;
 
+       // For a given block, find any parents it may have that are inputs.
+       // If there is more than one, throws an error. If there are zero,
+       // returns nullptr (should probably also be an error).
+       const Block *find_root_input_block(lua_State *L, const Block *block);
+
 public:
        Scene(Theme *theme, float aspect_nom, float aspect_denom);
        size_t compute_chain_number(bool is_main_chain) const;
@@ -192,6 +201,7 @@ public:
        static int add_input(lua_State *L);
        static int add_effect(lua_State *L);
        static int add_optional_effect(lua_State *L);
+       static int add_auto_white_balance(lua_State *L);
        static int finalize(lua_State *L);
 };
 
index 78b0fde35cd2b075e2482f921776da71ade955f9..e3cdac2df54feac52374d623ce36bf34841cabf9 100644 (file)
@@ -116,6 +116,7 @@ Effect *instantiate_effect(EffectChain *chain, EffectType effect_type)
        case IDENTITY_EFFECT:
                return new IdentityEffect;
        case WHITE_BALANCE_EFFECT:
+       case AUTO_WHITE_BALANCE_EFFECT:
                return new WhiteBalanceEffect;
        case RESAMPLE_EFFECT:
                return new ResampleEffect;
@@ -860,6 +861,7 @@ const luaL_Reg Scene_funcs[] = {
        { "new", Scene_new },
        { "__gc", Scene_gc },
        { "add_input", Scene::add_input },
+       { "add_auto_white_balance", Scene::add_auto_white_balance },
        { "add_effect", Scene::add_effect },
        { "add_optional_effect", Scene::add_optional_effect },
        { "finalize", Scene::finalize },
@@ -1682,10 +1684,21 @@ bool Theme::get_supports_set_wb(unsigned channel)
        return ret;
 }
 
-void Theme::set_wb(unsigned channel, double r, double g, double b)
+void Theme::set_wb(unsigned channel, float r, float g, float b)
 {
        lock_guard<mutex> lock(m);
+
+       if (channel_signals.count(channel)) {
+               white_balance_for_signal[channel_signals[channel]] = WhiteBalance{ r, g, b };
+       }
+
        lua_getglobal(L, "set_wb");
+       if (lua_isnil(L, -1)) {
+               // The function doesn't exist, to just ignore. We've stored the white balance,
+               // and most likely, it will be picked up by auto white balance instead.
+               lua_pop(L, 1);
+               return;
+       }
        lua_pushnumber(L, channel);
        lua_pushnumber(L, r);
        lua_pushnumber(L, g);
@@ -1698,6 +1711,15 @@ void Theme::set_wb(unsigned channel, double r, double g, double b)
        assert(lua_gettop(L) == 0);
 }
 
+Theme::WhiteBalance Theme::get_white_balance_for_signal(int signal)
+{
+       if (white_balance_for_signal.count(signal)) {
+               return white_balance_for_signal[signal];
+       } else {
+               return WhiteBalance{ 1.0, 1.0, 1.0 };
+       }
+}
+
 vector<string> Theme::get_transition_names(float t)
 {
        lock_guard<mutex> lock(m);
index 37113b2b954d88a607087ca8e691df1aa082a8fd..7fd132544264e8283fbc0bd1d13ec6d6dc05a084 100644 (file)
@@ -39,6 +39,7 @@ enum EffectType {
 
        IDENTITY_EFFECT,
        WHITE_BALANCE_EFFECT,
+       AUTO_WHITE_BALANCE_EFFECT,  // Same as WHITE_BALANCE_EFFECT, but sets its value automatically.
        RESAMPLE_EFFECT,
        PADDING_EFFECT,
        INTEGRAL_PADDING_EFFECT,
@@ -93,6 +94,9 @@ public:
                // for non-interlaced inputs.
                std::vector<RefCountedFrame> input_frames;
        };
+       struct WhiteBalance {
+               float r, g, b;
+       };
 
        Chain get_chain(unsigned num, float t, unsigned width, unsigned height, const InputState &input_state);
 
@@ -102,9 +106,12 @@ public:
        std::string get_channel_name(unsigned channel);
        int get_channel_signal(unsigned channel);
        bool get_supports_set_wb(unsigned channel);
-       void set_wb(unsigned channel, double r, double g, double b);
+       void set_wb(unsigned channel, float r, float g, float b);
+       WhiteBalance get_white_balance_for_signal(int signal);
        std::string get_channel_color(unsigned channel);
 
+       std::unordered_map<int, WhiteBalance> white_balance_for_signal;
+
        std::vector<std::string> get_transition_names(float t);
 
        void transition_clicked(int transition_num, float t);
index 349d33b865fa7ebbfae90179ab8d05235cd363e9..195e89b88ab11e5bbcfc9eb7e187dc4514e690de 100644 (file)
@@ -16,11 +16,6 @@ local state = {
        transition_src_signal = 0,
        transition_dst_signal = 0,
 
-       neutral_colors = {
-               {0.5, 0.5, 0.5},  -- Input 0.
-               {0.5, 0.5, 0.5}   -- Input 1.
-       },
-
        live_signal_num = 0,
        preview_signal_num = 1
 }
@@ -38,9 +33,9 @@ local FADE_TRANSITION = 2
 
 function make_sbs_input(scene)
        return {
-               input = scene:add_input(),
+               input = scene:add_input(0),  -- Live inputs only.
                resample_effect = scene:add_effect({ResampleEffect.new(), ResizeEffect.new()}),
-               wb_effect = scene:add_effect(WhiteBalanceEffect.new()),
+               wb_effect = scene:add_auto_white_balance(),
                padding_effect = scene:add_effect(IntegralPaddingEffect.new())
        }
 end
@@ -72,7 +67,7 @@ function make_fade_input(scene)
        return {
                input = scene:add_input(),
                resample_effect = scene:add_optional_effect(ResampleEffect.new()),  -- Activated if scaling.
-               wb_effect = scene:add_optional_effect(WhiteBalanceEffect.new())  -- Activated for video inputs.
+               wb_effect = scene:add_auto_white_balance()  -- Activated for video inputs.
        }
 end
 
@@ -100,7 +95,7 @@ local simple_scene = {
        scene = scene,
        input = scene:add_input(),
        resample_effect = scene:add_effect({ResampleEffect.new(), ResizeEffect.new(), IdentityEffect.new()}),
-       wb_effect = scene:add_effect(WhiteBalanceEffect.new())
+       wb_effect = scene:add_auto_white_balance()
 }
 scene:finalize()
 
@@ -168,15 +163,6 @@ function channel_involved_in(channel, signal_num)
        return false
 end
 
--- API ENTRY POINT
--- 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 is_plain_signal(channel - 2) then
-               state.neutral_colors[channel - 2 + 1] = { red, green, blue }
-       end
-end
-
 function finish_transitions(t)
        if state.transition_type ~= NO_TRANSITION and t >= state.transition_end then
                state.live_signal_num = state.transition_dst_signal
@@ -296,7 +282,6 @@ function setup_fade_input(state, input, signals, signal_num, width, height)
        else
                input.input:display(signal_num)
                input.wb_effect:enable()
-               set_neutral_color(input.wb_effect, state.neutral_colors[signal_num - INPUT0_SIGNAL_NUM + 1])
 
                if (signals:get_width(signal_num) ~= width or signals:get_height(signal_num) ~= height) then
                        input.resample_effect:enable()
@@ -330,7 +315,6 @@ function setup_simple_input(state, signals, signal_num, width, height, hq)
        else
                simple_scene.resample_effect:disable()  -- No scaling.
        end
-       set_neutral_color_from_signal(state, simple_scene.wb_effect, signal_num)
 end
 
 -- API ENTRY POINT
@@ -535,9 +519,6 @@ function pos_from_top_left(x, y, width, height, screen_width, screen_height)
 end
 
 function prepare_sbs_scene(state, t, transition_type, src_signal, dst_signal, screen_width, screen_height, input_resolution, hq)
-       set_neutral_color(sbs_scene.input0.wb_effect, state.neutral_colors[1])
-       set_neutral_color(sbs_scene.input1.wb_effect, state.neutral_colors[2])
-
        -- First input is positioned (16,48) from top-left.
        -- Second input is positioned (16,48) from the bottom-right.
        local pos0 = pos_from_top_left(16, 48, 848, 477, screen_width, screen_height)
@@ -594,16 +575,6 @@ function place_rectangle_with_affine(input, pos, aff, screen_width, screen_heigh
        place_rectangle(input, x0, y0, x1, y1, screen_width, screen_height, input_width, input_height, hq)
 end
 
-function set_neutral_color(effect, color)
-       effect:set_vec3("neutral_color", color[1], color[2], color[3])
-end
-
-function set_neutral_color_from_signal(state, effect, signal)
-       if is_plain_signal(signal) then
-               set_neutral_color(effect, state.neutral_colors[signal - INPUT0_SIGNAL_NUM + 1])
-       end
-end
-
 function calc_zoom_progress(state, t)
        if t < state.transition_start then
                return 0.0