WPF Color의 기본 색 영역은 (sRGB가 아닌) scRGB
재미있는 질문이 올라왔군요. ^^
안녕하세요 rgb 계산 오차가 있는데 원인을 모르겠습니다..
; https://www.sysnet.pe.kr/3/0/5713
간단하게 코드로는 다음과 같이 재현할 수 있습니다.
Color start = Color.FromRgb(0xff, 0, 9);
Color add1 = Color.FromRgb(0, 0, 5);
Color add2 = Color.FromRgb(0, 0, 6);
Color newColor1 = start + add1;
Color newColor2 = start + add2;
System.Diagnostics.Trace.WriteLine($"{newColor1.R}, {newColor1.G}, {newColor1.B}");
System.Diagnostics.Trace.WriteLine($"{newColor2.R}, {newColor2.G}, {newColor2.B}");
/* 출력 결과
255, 0, 14
255, 0, 14
*/
보는 바와 같이 출력 결과가 동일합니다. 왜 이런 결과가 나올까요? ^^ 사실 WPF의 Color는 (외부적으로는 사용하기 쉬우므로) sRGB 체계를 사용하는 듯하지만 내부적으로는 이것을 scRGB 체계로 변환해 연산을 하기 때문입니다.
이를 Color.FromRgb의 소스 코드에서 알 수 있습니다.
public static Color FromArgb(byte a, byte r, byte g, byte b)
{
Color result = default(Color);
result.scRgbColor.a = (float)(int)a / 255f;
result.scRgbColor.r = sRgbToScRgb(r);
result.scRgbColor.g = sRgbToScRgb(g);
result.scRgbColor.b = sRgbToScRgb(b);
result.context = null;
result.sRgbColor.a = a;
result.sRgbColor.r = ScRgbTosRgb(result.scRgbColor.r);
result.sRgbColor.g = ScRgbTosRgb(result.scRgbColor.g);
result.sRgbColor.b = ScRgbTosRgb(result.scRgbColor.b);
result.isFromScRgb = false;
return result;
}
보는 바와 같이 전달된 R, G, B 값을 변환해 scRgbColor에 설정하고 있고, 심지어 sRgbColor 값조차도 전달된 인자를 대입하지 않고 굳이 ScRgbTosRgb를 통해 설정하고 있습니다. 이후 더하기 연산을 할 때는,
public static Color operator +(Color color1, Color color2)
{
if (color1.context == null && color2.context == null)
{
return FromScRgb(color1.scRgbColor.a + color2.scRgbColor.a, color1.scRgbColor.r + color2.scRgbColor.r, color1.scRgbColor.g + color2.scRgbColor.g, color1.scRgbColor.b + color2.scRgbColor.b);
}
if (color1.context == color2.context)
{
// ... context에 따른 연산...
return result;
}
throw new ArgumentException(MS.Internal.PresentationCore.SR.Get("Color_ColorContextTypeMismatch", null));
}
(context의 기본값이 null이므로) scRgbColor의 rgba 값을 더하는 연산을 합니다. 따라서 처음에 예로 든 코드를 scRGB 기준으로 다시 출력해 보면,
System.Diagnostics.Trace.WriteLine(newColor1);
System.Diagnostics.Trace.WriteLine(newColor2);
/* 출력 결과
sc#2, 1, 0, 0.00424937764
sc#2, 1, 0, 0.00455290452
*/
다르게 나오므로 정상적으로 더하기 연산은 된 것입니다. 단지, scRGB에서의 저런 차이가 sRGB 값으로 바꿀 때 발생하는 연산 손실로 인해 같은 것처럼 보이는 것입니다.
byte b1 = ScRgbTosRgb(0.00424937764);
byte b2 = ScRgbTosRgb(0.00455290452);
System.Diagnostics.Trace.WriteLine(b1); // 14
System.Diagnostics.Trace.WriteLine(b2); // 14
static byte ScRgbTosRgb(float val)
{
if (!((double)val > 0.0))
{
return 0;
}
if ((double)val <= 0.0031308)
{
return (byte)(255f * val * 12.92f + 0.5f);
}
if ((double)val < 1.0)
{
return (byte)(255f * (1.055f * (float)Math.Pow(val, 5.0 / 12.0) - 0.055f) + 0.5f);
}
return byte.MaxValue;
}
어쩔 수 없습니다. Color space끼리의 변환이 비가역적인 연산이기 때문에 출력에서의 R, G, B는 그에 대한 부분을 감안해야 합니다.
정리해 보면, WPF가 scRGB를 따르므로 원래 연산은 이런 식으로 했어야 합니다.
Color start = Color.FromScRgb(0, 1, 0, 0.0027317428f);
Color add1 = Color.FromScRgb(0, 0, 0, 0.001517635f);
Color add2 = Color.FromScRgb(0, 0, 0, 0.001821162f);
Color newColor1 = start + add1;
Color newColor2 = start + add2;
System.Diagnostics.Trace.WriteLine(newColor1);
System.Diagnostics.Trace.WriteLine(newColor2);
/* 출력 결과
sc#0, 1, 0, 0.00424937764
sc#0, 1, 0, 0.00455290452
*/
저렇게 보면... 전혀 이상한 것이 없죠?~~~ ^^
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]