Add a simple luma wipe transition.
authorSteinar H. Gunderson <sgunderson@bigfoot.com>
Tue, 11 Mar 2014 23:05:28 +0000 (00:05 +0100)
committerSteinar H. Gunderson <sgunderson@bigfoot.com>
Tue, 11 Mar 2014 23:05:28 +0000 (00:05 +0100)
Makefile.in
effect_chain.h
luma_mix_effect.cpp [new file with mode: 0644]
luma_mix_effect.frag [new file with mode: 0644]
luma_mix_effect.h [new file with mode: 0644]
luma_mix_effect_test.cpp [new file with mode: 0644]

index f531c3b..f88fd5a 100644 (file)
@@ -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
index 4417de9..cbc1221 100644 (file)
@@ -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<Effect *> 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<Effect *> &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 (file)
index 0000000..e34d87f
--- /dev/null
@@ -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 (file)
index 0000000..21365a6
--- /dev/null
@@ -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 (file)
index 0000000..8bd3c50
--- /dev/null
@@ -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 <string>
+
+#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 (file)
index 0000000..618b733
--- /dev/null
@@ -0,0 +1,99 @@
+// Unit tests for LumaMixEffect.
+
+#include <GL/glew.h>
+
+#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