@@ -87,6 +87,11 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
8787 /// </summary>
8888 private IMemoryOwner < byte > currentScanline ;
8989
90+ /// <summary>
91+ /// The color profile name.
92+ /// </summary>
93+ private const string ColorProfileName = "ICC Profile" ;
94+
9095 /// <summary>
9196 /// Initializes a new instance of the <see cref="PngEncoderCore" /> class.
9297 /// </summary>
@@ -134,6 +139,7 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken
134139
135140 this . WriteHeaderChunk ( stream ) ;
136141 this . WriteGammaChunk ( stream ) ;
142+ this . WriteColorProfileChunk ( stream , metadata ) ;
137143 this . WritePaletteChunk ( stream , quantized ) ;
138144 this . WriteTransparencyChunk ( stream , pngMetadata ) ;
139145 this . WritePhysicalChunk ( stream , metadata ) ;
@@ -656,7 +662,7 @@ private void WriteExifChunk(Stream stream, ImageMetadata meta)
656662 }
657663
658664 /// <summary>
659- /// Writes an iTXT chunk, containing the XMP metdata to the stream, if such profile is present in the metadata.
665+ /// Writes an iTXT chunk, containing the XMP metadata to the stream, if such profile is present in the metadata.
660666 /// </summary>
661667 /// <param name="stream">The <see cref="Stream"/> containing image data.</param>
662668 /// <param name="meta">The image metadata.</param>
@@ -673,7 +679,7 @@ private void WriteXmpChunk(Stream stream, ImageMetadata meta)
673679 return ;
674680 }
675681
676- var xmpData = meta . XmpProfile . Data ;
682+ byte [ ] xmpData = meta . XmpProfile . Data ;
677683
678684 if ( xmpData . Length == 0 )
679685 {
@@ -687,19 +693,49 @@ private void WriteXmpChunk(Stream stream, ImageMetadata meta)
687693 PngConstants . XmpKeyword . CopyTo ( payload ) ;
688694 int bytesWritten = PngConstants . XmpKeyword . Length ;
689695
690- // Write the iTxt header (all zeros in this case)
691- payload [ bytesWritten ++ ] = 0 ;
692- payload [ bytesWritten ++ ] = 0 ;
693- payload [ bytesWritten ++ ] = 0 ;
694- payload [ bytesWritten ++ ] = 0 ;
695- payload [ bytesWritten ++ ] = 0 ;
696+ // Write the iTxt header (all zeros in this case).
697+ Span < byte > iTxtHeader = payload . Slice ( bytesWritten ) ;
698+ iTxtHeader [ 4 ] = 0 ;
699+ iTxtHeader [ 3 ] = 0 ;
700+ iTxtHeader [ 2 ] = 0 ;
701+ iTxtHeader [ 1 ] = 0 ;
702+ iTxtHeader [ 0 ] = 0 ;
703+ bytesWritten += 5 ;
696704
697- // And the XMP data itself
705+ // And the XMP data itself.
698706 xmpData . CopyTo ( payload . Slice ( bytesWritten ) ) ;
699707 this . WriteChunk ( stream , PngChunkType . InternationalText , payload ) ;
700708 }
701709 }
702710
711+ /// <summary>
712+ /// Writes the color profile chunk.
713+ /// </summary>
714+ /// <param name="stream">The stream to write to.</param>
715+ /// <param name="metaData">The image meta data.</param>
716+ private void WriteColorProfileChunk ( Stream stream , ImageMetadata metaData )
717+ {
718+ if ( metaData . IccProfile is null )
719+ {
720+ return ;
721+ }
722+
723+ byte [ ] iccProfileBytes = metaData . IccProfile . ToByteArray ( ) ;
724+
725+ byte [ ] compressedData = this . GetZlibCompressedBytes ( iccProfileBytes ) ;
726+ int payloadLength = ColorProfileName . Length + compressedData . Length + 2 ;
727+ using ( IMemoryOwner < byte > owner = this . memoryAllocator . Allocate < byte > ( payloadLength ) )
728+ {
729+ Span < byte > outputBytes = owner . GetSpan ( ) ;
730+ PngConstants . Encoding . GetBytes ( ColorProfileName ) . CopyTo ( outputBytes ) ;
731+ int bytesWritten = ColorProfileName . Length ;
732+ outputBytes [ bytesWritten ++ ] = 0 ; // Null separator.
733+ outputBytes [ bytesWritten ++ ] = 0 ; // Compression.
734+ compressedData . CopyTo ( outputBytes . Slice ( bytesWritten ) ) ;
735+ this . WriteChunk ( stream , PngChunkType . EmbeddedColorProfile , outputBytes ) ;
736+ }
737+ }
738+
703739 /// <summary>
704740 /// Writes a text chunk to the stream. Can be either a tTXt, iTXt or zTXt chunk,
705741 /// depending whether the text contains any latin characters or should be compressed.
@@ -727,13 +763,12 @@ private void WriteTextChunks(Stream stream, PngMetadata meta)
727763 }
728764 }
729765
730- if ( hasUnicodeCharacters || ( ! string . IsNullOrWhiteSpace ( textData . LanguageTag ) ||
731- ! string . IsNullOrWhiteSpace ( textData . TranslatedKeyword ) ) )
766+ if ( hasUnicodeCharacters || ( ! string . IsNullOrWhiteSpace ( textData . LanguageTag ) || ! string . IsNullOrWhiteSpace ( textData . TranslatedKeyword ) ) )
732767 {
733768 // Write iTXt chunk.
734769 byte [ ] keywordBytes = PngConstants . Encoding . GetBytes ( textData . Keyword ) ;
735770 byte [ ] textBytes = textData . Value . Length > this . options . TextCompressionThreshold
736- ? this . GetCompressedTextBytes ( PngConstants . TranslatedEncoding . GetBytes ( textData . Value ) )
771+ ? this . GetZlibCompressedBytes ( PngConstants . TranslatedEncoding . GetBytes ( textData . Value ) )
737772 : PngConstants . TranslatedEncoding . GetBytes ( textData . Value ) ;
738773
739774 byte [ ] translatedKeyword = PngConstants . TranslatedEncoding . GetBytes ( textData . TranslatedKeyword ) ;
@@ -772,18 +807,17 @@ private void WriteTextChunks(Stream stream, PngMetadata meta)
772807 if ( textData . Value . Length > this . options . TextCompressionThreshold )
773808 {
774809 // Write zTXt chunk.
775- byte [ ] compressedData =
776- this . GetCompressedTextBytes ( PngConstants . Encoding . GetBytes ( textData . Value ) ) ;
810+ byte [ ] compressedData = this . GetZlibCompressedBytes ( PngConstants . Encoding . GetBytes ( textData . Value ) ) ;
777811 int payloadLength = textData . Keyword . Length + compressedData . Length + 2 ;
778812 using ( IMemoryOwner < byte > owner = this . memoryAllocator . Allocate < byte > ( payloadLength ) )
779813 {
780814 Span < byte > outputBytes = owner . GetSpan ( ) ;
781815 PngConstants . Encoding . GetBytes ( textData . Keyword ) . CopyTo ( outputBytes ) ;
782816 int bytesWritten = textData . Keyword . Length ;
783- outputBytes [ bytesWritten ++ ] = 0 ;
784- outputBytes [ bytesWritten ++ ] = 0 ;
817+ outputBytes [ bytesWritten ++ ] = 0 ; // Null separator.
818+ outputBytes [ bytesWritten ++ ] = 0 ; // Compression.
785819 compressedData . CopyTo ( outputBytes . Slice ( bytesWritten ) ) ;
786- this . WriteChunk ( stream , PngChunkType . CompressedText , outputBytes . ToArray ( ) ) ;
820+ this . WriteChunk ( stream , PngChunkType . CompressedText , outputBytes ) ;
787821 }
788822 }
789823 else
@@ -796,9 +830,8 @@ private void WriteTextChunks(Stream stream, PngMetadata meta)
796830 PngConstants . Encoding . GetBytes ( textData . Keyword ) . CopyTo ( outputBytes ) ;
797831 int bytesWritten = textData . Keyword . Length ;
798832 outputBytes [ bytesWritten ++ ] = 0 ;
799- PngConstants . Encoding . GetBytes ( textData . Value )
800- . CopyTo ( outputBytes . Slice ( bytesWritten ) ) ;
801- this . WriteChunk ( stream , PngChunkType . Text , outputBytes . ToArray ( ) ) ;
833+ PngConstants . Encoding . GetBytes ( textData . Value ) . CopyTo ( outputBytes . Slice ( bytesWritten ) ) ;
834+ this . WriteChunk ( stream , PngChunkType . Text , outputBytes ) ;
802835 }
803836 }
804837 }
@@ -808,15 +841,15 @@ private void WriteTextChunks(Stream stream, PngMetadata meta)
808841 /// <summary>
809842 /// Compresses a given text using Zlib compression.
810843 /// </summary>
811- /// <param name="textBytes ">The text bytes to compress.</param>
812- /// <returns>The compressed text byte array.</returns>
813- private byte [ ] GetCompressedTextBytes ( byte [ ] textBytes )
844+ /// <param name="dataBytes ">The bytes to compress.</param>
845+ /// <returns>The compressed byte array.</returns>
846+ private byte [ ] GetZlibCompressedBytes ( byte [ ] dataBytes )
814847 {
815848 using ( var memoryStream = new MemoryStream ( ) )
816849 {
817850 using ( var deflateStream = new ZlibDeflateStream ( this . memoryAllocator , memoryStream , this . options . CompressionLevel ) )
818851 {
819- deflateStream . Write ( textBytes ) ;
852+ deflateStream . Write ( dataBytes ) ;
820853 }
821854
822855 return memoryStream . ToArray ( ) ;
0 commit comments