@@ -15,63 +15,77 @@ namespace Ultima.Helpers
1515{
1616 public static class HueHelpers
1717 {
18- /// <summary>
19- /// Converts RGB value to Hue color
20- /// </summary>
21- /// <param name="color"></param>
22- /// <returns></returns>
23- public static ushort ColorToHue ( Color color )
18+ // Canonical 8-bit RGB -> 15-bit hue. Uses bit-shift (>>3) packing with the rule:
19+ // input all-zero -> 0; else any lane that collapses to 0 -> 1.
20+ public static ushort ColorToHue ( Color color ) => ColorToHueShift ( color . R , color . G , color . B ) ;
21+
22+ // Canonical 15-bit hue -> 32-bit ARGB, expanding 5-bit components via
23+ // (c<<3)|(c>>2) so 31 maps to 255 (not 248 as the previous *8 integer math did).
24+ public static Color HueToColor ( ushort hue )
2425 {
25- const double scale = 31.0 / 255 ;
26+ return Color . FromArgb (
27+ Expand5To8 ( ( hue & 0x7c00 ) >> 10 ) ,
28+ Expand5To8 ( ( hue & 0x03e0 ) >> 5 ) ,
29+ Expand5To8 ( hue & 0x001f ) ) ;
30+ }
2631
27- ushort origRed = color . R ;
28- var newRed = ( ushort ) ( origRed * scale ) ;
29- if ( newRed == 0 && origRed != 0 )
30- {
31- newRed = 1 ;
32- }
32+ public static int HueToColorR ( ushort hue ) => Expand5To8 ( ( hue & 0x7c00 ) >> 10 ) ;
33+ public static int HueToColorG ( ushort hue ) => Expand5To8 ( ( hue & 0x03e0 ) >> 5 ) ;
34+ public static int HueToColorB ( ushort hue ) => Expand5To8 ( hue & 0x001f ) ;
3335
34- ushort origGreen = color . G ;
35- var newGreen = ( ushort ) ( origGreen * scale ) ;
36- if ( newGreen == 0 && origGreen != 0 )
36+ // Canonical RGB->555 packer for this format. Packs each channel via bit-shift:
37+ // result = ((r>>3) << 10) | ((g>>3) << 5) | (b>>3)
38+ // Clamp rule: if r|g|b == 0 the pixel is 0 (transparent); else if a lane
39+ // downscales to 0, force that lane to 1.
40+ public static ushort ColorToHueShift ( int r8 , int g8 , int b8 )
41+ {
42+ if ( ( r8 | g8 | b8 ) == 0 )
3743 {
38- newGreen = 1 ;
44+ return 0 ;
3945 }
4046
41- ushort origBlue = color . B ;
42- var newBlue = ( ushort ) ( origBlue * scale ) ;
43- if ( newBlue == 0 && origBlue != 0 )
47+ int r5 = r8 >> 3 ;
48+ int g5 = g8 >> 3 ;
49+ int b5 = b8 >> 3 ;
50+ if ( r5 == 0 && g5 == 0 && b5 == 0 )
4451 {
45- newBlue = 1 ;
52+ return 1 ;
4653 }
4754
48- return ( ushort ) ( ( newRed << 10 ) | ( newGreen << 5 ) | newBlue ) ;
55+ return ( ushort ) ( ( r5 << 10 ) | ( g5 << 5 ) | b5 ) ;
4956 }
5057
51- /// <summary>
52- /// Converts Hue color to RGB color
53- /// </summary>
54- /// <param name="hue"></param>
55- /// <returns></returns>
56- public static Color HueToColor ( ushort hue )
58+ // Rounding alternative: ((c*31 + 127) / 255). Same clamp rule as the shift version.
59+ public static ushort ColorToHueRounded ( int r8 , int g8 , int b8 )
5760 {
58- const int scale = 255 / 31 ;
59- return Color . FromArgb ( ( ( hue & 0x7c00 ) >> 10 ) * scale , ( ( hue & 0x3e0 ) >> 5 ) * scale , ( hue & 0x1f ) * scale ) ;
60- }
61+ if ( ( r8 | g8 | b8 ) == 0 )
62+ {
63+ return 0 ;
64+ }
6165
62- public static int HueToColorR ( ushort hue )
63- {
64- return ( ( hue & 0x7c00 ) >> 10 ) * ( 255 / 31 ) ;
66+ int r5 = ( r8 * 31 + 127 ) / 255 ;
67+ int g5 = ( g8 * 31 + 127 ) / 255 ;
68+ int b5 = ( b8 * 31 + 127 ) / 255 ;
69+ if ( r5 == 0 && g5 == 0 && b5 == 0 )
70+ {
71+ return 1 ;
72+ }
73+
74+ return ( ushort ) ( ( r5 << 10 ) | ( g5 << 5 ) | b5 ) ;
6575 }
6676
67- public static int HueToColorG ( ushort hue )
77+ public static void HueExtract5 ( ushort hue , out int r5 , out int g5 , out int b5 )
6878 {
69- return ( ( hue & 0x3e0 ) >> 5 ) * ( 255 / 31 ) ;
79+ r5 = ( hue & 0x7c00 ) >> 10 ;
80+ g5 = ( hue & 0x03e0 ) >> 5 ;
81+ b5 = hue & 0x001f ;
7082 }
7183
72- public static int HueToColorB ( ushort hue )
84+ // Canonical 5-bit -> 8-bit expansion: replicate top 3 bits into the low ones.
85+ // Equivalent to round(c5 * 255 / 31). 0->0, 31->255, monotonic.
86+ public static int Expand5To8 ( int c5 )
7387 {
74- return ( hue & 0x1f ) * ( 255 / 31 ) ;
88+ return ( c5 << 3 ) | ( c5 >> 2 ) ;
7589 }
7690 }
7791}
0 commit comments