33
44using System ;
55using System . Buffers ;
6+ using System . Buffers . Binary ;
67using System . IO ;
78using System . Runtime . InteropServices ;
89using System . Threading ;
@@ -79,9 +80,10 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals
7980 /// <summary>
8081 /// A bitmap v4 header will only be written, if the user explicitly wants support for transparency.
8182 /// In this case the compression type BITFIELDS will be used.
83+ /// If the image contains a color profile, a bitmap v5 header is written, which is needed to write this info.
8284 /// Otherwise a bitmap v3 header will be written, which is supported by almost all decoders.
8385 /// </summary>
84- private readonly bool writeV4Header ;
86+ private BmpInfoHeaderType infoHeaderType ;
8587
8688 /// <summary>
8789 /// The quantizer for reducing the color count for 8-Bit, 4-Bit and 1-Bit images.
@@ -97,8 +99,8 @@ public BmpEncoderCore(IBmpEncoderOptions options, MemoryAllocator memoryAllocato
9799 {
98100 this . memoryAllocator = memoryAllocator ;
99101 this . bitsPerPixel = options . BitsPerPixel ;
100- this . writeV4Header = options . SupportTransparency ;
101102 this . quantizer = options . Quantizer ?? KnownQuantizers . Octree ;
103+ this . infoHeaderType = options . SupportTransparency ? BmpInfoHeaderType . WinVersion4 : BmpInfoHeaderType . WinVersion3 ;
102104 }
103105
104106 /// <summary>
@@ -123,7 +125,62 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken
123125 int bytesPerLine = 4 * ( ( ( image . Width * bpp ) + 31 ) / 32 ) ;
124126 this . padding = bytesPerLine - ( int ) ( image . Width * ( bpp / 8F ) ) ;
125127
126- // Set Resolution.
128+ int colorPaletteSize = 0 ;
129+ if ( this . bitsPerPixel == BmpBitsPerPixel . Pixel8 )
130+ {
131+ colorPaletteSize = ColorPaletteSize8Bit ;
132+ }
133+ else if ( this . bitsPerPixel == BmpBitsPerPixel . Pixel4 )
134+ {
135+ colorPaletteSize = ColorPaletteSize4Bit ;
136+ }
137+ else if ( this . bitsPerPixel == BmpBitsPerPixel . Pixel1 )
138+ {
139+ colorPaletteSize = ColorPaletteSize1Bit ;
140+ }
141+
142+ byte [ ] iccProfileData = null ;
143+ int iccProfileSize = 0 ;
144+ if ( metadata . IccProfile != null )
145+ {
146+ this . infoHeaderType = BmpInfoHeaderType . WinVersion5 ;
147+ iccProfileData = metadata . IccProfile . ToByteArray ( ) ;
148+ iccProfileSize = iccProfileData . Length ;
149+ }
150+
151+ int infoHeaderSize = this . infoHeaderType switch
152+ {
153+ BmpInfoHeaderType . WinVersion3 => BmpInfoHeader . SizeV3 ,
154+ BmpInfoHeaderType . WinVersion4 => BmpInfoHeader . SizeV4 ,
155+ BmpInfoHeaderType . WinVersion5 => BmpInfoHeader . SizeV5 ,
156+ _ => BmpInfoHeader . SizeV3
157+ } ;
158+
159+ BmpInfoHeader infoHeader = this . CreateBmpInfoHeader ( image . Width , image . Height , infoHeaderSize , bpp , bytesPerLine , metadata , iccProfileData ) ;
160+
161+ Span < byte > buffer = stackalloc byte [ infoHeaderSize ] ;
162+
163+ this . WriteBitmapFileHeader ( stream , infoHeaderSize , colorPaletteSize , iccProfileSize , infoHeader , buffer ) ;
164+ this . WriteBitmapInfoHeader ( stream , infoHeader , buffer , infoHeaderSize ) ;
165+ this . WriteImage ( stream , image . Frames . RootFrame ) ;
166+ this . WriteColorProfile ( stream , iccProfileData , buffer ) ;
167+
168+ stream . Flush ( ) ;
169+ }
170+
171+ /// <summary>
172+ /// Creates the bitmap information header.
173+ /// </summary>
174+ /// <param name="width">The width of the image.</param>
175+ /// <param name="height">The height of the image.</param>
176+ /// <param name="infoHeaderSize">Size of the information header.</param>
177+ /// <param name="bpp">The bits per pixel.</param>
178+ /// <param name="bytesPerLine">The bytes per line.</param>
179+ /// <param name="metadata">The metadata.</param>
180+ /// <param name="iccProfileData">The icc profile data.</param>
181+ /// <returns>The bitmap information header.</returns>
182+ private BmpInfoHeader CreateBmpInfoHeader ( int width , int height , int infoHeaderSize , short bpp , int bytesPerLine , ImageMetadata metadata , byte [ ] iccProfileData )
183+ {
127184 int hResolution = 0 ;
128185 int vResolution = 0 ;
129186
@@ -154,20 +211,19 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken
154211 }
155212 }
156213
157- int infoHeaderSize = this . writeV4Header ? BmpInfoHeader . SizeV4 : BmpInfoHeader . SizeV3 ;
158214 var infoHeader = new BmpInfoHeader (
159215 headerSize : infoHeaderSize ,
160- height : image . Height ,
161- width : image . Width ,
216+ height : height ,
217+ width : width ,
162218 bitsPerPixel : bpp ,
163219 planes : 1 ,
164- imageSize : image . Height * bytesPerLine ,
220+ imageSize : height * bytesPerLine ,
165221 clrUsed : 0 ,
166222 clrImportant : 0 ,
167223 xPelsPerMeter : hResolution ,
168224 yPelsPerMeter : vResolution ) ;
169225
170- if ( this . writeV4Header && this . bitsPerPixel == BmpBitsPerPixel . Pixel32 )
226+ if ( ( this . infoHeaderType is BmpInfoHeaderType . WinVersion4 or BmpInfoHeaderType . WinVersion5 ) && this . bitsPerPixel == BmpBitsPerPixel . Pixel32 )
171227 {
172228 infoHeader . AlphaMask = Rgba32AlphaMask ;
173229 infoHeader . RedMask = Rgba32RedMask ;
@@ -176,45 +232,79 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken
176232 infoHeader . Compression = BmpCompression . BitFields ;
177233 }
178234
179- int colorPaletteSize = 0 ;
180- if ( this . bitsPerPixel == BmpBitsPerPixel . Pixel8 )
235+ if ( this . infoHeaderType is BmpInfoHeaderType . WinVersion5 && metadata . IccProfile != null )
181236 {
182- colorPaletteSize = ColorPaletteSize8Bit ;
237+ infoHeader . ProfileSize = iccProfileData . Length ;
238+ infoHeader . CsType = BmpColorSpace . PROFILE_EMBEDDED ;
239+ infoHeader . Intent = BmpRenderingIntent . LCS_GM_IMAGES ;
183240 }
184- else if ( this . bitsPerPixel == BmpBitsPerPixel . Pixel4 )
185- {
186- colorPaletteSize = ColorPaletteSize4Bit ;
187- }
188- else if ( this . bitsPerPixel == BmpBitsPerPixel . Pixel1 )
241+
242+ return infoHeader ;
243+ }
244+
245+ /// <summary>
246+ /// Writes the color profile to the stream.
247+ /// </summary>
248+ /// <param name="stream">The stream to write to.</param>
249+ /// <param name="iccProfileData">The color profile data.</param>
250+ /// <param name="buffer">The buffer.</param>
251+ private void WriteColorProfile ( Stream stream , byte [ ] iccProfileData , Span < byte > buffer )
252+ {
253+ if ( iccProfileData != null )
189254 {
190- colorPaletteSize = ColorPaletteSize1Bit ;
255+ // The offset, in bytes, from the beginning of the BITMAPV5HEADER structure to the start of the profile data.
256+ int streamPositionAfterImageData = ( int ) stream . Position - BmpFileHeader . Size ;
257+ stream . Write ( iccProfileData ) ;
258+ BinaryPrimitives . WriteInt32LittleEndian ( buffer , streamPositionAfterImageData ) ;
259+ stream . Position = BmpFileHeader . Size + 112 ;
260+ stream . Write ( buffer . Slice ( 0 , 4 ) ) ;
191261 }
262+ }
192263
264+ /// <summary>
265+ /// Writes the bitmap file header.
266+ /// </summary>
267+ /// <param name="stream">The stream to write the header to.</param>
268+ /// <param name="infoHeaderSize">Size of the bitmap information header.</param>
269+ /// <param name="colorPaletteSize">Size of the color palette.</param>
270+ /// <param name="iccProfileSize">The size in bytes of the color profile.</param>
271+ /// <param name="infoHeader">The information header to write.</param>
272+ /// <param name="buffer">The buffer to write to.</param>
273+ private void WriteBitmapFileHeader ( Stream stream , int infoHeaderSize , int colorPaletteSize , int iccProfileSize , BmpInfoHeader infoHeader , Span < byte > buffer )
274+ {
193275 var fileHeader = new BmpFileHeader (
194276 type : BmpConstants . TypeMarkers . Bitmap ,
195- fileSize : BmpFileHeader . Size + infoHeaderSize + colorPaletteSize + infoHeader . ImageSize ,
277+ fileSize : BmpFileHeader . Size + infoHeaderSize + colorPaletteSize + iccProfileSize + infoHeader . ImageSize ,
196278 reserved : 0 ,
197279 offset : BmpFileHeader . Size + infoHeaderSize + colorPaletteSize ) ;
198280
199- Span < byte > buffer = stackalloc byte [ infoHeaderSize ] ;
200281 fileHeader . WriteTo ( buffer ) ;
201-
202282 stream . Write ( buffer , 0 , BmpFileHeader . Size ) ;
283+ }
203284
204- if ( this . writeV4Header )
205- {
206- infoHeader . WriteV4Header ( buffer ) ;
207- }
208- else
285+ /// <summary>
286+ /// Writes the bitmap information header.
287+ /// </summary>
288+ /// <param name="stream">The stream to write info header into.</param>
289+ /// <param name="infoHeader">The information header.</param>
290+ /// <param name="buffer">The buffer.</param>
291+ /// <param name="infoHeaderSize">Size of the information header.</param>
292+ private void WriteBitmapInfoHeader ( Stream stream , BmpInfoHeader infoHeader , Span < byte > buffer , int infoHeaderSize )
293+ {
294+ switch ( this . infoHeaderType )
209295 {
210- infoHeader . WriteV3Header ( buffer ) ;
296+ case BmpInfoHeaderType . WinVersion3 :
297+ infoHeader . WriteV3Header ( buffer ) ;
298+ break ;
299+ case BmpInfoHeaderType . WinVersion4 :
300+ infoHeader . WriteV4Header ( buffer ) ;
301+ break ;
302+ case BmpInfoHeaderType . WinVersion5 :
303+ infoHeader . WriteV5Header ( buffer ) ;
304+ break ;
211305 }
212306
213307 stream . Write ( buffer , 0 , infoHeaderSize ) ;
214-
215- this . WriteImage ( stream , image . Frames . RootFrame ) ;
216-
217- stream . Flush ( ) ;
218308 }
219309
220310 /// <summary>
0 commit comments