Skip to content

Commit 86d884c

Browse files
committed
Merge remote-tracking branch 'origin/master' into af/general-color-type
# Conflicts: # src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs
2 parents 552d885 + 2461da8 commit 86d884c

File tree

10 files changed

+170
-20
lines changed

10 files changed

+170
-20
lines changed

src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,15 @@
44
namespace SixLabors.ImageSharp.Formats.Bmp
55
{
66
/// <summary>
7-
/// Enumerates the available bits per pixel for bitmap.
7+
/// Enumerates the available bits per pixel the bitmap encoder supports.
88
/// </summary>
99
public enum BmpBitsPerPixel : short
1010
{
11+
/// <summary>
12+
/// 8 bits per pixel. Each pixel consists of 1 byte.
13+
/// </summary>
14+
Pixel8 = 8,
15+
1116
/// <summary>
1217
/// 16 bits per pixel. Each pixel consists of 2 bytes.
1318
/// </summary>

src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1022,7 +1022,8 @@ private void ReadInfoHeader()
10221022
this.bmpMetadata.InfoHeaderType = infoHeaderType;
10231023

10241024
// We can only encode at these bit rates so far.
1025-
if (bitsPerPixel.Equals((short)BmpBitsPerPixel.Pixel16)
1025+
if (bitsPerPixel.Equals((short)BmpBitsPerPixel.Pixel8)
1026+
|| bitsPerPixel.Equals((short)BmpBitsPerPixel.Pixel16)
10261027
|| bitsPerPixel.Equals((short)BmpBitsPerPixel.Pixel24)
10271028
|| bitsPerPixel.Equals((short)BmpBitsPerPixel.Pixel32))
10281029
{

src/ImageSharp/Formats/Bmp/BmpEncoder.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@
44
using System.IO;
55
using SixLabors.ImageSharp.Advanced;
66
using SixLabors.ImageSharp.PixelFormats;
7+
using SixLabors.ImageSharp.Processing.Processors.Quantization;
78

89
namespace SixLabors.ImageSharp.Formats.Bmp
910
{
1011
/// <summary>
1112
/// Image encoder for writing an image to a stream as a Windows bitmap.
1213
/// </summary>
13-
/// <remarks>The encoder can currently only write 24-bit rgb images to streams.</remarks>
1414
public sealed class BmpEncoder : IImageEncoder, IBmpEncoderOptions
1515
{
1616
/// <summary>
@@ -26,6 +26,12 @@ public sealed class BmpEncoder : IImageEncoder, IBmpEncoderOptions
2626
/// </summary>
2727
public bool SupportTransparency { get; set; }
2828

29+
/// <summary>
30+
/// Gets or sets the quantizer for reducing the color count for 8-Bit images.
31+
/// Defaults to OctreeQuantizer.
32+
/// </summary>
33+
public IQuantizer Quantizer { get; set; }
34+
2935
/// <inheritdoc/>
3036
public void Encode<TPixel>(Image<TPixel> image, Stream stream)
3137
where TPixel : struct, IPixel<TPixel>

src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@
22
// Licensed under the Apache License, Version 2.0.
33

44
using System;
5+
using System.Buffers;
56
using System.IO;
67

78
using SixLabors.ImageSharp.Advanced;
89
using SixLabors.ImageSharp.Common.Helpers;
910
using SixLabors.ImageSharp.Memory;
1011
using SixLabors.ImageSharp.Metadata;
1112
using SixLabors.ImageSharp.PixelFormats;
13+
using SixLabors.ImageSharp.Processing.Processors.Quantization;
1214
using SixLabors.Memory;
1315

1416
namespace SixLabors.ImageSharp.Formats.Bmp
@@ -43,6 +45,11 @@ internal sealed class BmpEncoderCore
4345
/// </summary>
4446
private const int Rgba32BlueMask = 0xFF;
4547

48+
/// <summary>
49+
/// The color palette for an 8 bit image will have 256 entry's with 4 bytes for each entry.
50+
/// </summary>
51+
private const int ColorPaletteSize8Bit = 1024;
52+
4653
private readonly MemoryAllocator memoryAllocator;
4754

4855
private Configuration configuration;
@@ -56,6 +63,11 @@ internal sealed class BmpEncoderCore
5663
/// </summary>
5764
private readonly bool writeV4Header;
5865

66+
/// <summary>
67+
/// The quantizer for reducing the color count for 8-Bit images.
68+
/// </summary>
69+
private readonly IQuantizer quantizer;
70+
5971
/// <summary>
6072
/// Initializes a new instance of the <see cref="BmpEncoderCore"/> class.
6173
/// </summary>
@@ -66,6 +78,7 @@ public BmpEncoderCore(IBmpEncoderOptions options, MemoryAllocator memoryAllocato
6678
this.memoryAllocator = memoryAllocator;
6779
this.bitsPerPixel = options.BitsPerPixel;
6880
this.writeV4Header = options.SupportTransparency;
81+
this.quantizer = options.Quantizer ?? new OctreeQuantizer(dither: true, maxColors: 256);
6982
}
7083

7184
/// <summary>
@@ -142,11 +155,13 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream)
142155
infoHeader.Compression = BmpCompression.BitFields;
143156
}
144157

158+
int colorPaletteSize = this.bitsPerPixel == BmpBitsPerPixel.Pixel8 ? ColorPaletteSize8Bit : 0;
159+
145160
var fileHeader = new BmpFileHeader(
146161
type: BmpConstants.TypeMarkers.Bitmap,
147162
fileSize: BmpFileHeader.Size + infoHeaderSize + infoHeader.ImageSize,
148163
reserved: 0,
149-
offset: BmpFileHeader.Size + infoHeaderSize);
164+
offset: BmpFileHeader.Size + infoHeaderSize + colorPaletteSize);
150165

151166
#if NETCOREAPP2_1
152167
Span<byte> buffer = stackalloc byte[infoHeaderSize];
@@ -198,6 +213,10 @@ private void WriteImage<TPixel>(Stream stream, ImageFrame<TPixel> image)
198213
case BmpBitsPerPixel.Pixel16:
199214
this.Write16Bit(stream, pixels);
200215
break;
216+
217+
case BmpBitsPerPixel.Pixel8:
218+
this.Write8Bit(stream, image);
219+
break;
201220
}
202221
}
203222

@@ -276,5 +295,47 @@ private void Write16Bit<TPixel>(Stream stream, Buffer2D<TPixel> pixels)
276295
}
277296
}
278297
}
298+
299+
/// <summary>
300+
/// Writes an 8 Bit image with a color palette. The color palette has 256 entry's with 4 bytes for each entry.
301+
/// </summary>
302+
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
303+
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
304+
/// <param name="image"> The <see cref="ImageFrame{TPixel}"/> containing pixel data.</param>
305+
private void Write8Bit<TPixel>(Stream stream, ImageFrame<TPixel> image)
306+
where TPixel : struct, IPixel<TPixel>
307+
{
308+
using (IMemoryOwner<byte> colorPaletteBuffer = this.memoryAllocator.AllocateManagedByteBuffer(ColorPaletteSize8Bit, AllocationOptions.Clean))
309+
using (QuantizedFrame<TPixel> quantized = this.quantizer.CreateFrameQuantizer<TPixel>(this.configuration, 256).QuantizeFrame(image))
310+
{
311+
Span<byte> colorPalette = colorPaletteBuffer.GetSpan();
312+
int idx = 0;
313+
var color = default(Rgba32);
314+
foreach (TPixel quantizedColor in quantized.Palette)
315+
{
316+
quantizedColor.ToRgba32(ref color);
317+
colorPalette[idx] = color.B;
318+
colorPalette[idx + 1] = color.G;
319+
colorPalette[idx + 2] = color.R;
320+
321+
// Padding byte, always 0
322+
colorPalette[idx + 3] = 0;
323+
idx += 4;
324+
}
325+
326+
stream.Write(colorPalette);
327+
328+
for (int y = image.Height - 1; y >= 0; y--)
329+
{
330+
Span<byte> pixelSpan = quantized.GetRowSpan(y);
331+
stream.Write(pixelSpan);
332+
333+
for (int i = 0; i < this.padding; i++)
334+
{
335+
stream.WriteByte(0);
336+
}
337+
}
338+
}
339+
}
279340
}
280341
}

src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
// Copyright (c) Six Labors and contributors.
22
// Licensed under the Apache License, Version 2.0.
33

4+
using SixLabors.ImageSharp.Processing.Processors.Quantization;
5+
46
namespace SixLabors.ImageSharp.Formats.Bmp
57
{
68
/// <summary>
7-
/// Configuration options for use during bmp encoding
9+
/// Configuration options for use during bmp encoding.
810
/// </summary>
9-
/// <remarks>The encoder can currently only write 16-bit, 24-bit and 32-bit rgb images to streams.</remarks>
1011
internal interface IBmpEncoderOptions
1112
{
1213
/// <summary>
@@ -21,5 +22,10 @@ internal interface IBmpEncoderOptions
2122
/// Instead a bitmap version 4 info header will be written with the BITFIELDS compression.
2223
/// </summary>
2324
bool SupportTransparency { get; }
25+
26+
/// <summary>
27+
/// Gets the quantizer for reducing the color count for 8-Bit images.
28+
/// </summary>
29+
IQuantizer Quantizer { get; }
2430
}
2531
}

src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public OctreeQuantizer(int maxColors)
3535
/// <summary>
3636
/// Initializes a new instance of the <see cref="OctreeQuantizer"/> class.
3737
/// </summary>
38-
/// <param name="dither">Whether to apply dithering to the output image</param>
38+
/// <param name="dither">Whether to apply dithering to the output image.</param>
3939
public OctreeQuantizer(bool dither)
4040
: this(GetDiffuser(dither), QuantizerConstants.MaxColors)
4141
{
@@ -44,7 +44,17 @@ public OctreeQuantizer(bool dither)
4444
/// <summary>
4545
/// Initializes a new instance of the <see cref="OctreeQuantizer"/> class.
4646
/// </summary>
47-
/// <param name="diffuser">The error diffusion algorithm, if any, to apply to the output image</param>
47+
/// <param name="maxColors">The maximum number of colors to hold in the color palette.</param>
48+
/// <param name="dither">Whether to apply dithering to the output image.</param>
49+
public OctreeQuantizer(bool dither, int maxColors)
50+
: this(GetDiffuser(dither), maxColors)
51+
{
52+
}
53+
54+
/// <summary>
55+
/// Initializes a new instance of the <see cref="OctreeQuantizer"/> class.
56+
/// </summary>
57+
/// <param name="diffuser">The error diffusion algorithm, if any, to apply to the output image.</param>
4858
public OctreeQuantizer(IErrorDiffuser diffuser)
4959
: this(diffuser, QuantizerConstants.MaxColors)
5060
{
@@ -53,8 +63,8 @@ public OctreeQuantizer(IErrorDiffuser diffuser)
5363
/// <summary>
5464
/// Initializes a new instance of the <see cref="OctreeQuantizer"/> class.
5565
/// </summary>
56-
/// <param name="diffuser">The error diffusion algorithm, if any, to apply to the output image</param>
57-
/// <param name="maxColors">The maximum number of colors to hold in the color palette</param>
66+
/// <param name="diffuser">The error diffusion algorithm, if any, to apply to the output image.</param>
67+
/// <param name="maxColors">The maximum number of colors to hold in the color palette.</param>
5868
public OctreeQuantizer(IErrorDiffuser diffuser, int maxColors)
5969
{
6070
this.Diffuser = diffuser;

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

Lines changed: 68 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22
// Licensed under the Apache License, Version 2.0.
33

44
using System.IO;
5+
56
using SixLabors.ImageSharp.Formats.Bmp;
67
using SixLabors.ImageSharp.Metadata;
78
using SixLabors.ImageSharp.PixelFormats;
89
using SixLabors.ImageSharp.Processing;
10+
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
911

1012
using Xunit;
1113
using Xunit.Abstractions;
@@ -110,11 +112,7 @@ public void Encode_WorksWithDifferentSizes<TPixel>(TestImageProvider<TPixel> pro
110112
[WithFile(Bit32Rgba, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Pixel32)]
111113
[WithFile(WinBmpv4, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Pixel32)]
112114
[WithFile(WinBmpv5, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Pixel32)]
113-
// WinBmpv3 is a 24 bits per pixel image
114-
[WithFile(WinBmpv3, PixelTypes.Rgb24, BmpBitsPerPixel.Pixel24)]
115-
[WithFile(Rgb16, PixelTypes.Bgra5551, BmpBitsPerPixel.Pixel16)]
116-
[WithFile(Bit16, PixelTypes.Bgra5551, BmpBitsPerPixel.Pixel16)]
117-
public void Encode_WithV3Header_Works<TPixel>(TestImageProvider<TPixel> provider, BmpBitsPerPixel bitsPerPixel)
115+
public void Encode_32Bit_WithV3Header_Works<TPixel>(TestImageProvider<TPixel> provider, BmpBitsPerPixel bitsPerPixel)
118116
// if supportTransparency is false, a v3 bitmap header will be written
119117
where TPixel : struct, IPixel<TPixel> => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: false);
120118

@@ -123,32 +121,92 @@ public void Encode_WithV3Header_Works<TPixel>(TestImageProvider<TPixel> provider
123121
[WithFile(Bit32Rgba, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Pixel32)]
124122
[WithFile(WinBmpv4, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Pixel32)]
125123
[WithFile(WinBmpv5, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Pixel32)]
124+
public void Encode_32Bit_WithV4Header_Works<TPixel>(TestImageProvider<TPixel> provider, BmpBitsPerPixel bitsPerPixel)
125+
where TPixel : struct, IPixel<TPixel> => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true);
126+
127+
[Theory]
128+
// WinBmpv3 is a 24 bits per pixel image
129+
[WithFile(WinBmpv3, PixelTypes.Rgb24, BmpBitsPerPixel.Pixel24)]
130+
[WithFile(F, PixelTypes.Rgb24, BmpBitsPerPixel.Pixel24)]
131+
public void Encode_24Bit_WithV3Header_Works<TPixel>(TestImageProvider<TPixel> provider, BmpBitsPerPixel bitsPerPixel)
132+
where TPixel : struct, IPixel<TPixel> => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: false);
133+
134+
[Theory]
126135
[WithFile(WinBmpv3, PixelTypes.Rgb24, BmpBitsPerPixel.Pixel24)]
136+
[WithFile(F, PixelTypes.Rgb24, BmpBitsPerPixel.Pixel24)]
137+
public void Encode_24Bit_WithV4Header_Works<TPixel>(TestImageProvider<TPixel> provider, BmpBitsPerPixel bitsPerPixel)
138+
where TPixel : struct, IPixel<TPixel> => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true);
139+
140+
141+
[Theory]
142+
[WithFile(Rgb16, PixelTypes.Bgra5551, BmpBitsPerPixel.Pixel16)]
143+
[WithFile(Bit16, PixelTypes.Bgra5551, BmpBitsPerPixel.Pixel16)]
144+
public void Encode_16Bit_WithV3Header_Works<TPixel>(TestImageProvider<TPixel> provider, BmpBitsPerPixel bitsPerPixel)
145+
where TPixel : struct, IPixel<TPixel> => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: false);
146+
147+
[Theory]
127148
[WithFile(Rgb16, PixelTypes.Bgra5551, BmpBitsPerPixel.Pixel16)]
128149
[WithFile(Bit16, PixelTypes.Bgra5551, BmpBitsPerPixel.Pixel16)]
129-
public void Encode_WithV4Header_Works<TPixel>(TestImageProvider<TPixel> provider, BmpBitsPerPixel bitsPerPixel)
150+
public void Encode_16Bit_WithV4Header_Works<TPixel>(TestImageProvider<TPixel> provider, BmpBitsPerPixel bitsPerPixel)
151+
where TPixel : struct, IPixel<TPixel> => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true);
152+
153+
[Theory]
154+
[WithFile(WinBmpv5, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel8)]
155+
[WithFile(Bit8Palette4, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel8)]
156+
public void Encode_8Bit_WithV3Header_Works<TPixel>(TestImageProvider<TPixel> provider, BmpBitsPerPixel bitsPerPixel)
157+
where TPixel : struct, IPixel<TPixel> => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: false);
158+
159+
[Theory]
160+
[WithFile(WinBmpv5, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel8)]
161+
[WithFile(Bit8Palette4, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel8)]
162+
public void Encode_8Bit_WithV4Header_Works<TPixel>(TestImageProvider<TPixel> provider, BmpBitsPerPixel bitsPerPixel)
130163
where TPixel : struct, IPixel<TPixel> => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true);
131164

165+
[Theory]
166+
[WithFile(Bit8Gs, PixelTypes.Gray8, BmpBitsPerPixel.Pixel8)]
167+
public void Encode_8BitGray_WithV3Header_Works<TPixel>(TestImageProvider<TPixel> provider, BmpBitsPerPixel bitsPerPixel)
168+
where TPixel : struct, IPixel<TPixel> =>
169+
TestBmpEncoderCore(
170+
provider,
171+
bitsPerPixel,
172+
supportTransparency: false,
173+
ImageComparer.TolerantPercentage(0.01f));
174+
175+
[Theory]
176+
[WithFile(Bit8Gs, PixelTypes.Gray8, BmpBitsPerPixel.Pixel8)]
177+
public void Encode_8BitGray_WithV4Header_Works<TPixel>(TestImageProvider<TPixel> provider, BmpBitsPerPixel bitsPerPixel)
178+
where TPixel : struct, IPixel<TPixel> =>
179+
TestBmpEncoderCore(
180+
provider,
181+
bitsPerPixel,
182+
supportTransparency: true,
183+
ImageComparer.TolerantPercentage(0.01f));
184+
132185
[Theory]
133186
[WithFile(TestImages.Png.GrayAlpha2BitInterlaced, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel32)]
187+
[WithFile(Bit32Rgba, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel32)]
134188
public void Encode_PreservesAlpha<TPixel>(TestImageProvider<TPixel> provider, BmpBitsPerPixel bitsPerPixel)
135189
where TPixel : struct, IPixel<TPixel> => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true);
136190

137-
private static void TestBmpEncoderCore<TPixel>(TestImageProvider<TPixel> provider, BmpBitsPerPixel bitsPerPixel, bool supportTransparency = true)
191+
private static void TestBmpEncoderCore<TPixel>(
192+
TestImageProvider<TPixel> provider,
193+
BmpBitsPerPixel bitsPerPixel,
194+
bool supportTransparency = true,
195+
ImageComparer customComparer = null)
138196
where TPixel : struct, IPixel<TPixel>
139197
{
140198
using (Image<TPixel> image = provider.GetImage())
141199
{
142-
// There is no alpha in bmp with 24 bits per pixels, so the reference image will be made opaque.
143-
if (bitsPerPixel == BmpBitsPerPixel.Pixel24)
200+
// There is no alpha in bmp with less then 32 bits per pixels, so the reference image will be made opaque.
201+
if (bitsPerPixel != BmpBitsPerPixel.Pixel32)
144202
{
145203
image.Mutate(c => c.MakeOpaque());
146204
}
147205

148206
var encoder = new BmpEncoder { BitsPerPixel = bitsPerPixel, SupportTransparency = supportTransparency };
149207

150208
// Does DebugSave & load reference CompareToReferenceInput():
151-
image.VerifyEncoder(provider, "bmp", bitsPerPixel, encoder);
209+
image.VerifyEncoder(provider, "bmp", bitsPerPixel, encoder, customComparer);
152210
}
153211
}
154212
}

tests/ImageSharp.Tests/TestImages.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,7 @@ public static class Bmp
236236
public const string Bit1Pal1 = "Bmp/pal1p1.bmp";
237237
public const string Bit4 = "Bmp/pal4.bmp";
238238
public const string Bit8 = "Bmp/test8.bmp";
239+
public const string Bit8Gs = "Bmp/pal8gs.bmp";
239240
public const string Bit8Inverted = "Bmp/test8-inverted.bmp";
240241
public const string Bit16 = "Bmp/test16.bmp";
241242
public const string Bit16Inverted = "Bmp/test16-inverted.bmp";

tests/ImageSharp.Tests/TestUtilities/PixelTypes.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ public enum PixelTypes
6060

6161
Bgra5551 = 1 << 22,
6262

63+
Gray8 = 1 << 23,
64+
6365
// TODO: Add multi-flag entries by rules defined in PackedPixelConverterHelper
6466

6567
// "All" is handled as a separate, individual case instead of using bitwise OR

tests/Images/Input/Bmp/pal8gs.bmp

9.04 KB
Binary file not shown.

0 commit comments

Comments
 (0)