]> git.sesse.net Git - movit/commitdiff
Add a new alpha handling method, INPUT_PREMULTIPLIED_ALPHA_KEEP_BLANK.
authorSteinar H. Gunderson <sgunderson@bigfoot.com>
Sat, 2 Feb 2013 01:35:18 +0000 (02:35 +0100)
committerSteinar H. Gunderson <sgunderson@bigfoot.com>
Sat, 2 Feb 2013 01:35:18 +0000 (02:35 +0100)
This should fix most of the problems where you only process inputs
without alpha, but the framework still inserts an AlphaDivisionEffect
at the end because it loses track of the blank alpha state throughout
the pipeline and the output expects postmultiplied alpha.

There's one important case left, though: MixEffect will always reset
tha alpha state to premultiplied. We should probably fix that too
at some later stage.

13 files changed:
blur_effect.h
deconvolution_sharpen_effect.h
diffusion_effect.h
effect.h
effect_chain.cpp
effect_chain_test.cpp
glow_effect.h
lift_gamma_gain_effect.h
mix_effect.h
overlay_effect.h
padding_effect.cpp
resample_effect.h
resize_effect.h

index 4af11a2b16a388ca6435e03b3d500eb2946039c3..062807dc5b731b3976d1a496757589b9ca1640cb 100644 (file)
@@ -24,7 +24,7 @@ public:
        virtual bool needs_texture_bounce() const { return true; }
        virtual bool needs_mipmaps() const { return true; }
        virtual bool needs_srgb_primaries() const { return false; }
        virtual bool needs_texture_bounce() const { return true; }
        virtual bool needs_mipmaps() const { return true; }
        virtual bool needs_srgb_primaries() const { return false; }
-       virtual AlphaHandling alpha_handling() const { return INPUT_AND_OUTPUT_PREMULTIPLIED_ALPHA; }
+       virtual AlphaHandling alpha_handling() const { return INPUT_PREMULTIPLIED_ALPHA_KEEP_BLANK; }
 
        virtual void inform_input_size(unsigned input_num, unsigned width, unsigned height);
 
 
        virtual void inform_input_size(unsigned input_num, unsigned width, unsigned height);
 
index 293916fd52da9433d7843ff9ca26c3526a8fdc06..a0b405a6c7f829f5d94cc4bf3ca3429c85968e45 100644 (file)
@@ -39,6 +39,7 @@ public:
        }
 
        void set_gl_state(GLuint glsl_program_num, const std::string &prefix, unsigned *sampler_num);
        }
 
        void set_gl_state(GLuint glsl_program_num, const std::string &prefix, unsigned *sampler_num);
+       virtual AlphaHandling alpha_handling() const { return INPUT_PREMULTIPLIED_ALPHA_KEEP_BLANK; }
 
 private:
        // Input size.
 
 private:
        // Input size.
index 624990cbd664d90cd2e6d9ce2649bed96fecb10d..2c635d0cd30082d57c08afcd4d46065e266be958 100644 (file)
@@ -44,6 +44,7 @@ public:
        OverlayMatteEffect();
        virtual std::string effect_type_id() const { return "OverlayMatteEffect"; }
        std::string output_fragment_shader();
        OverlayMatteEffect();
        virtual std::string effect_type_id() const { return "OverlayMatteEffect"; }
        std::string output_fragment_shader();
+       virtual AlphaHandling alpha_handling() const { return INPUT_PREMULTIPLIED_ALPHA_KEEP_BLANK; }
 
        unsigned num_inputs() const { return 2; }
 
 
        unsigned num_inputs() const { return 2; }
 
index 43849dfa5cdddd61035a58a528061f7045276ead..771648cde059ec269c514c845ed798694cdf6ba0 100644 (file)
--- a/effect.h
+++ b/effect.h
@@ -99,7 +99,8 @@ public:
        // This is the most natural format for processing, and the default in
        // most of Movit (just like linear light is).
        //
        // This is the most natural format for processing, and the default in
        // most of Movit (just like linear light is).
        //
-       // If you set INPUT_AND_OUTPUT_PREMULTIPLIED_ALPHA, all of your inputs
+       // If you set INPUT_AND_OUTPUT_PREMULTIPLIED_ALPHA or
+       // INPUT_PREMULTIPLIED_ALPHA_KEEP_BLANK, all of your inputs
        // (if any) are guaranteed to also be in premultiplied alpha.
        // Otherwise, you can get postmultiplied or premultiplied alpha;
        // you won't know. If you have multiple inputs, you will get the same
        // (if any) are guaranteed to also be in premultiplied alpha.
        // Otherwise, you can get postmultiplied or premultiplied alpha;
        // you won't know. If you have multiple inputs, you will get the same
@@ -121,9 +122,19 @@ public:
                // If you set this, you should also set needs_linear_light().
                INPUT_AND_OUTPUT_PREMULTIPLIED_ALPHA,
 
                // If you set this, you should also set needs_linear_light().
                INPUT_AND_OUTPUT_PREMULTIPLIED_ALPHA,
 
-               // Keeps the type of alpha unchanged from input to output.
-               // Usually appropriate if you process all color channels
-               // in a linear fashion, and do not change alpha.
+               // Like INPUT_AND_OUTPUT_PREMULTIPLIED_ALPHA, but also guarantees
+               // that if you get blank alpha in, you also keep blank alpha out.
+               // This is a somewhat weaker guarantee than DONT_CARE_ALPHA_TYPE,
+               // but is still useful in many situations, and appropriate when
+               // e.g. you don't touch alpha at all.
+               //
+               // Does not make sense for inputs.
+               INPUT_PREMULTIPLIED_ALPHA_KEEP_BLANK,
+
+               // Keeps the type of alpha (premultiplied, postmultiplied, blank)
+               // unchanged from input to output. Usually appropriate if you
+               // process all color channels in a linear fashion, do not change
+               // alpha, and do not produce any new pixels thare have alpha != 1.0.
                //
                // Does not make sense for inputs.
                DONT_CARE_ALPHA_TYPE,
                //
                // Does not make sense for inputs.
                DONT_CARE_ALPHA_TYPE,
index bd039b0113e662a9279976a3d01ed43e2a406102..0f8876d8f70c68ccb6b4d2a41a538414eddf9efe 100644 (file)
@@ -782,6 +782,7 @@ void EffectChain::find_color_spaces_for_inputs()
                        case Effect::OUTPUT_POSTMULTIPLIED_ALPHA:
                                node->output_alpha_type = ALPHA_POSTMULTIPLIED;
                                break;
                        case Effect::OUTPUT_POSTMULTIPLIED_ALPHA:
                                node->output_alpha_type = ALPHA_POSTMULTIPLIED;
                                break;
+                       case Effect::INPUT_PREMULTIPLIED_ALPHA_KEEP_BLANK:
                        case Effect::DONT_CARE_ALPHA_TYPE:
                        default:
                                assert(false);
                        case Effect::DONT_CARE_ALPHA_TYPE:
                        default:
                                assert(false);
@@ -894,6 +895,7 @@ void EffectChain::propagate_alpha()
                // whether to divide away the old alpha or not.
                Effect::AlphaHandling alpha_handling = node->effect->alpha_handling();
                assert(alpha_handling == Effect::INPUT_AND_OUTPUT_PREMULTIPLIED_ALPHA ||
                // whether to divide away the old alpha or not.
                Effect::AlphaHandling alpha_handling = node->effect->alpha_handling();
                assert(alpha_handling == Effect::INPUT_AND_OUTPUT_PREMULTIPLIED_ALPHA ||
+                      alpha_handling == Effect::INPUT_PREMULTIPLIED_ALPHA_KEEP_BLANK ||
                       alpha_handling == Effect::DONT_CARE_ALPHA_TYPE);
 
                // If the node has multiple inputs, check that they are all valid and
                       alpha_handling == Effect::DONT_CARE_ALPHA_TYPE);
 
                // If the node has multiple inputs, check that they are all valid and
@@ -933,16 +935,16 @@ void EffectChain::propagate_alpha()
                        continue;
                }
 
                        continue;
                }
 
-               if (alpha_handling == Effect::INPUT_AND_OUTPUT_PREMULTIPLIED_ALPHA) {
+               if (alpha_handling == Effect::INPUT_AND_OUTPUT_PREMULTIPLIED_ALPHA ||
+                   alpha_handling == Effect::INPUT_PREMULTIPLIED_ALPHA_KEEP_BLANK) {
                        // If the effect has asked for premultiplied alpha, check that it has got it.
                        if (any_postmultiplied) {
                                node->output_alpha_type = ALPHA_INVALID;
                        // If the effect has asked for premultiplied alpha, check that it has got it.
                        if (any_postmultiplied) {
                                node->output_alpha_type = ALPHA_INVALID;
+                       } else if (!any_premultiplied &&
+                                  alpha_handling == Effect::INPUT_PREMULTIPLIED_ALPHA_KEEP_BLANK) {
+                               // Blank input alpha, and the effect preserves blank alpha.
+                               node->output_alpha_type = ALPHA_BLANK;
                        } else {
                        } else {
-                               // In some rare cases, it might be advantageous to say
-                               // that blank input alpha yields blank output alpha.
-                               // However, this would cause a more complex Effect interface
-                               // an effect would need to guarantee that it doesn't mess with
-                               // blank alpha), so this is the simplest.
                                node->output_alpha_type = ALPHA_PREMULTIPLIED;
                        }
                } else {
                                node->output_alpha_type = ALPHA_PREMULTIPLIED;
                        }
                } else {
index e7f25e53861045ac8b0b49581ed91c173709b6b6..cea22e0a3e03dd5a1b855c46dc3600c3cb925e56 100644 (file)
@@ -448,6 +448,64 @@ TEST(EffectChainTest, NoAlphaConversionsWithBlankAlpha) {
        expect_equal(data, out_data, 4, size);
 }
 
        expect_equal(data, out_data, 4, size);
 }
 
+// An effect that does nothing, and specifies that it preserves blank alpha.
+class BlankAlphaPreservingEffect : public Effect {
+public:
+       BlankAlphaPreservingEffect() {}
+       virtual std::string effect_type_id() const { return "BlankAlphaPreservingEffect"; }
+       std::string output_fragment_shader() { return read_file("identity.frag"); }
+       virtual AlphaHandling alpha_handling() const { return INPUT_PREMULTIPLIED_ALPHA_KEEP_BLANK; }
+};
+
+TEST(EffectChainTest, NoAlphaConversionsWithBlankAlphaPreservingEffect) {
+       const int size = 3;
+       float data[4 * size] = {
+               0.0f, 0.0f, 1.0f, 1.0f,
+               0.0f, 0.0f, 1.0f, 1.0f,
+               0.0f, 0.0f, 1.0f, 1.0f,
+       };
+       float out_data[4 * size];
+       EffectChainTester tester(NULL, size, 1);
+       tester.get_chain()->add_input(new BlueInput());
+       tester.get_chain()->add_effect(new BlankAlphaPreservingEffect());
+       RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
+       tester.get_chain()->add_effect(effect);
+       tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED);
+
+       Node *node = effect->replaced_node;
+       EXPECT_EQ(1, node->incoming_links.size());
+       EXPECT_EQ(0, node->outgoing_links.size());
+
+       expect_equal(data, out_data, 4, size);
+}
+
+// This is the counter-test to NoAlphaConversionsWithBlankAlphaPreservingEffect;
+// just to be sure that with a normal INPUT_AND_OUTPUT_PREMULTIPLIED_ALPHA effect,
+// an alpha conversion _should_ be inserted at the very end. (There is some overlap
+// with other tests.)
+TEST(EffectChainTest, AlphaConversionsWithNonBlankAlphaPreservingEffect) {
+       const int size = 3;
+       float data[4 * size] = {
+               0.0f, 0.0f, 1.0f, 1.0f,
+               0.0f, 0.0f, 1.0f, 1.0f,
+               0.0f, 0.0f, 1.0f, 1.0f,
+       };
+       float out_data[4 * size];
+       EffectChainTester tester(NULL, size, 1);
+       tester.get_chain()->add_input(new BlueInput());
+       tester.get_chain()->add_effect(new IdentityEffect());  // Not BlankAlphaPreservingEffect.
+       RewritingEffect<MirrorEffect> *effect = new RewritingEffect<MirrorEffect>();
+       tester.get_chain()->add_effect(effect);
+       tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_LINEAR, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED);
+
+       Node *node = effect->replaced_node;
+       EXPECT_EQ(1, node->incoming_links.size());
+       EXPECT_EQ(1, node->outgoing_links.size());
+       EXPECT_EQ("AlphaDivisionEffect", node->outgoing_links[0]->effect->effect_type_id());
+
+       expect_equal(data, out_data, 4, size);
+}
+
 // Effectively scales down its input linearly by 4x (and repeating it),
 // which is not attainable without mipmaps.
 class MipmapNeedingEffect : public Effect {
 // Effectively scales down its input linearly by 4x (and repeating it),
 // which is not attainable without mipmaps.
 class MipmapNeedingEffect : public Effect {
index 8b21e521a9449979aae90ad7df14ef903f8c6b34..abadb84a4cb332a10d45216d73a2fe6bfa2edc6c 100644 (file)
@@ -42,6 +42,8 @@ public:
        HighlightCutoffEffect();
        virtual std::string effect_type_id() const { return "HighlightCutoffEffect"; }
        std::string output_fragment_shader();
        HighlightCutoffEffect();
        virtual std::string effect_type_id() const { return "HighlightCutoffEffect"; }
        std::string output_fragment_shader();
+       
+       virtual AlphaHandling alpha_handling() const { return INPUT_PREMULTIPLIED_ALPHA_KEEP_BLANK; }
 
 private:
        float cutoff;
 
 private:
        float cutoff;
index 36f6a77370461c2c66b8d6b5e60b11e298c21df4..6835620e11b432a73daf0df7d1331d9303e5a9fa 100644 (file)
@@ -26,6 +26,7 @@ class LiftGammaGainEffect : public Effect {
 public:
        LiftGammaGainEffect();
        virtual std::string effect_type_id() const { return "LiftGammaGainEffect"; }
 public:
        LiftGammaGainEffect();
        virtual std::string effect_type_id() const { return "LiftGammaGainEffect"; }
+       virtual AlphaHandling alpha_handling() const { return INPUT_PREMULTIPLIED_ALPHA_KEEP_BLANK; }
        std::string output_fragment_shader();
 
        void set_gl_state(GLuint glsl_program_num, const std::string &prefix, unsigned *sampler_num);
        std::string output_fragment_shader();
 
        void set_gl_state(GLuint glsl_program_num, const std::string &prefix, unsigned *sampler_num);
index c6739653b398b7cd9791af5361d32197dd3d8499..967c06b2ba59ac47b9ed21b7f1692f90bfdde854 100644 (file)
@@ -15,6 +15,10 @@ public:
        virtual bool needs_srgb_primaries() const { return false; }
        virtual unsigned num_inputs() const { return 2; }
 
        virtual bool needs_srgb_primaries() const { return false; }
        virtual unsigned num_inputs() const { return 2; }
 
+       // TODO: In the common case where a+b=1, it would be useful to be able to set
+       // alpha_handling() to INPUT_PREMULTIPLIED_ALPHA_KEEP_BLANK. However, right now
+       // we have no way of knowing that at instantiation time.
+
 private:
        float strength_first, strength_second;
 };
 private:
        float strength_first, strength_second;
 };
index d26d9175110ca636890e5c03084a98a7e747560f..4c6d37a4fea6d9a95715031426eb7332fcf73346 100644 (file)
@@ -21,10 +21,12 @@ public:
        virtual bool needs_srgb_primaries() const { return false; }
        virtual unsigned num_inputs() const { return 2; }
 
        virtual bool needs_srgb_primaries() const { return false; }
        virtual unsigned num_inputs() const { return 2; }
 
-       // Actually, if either image has blank alpha, our output will have
-       // blank alpha, too. However, understanding that would require changes
+       // Actually, if _either_ image has blank alpha, our output will have
+       // blank alpha, too (this only tells the framework that having _both_
+       // images with blank alpha would result in blank alpha).
+       // However, understanding that would require changes
        // to EffectChain, so postpone that optimization for later.
        // to EffectChain, so postpone that optimization for later.
-       virtual AlphaHandling alpha_handling() const { return INPUT_AND_OUTPUT_PREMULTIPLIED_ALPHA; }
+       virtual AlphaHandling alpha_handling() const { return INPUT_PREMULTIPLIED_ALPHA_KEEP_BLANK; }
 };
 
 #endif // !defined(_OVERLAY_EFFECT_H)
 };
 
 #endif // !defined(_OVERLAY_EFFECT_H)
index 3c4919a0c3b1b98a3aa31ebf9aad14ff536bcdcf..f8d30d480886aa364d103f90e3d38b1899e0f32d 100644 (file)
@@ -89,13 +89,25 @@ bool PaddingEffect::needs_srgb_primaries() const
        return true;
 }
 
        return true;
 }
 
-// If the border color is black, it doesn't matter if we're pre- or postmultiplied
-// (or even blank, as a hack). Otherwise, it does.
 Effect::AlphaHandling PaddingEffect::alpha_handling() const
 {
 Effect::AlphaHandling PaddingEffect::alpha_handling() const
 {
-       if (border_color.r == 0.0 && border_color.g == 0.0 && border_color.b == 0.0) {
+       // If the border color is black, it doesn't matter if we're pre- or postmultiplied.
+       // Note that for non-solid black (i.e. alpha < 1.0), we're equally fine with
+       // pre- and postmultiplied, but later effects might change this status
+       // (consider e.g. blur), so setting DONT_CARE_ALPHA_TYPE is inappropriate,
+       // as it propagate blank alpha through this effect.
+       if (border_color.r == 0.0 && border_color.g == 0.0 && border_color.b == 0.0 && border_color.a == 1.0) {
                return DONT_CARE_ALPHA_TYPE;
        }
                return DONT_CARE_ALPHA_TYPE;
        }
+
+       // If the border color is solid, we preserve blank alpha, as we never output any
+       // new non-solid pixels.
+       if (border_color.a == 1.0) {
+               return INPUT_PREMULTIPLIED_ALPHA_KEEP_BLANK;
+       }
+
+       // Otherwise, we're going to output our border color in premultiplied alpha,
+       // so the other pixels better be premultiplied as well.
        return INPUT_AND_OUTPUT_PREMULTIPLIED_ALPHA;
 }
        
        return INPUT_AND_OUTPUT_PREMULTIPLIED_ALPHA;
 }
        
index a0ae1a8c156176da9ed41d222cfff4d09318c402..b38728f05fa423061bb9002ce2bfb261ff73c4ff 100644 (file)
@@ -29,6 +29,7 @@ public:
        // down quite a lot.
        virtual bool needs_texture_bounce() const { return true; }
        virtual bool needs_srgb_primaries() const { return false; }
        // down quite a lot.
        virtual bool needs_texture_bounce() const { return true; }
        virtual bool needs_srgb_primaries() const { return false; }
+       virtual AlphaHandling alpha_handling() const { return INPUT_PREMULTIPLIED_ALPHA_KEEP_BLANK; }
 
        virtual void inform_input_size(unsigned input_num, unsigned width, unsigned height);
 
 
        virtual void inform_input_size(unsigned input_num, unsigned width, unsigned height);
 
index c11f4bc9ea6dd9fd4e52241e5e16b8c46b73da21..3cb40e3344d7703180e90e8d2352a73df876e87a 100644 (file)
@@ -17,6 +17,7 @@ public:
        // in case we need to scale down a lot.
        virtual bool need_texture_bounce() const { return true; }
        virtual bool needs_mipmaps() const { return true; }
        // in case we need to scale down a lot.
        virtual bool need_texture_bounce() const { return true; }
        virtual bool needs_mipmaps() const { return true; }
+       virtual AlphaHandling alpha_handling() const { return INPUT_PREMULTIPLIED_ALPHA_KEEP_BLANK; }
 
        virtual bool changes_output_size() const { return true; }
        virtual void get_output_size(unsigned *width, unsigned *height, unsigned *virtual_width, unsigned *virtual_height) const;
 
        virtual bool changes_output_size() const { return true; }
        virtual void get_output_size(unsigned *width, unsigned *height, unsigned *virtual_width, unsigned *virtual_height) const;