1

I implemented this code from another question, and it works really well.

The issue I am having is repeatedly mixing two colors slowly results in black. Here is an example of the effect.

Example

I am using C#, and I have the RGBA of two colors, (background & foreground) in a Color32 type - 1 byte for red, green, blue and alpha. At the moment I am converting the 0-255 to 0-1 but I think there is some floating point conversion issues.

What's a way that won't have this effect?

EDIT: Here is the code I am using. currentColor is the canvas (background) and brushColor is the foreground.

float bg_r = ((float)currentColor.r) / 255.0f;
float bg_g = ((float)currentColor.g) / 255.0f;
float bg_b = ((float)currentColor.b) / 255.0f;
float bg_a = ((float)currentColor.a) / 255.0f;

float bg_r_a = bg_r * bg_a;
float bg_g_a = bg_g * bg_a;
float bg_b_a = bg_b * bg_a;

float fg_r = ((float)brushColor.r) / 255.0f;
float fg_g = ((float)brushColor.g) / 255.0f;
float fg_b = ((float)brushColor.b) / 255.0f;
float fg_a = (((float)brushColor.a) / 255.0f) * brush.pressure; // 0 - 1

float fg_r_a = fg_r * fg_a;
float fg_g_a = fg_g * fg_a;
float fg_b_a = fg_b * fg_a;

float col_r_a = fg_r_a + bg_r_a * ( 1.0f - fg_a );
float col_g_a = fg_g_a + bg_g_a * ( 1.0f - fg_a );
float col_b_a = fg_b_a + bg_b_a * ( 1.0f - fg_a );
float col_a = fg_a + bg_a * ( 1.0f - fg_a );

float col_r = col_r_a / col_a;
float col_g = col_g_a / col_a;
float col_b = col_b_a / col_a;

byte colR = (byte)Mathf.Clamp( col_r * 255.0f, 0.0f, 255.0f );
byte colG = (byte)Mathf.Clamp( col_g * 255.0f, 0.0f, 255.0f );
byte colB = (byte)Mathf.Clamp( col_b * 255.0f, 0.0f, 255.0f );
byte colA = (byte)Mathf.Clamp( col_a * 255.0f, 0.0f, 255.0f );

Color32 outputColor = new Color32( colR, colG, colB, colA );

Link: http://pastebin.com/hXkKSYLe

Community
  • 1
  • 1
Hugo Scott-Slade
  • 896
  • 9
  • 16
  • 2
    Can we see your code? – itsme86 Apr 14 '14 at 14:23
  • Agree with itsme. Do you round correctly or just truncate the floats? If it's the latter, this might be the problem. Apart from that, if you need the float values anyway, I would store it as a float and just convert to int for displaying the image. This way you won't have data loss. – Nico Schertler Apr 14 '14 at 14:25
  • I added my code sample. Storing as a float is probably the way to go, I will convert over and test later. – Hugo Scott-Slade Apr 15 '14 at 11:26
  • Realising this is years later and I never posted my result - storing data internally as float and only converting to byte when setting data fixed the issue. – Hugo Scott-Slade Jul 14 '17 at 15:02

1 Answers1

1

The problem is likely in these lines:

byte colR = (byte)Mathf.Clamp( col_r * 255.0f, 0.0f, 255.0f );

Specifically, the (byte) cast. Casting from a floating-point type to an integral type will truncate the fractional part:

Console.WriteLine((byte)1.9f);  // Outputs "1"
Console.WriteLine((int)-1.9f);  // Outputs "-1"

So after each conversion back to bytes, you'll lose an average of 0.5 byte-sized increments, and after an average of 512 conversions, your color will drift to black.

Try adding a call to Math.Round to remove the drift.

byte colR = (byte)Math.Round(Mathf.Clamp( col_r * 255.0f, 0.0f, 255.0f ));
Joe White
  • 94,807
  • 60
  • 220
  • 330