From 6c3f12eeaafdae39d96f4e8a25244febd0b09f01 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 2 Apr 2017 00:14:20 +1100 Subject: [PATCH 01/27] Update all icons [skip ci] --- build/icons/imagesharp-logo-128.png | 4 ++-- build/icons/imagesharp-logo-256.png | 4 ++-- build/icons/imagesharp-logo-32.png | 4 ++-- build/icons/imagesharp-logo-512.png | 4 ++-- build/icons/imagesharp-logo-64.png | 4 ++-- build/icons/imagesharp-logo-heading.png | 4 ++-- build/icons/imagesharp-logo.svg | 2 +- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/build/icons/imagesharp-logo-128.png b/build/icons/imagesharp-logo-128.png index d9ae997ba..f825a4034 100644 --- a/build/icons/imagesharp-logo-128.png +++ b/build/icons/imagesharp-logo-128.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:47f14bb7d24f7228cd8833d8d1881a72750b2c7813f391bd2a0dd0eeea936841 -size 6569 +oid sha256:e0f7d4c9396d4c7fe8173057c44cc2c0ecc24e52b52ce67c97dd11a7ac6e79ed +size 8113 diff --git a/build/icons/imagesharp-logo-256.png b/build/icons/imagesharp-logo-256.png index f1e67dd78..3ee8e026f 100644 --- a/build/icons/imagesharp-logo-256.png +++ b/build/icons/imagesharp-logo-256.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:757ec2f45cc5f9c2083fc65a236100f1a7776eee16bd1095a550e05783106a9f -size 13949 +oid sha256:7583dab9f399b7b9a7f69fc87086caa439249a705b058d6bfbd88238cfd95d4e +size 16613 diff --git a/build/icons/imagesharp-logo-32.png b/build/icons/imagesharp-logo-32.png index 80435989a..fd7cbfa9c 100644 --- a/build/icons/imagesharp-logo-32.png +++ b/build/icons/imagesharp-logo-32.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0f3a5375ce20321c2cfdc888a21dcb629d3e6a85641df5cca7c66e5b2a5f70f6 -size 1439 +oid sha256:c2b4b7ede0bf2fb4a9d028c9c4e930ab092b8c45689dd288ed1e419b5207048b +size 1904 diff --git a/build/icons/imagesharp-logo-512.png b/build/icons/imagesharp-logo-512.png index a5f880e3a..f1de1fb95 100644 --- a/build/icons/imagesharp-logo-512.png +++ b/build/icons/imagesharp-logo-512.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0e4cd18406375999c2bee1c39ad439b37f9524485d6e247ab0f14d2eb90a65f3 -size 31256 +oid sha256:cd9c567ef861075fcaef709577e87017b5834556df5fc6ff51c4ded8972568a8 +size 35069 diff --git a/build/icons/imagesharp-logo-64.png b/build/icons/imagesharp-logo-64.png index f59e202bf..e326b9c3a 100644 --- a/build/icons/imagesharp-logo-64.png +++ b/build/icons/imagesharp-logo-64.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fa25e5dbe84f942107a1c29f4f68ff2a73f497412ae91b6e60fc5464bc9b5f05 -size 3132 +oid sha256:28a0995e02d0535a13d08997d36cc7b4df95760f9499396b209ad2c7d73ccef6 +size 3962 diff --git a/build/icons/imagesharp-logo-heading.png b/build/icons/imagesharp-logo-heading.png index 20779215f..49c998fd6 100644 --- a/build/icons/imagesharp-logo-heading.png +++ b/build/icons/imagesharp-logo-heading.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bf2335642c6fd291befa0b203dbfb3387d99434369399b35aeea037c0f9eba45 -size 10474 +oid sha256:9de8af9ea137adb4a5d5ff286e40c45e835d29f2dda9e834360c336d3eacc756 +size 9450 diff --git a/build/icons/imagesharp-logo.svg b/build/icons/imagesharp-logo.svg index 2df3cc80c..cd4dfa117 100644 --- a/build/icons/imagesharp-logo.svg +++ b/build/icons/imagesharp-logo.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file From f3892ada26f57f2be99070bd098bb45d4ee431e2 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 3 Apr 2017 12:27:46 +1000 Subject: [PATCH 02/27] Fix logos [skip ci] --- build/icons/imagesharp-logo-heading.png | 4 ++-- build/icons/imagesharp-logo.png | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/icons/imagesharp-logo-heading.png b/build/icons/imagesharp-logo-heading.png index 49c998fd6..0fe132205 100644 --- a/build/icons/imagesharp-logo-heading.png +++ b/build/icons/imagesharp-logo-heading.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9de8af9ea137adb4a5d5ff286e40c45e835d29f2dda9e834360c336d3eacc756 -size 9450 +oid sha256:9480cb9ac66963e717b7e89666375e8bb23183e456067fd7d445ffb3c182b425 +size 11390 diff --git a/build/icons/imagesharp-logo.png b/build/icons/imagesharp-logo.png index e0f1854cc..f1de1fb95 100644 --- a/build/icons/imagesharp-logo.png +++ b/build/icons/imagesharp-logo.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e4217fe820af06a593903441f0719cab1ca650fd4de795f0e6808c4240a89819 -size 59646 +oid sha256:cd9c567ef861075fcaef709577e87017b5834556df5fc6ff51c4ded8972568a8 +size 35069 From c660602eb6a25c7a1a697faa2ca43a0fa0a99387 Mon Sep 17 00:00:00 2001 From: Pia Mancini Date: Mon, 3 Apr 2017 20:57:03 -0300 Subject: [PATCH 03/27] Add backers and sponsors from Open Collective Now your open collective backers and sponsors can to appear directly on your README. see how it'll look [here](https://github.com/apex/apex#backers) [More info](https://github.com/opencollective/opencollective/wiki/Github-banner) Also add badges on top. --- README.md | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/README.md b/README.md index 86ce5ddd4..6ba113e92 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,9 @@ [![GitHub forks](https://img.shields.io/github/forks/JimBobSquarePants/ImageSharp.svg)](https://github.com/JimBobSquarePants/ImageSharp/network) [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/ImageSharp/General?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Twitter](https://img.shields.io/twitter/url/https/github.com/JimBobSquarePants/ImageSharp.svg?style=social)](https://twitter.com/intent/tweet?hashtags=imagesharp,dotnet,oss&text=ImageSharp.+A+new+cross-platform+2D+graphics+API+in+C%23&url=https%3a%2f%2fgithub.com%2fJimBobSquarePants%2fImageSharp&via=james_m_south) +[![OpenCollective](https://opencollective.com/imagesharp/backers/badge.svg)](#backers) +[![OpenCollective](https://opencollective.com/imagesharp/sponsors/badge.svg)](#sponsors) + | |Build Status|Code Coverage| @@ -126,3 +129,73 @@ Core Team - [Anton Firsov](https://github.com/antonfirsov) - [Olivia Ifrim](https://github.com/olivif) - [Scott Williams](https://github.com/tocsoft) + +### Backers + +Support us with a monthly donation and help us continue our activities. [[Become a backer](https://opencollective.com/imagesharp#backer)] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +### Sponsors + +Become a sponsor and get your logo on our README on Github with a link to your site. [[Become a sponsor](https://opencollective.com/imagesharp#sponsor)] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 6a78a74e2d3b06875b966ebc3ac93d2453850826 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 5 Apr 2017 00:38:18 +1000 Subject: [PATCH 04/27] Fix resize banding bug --- .../Transforms/ResamplingWeightedProcessor.Weights.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs index 24d898fee..99b143de6 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs @@ -168,7 +168,7 @@ namespace ImageSharp.Processing.Processors /// The weights public WeightsWindow GetWeightsWindow(int destIdx, int leftIdx, int rightIdx) { - BufferSpan span = this.dataBuffer.GetRowSpan(destIdx).Slice(leftIdx, rightIdx - leftIdx); + BufferSpan span = this.dataBuffer.GetRowSpan(destIdx).Slice(leftIdx, rightIdx - leftIdx + 1); return new WeightsWindow(leftIdx, span); } } From 91d1303912354acea297116cf392577ff97ca768 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Wed, 5 Apr 2017 08:59:06 +0100 Subject: [PATCH 05/27] fix loading from unseekable stream fixes #164 --- src/ImageSharp/Image.FromStream.cs | 4 +- .../ImageSharp.Tests/Image/ImageLoadTests.cs | 14 ++++++ .../Image/NoneSeekableStream.cs | 50 +++++++++++++++++++ 3 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 tests/ImageSharp.Tests/Image/NoneSeekableStream.cs diff --git a/src/ImageSharp/Image.FromStream.cs b/src/ImageSharp/Image.FromStream.cs index 41ac7757e..112b8a109 100644 --- a/src/ImageSharp/Image.FromStream.cs +++ b/src/ImageSharp/Image.FromStream.cs @@ -201,7 +201,7 @@ namespace ImageSharp { config = config ?? Configuration.Default; - Image img = WithSeekableStream(stream, s => Decode(stream, options, config)); + Image img = WithSeekableStream(stream, s => Decode(s, options, config)); if (img != null) { @@ -238,7 +238,7 @@ namespace ImageSharp stream.CopyTo(ms); ms.Position = 0; - return action(stream); + return action(ms); } } } diff --git a/tests/ImageSharp.Tests/Image/ImageLoadTests.cs b/tests/ImageSharp.Tests/Image/ImageLoadTests.cs index ddb9414cc..10b0cbb94 100644 --- a/tests/ImageSharp.Tests/Image/ImageLoadTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageLoadTests.cs @@ -86,6 +86,20 @@ namespace ImageSharp.Tests } + [Fact] + public void LoadFromNoneSeekableStream() + { + NoneSeekableStream stream = new NoneSeekableStream(this.DataStream); + Image img = Image.Load(stream); + + Assert.NotNull(img); + Assert.Equal(TestFormat.GlobalTestFormat, img.CurrentImageFormat); + + + TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, null, Configuration.Default); + + } + [Fact] public void LoadFromStreamWithType() { diff --git a/tests/ImageSharp.Tests/Image/NoneSeekableStream.cs b/tests/ImageSharp.Tests/Image/NoneSeekableStream.cs new file mode 100644 index 000000000..bc36b60eb --- /dev/null +++ b/tests/ImageSharp.Tests/Image/NoneSeekableStream.cs @@ -0,0 +1,50 @@ +using System; +using System.IO; + +namespace ImageSharp.Tests +{ + internal class NoneSeekableStream : Stream + { + private Stream dataStream; + + public NoneSeekableStream(Stream dataStream) + { + this.dataStream = dataStream; + } + + public override bool CanRead => this.dataStream.CanRead; + + public override bool CanSeek => false; + + public override bool CanWrite => false; + + public override long Length => this.dataStream.Length; + + public override long Position { get => this.dataStream.Position; set => throw new NotImplementedException(); } + + public override void Flush() + { + this.dataStream.Flush(); + } + + public override int Read(byte[] buffer, int offset, int count) + { + return this.dataStream.Read(buffer, offset, count); + } + + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotImplementedException(); + } + + public override void SetLength(long value) + { + throw new NotImplementedException(); + } + + public override void Write(byte[] buffer, int offset, int count) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file From 09300d142f9dcbdab6235ea7672ad6a66e2f6bdd Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Wed, 5 Apr 2017 09:29:51 +0100 Subject: [PATCH 06/27] bump SixLabors.Shapes version for perf increase --- src/ImageSharp.Drawing/ImageSharp.Drawing.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj b/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj index 351764bb9..259ae4b0f 100644 --- a/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj +++ b/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj @@ -40,7 +40,7 @@ All - + ..\..\ImageSharp.ruleset From 7243aadf8a3efa8ad2c97956abc6cdc270b5a955 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 5 Apr 2017 23:55:29 +1000 Subject: [PATCH 07/27] Fix indexed png alpha selection. Fix #163 #165 --- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 9 +++++++-- src/ImageSharp/Quantizers/QuantizedImage.cs | 1 - 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index 498ae578c..552673179 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -516,9 +516,14 @@ namespace ImageSharp.Formats colorTable[offset + 1] = bytes[1]; colorTable[offset + 2] = bytes[2]; - if (alpha <= this.options.Threshold) + if (alpha < 255 && alpha <= this.options.Threshold) { - transparentPixels.Add((byte)offset); + // Ensure the index is actually being used in our array. + // I'd like to find a faster way of doing this. + if (quantized.Pixels.Contains((byte)i)) + { + transparentPixels.Add((byte)i); + } } } diff --git a/src/ImageSharp/Quantizers/QuantizedImage.cs b/src/ImageSharp/Quantizers/QuantizedImage.cs index 528da7717..471abbae7 100644 --- a/src/ImageSharp/Quantizers/QuantizedImage.cs +++ b/src/ImageSharp/Quantizers/QuantizedImage.cs @@ -6,7 +6,6 @@ namespace ImageSharp.Quantizers { using System; - using System.Threading.Tasks; /// /// Represents a quantized image where the pixels indexed by a color palette. From 14eee4cdc38cee1919c14887a87f195bb4167f13 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 6 Apr 2017 00:05:46 +1000 Subject: [PATCH 08/27] Update readme [skip ci] --- README.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 6ba113e92..ef37e4e91 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,13 @@ -# ImageSharp +# ImageSharp ImageSharp -**ImageSharp** is a new cross-platform 2D graphics API designed to allow the processing of images without the use of `System.Drawing`. +**ImageSharp** is a new ImageSharp is a fully featured, fully managed, cross-platform, 2D graphics API designed to allow the processing of images without the use of `System.Drawing`. -> **ImageSharp is still in early stages (alpha) but progress has been pretty quick. As such, please do not use on production environments until the library reaches release candidate status. Pre-release downloads are available from the [MyGet package repository](https://www.myget.org/gallery/imagesharp).** +Built against .Net Standard 1.1 ImageSharp can be used in device, cloud, and embedded/IoT scenarios. + +> **ImageSharp has made excellent progress and contains many great features but is still considered by us to be still in early stages (alpha). As such, we cannot support its use on production environments until the library reaches release candidate status. +> +> Pre-release downloads are available from the [MyGet package repository](https://www.myget.org/gallery/imagesharp).** [![GitHub license](https://img.shields.io/badge/license-Apache%202-blue.svg)](https://raw.githubusercontent.com/JimBobSquarePants/ImageSharp/master/APACHE-2.0-LICENSE.txt) [![GitHub issues](https://img.shields.io/github/issues/JimBobSquarePants/ImageSharp.svg)](https://github.com/JimBobSquarePants/ImageSharp/issues) @@ -69,7 +73,9 @@ There's plenty there and more coming. Check out the [current features](features. ### API -Without the constraints of `System.Drawing` We have been able to develop something much more flexible, easier to code against, and much, much less prone to memory leaks. Gone are system-wide process-locks. Images and processors are thread safe usable in parallel processing utilizing all the availables cores. +Without the constraints of `System.Drawing` We have been able to develop something much more flexible, easier to code against, and much, much less prone to memory leaks. + +Gone are system-wide process-locks; ImageSharp images are thread-safe and fully supported in web environments. Many `Image` methods are also fluent. From 4951624a00292440eb483e8f82023f23e489c3aa Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 6 Apr 2017 00:06:35 +1000 Subject: [PATCH 09/27] Fix bold [skip ci] --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ef37e4e91..1a6995d46 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,9 @@ Built against .Net Standard 1.1 ImageSharp can be used in device, cloud, and embedded/IoT scenarios. -> **ImageSharp has made excellent progress and contains many great features but is still considered by us to be still in early stages (alpha). As such, we cannot support its use on production environments until the library reaches release candidate status. +> **ImageSharp** has made excellent progress and contains many great features but is still considered by us to be still in early stages (alpha). As such, we cannot support its use on production environments until the library reaches release candidate status. > -> Pre-release downloads are available from the [MyGet package repository](https://www.myget.org/gallery/imagesharp).** +> Pre-release downloads are available from the [MyGet package repository](https://www.myget.org/gallery/imagesharp). [![GitHub license](https://img.shields.io/badge/license-Apache%202-blue.svg)](https://raw.githubusercontent.com/JimBobSquarePants/ImageSharp/master/APACHE-2.0-LICENSE.txt) [![GitHub issues](https://img.shields.io/github/issues/JimBobSquarePants/ImageSharp.svg)](https://github.com/JimBobSquarePants/ImageSharp/issues) From 524d7e847e195b833131ee5b6ee21f5d67dbdacb Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Thu, 6 Apr 2017 09:27:40 +0100 Subject: [PATCH 10/27] Fix for off by one for indexed pngs --- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index d6529940e..5ce9b5eb0 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -579,7 +579,7 @@ namespace ImageSharp.Formats // channel and we should try to read it. for (int x = 0; x < this.header.Width; x++) { - int index = newScanline[x]; + int index = newScanline[x + 1]; int pixelOffset = index * 3; byte a = this.paletteAlpha.Length > index ? this.paletteAlpha[index] : (byte)255; @@ -603,7 +603,7 @@ namespace ImageSharp.Formats { for (int x = 0; x < this.header.Width; x++) { - int index = newScanline[x]; + int index = newScanline[x + 1]; int pixelOffset = index * 3; byte r = this.palette[pixelOffset]; @@ -982,4 +982,4 @@ namespace ImageSharp.Formats } } } -} \ No newline at end of file +} From 0bc7fba31cf3274929cdade1547e7e554b564b6f Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 6 Apr 2017 19:44:10 +1000 Subject: [PATCH 11/27] Fix duplicate [skip ci] --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1a6995d46..6d37dd5e7 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # ImageSharp ImageSharp -**ImageSharp** is a new ImageSharp is a fully featured, fully managed, cross-platform, 2D graphics API designed to allow the processing of images without the use of `System.Drawing`. +**ImageSharp** is a new, fully featured, fully managed, cross-platform, 2D graphics API designed to allow the processing of images without the use of `System.Drawing`. Built against .Net Standard 1.1 ImageSharp can be used in device, cloud, and embedded/IoT scenarios. From 183260b48e5bbb45c84b326105370216e9b2014a Mon Sep 17 00:00:00 2001 From: Steffen Habermehl Date: Thu, 6 Apr 2017 14:36:05 +0200 Subject: [PATCH 12/27] Issue #132: Handle undefined EXIF data type where no number of components set --- .../MetaData/Profiles/Exif/ExifReader.cs | 7 ++++++ .../Profiles/Exif/ExifProfileTests.cs | 22 +++++++++++++++++++ tests/ImageSharp.Tests/TestImages.cs | 1 + .../Formats/Jpg/baseline/ExifUndefType.jpg | 3 +++ 4 files changed, 33 insertions(+) create mode 100644 tests/ImageSharp.Tests/TestImages/Formats/Jpg/baseline/ExifUndefType.jpg diff --git a/src/ImageSharp/MetaData/Profiles/Exif/ExifReader.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifReader.cs index 0b845c491..58f32bdd8 100644 --- a/src/ImageSharp/MetaData/Profiles/Exif/ExifReader.cs +++ b/src/ImageSharp/MetaData/Profiles/Exif/ExifReader.cs @@ -319,6 +319,13 @@ namespace ImageSharp uint numberOfComponents = this.GetLong(); + // Issue #132: ExifDataType == Undefined is treated like a byte array. + // If numberOfComponents == 0 this value can only be handled as an inline value and must fallback to 4 (bytes) + if (dataType == ExifDataType.Undefined && numberOfComponents == 0) + { + numberOfComponents = 4; + } + uint size = numberOfComponents * ExifValue.GetSize(dataType); byte[] data = this.GetBytes(4); diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs index 1bc31286d..2c902d3b8 100644 --- a/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs +++ b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs @@ -7,9 +7,11 @@ namespace ImageSharp.Tests { using System; using System.Collections; + using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; + using ImageSharp.Processing; using Xunit; public class ExifProfileTests @@ -268,6 +270,26 @@ namespace ImageSharp.Tests } } + [Fact] + public void ExifTypeUndefined() + { + Image image = TestFile.Create(TestImages.Jpeg.Baseline.Bad.ExifUndefType).CreateImage(); + Assert.NotNull(image); + + ExifProfile profile = image.MetaData.ExifProfile; + Assert.NotNull(profile); + + IEnumerator enumerator = profile.Values.GetEnumerator(); + while (enumerator.MoveNext()) + { + ExifValue entry = enumerator.Current; + if (entry.DataType == ExifDataType.Undefined) + { + Assert.NotEqual(0, entry.NumberOfComponents); + } + } + } + private static ExifProfile GetExifProfile() { Image image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateImage(); diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index f0a0e8dd8..e9d658887 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -61,6 +61,7 @@ namespace ImageSharp.Tests public static class Bad { public const string MissingEOF = "Jpg/baseline/badeof.jpg"; + public const string ExifUndefType = "Jpg/baseline/ExifUndefType.jpg"; } public const string Cmyk = "Jpg/baseline/cmyk.jpg"; diff --git a/tests/ImageSharp.Tests/TestImages/Formats/Jpg/baseline/ExifUndefType.jpg b/tests/ImageSharp.Tests/TestImages/Formats/Jpg/baseline/ExifUndefType.jpg new file mode 100644 index 000000000..e1a8a80e2 --- /dev/null +++ b/tests/ImageSharp.Tests/TestImages/Formats/Jpg/baseline/ExifUndefType.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6c8f3a1392c59bd60a71c689e04bc028a5b6dc7051edbb17cee2e48d91245f98 +size 6582 From 5f47d4e768bb581fb150ce4d567d5d5e6ae2d06d Mon Sep 17 00:00:00 2001 From: Steffen Habermehl Date: Fri, 7 Apr 2017 05:59:12 +0200 Subject: [PATCH 13/27] Updates unit test for undefined EXIF types --- .../MetaData/Profiles/Exif/ExifProfileTests.cs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs index 2c902d3b8..f380724df 100644 --- a/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs +++ b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs @@ -7,11 +7,9 @@ namespace ImageSharp.Tests { using System; using System.Collections; - using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; - using ImageSharp.Processing; using Xunit; public class ExifProfileTests @@ -279,13 +277,11 @@ namespace ImageSharp.Tests ExifProfile profile = image.MetaData.ExifProfile; Assert.NotNull(profile); - IEnumerator enumerator = profile.Values.GetEnumerator(); - while (enumerator.MoveNext()) + foreach (ExifValue value in profile.Values) { - ExifValue entry = enumerator.Current; - if (entry.DataType == ExifDataType.Undefined) + if (value.DataType == ExifDataType.Undefined) { - Assert.NotEqual(0, entry.NumberOfComponents); + Assert.Equal(4, value.NumberOfComponents); } } } From a36be6c76a2dc69c66d40f3937ec263fd39c06ab Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Mon, 10 Apr 2017 16:28:39 +0100 Subject: [PATCH 14/27] add alpha pallette if any alpha channel set --- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 36 +++++++++----------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index 552673179..1e2203e36 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -495,35 +495,31 @@ namespace ImageSharp.Formats // Grab the palette and write it to the stream. TColor[] palette = quantized.Palette; - int pixelCount = palette.Length; - List transparentPixels = new List(); + byte pixelCount = (byte)palette.Length; // Get max colors for bit depth. int colorTableLength = (int)Math.Pow(2, header.BitDepth) * 3; byte[] colorTable = ArrayPool.Shared.Rent(colorTableLength); + byte[] alphaTable = ArrayPool.Shared.Rent(pixelCount); byte[] bytes = ArrayPool.Shared.Rent(4); - + bool anyAlpha = false; try { - for (int i = 0; i < pixelCount; i++) + for (byte i = 0; i < pixelCount; i++) { - int offset = i * 3; - palette[i].ToXyzwBytes(bytes, 0); + if (quantized.Pixels.Contains(i)) + { + int offset = i * 3; + palette[i].ToXyzwBytes(bytes, 0); - int alpha = bytes[3]; + byte alpha = bytes[3]; - colorTable[offset] = bytes[0]; - colorTable[offset + 1] = bytes[1]; - colorTable[offset + 2] = bytes[2]; + colorTable[offset] = bytes[0]; + colorTable[offset + 1] = bytes[1]; + colorTable[offset + 2] = bytes[2]; - if (alpha < 255 && alpha <= this.options.Threshold) - { - // Ensure the index is actually being used in our array. - // I'd like to find a faster way of doing this. - if (quantized.Pixels.Contains((byte)i)) - { - transparentPixels.Add((byte)i); - } + anyAlpha = anyAlpha || alpha < 255; + alphaTable[i] = alpha; } } @@ -536,9 +532,9 @@ namespace ImageSharp.Formats } // Write the transparency data - if (transparentPixels.Any()) + if (anyAlpha) { - this.WriteChunk(stream, PngChunkTypes.PaletteAlpha, transparentPixels.ToArray()); + this.WriteChunk(stream, PngChunkTypes.PaletteAlpha, alphaTable, 0, pixelCount); } return quantized; From ba81ec0e2e704e5a96a06ff288900381966224a7 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Tue, 11 Apr 2017 09:20:00 +0100 Subject: [PATCH 15/27] remove theashold alpha theshold doesn't make sense for palette indexed colors. --- .../Formats/Png/IPngEncoderOptions.cs | 5 --- .../Formats/Png/PngEncoderOptions.cs | 5 --- .../Formats/Png/PngSmokeTests.cs | 40 +++++++++++++++++++ 3 files changed, 40 insertions(+), 10 deletions(-) diff --git a/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs b/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs index 0008080d3..3f5b33c0c 100644 --- a/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs +++ b/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs @@ -40,11 +40,6 @@ namespace ImageSharp.Formats /// IQuantizer Quantizer { get; } - /// - /// Gets the transparency threshold. - /// - byte Threshold { get; } - /// /// Gets a value indicating whether this instance should write /// gamma information to the stream. diff --git a/src/ImageSharp/Formats/Png/PngEncoderOptions.cs b/src/ImageSharp/Formats/Png/PngEncoderOptions.cs index 2891f1974..3c5bb1f62 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderOptions.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderOptions.cs @@ -57,11 +57,6 @@ namespace ImageSharp.Formats /// public IQuantizer Quantizer { get; set; } - /// - /// Gets or sets the transparency threshold. - /// - public byte Threshold { get; set; } = 0; - /// /// Gets or sets a value indicating whether this instance should write /// gamma information to the stream. The default value is false. diff --git a/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs index 882f903d6..4bdfd288c 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs @@ -58,6 +58,46 @@ namespace ImageSharp.Tests.Formats.Png } } + [Theory] + [WithTestPatternImages(100, 100, PixelTypes.Color)] + public void CanSaveIndexedPngTwice(TestImageProvider provider) + where TColor : struct, IPixel + { + // does saving a file then repoening mean both files are identical??? + using (Image source = provider.GetImage()) + using (MemoryStream ms = new MemoryStream()) + { + // image.Save(provider.Utility.GetTestOutputFileName("bmp")); + source.MetaData.Quality = 256; + source.Save(ms, new PngEncoder()); + ms.Position = 0; + using (Image img1 = Image.Load(ms, new PngDecoder())) + { + using (MemoryStream ms2 = new MemoryStream()) + { + img1.Save(ms2, new PngEncoder()); + ms2.Position = 0; + using (Image img2 = Image.Load(ms2, new PngDecoder())) + { + using (PixelAccessor pixels1 = img1.Lock()) + using (PixelAccessor pixels2 = img2.Lock()) + { + for (int y = 0; y < img1.Height; y++) + { + for (int x = 0; x < img1.Width; x++) + { + Assert.Equal(pixels1[x, y], pixels2[x, y]); + } + } + } + // img2.Save(provider.Utility.GetTestOutputFileName("bmp", "_loaded"), new BmpEncoder()); + ImageComparer.CheckSimilarity(source, img2); + } + } + } + } + } + [Theory] [WithTestPatternImages(300, 300, PixelTypes.All)] public void Resize(TestImageProvider provider) From b4fba454d59c49aeac5e488d4ee534299442c725 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Tue, 11 Apr 2017 10:24:48 +0100 Subject: [PATCH 16/27] re-enstate threashold re-enstate the pngencoderoptions threashold but how set the default to 255 so all alpha values are retained. --- src/ImageSharp/Formats/Png/IPngEncoderOptions.cs | 5 +++++ src/ImageSharp/Formats/Png/PngEncoderCore.cs | 7 ++++++- src/ImageSharp/Formats/Png/PngEncoderOptions.cs | 5 +++++ tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs | 13 ++++++++----- 4 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs b/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs index 3f5b33c0c..0008080d3 100644 --- a/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs +++ b/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs @@ -40,6 +40,11 @@ namespace ImageSharp.Formats /// IQuantizer Quantizer { get; } + /// + /// Gets the transparency threshold. + /// + byte Threshold { get; } + /// /// Gets a value indicating whether this instance should write /// gamma information to the stream. diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index 1e2203e36..e6ade1df0 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -495,7 +495,7 @@ namespace ImageSharp.Formats // Grab the palette and write it to the stream. TColor[] palette = quantized.Palette; - byte pixelCount = (byte)palette.Length; + byte pixelCount = palette.Length.ToByte(); // Get max colors for bit depth. int colorTableLength = (int)Math.Pow(2, header.BitDepth) * 3; @@ -518,6 +518,11 @@ namespace ImageSharp.Formats colorTable[offset + 1] = bytes[1]; colorTable[offset + 2] = bytes[2]; + if (alpha > this.options.Threshold) + { + alpha = 255; + } + anyAlpha = anyAlpha || alpha < 255; alphaTable[i] = alpha; } diff --git a/src/ImageSharp/Formats/Png/PngEncoderOptions.cs b/src/ImageSharp/Formats/Png/PngEncoderOptions.cs index 3c5bb1f62..90175c6d6 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderOptions.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderOptions.cs @@ -57,6 +57,11 @@ namespace ImageSharp.Formats /// public IQuantizer Quantizer { get; set; } + /// + /// Gets or sets the transparency threshold. + /// + public byte Threshold { get; set; } = 255; + /// /// Gets or sets a value indicating whether this instance should write /// gamma information to the stream. The default value is false. diff --git a/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs index 4bdfd288c..2ba570453 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs @@ -13,6 +13,7 @@ namespace ImageSharp.Tests.Formats.Png using ImageSharp.Formats; using System.Linq; using ImageSharp.IO; + using System.Numerics; public class PngSmokeTests { @@ -67,15 +68,19 @@ namespace ImageSharp.Tests.Formats.Png using (Image source = provider.GetImage()) using (MemoryStream ms = new MemoryStream()) { - // image.Save(provider.Utility.GetTestOutputFileName("bmp")); source.MetaData.Quality = 256; - source.Save(ms, new PngEncoder()); + source.Save(ms, new PngEncoder(), new PngEncoderOptions { + Threshold = 200 + }); ms.Position = 0; using (Image img1 = Image.Load(ms, new PngDecoder())) { using (MemoryStream ms2 = new MemoryStream()) { - img1.Save(ms2, new PngEncoder()); + img1.Save(ms2, new PngEncoder(), new PngEncoderOptions + { + Threshold = 200 + }); ms2.Position = 0; using (Image img2 = Image.Load(ms2, new PngDecoder())) { @@ -90,8 +95,6 @@ namespace ImageSharp.Tests.Formats.Png } } } - // img2.Save(provider.Utility.GetTestOutputFileName("bmp", "_loaded"), new BmpEncoder()); - ImageComparer.CheckSimilarity(source, img2); } } } From 44d1aae2b367150c5c895c4c9cec26f222ee4119 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Tue, 11 Apr 2017 10:42:14 +0100 Subject: [PATCH 17/27] return alpha array to array pool --- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index e6ade1df0..469c459fb 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -529,19 +529,20 @@ namespace ImageSharp.Formats } this.WriteChunk(stream, PngChunkTypes.Palette, colorTable, 0, colorTableLength); + + // Write the transparency data + if (anyAlpha) + { + this.WriteChunk(stream, PngChunkTypes.PaletteAlpha, alphaTable, 0, pixelCount); + } } finally { ArrayPool.Shared.Return(colorTable); + ArrayPool.Shared.Return(alphaTable); ArrayPool.Shared.Return(bytes); } - // Write the transparency data - if (anyAlpha) - { - this.WriteChunk(stream, PngChunkTypes.PaletteAlpha, alphaTable, 0, pixelCount); - } - return quantized; } From 0c3675791109133792d59bb3350975711e604e4a Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Wed, 12 Apr 2017 13:06:23 +0100 Subject: [PATCH 18/27] update to latest SixLabors.Fonts Now support text wrapping --- src/ImageSharp.Drawing/ImageSharp.Drawing.csproj | 2 +- src/ImageSharp.Drawing/Text/DrawText.cs | 7 ++++--- src/ImageSharp.Drawing/Text/TextGraphicsOptions.cs | 6 ++++++ 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj b/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj index 259ae4b0f..8ba0cbdd0 100644 --- a/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj +++ b/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj @@ -39,7 +39,7 @@ All - + diff --git a/src/ImageSharp.Drawing/Text/DrawText.cs b/src/ImageSharp.Drawing/Text/DrawText.cs index 1f5f4cdb1..eba11b5c5 100644 --- a/src/ImageSharp.Drawing/Text/DrawText.cs +++ b/src/ImageSharp.Drawing/Text/DrawText.cs @@ -177,13 +177,14 @@ namespace ImageSharp dpi = new Vector2((float)source.MetaData.HorizontalResolution, (float)source.MetaData.VerticalResolution); } - FontSpan style = new FontSpan(font) + FontSpan style = new FontSpan(font, dpi) { ApplyKerning = options.ApplyKerning, - TabWidth = options.TabWidth + TabWidth = options.TabWidth, + WrappingWidth = options.WrapTextWidth }; - renderer.RenderText(text, style, dpi); + renderer.RenderText(text, style); System.Collections.Generic.IEnumerable shapesToDraw = glyphBuilder.Paths; diff --git a/src/ImageSharp.Drawing/Text/TextGraphicsOptions.cs b/src/ImageSharp.Drawing/Text/TextGraphicsOptions.cs index b58a40b34..6b09f2395 100644 --- a/src/ImageSharp.Drawing/Text/TextGraphicsOptions.cs +++ b/src/ImageSharp.Drawing/Text/TextGraphicsOptions.cs @@ -41,6 +41,11 @@ namespace ImageSharp.Drawing /// public bool UseImageResolution; + /// + /// If greater than zero determine the width at which text should wrap. + /// + public float WrapTextWidth; + /// /// Initializes a new instance of the struct. /// @@ -52,6 +57,7 @@ namespace ImageSharp.Drawing this.TabWidth = 4; this.AntialiasSubpixelDepth = 16; this.UseImageResolution = false; + this.WrapTextWidth = 0; } /// From 13da8add28a2a22540d17497ba95bd4f152f6883 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 13 Apr 2017 10:37:28 +1000 Subject: [PATCH 19/27] Improve Wu performance. 104ms > 82ms in benchmark --- src/ImageSharp/Quantizers/Wu/Box.cs | 2 +- src/ImageSharp/Quantizers/Wu/WuQuantizer.cs | 110 +++++++++----------- 2 files changed, 53 insertions(+), 59 deletions(-) diff --git a/src/ImageSharp/Quantizers/Wu/Box.cs b/src/ImageSharp/Quantizers/Wu/Box.cs index e715c7839..42e4217b5 100644 --- a/src/ImageSharp/Quantizers/Wu/Box.cs +++ b/src/ImageSharp/Quantizers/Wu/Box.cs @@ -8,7 +8,7 @@ namespace ImageSharp.Quantizers /// /// Represents a box color cube. /// - internal sealed class Box + internal struct Box { /// /// Gets or sets the min red value, exclusive. diff --git a/src/ImageSharp/Quantizers/Wu/WuQuantizer.cs b/src/ImageSharp/Quantizers/Wu/WuQuantizer.cs index d632cdd73..a8cdb0f9a 100644 --- a/src/ImageSharp/Quantizers/Wu/WuQuantizer.cs +++ b/src/ImageSharp/Quantizers/Wu/WuQuantizer.cs @@ -64,9 +64,9 @@ namespace ImageSharp.Quantizers private static readonly ArrayPool LongPool = ArrayPool.Create(TableLength, 25); /// - /// The double array pool. + /// The float array pool. /// - private static readonly ArrayPool DoublePool = ArrayPool.Create(TableLength, 5); + private static readonly ArrayPool FloatPool = ArrayPool.Create(TableLength, 5); /// /// The byte array pool. @@ -101,7 +101,7 @@ namespace ImageSharp.Quantizers /// /// Moment of c^2*P(c). /// - private readonly double[] m2; + private readonly float[] m2; /// /// Color space tag. @@ -123,7 +123,7 @@ namespace ImageSharp.Quantizers this.vmg = LongPool.Rent(TableLength); this.vmb = LongPool.Rent(TableLength); this.vma = LongPool.Rent(TableLength); - this.m2 = DoublePool.Rent(TableLength); + this.m2 = FloatPool.Rent(TableLength); this.tag = BytePool.Rent(TableLength); } @@ -141,8 +141,7 @@ namespace ImageSharp.Quantizers this.Build3DHistogram(imagePixels); this.Get3DMoments(); - Box[] cube; - this.BuildCube(out cube, ref colorCount); + this.BuildCube(out Box[] cube, ref colorCount); return this.GenerateResult(imagePixels, colorCount, cube); } @@ -169,7 +168,7 @@ namespace ImageSharp.Quantizers /// The cube. /// The moment. /// The result. - private static double Volume(Box cube, long[] moment) + private static float Volume(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)] @@ -369,14 +368,14 @@ namespace ImageSharp.Quantizers long[] volumeG = ArrayPool.Shared.Rent(IndexCount * IndexAlphaCount); long[] volumeB = ArrayPool.Shared.Rent(IndexCount * IndexAlphaCount); long[] volumeA = ArrayPool.Shared.Rent(IndexCount * IndexAlphaCount); - double[] volume2 = ArrayPool.Shared.Rent(IndexCount * IndexAlphaCount); + float[] volume2 = ArrayPool.Shared.Rent(IndexCount * IndexAlphaCount); long[] area = ArrayPool.Shared.Rent(IndexAlphaCount); long[] areaR = ArrayPool.Shared.Rent(IndexAlphaCount); long[] areaG = ArrayPool.Shared.Rent(IndexAlphaCount); long[] areaB = ArrayPool.Shared.Rent(IndexAlphaCount); long[] areaA = ArrayPool.Shared.Rent(IndexAlphaCount); - double[] area2 = ArrayPool.Shared.Rent(IndexAlphaCount); + float[] area2 = ArrayPool.Shared.Rent(IndexAlphaCount); try { @@ -405,7 +404,7 @@ namespace ImageSharp.Quantizers long lineG = 0; long lineB = 0; long lineA = 0; - double line2 = 0; + float line2 = 0; for (int a = 1; a < IndexAlphaCount; a++) { @@ -454,14 +453,14 @@ namespace ImageSharp.Quantizers ArrayPool.Shared.Return(volumeG); ArrayPool.Shared.Return(volumeB); ArrayPool.Shared.Return(volumeA); - ArrayPool.Shared.Return(volume2); + ArrayPool.Shared.Return(volume2); ArrayPool.Shared.Return(area); ArrayPool.Shared.Return(areaR); ArrayPool.Shared.Return(areaG); ArrayPool.Shared.Return(areaB); ArrayPool.Shared.Return(areaA); - ArrayPool.Shared.Return(area2); + ArrayPool.Shared.Return(area2); } } @@ -469,15 +468,15 @@ namespace ImageSharp.Quantizers /// Computes the weighted variance of a box cube. /// /// The cube. - /// The . - private double Variance(Box cube) + /// The . + private float Variance(Box cube) { - double dr = Volume(cube, this.vmr); - double dg = Volume(cube, this.vmg); - double db = Volume(cube, this.vmb); - double da = Volume(cube, this.vma); + float dr = Volume(cube, this.vmr); + float dg = Volume(cube, this.vmg); + float db = Volume(cube, this.vmb); + float da = Volume(cube, this.vma); - double xx = + float xx = this.m2[GetPaletteIndex(cube.R1, cube.G1, cube.B1, cube.A1)] - this.m2[GetPaletteIndex(cube.R1, cube.G1, cube.B1, cube.A0)] - this.m2[GetPaletteIndex(cube.R1, cube.G1, cube.B0, cube.A1)] @@ -515,8 +514,8 @@ namespace ImageSharp.Quantizers /// The whole blue. /// The whole alpha. /// The whole weight. - /// The . - private double Maximize(Box cube, int direction, int first, int last, out int cut, double wholeR, double wholeG, double wholeB, double wholeA, double wholeW) + /// 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) { long baseR = Bottom(cube, direction, this.vmr); long baseG = Bottom(cube, direction, this.vmg); @@ -524,20 +523,20 @@ namespace ImageSharp.Quantizers long baseA = Bottom(cube, direction, this.vma); long baseW = Bottom(cube, direction, this.vwt); - double max = 0.0; + float max = 0F; cut = -1; for (int i = first; i < last; i++) { - double halfR = baseR + Top(cube, direction, i, this.vmr); - double halfG = baseG + Top(cube, direction, i, this.vmg); - double halfB = baseB + Top(cube, direction, i, this.vmb); - double halfA = baseA + Top(cube, direction, i, this.vma); - double halfW = baseW + Top(cube, direction, i, this.vwt); + 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); - double temp; + float temp; - if (Math.Abs(halfW) < Constants.Epsilon) + if (MathF.Abs(halfW) < Constants.Epsilon) { continue; } @@ -550,7 +549,7 @@ namespace ImageSharp.Quantizers halfA = wholeA - halfA; halfW = wholeW - halfW; - if (Math.Abs(halfW) < Constants.Epsilon) + if (MathF.Abs(halfW) < Constants.Epsilon) { continue; } @@ -575,21 +574,16 @@ namespace ImageSharp.Quantizers /// Returns a value indicating whether the box has been split. private bool Cut(Box set1, Box set2) { - double wholeR = Volume(set1, this.vmr); - double wholeG = Volume(set1, this.vmg); - double wholeB = Volume(set1, this.vmb); - double wholeA = Volume(set1, this.vma); - double wholeW = Volume(set1, this.vwt); - - int cutr; - int cutg; - int cutb; - int cuta; - - double maxr = this.Maximize(set1, 0, set1.R0 + 1, set1.R1, out cutr, wholeR, wholeG, wholeB, wholeA, wholeW); - double maxg = this.Maximize(set1, 1, set1.G0 + 1, set1.G1, out cutg, wholeR, wholeG, wholeB, wholeA, wholeW); - double maxb = this.Maximize(set1, 2, set1.B0 + 1, set1.B1, out cutb, wholeR, wholeG, wholeB, wholeA, wholeW); - double maxa = this.Maximize(set1, 3, set1.A0 + 1, set1.A1, out cuta, wholeR, wholeG, wholeB, wholeA, wholeW); + 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 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); int dir; @@ -691,11 +685,11 @@ namespace ImageSharp.Quantizers private void BuildCube(out Box[] cube, ref int colorCount) { cube = new Box[colorCount]; - double[] vv = new double[colorCount]; + float[] vv = new float[colorCount]; for (int i = 0; i < colorCount; i++) { - cube[i] = new Box(); + cube[i] = default(Box); } cube[0].R0 = cube[0].G0 = cube[0].B0 = cube[0].A0 = 0; @@ -708,18 +702,18 @@ namespace ImageSharp.Quantizers { if (this.Cut(cube[next], cube[i])) { - vv[next] = cube[next].Volume > 1 ? this.Variance(cube[next]) : 0.0; - vv[i] = cube[i].Volume > 1 ? this.Variance(cube[i]) : 0.0; + vv[next] = cube[next].Volume > 1 ? this.Variance(cube[next]) : 0F; + vv[i] = cube[i].Volume > 1 ? this.Variance(cube[i]) : 0F; } else { - vv[next] = 0.0; + vv[next] = 0F; i--; } next = 0; - double temp = vv[0]; + float temp = vv[0]; for (int k = 1; k <= i; k++) { if (vv[k] > temp) @@ -755,14 +749,14 @@ namespace ImageSharp.Quantizers { this.Mark(cube[k], (byte)k); - double weight = Volume(cube[k], this.vwt); + float weight = Volume(cube[k], this.vwt); - if (Math.Abs(weight) > Constants.Epsilon) + if (MathF.Abs(weight) > Constants.Epsilon) { - float r = (float)(Volume(cube[k], this.vmr) / weight); - float g = (float)(Volume(cube[k], this.vmg) / weight); - float b = (float)(Volume(cube[k], this.vmb) / weight); - float a = (float)(Volume(cube[k], this.vma) / weight); + float r = Volume(cube[k], this.vmr) / weight; + float g = Volume(cube[k], this.vmg) / weight; + float b = Volume(cube[k], this.vmb) / weight; + float a = Volume(cube[k], this.vma) / weight; TColor color = default(TColor); color.PackFromVector4(new Vector4(r, g, b, a) / 255F); @@ -800,7 +794,7 @@ namespace ImageSharp.Quantizers LongPool.Return(this.vmg); LongPool.Return(this.vmb); LongPool.Return(this.vma); - DoublePool.Return(this.m2); + FloatPool.Return(this.m2); BytePool.Return(this.tag); return new QuantizedImage(width, height, pallette, pixels); From 27ca2cb629335bae3c8def011f5fbeb1bcf346a8 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 13 Apr 2017 10:57:24 +1000 Subject: [PATCH 20/27] Use a single array pool --- src/ImageSharp/Quantizers/Wu/WuArrayPool.cs | 36 +++++++++++++++++ src/ImageSharp/Quantizers/Wu/WuQuantizer.cs | 43 +++++++-------------- 2 files changed, 50 insertions(+), 29 deletions(-) create mode 100644 src/ImageSharp/Quantizers/Wu/WuArrayPool.cs diff --git a/src/ImageSharp/Quantizers/Wu/WuArrayPool.cs b/src/ImageSharp/Quantizers/Wu/WuArrayPool.cs new file mode 100644 index 000000000..5e4956f01 --- /dev/null +++ b/src/ImageSharp/Quantizers/Wu/WuArrayPool.cs @@ -0,0 +1,36 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Quantizers +{ + using System.Buffers; + + /// + /// Provides array pooling for the . + /// This is a separate class so that the pools can be shared accross multiple generic quantizer instaces. + /// + internal static class WuArrayPool + { + /// + /// The long array pool. + /// + public static readonly ArrayPool LongPool = ArrayPool.Create(TableLength, 25); + + /// + /// The float array pool. + /// + public static readonly ArrayPool FloatPool = ArrayPool.Create(TableLength, 5); + + /// + /// The byte array pool. + /// + public static readonly ArrayPool BytePool = ArrayPool.Create(TableLength, 5); + + /// + /// The table length. Matches the calculated value in + /// + private const int TableLength = 2471625; + } +} \ No newline at end of file diff --git a/src/ImageSharp/Quantizers/Wu/WuQuantizer.cs b/src/ImageSharp/Quantizers/Wu/WuQuantizer.cs index a8cdb0f9a..ba8047c06 100644 --- a/src/ImageSharp/Quantizers/Wu/WuQuantizer.cs +++ b/src/ImageSharp/Quantizers/Wu/WuQuantizer.cs @@ -58,21 +58,6 @@ namespace ImageSharp.Quantizers /// private const int TableLength = IndexCount * IndexCount * IndexCount * IndexAlphaCount; - /// - /// The long array pool. - /// - private static readonly ArrayPool LongPool = ArrayPool.Create(TableLength, 25); - - /// - /// The float array pool. - /// - private static readonly ArrayPool FloatPool = ArrayPool.Create(TableLength, 5); - - /// - /// The byte array pool. - /// - private static readonly ArrayPool BytePool = ArrayPool.Create(TableLength, 5); - /// /// Moment of P(c). /// @@ -118,13 +103,13 @@ namespace ImageSharp.Quantizers /// public WuQuantizer() { - this.vwt = LongPool.Rent(TableLength); - this.vmr = LongPool.Rent(TableLength); - this.vmg = LongPool.Rent(TableLength); - this.vmb = LongPool.Rent(TableLength); - this.vma = LongPool.Rent(TableLength); - this.m2 = FloatPool.Rent(TableLength); - this.tag = BytePool.Rent(TableLength); + this.vwt = WuArrayPool.LongPool.Rent(TableLength); + this.vmr = WuArrayPool.LongPool.Rent(TableLength); + this.vmg = WuArrayPool.LongPool.Rent(TableLength); + this.vmb = WuArrayPool.LongPool.Rent(TableLength); + this.vma = WuArrayPool.LongPool.Rent(TableLength); + this.m2 = WuArrayPool.FloatPool.Rent(TableLength); + this.tag = WuArrayPool.BytePool.Rent(TableLength); } /// @@ -789,13 +774,13 @@ namespace ImageSharp.Quantizers }); // Cleanup - LongPool.Return(this.vwt); - LongPool.Return(this.vmr); - LongPool.Return(this.vmg); - LongPool.Return(this.vmb); - LongPool.Return(this.vma); - FloatPool.Return(this.m2); - BytePool.Return(this.tag); + WuArrayPool.LongPool.Return(this.vwt); + WuArrayPool.LongPool.Return(this.vmr); + WuArrayPool.LongPool.Return(this.vmg); + WuArrayPool.LongPool.Return(this.vmb); + WuArrayPool.LongPool.Return(this.vma); + WuArrayPool.FloatPool.Return(this.m2); + WuArrayPool.BytePool.Return(this.tag); return new QuantizedImage(width, height, pallette, pixels); } From 86fdf5686738ce1312957ac053d5b67abe3e238c Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 13 Apr 2017 12:52:10 +1000 Subject: [PATCH 21/27] WuQuantizer now inherits Quantizer --- src/ImageSharp/Quantizers/Wu/Box.cs | 3 +- src/ImageSharp/Quantizers/Wu/WuQuantizer.cs | 361 ++++++++++++-------- 2 files changed, 212 insertions(+), 152 deletions(-) diff --git a/src/ImageSharp/Quantizers/Wu/Box.cs b/src/ImageSharp/Quantizers/Wu/Box.cs index 42e4217b5..7f2a32087 100644 --- a/src/ImageSharp/Quantizers/Wu/Box.cs +++ b/src/ImageSharp/Quantizers/Wu/Box.cs @@ -7,8 +7,9 @@ namespace ImageSharp.Quantizers { /// /// Represents a box color cube. + /// TODO: This should be a struct for performance /// - internal struct Box + internal sealed class Box { /// /// Gets or sets the min red value, exclusive. diff --git a/src/ImageSharp/Quantizers/Wu/WuQuantizer.cs b/src/ImageSharp/Quantizers/Wu/WuQuantizer.cs index ba8047c06..998bd0c82 100644 --- a/src/ImageSharp/Quantizers/Wu/WuQuantizer.cs +++ b/src/ImageSharp/Quantizers/Wu/WuQuantizer.cs @@ -7,8 +7,9 @@ namespace ImageSharp.Quantizers { using System; using System.Buffers; + using System.Collections.Generic; using System.Numerics; - using System.Threading.Tasks; + using System.Runtime.CompilerServices; /// /// An implementation of Wu's color quantizer with alpha channel. @@ -30,7 +31,7 @@ namespace ImageSharp.Quantizers /// /// /// The pixel format. - public sealed class WuQuantizer : IQuantizer + public class WuQuantizer : Quantizer where TColor : struct, IPixel { /// @@ -58,82 +59,232 @@ namespace ImageSharp.Quantizers /// private const int TableLength = IndexCount * IndexCount * IndexCount * IndexAlphaCount; + /// + /// A buffer for storing pixels + /// + private readonly byte[] rgbaBuffer = new byte[4]; + + /// + /// A lookup table for colors + /// + private readonly Dictionary colorMap = new Dictionary(); + /// /// Moment of P(c). /// - private readonly long[] vwt; + private long[] vwt; /// /// Moment of r*P(c). /// - private readonly long[] vmr; + private long[] vmr; /// /// Moment of g*P(c). /// - private readonly long[] vmg; + private long[] vmg; /// /// Moment of b*P(c). /// - private readonly long[] vmb; + private long[] vmb; /// /// Moment of a*P(c). /// - private readonly long[] vma; + private long[] vma; /// /// Moment of c^2*P(c). /// - private readonly float[] m2; + private float[] m2; /// /// Color space tag. /// - private readonly byte[] tag; + private byte[] tag; /// - /// A buffer for storing pixels + /// Maximum allowed color depth /// - private readonly byte[] rgbaBuffer = new byte[4]; + private int colors; + + /// + /// The reduced image palette + /// + private TColor[] palette; + + /// + /// The color cube representing the image palette + /// + private Box[] colorCube; /// /// Initializes a new instance of the class. /// + /// + /// The Wu quantizer is a two pass algorithm. The initial pass sets up the 3-D color histogram, + /// the second pass quantizes a color based on the position in the histogram. + /// public WuQuantizer() + : base(false) { - this.vwt = WuArrayPool.LongPool.Rent(TableLength); - this.vmr = WuArrayPool.LongPool.Rent(TableLength); - this.vmg = WuArrayPool.LongPool.Rent(TableLength); - this.vmb = WuArrayPool.LongPool.Rent(TableLength); - this.vma = WuArrayPool.LongPool.Rent(TableLength); - this.m2 = WuArrayPool.FloatPool.Rent(TableLength); - this.tag = WuArrayPool.BytePool.Rent(TableLength); } /// - public QuantizedImage Quantize(ImageBase image, int maxColors) + public override QuantizedImage Quantize(ImageBase image, int maxColors) { Guard.NotNull(image, nameof(image)); - int colorCount = maxColors.Clamp(1, 256); + this.colors = maxColors.Clamp(1, 256); + + try + { + this.vwt = WuArrayPool.LongPool.Rent(TableLength); + this.vmr = WuArrayPool.LongPool.Rent(TableLength); + this.vmg = WuArrayPool.LongPool.Rent(TableLength); + this.vmb = WuArrayPool.LongPool.Rent(TableLength); + this.vma = WuArrayPool.LongPool.Rent(TableLength); + this.m2 = WuArrayPool.FloatPool.Rent(TableLength); + this.tag = WuArrayPool.BytePool.Rent(TableLength); + + return base.Quantize(image, this.colors); + } + finally + { + WuArrayPool.LongPool.Return(this.vwt, true); + WuArrayPool.LongPool.Return(this.vmr, true); + WuArrayPool.LongPool.Return(this.vmg, true); + WuArrayPool.LongPool.Return(this.vmb, true); + WuArrayPool.LongPool.Return(this.vma, true); + WuArrayPool.FloatPool.Return(this.m2, true); + WuArrayPool.BytePool.Return(this.tag, true); + } + } + + /// + protected override TColor[] GetPalette() + { + if (this.palette == null) + { + this.palette = new TColor[this.colors]; + for (int k = 0; k < this.colors; k++) + { + this.Mark(this.colorCube[k], (byte)k); + + float weight = Volume(this.colorCube[k], this.vwt); + + 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; + + TColor color = default(TColor); + color.PackFromVector4(new Vector4(r, g, b, a) / 255F); + this.palette[k] = color; + } + } + } + + return this.palette; + } + + /// + protected override void InitialQuantizePixel(TColor pixel) + { + // Add the color to a 3-D color histogram. + // Colors are expected in r->g->b->a format + pixel.ToXyzwBytes(this.rgbaBuffer, 0); + + byte r = this.rgbaBuffer[0]; + byte g = this.rgbaBuffer[1]; + byte b = this.rgbaBuffer[2]; + byte a = this.rgbaBuffer[3]; + + int inr = r >> (8 - IndexBits); + int ing = g >> (8 - IndexBits); + int inb = b >> (8 - IndexBits); + int ina = a >> (8 - IndexAlphaBits); + + int ind = GetPaletteIndex(inr + 1, ing + 1, inb + 1, ina + 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); + } + + /// + protected override void FirstPass(PixelAccessor source, int width, int height) + { + // Build up the 3-D color histogram + // Loop through each row + for (int y = 0; y < height; y++) + { + // And loop through each column + for (int x = 0; x < width; x++) + { + // Now I have the pixel, call the FirstPassQuantize function... + this.InitialQuantizePixel(source[x, y]); + } + } - this.Clear(); + this.Get3DMoments(); + this.BuildCube(); + } - using (PixelAccessor imagePixels = image.Lock()) + /// + protected override void SecondPass(PixelAccessor source, byte[] output, int width, int height) + { + // Load up the values for the first pixel. We can use these to speed up the second + // pass of the algorithm by avoiding transforming rows of identical color. + TColor sourcePixel = source[0, 0]; + TColor previousPixel = sourcePixel; + byte pixelValue = this.QuantizePixel(sourcePixel); + TColor[] colorPalette = this.GetPalette(); + TColor transformedPixel = colorPalette[pixelValue]; + + for (int y = 0; y < height; y++) { - this.Build3DHistogram(imagePixels); - this.Get3DMoments(); + // And loop through each column + for (int x = 0; x < width; x++) + { + // Get the pixel. + sourcePixel = source[x, y]; + + // Check if this is the same as the last pixel. If so use that value + // rather than calculating it again. This is an inexpensive optimization. + if (!previousPixel.Equals(sourcePixel)) + { + // Quantize the pixel + pixelValue = this.QuantizePixel(sourcePixel); - this.BuildCube(out Box[] cube, ref colorCount); + // And setup the previous pointer + previousPixel = sourcePixel; - return this.GenerateResult(imagePixels, colorCount, cube); + if (this.Dither) + { + transformedPixel = colorPalette[pixelValue]; + } + } + + if (this.Dither) + { + // Apply the dithering matrix. We have to reapply the value now as the original has changed. + this.DitherType.Dither(source, sourcePixel, transformedPixel, x, y, width, height); + } + + output[(y * source.Width) + x] = pixelValue; + } } } /// - /// Gets an index. + /// Gets the index index of the given color in the palette. /// /// The red value. /// The green value. @@ -294,55 +445,6 @@ namespace ImageSharp.Quantizers } } - /// - /// Clears the tables. - /// - private void Clear() - { - Array.Clear(this.vwt, 0, TableLength); - Array.Clear(this.vmr, 0, TableLength); - Array.Clear(this.vmg, 0, TableLength); - Array.Clear(this.vmb, 0, TableLength); - Array.Clear(this.vma, 0, TableLength); - Array.Clear(this.m2, 0, TableLength); - Array.Clear(this.tag, 0, TableLength); - } - - /// - /// Builds a 3-D color histogram of counts, r/g/b, c^2. - /// - /// The pixel accessor. - private void Build3DHistogram(PixelAccessor pixels) - { - for (int y = 0; y < pixels.Height; y++) - { - for (int x = 0; x < pixels.Width; x++) - { - // Colors are expected in r->g->b->a format - pixels[x, y].ToXyzwBytes(this.rgbaBuffer, 0); - - byte r = this.rgbaBuffer[0]; - byte g = this.rgbaBuffer[1]; - byte b = this.rgbaBuffer[2]; - byte a = this.rgbaBuffer[3]; - - int inr = r >> (8 - IndexBits); - int ing = g >> (8 - IndexBits); - int inb = b >> (8 - IndexBits); - int ina = a >> (8 - IndexAlphaBits); - - int ind = GetPaletteIndex(inr + 1, ing + 1, inb + 1, ina + 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); - } - } - } - /// /// Converts the histogram into moments so that we can rapidly calculate the sums of the above quantities over any desired box. /// @@ -665,30 +767,28 @@ namespace ImageSharp.Quantizers /// /// Builds the cube. /// - /// The cube. - /// The color count. - private void BuildCube(out Box[] cube, ref int colorCount) + private void BuildCube() { - cube = new Box[colorCount]; - float[] vv = new float[colorCount]; + this.colorCube = new Box[this.colors]; + float[] vv = new float[this.colors]; - for (int i = 0; i < colorCount; i++) + for (int i = 0; i < this.colors; i++) { - cube[i] = default(Box); + this.colorCube[i] = new Box(); } - cube[0].R0 = cube[0].G0 = cube[0].B0 = cube[0].A0 = 0; - cube[0].R1 = cube[0].G1 = cube[0].B1 = IndexCount - 1; - cube[0].A1 = IndexAlphaCount - 1; + 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; int next = 0; - for (int i = 1; i < colorCount; i++) + for (int i = 1; i < this.colors; i++) { - if (this.Cut(cube[next], cube[i])) + if (this.Cut(this.colorCube[next], this.colorCube[i])) { - vv[next] = cube[next].Volume > 1 ? this.Variance(cube[next]) : 0F; - vv[i] = cube[i].Volume > 1 ? this.Variance(cube[i]) : 0F; + 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; } else { @@ -710,79 +810,38 @@ namespace ImageSharp.Quantizers if (temp <= 0.0) { - colorCount = i + 1; + this.colors = i + 1; break; } } } /// - /// Generates the quantized result. + /// Process the pixel in the second pass of the algorithm /// - /// The image pixels. - /// The color count. - /// The cube. - /// The result. - private QuantizedImage GenerateResult(PixelAccessor imagePixels, int colorCount, Box[] cube) + /// The pixel to quantize + /// + /// The quantized value + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private byte QuantizePixel(TColor pixel) { - TColor[] pallette = new TColor[colorCount]; - byte[] pixels = new byte[imagePixels.Width * imagePixels.Height]; - int width = imagePixels.Width; - int height = imagePixels.Height; - - for (int k = 0; k < colorCount; k++) + if (this.Dither) { - this.Mark(cube[k], (byte)k); - - float weight = Volume(cube[k], this.vwt); - - if (MathF.Abs(weight) > Constants.Epsilon) - { - float r = Volume(cube[k], this.vmr) / weight; - float g = Volume(cube[k], this.vmg) / weight; - float b = Volume(cube[k], this.vmb) / weight; - float a = Volume(cube[k], this.vma) / weight; - - TColor color = default(TColor); - color.PackFromVector4(new Vector4(r, g, b, a) / 255F); - pallette[k] = color; - } + // The colors have changed so we need to use Euclidean distance caclulation to find the closest value. + // This palette can never be null here. + return this.GetClosestColor(pixel, this.palette, this.colorMap); } - Parallel.For( - 0, - height, - imagePixels.ParallelOptions, - y => - { - byte[] rgba = ArrayPool.Shared.Rent(4); - for (int x = 0; x < width; x++) - { - // Expected order r->g->b->a - imagePixels[x, y].ToXyzwBytes(rgba, 0); - - int r = rgba[0] >> (8 - IndexBits); - int g = rgba[1] >> (8 - IndexBits); - int b = rgba[2] >> (8 - IndexBits); - int a = rgba[3] >> (8 - IndexAlphaBits); - - int ind = GetPaletteIndex(r + 1, g + 1, b + 1, a + 1); - pixels[(y * width) + x] = this.tag[ind]; - } - - ArrayPool.Shared.Return(rgba); - }); + // Expected order r->g->b->a + pixel.ToXyzwBytes(this.rgbaBuffer, 0); - // Cleanup - WuArrayPool.LongPool.Return(this.vwt); - WuArrayPool.LongPool.Return(this.vmr); - WuArrayPool.LongPool.Return(this.vmg); - WuArrayPool.LongPool.Return(this.vmb); - WuArrayPool.LongPool.Return(this.vma); - WuArrayPool.FloatPool.Return(this.m2); - WuArrayPool.BytePool.Return(this.tag); + int r = this.rgbaBuffer[0] >> (8 - IndexBits); + int g = this.rgbaBuffer[1] >> (8 - IndexBits); + int b = this.rgbaBuffer[2] >> (8 - IndexBits); + int a = this.rgbaBuffer[3] >> (8 - IndexAlphaBits); - return new QuantizedImage(width, height, pallette, pixels); + return (byte)GetPaletteIndex(r + 1, g + 1, b + 1, a + 1); } } -} \ No newline at end of file +} From 8dab59b321ff4ef27986afd909177671eb5f229c Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 13 Apr 2017 13:13:27 +1000 Subject: [PATCH 22/27] Octree Quantizer now only supports 1 alpha value ... As nature intended. My Existing hack was incorrect and led to strange results. --- .../Quantizers/Octree/OctreeQuantizer.cs | 70 +++++-------------- .../Quantizers/Options/Quantization.cs | 5 +- tests/ImageSharp.Tests/FileTestBase.cs | 3 +- tests/ImageSharp.Tests/TestImages.cs | 1 + .../TestImages/Formats/Gif/trans.gif | 3 + 5 files changed, 28 insertions(+), 54 deletions(-) create mode 100644 tests/ImageSharp.Tests/TestImages/Formats/Gif/trans.gif diff --git a/src/ImageSharp/Quantizers/Octree/OctreeQuantizer.cs b/src/ImageSharp/Quantizers/Octree/OctreeQuantizer.cs index 2590f297e..f95ae5fce 100644 --- a/src/ImageSharp/Quantizers/Octree/OctreeQuantizer.cs +++ b/src/ImageSharp/Quantizers/Octree/OctreeQuantizer.cs @@ -58,18 +58,12 @@ namespace ImageSharp.Quantizers public override QuantizedImage Quantize(ImageBase image, int maxColors) { this.colors = maxColors.Clamp(1, 255); - this.octree = new Octree(this.GetBitsNeededForColorDepth(maxColors)); + this.octree = new Octree(this.GetBitsNeededForColorDepth(this.colors)); - return base.Quantize(image, maxColors); + return base.Quantize(image, this.colors); } - /// - /// Execute a second pass through the bitmap - /// - /// The source image. - /// The output pixel array - /// The width in pixels of the image - /// The height in pixels of the image + /// protected override void SecondPass(PixelAccessor source, byte[] output, int width, int height) { // Load up the values for the first pixel. We can use these to speed up the second @@ -115,36 +109,17 @@ namespace ImageSharp.Quantizers } } - /// - /// Process the pixel in the first pass of the algorithm - /// - /// - /// The pixel to quantize - /// - /// - /// This function need only be overridden if your quantize algorithm needs two passes, - /// such as an Octree quantizer. - /// + /// protected override void InitialQuantizePixel(TColor pixel) { // Add the color to the Octree this.octree.AddColor(pixel, this.pixelBuffer); } - /// - /// Retrieve the palette for the quantized image. - /// - /// - /// The new color palette - /// + /// protected override TColor[] GetPalette() { - if (this.palette == null) - { - this.palette = this.octree.Palletize(Math.Max(this.colors, 1)); - } - - return this.palette; + return this.palette ?? (this.palette = this.octree.Palletize(Math.Max(this.colors, 1))); } /// @@ -175,6 +150,7 @@ namespace ImageSharp.Quantizers /// /// The /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] private int GetBitsNeededForColorDepth(int colorCount) { return (int)Math.Ceiling(Math.Log(colorCount, 2)); @@ -189,7 +165,7 @@ namespace ImageSharp.Quantizers /// Mask used when getting the appropriate pixels for a given node /// // ReSharper disable once StaticMemberInGenericType - private static readonly int[] Mask = { 0x100, 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 }; + private static readonly int[] Mask = { 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 }; /// /// The root of the Octree @@ -379,11 +355,6 @@ namespace ImageSharp.Quantizers /// private int blue; - /// - /// Alpha component - /// - private int alpha; - /// /// The index of this node in the palette /// @@ -406,7 +377,7 @@ namespace ImageSharp.Quantizers // Construct the new node this.leaf = level == colorBits; - this.red = this.green = this.blue = this.alpha = 0; + this.red = this.green = this.blue = 0; this.pixelCount = 0; // If a leaf, increment the leaf count @@ -454,10 +425,9 @@ namespace ImageSharp.Quantizers int shift = 7 - level; pixel.ToXyzwBytes(buffer, 0); - int index = ((buffer[3] & Mask[0]) >> (shift - 3)) | - ((buffer[2] & Mask[level + 1]) >> (shift - 2)) | - ((buffer[1] & Mask[level + 1]) >> (shift - 1)) | - ((buffer[0] & Mask[level + 1]) >> shift); + int index = ((buffer[2] & Mask[level]) >> (shift - 2)) | + ((buffer[1] & Mask[level]) >> (shift - 1)) | + ((buffer[0] & Mask[level]) >> shift); OctreeNode child = this.children[index]; @@ -479,7 +449,7 @@ namespace ImageSharp.Quantizers /// The number of leaves removed public int Reduce() { - this.red = this.green = this.blue = this.alpha = 0; + this.red = this.green = this.blue = 0; int childNodes = 0; // Loop through all children and add their information to this node @@ -490,7 +460,6 @@ namespace ImageSharp.Quantizers this.red += this.children[index].red; this.green += this.children[index].green; this.blue += this.children[index].blue; - this.alpha += this.children[index].alpha; this.pixelCount += this.children[index].pixelCount; ++childNodes; this.children[index] = null; @@ -517,11 +486,10 @@ namespace ImageSharp.Quantizers byte r = (this.red / this.pixelCount).ToByte(); byte g = (this.green / this.pixelCount).ToByte(); byte b = (this.blue / this.pixelCount).ToByte(); - byte a = (this.alpha / this.pixelCount).ToByte(); // And set the color of the palette entry TColor pixel = default(TColor); - pixel.PackFromBytes(r, g, b, a); + pixel.PackFromBytes(r, g, b, 255); palette[index] = pixel; // Consume the next palette index @@ -558,10 +526,9 @@ namespace ImageSharp.Quantizers int shift = 7 - level; pixel.ToXyzwBytes(buffer, 0); - int pixelIndex = ((buffer[3] & Mask[0]) >> (shift - 3)) | - ((buffer[2] & Mask[level + 1]) >> (shift - 2)) | - ((buffer[1] & Mask[level + 1]) >> (shift - 1)) | - ((buffer[0] & Mask[level + 1]) >> shift); + int pixelIndex = ((buffer[2] & Mask[level]) >> (shift - 2)) | + ((buffer[1] & Mask[level]) >> (shift - 1)) | + ((buffer[0] & Mask[level]) >> shift); if (this.children[pixelIndex] != null) { @@ -588,9 +555,8 @@ namespace ImageSharp.Quantizers this.red += buffer[0]; this.green += buffer[1]; this.blue += buffer[2]; - this.alpha += buffer[3]; } } } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Quantizers/Options/Quantization.cs b/src/ImageSharp/Quantizers/Options/Quantization.cs index 428c8e801..039404384 100644 --- a/src/ImageSharp/Quantizers/Options/Quantization.cs +++ b/src/ImageSharp/Quantizers/Options/Quantization.cs @@ -12,17 +12,20 @@ namespace ImageSharp { /// /// An adaptive Octree quantizer. Fast with good quality. + /// The quantizer only supports a single alpha value. /// Octree, /// /// Xiaolin Wu's Color Quantizer which generates high quality output. + /// The quantizer supports multiple alpha values. /// Wu, /// /// Palette based, Uses the collection of web-safe colors by default. + /// The quantizer supports multiple alpha values. /// Palette } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/FileTestBase.cs b/tests/ImageSharp.Tests/FileTestBase.cs index 765ff3a42..1fa26063d 100644 --- a/tests/ImageSharp.Tests/FileTestBase.cs +++ b/tests/ImageSharp.Tests/FileTestBase.cs @@ -28,7 +28,7 @@ namespace ImageSharp.Tests // TestFile.Create(TestImages.Jpeg.Baseline.GammaDalaiLamaGray), // Perf: Enable for local testing only // TestFile.Create(TestImages.Jpeg.Progressive.Bad.BadEOF), // Perf: Enable for local testing only TestFile.Create(TestImages.Bmp.Car), - // TestFile.Create(TestImages.Bmp.Neg_height), // Perf: Enable for local testing only + // TestFile.Create(TestImages.Bmp.NegHeight), // Perf: Enable for local testing only TestFile.Create(TestImages.Png.Splash), // TestFile.Create(TestImages.Png.ChunkLength1), // Perf: Enable for local testing only // TestFile.Create(TestImages.Png.ChunkLength2), // Perf: Enable for local testing only @@ -46,6 +46,7 @@ namespace ImageSharp.Tests // TestFile.Create(TestImages.Png.P1), // Perf: Enable for local testing only // TestFile.Create(TestImages.Png.Pd), // Perf: Enable for local testing only TestFile.Create(TestImages.Gif.Rings), + // TestFile.Create(TestImages.Gif.Trans), // Perf: Enable for local testing only // TestFile.Create(TestImages.Gif.Cheers), // Perf: Enable for local testing only // TestFile.Create(TestImages.Gif.Giphy) // Perf: Enable for local testing only }; diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index e9d658887..5be1240ef 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -103,6 +103,7 @@ namespace ImageSharp.Tests public const string Rings = "Gif/rings.gif"; public const string Giphy = "Gif/giphy.gif"; public const string Cheers = "Gif/cheers.gif"; + public const string Trans = "Gif/trans.gif"; } } } diff --git a/tests/ImageSharp.Tests/TestImages/Formats/Gif/trans.gif b/tests/ImageSharp.Tests/TestImages/Formats/Gif/trans.gif new file mode 100644 index 000000000..6a7577fa1 --- /dev/null +++ b/tests/ImageSharp.Tests/TestImages/Formats/Gif/trans.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:99b20b417a63c30f62fa69a00fa0a76c2cb9848988e5def1b886460a129197f7 +size 13707 From 9710f02cf9fe7e2f9f650ae0827b2deef9593b9c Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 13 Apr 2017 13:15:16 +1000 Subject: [PATCH 23/27] Png uses Wu by default to ensure full alpha range --- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index 469c459fb..c11fc94df 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -7,7 +7,6 @@ namespace ImageSharp.Formats { using System; using System.Buffers; - using System.Collections.Generic; using System.IO; using System.Linq; @@ -487,7 +486,7 @@ namespace ImageSharp.Formats if (this.quantizer == null) { - this.quantizer = new OctreeQuantizer(); + this.quantizer = new WuQuantizer(); } // Quantize the image returning a palette. This boxing is icky. From 139e21e68f14c9978430bdd9f985d56e6f1655da Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 13 Apr 2017 13:31:56 +1000 Subject: [PATCH 24/27] Cleanup --- src/ImageSharp/Quantizers/{Wu => }/Box.cs | 0 .../Quantizers/{Octree => }/OctreeQuantizer.cs | 0 .../Quantizers/{Palette => }/PaletteQuantizer.cs | 9 +-------- src/ImageSharp/Quantizers/{Options => }/Quantization.cs | 0 src/ImageSharp/Quantizers/{Octree => }/Quantizer.cs | 0 src/ImageSharp/Quantizers/{Wu => }/WuArrayPool.cs | 0 src/ImageSharp/Quantizers/{Wu => }/WuQuantizer.cs | 2 +- 7 files changed, 2 insertions(+), 9 deletions(-) rename src/ImageSharp/Quantizers/{Wu => }/Box.cs (100%) rename src/ImageSharp/Quantizers/{Octree => }/OctreeQuantizer.cs (100%) rename src/ImageSharp/Quantizers/{Palette => }/PaletteQuantizer.cs (92%) rename src/ImageSharp/Quantizers/{Options => }/Quantization.cs (100%) rename src/ImageSharp/Quantizers/{Octree => }/Quantizer.cs (100%) rename src/ImageSharp/Quantizers/{Wu => }/WuArrayPool.cs (100%) rename src/ImageSharp/Quantizers/{Wu => }/WuQuantizer.cs (99%) diff --git a/src/ImageSharp/Quantizers/Wu/Box.cs b/src/ImageSharp/Quantizers/Box.cs similarity index 100% rename from src/ImageSharp/Quantizers/Wu/Box.cs rename to src/ImageSharp/Quantizers/Box.cs diff --git a/src/ImageSharp/Quantizers/Octree/OctreeQuantizer.cs b/src/ImageSharp/Quantizers/OctreeQuantizer.cs similarity index 100% rename from src/ImageSharp/Quantizers/Octree/OctreeQuantizer.cs rename to src/ImageSharp/Quantizers/OctreeQuantizer.cs diff --git a/src/ImageSharp/Quantizers/Palette/PaletteQuantizer.cs b/src/ImageSharp/Quantizers/PaletteQuantizer.cs similarity index 92% rename from src/ImageSharp/Quantizers/Palette/PaletteQuantizer.cs rename to src/ImageSharp/Quantizers/PaletteQuantizer.cs index dedb5ca23..b9e4573e6 100644 --- a/src/ImageSharp/Quantizers/Palette/PaletteQuantizer.cs +++ b/src/ImageSharp/Quantizers/PaletteQuantizer.cs @@ -7,7 +7,6 @@ namespace ImageSharp.Quantizers { using System; using System.Collections.Generic; - using System.Numerics; using System.Runtime.CompilerServices; /// @@ -71,13 +70,7 @@ namespace ImageSharp.Quantizers return base.Quantize(image, maxColors); } - /// - /// Execute a second pass through the bitmap - /// - /// The source image. - /// The output pixel array - /// The width in pixels of the image - /// The height in pixels of the image + /// protected override void SecondPass(PixelAccessor source, byte[] output, int width, int height) { // Load up the values for the first pixel. We can use these to speed up the second diff --git a/src/ImageSharp/Quantizers/Options/Quantization.cs b/src/ImageSharp/Quantizers/Quantization.cs similarity index 100% rename from src/ImageSharp/Quantizers/Options/Quantization.cs rename to src/ImageSharp/Quantizers/Quantization.cs diff --git a/src/ImageSharp/Quantizers/Octree/Quantizer.cs b/src/ImageSharp/Quantizers/Quantizer.cs similarity index 100% rename from src/ImageSharp/Quantizers/Octree/Quantizer.cs rename to src/ImageSharp/Quantizers/Quantizer.cs diff --git a/src/ImageSharp/Quantizers/Wu/WuArrayPool.cs b/src/ImageSharp/Quantizers/WuArrayPool.cs similarity index 100% rename from src/ImageSharp/Quantizers/Wu/WuArrayPool.cs rename to src/ImageSharp/Quantizers/WuArrayPool.cs diff --git a/src/ImageSharp/Quantizers/Wu/WuQuantizer.cs b/src/ImageSharp/Quantizers/WuQuantizer.cs similarity index 99% rename from src/ImageSharp/Quantizers/Wu/WuQuantizer.cs rename to src/ImageSharp/Quantizers/WuQuantizer.cs index 998bd0c82..9881b866a 100644 --- a/src/ImageSharp/Quantizers/Wu/WuQuantizer.cs +++ b/src/ImageSharp/Quantizers/WuQuantizer.cs @@ -844,4 +844,4 @@ namespace ImageSharp.Quantizers return (byte)GetPaletteIndex(r + 1, g + 1, b + 1, a + 1); } } -} +} \ No newline at end of file From 1fe84d52eab764bbd5df61a3763e96ea9912fa55 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 13 Apr 2017 16:37:20 +1000 Subject: [PATCH 25/27] Fix non-dithered Wu output --- src/ImageSharp/Quantizers/WuQuantizer.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ImageSharp/Quantizers/WuQuantizer.cs b/src/ImageSharp/Quantizers/WuQuantizer.cs index 9881b866a..fdf8e4136 100644 --- a/src/ImageSharp/Quantizers/WuQuantizer.cs +++ b/src/ImageSharp/Quantizers/WuQuantizer.cs @@ -291,6 +291,7 @@ namespace ImageSharp.Quantizers /// The blue value. /// The alpha value. /// The index. + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static int GetPaletteIndex(int r, int g, int b, int a) { return (r << ((IndexBits * 2) + IndexAlphaBits)) + (r << (IndexBits + IndexAlphaBits + 1)) @@ -841,7 +842,7 @@ namespace ImageSharp.Quantizers int b = this.rgbaBuffer[2] >> (8 - IndexBits); int a = this.rgbaBuffer[3] >> (8 - IndexAlphaBits); - return (byte)GetPaletteIndex(r + 1, g + 1, b + 1, a + 1); + return this.tag[GetPaletteIndex(r + 1, g + 1, b + 1, a + 1)]; } } } \ No newline at end of file From a46e5fc59fbc3efdf3831570e95671ea8cb7dee0 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 13 Apr 2017 16:47:00 +1000 Subject: [PATCH 26/27] Disable double index smoke test I'm not 100% sure whether this test is practical. --- .../Formats/Png/PngSmokeTests.cs | 83 ++++++++++--------- 1 file changed, 42 insertions(+), 41 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs index 2ba570453..a7453f77c 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs @@ -59,47 +59,48 @@ namespace ImageSharp.Tests.Formats.Png } } - [Theory] - [WithTestPatternImages(100, 100, PixelTypes.Color)] - public void CanSaveIndexedPngTwice(TestImageProvider provider) - where TColor : struct, IPixel - { - // does saving a file then repoening mean both files are identical??? - using (Image source = provider.GetImage()) - using (MemoryStream ms = new MemoryStream()) - { - source.MetaData.Quality = 256; - source.Save(ms, new PngEncoder(), new PngEncoderOptions { - Threshold = 200 - }); - ms.Position = 0; - using (Image img1 = Image.Load(ms, new PngDecoder())) - { - using (MemoryStream ms2 = new MemoryStream()) - { - img1.Save(ms2, new PngEncoder(), new PngEncoderOptions - { - Threshold = 200 - }); - ms2.Position = 0; - using (Image img2 = Image.Load(ms2, new PngDecoder())) - { - using (PixelAccessor pixels1 = img1.Lock()) - using (PixelAccessor pixels2 = img2.Lock()) - { - for (int y = 0; y < img1.Height; y++) - { - for (int x = 0; x < img1.Width; x++) - { - Assert.Equal(pixels1[x, y], pixels2[x, y]); - } - } - } - } - } - } - } - } + // JJS: Commented out for now since the test does not take into lossy nature of indexing. + //[Theory] + //[WithTestPatternImages(100, 100, PixelTypes.Color)] + //public void CanSaveIndexedPngTwice(TestImageProvider provider) + // where TColor : struct, IPixel + //{ + // // does saving a file then repoening mean both files are identical??? + // using (Image source = provider.GetImage()) + // using (MemoryStream ms = new MemoryStream()) + // { + // source.MetaData.Quality = 256; + // source.Save(ms, new PngEncoder(), new PngEncoderOptions { + // Threshold = 200 + // }); + // ms.Position = 0; + // using (Image img1 = Image.Load(ms, new PngDecoder())) + // { + // using (MemoryStream ms2 = new MemoryStream()) + // { + // img1.Save(ms2, new PngEncoder(), new PngEncoderOptions + // { + // Threshold = 200 + // }); + // ms2.Position = 0; + // using (Image img2 = Image.Load(ms2, new PngDecoder())) + // { + // using (PixelAccessor pixels1 = img1.Lock()) + // using (PixelAccessor pixels2 = img2.Lock()) + // { + // for (int y = 0; y < img1.Height; y++) + // { + // for (int x = 0; x < img1.Width; x++) + // { + // Assert.Equal(pixels1[x, y], pixels2[x, y]); + // } + // } + // } + // } + // } + // } + // } + //} [Theory] [WithTestPatternImages(300, 300, PixelTypes.All)] From b0b9221b2d1038fc3aa4f918808b0aa6d99b0a1e Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 13 Apr 2017 17:36:10 +1000 Subject: [PATCH 27/27] Fix dithering causing random opaque pixels --- .../Dithering/ErrorDiffusion/ErrorDiffuser.cs | 15 +++++++++++++-- .../Dithering/ErrorDiffusion/IErrorDiffuser.cs | 18 ++++++++++++++++++ src/ImageSharp/Quantizers/OctreeQuantizer.cs | 2 +- src/ImageSharp/Quantizers/PaletteQuantizer.cs | 2 +- src/ImageSharp/Quantizers/Quantizer.cs | 17 ++--------------- src/ImageSharp/Quantizers/WuQuantizer.cs | 2 +- 6 files changed, 36 insertions(+), 20 deletions(-) diff --git a/src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuser.cs b/src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuser.cs index cde146f1e..d6ab8eb64 100644 --- a/src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuser.cs +++ b/src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuser.cs @@ -71,8 +71,19 @@ namespace ImageSharp.Dithering public void Dither(PixelAccessor pixels, TColor source, TColor transformed, int x, int y, int width, int height) where TColor : struct, IPixel { - // Assign the transformed pixel to the array. - pixels[x, y] = transformed; + this.Dither(pixels, source, transformed, x, y, width, height, true); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Dither(PixelAccessor pixels, TColor source, TColor transformed, int x, int y, int width, int height, bool replacePixel) + where TColor : struct, IPixel + { + if (replacePixel) + { + // Assign the transformed pixel to the array. + pixels[x, y] = transformed; + } // Calculate the error Vector4 error = source.ToVector4() - transformed.ToVector4(); diff --git a/src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs b/src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs index 18079b1fb..66ec3d515 100644 --- a/src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs +++ b/src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs @@ -25,5 +25,23 @@ namespace ImageSharp.Dithering /// The pixel format. void Dither(PixelAccessor pixels, TColor source, TColor transformed, int x, int y, int width, int height) where TColor : struct, IPixel; + + /// + /// Transforms the image applying the dither matrix. This method alters the input pixels array + /// + /// The pixel accessor + /// The source pixel + /// The transformed pixel + /// The column index. + /// The row index. + /// The image width. + /// The image height. + /// + /// Whether to replace the pixel at the given coordinates with the transformed value. + /// Generally this would be true for standard two-color dithering but when used in conjunction with color quantization this should be false. + /// + /// The pixel format. + void Dither(PixelAccessor pixels, TColor source, TColor transformed, int x, int y, int width, int height, bool replacePixel) + where TColor : struct, IPixel; } } diff --git a/src/ImageSharp/Quantizers/OctreeQuantizer.cs b/src/ImageSharp/Quantizers/OctreeQuantizer.cs index f95ae5fce..df52ee7f9 100644 --- a/src/ImageSharp/Quantizers/OctreeQuantizer.cs +++ b/src/ImageSharp/Quantizers/OctreeQuantizer.cs @@ -101,7 +101,7 @@ namespace ImageSharp.Quantizers if (this.Dither) { // Apply the dithering matrix. We have to reapply the value now as the original has changed. - this.DitherType.Dither(source, sourcePixel, transformedPixel, x, y, width, height); + this.DitherType.Dither(source, sourcePixel, transformedPixel, x, y, width, height, false); } output[(y * source.Width) + x] = pixelValue; diff --git a/src/ImageSharp/Quantizers/PaletteQuantizer.cs b/src/ImageSharp/Quantizers/PaletteQuantizer.cs index b9e4573e6..f039fe0c5 100644 --- a/src/ImageSharp/Quantizers/PaletteQuantizer.cs +++ b/src/ImageSharp/Quantizers/PaletteQuantizer.cs @@ -108,7 +108,7 @@ namespace ImageSharp.Quantizers if (this.Dither) { // Apply the dithering matrix. We have to reapply the value now as the original has changed. - this.DitherType.Dither(source, sourcePixel, transformedPixel, x, y, width, height); + this.DitherType.Dither(source, sourcePixel, transformedPixel, x, y, width, height, false); } output[(y * source.Width) + x] = pixelValue; diff --git a/src/ImageSharp/Quantizers/Quantizer.cs b/src/ImageSharp/Quantizers/Quantizer.cs index ccf8da3df..bb856ccc5 100644 --- a/src/ImageSharp/Quantizers/Quantizer.cs +++ b/src/ImageSharp/Quantizers/Quantizer.cs @@ -66,22 +66,9 @@ namespace ImageSharp.Quantizers this.FirstPass(pixels, width, height); } - // Collect the palette. Octree requires this to be done before the second pass runs. + // Collect the palette. Required before the second pass runs. colorPalette = this.GetPalette(); - - if (this.Dither) - { - // We clone the image as we don't want to alter the original. - using (Image clone = new Image(image)) - using (PixelAccessor clonedPixels = clone.Lock()) - { - this.SecondPass(clonedPixels, quantizedPixels, width, height); - } - } - else - { - this.SecondPass(pixels, quantizedPixels, width, height); - } + this.SecondPass(pixels, quantizedPixels, width, height); } return new QuantizedImage(width, height, colorPalette, quantizedPixels); diff --git a/src/ImageSharp/Quantizers/WuQuantizer.cs b/src/ImageSharp/Quantizers/WuQuantizer.cs index fdf8e4136..c1c81d0ac 100644 --- a/src/ImageSharp/Quantizers/WuQuantizer.cs +++ b/src/ImageSharp/Quantizers/WuQuantizer.cs @@ -275,7 +275,7 @@ namespace ImageSharp.Quantizers if (this.Dither) { // Apply the dithering matrix. We have to reapply the value now as the original has changed. - this.DitherType.Dither(source, sourcePixel, transformedPixel, x, y, width, height); + this.DitherType.Dither(source, sourcePixel, transformedPixel, x, y, width, height, false); } output[(y * source.Width) + x] = pixelValue;