3

I'm trying to average two colors.

My original (horrible) implement is as follows:

//color is a union
int ColorAverage(int c1, int c2) {
    color C1(c1);
    color C2(c2);
    return color(
        (unsigned char)(0.5f * C1.a + 0.5f * C2.a),
        (unsigned char)(0.5f * C1.r + 0.5f * C2.r),
        (unsigned char)(0.5f * C1.g + 0.5f * C2.g),
        (unsigned char)(0.5f * C1.b + 0.5f * C2.b)
    ).c;
}

My current solution is as follows (which performs considerably better):

int ColorAverage(int c1, int c2) {
    unsigned char* b1 = reinterpret_cast<unsigned char*>(&c1);
    unsigned char* b2 = reinterpret_cast<unsigned char*>(&c2);
    int value;
    unsigned char* bv = reinterpret_cast<unsigned char*>(&value);
    bv[0] = (b1[0] + b2[0]) / 2;
    bv[1] = (b1[1] + b2[1]) / 2;
    bv[2] = (b1[2] + b2[2]) / 2;
    bv[3] = (b1[3] + b2[3]) / 2;
    return(value);
}

However, it's still quite slow (it's about 3% of my frame time).

I did find a solution for 24bit, but it does not apply to 32bit (the alpha is lost):

#define AVERAGE(a, b)   ( ((((a) ^ (b)) & 0xfffefefeL) >> 1) + ((a) & (b)) )

http://www.compuphase.com/graphic/scale3.htm#HSIEH1

Mike Weir
  • 3,094
  • 1
  • 30
  • 46

2 Answers2

5

Try extending your mask to 32 bits, like this:

#define AVERAGE(a, b)   ( ((((a) ^ (b)) & 0xfefefefeL) >> 1) + ((a) & (b)) )

Edit: I did a quick check, and it appears to work for my test case. Nice formula, by the way!

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
4

The goal is to take the following operation:

(a + b) / 2 = ((a ^ b) >> 1) + (a & b)

And apply it to all four bytes of the integer. If this were just one byte, then the right shift by 1 bit would discard the right-most bit. However, in this case, the right-most bit of the leading 3 bytes isn't discarded---it's shifted into the neighboring byte. The idea to keep in mind is that you need the mask the last bit of each byte so that it doesn't 'contaminate' the neighboring byte during the shift. For example, say that a ^ b is this:

a XOR b = 1011 1101 1110 1001

A right-shift of 1 bit, without the mask, would look like this:

(a XOR b) >> 1 = 0101 1110 1111 0100

Which is wrong. The mask zero's out the last bit of each byte, so that this doesn't happen:

(a XOR b) AND 0xfefefefe = 1010 1100 1110 1000

Then you can shift this value safely to the right:

((a XOR b) AND 0xfefefefe) = 0101 0110 0111 0100

So:

#define AVERAGE(a, b)   ( ((((a) ^ (b)) & 0xfefefefeL) >> 1) + ((a) & (b)) )

One thing to keep in mind is that C does not differentiate arithmetic right shift from logical right shift with its operator. You'll want to make sure that the integers you are shifting are unsigned, to prevent implementation-specific signed-integer shift voodoo.

EDIT: I think @dasblinkenlight may have beaten me the this answer. Just beware of shifting signed integers and you should be good.

Community
  • 1
  • 1
Christopher Neylan
  • 8,018
  • 3
  • 38
  • 51