@@ -172,21 +172,20 @@ public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken
172
172
if ( image is null )
173
173
{
174
174
this . InitializeImage ( metadata , out image ) ;
175
+
176
+ // Both PLTE and tRNS chunks, if present, have been read at this point as per spec.
177
+ AssignColorPalette ( this . palette , this . paletteAlpha , pngMetadata ) ;
175
178
}
176
179
177
180
this . ReadScanlines ( chunk , image . Frames . RootFrame , pngMetadata , cancellationToken ) ;
178
181
179
182
break ;
180
183
case PngChunkType . Palette :
181
- byte [ ] pal = new byte [ chunk . Length ] ;
182
- chunk . Data . GetSpan ( ) . CopyTo ( pal ) ;
183
- this . palette = pal ;
184
+ this . palette = chunk . Data . GetSpan ( ) . ToArray ( ) ;
184
185
break ;
185
186
case PngChunkType . Transparency :
186
- byte [ ] alpha = new byte [ chunk . Length ] ;
187
- chunk . Data . GetSpan ( ) . CopyTo ( alpha ) ;
188
- this . paletteAlpha = alpha ;
189
- this . AssignTransparentMarkers ( alpha , pngMetadata ) ;
187
+ this . paletteAlpha = chunk . Data . GetSpan ( ) . ToArray ( ) ;
188
+ this . AssignTransparentMarkers ( this . paletteAlpha , pngMetadata ) ;
190
189
break ;
191
190
case PngChunkType . Text :
192
191
this . ReadTextChunk ( metadata , pngMetadata , chunk . Data . GetSpan ( ) ) ;
@@ -292,12 +291,15 @@ public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellat
292
291
293
292
this . SkipChunkDataAndCrc ( chunk ) ;
294
293
break ;
294
+ case PngChunkType . Palette :
295
+ this . palette = chunk . Data . GetSpan ( ) . ToArray ( ) ;
296
+ break ;
297
+
295
298
case PngChunkType . Transparency :
296
- byte [ ] alpha = new byte [ chunk . Length ] ;
297
- chunk . Data . GetSpan ( ) . CopyTo ( alpha ) ;
298
- this . paletteAlpha = alpha ;
299
- this . AssignTransparentMarkers ( alpha , pngMetadata ) ;
299
+ this . paletteAlpha = chunk . Data . GetSpan ( ) . ToArray ( ) ;
300
+ this . AssignTransparentMarkers ( this . paletteAlpha , pngMetadata ) ;
300
301
302
+ // Spec says tRNS must be after PLTE so safe to exit.
301
303
if ( this . colorMetadataOnly )
302
304
{
303
305
goto EOF ;
@@ -370,6 +372,9 @@ public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellat
370
372
PngThrowHelper . ThrowNoHeader ( ) ;
371
373
}
372
374
375
+ // Both PLTE and tRNS chunks, if present, have been read at this point as per spec.
376
+ AssignColorPalette ( this . palette , this . paletteAlpha , pngMetadata ) ;
377
+
373
378
return new ImageInfo ( new PixelTypeInfo ( this . CalculateBitsPerPixel ( ) ) , new ( this . header . Width , this . header . Height ) , metadata ) ;
374
379
}
375
380
finally
@@ -766,9 +771,7 @@ private void ProcessDefilteredScanline<TPixel>(ReadOnlySpan<byte> defilteredScan
766
771
this . header ,
767
772
scanlineSpan ,
768
773
rowSpan ,
769
- pngMetadata . HasTransparency ,
770
- pngMetadata . TransparentL16 . GetValueOrDefault ( ) ,
771
- pngMetadata . TransparentL8 . GetValueOrDefault ( ) ) ;
774
+ pngMetadata . TransparentColor ) ;
772
775
773
776
break ;
774
777
@@ -787,8 +790,7 @@ private void ProcessDefilteredScanline<TPixel>(ReadOnlySpan<byte> defilteredScan
787
790
this . header ,
788
791
scanlineSpan ,
789
792
rowSpan ,
790
- this . palette ,
791
- this . paletteAlpha ) ;
793
+ pngMetadata . ColorTable ) ;
792
794
793
795
break ;
794
796
@@ -800,9 +802,7 @@ private void ProcessDefilteredScanline<TPixel>(ReadOnlySpan<byte> defilteredScan
800
802
rowSpan ,
801
803
this . bytesPerPixel ,
802
804
this . bytesPerSample ,
803
- pngMetadata . HasTransparency ,
804
- pngMetadata . TransparentRgb48 . GetValueOrDefault ( ) ,
805
- pngMetadata . TransparentRgb24 . GetValueOrDefault ( ) ) ;
805
+ pngMetadata . TransparentColor ) ;
806
806
807
807
break ;
808
808
@@ -860,9 +860,7 @@ private void ProcessInterlacedDefilteredScanline<TPixel>(ReadOnlySpan<byte> defi
860
860
rowSpan ,
861
861
( uint ) pixelOffset ,
862
862
( uint ) increment ,
863
- pngMetadata . HasTransparency ,
864
- pngMetadata . TransparentL16 . GetValueOrDefault ( ) ,
865
- pngMetadata . TransparentL8 . GetValueOrDefault ( ) ) ;
863
+ pngMetadata . TransparentColor ) ;
866
864
867
865
break ;
868
866
@@ -885,8 +883,7 @@ private void ProcessInterlacedDefilteredScanline<TPixel>(ReadOnlySpan<byte> defi
885
883
rowSpan ,
886
884
( uint ) pixelOffset ,
887
885
( uint ) increment ,
888
- this . palette ,
889
- this . paletteAlpha ) ;
886
+ pngMetadata . ColorTable ) ;
890
887
891
888
break ;
892
889
@@ -899,9 +896,7 @@ private void ProcessInterlacedDefilteredScanline<TPixel>(ReadOnlySpan<byte> defi
899
896
( uint ) increment ,
900
897
this . bytesPerPixel ,
901
898
this . bytesPerSample ,
902
- pngMetadata . HasTransparency ,
903
- pngMetadata . TransparentRgb48 . GetValueOrDefault ( ) ,
904
- pngMetadata . TransparentRgb24 . GetValueOrDefault ( ) ) ;
899
+ pngMetadata . TransparentColor ) ;
905
900
906
901
break ;
907
902
@@ -924,10 +919,44 @@ private void ProcessInterlacedDefilteredScanline<TPixel>(ReadOnlySpan<byte> defi
924
919
}
925
920
}
926
921
922
+ /// <summary>
923
+ /// Decodes and assigns the color palette to the metadata
924
+ /// </summary>
925
+ /// <param name="palette">The palette buffer.</param>
926
+ /// <param name="alpha">The alpha palette buffer.</param>
927
+ /// <param name="pngMetadata">The png metadata.</param>
928
+ private static void AssignColorPalette ( ReadOnlySpan < byte > palette , ReadOnlySpan < byte > alpha , PngMetadata pngMetadata )
929
+ {
930
+ if ( palette . Length == 0 )
931
+ {
932
+ return ;
933
+ }
934
+
935
+ Color [ ] colorTable = new Color [ palette . Length / Unsafe . SizeOf < Rgb24 > ( ) ] ;
936
+ ReadOnlySpan < Rgb24 > rgbTable = MemoryMarshal . Cast < byte , Rgb24 > ( palette ) ;
937
+ for ( int i = 0 ; i < colorTable . Length ; i ++ )
938
+ {
939
+ colorTable [ i ] = new Color ( rgbTable [ i ] ) ;
940
+ }
941
+
942
+ if ( alpha . Length > 0 )
943
+ {
944
+ // The alpha chunk may contain as many transparency entries as there are palette entries
945
+ // (more than that would not make any sense) or as few as one.
946
+ for ( int i = 0 ; i < alpha . Length ; i ++ )
947
+ {
948
+ ref Color color = ref colorTable [ i ] ;
949
+ color = color . WithAlpha ( alpha [ i ] / 255F ) ;
950
+ }
951
+ }
952
+
953
+ pngMetadata . ColorTable = colorTable ;
954
+ }
955
+
927
956
/// <summary>
928
957
/// Decodes and assigns marker colors that identify transparent pixels in non indexed images.
929
958
/// </summary>
930
- /// <param name="alpha">The alpha tRNS array .</param>
959
+ /// <param name="alpha">The alpha tRNS buffer .</param>
931
960
/// <param name="pngMetadata">The png metadata.</param>
932
961
private void AssignTransparentMarkers ( ReadOnlySpan < byte > alpha , PngMetadata pngMetadata )
933
962
{
@@ -941,16 +970,14 @@ private void AssignTransparentMarkers(ReadOnlySpan<byte> alpha, PngMetadata pngM
941
970
ushort gc = BinaryPrimitives . ReadUInt16LittleEndian ( alpha . Slice ( 2 , 2 ) ) ;
942
971
ushort bc = BinaryPrimitives . ReadUInt16LittleEndian ( alpha . Slice ( 4 , 2 ) ) ;
943
972
944
- pngMetadata . TransparentRgb48 = new Rgb48 ( rc , gc , bc ) ;
945
- pngMetadata . HasTransparency = true ;
973
+ pngMetadata . TransparentColor = new ( new Rgb48 ( rc , gc , bc ) ) ;
946
974
return ;
947
975
}
948
976
949
977
byte r = ReadByteLittleEndian ( alpha , 0 ) ;
950
978
byte g = ReadByteLittleEndian ( alpha , 2 ) ;
951
979
byte b = ReadByteLittleEndian ( alpha , 4 ) ;
952
- pngMetadata . TransparentRgb24 = new Rgb24 ( r , g , b ) ;
953
- pngMetadata . HasTransparency = true ;
980
+ pngMetadata . TransparentColor = new ( new Rgb24 ( r , g , b ) ) ;
954
981
}
955
982
}
956
983
else if ( this . pngColorType == PngColorType . Grayscale )
@@ -959,20 +986,14 @@ private void AssignTransparentMarkers(ReadOnlySpan<byte> alpha, PngMetadata pngM
959
986
{
960
987
if ( this . header . BitDepth == 16 )
961
988
{
962
- pngMetadata . TransparentL16 = new L16 ( BinaryPrimitives . ReadUInt16LittleEndian ( alpha [ ..2 ] ) ) ;
989
+ pngMetadata . TransparentColor = Color . FromPixel ( new L16 ( BinaryPrimitives . ReadUInt16LittleEndian ( alpha [ ..2 ] ) ) ) ;
963
990
}
964
991
else
965
992
{
966
- pngMetadata . TransparentL8 = new L8 ( ReadByteLittleEndian ( alpha , 0 ) ) ;
993
+ pngMetadata . TransparentColor = Color . FromPixel ( new L8 ( ReadByteLittleEndian ( alpha , 0 ) ) ) ;
967
994
}
968
-
969
- pngMetadata . HasTransparency = true ;
970
995
}
971
996
}
972
- else if ( this . pngColorType == PngColorType . Palette && alpha . Length > 0 )
973
- {
974
- pngMetadata . HasTransparency = true ;
975
- }
976
997
}
977
998
978
999
/// <summary>
@@ -1461,7 +1482,7 @@ private bool TryReadChunk(Span<byte> buffer, out PngChunk chunk)
1461
1482
1462
1483
// If we're reading color metadata only we're only interested in the IHDR and tRNS chunks.
1463
1484
// We can skip all other chunk data in the stream for better performance.
1464
- if ( this . colorMetadataOnly && type != PngChunkType . Header && type != PngChunkType . Transparency )
1485
+ if ( this . colorMetadataOnly && type != PngChunkType . Header && type != PngChunkType . Transparency && type != PngChunkType . Palette )
1465
1486
{
1466
1487
chunk = new PngChunk ( length , type ) ;
1467
1488
0 commit comments