@@ -16,8 +16,15 @@ internal class PaletteTiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
1616 where TPixel : unmanaged, IPixel < TPixel >
1717{
1818 private readonly ushort bitsPerSample0 ;
19+ private readonly ushort bitsPerSample1 ;
20+ private readonly TiffExtraSampleType ? extraSamplesType ;
1921
20- private readonly TPixel [ ] palette ;
22+ private readonly Vector4 [ ] vectorPallete ;
23+ private readonly TPixel [ ] pixelPalette ;
24+
25+ private readonly float alphaScale ;
26+ private readonly bool hasAlpha ;
27+ private Color [ ] ? paletteColors ;
2128
2229 private const float InvMax = 1f / 65535f ;
2330
@@ -26,32 +33,124 @@ internal class PaletteTiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
2633 /// </summary>
2734 /// <param name="bitsPerSample">The number of bits per sample for each pixel.</param>
2835 /// <param name="colorMap">The RGB color lookup table to use for decoding the image.</param>
29- public PaletteTiffColor ( TiffBitsPerSample bitsPerSample , ushort [ ] colorMap )
36+ /// <param name="extraSamplesType">The type of extra samples.</param>
37+ public PaletteTiffColor ( TiffBitsPerSample bitsPerSample , ushort [ ] colorMap , TiffExtraSampleType ? extraSamplesType )
3038 {
3139 this . bitsPerSample0 = bitsPerSample . Channel0 ;
40+ this . bitsPerSample1 = bitsPerSample . Channel1 ;
41+ this . extraSamplesType = extraSamplesType ;
42+
3243 int colorCount = 1 << this . bitsPerSample0 ;
33- this . palette = GeneratePalette ( colorMap , colorCount ) ;
44+
45+ // TIFF PaletteColor uses ColorMap (tag 320 / 0x0140) which is RGB-only (no alpha).
46+ this . vectorPallete = GenerateVectorPalette ( colorMap , colorCount ) ;
47+
48+ // ExtraSamples (tag 338 / 0x0152) describes extra per-pixel samples stored in the image data stream.
49+ // For PaletteColor, any alpha is per pixel (stored alongside the index), not per palette entry.
50+ this . hasAlpha =
51+ this . bitsPerSample1 > 0
52+ && this . extraSamplesType . HasValue
53+ && this . extraSamplesType != TiffExtraSampleType . UnspecifiedData ;
54+
55+ if ( this . hasAlpha )
56+ {
57+ ulong alphaMax = ( 1UL << this . bitsPerSample1 ) - 1 ;
58+ this . alphaScale = alphaMax > 0 ? 1f / alphaMax : 1f ;
59+ this . pixelPalette = [ ] ;
60+ }
61+ else
62+ {
63+ // Pre-generate pixel palette for non-alpha case for performance.
64+ this . pixelPalette = GeneratePixelPalette ( colorMap , colorCount ) ;
65+ }
3466 }
3567
68+ public Color [ ] PaletteColors => this . paletteColors ??= GenerateColorPalette ( this . vectorPallete ) ;
69+
3670 /// <inheritdoc/>
3771 public override void Decode ( ReadOnlySpan < byte > data , Buffer2D < TPixel > pixels , int left , int top , int width , int height )
3872 {
3973 BitReader bitReader = new ( data ) ;
4074
75+ if ( this . hasAlpha )
76+ {
77+ Color [ ] colors = this . paletteColors ??= GenerateColorPalette ( this . vectorPallete ) ;
78+
79+ // NOTE: ExtraSamples may report "AssociatedAlphaData". For PaletteColor, the stored color sample is the
80+ // palette index, not per-pixel RGB components, so the premultiplication concept is not representable
81+ // in the encoded stream. We therefore treat the alpha sample as a per-pixel alpha value applied after
82+ // palette expansion.
83+ for ( int y = top ; y < top + height ; y ++ )
84+ {
85+ Span < TPixel > pixelRow = pixels . DangerousGetRowSpan ( y ) . Slice ( left , width ) ;
86+ for ( int x = 0 ; x < pixelRow . Length ; x ++ )
87+ {
88+ int index = bitReader . ReadBits ( this . bitsPerSample0 ) ;
89+ float alpha = bitReader . ReadBits ( this . bitsPerSample1 ) * this . alphaScale ;
90+
91+ // Defensive guard against malformed streams.
92+ if ( ( uint ) index >= ( uint ) this . vectorPallete . Length )
93+ {
94+ index = 0 ;
95+ }
96+
97+ Vector4 color = this . vectorPallete [ index ] ;
98+ color . W = alpha ;
99+
100+ pixelRow [ x ] = TPixel . FromScaledVector4 ( color ) ;
101+
102+ // Best-effort palette update for downstream conversions.
103+ // This is intentionally "last writer wins" with no per-pixel branch.
104+ // Performance is not an issue here since the constructor performs no actual transformations.
105+ colors [ index ] = Color . FromScaledVector ( color ) ;
106+ }
107+
108+ bitReader . NextRow ( ) ;
109+ }
110+
111+ return ;
112+ }
113+
41114 for ( int y = top ; y < top + height ; y ++ )
42115 {
43116 Span < TPixel > pixelRow = pixels . DangerousGetRowSpan ( y ) . Slice ( left , width ) ;
44117 for ( int x = 0 ; x < pixelRow . Length ; x ++ )
45118 {
46119 int index = bitReader . ReadBits ( this . bitsPerSample0 ) ;
47- pixelRow [ x ] = this . palette [ index ] ;
120+
121+ // Defensive guard against malformed streams.
122+ if ( ( uint ) index >= ( uint ) this . pixelPalette . Length )
123+ {
124+ index = 0 ;
125+ }
126+
127+ pixelRow [ x ] = this . pixelPalette [ index ] ;
48128 }
49129
50130 bitReader . NextRow ( ) ;
51131 }
52132 }
53133
54- private static TPixel [ ] GeneratePalette ( ushort [ ] colorMap , int colorCount )
134+ private static Vector4 [ ] GenerateVectorPalette ( ushort [ ] colorMap , int colorCount )
135+ {
136+ Vector4 [ ] palette = new Vector4 [ colorCount ] ;
137+
138+ const int rOffset = 0 ;
139+ int gOffset = colorCount ;
140+ int bOffset = colorCount * 2 ;
141+
142+ for ( int i = 0 ; i < palette . Length ; i ++ )
143+ {
144+ float r = colorMap [ rOffset + i ] * InvMax ;
145+ float g = colorMap [ gOffset + i ] * InvMax ;
146+ float b = colorMap [ bOffset + i ] * InvMax ;
147+ palette [ i ] = new Vector4 ( r , g , b , 1f ) ;
148+ }
149+
150+ return palette ;
151+ }
152+
153+ private static TPixel [ ] GeneratePixelPalette ( ushort [ ] colorMap , int colorCount )
55154 {
56155 TPixel [ ] palette = new TPixel [ colorCount ] ;
57156
@@ -69,4 +168,11 @@ private static TPixel[] GeneratePalette(ushort[] colorMap, int colorCount)
69168
70169 return palette ;
71170 }
171+
172+ private static Color [ ] GenerateColorPalette ( Vector4 [ ] palette )
173+ {
174+ Color [ ] colors = new Color [ palette . Length ] ;
175+ Color . FromScaledVector ( palette , colors ) ;
176+ return colors ;
177+ }
72178}
0 commit comments