diff --git a/src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs b/src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs
index 38f5c1d66..6e1145beb 100644
--- a/src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs
@@ -4,10 +4,15 @@
namespace SixLabors.ImageSharp.Formats.Bmp
{
///
- /// Enumerates the available bits per pixel for bitmap.
+ /// Enumerates the available bits per pixel the bitmap encoder supports.
///
public enum BmpBitsPerPixel : short
{
+ ///
+ /// 8 bits per pixel. Each pixel consists of 1 byte.
+ ///
+ Pixel8 = 8,
+
///
/// 16 bits per pixel. Each pixel consists of 2 bytes.
///
diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoder.cs b/src/ImageSharp/Formats/Bmp/BmpEncoder.cs
index 4efdedb34..f3eb9282c 100644
--- a/src/ImageSharp/Formats/Bmp/BmpEncoder.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpEncoder.cs
@@ -10,7 +10,6 @@ namespace SixLabors.ImageSharp.Formats.Bmp
///
/// Image encoder for writing an image to a stream as a Windows bitmap.
///
- /// The encoder can currently only write 24-bit rgb images to streams.
public sealed class BmpEncoder : IImageEncoder, IBmpEncoderOptions
{
///
diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
index 82483e390..530bcf8cd 100644
--- a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
@@ -9,6 +9,7 @@ using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.ImageSharp.Processing.Processors.Quantization;
using SixLabors.Memory;
namespace SixLabors.ImageSharp.Formats.Bmp
@@ -43,6 +44,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp
///
private const int Rgba32BlueMask = 0xFF;
+ ///
+ /// The color palette for an 8 bit image will have 256 entry's with 4 bytes for each entry.
+ ///
+ private const int ColorPaletteSize8Bit = 1024;
+
private readonly MemoryAllocator memoryAllocator;
private Configuration configuration;
@@ -142,11 +148,13 @@ namespace SixLabors.ImageSharp.Formats.Bmp
infoHeader.Compression = BmpCompression.BitFields;
}
+ int colorPaletteSize = this.bitsPerPixel == BmpBitsPerPixel.Pixel8 ? ColorPaletteSize8Bit : 0;
+
var fileHeader = new BmpFileHeader(
type: BmpConstants.TypeMarkers.Bitmap,
fileSize: BmpFileHeader.Size + infoHeaderSize + infoHeader.ImageSize,
reserved: 0,
- offset: BmpFileHeader.Size + infoHeaderSize);
+ offset: BmpFileHeader.Size + infoHeaderSize + colorPaletteSize);
#if NETCOREAPP2_1
Span buffer = stackalloc byte[infoHeaderSize];
@@ -198,6 +206,10 @@ namespace SixLabors.ImageSharp.Formats.Bmp
case BmpBitsPerPixel.Pixel16:
this.Write16Bit(stream, pixels);
break;
+
+ case BmpBitsPerPixel.Pixel8:
+ this.Write8Bit(stream, image);
+ break;
}
}
@@ -276,5 +288,51 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
}
}
+
+ ///
+ /// Writes an 8 Bit image with a color palette. The color palette has 256 entry's with 4 bytes for each entry.
+ ///
+ /// The type of the pixel.
+ /// The to write to.
+ /// The containing pixel data.
+ private void Write8Bit(Stream stream, ImageFrame image)
+ where TPixel : struct, IPixel
+ {
+#if NETCOREAPP2_1
+ Span colorPalette = stackalloc byte[ColorPaletteSize8Bit];
+#else
+ byte[] colorPalette = new byte[ColorPaletteSize8Bit];
+#endif
+
+ var quantizer = new WuQuantizer(256);
+ QuantizedFrame quantized = quantizer.CreateFrameQuantizer(this.configuration).QuantizeFrame(image);
+
+ int idx = 0;
+ var color = default(Rgba32);
+ foreach (TPixel quantizedColor in quantized.Palette)
+ {
+ quantizedColor.ToRgba32(ref color);
+ colorPalette[idx] = color.B;
+ colorPalette[idx + 1] = color.G;
+ colorPalette[idx + 2] = color.R;
+
+ // Padding byte, always 0
+ colorPalette[idx + 3] = 0;
+ idx += 4;
+ }
+
+ stream.Write(colorPalette, 0, ColorPaletteSize8Bit);
+
+ for (int y = image.Height - 1; y >= 0; y--)
+ {
+ Span pixelSpan = quantized.GetRowSpan(y);
+ stream.Write(pixelSpan);
+
+ for (int i = 0; i < this.padding; i++)
+ {
+ stream.WriteByte(0);
+ }
+ }
+ }
}
}
diff --git a/src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs b/src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs
index 96ec423e7..92ff09270 100644
--- a/src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs
+++ b/src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs
@@ -6,7 +6,6 @@ namespace SixLabors.ImageSharp.Formats.Bmp
///
/// Configuration options for use during bmp encoding
///
- /// The encoder can currently only write 16-bit, 24-bit and 32-bit rgb images to streams.
internal interface IBmpEncoderOptions
{
///
diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs
index 38862ef44..30b8bc846 100644
--- a/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs
+++ b/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs
@@ -62,7 +62,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
///
/// Gets the representation of the pixels as a of contiguous memory
- /// at row beginning from the the first pixel on that row.
+ /// at row beginning from the first pixel on that row.
///
/// The row.
/// The
diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs
index 7e054734e..42ec3088d 100644
--- a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs
@@ -114,6 +114,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
[WithFile(WinBmpv3, PixelTypes.Rgb24, BmpBitsPerPixel.Pixel24)]
[WithFile(Rgb16, PixelTypes.Bgra5551, BmpBitsPerPixel.Pixel16)]
[WithFile(Bit16, PixelTypes.Bgra5551, BmpBitsPerPixel.Pixel16)]
+ [WithFile(Bit8Palette4, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel8)]
+ [WithFile(Bit8Gs, PixelTypes.Gray8, BmpBitsPerPixel.Pixel8)]
public void Encode_WithV3Header_Works(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel)
// if supportTransparency is false, a v3 bitmap header will be written
where TPixel : struct, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: false);
@@ -126,11 +128,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
[WithFile(WinBmpv3, PixelTypes.Rgb24, BmpBitsPerPixel.Pixel24)]
[WithFile(Rgb16, PixelTypes.Bgra5551, BmpBitsPerPixel.Pixel16)]
[WithFile(Bit16, PixelTypes.Bgra5551, BmpBitsPerPixel.Pixel16)]
+ [WithFile(Bit8Palette4, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel8)]
+ [WithFile(Bit8Gs, PixelTypes.Gray8, BmpBitsPerPixel.Pixel8)]
public void Encode_WithV4Header_Works(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel)
where TPixel : struct, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true);
[Theory]
[WithFile(TestImages.Png.GrayAlpha2BitInterlaced, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel32)]
+ [WithFile(Bit32Rgba, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel32)]
public void Encode_PreservesAlpha(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel)
where TPixel : struct, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true);
@@ -139,8 +144,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
{
using (Image image = provider.GetImage())
{
- // There is no alpha in bmp with 24 bits per pixels, so the reference image will be made opaque.
- if (bitsPerPixel == BmpBitsPerPixel.Pixel24)
+ // There is no alpha in bmp with less then 32 bits per pixels, so the reference image will be made opaque.
+ if (bitsPerPixel != BmpBitsPerPixel.Pixel32)
{
image.Mutate(c => c.MakeOpaque());
}
diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs
index f82278ef5..62b7ae2ec 100644
--- a/tests/ImageSharp.Tests/TestImages.cs
+++ b/tests/ImageSharp.Tests/TestImages.cs
@@ -236,6 +236,7 @@ namespace SixLabors.ImageSharp.Tests
public const string Bit1Pal1 = "Bmp/pal1p1.bmp";
public const string Bit4 = "Bmp/pal4.bmp";
public const string Bit8 = "Bmp/test8.bmp";
+ public const string Bit8Gs = "Bmp/pal8gs.bmp";
public const string Bit8Inverted = "Bmp/test8-inverted.bmp";
public const string Bit16 = "Bmp/test16.bmp";
public const string Bit16Inverted = "Bmp/test16-inverted.bmp";
diff --git a/tests/ImageSharp.Tests/TestUtilities/PixelTypes.cs b/tests/ImageSharp.Tests/TestUtilities/PixelTypes.cs
index e4a7572d6..78431f31a 100644
--- a/tests/ImageSharp.Tests/TestUtilities/PixelTypes.cs
+++ b/tests/ImageSharp.Tests/TestUtilities/PixelTypes.cs
@@ -60,6 +60,8 @@ namespace SixLabors.ImageSharp.Tests
Bgra5551 = 1 << 22,
+ Gray8 = 1 << 23,
+
// TODO: Add multi-flag entries by rules defined in PackedPixelConverterHelper
// "All" is handled as a separate, individual case instead of using bitwise OR
diff --git a/tests/Images/Input/Bmp/pal8gs.bmp b/tests/Images/Input/Bmp/pal8gs.bmp
new file mode 100644
index 000000000..359499c7a
--- /dev/null
+++ b/tests/Images/Input/Bmp/pal8gs.bmp
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:abb09008dc6af0b33db70ed01e4183f946cc90b647bd84b078794b2d97eb9c33
+size 9254