@@ -51,6 +51,11 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals
5151 /// </summary>
5252 private const int ColorPaletteSize8Bit = 1024 ;
5353
54+ /// <summary>
55+ /// The color palette for an 4 bit image will have 16 entry's with 4 bytes for each entry.
56+ /// </summary>
57+ private const int ColorPaletteSize4Bit = 64 ;
58+
5459 /// <summary>
5560 /// Used for allocating memory during processing operations.
5661 /// </summary>
@@ -107,7 +112,7 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken
107112 this . configuration = image . GetConfiguration ( ) ;
108113 ImageMetadata metadata = image . Metadata ;
109114 BmpMetadata bmpMetadata = metadata . GetBmpMetadata ( ) ;
110- this . bitsPerPixel = this . bitsPerPixel ?? bmpMetadata . BitsPerPixel ;
115+ this . bitsPerPixel ??= bmpMetadata . BitsPerPixel ;
111116
112117 short bpp = ( short ) this . bitsPerPixel ;
113118 int bytesPerLine = 4 * ( ( ( image . Width * bpp ) + 31 ) / 32 ) ;
@@ -166,7 +171,15 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken
166171 infoHeader . Compression = BmpCompression . BitFields ;
167172 }
168173
169- int colorPaletteSize = this . bitsPerPixel == BmpBitsPerPixel . Pixel8 ? ColorPaletteSize8Bit : 0 ;
174+ int colorPaletteSize = 0 ;
175+ if ( this . bitsPerPixel == BmpBitsPerPixel . Pixel8 )
176+ {
177+ colorPaletteSize = ColorPaletteSize8Bit ;
178+ }
179+ else if ( this . bitsPerPixel == BmpBitsPerPixel . Pixel4 )
180+ {
181+ colorPaletteSize = ColorPaletteSize4Bit ;
182+ }
170183
171184 var fileHeader = new BmpFileHeader (
172185 type : BmpConstants . TypeMarkers . Bitmap ,
@@ -224,6 +237,10 @@ private void WriteImage<TPixel>(Stream stream, ImageFrame<TPixel> image)
224237 case BmpBitsPerPixel . Pixel8 :
225238 this . Write8Bit ( stream , image ) ;
226239 break ;
240+
241+ case BmpBitsPerPixel . Pixel4 :
242+ this . Write4BitColor ( stream , image ) ;
243+ break ;
227244 }
228245 }
229246
@@ -344,16 +361,8 @@ private void Write8BitColor<TPixel>(Stream stream, ImageFrame<TPixel> image, Spa
344361 using IQuantizer < TPixel > frameQuantizer = this . quantizer . CreatePixelSpecificQuantizer < TPixel > ( this . configuration ) ;
345362 using IndexedImageFrame < TPixel > quantized = frameQuantizer . BuildPaletteAndQuantizeFrame ( image , image . Bounds ( ) ) ;
346363
347- ReadOnlySpan < TPixel > quantizedColors = quantized . Palette . Span ;
348- var quantizedColorBytes = quantizedColors . Length * 4 ;
349- PixelOperations < TPixel > . Instance . ToBgra32 ( this . configuration , quantizedColors , MemoryMarshal . Cast < byte , Bgra32 > ( colorPalette . Slice ( 0 , quantizedColorBytes ) ) ) ;
350- Span < uint > colorPaletteAsUInt = MemoryMarshal . Cast < byte , uint > ( colorPalette ) ;
351- for ( int i = 0 ; i < colorPaletteAsUInt . Length ; i ++ )
352- {
353- colorPaletteAsUInt [ i ] = colorPaletteAsUInt [ i ] & 0x00FFFFFF ; // Padding byte, always 0.
354- }
355-
356- stream . Write ( colorPalette ) ;
364+ ReadOnlySpan < TPixel > quantizedColorPalette = quantized . Palette . Span ;
365+ this . WriteColorPalette ( stream , quantizedColorPalette , colorPalette ) ;
357366
358367 for ( int y = image . Height - 1 ; y >= 0 ; y -- )
359368 {
@@ -404,5 +413,70 @@ private void Write8BitGray<TPixel>(Stream stream, ImageFrame<TPixel> image, Span
404413 }
405414 }
406415 }
416+
417+ /// <summary>
418+ /// Writes an 4 Bit color image with a color palette. The color palette has 16 entry's with 4 bytes for each entry.
419+ /// </summary>
420+ /// <typeparam name="TPixel">The type of the pixel.</typeparam>
421+ /// <param name="stream">The <see cref="Stream"/> to write to.</param>
422+ /// <param name="image"> The <see cref="ImageFrame{TPixel}"/> containing pixel data.</param>
423+ private void Write4BitColor < TPixel > ( Stream stream , ImageFrame < TPixel > image )
424+ where TPixel : unmanaged, IPixel < TPixel >
425+ {
426+ using IQuantizer < TPixel > frameQuantizer = this . quantizer . CreatePixelSpecificQuantizer < TPixel > ( this . configuration , new QuantizerOptions ( )
427+ {
428+ MaxColors = 16
429+ } ) ;
430+ using IndexedImageFrame < TPixel > quantized = frameQuantizer . BuildPaletteAndQuantizeFrame ( image , image . Bounds ( ) ) ;
431+ using IMemoryOwner < byte > colorPaletteBuffer = this . memoryAllocator . AllocateManagedByteBuffer ( ColorPaletteSize4Bit , AllocationOptions . Clean ) ;
432+
433+ Span < byte > colorPalette = colorPaletteBuffer . GetSpan ( ) ;
434+ ReadOnlySpan < TPixel > quantizedColorPalette = quantized . Palette . Span ;
435+ this . WriteColorPalette ( stream , quantizedColorPalette , colorPalette ) ;
436+
437+ ReadOnlySpan < byte > pixelRowSpan = quantized . GetPixelRowSpan ( 0 ) ;
438+ int rowPadding = pixelRowSpan . Length % 2 != 0 ? this . padding - 1 : this . padding ;
439+ for ( int y = image . Height - 1 ; y >= 0 ; y -- )
440+ {
441+ pixelRowSpan = quantized . GetPixelRowSpan ( y ) ;
442+
443+ int endIdx = pixelRowSpan . Length % 2 == 0 ? pixelRowSpan . Length : pixelRowSpan . Length - 1 ;
444+ for ( int i = 0 ; i < endIdx ; i += 2 )
445+ {
446+ stream . WriteByte ( ( byte ) ( ( pixelRowSpan [ i ] << 4 ) | pixelRowSpan [ i + 1 ] ) ) ;
447+ }
448+
449+ if ( pixelRowSpan . Length % 2 != 0 )
450+ {
451+ stream . WriteByte ( ( byte ) ( ( pixelRowSpan [ pixelRowSpan . Length - 1 ] << 4 ) | 0 ) ) ;
452+ }
453+
454+ for ( int i = 0 ; i < rowPadding ; i ++ )
455+ {
456+ stream . WriteByte ( 0 ) ;
457+ }
458+ }
459+ }
460+
461+ /// <summary>
462+ /// Writes the color palette to the stream. The color palette has 4 bytes for each entry.
463+ /// </summary>
464+ /// <typeparam name="TPixel">The type of the pixel.</typeparam>
465+ /// <param name="stream">The <see cref="Stream"/> to write to.</param>
466+ /// <param name="quantizedColorPalette">The color palette from the quantized image.</param>
467+ /// <param name="colorPalette">A temporary byte span to write the color palette to.</param>
468+ private void WriteColorPalette < TPixel > ( Stream stream , ReadOnlySpan < TPixel > quantizedColorPalette , Span < byte > colorPalette )
469+ where TPixel : unmanaged, IPixel < TPixel >
470+ {
471+ int quantizedColorBytes = quantizedColorPalette . Length * 4 ;
472+ PixelOperations < TPixel > . Instance . ToBgra32 ( this . configuration , quantizedColorPalette , MemoryMarshal . Cast < byte , Bgra32 > ( colorPalette . Slice ( 0 , quantizedColorBytes ) ) ) ;
473+ Span < uint > colorPaletteAsUInt = MemoryMarshal . Cast < byte , uint > ( colorPalette ) ;
474+ for ( int i = 0 ; i < colorPaletteAsUInt . Length ; i ++ )
475+ {
476+ colorPaletteAsUInt [ i ] = colorPaletteAsUInt [ i ] & 0x00FFFFFF ; // Padding byte, always 0.
477+ }
478+
479+ stream . Write ( colorPalette ) ;
480+ }
407481 }
408482}
0 commit comments