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);
}
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.
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; }
// 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 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,
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);
// 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
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;
+ } 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 {
- // 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 {
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 {
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;
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);
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;
};
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.
- 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)
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
{
- 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;
}
+
+ // 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;
}
// 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);
// 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;