// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Quantization; namespace SixLabors.ImageSharp.Tests.Quantization; public class WuQuantizerTests { [Fact] public void SinglePixelOpaque() { Configuration config = Configuration.Default; WuQuantizer quantizer = new(new QuantizerOptions { Dither = null }); using Image image = new(config, 1, 1, Color.Black.ToPixel()); ImageFrame frame = image.Frames.RootFrame; using IQuantizer frameQuantizer = quantizer.CreatePixelSpecificQuantizer(config); using IndexedImageFrame result = frameQuantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds); Assert.Equal(1, result.Palette.Length); Assert.Equal(1, result.Width); Assert.Equal(1, result.Height); Assert.Equal(Color.Black, Color.FromPixel(result.Palette.Span[0])); Assert.Equal(0, result.DangerousGetRowSpan(0)[0]); } [Fact] public void SinglePixelTransparent() { Configuration config = Configuration.Default; WuQuantizer quantizer = new(new QuantizerOptions { Dither = null }); using Image image = new(config, 1, 1, default(Rgba32)); ImageFrame frame = image.Frames.RootFrame; using IQuantizer frameQuantizer = quantizer.CreatePixelSpecificQuantizer(config); using IndexedImageFrame result = frameQuantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds); Assert.Equal(1, result.Palette.Length); Assert.Equal(1, result.Width); Assert.Equal(1, result.Height); Assert.Equal(default, result.Palette.Span[0]); Assert.Equal(0, result.DangerousGetRowSpan(0)[0]); } [Fact] public void GrayScale() => TestScale(c => new Rgba32(c, c, c, 128)); [Fact] public void RedScale() => TestScale(c => new Rgba32(c, 0, 0, 128)); [Fact] public void GreenScale() => TestScale(c => new Rgba32(0, c, 0, 128)); [Fact] public void BlueScale() => TestScale(c => new Rgba32(0, 0, c, 128)); [Fact] public void AlphaScale() => TestScale(c => new Rgba32(0, 0, 0, c)); [Fact] public void Palette256() { using Image image = new(1, 256); for (int i = 0; i < 256; i++) { byte r = (byte)((i % 4) * 85); byte g = (byte)(((i / 4) % 4) * 85); byte b = (byte)(((i / 16) % 4) * 85); byte a = (byte)((i / 64) * 85); image[0, i] = new Rgba32(r, g, b, a); } Configuration config = Configuration.Default; WuQuantizer quantizer = new(new QuantizerOptions { Dither = null, TransparencyThreshold = 0 }); ImageFrame frame = image.Frames.RootFrame; using IQuantizer frameQuantizer = quantizer.CreatePixelSpecificQuantizer(config); using IndexedImageFrame result = frameQuantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds); Assert.Equal(256, result.Palette.Length); Assert.Equal(1, result.Width); Assert.Equal(256, result.Height); using Image actualImage = new(1, 256); actualImage.ProcessPixelRows(accessor => { ReadOnlySpan paletteSpan = result.Palette.Span; int paletteCount = paletteSpan.Length - 1; for (int y = 0; y < accessor.Height; y++) { Span row = accessor.GetRowSpan(y); ReadOnlySpan quantizedPixelSpan = result.DangerousGetRowSpan(y); for (int x = 0; x < accessor.Width; x++) { row[x] = paletteSpan[Math.Min(paletteCount, quantizedPixelSpan[x])]; } } }); image.ProcessPixelRows(actualImage, static (imageAccessor, actualImageAccessor) => { for (int y = 0; y < imageAccessor.Height; y++) { Assert.True(imageAccessor.GetRowSpan(y).SequenceEqual(actualImageAccessor.GetRowSpan(y))); } }); } [Theory] [WithFile(TestImages.Png.LowColorVariance, PixelTypes.Rgba32)] public void LowVariance(TestImageProvider provider) where TPixel : unmanaged, IPixel { // See https://github.com/SixLabors/ImageSharp/issues/866 using Image image = provider.GetImage(); Configuration config = Configuration.Default; WuQuantizer quantizer = new(new QuantizerOptions { Dither = null }); ImageFrame frame = image.Frames.RootFrame; using IQuantizer frameQuantizer = quantizer.CreatePixelSpecificQuantizer(config); using IndexedImageFrame result = frameQuantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds); Assert.Equal(48, result.Palette.Length); } private static void TestScale(Func pixelBuilder) { using Image image = new(1, 256); using Image expectedImage = new(1, 256); using Image actualImage = new(1, 256); for (int i = 0; i < 256; i++) { byte c = (byte)i; image[0, i] = pixelBuilder.Invoke(c); } for (int i = 0; i < 256; i++) { byte c = (byte)((i & ~7) + 4); expectedImage[0, i] = pixelBuilder.Invoke(c); } Configuration config = Configuration.Default; WuQuantizer quantizer = new(new QuantizerOptions { Dither = null, TransparencyThreshold = 0 }); ImageFrame frame = image.Frames.RootFrame; using (IQuantizer frameQuantizer = quantizer.CreatePixelSpecificQuantizer(config)) using (IndexedImageFrame result = frameQuantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds)) { Assert.Equal(4 * 8, result.Palette.Length); Assert.Equal(1, result.Width); Assert.Equal(256, result.Height); actualImage.ProcessPixelRows(accessor => { ReadOnlySpan paletteSpan = result.Palette.Span; int paletteCount = paletteSpan.Length - 1; for (int y = 0; y < accessor.Height; y++) { Span row = accessor.GetRowSpan(y); ReadOnlySpan quantizedPixelSpan = result.DangerousGetRowSpan(y); for (int x = 0; x < accessor.Width; x++) { row[x] = paletteSpan[Math.Min(paletteCount, quantizedPixelSpan[x])]; } } }); } expectedImage.ProcessPixelRows(actualImage, static (expectedAccessor, actualAccessor) => { for (int y = 0; y < expectedAccessor.Height; y++) { Assert.True(expectedAccessor.GetRowSpan(y).SequenceEqual(actualAccessor.GetRowSpan(y))); } }); } }