From c15f2328ecfac66250d3ed64ca80c3e02a72c609 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 6 Dec 2017 00:58:44 +1100 Subject: [PATCH 01/12] Minor hashing optimizations --- src/ImageSharp/Formats/Png/Zlib/Adler32.cs | 45 ++++++++----------- src/ImageSharp/Formats/Png/Zlib/Crc32.cs | 35 ++++++--------- .../ImageSharp.Benchmarks/Image/DecodePng.cs | 20 ++++++--- 3 files changed, 47 insertions(+), 53 deletions(-) diff --git a/src/ImageSharp/Formats/Png/Zlib/Adler32.cs b/src/ImageSharp/Formats/Png/Zlib/Adler32.cs index 6841c1cb8..1cce90c0b 100644 --- a/src/ImageSharp/Formats/Png/Zlib/Adler32.cs +++ b/src/ImageSharp/Formats/Png/Zlib/Adler32.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Runtime.CompilerServices; namespace SixLabors.ImageSharp.Formats.Png.Zlib { @@ -74,9 +75,17 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib } /// - public long Value => this.checksum; + public long Value + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + return this.checksum; + } + } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Reset() { this.checksum = 1; @@ -88,6 +97,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib /// /// The data value to add. The high byte of the int is ignored. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Update(int value) { // We could make a length 1 byte array and call update again, but I @@ -102,6 +112,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Update(byte[] buffer) { if (buffer == null) @@ -113,32 +124,14 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Update(byte[] buffer, int offset, int count) { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } - - if (offset < 0) - { - throw new ArgumentOutOfRangeException(nameof(offset), "cannot be negative"); - } - - if (count < 0) - { - throw new ArgumentOutOfRangeException(nameof(count), "cannot be negative"); - } - - if (offset >= buffer.Length) - { - throw new ArgumentOutOfRangeException(nameof(offset), "not a valid index into buffer"); - } - - if (offset + count > buffer.Length) - { - throw new ArgumentOutOfRangeException(nameof(count), "exceeds buffer size"); - } + DebugGuard.NotNull(buffer, nameof(buffer)); + DebugGuard.MustBeGreaterThanOrEqualTo(offset, 0, nameof(offset)); + DebugGuard.MustBeGreaterThanOrEqualTo(count, 0, nameof(count)); + DebugGuard.MustBeLessThan(offset, buffer.Length, nameof(offset)); + DebugGuard.MustBeLessThanOrEqualTo(offset + count, buffer.Length, nameof(count)); // (By Per Bothner) uint s1 = this.checksum & 0xFFFF; @@ -169,4 +162,4 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib this.checksum = (s2 << 16) | s1; } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Png/Zlib/Crc32.cs b/src/ImageSharp/Formats/Png/Zlib/Crc32.cs index 14a29b7af..bd686f2b9 100644 --- a/src/ImageSharp/Formats/Png/Zlib/Crc32.cs +++ b/src/ImageSharp/Formats/Png/Zlib/Crc32.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Runtime.CompilerServices; namespace SixLabors.ImageSharp.Formats.Png.Zlib { @@ -108,18 +109,15 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib /// public long Value { - get - { - return this.crc; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.crc; - set - { - this.crc = (uint)value; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => this.crc = (uint)value; } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Reset() { this.crc = 0; @@ -129,6 +127,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib /// Updates the checksum with the given value. /// /// The byte is taken as the lower 8 bits of value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Update(int value) { this.crc ^= CrcSeed; @@ -137,6 +136,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Update(byte[] buffer) { if (buffer == null) @@ -148,22 +148,13 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Update(byte[] buffer, int offset, int count) { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } - - if (count < 0) - { - throw new ArgumentOutOfRangeException(nameof(count), "Count cannot be less than zero"); - } - - if (offset < 0 || offset + count > buffer.Length) - { - throw new ArgumentOutOfRangeException(nameof(offset)); - } + DebugGuard.NotNull(buffer, nameof(buffer)); + DebugGuard.MustBeGreaterThanOrEqualTo(count, 0, nameof(count)); + DebugGuard.MustBeGreaterThanOrEqualTo(offset, 0, nameof(offset)); + DebugGuard.MustBeLessThanOrEqualTo(offset + count, buffer.Length, nameof(count)); this.crc ^= CrcSeed; diff --git a/tests/ImageSharp.Benchmarks/Image/DecodePng.cs b/tests/ImageSharp.Benchmarks/Image/DecodePng.cs index b9a9d5cfa..eb13cf929 100644 --- a/tests/ImageSharp.Benchmarks/Image/DecodePng.cs +++ b/tests/ImageSharp.Benchmarks/Image/DecodePng.cs @@ -10,29 +10,39 @@ namespace SixLabors.ImageSharp.Benchmarks.Image using BenchmarkDotNet.Attributes; + using SixLabors.ImageSharp.Tests; + using CoreImage = ImageSharp.Image; using CoreSize = SixLabors.Primitives.Size; + [Config(typeof(Config.ShortClr))] public class DecodePng : BenchmarkBase { private byte[] pngBytes; + private string TestImageFullPath => Path.Combine( + TestEnvironment.InputImagesDirectoryFullPath, + this.TestImage); + + [Params(TestImages.Png.Splash)] + public string TestImage { get; set; } + [GlobalSetup] public void ReadImages() { if (this.pngBytes == null) { - this.pngBytes = File.ReadAllBytes("../ImageSharp.Tests/TestImages/Formats/Png/splash.png"); + this.pngBytes = File.ReadAllBytes(this.TestImageFullPath); } } [Benchmark(Baseline = true, Description = "System.Drawing Png")] public Size PngSystemDrawing() { - using (MemoryStream memoryStream = new MemoryStream(this.pngBytes)) + using (var memoryStream = new MemoryStream(this.pngBytes)) { - using (Image image = Image.FromStream(memoryStream)) + using (var image = Image.FromStream(memoryStream)) { return image.Size; } @@ -42,9 +52,9 @@ namespace SixLabors.ImageSharp.Benchmarks.Image [Benchmark(Description = "ImageSharp Png")] public CoreSize PngCore() { - using (MemoryStream memoryStream = new MemoryStream(this.pngBytes)) + using (var memoryStream = new MemoryStream(this.pngBytes)) { - using (Image image = CoreImage.Load(memoryStream)) + using (var image = CoreImage.Load(memoryStream)) { return new CoreSize(image.Width, image.Height); } From 02ba124667906c9ec81070373ee016d3076ba32b Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 7 Dec 2017 12:27:56 +1100 Subject: [PATCH 02/12] Use ref --- src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs b/src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs index 44311e080..d58a960b5 100644 --- a/src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs +++ b/src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs @@ -179,9 +179,8 @@ namespace SixLabors.ImageSharp.Quantizers float b = Volume(this.colorCube[k], this.vmb) / weight; float a = Volume(this.colorCube[k], this.vma) / weight; - var color = default(TPixel); + ref TPixel color = ref this.palette[k]; color.PackFromVector4(new Vector4(r, g, b, a) / 255F); - this.palette[k] = color; } } } From b219fda083b0ba0537e65e572e22f178e284c0f8 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 7 Dec 2017 14:00:29 +1100 Subject: [PATCH 03/12] Fix quantized output --- .../Quantizers/WuQuantizer{TPixel}.cs | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs b/src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs index d58a960b5..639786406 100644 --- a/src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs +++ b/src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs @@ -196,19 +196,21 @@ namespace SixLabors.ImageSharp.Quantizers var rgba = default(Rgba32); pixel.ToRgba32(ref rgba); - int r = rgba.R >> 2; // 8 - IndexBits - int g = rgba.G >> 2; - int b = rgba.B >> 2; - int a = rgba.A >> 5; // 8 - IndexAlphaBits - - int ind = GetPaletteIndex(r + 1, g + 1, b + 1, a + 1); - - this.vwt[ind]++; - this.vmr[ind] += r; - this.vmg[ind] += g; - this.vmb[ind] += b; - this.vma[ind] += a; - this.m2[ind] += (r * r) + (g * g) + (b * b) + (a * a); + int r = rgba.R >> (8 - IndexBits); + int g = rgba.G >> (8 - IndexBits); + int b = rgba.B >> (8 - IndexBits); + int a = rgba.A >> (8 - IndexAlphaBits); + + int index = GetPaletteIndex(r + 1, g + 1, b + 1, a + 1); + + this.vwt[index]++; + this.vmr[index] += rgba.R; + this.vmg[index] += rgba.G; + this.vmb[index] += rgba.B; + this.vma[index] += rgba.A; + + var vector = new Vector4(rgba.R, rgba.G, rgba.B, rgba.A); + this.m2[index] += Vector4.Dot(vector, vector); } /// @@ -617,14 +619,12 @@ namespace SixLabors.ImageSharp.Quantizers float halfA = baseA + Top(cube, direction, i, this.vma); float halfW = baseW + Top(cube, direction, i, this.vwt); - float temp; - if (MathF.Abs(halfW) < Constants.Epsilon) { continue; } - temp = ((halfR * halfR) + (halfG * halfG) + (halfB * halfB) + (halfA * halfA)) / halfW; + float temp = ((halfR * halfR) + (halfG * halfG) + (halfB * halfB) + (halfA * halfA)) / halfW; halfR = wholeR - halfR; halfG = wholeG - halfG; From a7ffeffcec5c1f410bb787f9b5dfd3ffecf73990 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 7 Dec 2017 14:22:23 +1100 Subject: [PATCH 04/12] Minor optimization --- src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs b/src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs index 639786406..629bc1431 100644 --- a/src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs +++ b/src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs @@ -174,13 +174,13 @@ namespace SixLabors.ImageSharp.Quantizers if (MathF.Abs(weight) > Constants.Epsilon) { - float r = Volume(this.colorCube[k], this.vmr) / weight; - float g = Volume(this.colorCube[k], this.vmg) / weight; - float b = Volume(this.colorCube[k], this.vmb) / weight; - float a = Volume(this.colorCube[k], this.vma) / weight; + float r = Volume(this.colorCube[k], this.vmr); + float g = Volume(this.colorCube[k], this.vmg); + float b = Volume(this.colorCube[k], this.vmb); + float a = Volume(this.colorCube[k], this.vma); ref TPixel color = ref this.palette[k]; - color.PackFromVector4(new Vector4(r, g, b, a) / 255F); + color.PackFromVector4(new Vector4(r, g, b, a) / weight / 255F); } } } From 31b4a6420d43b5f06ce31d124bf26c335ce2ade2 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 7 Dec 2017 14:35:51 +1100 Subject: [PATCH 05/12] Use struct for Box --- src/ImageSharp/Quantizers/Box.cs | 5 +- .../Quantizers/WuQuantizer{TPixel}.cs | 93 +++++++++---------- 2 files changed, 47 insertions(+), 51 deletions(-) diff --git a/src/ImageSharp/Quantizers/Box.cs b/src/ImageSharp/Quantizers/Box.cs index 4a1e17753..cd1936b65 100644 --- a/src/ImageSharp/Quantizers/Box.cs +++ b/src/ImageSharp/Quantizers/Box.cs @@ -5,9 +5,8 @@ namespace SixLabors.ImageSharp.Quantizers { /// /// Represents a box color cube. - /// TODO: This should be a struct for performance /// - internal sealed class Box + internal struct Box { /// /// Gets or sets the min red value, exclusive. @@ -54,4 +53,4 @@ namespace SixLabors.ImageSharp.Quantizers /// public int Volume { get; set; } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs b/src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs index 629bc1431..11d9bd5db 100644 --- a/src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs +++ b/src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs @@ -168,16 +168,16 @@ namespace SixLabors.ImageSharp.Quantizers this.palette = new TPixel[this.colors]; for (int k = 0; k < this.colors; k++) { - this.Mark(this.colorCube[k], (byte)k); + this.Mark(ref this.colorCube[k], (byte)k); - float weight = Volume(this.colorCube[k], this.vwt); + float weight = Volume(ref this.colorCube[k], this.vwt); if (MathF.Abs(weight) > Constants.Epsilon) { - float r = Volume(this.colorCube[k], this.vmr); - float g = Volume(this.colorCube[k], this.vmg); - float b = Volume(this.colorCube[k], this.vmb); - float a = Volume(this.colorCube[k], this.vma); + float r = Volume(ref this.colorCube[k], this.vmr); + float g = Volume(ref this.colorCube[k], this.vmg); + float b = Volume(ref this.colorCube[k], this.vmb); + float a = Volume(ref this.colorCube[k], this.vma); ref TPixel color = ref this.palette[k]; color.PackFromVector4(new Vector4(r, g, b, a) / weight / 255F); @@ -302,7 +302,7 @@ namespace SixLabors.ImageSharp.Quantizers /// The cube. /// The moment. /// The result. - private static float Volume(Box cube, long[] moment) + private static float Volume(ref Box cube, long[] moment) { return moment[GetPaletteIndex(cube.R1, cube.G1, cube.B1, cube.A1)] - moment[GetPaletteIndex(cube.R1, cube.G1, cube.B1, cube.A0)] @@ -329,7 +329,7 @@ namespace SixLabors.ImageSharp.Quantizers /// The direction. /// The moment. /// The result. - private static long Bottom(Box cube, int direction, long[] moment) + private static long Bottom(ref Box cube, int direction, long[] moment) { switch (direction) { @@ -390,7 +390,7 @@ namespace SixLabors.ImageSharp.Quantizers /// The position. /// The moment. /// The result. - private static long Top(Box cube, int direction, int position, long[] moment) + private static long Top(ref Box cube, int direction, int position, long[] moment) { switch (direction) { @@ -554,12 +554,12 @@ namespace SixLabors.ImageSharp.Quantizers /// /// The cube. /// The . - private float Variance(Box cube) + private float Variance(ref Box cube) { - float dr = Volume(cube, this.vmr); - float dg = Volume(cube, this.vmg); - float db = Volume(cube, this.vmb); - float da = Volume(cube, this.vma); + float dr = Volume(ref cube, this.vmr); + float dg = Volume(ref cube, this.vmg); + float db = Volume(ref cube, this.vmb); + float da = Volume(ref cube, this.vma); float xx = this.m2[GetPaletteIndex(cube.R1, cube.G1, cube.B1, cube.A1)] @@ -579,7 +579,8 @@ namespace SixLabors.ImageSharp.Quantizers - this.m2[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A1)] + this.m2[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A0)]; - return xx - (((dr * dr) + (dg * dg) + (db * db) + (da * da)) / Volume(cube, this.vwt)); + // TODO: Vector.Dot + return xx - (((dr * dr) + (dg * dg) + (db * db) + (da * da)) / Volume(ref cube, this.vwt)); } /// @@ -600,24 +601,24 @@ namespace SixLabors.ImageSharp.Quantizers /// The whole alpha. /// The whole weight. /// The . - private float Maximize(Box cube, int direction, int first, int last, out int cut, float wholeR, float wholeG, float wholeB, float wholeA, float wholeW) + private float Maximize(ref Box cube, int direction, int first, int last, out int cut, float wholeR, float wholeG, float wholeB, float wholeA, float wholeW) { - long baseR = Bottom(cube, direction, this.vmr); - long baseG = Bottom(cube, direction, this.vmg); - long baseB = Bottom(cube, direction, this.vmb); - long baseA = Bottom(cube, direction, this.vma); - long baseW = Bottom(cube, direction, this.vwt); + long baseR = Bottom(ref cube, direction, this.vmr); + long baseG = Bottom(ref cube, direction, this.vmg); + long baseB = Bottom(ref cube, direction, this.vmb); + long baseA = Bottom(ref cube, direction, this.vma); + long baseW = Bottom(ref cube, direction, this.vwt); float max = 0F; cut = -1; for (int i = first; i < last; i++) { - float halfR = baseR + Top(cube, direction, i, this.vmr); - float halfG = baseG + Top(cube, direction, i, this.vmg); - float halfB = baseB + Top(cube, direction, i, this.vmb); - float halfA = baseA + Top(cube, direction, i, this.vma); - float halfW = baseW + Top(cube, direction, i, this.vwt); + float halfR = baseR + Top(ref cube, direction, i, this.vmr); + float halfG = baseG + Top(ref cube, direction, i, this.vmg); + float halfB = baseB + Top(ref cube, direction, i, this.vmb); + float halfA = baseA + Top(ref cube, direction, i, this.vma); + float halfW = baseW + Top(ref cube, direction, i, this.vwt); if (MathF.Abs(halfW) < Constants.Epsilon) { @@ -655,18 +656,18 @@ namespace SixLabors.ImageSharp.Quantizers /// The first set. /// The second set. /// Returns a value indicating whether the box has been split. - private bool Cut(Box set1, Box set2) + private bool Cut(ref Box set1, ref Box set2) { - float wholeR = Volume(set1, this.vmr); - float wholeG = Volume(set1, this.vmg); - float wholeB = Volume(set1, this.vmb); - float wholeA = Volume(set1, this.vma); - float wholeW = Volume(set1, this.vwt); + float wholeR = Volume(ref set1, this.vmr); + float wholeG = Volume(ref set1, this.vmg); + float wholeB = Volume(ref set1, this.vmb); + float wholeA = Volume(ref set1, this.vma); + float wholeW = Volume(ref set1, this.vwt); - float maxr = this.Maximize(set1, 0, set1.R0 + 1, set1.R1, out int cutr, wholeR, wholeG, wholeB, wholeA, wholeW); - float maxg = this.Maximize(set1, 1, set1.G0 + 1, set1.G1, out int cutg, wholeR, wholeG, wholeB, wholeA, wholeW); - float maxb = this.Maximize(set1, 2, set1.B0 + 1, set1.B1, out int cutb, wholeR, wholeG, wholeB, wholeA, wholeW); - float maxa = this.Maximize(set1, 3, set1.A0 + 1, set1.A1, out int cuta, wholeR, wholeG, wholeB, wholeA, wholeW); + float maxr = this.Maximize(ref set1, 0, set1.R0 + 1, set1.R1, out int cutr, wholeR, wholeG, wholeB, wholeA, wholeW); + float maxg = this.Maximize(ref set1, 1, set1.G0 + 1, set1.G1, out int cutg, wholeR, wholeG, wholeB, wholeA, wholeW); + float maxb = this.Maximize(ref set1, 2, set1.B0 + 1, set1.B1, out int cutb, wholeR, wholeG, wholeB, wholeA, wholeW); + float maxa = this.Maximize(ref set1, 3, set1.A0 + 1, set1.A1, out int cuta, wholeR, wholeG, wholeB, wholeA, wholeW); int dir; @@ -743,7 +744,7 @@ namespace SixLabors.ImageSharp.Quantizers /// /// The cube. /// A label. - private void Mark(Box cube, byte label) + private void Mark(ref Box cube, byte label) { for (int r = cube.R0 + 1; r <= cube.R1; r++) { @@ -768,23 +769,19 @@ namespace SixLabors.ImageSharp.Quantizers this.colorCube = new Box[this.colors]; float[] vv = new float[this.colors]; - for (int i = 0; i < this.colors; i++) - { - this.colorCube[i] = new Box(); - } - - this.colorCube[0].R0 = this.colorCube[0].G0 = this.colorCube[0].B0 = this.colorCube[0].A0 = 0; - this.colorCube[0].R1 = this.colorCube[0].G1 = this.colorCube[0].B1 = IndexCount - 1; - this.colorCube[0].A1 = IndexAlphaCount - 1; + ref var cube = ref this.colorCube[0]; + cube.R0 = cube.G0 = cube.B0 = cube.A0 = 0; + cube.R1 = cube.G1 = cube.B1 = IndexCount - 1; + cube.A1 = IndexAlphaCount - 1; int next = 0; for (int i = 1; i < this.colors; i++) { - if (this.Cut(this.colorCube[next], this.colorCube[i])) + if (this.Cut(ref this.colorCube[next], ref this.colorCube[i])) { - vv[next] = this.colorCube[next].Volume > 1 ? this.Variance(this.colorCube[next]) : 0F; - vv[i] = this.colorCube[i].Volume > 1 ? this.Variance(this.colorCube[i]) : 0F; + vv[next] = this.colorCube[next].Volume > 1 ? this.Variance(ref this.colorCube[next]) : 0F; + vv[i] = this.colorCube[i].Volume > 1 ? this.Variance(ref this.colorCube[i]) : 0F; } else { From 5f734e18b8991488a933012365e79cfc90190e27 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 7 Dec 2017 14:59:14 +1100 Subject: [PATCH 06/12] Use Vector4.Dot where we can --- .../Quantizers/WuQuantizer{TPixel}.cs | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs b/src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs index 11d9bd5db..ba1a3c165 100644 --- a/src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs +++ b/src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs @@ -579,8 +579,8 @@ namespace SixLabors.ImageSharp.Quantizers - this.m2[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A1)] + this.m2[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A0)]; - // TODO: Vector.Dot - return xx - (((dr * dr) + (dg * dg) + (db * db) + (da * da)) / Volume(ref cube, this.vwt)); + var vector = new Vector4(dr, dg, db, da); + return xx - (Vector4.Dot(vector, vector) / Volume(ref cube, this.vwt)); } /// @@ -625,12 +625,9 @@ namespace SixLabors.ImageSharp.Quantizers continue; } - float temp = ((halfR * halfR) + (halfG * halfG) + (halfB * halfB) + (halfA * halfA)) / halfW; + var vector = new Vector4(halfR, halfG, halfB, halfA); + float temp = Vector4.Dot(vector, vector) / halfW; - halfR = wholeR - halfR; - halfG = wholeG - halfG; - halfB = wholeB - halfB; - halfA = wholeA - halfA; halfW = wholeW - halfW; if (MathF.Abs(halfW) < Constants.Epsilon) @@ -638,7 +635,14 @@ namespace SixLabors.ImageSharp.Quantizers continue; } - temp += ((halfR * halfR) + (halfG * halfG) + (halfB * halfB) + (halfA * halfA)) / halfW; + halfR = wholeR - halfR; + halfG = wholeG - halfG; + halfB = wholeB - halfB; + halfA = wholeA - halfA; + + vector = new Vector4(halfR, halfG, halfB, halfA); + + temp += Vector4.Dot(vector, vector) / halfW; if (temp > max) { From 4876a0087fb8e3b672b5a5f0b3089cf21ea0062c Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 7 Dec 2017 15:31:58 +1100 Subject: [PATCH 07/12] Use same ordering as original code Makes debugging a lot easier --- .../Quantizers/WuQuantizer{TPixel}.cs | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs b/src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs index ba1a3c165..740902168 100644 --- a/src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs +++ b/src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs @@ -334,7 +334,7 @@ namespace SixLabors.ImageSharp.Quantizers switch (direction) { // Red - case 0: + case 3: return -moment[GetPaletteIndex(cube.R0, cube.G1, cube.B1, cube.A1)] + moment[GetPaletteIndex(cube.R0, cube.G1, cube.B1, cube.A0)] + moment[GetPaletteIndex(cube.R0, cube.G1, cube.B0, cube.A1)] @@ -345,7 +345,7 @@ namespace SixLabors.ImageSharp.Quantizers + moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A0)]; // Green - case 1: + case 2: return -moment[GetPaletteIndex(cube.R1, cube.G0, cube.B1, cube.A1)] + moment[GetPaletteIndex(cube.R1, cube.G0, cube.B1, cube.A0)] + moment[GetPaletteIndex(cube.R1, cube.G0, cube.B0, cube.A1)] @@ -356,7 +356,7 @@ namespace SixLabors.ImageSharp.Quantizers + moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A0)]; // Blue - case 2: + case 1: return -moment[GetPaletteIndex(cube.R1, cube.G1, cube.B0, cube.A1)] + moment[GetPaletteIndex(cube.R1, cube.G1, cube.B0, cube.A0)] + moment[GetPaletteIndex(cube.R1, cube.G0, cube.B0, cube.A1)] @@ -367,7 +367,7 @@ namespace SixLabors.ImageSharp.Quantizers + moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A0)]; // Alpha - case 3: + case 0: return -moment[GetPaletteIndex(cube.R1, cube.G1, cube.B1, cube.A0)] + moment[GetPaletteIndex(cube.R1, cube.G1, cube.B0, cube.A0)] + moment[GetPaletteIndex(cube.R1, cube.G0, cube.B1, cube.A0)] @@ -395,7 +395,7 @@ namespace SixLabors.ImageSharp.Quantizers switch (direction) { // Red - case 0: + case 3: return moment[GetPaletteIndex(position, cube.G1, cube.B1, cube.A1)] - moment[GetPaletteIndex(position, cube.G1, cube.B1, cube.A0)] - moment[GetPaletteIndex(position, cube.G1, cube.B0, cube.A1)] @@ -406,7 +406,7 @@ namespace SixLabors.ImageSharp.Quantizers - moment[GetPaletteIndex(position, cube.G0, cube.B0, cube.A0)]; // Green - case 1: + case 2: return moment[GetPaletteIndex(cube.R1, position, cube.B1, cube.A1)] - moment[GetPaletteIndex(cube.R1, position, cube.B1, cube.A0)] - moment[GetPaletteIndex(cube.R1, position, cube.B0, cube.A1)] @@ -417,7 +417,7 @@ namespace SixLabors.ImageSharp.Quantizers - moment[GetPaletteIndex(cube.R0, position, cube.B0, cube.A0)]; // Blue - case 2: + case 1: return moment[GetPaletteIndex(cube.R1, cube.G1, position, cube.A1)] - moment[GetPaletteIndex(cube.R1, cube.G1, position, cube.A0)] - moment[GetPaletteIndex(cube.R1, cube.G0, position, cube.A1)] @@ -428,7 +428,7 @@ namespace SixLabors.ImageSharp.Quantizers - moment[GetPaletteIndex(cube.R0, cube.G0, position, cube.A0)]; // Alpha - case 3: + case 0: return moment[GetPaletteIndex(cube.R1, cube.G1, cube.B1, position)] - moment[GetPaletteIndex(cube.R1, cube.G1, cube.B0, position)] - moment[GetPaletteIndex(cube.R1, cube.G0, cube.B1, position)] @@ -668,16 +668,16 @@ namespace SixLabors.ImageSharp.Quantizers float wholeA = Volume(ref set1, this.vma); float wholeW = Volume(ref set1, this.vwt); - float maxr = this.Maximize(ref set1, 0, set1.R0 + 1, set1.R1, out int cutr, wholeR, wholeG, wholeB, wholeA, wholeW); - float maxg = this.Maximize(ref set1, 1, set1.G0 + 1, set1.G1, out int cutg, wholeR, wholeG, wholeB, wholeA, wholeW); - float maxb = this.Maximize(ref set1, 2, set1.B0 + 1, set1.B1, out int cutb, wholeR, wholeG, wholeB, wholeA, wholeW); - float maxa = this.Maximize(ref set1, 3, set1.A0 + 1, set1.A1, out int cuta, wholeR, wholeG, wholeB, wholeA, wholeW); + float maxr = this.Maximize(ref set1, 3, set1.R0 + 1, set1.R1, out int cutr, wholeR, wholeG, wholeB, wholeA, wholeW); + float maxg = this.Maximize(ref set1, 2, set1.G0 + 1, set1.G1, out int cutg, wholeR, wholeG, wholeB, wholeA, wholeW); + float maxb = this.Maximize(ref set1, 1, set1.B0 + 1, set1.B1, out int cutb, wholeR, wholeG, wholeB, wholeA, wholeW); + float maxa = this.Maximize(ref set1, 0, set1.A0 + 1, set1.A1, out int cuta, wholeR, wholeG, wholeB, wholeA, wholeW); int dir; if ((maxr >= maxg) && (maxr >= maxb) && (maxr >= maxa)) { - dir = 0; + dir = 3; if (cutr < 0) { @@ -686,15 +686,15 @@ namespace SixLabors.ImageSharp.Quantizers } else if ((maxg >= maxr) && (maxg >= maxb) && (maxg >= maxa)) { - dir = 1; + dir = 2; } else if ((maxb >= maxr) && (maxb >= maxg) && (maxb >= maxa)) { - dir = 2; + dir = 1; } else { - dir = 3; + dir = 0; } set2.R1 = set1.R1; @@ -705,7 +705,7 @@ namespace SixLabors.ImageSharp.Quantizers switch (dir) { // Red - case 0: + case 3: set2.R0 = set1.R1 = cutr; set2.G0 = set1.G0; set2.B0 = set1.B0; @@ -713,7 +713,7 @@ namespace SixLabors.ImageSharp.Quantizers break; // Green - case 1: + case 2: set2.G0 = set1.G1 = cutg; set2.R0 = set1.R0; set2.B0 = set1.B0; @@ -721,7 +721,7 @@ namespace SixLabors.ImageSharp.Quantizers break; // Blue - case 2: + case 1: set2.B0 = set1.B1 = cutb; set2.R0 = set1.R0; set2.G0 = set1.G0; @@ -729,7 +729,7 @@ namespace SixLabors.ImageSharp.Quantizers break; // Alpha - case 3: + case 0: set2.A0 = set1.A1 = cuta; set2.R0 = set1.R0; set2.G0 = set1.G0; From bf7c0163bb3079cb386cfab11387fadd59231d3c Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 7 Dec 2017 15:50:37 +1100 Subject: [PATCH 08/12] A little more ref --- src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs b/src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs index 740902168..cb9eb9b0e 100644 --- a/src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs +++ b/src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs @@ -782,10 +782,12 @@ namespace SixLabors.ImageSharp.Quantizers for (int i = 1; i < this.colors; i++) { - if (this.Cut(ref this.colorCube[next], ref this.colorCube[i])) + ref var nextCube = ref this.colorCube[next]; + ref var currentCube = ref this.colorCube[i]; + if (this.Cut(ref nextCube, ref currentCube)) { - vv[next] = this.colorCube[next].Volume > 1 ? this.Variance(ref this.colorCube[next]) : 0F; - vv[i] = this.colorCube[i].Volume > 1 ? this.Variance(ref this.colorCube[i]) : 0F; + vv[next] = nextCube.Volume > 1 ? this.Variance(ref nextCube) : 0F; + vv[i] = currentCube.Volume > 1 ? this.Variance(ref currentCube) : 0F; } else { @@ -805,7 +807,7 @@ namespace SixLabors.ImageSharp.Quantizers } } - if (temp <= 0.0) + if (temp <= 0F) { this.colors = i + 1; break; From d24decbae7b20976585c089e214720188ceb2e6f Mon Sep 17 00:00:00 2001 From: Dirk Lemstra Date: Fri, 8 Dec 2017 16:53:04 +0100 Subject: [PATCH 09/12] Removed invalid check reported in issue #394. --- src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegDecoderCore.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegDecoderCore.cs index e7ffaa9d1..61b18af55 100644 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegDecoderCore.cs @@ -688,7 +688,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort } int th = this.Temp[0] & 0x0f; - if (th > OrigHuffmanTree.MaxTh || (!this.IsProgressive && (th > 1))) + if (th > OrigHuffmanTree.MaxTh) { throw new ImageFormatException("Bad Th value"); } From bcd3371da019d61e9774d7ed0f19238bf6750a55 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 9 Dec 2017 14:29:11 +0100 Subject: [PATCH 10/12] add test for #394 + #395 --- tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs | 1 + tests/ImageSharp.Tests/TestImages.cs | 1 + tests/Images/External | 2 +- .../Jpg/issues/Issue394-MultiHuffmanBaseline-Speakers.jpg | 3 +++ 4 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 tests/Images/Input/Jpg/issues/Issue394-MultiHuffmanBaseline-Speakers.jpg diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index db42be284..d529bbaeb 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -42,6 +42,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg TestImages.Jpeg.Baseline.Jpeg444, TestImages.Jpeg.Baseline.Bad.BadEOF, TestImages.Jpeg.Baseline.Bad.ExifUndefType, + TestImages.Jpeg.Issues.MultiHuffmanBaseline394, }; public static string[] ProgressiveTestJpegs = diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index f34254239..1b5f0dbad 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -123,6 +123,7 @@ namespace SixLabors.ImageSharp.Tests public const string MissingFF00ProgressiveGirl159 = "Jpg/issues/Issue159-MissingFF00-Progressive-Girl.jpg"; public const string BadCoeffsProgressive178 = "Jpg/issues/Issue178-BadCoeffsProgressive-Lemon.jpg"; public const string BadZigZagProgressive385 = "Jpg/issues/Issue385-BadZigZag-Progressive.jpg"; + public const string MultiHuffmanBaseline394 = "Jpg/issues/Issue394-MultiHuffmanBaseline-Speakers.jpg"; } public static readonly string[] All = Baseline.All.Concat(Progressive.All).ToArray(); diff --git a/tests/Images/External b/tests/Images/External index ab7c90362..dc5479d00 160000 --- a/tests/Images/External +++ b/tests/Images/External @@ -1 +1 @@ -Subproject commit ab7c90362a4881c790c6f264f400dcf4b4dea0ce +Subproject commit dc5479d00b2312f691e6249b9f7765e2316d4a30 diff --git a/tests/Images/Input/Jpg/issues/Issue394-MultiHuffmanBaseline-Speakers.jpg b/tests/Images/Input/Jpg/issues/Issue394-MultiHuffmanBaseline-Speakers.jpg new file mode 100644 index 000000000..6e4dc0d0f --- /dev/null +++ b/tests/Images/Input/Jpg/issues/Issue394-MultiHuffmanBaseline-Speakers.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8e90cf157d8e2599598c1bea1b8e2bf262651b004cc6261d87bbb49bd6131034 +size 257401 From 554974f55faf95fa42752bd253d48bd3a6b20b26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Sun, 10 Dec 2017 21:25:23 +0100 Subject: [PATCH 11/12] Typo in ResamplingWeightedProcessor Removing duplicated "to". Contribution of the year I guess :D. --- .../Transforms/ResamplingWeightedProcessor.Weights.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs index 1169d2ead..61902c3a2 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs @@ -14,7 +14,7 @@ namespace SixLabors.ImageSharp.Processing.Processors internal abstract partial class ResamplingWeightedProcessor { /// - /// Points to a collection of of weights allocated in . + /// Points to a collection of weights allocated in . /// internal struct WeightsWindow { @@ -196,4 +196,4 @@ namespace SixLabors.ImageSharp.Processing.Processors } } } -} \ No newline at end of file +} From 828c7ba6afb844a0bd82c17f45bb4bf9650f550d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Sun, 10 Dec 2017 21:34:30 +0100 Subject: [PATCH 12/12] Fixing more typos in various comments --- src/ImageSharp/Memory/Buffer2DExtensions.cs | 2 +- .../Transforms/ResamplingWeightedProcessor.Weights.cs | 2 +- .../Processing/Processors/Transforms/ResizeProcessor.cs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Memory/Buffer2DExtensions.cs b/src/ImageSharp/Memory/Buffer2DExtensions.cs index 401003eed..ac5ab09db 100644 --- a/src/ImageSharp/Memory/Buffer2DExtensions.cs +++ b/src/ImageSharp/Memory/Buffer2DExtensions.cs @@ -70,7 +70,7 @@ namespace SixLabors.ImageSharp.Memory /// /// The element type /// The - /// The rectangel subarea + /// The rectangle subarea /// The public static BufferArea GetArea(this IBuffer2D buffer, Rectangle rectangle) where T : struct => new BufferArea(buffer, rectangle); diff --git a/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs index 61902c3a2..22a7c90b7 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs @@ -153,7 +153,7 @@ namespace SixLabors.ImageSharp.Processing.Processors } /// - /// Holds the values in an optimized contigous memory region. + /// Holds the values in an optimized contiguous memory region. /// internal class WeightsBuffer : IDisposable { diff --git a/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs index a4fdb1a1b..17b42c504 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs @@ -58,9 +58,9 @@ namespace SixLabors.ImageSharp.Processing.Processors // We will always be creating the clone even for mutate because thats the way this base processor works // ------------ // For resize we know we are going to populate every pixel with fresh data and we want a different target size so - // let's manually clone an empty set of images at the correct target and then have the base class processs them in turn. + // let's manually clone an empty set of images at the correct target and then have the base class process them in turn. IEnumerable> frames = source.Frames.Select(x => new ImageFrame(this.Width, this.Height, x.MetaData.Clone())); // this will create places holders - var image = new Image(config, source.MetaData.Clone(), frames); // base the place holder images in to prevet a extra frame being added + var image = new Image(config, source.MetaData.Clone(), frames); // base the place holder images in to prevent a extra frame being added return image; }