3
3
4
4
using System ;
5
5
using System . Buffers ;
6
+ using System . Buffers . Binary ;
6
7
using System . IO ;
7
8
using System . Runtime . InteropServices ;
8
9
using System . Threading ;
@@ -79,9 +80,10 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals
79
80
/// <summary>
80
81
/// A bitmap v4 header will only be written, if the user explicitly wants support for transparency.
81
82
/// 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.
82
84
/// Otherwise a bitmap v3 header will be written, which is supported by almost all decoders.
83
85
/// </summary>
84
- private readonly bool writeV4Header ;
86
+ private BmpInfoHeaderType infoHeaderType ;
85
87
86
88
/// <summary>
87
89
/// 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
97
99
{
98
100
this . memoryAllocator = memoryAllocator ;
99
101
this . bitsPerPixel = options . BitsPerPixel ;
100
- this . writeV4Header = options . SupportTransparency ;
101
102
this . quantizer = options . Quantizer ?? KnownQuantizers . Octree ;
103
+ this . infoHeaderType = options . SupportTransparency ? BmpInfoHeaderType . WinVersion4 : BmpInfoHeaderType . WinVersion3 ;
102
104
}
103
105
104
106
/// <summary>
@@ -123,7 +125,62 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken
123
125
int bytesPerLine = 4 * ( ( ( image . Width * bpp ) + 31 ) / 32 ) ;
124
126
this . padding = bytesPerLine - ( int ) ( image . Width * ( bpp / 8F ) ) ;
125
127
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
+ {
127
184
int hResolution = 0 ;
128
185
int vResolution = 0 ;
129
186
@@ -154,20 +211,19 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken
154
211
}
155
212
}
156
213
157
- int infoHeaderSize = this . writeV4Header ? BmpInfoHeader . SizeV4 : BmpInfoHeader . SizeV3 ;
158
214
var infoHeader = new BmpInfoHeader (
159
215
headerSize : infoHeaderSize ,
160
- height : image . Height ,
161
- width : image . Width ,
216
+ height : height ,
217
+ width : width ,
162
218
bitsPerPixel : bpp ,
163
219
planes : 1 ,
164
- imageSize : image . Height * bytesPerLine ,
220
+ imageSize : height * bytesPerLine ,
165
221
clrUsed : 0 ,
166
222
clrImportant : 0 ,
167
223
xPelsPerMeter : hResolution ,
168
224
yPelsPerMeter : vResolution ) ;
169
225
170
- if ( this . writeV4Header && this . bitsPerPixel == BmpBitsPerPixel . Pixel32 )
226
+ if ( ( this . infoHeaderType is BmpInfoHeaderType . WinVersion4 or BmpInfoHeaderType . WinVersion5 ) && this . bitsPerPixel == BmpBitsPerPixel . Pixel32 )
171
227
{
172
228
infoHeader . AlphaMask = Rgba32AlphaMask ;
173
229
infoHeader . RedMask = Rgba32RedMask ;
@@ -176,45 +232,79 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken
176
232
infoHeader . Compression = BmpCompression . BitFields ;
177
233
}
178
234
179
- int colorPaletteSize = 0 ;
180
- if ( this . bitsPerPixel == BmpBitsPerPixel . Pixel8 )
235
+ if ( this . infoHeaderType is BmpInfoHeaderType . WinVersion5 && metadata . IccProfile != null )
181
236
{
182
- colorPaletteSize = ColorPaletteSize8Bit ;
237
+ infoHeader . ProfileSize = iccProfileData . Length ;
238
+ infoHeader . CsType = BmpColorSpace . PROFILE_EMBEDDED ;
239
+ infoHeader . Intent = BmpRenderingIntent . LCS_GM_IMAGES ;
183
240
}
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 )
189
254
{
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 ) ) ;
191
261
}
262
+ }
192
263
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
+ {
193
275
var fileHeader = new BmpFileHeader (
194
276
type : BmpConstants . TypeMarkers . Bitmap ,
195
- fileSize : BmpFileHeader . Size + infoHeaderSize + colorPaletteSize + infoHeader . ImageSize ,
277
+ fileSize : BmpFileHeader . Size + infoHeaderSize + colorPaletteSize + iccProfileSize + infoHeader . ImageSize ,
196
278
reserved : 0 ,
197
279
offset : BmpFileHeader . Size + infoHeaderSize + colorPaletteSize ) ;
198
280
199
- Span < byte > buffer = stackalloc byte [ infoHeaderSize ] ;
200
281
fileHeader . WriteTo ( buffer ) ;
201
-
202
282
stream . Write ( buffer , 0 , BmpFileHeader . Size ) ;
283
+ }
203
284
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 )
209
295
{
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 ;
211
305
}
212
306
213
307
stream . Write ( buffer , 0 , infoHeaderSize ) ;
214
-
215
- this . WriteImage ( stream , image . Frames . RootFrame ) ;
216
-
217
- stream . Flush ( ) ;
218
308
}
219
309
220
310
/// <summary>
0 commit comments