Skip to content

Commit 07c16a5

Browse files
Merge pull request #1114 from SixLabors/js/quantization-tweaks
Fix quantization and dithering.
2 parents dfa34e9 + 429872d commit 07c16a5

File tree

96 files changed

+2584
-2981
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

96 files changed

+2584
-2981
lines changed

src/ImageSharp/Advanced/AotCompilerTools.cs

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

4+
using System;
45
using System.Diagnostics.CodeAnalysis;
56
using System.Numerics;
67
using System.Runtime.CompilerServices;
78
using SixLabors.ImageSharp.Formats;
89
using SixLabors.ImageSharp.Formats.Jpeg.Components;
910
using SixLabors.ImageSharp.PixelFormats;
11+
using SixLabors.ImageSharp.Processing;
1012
using SixLabors.ImageSharp.Processing.Processors.Dithering;
1113
using SixLabors.ImageSharp.Processing.Processors.Quantization;
1214

@@ -82,6 +84,7 @@ private static void Seed<TPixel>()
8284
// This is we actually call all the individual methods you need to seed.
8385
AotCompileOctreeQuantizer<TPixel>();
8486
AotCompileWuQuantizer<TPixel>();
87+
AotCompilePaletteQuantizer<TPixel>();
8588
AotCompileDithering<TPixel>();
8689
AotCompilePixelOperations<TPixel>();
8790

@@ -109,9 +112,10 @@ private static void Seed<TPixel>()
109112
private static void AotCompileOctreeQuantizer<TPixel>()
110113
where TPixel : struct, IPixel<TPixel>
111114
{
112-
using (var test = new OctreeFrameQuantizer<TPixel>(Configuration.Default, new OctreeQuantizer(false)))
115+
using (var test = new OctreeFrameQuantizer<TPixel>(Configuration.Default, new OctreeQuantizer().Options))
113116
{
114-
test.AotGetPalette();
117+
var frame = new ImageFrame<TPixel>(Configuration.Default, 1, 1);
118+
test.QuantizeFrame(frame, frame.Bounds());
115119
}
116120
}
117121

@@ -122,10 +126,24 @@ private static void AotCompileOctreeQuantizer<TPixel>()
122126
private static void AotCompileWuQuantizer<TPixel>()
123127
where TPixel : struct, IPixel<TPixel>
124128
{
125-
using (var test = new WuFrameQuantizer<TPixel>(Configuration.Default, new WuQuantizer(false)))
129+
using (var test = new WuFrameQuantizer<TPixel>(Configuration.Default, new WuQuantizer().Options))
126130
{
127-
test.QuantizeFrame(new ImageFrame<TPixel>(Configuration.Default, 1, 1));
128-
test.AotGetPalette();
131+
var frame = new ImageFrame<TPixel>(Configuration.Default, 1, 1);
132+
test.QuantizeFrame(frame, frame.Bounds());
133+
}
134+
}
135+
136+
/// <summary>
137+
/// This method pre-seeds the PaletteQuantizer in the AoT compiler for iOS.
138+
/// </summary>
139+
/// <typeparam name="TPixel">The pixel format.</typeparam>
140+
private static void AotCompilePaletteQuantizer<TPixel>()
141+
where TPixel : struct, IPixel<TPixel>
142+
{
143+
using (var test = (PaletteFrameQuantizer<TPixel>)new PaletteQuantizer(Array.Empty<Color>()).CreateFrameQuantizer<TPixel>(Configuration.Default))
144+
{
145+
var frame = new ImageFrame<TPixel>(Configuration.Default, 1, 1);
146+
test.QuantizeFrame(frame, frame.Bounds());
129147
}
130148
}
131149

@@ -136,11 +154,13 @@ private static void AotCompileWuQuantizer<TPixel>()
136154
private static void AotCompileDithering<TPixel>()
137155
where TPixel : struct, IPixel<TPixel>
138156
{
139-
var test = new FloydSteinbergDiffuser();
157+
ErrorDither errorDither = ErrorDither.FloydSteinberg;
158+
OrderedDither orderedDither = OrderedDither.Bayer2x2;
140159
TPixel pixel = default;
141160
using (var image = new ImageFrame<TPixel>(Configuration.Default, 1, 1))
142161
{
143-
test.Dither(image, pixel, pixel, 0, 0, 0, 0, 0);
162+
errorDither.Dither(image, image.Bounds(), pixel, pixel, 0, 0, 0);
163+
orderedDither.Dither(pixel, 0, 0, 0, 0);
144164
}
145165
}
146166

src/ImageSharp/Color/Color.NamedColors.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ namespace SixLabors.ImageSharp
88
{
99
/// <content>
1010
/// Contains static named color values.
11+
/// <see href="https://www.w3.org/TR/css-color-3/"/>
1112
/// </content>
1213
public readonly partial struct Color
1314
{
@@ -719,9 +720,9 @@ public readonly partial struct Color
719720
public static readonly Color Tomato = FromRgba(255, 99, 71, 255);
720721

721722
/// <summary>
722-
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #FFFFFF.
723+
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #00000000.
723724
/// </summary>
724-
public static readonly Color Transparent = FromRgba(255, 255, 255, 0);
725+
public static readonly Color Transparent = FromRgba(0, 0, 0, 0);
725726

726727
/// <summary>
727728
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #40E0D0.

src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs

Lines changed: 27 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
using SixLabors.ImageSharp.Memory;
1212
using SixLabors.ImageSharp.Metadata;
1313
using SixLabors.ImageSharp.PixelFormats;
14+
using SixLabors.ImageSharp.Processing;
1415
using SixLabors.ImageSharp.Processing.Processors.Quantization;
1516

1617
namespace SixLabors.ImageSharp.Formats.Bmp
@@ -87,7 +88,7 @@ public BmpEncoderCore(IBmpEncoderOptions options, MemoryAllocator memoryAllocato
8788
this.memoryAllocator = memoryAllocator;
8889
this.bitsPerPixel = options.BitsPerPixel;
8990
this.writeV4Header = options.SupportTransparency;
90-
this.quantizer = options.Quantizer ?? new OctreeQuantizer(dither: true, maxColors: 256);
91+
this.quantizer = options.Quantizer ?? KnownQuantizers.Octree;
9192
}
9293

9394
/// <summary>
@@ -335,36 +336,36 @@ private void Write8Bit<TPixel>(Stream stream, ImageFrame<TPixel> image)
335336
private void Write8BitColor<TPixel>(Stream stream, ImageFrame<TPixel> image, Span<byte> colorPalette)
336337
where TPixel : struct, IPixel<TPixel>
337338
{
338-
using (IQuantizedFrame<TPixel> quantized = this.quantizer.CreateFrameQuantizer<TPixel>(this.configuration, 256).QuantizeFrame(image))
339+
using IFrameQuantizer<TPixel> quantizer = this.quantizer.CreateFrameQuantizer<TPixel>(this.configuration);
340+
using QuantizedFrame<TPixel> quantized = quantizer.QuantizeFrame(image, image.Bounds());
341+
342+
ReadOnlySpan<TPixel> quantizedColors = quantized.Palette.Span;
343+
var color = default(Rgba32);
344+
345+
// TODO: Use bulk conversion here for better perf
346+
int idx = 0;
347+
foreach (TPixel quantizedColor in quantizedColors)
339348
{
340-
ReadOnlySpan<TPixel> quantizedColors = quantized.Palette.Span;
341-
var color = default(Rgba32);
349+
quantizedColor.ToRgba32(ref color);
350+
colorPalette[idx] = color.B;
351+
colorPalette[idx + 1] = color.G;
352+
colorPalette[idx + 2] = color.R;
342353

343-
// TODO: Use bulk conversion here for better perf
344-
int idx = 0;
345-
foreach (TPixel quantizedColor in quantizedColors)
346-
{
347-
quantizedColor.ToRgba32(ref color);
348-
colorPalette[idx] = color.B;
349-
colorPalette[idx + 1] = color.G;
350-
colorPalette[idx + 2] = color.R;
351-
352-
// Padding byte, always 0.
353-
colorPalette[idx + 3] = 0;
354-
idx += 4;
355-
}
354+
// Padding byte, always 0.
355+
colorPalette[idx + 3] = 0;
356+
idx += 4;
357+
}
358+
359+
stream.Write(colorPalette);
356360

357-
stream.Write(colorPalette);
361+
for (int y = image.Height - 1; y >= 0; y--)
362+
{
363+
ReadOnlySpan<byte> pixelSpan = quantized.GetRowSpan(y);
364+
stream.Write(pixelSpan);
358365

359-
for (int y = image.Height - 1; y >= 0; y--)
366+
for (int i = 0; i < this.padding; i++)
360367
{
361-
ReadOnlySpan<byte> pixelSpan = quantized.GetRowSpan(y);
362-
stream.Write(pixelSpan);
363-
364-
for (int i = 0; i < this.padding; i++)
365-
{
366-
stream.WriteByte(0);
367-
}
368+
stream.WriteByte(0);
368369
}
369370
}
370371
}

src/ImageSharp/Formats/Gif/GifEncoderCore.cs

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66
using System.IO;
77
using System.Runtime.CompilerServices;
88
using System.Runtime.InteropServices;
9-
10-
using SixLabors.ImageSharp.Advanced;
119
using SixLabors.ImageSharp.Memory;
1210
using SixLabors.ImageSharp.Metadata;
1311
using SixLabors.ImageSharp.PixelFormats;
@@ -28,7 +26,7 @@ internal sealed class GifEncoderCore
2826
/// <summary>
2927
/// Configuration bound to the encoding operation.
3028
/// </summary>
31-
private Configuration configuration;
29+
private readonly Configuration configuration;
3230

3331
/// <summary>
3432
/// A reusable buffer used to reduce allocations.
@@ -81,10 +79,10 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream)
8179
bool useGlobalTable = this.colorTableMode == GifColorTableMode.Global;
8280

8381
// Quantize the image returning a palette.
84-
IQuantizedFrame<TPixel> quantized;
82+
QuantizedFrame<TPixel> quantized;
8583
using (IFrameQuantizer<TPixel> frameQuantizer = this.quantizer.CreateFrameQuantizer<TPixel>(this.configuration))
8684
{
87-
quantized = frameQuantizer.QuantizeFrame(image.Frames.RootFrame);
85+
quantized = frameQuantizer.QuantizeFrame(image.Frames.RootFrame, image.Bounds());
8886
}
8987

9088
// Get the number of bits.
@@ -127,7 +125,7 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream)
127125
stream.WriteByte(GifConstants.EndIntroducer);
128126
}
129127

130-
private void EncodeGlobal<TPixel>(Image<TPixel> image, IQuantizedFrame<TPixel> quantized, int transparencyIndex, Stream stream)
128+
private void EncodeGlobal<TPixel>(Image<TPixel> image, QuantizedFrame<TPixel> quantized, int transparencyIndex, Stream stream)
131129
where TPixel : struct, IPixel<TPixel>
132130
{
133131
for (int i = 0; i < image.Frames.Count; i++)
@@ -144,19 +142,16 @@ private void EncodeGlobal<TPixel>(Image<TPixel> image, IQuantizedFrame<TPixel> q
144142
}
145143
else
146144
{
147-
using (IFrameQuantizer<TPixel> paletteFrameQuantizer =
148-
new PaletteFrameQuantizer<TPixel>(this.configuration, this.quantizer.Diffuser, quantized.Palette))
145+
using (var paletteFrameQuantizer = new PaletteFrameQuantizer<TPixel>(this.configuration, this.quantizer.Options, quantized.Palette))
146+
using (QuantizedFrame<TPixel> paletteQuantized = paletteFrameQuantizer.QuantizeFrame(frame, frame.Bounds()))
149147
{
150-
using (IQuantizedFrame<TPixel> paletteQuantized = paletteFrameQuantizer.QuantizeFrame(frame))
151-
{
152-
this.WriteImageData(paletteQuantized, stream);
153-
}
148+
this.WriteImageData(paletteQuantized, stream);
154149
}
155150
}
156151
}
157152
}
158153

159-
private void EncodeLocal<TPixel>(Image<TPixel> image, IQuantizedFrame<TPixel> quantized, Stream stream)
154+
private void EncodeLocal<TPixel>(Image<TPixel> image, QuantizedFrame<TPixel> quantized, Stream stream)
160155
where TPixel : struct, IPixel<TPixel>
161156
{
162157
ImageFrame<TPixel> previousFrame = null;
@@ -171,16 +166,23 @@ private void EncodeLocal<TPixel>(Image<TPixel> image, IQuantizedFrame<TPixel> qu
171166
if (previousFrame != null && previousMeta.ColorTableLength != frameMetadata.ColorTableLength
172167
&& frameMetadata.ColorTableLength > 0)
173168
{
174-
using (IFrameQuantizer<TPixel> frameQuantizer = this.quantizer.CreateFrameQuantizer<TPixel>(this.configuration, frameMetadata.ColorTableLength))
169+
var options = new QuantizerOptions
170+
{
171+
Dither = this.quantizer.Options.Dither,
172+
DitherScale = this.quantizer.Options.DitherScale,
173+
MaxColors = frameMetadata.ColorTableLength
174+
};
175+
176+
using (IFrameQuantizer<TPixel> frameQuantizer = this.quantizer.CreateFrameQuantizer<TPixel>(this.configuration, options))
175177
{
176-
quantized = frameQuantizer.QuantizeFrame(frame);
178+
quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds());
177179
}
178180
}
179181
else
180182
{
181183
using (IFrameQuantizer<TPixel> frameQuantizer = this.quantizer.CreateFrameQuantizer<TPixel>(this.configuration))
182184
{
183-
quantized = frameQuantizer.QuantizeFrame(frame);
185+
quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds());
184186
}
185187
}
186188
}
@@ -206,7 +208,7 @@ private void EncodeLocal<TPixel>(Image<TPixel> image, IQuantizedFrame<TPixel> qu
206208
/// <returns>
207209
/// The <see cref="int"/>.
208210
/// </returns>
209-
private int GetTransparentIndex<TPixel>(IQuantizedFrame<TPixel> quantized)
211+
private int GetTransparentIndex<TPixel>(QuantizedFrame<TPixel> quantized)
210212
where TPixel : struct, IPixel<TPixel>
211213
{
212214
// Transparent pixels are much more likely to be found at the end of a palette
@@ -435,7 +437,7 @@ private void WriteImageDescriptor<TPixel>(ImageFrame<TPixel> image, bool hasColo
435437
/// <typeparam name="TPixel">The pixel format.</typeparam>
436438
/// <param name="image">The <see cref="ImageFrame{TPixel}"/> to encode.</param>
437439
/// <param name="stream">The stream to write to.</param>
438-
private void WriteColorTable<TPixel>(IQuantizedFrame<TPixel> image, Stream stream)
440+
private void WriteColorTable<TPixel>(QuantizedFrame<TPixel> image, Stream stream)
439441
where TPixel : struct, IPixel<TPixel>
440442
{
441443
// The maximum number of colors for the bit depth
@@ -457,9 +459,9 @@ private void WriteColorTable<TPixel>(IQuantizedFrame<TPixel> image, Stream strea
457459
/// Writes the image pixel data to the stream.
458460
/// </summary>
459461
/// <typeparam name="TPixel">The pixel format.</typeparam>
460-
/// <param name="image">The <see cref="IQuantizedFrame{TPixel}"/> containing indexed pixels.</param>
462+
/// <param name="image">The <see cref="QuantizedFrame{TPixel}"/> containing indexed pixels.</param>
461463
/// <param name="stream">The stream to write to.</param>
462-
private void WriteImageData<TPixel>(IQuantizedFrame<TPixel> image, Stream stream)
464+
private void WriteImageData<TPixel>(QuantizedFrame<TPixel> image, Stream stream)
463465
where TPixel : struct, IPixel<TPixel>
464466
{
465467
using (var encoder = new LzwEncoder(this.memoryAllocator, (byte)this.bitDepth))

src/ImageSharp/Formats/Png/PngEncoderCore.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream)
146146
ImageMetadata metadata = image.Metadata;
147147
PngMetadata pngMetadata = metadata.GetPngMetadata();
148148
PngEncoderOptionsHelpers.AdjustOptions<TPixel>(this.options, pngMetadata, out this.use16Bit, out this.bytesPerPixel);
149-
IQuantizedFrame<TPixel> quantized = PngEncoderOptionsHelpers.CreateQuantizedFrame(this.options, image);
149+
QuantizedFrame<TPixel> quantized = PngEncoderOptionsHelpers.CreateQuantizedFrame(this.options, image);
150150
this.bitDepth = PngEncoderOptionsHelpers.CalculateBitDepth(this.options, image, quantized);
151151

152152
stream.Write(PngConstants.HeaderBytes, 0, PngConstants.HeaderBytes.Length);
@@ -371,7 +371,7 @@ private void CollectTPixelBytes<TPixel>(ReadOnlySpan<TPixel> rowSpan)
371371
/// <param name="rowSpan">The row span.</param>
372372
/// <param name="quantized">The quantized pixels. Can be null.</param>
373373
/// <param name="row">The row.</param>
374-
private void CollectPixelBytes<TPixel>(ReadOnlySpan<TPixel> rowSpan, IQuantizedFrame<TPixel> quantized, int row)
374+
private void CollectPixelBytes<TPixel>(ReadOnlySpan<TPixel> rowSpan, QuantizedFrame<TPixel> quantized, int row)
375375
where TPixel : struct, IPixel<TPixel>
376376
{
377377
switch (this.options.ColorType)
@@ -440,7 +440,7 @@ private IManagedByteBuffer FilterPixelBytes()
440440
/// <param name="quantized">The quantized pixels. Can be null.</param>
441441
/// <param name="row">The row.</param>
442442
/// <returns>The <see cref="IManagedByteBuffer"/></returns>
443-
private IManagedByteBuffer EncodePixelRow<TPixel>(ReadOnlySpan<TPixel> rowSpan, IQuantizedFrame<TPixel> quantized, int row)
443+
private IManagedByteBuffer EncodePixelRow<TPixel>(ReadOnlySpan<TPixel> rowSpan, QuantizedFrame<TPixel> quantized, int row)
444444
where TPixel : struct, IPixel<TPixel>
445445
{
446446
this.CollectPixelBytes(rowSpan, quantized, row);
@@ -546,7 +546,7 @@ private void WriteHeaderChunk(Stream stream)
546546
/// <typeparam name="TPixel">The pixel format.</typeparam>
547547
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
548548
/// <param name="quantized">The quantized frame.</param>
549-
private void WritePaletteChunk<TPixel>(Stream stream, IQuantizedFrame<TPixel> quantized)
549+
private void WritePaletteChunk<TPixel>(Stream stream, QuantizedFrame<TPixel> quantized)
550550
where TPixel : struct, IPixel<TPixel>
551551
{
552552
if (quantized == null)
@@ -783,7 +783,7 @@ private void WriteTransparencyChunk(Stream stream, PngMetadata pngMetadata)
783783
/// <param name="pixels">The image.</param>
784784
/// <param name="quantized">The quantized pixel data. Can be null.</param>
785785
/// <param name="stream">The stream.</param>
786-
private void WriteDataChunks<TPixel>(ImageFrame<TPixel> pixels, IQuantizedFrame<TPixel> quantized, Stream stream)
786+
private void WriteDataChunks<TPixel>(ImageFrame<TPixel> pixels, QuantizedFrame<TPixel> quantized, Stream stream)
787787
where TPixel : struct, IPixel<TPixel>
788788
{
789789
byte[] buffer;
@@ -881,7 +881,7 @@ private void AllocateExtBuffers()
881881
/// <param name="pixels">The pixels.</param>
882882
/// <param name="quantized">The quantized pixels span.</param>
883883
/// <param name="deflateStream">The deflate stream.</param>
884-
private void EncodePixels<TPixel>(ImageFrame<TPixel> pixels, IQuantizedFrame<TPixel> quantized, ZlibDeflateStream deflateStream)
884+
private void EncodePixels<TPixel>(ImageFrame<TPixel> pixels, QuantizedFrame<TPixel> quantized, ZlibDeflateStream deflateStream)
885885
where TPixel : struct, IPixel<TPixel>
886886
{
887887
int bytesPerScanline = this.CalculateScanlineLength(this.width);
@@ -960,7 +960,7 @@ private void EncodeAdam7Pixels<TPixel>(ImageFrame<TPixel> pixels, ZlibDeflateStr
960960
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
961961
/// <param name="quantized">The quantized.</param>
962962
/// <param name="deflateStream">The deflate stream.</param>
963-
private void EncodeAdam7IndexedPixels<TPixel>(IQuantizedFrame<TPixel> quantized, ZlibDeflateStream deflateStream)
963+
private void EncodeAdam7IndexedPixels<TPixel>(QuantizedFrame<TPixel> quantized, ZlibDeflateStream deflateStream)
964964
where TPixel : struct, IPixel<TPixel>
965965
{
966966
int width = quantized.Width;

0 commit comments

Comments
 (0)