|
|
@ -51,6 +51,16 @@ namespace SixLabors.ImageSharp.Formats.Bmp |
|
|
/// </summary>
|
|
|
/// </summary>
|
|
|
private const int ColorPaletteSize8Bit = 1024; |
|
|
private const int ColorPaletteSize8Bit = 1024; |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// The color palette for an 4 bit image will have 16 entry's with 4 bytes for each entry.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private const int ColorPaletteSize4Bit = 64; |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// The color palette for an 1 bit image will have 2 entry's with 4 bytes for each entry.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private const int ColorPaletteSize1Bit = 8; |
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
/// <summary>
|
|
|
/// Used for allocating memory during processing operations.
|
|
|
/// Used for allocating memory during processing operations.
|
|
|
/// </summary>
|
|
|
/// </summary>
|
|
|
@ -74,7 +84,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp |
|
|
private readonly bool writeV4Header; |
|
|
private readonly bool writeV4Header; |
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
/// <summary>
|
|
|
/// The quantizer for reducing the color count for 8-Bit images.
|
|
|
/// The quantizer for reducing the color count for 8-Bit, 4-Bit and 1-Bit images.
|
|
|
/// </summary>
|
|
|
/// </summary>
|
|
|
private readonly IQuantizer quantizer; |
|
|
private readonly IQuantizer quantizer; |
|
|
|
|
|
|
|
|
@ -88,7 +98,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp |
|
|
this.memoryAllocator = memoryAllocator; |
|
|
this.memoryAllocator = memoryAllocator; |
|
|
this.bitsPerPixel = options.BitsPerPixel; |
|
|
this.bitsPerPixel = options.BitsPerPixel; |
|
|
this.writeV4Header = options.SupportTransparency; |
|
|
this.writeV4Header = options.SupportTransparency; |
|
|
this.quantizer = options.Quantizer ?? KnownQuantizers.Octree; |
|
|
this.quantizer = options.Quantizer ?? KnownQuantizers.Wu; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
/// <summary>
|
|
|
@ -107,7 +117,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp |
|
|
this.configuration = image.GetConfiguration(); |
|
|
this.configuration = image.GetConfiguration(); |
|
|
ImageMetadata metadata = image.Metadata; |
|
|
ImageMetadata metadata = image.Metadata; |
|
|
BmpMetadata bmpMetadata = metadata.GetBmpMetadata(); |
|
|
BmpMetadata bmpMetadata = metadata.GetBmpMetadata(); |
|
|
this.bitsPerPixel = this.bitsPerPixel ?? bmpMetadata.BitsPerPixel; |
|
|
this.bitsPerPixel ??= bmpMetadata.BitsPerPixel; |
|
|
|
|
|
|
|
|
short bpp = (short)this.bitsPerPixel; |
|
|
short bpp = (short)this.bitsPerPixel; |
|
|
int bytesPerLine = 4 * (((image.Width * bpp) + 31) / 32); |
|
|
int bytesPerLine = 4 * (((image.Width * bpp) + 31) / 32); |
|
|
@ -166,7 +176,19 @@ namespace SixLabors.ImageSharp.Formats.Bmp |
|
|
infoHeader.Compression = BmpCompression.BitFields; |
|
|
infoHeader.Compression = BmpCompression.BitFields; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
int colorPaletteSize = this.bitsPerPixel == BmpBitsPerPixel.Pixel8 ? ColorPaletteSize8Bit : 0; |
|
|
int colorPaletteSize = 0; |
|
|
|
|
|
if (this.bitsPerPixel == BmpBitsPerPixel.Pixel8) |
|
|
|
|
|
{ |
|
|
|
|
|
colorPaletteSize = ColorPaletteSize8Bit; |
|
|
|
|
|
} |
|
|
|
|
|
else if (this.bitsPerPixel == BmpBitsPerPixel.Pixel4) |
|
|
|
|
|
{ |
|
|
|
|
|
colorPaletteSize = ColorPaletteSize4Bit; |
|
|
|
|
|
} |
|
|
|
|
|
else if (this.bitsPerPixel == BmpBitsPerPixel.Pixel1) |
|
|
|
|
|
{ |
|
|
|
|
|
colorPaletteSize = ColorPaletteSize1Bit; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
var fileHeader = new BmpFileHeader( |
|
|
var fileHeader = new BmpFileHeader( |
|
|
type: BmpConstants.TypeMarkers.Bitmap, |
|
|
type: BmpConstants.TypeMarkers.Bitmap, |
|
|
@ -224,6 +246,14 @@ namespace SixLabors.ImageSharp.Formats.Bmp |
|
|
case BmpBitsPerPixel.Pixel8: |
|
|
case BmpBitsPerPixel.Pixel8: |
|
|
this.Write8Bit(stream, image); |
|
|
this.Write8Bit(stream, image); |
|
|
break; |
|
|
break; |
|
|
|
|
|
|
|
|
|
|
|
case BmpBitsPerPixel.Pixel4: |
|
|
|
|
|
this.Write4BitColor(stream, image); |
|
|
|
|
|
break; |
|
|
|
|
|
|
|
|
|
|
|
case BmpBitsPerPixel.Pixel1: |
|
|
|
|
|
this.Write1BitColor(stream, image); |
|
|
|
|
|
break; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@ -308,7 +338,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
/// <summary>
|
|
|
/// Writes an 8 Bit image with a color palette. The color palette has 256 entry's with 4 bytes for each entry.
|
|
|
/// Writes an 8 bit image with a color palette. The color palette has 256 entry's with 4 bytes for each entry.
|
|
|
/// </summary>
|
|
|
/// </summary>
|
|
|
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
|
|
|
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
|
|
|
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
|
|
|
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
|
|
|
@ -332,7 +362,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
/// <summary>
|
|
|
/// Writes an 8 Bit color image with a color palette. The color palette has 256 entry's with 4 bytes for each entry.
|
|
|
/// Writes an 8 bit color image with a color palette. The color palette has 256 entry's with 4 bytes for each entry.
|
|
|
/// </summary>
|
|
|
/// </summary>
|
|
|
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
|
|
|
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
|
|
|
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
|
|
|
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
|
|
|
@ -344,16 +374,8 @@ namespace SixLabors.ImageSharp.Formats.Bmp |
|
|
using IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration); |
|
|
using IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration); |
|
|
using IndexedImageFrame<TPixel> quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds()); |
|
|
using IndexedImageFrame<TPixel> quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds()); |
|
|
|
|
|
|
|
|
ReadOnlySpan<TPixel> quantizedColors = quantized.Palette.Span; |
|
|
ReadOnlySpan<TPixel> quantizedColorPalette = quantized.Palette.Span; |
|
|
var quantizedColorBytes = quantizedColors.Length * 4; |
|
|
this.WriteColorPalette(stream, quantizedColorPalette, colorPalette); |
|
|
PixelOperations<TPixel>.Instance.ToBgra32(this.configuration, quantizedColors, MemoryMarshal.Cast<byte, Bgra32>(colorPalette.Slice(0, quantizedColorBytes))); |
|
|
|
|
|
Span<uint> colorPaletteAsUInt = MemoryMarshal.Cast<byte, uint>(colorPalette); |
|
|
|
|
|
for (int i = 0; i < colorPaletteAsUInt.Length; i++) |
|
|
|
|
|
{ |
|
|
|
|
|
colorPaletteAsUInt[i] = colorPaletteAsUInt[i] & 0x00FFFFFF; // Padding byte, always 0.
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
stream.Write(colorPalette); |
|
|
|
|
|
|
|
|
|
|
|
for (int y = image.Height - 1; y >= 0; y--) |
|
|
for (int y = image.Height - 1; y >= 0; y--) |
|
|
{ |
|
|
{ |
|
|
@ -368,7 +390,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
/// <summary>
|
|
|
/// Writes an 8 Bit gray image with a color palette. The color palette has 256 entry's with 4 bytes for each entry.
|
|
|
/// Writes an 8 bit gray image with a color palette. The color palette has 256 entry's with 4 bytes for each entry.
|
|
|
/// </summary>
|
|
|
/// </summary>
|
|
|
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
|
|
|
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
|
|
|
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
|
|
|
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
|
|
|
@ -404,5 +426,136 @@ namespace SixLabors.ImageSharp.Formats.Bmp |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Writes an 4 bit color image with a color palette. The color palette has 16 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 Write4BitColor<TPixel>(Stream stream, ImageFrame<TPixel> image) |
|
|
|
|
|
where TPixel : unmanaged, IPixel<TPixel> |
|
|
|
|
|
{ |
|
|
|
|
|
using IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration, new QuantizerOptions() |
|
|
|
|
|
{ |
|
|
|
|
|
MaxColors = 16 |
|
|
|
|
|
}); |
|
|
|
|
|
using IndexedImageFrame<TPixel> quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds()); |
|
|
|
|
|
using IMemoryOwner<byte> colorPaletteBuffer = this.memoryAllocator.AllocateManagedByteBuffer(ColorPaletteSize4Bit, AllocationOptions.Clean); |
|
|
|
|
|
|
|
|
|
|
|
Span<byte> colorPalette = colorPaletteBuffer.GetSpan(); |
|
|
|
|
|
ReadOnlySpan<TPixel> quantizedColorPalette = quantized.Palette.Span; |
|
|
|
|
|
this.WriteColorPalette(stream, quantizedColorPalette, colorPalette); |
|
|
|
|
|
|
|
|
|
|
|
ReadOnlySpan<byte> pixelRowSpan = quantized.GetPixelRowSpan(0); |
|
|
|
|
|
int rowPadding = pixelRowSpan.Length % 2 != 0 ? this.padding - 1 : this.padding; |
|
|
|
|
|
for (int y = image.Height - 1; y >= 0; y--) |
|
|
|
|
|
{ |
|
|
|
|
|
pixelRowSpan = quantized.GetPixelRowSpan(y); |
|
|
|
|
|
|
|
|
|
|
|
int endIdx = pixelRowSpan.Length % 2 == 0 ? pixelRowSpan.Length : pixelRowSpan.Length - 1; |
|
|
|
|
|
for (int i = 0; i < endIdx; i += 2) |
|
|
|
|
|
{ |
|
|
|
|
|
stream.WriteByte((byte)((pixelRowSpan[i] << 4) | pixelRowSpan[i + 1])); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (pixelRowSpan.Length % 2 != 0) |
|
|
|
|
|
{ |
|
|
|
|
|
stream.WriteByte((byte)((pixelRowSpan[pixelRowSpan.Length - 1] << 4) | 0)); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < rowPadding; i++) |
|
|
|
|
|
{ |
|
|
|
|
|
stream.WriteByte(0); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Writes a 1 bit image with a color palette. The color palette has 2 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 Write1BitColor<TPixel>(Stream stream, ImageFrame<TPixel> image) |
|
|
|
|
|
where TPixel : unmanaged, IPixel<TPixel> |
|
|
|
|
|
{ |
|
|
|
|
|
using IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration, new QuantizerOptions() |
|
|
|
|
|
{ |
|
|
|
|
|
MaxColors = 2 |
|
|
|
|
|
}); |
|
|
|
|
|
using IndexedImageFrame<TPixel> quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds()); |
|
|
|
|
|
using IMemoryOwner<byte> colorPaletteBuffer = this.memoryAllocator.AllocateManagedByteBuffer(ColorPaletteSize1Bit, AllocationOptions.Clean); |
|
|
|
|
|
|
|
|
|
|
|
Span<byte> colorPalette = colorPaletteBuffer.GetSpan(); |
|
|
|
|
|
ReadOnlySpan<TPixel> quantizedColorPalette = quantized.Palette.Span; |
|
|
|
|
|
this.WriteColorPalette(stream, quantizedColorPalette, colorPalette); |
|
|
|
|
|
|
|
|
|
|
|
ReadOnlySpan<byte> quantizedPixelRow = quantized.GetPixelRowSpan(0); |
|
|
|
|
|
int rowPadding = quantizedPixelRow.Length % 8 != 0 ? this.padding - 1 : this.padding; |
|
|
|
|
|
for (int y = image.Height - 1; y >= 0; y--) |
|
|
|
|
|
{ |
|
|
|
|
|
quantizedPixelRow = quantized.GetPixelRowSpan(y); |
|
|
|
|
|
|
|
|
|
|
|
int endIdx = quantizedPixelRow.Length % 8 == 0 ? quantizedPixelRow.Length : quantizedPixelRow.Length - 8; |
|
|
|
|
|
for (int i = 0; i < endIdx; i += 8) |
|
|
|
|
|
{ |
|
|
|
|
|
Write1BitPalette(stream, i, i + 8, quantizedPixelRow); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (quantizedPixelRow.Length % 8 != 0) |
|
|
|
|
|
{ |
|
|
|
|
|
int startIdx = quantizedPixelRow.Length - 7; |
|
|
|
|
|
endIdx = quantizedPixelRow.Length; |
|
|
|
|
|
Write1BitPalette(stream, startIdx, endIdx, quantizedPixelRow); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < rowPadding; i++) |
|
|
|
|
|
{ |
|
|
|
|
|
stream.WriteByte(0); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Writes the color palette to the stream. The color palette has 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="quantizedColorPalette">The color palette from the quantized image.</param>
|
|
|
|
|
|
/// <param name="colorPalette">A temporary byte span to write the color palette to.</param>
|
|
|
|
|
|
private void WriteColorPalette<TPixel>(Stream stream, ReadOnlySpan<TPixel> quantizedColorPalette, Span<byte> colorPalette) |
|
|
|
|
|
where TPixel : unmanaged, IPixel<TPixel> |
|
|
|
|
|
{ |
|
|
|
|
|
int quantizedColorBytes = quantizedColorPalette.Length * 4; |
|
|
|
|
|
PixelOperations<TPixel>.Instance.ToBgra32(this.configuration, quantizedColorPalette, MemoryMarshal.Cast<byte, Bgra32>(colorPalette.Slice(0, quantizedColorBytes))); |
|
|
|
|
|
Span<uint> colorPaletteAsUInt = MemoryMarshal.Cast<byte, uint>(colorPalette); |
|
|
|
|
|
for (int i = 0; i < colorPaletteAsUInt.Length; i++) |
|
|
|
|
|
{ |
|
|
|
|
|
colorPaletteAsUInt[i] = colorPaletteAsUInt[i] & 0x00FFFFFF; // Padding byte, always 0.
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
stream.Write(colorPalette); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Writes a 1-bit palette.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="stream">The stream to write the palette to.</param>
|
|
|
|
|
|
/// <param name="startIdx">The start index.</param>
|
|
|
|
|
|
/// <param name="endIdx">The end index.</param>
|
|
|
|
|
|
/// <param name="quantizedPixelRow">A quantized pixel row.</param>
|
|
|
|
|
|
private static void Write1BitPalette(Stream stream, int startIdx, int endIdx, ReadOnlySpan<byte> quantizedPixelRow) |
|
|
|
|
|
{ |
|
|
|
|
|
int shift = 7; |
|
|
|
|
|
byte indices = 0; |
|
|
|
|
|
for (int j = startIdx; j < endIdx; j++) |
|
|
|
|
|
{ |
|
|
|
|
|
indices = (byte)(indices | ((byte)(quantizedPixelRow[j] & 1) << shift)); |
|
|
|
|
|
shift--; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
stream.WriteByte(indices); |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|