3
$\begingroup$

I have two RGBA colors in linear [0..1] space, src and dst and I want to alpha blender src over/under dst n times. Of course, I could call the respective operation n times using the result from the previous one (which is what I am currently doing), but I want to optimize it into a single operation. I think just calculating the correct alpha on src should be sufficient, but I may be wrong. Here's what I currently use:

public static final Color alphaOver(Color dst, Color src) {
    float src1A = 1 - src.a;
    float outA = src.a + dst.a * src1A;

    if (outA == 0)
        return Color.TRANSPARENT;
    return new Color(
            outA,
            (src.r * src.a + dst.r * dst.a * src1A) / outA,
            (src.g * src.a + dst.g * dst.a * src1A) / outA,
            (src.b * src.a + dst.b * dst.a * src1A) / outA);
}

public static final Color alphaUnder(Color dst, Color src) {
    return alphaOver(src, dst);
}

public static final Color alphaOver(Color dst, Color src, int times) {
    Color ret = dst;
    for (int i = 0; i < times; i++)
        ret = alphaOver(ret, src);
    return ret;
}

public static final Color alphaUnder(Color dst, Color src, int times) {
    Color ret = dst;
    for (int i = 0; i < times; i++)
        ret = alphaUnder(ret, src);
    return ret;
}

How can I optimize the last two functions?

$\endgroup$
1
$\begingroup$

So taking source over as an example, the math works out to:

dst = a * src + (1 - a) * dst

Taking Nathan Reed's suggestion of replacing (1 - a) with b, that gives us:

dst = a * src + b * dst

If we perform the next iteration, it becomes:

dst = a * src + ab * src + b^2 * dst

Doing it again, we get:

dst = a * src + ab * src + ab^2 * src + b^3 * dst

And then:

dst = a * src + ab * src + ab^2 * src + ab^3 * src + b^4 * dst

The pattern appears to be:

int exponent;
for (exponent = 0; exponent < numIterations; exponent++)
{
    newDst += a * pow(b, exponent) * src;
}
newDst += pow(b, exponent) * dst;
$\endgroup$
  • $\begingroup$ Are you sure to plug the result of the first alpha over into src of the next one? As I understand it, I would put it into dst instead. This lines up with some quick tests of the final equation with black src, white dst and an a>0. Overlaying black over white should darken the color but ` + dst` prevents this. $\endgroup$ – piegames Sep 7 '18 at 20:43
  • $\begingroup$ I agree that substituting into dst is the correct thing to do here. If you imagine doing n separate blends on top of each other, the src would be the same for all, and result of each blend would be the following blend's dst. I think you should be able to simplify the result to a single blend using alpha of 1 - (1 - a)^n though. (Try defining a new variable b = 1 - a, and repeating the derivation you used for the first part of the answer.) $\endgroup$ – Nathan Reed Sep 8 '18 at 2:37
  • $\begingroup$ Great idea! I've updated it with your suggestion. Let me know how it works. $\endgroup$ – user1118321 Sep 8 '18 at 5:30
  • $\begingroup$ How about dst := (1 - b)*src + b*dst, and then rewrite that as src + b*(dst - src). Then it works out similarly to your original derivation. :) $\endgroup$ – Nathan Reed Sep 8 '18 at 5:48
  • $\begingroup$ Ah, I see what you mean. I'll give that a try in the morning. I'm getting sleepy now and would probably screw it up. $\endgroup$ – user1118321 Sep 8 '18 at 6:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.