Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
72 changes: 67 additions & 5 deletions src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Buffers;
using System.IO;
using System.Runtime.InteropServices;

using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Common.Helpers;
Expand Down Expand Up @@ -305,23 +306,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 +364,43 @@ 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);

for (int y = image.Height - 1; y >= 0; y--)
{
ReadOnlySpan<TPixel> inputPixelRow = image.GetPixelRowSpan(y);
ReadOnlySpan<byte> outputPixelRow = MemoryMarshal.AsBytes(inputPixelRow);
stream.Write(outputPixelRow);

for (int i = 0; i < this.padding; i++)
{
stream.WriteByte(0);
}
}
}
}
}
22 changes: 11 additions & 11 deletions tests/ImageSharp.Tests/Drawing/DrawImageTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public class DrawImageTests
};

[Theory]
[WithFile( TestImages.Png.Rainbow,nameof(BlendingModes), PixelTypes.Rgba32)]
[WithFile(TestImages.Png.Rainbow, nameof(BlendingModes), PixelTypes.Rgba32)]
public void ImageBlendingMatchesSvgSpecExamples<TPixel>(TestImageProvider<TPixel> provider, PixelColorBlendingMode mode)
where TPixel : struct, IPixel<TPixel>
{
Expand All @@ -54,13 +54,13 @@ public void ImageBlendingMatchesSvgSpecExamples<TPixel>(TestImageProvider<TPixel
appendSourceFileOrDescription: false);
}
}

[Theory]
[WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32, TestImages.Png.Splash, PixelColorBlendingMode.Normal, 1f)]
[WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Bgr24, TestImages.Png.Bike, PixelColorBlendingMode.Normal, 1f)]
[WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32, TestImages.Png.Splash, PixelColorBlendingMode.Normal, 0.75f)]
[WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32, TestImages.Png.Splash, PixelColorBlendingMode.Normal, 0.25f)]

[WithTestPatternImages(400, 400, PixelTypes.Rgba32, TestImages.Png.Splash, PixelColorBlendingMode.Multiply, 0.5f)]
[WithTestPatternImages(400, 400, PixelTypes.Rgba32, TestImages.Png.Splash, PixelColorBlendingMode.Add, 0.5f)]
[WithTestPatternImages(400, 400, PixelTypes.Rgba32, TestImages.Png.Splash, PixelColorBlendingMode.Subtract, 0.5f)]
Expand All @@ -77,7 +77,7 @@ public void WorksWithDifferentConfigurations<TPixel>(
using (Image<TPixel> image = provider.GetImage())
using (var blend = Image.Load<TPixel>(TestFile.Create(brushImage).Bytes))
{
Size size = new Size(image.Width * 3 / 4, image.Height *3/ 4);
Size size = new Size(image.Width * 3 / 4, image.Height * 3 / 4);
Point position = new Point(image.Width / 8, image.Height / 8);
blend.Mutate(x => x.Resize(size.Width, size.Height, KnownResamplers.Bicubic));
image.Mutate(x => x.DrawImage(blend, position, mode, opacity));
Expand All @@ -89,7 +89,7 @@ public void WorksWithDifferentConfigurations<TPixel>(
{
encoder.BitDepth = PngBitDepth.Bit16;
}

image.DebugSave(provider, testInfo, encoder: encoder);
image.CompareToReferenceOutput(ImageComparer.TolerantPercentage(0.01f),
provider,
Expand Down Expand Up @@ -138,15 +138,15 @@ public void WorksWithDifferentLocations(TestImageProvider<Rgba32> provider, int
testOutputDetails: $"{x}_{y}",
appendPixelTypeToFileName: false,
appendSourceFileOrDescription: false);

background.CompareToReferenceOutput(
provider,
testOutputDetails: $"{x}_{y}",
appendPixelTypeToFileName: false,
appendSourceFileOrDescription: false);
}
}

[Theory]
[WithFile(TestImages.Png.Splash, PixelTypes.Rgba32)]
public void DrawTransformed<TPixel>(TestImageProvider<TPixel> provider)
Expand All @@ -166,12 +166,12 @@ public void DrawTransformed<TPixel>(TestImageProvider<TPixel> provider)

// Lets center the matrix so we can tell whether any cut-off issues we may have belong to the drawing processor
var position = new Point((image.Width - blend.Width) / 2, (image.Height - blend.Height) / 2);
image.Mutate(x => x.DrawImage(blend, position, .75F));
image.Mutate(x => x.DrawImage(blend, position, .75F));

image.DebugSave(provider, appendSourceFileOrDescription: false, appendPixelTypeToFileName: false);
image.CompareToReferenceOutput(ImageComparer.TolerantPercentage(0.002f),
provider,
appendSourceFileOrDescription: false,
appendSourceFileOrDescription: false,
appendPixelTypeToFileName: false);
}
}
Expand All @@ -197,6 +197,6 @@ void Test()
}
}


}
}
60 changes: 56 additions & 4 deletions tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@

using System.IO;

using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Bmp;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Quantization;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;

using Xunit;
Expand Down Expand Up @@ -169,8 +171,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 +180,59 @@ public void Encode_8BitGray_WithV4Header_Works<TPixel>(TestImageProvider<TPixel>
TestBmpEncoderCore(
provider,
bitsPerPixel,
supportTransparency: true,
ImageComparer.TolerantPercentage(0.01f));
supportTransparency: true);

[Theory]
[WithFile(Bit32Rgb, PixelTypes.Rgba32)]
public void Encode_8BitColor_WithWuQuantizer<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
if (!TestEnvironment.Is64BitProcess)
{
return;
}

using (Image<TPixel> image = provider.GetImage())
{
var encoder = new BmpEncoder
{
BitsPerPixel = BmpBitsPerPixel.Pixel8,
Quantizer = new WuQuantizer(256)
};
string actualOutputFile = provider.Utility.SaveTestOutputFile(image, "bmp", encoder, appendPixelTypeToFileName: false);
IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile);
using (var referenceImage = Image.Load<TPixel>(actualOutputFile, referenceDecoder))
{
referenceImage.CompareToReferenceOutput(ImageComparer.TolerantPercentage(0.01f), provider, extension: "bmp", appendPixelTypeToFileName: false);
}
}
}

[Theory]
[WithFile(Bit32Rgb, PixelTypes.Rgba32)]
public void Encode_8BitColor_WithOctreeQuantizer<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
if (!TestEnvironment.Is64BitProcess)
{
return;
}

using (Image<TPixel> image = provider.GetImage())
{
var encoder = new BmpEncoder
{
BitsPerPixel = BmpBitsPerPixel.Pixel8,
Quantizer = new OctreeQuantizer(256)
};
string actualOutputFile = provider.Utility.SaveTestOutputFile(image, "bmp", encoder, appendPixelTypeToFileName: false);
IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile);
using (var referenceImage = Image.Load<TPixel>(actualOutputFile, referenceDecoder))
{
referenceImage.CompareToReferenceOutput(ImageComparer.TolerantPercentage(0.01f), provider, extension: "bmp", appendPixelTypeToFileName: false);
}
}
}

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