How to do ARGB color interpolation without separating components?

63 views Asked by At

After trying and failing to figure this out myself, I have scavenged some code which should combine two colors (integers) by a specified fraction from another thread, but I am just simply not crafty enough to get it working with ARGB integers. Does anyone know how to convert this function from combining RGB to combining ARGB?

public static int mixColors(int a, int b, float fractionB){ 
   int mask1 = 0xff00ff; 
   int mask2 = 0x00ff00; 
    
   int f2 = (int)(256 * fractionB);
   int f1 = 256 - f2;
   
   return   ((((( a & mask1 ) * f1 ) + ( ( b & mask1 ) * f2 )) >>> 8 ) & mask1 ) 
          | ((((( a & mask2 ) * f1 ) + ( ( b & mask2 ) * f2 )) >>> 8 ) & mask2 );
}

I have tried setting the masks to mask1 = 0x00ff00ff; mask2 = 0xff00ff00; but the function still only outputs a 3 byte number.

2

There are 2 answers

1
MJSD On BEST ANSWER

"...since using int the 4th byte is lost - you can use long in the calculation (mask and factors) to avoid that" - user16320675

Thank you! I should have thought of that, but I guess that's what they all say. Here are the updated functions for 32bit integer ARGB color values:

public static int mixColors(int a, int b){
    long mask1 = 0x00ff00ffL;
    long mask2 = 0xff00ff00L;

    return (int)((((((a & mask1) + (b & mask1)) * 128) >> 8) & mask1) 
               | (((((a & mask2) + (b & mask2)) * 128) >> 8) & mask2));
}

public static int mixColors(int a, int b, float fractionB){
    long mask1 = 0x00ff00ffL;
    long mask2 = 0xff00ff00L;

    short f2 = (short)(256 * fractionB),
          f1 = (short)(256 - f2);
          
    return (int)((((((a & mask1) * f1) + ((b & mask1) * f2)) >> 8) & mask1) 
               | (((((a & mask2) * f1) + ((b & mask2) * f2)) >> 8) & mask2));
}
0
Dawid Kurzyniec On

Unfortunately, the answer above works only if the representation of ARGB is 'premultiplied' (see https://en.wikipedia.org/wiki/Alpha_compositing) - otherwise, interpolation will assign too much weight to non-opaque colors. (For example, if one of the colors has alpha = 0 (invisible), the 50/50 interpolation will still colorize the result using 50% of that invisible color).

For 'straight' (non-premultiplied) ARGB, one must first calculate the interpolated alpha (equal to a_alpha * f1 + b_alpha * f2), and then separately the RGB part, using adjusted interpolation coefficients. C++ code below (should be easy enough to convert to Java):

// Interpolates between the two colors using the specified `fraction`, in the
// 0-256 range. 0 means result = c1; 256 means result = c2; 128 means 50/50.
inline Color InterpolateColors(Color c1, Color c2, int16_t fraction) {
  int16_t f2 = fraction;
  int16_t f1 = 256 - fraction;
  uint16_t c1_a = c1.a();
  uint16_t c2_a = c2.a();
  uint32_t a_mult = (c1_a * f1 + c2_a * f2);
  uint32_t a = a_mult / 256;
  if (c1_a == c2_a) {
    // Common case, e.g. both colors opaque. Leave fractions as-is.
  } else if (c1_a == 0) {
    f1 = 0;
    f2 = 256;
  } else if (c2_a == 0) {
    f1 = 256;
    f2 = 0;
  } else {
    f2 = (uint32_t)256 * f2 * c2_a / a_mult;
    f1 = 256 - f2;
  }

  uint32_t mask1 = 0x00FF00FF;
  uint32_t mask2 = 0x0000FF00;
  uint32_t rgb =
      (((((c1.asArgb() & mask1) * f1) + ((c2.asArgb() & mask1) * f2)) / 256) &
       mask1) |
      (((((c1.asArgb() & mask2) * f1) + ((c2.asArgb() & mask2) * f2)) / 256) &
       mask2);
  return Color(a << 24 | rgb);
}