From 36d9ece323c89dbd553644c80fea449c5dd1e685 Mon Sep 17 00:00:00 2001 From: "Steinar H. Gunderson" Date: Wed, 12 Mar 2014 00:05:28 +0100 Subject: [PATCH] Add a simple luma wipe transition. --- Makefile.in | 1 + effect_chain.h | 7 +++ luma_mix_effect.cpp | 27 +++++++++++ luma_mix_effect.frag | 44 ++++++++++++++++++ luma_mix_effect.h | 35 ++++++++++++++ luma_mix_effect_test.cpp | 99 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 213 insertions(+) create mode 100644 luma_mix_effect.cpp create mode 100644 luma_mix_effect.frag create mode 100644 luma_mix_effect.h create mode 100644 luma_mix_effect_test.cpp diff --git a/Makefile.in b/Makefile.in index f531c3b..f88fd5a 100644 --- a/Makefile.in +++ b/Makefile.in @@ -57,6 +57,7 @@ TESTED_EFFECTS += fft_pass_effect TESTED_EFFECTS += vignette_effect TESTED_EFFECTS += slice_effect TESTED_EFFECTS += complex_modulate_effect +TESTED_EFFECTS += luma_mix_effect UNTESTED_EFFECTS = sandbox_effect UNTESTED_EFFECTS += mirror_effect diff --git a/effect_chain.h b/effect_chain.h index 4417de9..cbc1221 100644 --- a/effect_chain.h +++ b/effect_chain.h @@ -139,6 +139,13 @@ public: inputs.push_back(input2); return add_effect(effect, inputs); } + Effect *add_effect(Effect *effect, Effect *input1, Effect *input2, Effect *input3) { + std::vector inputs; + inputs.push_back(input1); + inputs.push_back(input2); + inputs.push_back(input3); + return add_effect(effect, inputs); + } Effect *add_effect(Effect *effect, const std::vector &inputs); void add_output(const ImageFormat &format, OutputAlphaFormat alpha_format); diff --git a/luma_mix_effect.cpp b/luma_mix_effect.cpp new file mode 100644 index 0000000..e34d87f --- /dev/null +++ b/luma_mix_effect.cpp @@ -0,0 +1,27 @@ +#include "luma_mix_effect.h" +#include "effect_util.h" +#include "util.h" + +using namespace std; + +namespace movit { + +LumaMixEffect::LumaMixEffect() + : transition_width(1.0f), progress(0.5f) +{ + register_float("transition_width", &transition_width); + register_float("progress", &progress); +} + +string LumaMixEffect::output_fragment_shader() +{ + return read_file("luma_mix_effect.frag"); +} + +void LumaMixEffect::set_gl_state(GLuint glsl_program_num, const string &prefix, unsigned *sampler_num) +{ + Effect::set_gl_state(glsl_program_num, prefix, sampler_num); + set_uniform_float(glsl_program_num, prefix, "progress_mul_w_plus_one", progress * (transition_width + 1.0)); +} + +} // namespace movit diff --git a/luma_mix_effect.frag b/luma_mix_effect.frag new file mode 100644 index 0000000..21365a6 --- /dev/null +++ b/luma_mix_effect.frag @@ -0,0 +1,44 @@ +uniform float PREFIX(progress_mul_w_plus_one); + +vec4 FUNCNAME(vec2 tc) { + vec4 first = INPUT1(tc); + vec4 second = INPUT2(tc); + + // We treat the luma as going from 0 to w, where w is the transition width + // (wider means the boundary between transitioned and non-transitioned + // will be harder, while w=0 is essentially just a straight fade). + // We need to map this 0..w range in the luma image to a (clamped) 0..1 + // range for how far this pixel has come in a fade. At the very + // beginning, we can visualize it like this, where every pixel is in + // the state 0.0 (100% first image, 0% second image): + // + // 0 w + // luma: |---------------------| + // mix: |----| + // 0 1 + // + // Then as we progress, eventually the luma range should move to the right + // so that more pixels start moving towards higher mix value: + // + // 0 w + // luma: |---------------------| + // mix: |----| + // 0 1 + // + // and at the very end, all pixels should be in the state 1.0 (0% first image, + // 100% second image): + // + // 0 w + // luma: |---------------------| + // mix: |----| + // 0 1 + // + // So clearly, it should move (w+1) units to the right, and apart from that + // just stay a simple mapping. + float w = PREFIX(transition_width); + float luma = INPUT3(tc).x * w; + float m = clamp((luma - w) + PREFIX(progress_mul_w_plus_one), 0.0, 1.0); + + return mix(first, second, m); +// return vec4(luma, luma, luma, 1.0); +} diff --git a/luma_mix_effect.h b/luma_mix_effect.h new file mode 100644 index 0000000..8bd3c50 --- /dev/null +++ b/luma_mix_effect.h @@ -0,0 +1,35 @@ +#ifndef _MOVIT_LUMA_MIX_EFFECT_H +#define _MOVIT_LUMA_MIX_EFFECT_H 1 + +// Fade between two images based on a third monochrome one; lighter pixels +// will be faded before darker pixels. This allows a wide range of different +// video wipes implemented using a single effect. +// +// Note that despite the name, the third input's _red_ channel is what's used +// for transitions; there is no luma calculation done. If you need that, +// put a SaturationEffect in front to desaturate (which calculates luma). + +#include + +#include "effect.h" + +namespace movit { + +class LumaMixEffect : public Effect { +public: + LumaMixEffect(); + virtual std::string effect_type_id() const { return "LumaMixEffect"; } + std::string output_fragment_shader(); + virtual void set_gl_state(GLuint glsl_program_num, const std::string &prefix, unsigned *sampler_num); + + virtual bool needs_srgb_primaries() const { return false; } + virtual unsigned num_inputs() const { return 3; } + virtual AlphaHandling alpha_handling() const { return INPUT_PREMULTIPLIED_ALPHA_KEEP_BLANK; } + +private: + float transition_width, progress; +}; + +} // namespace movit + +#endif // !defined(_MOVIT_MIX_EFFECT_H) diff --git a/luma_mix_effect_test.cpp b/luma_mix_effect_test.cpp new file mode 100644 index 0000000..618b733 --- /dev/null +++ b/luma_mix_effect_test.cpp @@ -0,0 +1,99 @@ +// Unit tests for LumaMixEffect. + +#include + +#include "effect_chain.h" +#include "gtest/gtest.h" +#include "image_format.h" +#include "input.h" +#include "luma_mix_effect.h" +#include "test_util.h" + +namespace movit { + +TEST(LumaMixEffectTest, HardWipe) { + float data_a[] = { + 0.0f, 0.25f, + 0.75f, 1.0f, + }; + float data_b[] = { + 1.0f, 0.5f, + 0.65f, 0.6f, + }; + float data_luma[] = { + 0.0f, 0.25f, + 0.5f, 0.75f, + }; + + EffectChainTester tester(data_a, 2, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR); + Effect *input1 = tester.get_chain()->last_added_effect(); + Effect *input2 = tester.add_input(data_b, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR); + Effect *input3 = tester.add_input(data_luma, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR); + + Effect *luma_mix_effect = tester.get_chain()->add_effect(new LumaMixEffect(), input1, input2, input3); + ASSERT_TRUE(luma_mix_effect->set_float("transition_width", 100000.0f)); + + float out_data[4]; + ASSERT_TRUE(luma_mix_effect->set_float("progress", 0.0f)); + tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR); + expect_equal(data_a, out_data, 2, 2); + + // Lower right from B, the rest from A. + float expected_data_049[] = { + 0.0f, 0.25f, + 0.75f, 0.6f, + }; + ASSERT_TRUE(luma_mix_effect->set_float("progress", 0.49f)); + tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR); + expect_equal(expected_data_049, out_data, 2, 2); + + // Lower two from B, the rest from A. + float expected_data_051[] = { + 0.0f, 0.25f, + 0.65f, 0.6f, + }; + ASSERT_TRUE(luma_mix_effect->set_float("progress", 0.51f)); + tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR); + expect_equal(expected_data_051, out_data, 2, 2); + + ASSERT_TRUE(luma_mix_effect->set_float("progress", 1.0f)); + tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR); + expect_equal(data_b, out_data, 2, 2); +} + +TEST(LumaMixEffectTest, SoftWipeHalfWayThrough) { + float data_a[] = { + 0.0f, 0.25f, + 0.75f, 1.0f, + }; + float data_b[] = { + 1.0f, 0.5f, + 0.65f, 0.6f, + }; + float data_luma[] = { + 0.0f, 0.25f, + 0.5f, 0.75f, + }; + // At this point, the luma range and the mix range should exactly line up, + // so we get a straight-up fade by luma. + float expected_data[] = { + data_a[0] + (data_b[0] - data_a[0]) * data_luma[0], + data_a[1] + (data_b[1] - data_a[1]) * data_luma[1], + data_a[2] + (data_b[2] - data_a[2]) * data_luma[2], + data_a[3] + (data_b[3] - data_a[3]) * data_luma[3], + }; + float out_data[4]; + + EffectChainTester tester(data_a, 2, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR); + Effect *input1 = tester.get_chain()->last_added_effect(); + Effect *input2 = tester.add_input(data_b, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR); + Effect *input3 = tester.add_input(data_luma, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR); + + Effect *luma_mix_effect = tester.get_chain()->add_effect(new LumaMixEffect(), input1, input2, input3); + ASSERT_TRUE(luma_mix_effect->set_float("transition_width", 1.0f)); + ASSERT_TRUE(luma_mix_effect->set_float("progress", 0.5f)); + tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR); + expect_equal(expected_data, out_data, 2, 2); +} + +} // namespace movit -- 2.39.2