Browse Source

Add support for encoding 8-bit bitmaps

af/merge-core
Brian Popow 7 years ago
parent
commit
fddae8734a
  1. 7
      src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs
  2. 1
      src/ImageSharp/Formats/Bmp/BmpEncoder.cs
  3. 60
      src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
  4. 1
      src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs
  5. 2
      src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs
  6. 9
      tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs
  7. 1
      tests/ImageSharp.Tests/TestImages.cs
  8. 2
      tests/ImageSharp.Tests/TestUtilities/PixelTypes.cs
  9. 3
      tests/Images/Input/Bmp/pal8gs.bmp

7
src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs

@ -4,10 +4,15 @@
namespace SixLabors.ImageSharp.Formats.Bmp
{
/// <summary>
/// Enumerates the available bits per pixel for bitmap.
/// Enumerates the available bits per pixel the bitmap encoder supports.
/// </summary>
public enum BmpBitsPerPixel : short
{
/// <summary>
/// 8 bits per pixel. Each pixel consists of 1 byte.
/// </summary>
Pixel8 = 8,
/// <summary>
/// 16 bits per pixel. Each pixel consists of 2 bytes.
/// </summary>

1
src/ImageSharp/Formats/Bmp/BmpEncoder.cs

@ -10,7 +10,6 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// <summary>
/// Image encoder for writing an image to a stream as a Windows bitmap.
/// </summary>
/// <remarks>The encoder can currently only write 24-bit rgb images to streams.</remarks>
public sealed class BmpEncoder : IImageEncoder, IBmpEncoderOptions
{
/// <summary>

60
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
/// </summary>
private const int Rgba32BlueMask = 0xFF;
/// <summary>
/// The color palette for an 8 bit image will have 256 entry's with 4 bytes for each entry.
/// </summary>
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<byte> 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
}
}
}
/// <summary>
/// Writes an 8 Bit image with a color palette. The color palette has 256 entry's with 4 bytes for each entry.
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="image"> The <see cref="ImageFrame{TPixel}"/> containing pixel data.</param>
private void Write8Bit<TPixel>(Stream stream, ImageFrame<TPixel> image)
where TPixel : struct, IPixel<TPixel>
{
#if NETCOREAPP2_1
Span<byte> colorPalette = stackalloc byte[ColorPaletteSize8Bit];
#else
byte[] colorPalette = new byte[ColorPaletteSize8Bit];
#endif
var quantizer = new WuQuantizer(256);
QuantizedFrame<TPixel> quantized = quantizer.CreateFrameQuantizer<TPixel>(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<byte> pixelSpan = quantized.GetRowSpan(y);
stream.Write(pixelSpan);
for (int i = 0; i < this.padding; i++)
{
stream.WriteByte(0);
}
}
}
}
}

1
src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs

@ -6,7 +6,6 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// <summary>
/// Configuration options for use during bmp encoding
/// </summary>
/// <remarks>The encoder can currently only write 16-bit, 24-bit and 32-bit rgb images to streams.</remarks>
internal interface IBmpEncoderOptions
{
/// <summary>

2
src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs

@ -62,7 +62,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <summary>
/// Gets the representation of the pixels as a <see cref="Span{T}"/> of contiguous memory
/// at row <paramref name="rowIndex"/> beginning from the the first pixel on that row.
/// at row <paramref name="rowIndex"/> beginning from the first pixel on that row.
/// </summary>
/// <param name="rowIndex">The row.</param>
/// <returns>The <see cref="Span{T}"/></returns>

9
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<TPixel>(TestImageProvider<TPixel> provider, BmpBitsPerPixel bitsPerPixel)
// if supportTransparency is false, a v3 bitmap header will be written
where TPixel : struct, IPixel<TPixel> => 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<TPixel>(TestImageProvider<TPixel> provider, BmpBitsPerPixel bitsPerPixel)
where TPixel : struct, IPixel<TPixel> => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true);
[Theory]
[WithFile(TestImages.Png.GrayAlpha2BitInterlaced, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel32)]
[WithFile(Bit32Rgba, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel32)]
public void Encode_PreservesAlpha<TPixel>(TestImageProvider<TPixel> provider, BmpBitsPerPixel bitsPerPixel)
where TPixel : struct, IPixel<TPixel> => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true);
@ -139,8 +144,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
{
using (Image<TPixel> 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());
}

1
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";

2
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

3
tests/Images/Input/Bmp/pal8gs.bmp

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:abb09008dc6af0b33db70ed01e4183f946cc90b647bd84b078794b2d97eb9c33
size 9254
Loading…
Cancel
Save