diff --git a/engine/Age.Engine.Tests/BlendMathTests.cs b/engine/Age.Engine.Tests/BlendMathTests.cs new file mode 100644 index 0000000..eaffa9a --- /dev/null +++ b/engine/Age.Engine.Tests/BlendMathTests.cs @@ -0,0 +1,34 @@ +using Age.Engine.Model; +using Xunit; + +public class BlendMathTests +{ + [Fact] + public void NegativeColorKey_IsNoKey() + { + Assert.False(BlendMath.HasColorKey(-1)); + Assert.False(BlendMath.ColorKeyMatches(0, 0, 0, -1)); // never matches when no key + } + + [Fact] + public void ZeroColorKey_KeysBlack() + { + Assert.True(BlendMath.HasColorKey(0)); + Assert.True(BlendMath.ColorKeyMatches(0, 0, 0, 0)); // (0,0,0) == key black + Assert.False(BlendMath.ColorKeyMatches(1, 0, 0, 0)); // near-black is NOT keyed (exact match) + } + + [Fact] + public void ColorKey_MatchesRgb888Bytes() + { + long key = 0xFF00FF; // magenta 0xRRGGBB + Assert.True(BlendMath.ColorKeyMatches(0xFF, 0x00, 0xFF, key)); + Assert.False(BlendMath.ColorKeyMatches(0xFE, 0x00, 0xFF, key)); + } + + [Fact] + public void UnpackArgb_SplitsBytes() + { + Assert.Equal((0x80, 0x12, 0x34, 0x56), BlendMath.UnpackArgb(0x80123456)); + } +} diff --git a/engine/Age.Engine/Model/BlendMath.cs b/engine/Age.Engine/Model/BlendMath.cs new file mode 100644 index 0000000..ff65d74 --- /dev/null +++ b/engine/Age.Engine/Model/BlendMath.cs @@ -0,0 +1,22 @@ +namespace Age.Engine.Model; + +/// Pure blend helpers shared by the engine resolution and the host blit. Colorkey semantics come +/// from the native loader (docs/engine-re.md ยง"Blend & transparency"): op arg 3 < 0 = no key; otherwise +/// the operand is 0xRRGGBB and matching (R,G,B) texels are transparent (operand 0 = key black). +public static class BlendMath +{ + /// A colorkey operand >= 0 is an active key; a negative operand means "no colorkey". + public static bool HasColorKey(long colorKey) => colorKey >= 0; + + /// True when (r,g,b) exactly equals the key's 0xRRGGBB bytes. Always false when there is no key. + public static bool ColorKeyMatches(byte r, byte g, byte b, long colorKey) + { + if (!HasColorKey(colorKey)) return false; + return r == ((colorKey >> 16) & 0xff) && g == ((colorKey >> 8) & 0xff) && b == (colorKey & 0xff); + } + + /// Split a 0xAARRGGBB packed color (see GfxState.PackColor) into its byte channels. + public static (int A, int R, int G, int B) UnpackArgb(long packed) + => ((int)((packed >> 24) & 0xff), (int)((packed >> 16) & 0xff), + (int)((packed >> 8) & 0xff), (int)(packed & 0xff)); +}