Browse Source

Merge pull request #909 from brianpopow/feature/dontUseQuantizeOnGray8

Not using quantization for Grey8 images when encoding 8Bit Bitmaps
af/merge-core
Anton Firsov 7 years ago
committed by GitHub
parent
commit
2ac0336c98
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 72
      src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
  2. 22
      tests/ImageSharp.Tests/Drawing/DrawImageTests.cs
  3. 60
      tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs
  4. 2
      tests/Images/External

72
src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs

@ -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;
@ -305,23 +306,46 @@ namespace SixLabors.ImageSharp.Formats.Bmp
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;
}
@ -340,5 +364,43 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
}
}
/// <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
tests/ImageSharp.Tests/Drawing/DrawImageTests.cs

@ -32,7 +32,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing
};
[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>
{
@ -54,13 +54,13 @@ namespace SixLabors.ImageSharp.Tests.Drawing
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)]
@ -77,7 +77,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing
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));
@ -89,7 +89,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing
{
encoder.BitDepth = PngBitDepth.Bit16;
}
image.DebugSave(provider, testInfo, encoder: encoder);
image.CompareToReferenceOutput(ImageComparer.TolerantPercentage(0.01f),
provider,
@ -138,7 +138,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing
testOutputDetails: $"{x}_{y}",
appendPixelTypeToFileName: false,
appendSourceFileOrDescription: false);
background.CompareToReferenceOutput(
provider,
testOutputDetails: $"{x}_{y}",
@ -146,7 +146,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing
appendSourceFileOrDescription: false);
}
}
[Theory]
[WithFile(TestImages.Png.Splash, PixelTypes.Rgba32)]
public void DrawTransformed<TPixel>(TestImageProvider<TPixel> provider)
@ -166,12 +166,12 @@ namespace SixLabors.ImageSharp.Tests.Drawing
// 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);
}
}
@ -197,6 +197,6 @@ namespace SixLabors.ImageSharp.Tests.Drawing
}
}
}
}

60
tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs

@ -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;
@ -169,8 +171,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
TestBmpEncoderCore(
provider,
bitsPerPixel,
supportTransparency: false,
ImageComparer.TolerantPercentage(0.01f));
supportTransparency: false);
[Theory]
[WithFile(Bit8Gs, PixelTypes.Gray8, BmpBitsPerPixel.Pixel8)]
@ -179,8 +180,59 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
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)]

2
tests/Images/External

@ -1 +1 @@
Subproject commit 82f082ed3b47cc21b38dda9c5f8dda06dca5233a
Subproject commit 42b3b980ed07afd7b6603a5bfa6ffb91d6c8a124
Loading…
Cancel
Save