Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 74 additions & 5 deletions src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -305,23 +305,46 @@ private void Write16Bit<TPixel>(Stream stream, Buffer2D<TPixel> pixels)
private void Write8Bit<TPixel>(Stream stream, ImageFrame<TPixel> image)
where TPixel : struct, IPixel<TPixel>
{
bool isGray8 = typeof(TPixel) == typeof(Gray8);
using (IMemoryOwner<byte> colorPaletteBuffer = this.memoryAllocator.AllocateManagedByteBuffer(ColorPaletteSize8Bit, AllocationOptions.Clean))
using (IQuantizedFrame<TPixel> quantized = this.quantizer.CreateFrameQuantizer<TPixel>(this.configuration, 256).QuantizeFrame(image))
{
Span<byte> colorPalette = colorPaletteBuffer.GetSpan();
int idx = 0;
if (isGray8)
{
this.Write8BitGray(stream, image, colorPalette);
}
else
{
this.Write8BitColor(stream, image, colorPalette);
}
}
}

/// <summary>
/// Writes an 8 Bit color 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>
/// <param name="colorPalette">A byte span of size 1024 for the color palette.</param>
private void Write8BitColor<TPixel>(Stream stream, ImageFrame<TPixel> image, Span<byte> colorPalette)
where TPixel : struct, IPixel<TPixel>
{
using (IQuantizedFrame<TPixel> quantized = this.quantizer.CreateFrameQuantizer<TPixel>(this.configuration, 256).QuantizeFrame(image))
{
ReadOnlySpan<TPixel> quantizedColors = quantized.Palette.Span;
var color = default(Rgba32);
ReadOnlySpan<TPixel> paletteSpan = quantized.Palette.Span;

// TODO: Use bulk conversion here for better perf
foreach (TPixel quantizedColor in paletteSpan)
int idx = 0;
foreach (TPixel quantizedColor in quantizedColors)
{
quantizedColor.ToRgba32(ref color);
colorPalette[idx] = color.B;
colorPalette[idx + 1] = color.G;
colorPalette[idx + 2] = color.R;

// Padding byte, always 0
// Padding byte, always 0.
colorPalette[idx + 3] = 0;
idx += 4;
}
Expand All @@ -340,5 +363,51 @@ private void Write8Bit<TPixel>(Stream stream, ImageFrame<TPixel> image)
}
}
}

/// <summary>
/// Writes an 8 Bit gray 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>
/// <param name="colorPalette">A byte span of size 1024 for the color palette.</param>
private void Write8BitGray<TPixel>(Stream stream, ImageFrame<TPixel> image, Span<byte> colorPalette)
where TPixel : struct, IPixel<TPixel>
{
// Create a color palette with 256 different gray values.
for (int i = 0; i <= 255; i++)
{
int idx = i * 4;
byte grayValue = (byte)i;
colorPalette[idx] = grayValue;
colorPalette[idx + 1] = grayValue;
colorPalette[idx + 2] = grayValue;

// Padding byte, always 0.
colorPalette[idx + 3] = 0;
}

stream.Write(colorPalette);

using (IMemoryOwner<byte> rowSpanBuffer = this.memoryAllocator.AllocateManagedByteBuffer(image.Width, AllocationOptions.Clean))
{
Span<byte> outputPixelRow = rowSpanBuffer.GetSpan();
for (int y = image.Height - 1; y >= 0; y--)
{
Span<TPixel> inputPixelRow = image.GetPixelRowSpan(y);
PixelOperations<TPixel>.Instance.ToGray8Bytes(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we replace this with inputPixelRow.AsBytes().CopyTo(outputPixelRow) here? Seems more explanatory about our intention.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can not find AsBytes(), is that an extension method im missing?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apologies, it's MemoryMarshal.AsBytes(spanOfThings) 😄

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i have changed that, please check it again.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks very good, thanks!

this.configuration,
inputPixelRow,
outputPixelRow,
image.Width);
stream.Write(outputPixelRow);

for (int i = 0; i < this.padding; i++)
{
stream.WriteByte(0);
}
}
}
}
}
}
6 changes: 2 additions & 4 deletions tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -169,8 +169,7 @@ public void Encode_8BitGray_WithV3Header_Works<TPixel>(TestImageProvider<TPixel>
TestBmpEncoderCore(
provider,
bitsPerPixel,
supportTransparency: false,
ImageComparer.TolerantPercentage(0.01f));
supportTransparency: false);

[Theory]
[WithFile(Bit8Gs, PixelTypes.Gray8, BmpBitsPerPixel.Pixel8)]
Expand All @@ -179,8 +178,7 @@ public void Encode_8BitGray_WithV4Header_Works<TPixel>(TestImageProvider<TPixel>
TestBmpEncoderCore(
provider,
bitsPerPixel,
supportTransparency: true,
ImageComparer.TolerantPercentage(0.01f));
supportTransparency: true);

[Theory]
[WithFile(TestImages.Png.GrayAlpha2BitInterlaced, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel32)]
Expand Down