From f3f7164c9c5d34dcbaaf1da9909398dd67810f28 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 28 Jan 2017 17:37:49 +0100 Subject: [PATCH 001/142] DecodedBlock cleanup & better comments --- .../Components/Decoder/DecodedBlock.cs | 58 ++++++++++ .../Components/Decoder/DecodedBlockArray.cs | 55 ++++++++++ .../Components/Decoder/DecodedBlockMemento.cs | 101 ------------------ .../Components/Decoder/JpegScanDecoder.cs | 10 +- .../JpegDecoderCore.cs | 18 ++-- 5 files changed, 128 insertions(+), 114 deletions(-) create mode 100644 src/ImageSharp.Formats.Jpeg/Components/Decoder/DecodedBlock.cs create mode 100644 src/ImageSharp.Formats.Jpeg/Components/Decoder/DecodedBlockArray.cs delete mode 100644 src/ImageSharp.Formats.Jpeg/Components/Decoder/DecodedBlockMemento.cs diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/DecodedBlock.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/DecodedBlock.cs new file mode 100644 index 0000000000..bc93a18f7c --- /dev/null +++ b/src/ImageSharp.Formats.Jpeg/Components/Decoder/DecodedBlock.cs @@ -0,0 +1,58 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats.Jpg +{ + using System; + + /// + /// A structure to store unprocessed instances and their coordinates while scanning the image. + /// The is present in a "raw" decoded frequency-domain form. + /// We need to apply IDCT and unzigging to transform them into color-space blocks. + /// + internal struct DecodedBlock + { + /// + /// A value indicating whether the instance is initialized. + /// + public bool Initialized; + + /// + /// X coordinate of the current block, in units of 8x8. (The third block in the first row has (bx, by) = (2, 0)) + /// + public int Bx; + + /// + /// Y coordinate of the current block, in units of 8x8. (The third block in the first row has (bx, by) = (2, 0)) + /// + public int By; + + /// + /// The + /// + public Block8x8F Block; + + /// + /// Store the block data into a at the given index of an . + /// + /// The array + /// The index in the array + /// X coordinate of the block + /// Y coordinate of the block + /// The + public static void Store(ref DecodedBlockArray blockArray, int index, int bx, int by, ref Block8x8F block) + { + if (index >= blockArray.Count) + { + throw new IndexOutOfRangeException("Block index is out of range in DecodedBlock.Store()!"); + } + + blockArray.Buffer[index].Initialized = true; + blockArray.Buffer[index].Bx = bx; + blockArray.Buffer[index].By = by; + blockArray.Buffer[index].Block = block; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/DecodedBlockArray.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/DecodedBlockArray.cs new file mode 100644 index 0000000000..97a79dd61b --- /dev/null +++ b/src/ImageSharp.Formats.Jpeg/Components/Decoder/DecodedBlockArray.cs @@ -0,0 +1,55 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats.Jpg +{ + using System; + using System.Buffers; + + /// + /// Because has no information for rented arrays, + /// we need to store the count and the buffer separately when storing pooled arrays. + /// + internal struct DecodedBlockArray : IDisposable + { + /// + /// The used to pool data in . + /// Should always clean arrays when returning! + /// + private static readonly ArrayPool ArrayPool = ArrayPool.Create(); + + /// + /// Initializes a new instance of the struct. Rents a buffer. + /// + /// The number of valid -s + public DecodedBlockArray(int count) + { + this.Count = count; + this.Buffer = ArrayPool.Rent(count); + } + + /// + /// Gets the number of actual -s inside + /// + public int Count { get; } + + /// + /// Gets the rented buffer. + /// + public DecodedBlock[] Buffer { get; private set; } + + /// + /// Returns the rented buffer to the pool. + /// + public void Dispose() + { + if (this.Buffer != null) + { + ArrayPool.Return(this.Buffer, true); + this.Buffer = null; + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/DecodedBlockMemento.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/DecodedBlockMemento.cs deleted file mode 100644 index 04ece04ee8..0000000000 --- a/src/ImageSharp.Formats.Jpeg/Components/Decoder/DecodedBlockMemento.cs +++ /dev/null @@ -1,101 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Formats.Jpg -{ - using System; - using System.Buffers; - - /// - /// A structure to store unprocessed instances and their coordinates while scanning the image. - /// - internal struct DecodedBlockMemento - { - /// - /// A value indicating whether the instance is initialized. - /// - public bool Initialized; - - /// - /// X coordinate of the current block, in units of 8x8. (The third block in the first row has (bx, by) = (2, 0)) - /// - public int Bx; - - /// - /// Y coordinate of the current block, in units of 8x8. (The third block in the first row has (bx, by) = (2, 0)) - /// - public int By; - - /// - /// The - /// - public Block8x8F Block; - - /// - /// Store the block data into a at the given index of an . - /// - /// The array - /// The index in the array - /// X coordinate of the block - /// Y coordinate of the block - /// The - public static void Store(ref DecodedBlockMemento.Array blockArray, int index, int bx, int by, ref Block8x8F block) - { - if (index >= blockArray.Count) - { - throw new IndexOutOfRangeException("Block index is out of range in DecodedBlockMemento.Store()!"); - } - - blockArray.Buffer[index].Initialized = true; - blockArray.Buffer[index].Bx = bx; - blockArray.Buffer[index].By = by; - blockArray.Buffer[index].Block = block; - } - - /// - /// Because has no information for rented arrays, we need to store the count and the buffer separately. - /// - public struct Array : IDisposable - { - /// - /// The used to pool data in . - /// Should always clean arrays when returning! - /// - private static readonly ArrayPool ArrayPool = ArrayPool.Create(); - - /// - /// Initializes a new instance of the struct. Rents a buffer. - /// - /// The number of valid -s - public Array(int count) - { - this.Count = count; - this.Buffer = ArrayPool.Rent(count); - } - - /// - /// Gets the number of actual -s inside - /// - public int Count { get; } - - /// - /// Gets the rented buffer. - /// - public DecodedBlockMemento[] Buffer { get; private set; } - - /// - /// Returns the rented buffer to the pool. - /// - public void Dispose() - { - if (this.Buffer != null) - { - ArrayPool.Return(this.Buffer, true); - this.Buffer = null; - } - } - } - } -} \ No newline at end of file diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs index 0e389771c0..103ee60c02 100644 --- a/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs +++ b/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs @@ -121,10 +121,10 @@ namespace ImageSharp.Formats.Jpg } /// - /// Loads the data from the given into the block. + /// Loads the data from the given into the block. /// - /// The - public void LoadMemento(ref DecodedBlockMemento memento) + /// The + public void LoadMemento(ref DecodedBlock memento) { this.bx = memento.Bx; this.by = memento.By; @@ -204,8 +204,8 @@ namespace ImageSharp.Formats.Jpg } // Store the decoded block - DecodedBlockMemento.Array blocks = decoder.DecodedBlocks[this.ComponentIndex]; - DecodedBlockMemento.Store(ref blocks, blockIndex, this.bx, this.by, ref this.data.Block); + DecodedBlockArray blocks = decoder.DecodedBlocks[this.ComponentIndex]; + DecodedBlock.Store(ref blocks, blockIndex, this.bx, this.by, ref this.data.Block); } // for j diff --git a/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs b/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs index eca4d46229..fd06018a22 100644 --- a/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs @@ -87,7 +87,7 @@ namespace ImageSharp.Formats this.QuantizationTables = new Block8x8F[MaxTq + 1]; this.Temp = new byte[2 * Block8x8F.ScalarCount]; this.ComponentArray = new Component[MaxComponents]; - this.DecodedBlocks = new DecodedBlockMemento.Array[MaxComponents]; + this.DecodedBlocks = new DecodedBlockArray[MaxComponents]; } /// @@ -101,10 +101,12 @@ namespace ImageSharp.Formats public HuffmanTree[] HuffmanTrees { get; } /// - /// Gets the saved state between progressive-mode scans. - /// TODO: Also save non-progressive data here. (Helps splitting and parallelizing JpegScanDecoder-s loop) + /// Gets the array of -s storing the "raw" frequency-domain decoded blocks. + /// We need to apply IDCT and unzigging to transform them into color-space blocks. + /// This is done by . + /// When ==true, we are touching these blocks each time we process a Scan. /// - public DecodedBlockMemento.Array[] DecodedBlocks { get; } + public DecodedBlockArray[] DecodedBlocks { get; } /// /// Gets the quantization tables, in zigzag order. @@ -191,7 +193,7 @@ namespace ImageSharp.Formats this.HuffmanTrees[i].Dispose(); } - foreach (DecodedBlockMemento.Array blockArray in this.DecodedBlocks) + foreach (DecodedBlockArray blockArray in this.DecodedBlocks) { blockArray.Dispose(); } @@ -461,7 +463,7 @@ namespace ImageSharp.Formats /// /// Process the blocks in into Jpeg image channels ( and ) - /// The blocks are expected in a "raw" frequency-domain decoded format. We need to apply IDCT and unzigging to transform them into color-space blocks. + /// are in a "raw" frequency-domain form. We need to apply IDCT and unzigging to transform them into color-space blocks. /// We can copy these blocks into -s afterwards. /// /// The pixel type @@ -477,7 +479,7 @@ namespace ImageSharp.Formats JpegScanDecoder.Init(&scanDecoder); scanDecoder.ComponentIndex = componentIndex; - DecodedBlockMemento.Array blockArray = this.DecodedBlocks[componentIndex]; + DecodedBlockArray blockArray = this.DecodedBlocks[componentIndex]; for (int i = 0; i < blockArray.Count; i++) { scanDecoder.LoadMemento(ref blockArray.Buffer[i]); @@ -1316,7 +1318,7 @@ namespace ImageSharp.Formats { int count = this.TotalMCUCount * this.ComponentArray[i].HorizontalFactor * this.ComponentArray[i].VerticalFactor; - this.DecodedBlocks[i] = new DecodedBlockMemento.Array(count); + this.DecodedBlocks[i] = new DecodedBlockArray(count); } } } From ca9b49ed7b7600c40ed4947baf8bcd33058c48fb Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 29 Jan 2017 18:15:33 +0100 Subject: [PATCH 002/142] cleanup + stylecop --- .../Components/Decoder/HuffmanTree.cs | 10 ++ .../Components/Decoder/JpegBlockProcessor.cs | 166 ++++++++++++++++++ .../JpegScanDecoder.ComputationData.cs | 15 -- .../Decoder/JpegScanDecoder.DataPointers.cs | 18 -- .../Components/Decoder/JpegScanDecoder.cs | 75 ++------ .../JpegDecoderCore.cs | 19 +- 6 files changed, 197 insertions(+), 106 deletions(-) create mode 100644 src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegBlockProcessor.cs diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/HuffmanTree.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/HuffmanTree.cs index 03013219c3..390e5dd150 100644 --- a/src/ImageSharp.Formats.Jpeg/Components/Decoder/HuffmanTree.cs +++ b/src/ImageSharp.Formats.Jpeg/Components/Decoder/HuffmanTree.cs @@ -12,6 +12,16 @@ namespace ImageSharp.Formats.Jpg /// internal struct HuffmanTree : IDisposable { + /// + /// The index of the AC table row + /// + public const int AcTableIndex = 1; + + /// + /// The index of the DC table row + /// + public const int DcTableIndex = 0; + /// /// The maximum (inclusive) number of codes in a Huffman tree. /// diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegBlockProcessor.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegBlockProcessor.cs new file mode 100644 index 0000000000..85018a06f1 --- /dev/null +++ b/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegBlockProcessor.cs @@ -0,0 +1,166 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats.Jpg +{ + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + + /// + /// Encapsulates the implementation of processing "raw" -s into Jpeg image channels. + /// + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct JpegBlockProcessor + { + /// + /// The + /// + private ComputationData data; + + /// + /// Pointers to elements of + /// + private DataPointers pointers; + + /// + /// The component index. + /// + private int componentIndex; + + /// + /// Initialize the instance on the stack. + /// + /// The instance + /// The current component index + public static void Init(JpegBlockProcessor* processor, int componentIndex) + { + processor->componentIndex = componentIndex; + processor->data = ComputationData.Create(); + processor->pointers = new DataPointers(&processor->data); + } + + /// + /// Dequantize, perform the inverse DCT and store the blocks to the into the corresponding instances. + /// + /// The instance + public void ProcessAllBlocks(JpegDecoderCore decoder) + { + DecodedBlockArray blockArray = decoder.DecodedBlocks[this.componentIndex]; + for (int i = 0; i < blockArray.Count; i++) + { + this.ProcessBlockColors(decoder, ref blockArray.Buffer[i]); + } + } + + /// + /// Dequantize, perform the inverse DCT and store decodedBlock.Block to the into the corresponding instance. + /// + /// The + /// The + private void ProcessBlockColors(JpegDecoderCore decoder, ref DecodedBlock decodedBlock) + { + this.data.Block = decodedBlock.Block; + int qtIndex = decoder.ComponentArray[this.componentIndex].Selector; + this.data.QuantiazationTable = decoder.QuantizationTables[qtIndex]; + + Block8x8F* b = this.pointers.Block; + + Block8x8F.UnZig(b, this.pointers.QuantiazationTable, this.pointers.Unzig); + + DCT.TransformIDCT(ref *b, ref *this.pointers.Temp1, ref *this.pointers.Temp2); + + var destChannel = decoder.GetDestinationChannel(this.componentIndex); + var destArea = destChannel.GetOffsetedSubAreaForBlock(decodedBlock.Bx, decodedBlock.By); + destArea.LoadColorsFrom(this.pointers.Temp1, this.pointers.Temp2); + } + + /// + /// Holds the "large" data blocks needed for computations. + /// + [StructLayout(LayoutKind.Sequential)] + public struct ComputationData + { + /// + /// Temporal block 1 to store intermediate and/or final computation results + /// + public Block8x8F Block; + + /// + /// Temporal block 1 to store intermediate and/or final computation results + /// + public Block8x8F Temp1; + + /// + /// Temporal block 2 to store intermediate and/or final computation results + /// + public Block8x8F Temp2; + + /// + /// The quantization table as + /// + public Block8x8F QuantiazationTable; + + /// + /// The jpeg unzig data + /// + public UnzigData Unzig; + + /// + /// Creates and initializes a new instance + /// + /// The + public static ComputationData Create() + { + ComputationData data = default(ComputationData); + data.Unzig = UnzigData.Create(); + return data; + } + } + + /// + /// Contains pointers to the memory regions of so they can be easily passed around to pointer based utility methods of + /// + public struct DataPointers + { + /// + /// Pointer to + /// + public Block8x8F* Block; + + /// + /// Pointer to + /// + public Block8x8F* Temp1; + + /// + /// Pointer to + /// + public Block8x8F* Temp2; + + /// + /// Pointer to + /// + public Block8x8F* QuantiazationTable; + + /// + /// Pointer to as int* + /// + public int* Unzig; + + /// + /// Initializes a new instance of the struct. + /// + /// Pointer to + internal DataPointers(ComputationData* dataPtr) + { + this.Block = &dataPtr->Block; + this.Temp1 = &dataPtr->Temp1; + this.Temp2 = &dataPtr->Temp2; + this.QuantiazationTable = &dataPtr->QuantiazationTable; + this.Unzig = dataPtr->Unzig.Data; + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.ComputationData.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.ComputationData.cs index 06f170be5a..7b910cdd24 100644 --- a/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.ComputationData.cs +++ b/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.ComputationData.cs @@ -23,21 +23,6 @@ namespace ImageSharp.Formats.Jpg /// public Block8x8F Block; - /// - /// Temporal block 1 to store intermediate and/or final computation results - /// - public Block8x8F Temp1; - - /// - /// Temporal block 2 to store intermediate and/or final computation results - /// - public Block8x8F Temp2; - - /// - /// The quantization table as - /// - public Block8x8F QuantiazationTable; - /// /// The jpeg unzig data /// diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.DataPointers.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.DataPointers.cs index b76ad59bb1..52e25f3a81 100644 --- a/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.DataPointers.cs +++ b/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.DataPointers.cs @@ -20,21 +20,6 @@ namespace ImageSharp.Formats.Jpg /// public Block8x8F* Block; - /// - /// Pointer to - /// - public Block8x8F* Temp1; - - /// - /// Pointer to - /// - public Block8x8F* Temp2; - - /// - /// Pointer to - /// - public Block8x8F* QuantiazationTable; - /// /// Pointer to as int* /// @@ -57,9 +42,6 @@ namespace ImageSharp.Formats.Jpg public DataPointers(ComputationData* basePtr) { this.Block = &basePtr->Block; - this.Temp1 = &basePtr->Temp1; - this.Temp2 = &basePtr->Temp2; - this.QuantiazationTable = &basePtr->QuantiazationTable; this.Unzig = basePtr->Unzig.Data; this.ComponentScan = (ComponentScan*)basePtr->ScanData; this.Dc = basePtr->Dc; diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs index 103ee60c02..a43c545cd4 100644 --- a/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs +++ b/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs @@ -7,10 +7,11 @@ namespace ImageSharp.Formats.Jpg { using System; using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; /// - /// Encapsulates the impementation of Jpeg SOS decoder. See JpegScanDecoder.md! - /// TODO: Split JpegScanDecoder: 1. JpegScanDecoder for Huffman-decoding () 2. JpegBlockProcessor for processing () + /// Encapsulates the impementation of Jpeg SOS Huffman decoding. See JpegScanDecoder.md! + /// /// and are the spectral selection bounds. /// and are the successive approximation high and low values. /// The spec calls these values Ss, Se, Ah and Al. @@ -26,17 +27,21 @@ namespace ImageSharp.Formats.Jpg /// significant bit. /// For baseline JPEGs, these parameters are hard-coded to 0/63/0/0. /// + [StructLayout(LayoutKind.Sequential)] internal unsafe partial struct JpegScanDecoder { + // The JpegScanDecoder members should be ordered in a way that results in optimal memory layout. +#pragma warning disable SA1202 // ElementsMustBeOrderedByAccess + /// - /// The AC table index + /// The buffer /// - public const int AcTableIndex = 1; + private ComputationData data; /// - /// The DC table index + /// Pointers to elements of /// - public const int DcTableIndex = 0; + private DataPointers pointers; /// /// The current component index @@ -88,16 +93,6 @@ namespace ImageSharp.Formats.Jpg /// private int eobRun; - /// - /// Pointers to elements of - /// - private DataPointers pointers; - - /// - /// The buffer - /// - private ComputationData data; - /// /// Initializes a default-constructed instance for reading data from -s stream. /// @@ -105,30 +100,10 @@ namespace ImageSharp.Formats.Jpg /// The instance /// The remaining bytes in the segment block. public static void InitStreamReading(JpegScanDecoder* p, JpegDecoderCore decoder, int remaining) - { - Init(p); - p->InitStreamReadingImpl(decoder, remaining); - } - - /// - /// Initializes a default-constructed instance, filling the data and setting the pointers. - /// - /// Pointer to on the stack - public static void Init(JpegScanDecoder* p) { p->data = ComputationData.Create(); p->pointers = new DataPointers(&p->data); - } - - /// - /// Loads the data from the given into the block. - /// - /// The - public void LoadMemento(ref DecodedBlock memento) - { - this.bx = memento.Bx; - this.by = memento.By; - this.data.Block = memento.Block; + p->InitStreamReadingImpl(decoder, remaining); } /// @@ -251,26 +226,6 @@ namespace ImageSharp.Formats.Jpg } } - /// - /// Dequantize, perform the inverse DCT and store the block to the into the corresponding instances. - /// - /// The instance - public void ProcessBlockColors(JpegDecoderCore decoder) - { - int qtIndex = decoder.ComponentArray[this.ComponentIndex].Selector; - this.data.QuantiazationTable = decoder.QuantizationTables[qtIndex]; - - Block8x8F* b = this.pointers.Block; - - Block8x8F.UnZig(b, this.pointers.QuantiazationTable, this.pointers.Unzig); - - DCT.TransformIDCT(ref *b, ref *this.pointers.Temp1, ref *this.pointers.Temp2); - - var destChannel = decoder.GetDestinationChannel(this.ComponentIndex); - var destArea = destChannel.GetOffsetedSubAreaForBlock(this.bx, this.by); - destArea.LoadColorsFrom(this.pointers.Temp1, this.pointers.Temp2); - } - private void ResetDc() { Unsafe.InitBlock(this.pointers.Dc, default(byte), sizeof(int) * JpegDecoderCore.MaxComponents); @@ -351,8 +306,7 @@ namespace ImageSharp.Formats.Jpg private void DecodeBlock(JpegDecoderCore decoder, int scanIndex) { var b = this.pointers.Block; - DecoderErrorCode errorCode; - int huffmannIdx = (AcTableIndex * HuffmanTree.ThRowSize) + this.pointers.ComponentScan[scanIndex].AcTableSelector; + int huffmannIdx = (HuffmanTree.AcTableIndex * HuffmanTree.ThRowSize) + this.pointers.ComponentScan[scanIndex].AcTableSelector; if (this.ah != 0) { this.Refine(ref decoder.InputProcessor, ref decoder.HuffmanTrees[huffmannIdx], 1 << this.al); @@ -360,13 +314,14 @@ namespace ImageSharp.Formats.Jpg else { int zig = this.zigStart; + DecoderErrorCode errorCode; if (zig == 0) { zig++; // Decode the DC coefficient, as specified in section F.2.2.1. int value; - int huffmanIndex = (DcTableIndex * HuffmanTree.ThRowSize) + this.pointers.ComponentScan[scanIndex].DcTableSelector; + int huffmanIndex = (HuffmanTree.DcTableIndex * HuffmanTree.ThRowSize) + this.pointers.ComponentScan[scanIndex].DcTableSelector; errorCode = decoder.InputProcessor.DecodeHuffmanUnsafe( ref decoder.HuffmanTrees[huffmanIndex], out value); diff --git a/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs b/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs index fd06018a22..72655a5007 100644 --- a/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs @@ -102,9 +102,9 @@ namespace ImageSharp.Formats /// /// Gets the array of -s storing the "raw" frequency-domain decoded blocks. - /// We need to apply IDCT and unzigging to transform them into color-space blocks. + /// We need to apply IDCT, dequantiazition and unzigging to transform them into color-space blocks. /// This is done by . - /// When ==true, we are touching these blocks each time we process a Scan. + /// When ==true, we are touching these blocks multiple times - each time we process a Scan. /// public DecodedBlockArray[] DecodedBlocks { get; } @@ -463,7 +463,7 @@ namespace ImageSharp.Formats /// /// Process the blocks in into Jpeg image channels ( and ) - /// are in a "raw" frequency-domain form. We need to apply IDCT and unzigging to transform them into color-space blocks. + /// are in a "raw" frequency-domain form. We need to apply IDCT, dequantization and unzigging to transform them into color-space blocks. /// We can copy these blocks into -s afterwards. /// /// The pixel type @@ -475,16 +475,9 @@ namespace ImageSharp.Formats this.ComponentCount, componentIndex => { - JpegScanDecoder scanDecoder = default(JpegScanDecoder); - JpegScanDecoder.Init(&scanDecoder); - - scanDecoder.ComponentIndex = componentIndex; - DecodedBlockArray blockArray = this.DecodedBlocks[componentIndex]; - for (int i = 0; i < blockArray.Count; i++) - { - scanDecoder.LoadMemento(ref blockArray.Buffer[i]); - scanDecoder.ProcessBlockColors(this); - } + JpegBlockProcessor processor = default(JpegBlockProcessor); + JpegBlockProcessor.Init(&processor, componentIndex); + processor.ProcessAllBlocks(this); }); } From 8e56aaaf68177d6e6a14c5dee2a7d4772452895c Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 29 Jan 2017 19:16:36 +0100 Subject: [PATCH 003/142] finished JpegBlockProcessor refactor --- .../Components/Decoder/DecodedBlock.cs | 18 +++++------------- .../Components/Decoder/JpegScanDecoder.cs | 2 +- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/DecodedBlock.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/DecodedBlock.cs index bc93a18f7c..900d77ec46 100644 --- a/src/ImageSharp.Formats.Jpeg/Components/Decoder/DecodedBlock.cs +++ b/src/ImageSharp.Formats.Jpeg/Components/Decoder/DecodedBlock.cs @@ -35,24 +35,16 @@ namespace ImageSharp.Formats.Jpg public Block8x8F Block; /// - /// Store the block data into a at the given index of an . + /// Store the block data into a /// - /// The array - /// The index in the array /// X coordinate of the block /// Y coordinate of the block /// The - public static void Store(ref DecodedBlockArray blockArray, int index, int bx, int by, ref Block8x8F block) + public void SaveBlock(int bx, int by, ref Block8x8F block) { - if (index >= blockArray.Count) - { - throw new IndexOutOfRangeException("Block index is out of range in DecodedBlock.Store()!"); - } - - blockArray.Buffer[index].Initialized = true; - blockArray.Buffer[index].Bx = bx; - blockArray.Buffer[index].By = by; - blockArray.Buffer[index].Block = block; + this.Bx = bx; + this.By = by; + this.Block = block; } } } \ No newline at end of file diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs index a43c545cd4..10f859e420 100644 --- a/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs +++ b/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs @@ -180,7 +180,7 @@ namespace ImageSharp.Formats.Jpg // Store the decoded block DecodedBlockArray blocks = decoder.DecodedBlocks[this.ComponentIndex]; - DecodedBlock.Store(ref blocks, blockIndex, this.bx, this.by, ref this.data.Block); + blocks.Buffer[blockIndex].SaveBlock(this.bx, this.by, ref this.data.Block); } // for j From d5064cadc5132c19aaa10a2df255a74c44bfb154 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 1 Feb 2017 19:29:38 +1100 Subject: [PATCH 004/142] Make all images implement IDisposable --- .../Convolution/Convolution2DProcessor.cs | 4 +- .../Convolution/Convolution2PassProcessor.cs | 21 +- .../Effects/OilPaintingProcessor.cs | 4 +- .../Processors/Effects/PixelateProcessor.cs | 4 +- .../Transforms/CompandingResizeProcessor.cs | 159 ++++++------ .../Processors/Transforms/CropProcessor.cs | 4 +- .../Processors/Transforms/FlipProcessor.cs | 8 +- .../Processors/Transforms/ResizeProcessor.cs | 159 ++++++------ .../Processors/Transforms/RotateProcessor.cs | 16 +- .../Processors/Transforms/SkewProcessor.cs | 4 +- src/ImageSharp/Image/IImageBase{TColor}.cs | 4 +- src/ImageSharp/Image/ImageBase{TColor}.cs | 111 ++++++++- src/ImageSharp/Image/Image{TColor}.cs | 16 +- src/ImageSharp/Image/PixelAccessor{TColor}.cs | 4 +- src/ImageSharp/Image/PixelPool{TColor}.cs | 50 ++++ .../Drawing/DrawBeziers.cs | 18 +- .../Drawing/DrawLines.cs | 24 +- .../Drawing/DrawPolygon.cs | 24 +- .../Drawing/FillPolygon.cs | 23 +- .../Drawing/FillRectangle.cs | 29 ++- .../Drawing/FillWithPattern.cs | 12 +- .../ImageSharp.Benchmarks/Image/CopyPixels.cs | 34 +-- .../ImageSharp.Benchmarks/Image/DecodeBmp.cs | 6 +- .../Image/DecodeFilteredPng.cs | 17 +- .../ImageSharp.Benchmarks/Image/DecodeGif.cs | 6 +- .../ImageSharp.Benchmarks/Image/DecodeJpeg.cs | 6 +- .../ImageSharp.Benchmarks/Image/DecodePng.cs | 6 +- .../ImageSharp.Benchmarks/Image/EncodeBmp.cs | 8 + .../Image/EncodeBmpMultiple.cs | 4 +- .../ImageSharp.Benchmarks/Image/EncodeGif.cs | 8 + .../ImageSharp.Benchmarks/Image/EncodeJpeg.cs | 8 + .../ImageSharp.Benchmarks/Image/EncodePng.cs | 8 + .../Image/GetSetPixel.cs | 10 +- tests/ImageSharp.Benchmarks/Samplers/Crop.cs | 8 +- .../Samplers/DetectEdges.cs | 6 + .../ImageSharp.Benchmarks/Samplers/Resize.cs | 16 +- .../ImageSharp.Tests/Drawing/BeziersTests.cs | 137 ++++++----- .../ImageSharp.Tests/Drawing/DrawImageTest.cs | 21 +- .../ImageSharp.Tests/Drawing/DrawPathTests.cs | 101 ++++---- .../Drawing/FillPatternTests.cs | 71 +++--- .../Drawing/FillSolidBrushTests.cs | 100 ++++---- .../Drawing/LineComplexPolygonTests.cs | 226 +++++++++--------- tests/ImageSharp.Tests/Drawing/LineTests.cs | 155 ++++++------ .../ImageSharp.Tests/Drawing/PolygonTests.cs | 112 ++++----- .../Drawing/RecolorImageTest.cs | 32 +-- .../Drawing/SolidBezierTests.cs | 102 ++++---- .../Drawing/SolidComplexPolygonTests.cs | 138 +++++------ .../Drawing/SolidPolygonTests.cs | 156 ++++++------ .../Formats/Bmp/BitmapTests.cs | 15 +- .../Formats/GeneralFormatTests.cs | 101 ++++---- .../Formats/Jpg/BadEofJpegTests.cs | 16 +- .../Formats/Jpg/JpegDecoderTests.cs | 61 ++--- .../Formats/Jpg/JpegEncoderTests.cs | 44 ++-- .../Formats/Jpg/JpegUtilsTests.cs | 85 +++---- .../Jpg/ReferenceImplementationsTests.cs | 4 +- .../ImageSharp.Tests/Formats/Png/PngTests.cs | 20 +- tests/ImageSharp.Tests/Image/ImageTests.cs | 9 +- .../Image/PixelAccessorTests.cs | 118 +++++---- .../Processors/Filters/AlphaTest.cs | 20 +- .../Processors/Filters/AutoOrientTests.cs | 19 +- .../Processors/Filters/BackgroundColorTest.cs | 8 +- .../Processors/Filters/BinaryThreshold.cs | 10 +- .../Processors/Filters/BlackWhiteTest.cs | 8 +- .../Processors/Filters/BoxBlurTest.cs | 10 +- .../Processors/Filters/BrightnessTest.cs | 10 +- .../Processors/Filters/ColorBlindnessTest.cs | 10 +- .../Processors/Filters/ContrastTest.cs | 10 +- .../Processors/Filters/CropTest.cs | 8 +- .../Processors/Filters/DetectEdgesTest.cs | 15 +- .../Processors/Filters/EntropyCropTest.cs | 10 +- .../Processors/Filters/FlipTests.cs | 10 +- .../Processors/Filters/GaussianBlurTest.cs | 10 +- .../Processors/Filters/GaussianSharpenTest.cs | 10 +- .../Processors/Filters/GlowTest.cs | 31 +-- .../Processors/Filters/GrayscaleTest.cs | 10 +- .../Processors/Filters/HueTest.cs | 10 +- .../Processors/Filters/InvertTest.cs | 16 +- .../Processors/Filters/KodachromeTest.cs | 8 +- .../Processors/Filters/LomographTest.cs | 15 +- .../Processors/Filters/OilPaintTest.cs | 21 +- .../Processors/Filters/PadTest.cs | 8 +- .../Processors/Filters/PixelateTest.cs | 8 +- .../Processors/Filters/PolaroidTest.cs | 8 +- .../Processors/Filters/ResizeTests.cs | 76 +++--- .../Processors/Filters/RotateFlipTest.cs | 10 +- .../Processors/Filters/RotateTest.cs | 20 +- .../Processors/Filters/SaturationTest.cs | 8 +- .../Processors/Filters/SepiaTest.cs | 6 +- .../Processors/Filters/SkewTest.cs | 12 +- .../Processors/Filters/VignetteTest.cs | 31 +-- .../Profiles/Exif/ExifProfileTests.cs | 3 + .../Exif/ExifTagDescriptionAttributeTests.cs | 2 +- .../Profiles/Exif/ExifValueTests.cs | 7 +- 93 files changed, 1671 insertions(+), 1480 deletions(-) create mode 100644 src/ImageSharp/Image/PixelPool{TColor}.cs diff --git a/src/ImageSharp.Processing/Processors/Convolution/Convolution2DProcessor.cs b/src/ImageSharp.Processing/Processors/Convolution/Convolution2DProcessor.cs index d6ea42f0c4..f77f1f4394 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/Convolution2DProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/Convolution2DProcessor.cs @@ -54,9 +54,9 @@ namespace ImageSharp.Processing.Processors int maxY = endY - 1; int maxX = endX - 1; - TColor[] target = new TColor[source.Width * source.Height]; + TColor[] target = PixelPool.RentPixels(source.Width * source.Height); using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock(source.Width, source.Height)) + using (PixelAccessor targetPixels = target.Lock(source.Width, source.Height)) { Parallel.For( startY, diff --git a/src/ImageSharp.Processing/Processors/Convolution/Convolution2PassProcessor.cs b/src/ImageSharp.Processing/Processors/Convolution/Convolution2PassProcessor.cs index ad7ed83ed6..ca343b8680 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/Convolution2PassProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/Convolution2PassProcessor.cs @@ -45,13 +45,20 @@ namespace ImageSharp.Processing.Processors int width = source.Width; int height = source.Height; - TColor[] target = new TColor[width * height]; - TColor[] firstPass = new TColor[width * height]; + TColor[] target = PixelPool.RentPixels(width * height); + TColor[] firstPass = PixelPool.RentPixels(width * height); - this.ApplyConvolution(width, height, firstPass, source.Pixels, sourceRectangle, kernelX); - this.ApplyConvolution(width, height, target, firstPass, sourceRectangle, kernelY); + try + { + this.ApplyConvolution(width, height, firstPass, source.Pixels, sourceRectangle, kernelX); + this.ApplyConvolution(width, height, target, firstPass, sourceRectangle, kernelY); - source.SetPixels(width, height, target); + source.SetPixels(width, height, target); + } + finally + { + PixelPool.ReturnPixels(firstPass); + } } /// @@ -80,8 +87,8 @@ namespace ImageSharp.Processing.Processors int maxY = endY - 1; int maxX = endX - 1; - using (PixelAccessor sourcePixels = source.Lock(width, height)) - using (PixelAccessor targetPixels = target.Lock(width, height)) + using (PixelAccessor sourcePixels = source.Lock(width, height)) + using (PixelAccessor targetPixels = target.Lock(width, height)) { Parallel.For( startY, diff --git a/src/ImageSharp.Processing/Processors/Effects/OilPaintingProcessor.cs b/src/ImageSharp.Processing/Processors/Effects/OilPaintingProcessor.cs index 9e12a2a91b..8d23402561 100644 --- a/src/ImageSharp.Processing/Processors/Effects/OilPaintingProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Effects/OilPaintingProcessor.cs @@ -67,9 +67,9 @@ namespace ImageSharp.Processing.Processors startX = 0; } - TColor[] target = new TColor[source.Width * source.Height]; + TColor[] target = PixelPool.RentPixels(source.Width * source.Height); using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock(source.Width, source.Height)) + using (PixelAccessor targetPixels = target.Lock(source.Width, source.Height)) { Parallel.For( minY, diff --git a/src/ImageSharp.Processing/Processors/Effects/PixelateProcessor.cs b/src/ImageSharp.Processing/Processors/Effects/PixelateProcessor.cs index 9c9cf92fec..d44858061d 100644 --- a/src/ImageSharp.Processing/Processors/Effects/PixelateProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Effects/PixelateProcessor.cs @@ -63,10 +63,10 @@ namespace ImageSharp.Processing.Processors // Get the range on the y-plane to choose from. IEnumerable range = EnumerableExtensions.SteppedRange(minY, i => i < maxY, size); - TColor[] target = new TColor[source.Width * source.Height]; + TColor[] target = PixelPool.RentPixels(source.Width * source.Height); using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock(source.Width, source.Height)) + using (PixelAccessor targetPixels = target.Lock(source.Width, source.Height)) { Parallel.ForEach( range, diff --git a/src/ImageSharp.Processing/Processors/Transforms/CompandingResizeProcessor.cs b/src/ImageSharp.Processing/Processors/Transforms/CompandingResizeProcessor.cs index a392de0513..d9e4f6675a 100644 --- a/src/ImageSharp.Processing/Processors/Transforms/CompandingResizeProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Transforms/CompandingResizeProcessor.cs @@ -66,103 +66,112 @@ namespace ImageSharp.Processing.Processors int minY = Math.Max(0, startY); int maxY = Math.Min(height, endY); - TColor[] target = new TColor[width * height]; + TColor[] firstPass = null; - if (this.Sampler is NearestNeighborResampler) + try { - // Scaling factors - float widthFactor = sourceRectangle.Width / (float)this.ResizeRectangle.Width; - float heightFactor = sourceRectangle.Height / (float)this.ResizeRectangle.Height; + TColor[] target = PixelPool.RentPixels(width * height); + if (this.Sampler is NearestNeighborResampler) + { + // Scaling factors + float widthFactor = sourceRectangle.Width / (float)this.ResizeRectangle.Width; + float heightFactor = sourceRectangle.Height / (float)this.ResizeRectangle.Height; + + using (PixelAccessor sourcePixels = source.Lock()) + using (PixelAccessor targetPixels = target.Lock(width, height)) + { + Parallel.For( + minY, + maxY, + this.ParallelOptions, + y => + { + // Y coordinates of source points + int originY = (int)((y - startY) * heightFactor); + + for (int x = minX; x < maxX; x++) + { + // X coordinates of source points + targetPixels[x, y] = sourcePixels[(int)((x - startX) * widthFactor), originY]; + } + }); + } + + // Break out now. + source.SetPixels(width, height, target); + return; + } + // Interpolate the image using the calculated weights. + // A 2-pass 1D algorithm appears to be faster than splitting a 1-pass 2D algorithm + // First process the columns. Since we are not using multiple threads startY and endY + // are the upper and lower bounds of the source rectangle. + firstPass = PixelPool.RentPixels(width * source.Height); using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock(width, height)) + using (PixelAccessor firstPassPixels = firstPass.Lock(width, source.Height)) + using (PixelAccessor targetPixels = target.Lock(width, height)) { Parallel.For( - minY, - maxY, + 0, + sourceRectangle.Height, this.ParallelOptions, y => { - // Y coordinates of source points - int originY = (int)((y - startY) * heightFactor); - for (int x = minX; x < maxX; x++) { - // X coordinates of source points - targetPixels[x, y] = sourcePixels[(int)((x - startX) * widthFactor), originY]; - } - }); - } + // Ensure offsets are normalised for cropping and padding. + Weight[] horizontalValues = this.HorizontalWeights[x - startX].Values; - // Break out now. - source.SetPixels(width, height, target); - return; - } + // Destination color components + Vector4 destination = Vector4.Zero; - // Interpolate the image using the calculated weights. - // A 2-pass 1D algorithm appears to be faster than splitting a 1-pass 2D algorithm - // First process the columns. Since we are not using multiple threads startY and endY - // are the upper and lower bounds of the source rectangle. - TColor[] firstPass = new TColor[width * source.Height]; - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor firstPassPixels = firstPass.Lock(width, source.Height)) - using (PixelAccessor targetPixels = target.Lock(width, height)) - { - Parallel.For( - 0, - sourceRectangle.Height, - this.ParallelOptions, - y => - { - for (int x = minX; x < maxX; x++) - { - // Ensure offsets are normalised for cropping and padding. - Weight[] horizontalValues = this.HorizontalWeights[x - startX].Values; + for (int i = 0; i < horizontalValues.Length; i++) + { + Weight xw = horizontalValues[i]; + destination += sourcePixels[xw.Index, y].ToVector4().Expand() * xw.Value; + } - // Destination color components - Vector4 destination = Vector4.Zero; - - for (int i = 0; i < horizontalValues.Length; i++) - { - Weight xw = horizontalValues[i]; - destination += sourcePixels[xw.Index, y].ToVector4().Expand() * xw.Value; + TColor d = default(TColor); + d.PackFromVector4(destination.Compress()); + firstPassPixels[x, y] = d; } + }); - TColor d = default(TColor); - d.PackFromVector4(destination.Compress()); - firstPassPixels[x, y] = d; - } - }); - - // Now process the rows. - Parallel.For( - minY, - maxY, - this.ParallelOptions, - y => - { - // Ensure offsets are normalised for cropping and padding. - Weight[] verticalValues = this.VerticalWeights[y - startY].Values; - - for (int x = 0; x < width; x++) + // Now process the rows. + Parallel.For( + minY, + maxY, + this.ParallelOptions, + y => { - // Destination color components - Vector4 destination = Vector4.Zero; + // Ensure offsets are normalised for cropping and padding. + Weight[] verticalValues = this.VerticalWeights[y - startY].Values; - for (int i = 0; i < verticalValues.Length; i++) + for (int x = 0; x < width; x++) { - Weight yw = verticalValues[i]; - destination += firstPassPixels[x, yw.Index].ToVector4().Expand() * yw.Value; + // Destination color components + Vector4 destination = Vector4.Zero; + + for (int i = 0; i < verticalValues.Length; i++) + { + Weight yw = verticalValues[i]; + destination += firstPassPixels[x, yw.Index].ToVector4().Expand() * yw.Value; + } + + TColor d = default(TColor); + d.PackFromVector4(destination.Compress()); + targetPixels[x, y] = d; } + }); + } - TColor d = default(TColor); - d.PackFromVector4(destination.Compress()); - targetPixels[x, y] = d; - } - }); + source.SetPixels(width, height, target); + } + finally + { + // We don't return target or source pixels as they are handled in the image itself. + PixelPool.ReturnPixels(firstPass); } - - source.SetPixels(width, height, target); } } } \ No newline at end of file diff --git a/src/ImageSharp.Processing/Processors/Transforms/CropProcessor.cs b/src/ImageSharp.Processing/Processors/Transforms/CropProcessor.cs index 27b5bef0c5..31bd08090d 100644 --- a/src/ImageSharp.Processing/Processors/Transforms/CropProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Transforms/CropProcessor.cs @@ -42,10 +42,10 @@ namespace ImageSharp.Processing.Processors int minX = Math.Max(this.CropRectangle.X, sourceRectangle.X); int maxX = Math.Min(this.CropRectangle.Right, sourceRectangle.Right); - TColor[] target = new TColor[this.CropRectangle.Width * this.CropRectangle.Height]; + TColor[] target = PixelPool.RentPixels(this.CropRectangle.Width * this.CropRectangle.Height); using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock(this.CropRectangle.Width, this.CropRectangle.Height)) + using (PixelAccessor targetPixels = target.Lock(this.CropRectangle.Width, this.CropRectangle.Height)) { Parallel.For( minY, diff --git a/src/ImageSharp.Processing/Processors/Transforms/FlipProcessor.cs b/src/ImageSharp.Processing/Processors/Transforms/FlipProcessor.cs index ba21dced7a..374d54fa28 100644 --- a/src/ImageSharp.Processing/Processors/Transforms/FlipProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Transforms/FlipProcessor.cs @@ -55,10 +55,10 @@ namespace ImageSharp.Processing.Processors int height = source.Height; int halfHeight = (int)Math.Ceiling(source.Height * .5F); - TColor[] target = new TColor[width * height]; + TColor[] target = PixelPool.RentPixels(width * height); using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock(width, height)) + using (PixelAccessor targetPixels = target.Lock(width, height)) { Parallel.For( 0, @@ -89,10 +89,10 @@ namespace ImageSharp.Processing.Processors int height = source.Height; int halfWidth = (int)Math.Ceiling(width * .5F); - TColor[] target = new TColor[width * height]; + TColor[] target = PixelPool.RentPixels(width * height); using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock(width, height)) + using (PixelAccessor targetPixels = target.Lock(width, height)) { Parallel.For( 0, diff --git a/src/ImageSharp.Processing/Processors/Transforms/ResizeProcessor.cs b/src/ImageSharp.Processing/Processors/Transforms/ResizeProcessor.cs index 687e452e6e..108391713d 100644 --- a/src/ImageSharp.Processing/Processors/Transforms/ResizeProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Transforms/ResizeProcessor.cs @@ -65,103 +65,112 @@ namespace ImageSharp.Processing.Processors int minY = Math.Max(0, startY); int maxY = Math.Min(height, endY); - TColor[] target = new TColor[width * height]; + TColor[] firstPass = null; - if (this.Sampler is NearestNeighborResampler) + try { - // Scaling factors - float widthFactor = sourceRectangle.Width / (float)this.ResizeRectangle.Width; - float heightFactor = sourceRectangle.Height / (float)this.ResizeRectangle.Height; + TColor[] target = PixelPool.RentPixels(width * height); + if (this.Sampler is NearestNeighborResampler) + { + // Scaling factors + float widthFactor = sourceRectangle.Width / (float)this.ResizeRectangle.Width; + float heightFactor = sourceRectangle.Height / (float)this.ResizeRectangle.Height; + + using (PixelAccessor sourcePixels = source.Lock()) + using (PixelAccessor targetPixels = target.Lock(width, height)) + { + Parallel.For( + minY, + maxY, + this.ParallelOptions, + y => + { + // Y coordinates of source points + int originY = (int)((y - startY) * heightFactor); + + for (int x = minX; x < maxX; x++) + { + // X coordinates of source points + targetPixels[x, y] = sourcePixels[(int)((x - startX) * widthFactor), originY]; + } + }); + } + + // Break out now. + source.SetPixels(width, height, target); + return; + } + // Interpolate the image using the calculated weights. + // A 2-pass 1D algorithm appears to be faster than splitting a 1-pass 2D algorithm + // First process the columns. Since we are not using multiple threads startY and endY + // are the upper and lower bounds of the source rectangle. + firstPass = PixelPool.RentPixels(width * source.Height); using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock(width, height)) + using (PixelAccessor firstPassPixels = firstPass.Lock(width, source.Height)) + using (PixelAccessor targetPixels = target.Lock(width, height)) { Parallel.For( - minY, - maxY, + 0, + sourceRectangle.Height, this.ParallelOptions, y => { - // Y coordinates of source points - int originY = (int)((y - startY) * heightFactor); - for (int x = minX; x < maxX; x++) { - // X coordinates of source points - targetPixels[x, y] = sourcePixels[(int)((x - startX) * widthFactor), originY]; - } - }); - } + // Ensure offsets are normalised for cropping and padding. + Weight[] horizontalValues = this.HorizontalWeights[x - startX].Values; - // Break out now. - source.SetPixels(width, height, target); - return; - } + // Destination color components + Vector4 destination = Vector4.Zero; - // Interpolate the image using the calculated weights. - // A 2-pass 1D algorithm appears to be faster than splitting a 1-pass 2D algorithm - // First process the columns. Since we are not using multiple threads startY and endY - // are the upper and lower bounds of the source rectangle. - TColor[] firstPass = new TColor[width * source.Height]; - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor firstPassPixels = firstPass.Lock(width, source.Height)) - using (PixelAccessor targetPixels = target.Lock(width, height)) - { - Parallel.For( - 0, - sourceRectangle.Height, - this.ParallelOptions, - y => - { - for (int x = minX; x < maxX; x++) - { - // Ensure offsets are normalised for cropping and padding. - Weight[] horizontalValues = this.HorizontalWeights[x - startX].Values; + for (int i = 0; i < horizontalValues.Length; i++) + { + Weight xw = horizontalValues[i]; + destination += sourcePixels[xw.Index, y].ToVector4() * xw.Value; + } - // Destination color components - Vector4 destination = Vector4.Zero; - - for (int i = 0; i < horizontalValues.Length; i++) - { - Weight xw = horizontalValues[i]; - destination += sourcePixels[xw.Index, y].ToVector4() * xw.Value; + TColor d = default(TColor); + d.PackFromVector4(destination); + firstPassPixels[x, y] = d; } + }); - TColor d = default(TColor); - d.PackFromVector4(destination); - firstPassPixels[x, y] = d; - } - }); - - // Now process the rows. - Parallel.For( - minY, - maxY, - this.ParallelOptions, - y => - { - // Ensure offsets are normalised for cropping and padding. - Weight[] verticalValues = this.VerticalWeights[y - startY].Values; - - for (int x = 0; x < width; x++) + // Now process the rows. + Parallel.For( + minY, + maxY, + this.ParallelOptions, + y => { - // Destination color components - Vector4 destination = Vector4.Zero; + // Ensure offsets are normalised for cropping and padding. + Weight[] verticalValues = this.VerticalWeights[y - startY].Values; - for (int i = 0; i < verticalValues.Length; i++) + for (int x = 0; x < width; x++) { - Weight yw = verticalValues[i]; - destination += firstPassPixels[x, yw.Index].ToVector4() * yw.Value; + // Destination color components + Vector4 destination = Vector4.Zero; + + for (int i = 0; i < verticalValues.Length; i++) + { + Weight yw = verticalValues[i]; + destination += firstPassPixels[x, yw.Index].ToVector4() * yw.Value; + } + + TColor d = default(TColor); + d.PackFromVector4(destination); + targetPixels[x, y] = d; } + }); + } - TColor d = default(TColor); - d.PackFromVector4(destination); - targetPixels[x, y] = d; - } - }); + source.SetPixels(width, height, target); + } + finally + { + // We don't return target or source pixels as they are handled in the image itself. + PixelPool.ReturnPixels(firstPass); } - - source.SetPixels(width, height, target); } } } \ No newline at end of file diff --git a/src/ImageSharp.Processing/Processors/Transforms/RotateProcessor.cs b/src/ImageSharp.Processing/Processors/Transforms/RotateProcessor.cs index 9b9534b390..313542adc9 100644 --- a/src/ImageSharp.Processing/Processors/Transforms/RotateProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Transforms/RotateProcessor.cs @@ -42,10 +42,10 @@ namespace ImageSharp.Processing.Processors int height = this.CanvasRectangle.Height; int width = this.CanvasRectangle.Width; Matrix3x2 matrix = this.GetCenteredMatrix(source, this.processMatrix); - TColor[] target = new TColor[width * height]; + TColor[] target = PixelPool.RentPixels(width * height); using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock(width, height)) + using (PixelAccessor targetPixels = target.Lock(width, height)) { Parallel.For( 0, @@ -124,10 +124,10 @@ namespace ImageSharp.Processing.Processors { int width = source.Width; int height = source.Height; - TColor[] target = new TColor[width * height]; + TColor[] target = PixelPool.RentPixels(width * height); using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock(height, width)) + using (PixelAccessor targetPixels = target.Lock(height, width)) { Parallel.For( 0, @@ -156,10 +156,10 @@ namespace ImageSharp.Processing.Processors { int width = source.Width; int height = source.Height; - TColor[] target = new TColor[width * height]; + TColor[] target = PixelPool.RentPixels(width * height); using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock(width, height)) + using (PixelAccessor targetPixels = target.Lock(width, height)) { Parallel.For( 0, @@ -187,10 +187,10 @@ namespace ImageSharp.Processing.Processors { int width = source.Width; int height = source.Height; - TColor[] target = new TColor[width * height]; + TColor[] target = PixelPool.RentPixels(width * height); using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock(height, width)) + using (PixelAccessor targetPixels = target.Lock(height, width)) { Parallel.For( 0, diff --git a/src/ImageSharp.Processing/Processors/Transforms/SkewProcessor.cs b/src/ImageSharp.Processing/Processors/Transforms/SkewProcessor.cs index c94f69358b..d2d2a129df 100644 --- a/src/ImageSharp.Processing/Processors/Transforms/SkewProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Transforms/SkewProcessor.cs @@ -42,10 +42,10 @@ namespace ImageSharp.Processing.Processors int height = this.CanvasRectangle.Height; int width = this.CanvasRectangle.Width; Matrix3x2 matrix = this.GetCenteredMatrix(source, this.processMatrix); - TColor[] target = new TColor[width * height]; + TColor[] target = PixelPool.RentPixels(width * height); using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock(width, height)) + using (PixelAccessor targetPixels = target.Lock(width, height)) { Parallel.For( 0, diff --git a/src/ImageSharp/Image/IImageBase{TColor}.cs b/src/ImageSharp/Image/IImageBase{TColor}.cs index bd5b317128..39f3fba67b 100644 --- a/src/ImageSharp/Image/IImageBase{TColor}.cs +++ b/src/ImageSharp/Image/IImageBase{TColor}.cs @@ -11,11 +11,13 @@ namespace ImageSharp /// Encapsulates the basic properties and methods required to manipulate images in varying formats. /// /// The pixel format. - public interface IImageBase : IImageBase + public interface IImageBase : IImageBase, IDisposable where TColor : struct, IPackedPixel, IEquatable { /// /// Gets the pixels as an array of the given packed pixel format. + /// Important. Due to the nature in the way this is constructed do not rely on the length + /// of the array for calculations. Use Width * Height. /// TColor[] Pixels { get; } diff --git a/src/ImageSharp/Image/ImageBase{TColor}.cs b/src/ImageSharp/Image/ImageBase{TColor}.cs index 8feaabdabf..c58f9412d0 100644 --- a/src/ImageSharp/Image/ImageBase{TColor}.cs +++ b/src/ImageSharp/Image/ImageBase{TColor}.cs @@ -6,6 +6,7 @@ namespace ImageSharp { using System; + using System.Buffers; using System.Diagnostics; /// @@ -14,14 +15,30 @@ namespace ImageSharp /// /// The pixel format. [DebuggerDisplay("Image: {Width}x{Height}")] - public abstract class ImageBase : IImageBase + public abstract class ImageBase : IImageBase // IImageBase implements IDisposable where TColor : struct, IPackedPixel, IEquatable { + /// + /// The used to pool data. TODO: Choose sensible default size and count + /// + private static readonly ArrayPool ArrayPool = ArrayPool.Create(int.MaxValue, 50); + /// /// The image pixels /// private TColor[] pixelBuffer; + /// + /// A value indicating whether this instance of the given entity has been disposed. + /// + /// if this instance has been disposed; otherwise, . + /// + /// If the entity is disposed, it must not be disposed a second time. The isDisposed field is set the first time the entity + /// is disposed. If the isDisposed field is true, then the Dispose() method will not dispose again. This help not to prolong the entity's + /// life in the Garbage Collector. + /// + private bool isDisposed; + /// /// Initializes a new instance of the class. /// @@ -41,7 +58,7 @@ namespace ImageSharp /// /// The configuration providing initialization code which allows extending the library. /// - /// + /// /// Thrown if either or are less than or equal to 0. /// protected ImageBase(int width, int height, Configuration configuration = null) @@ -67,15 +84,24 @@ namespace ImageSharp this.Height = other.Height; this.CopyProperties(other); - // Copy the pixels. Unsafe.CopyBlock gives us a nice speed boost here. - this.pixelBuffer = new TColor[this.Width * this.Height]; + // Rent then copy the pixels. Unsafe.CopyBlock gives us a nice speed boost here. + this.RentPixels(); using (PixelAccessor sourcePixels = other.Lock()) using (PixelAccessor target = this.Lock()) { + // Check we can do this without crashing sourcePixels.CopyTo(target); } } + /// + /// Finalizes an instance of the class. + /// + ~ImageBase() + { + this.Dispose(false); + } + /// public int MaxWidth { get; set; } = int.MaxValue; @@ -108,6 +134,19 @@ namespace ImageSharp /// public Configuration Configuration { get; private set; } + /// + public void Dispose() + { + this.Dispose(true); + + // This object will be cleaned up by the Dispose method. + // Therefore, you should call GC.SuppressFinalize to + // take this object off the finalization queue + // and prevent finalization code for this object + // from executing a second time. + GC.SuppressFinalize(this); + } + /// public void InitPixels(int width, int height) { @@ -116,7 +155,7 @@ namespace ImageSharp this.Width = width; this.Height = height; - this.pixelBuffer = new TColor[width * height]; + this.RentPixels(); } /// @@ -126,13 +165,15 @@ namespace ImageSharp Guard.MustBeGreaterThan(height, 0, nameof(height)); Guard.NotNull(pixels, nameof(pixels)); - if (pixels.Length != width * height) + if (!(pixels.Length >= width * height)) { - throw new ArgumentException("Pixel array must have the length of Width * Height."); + throw new ArgumentException($"Pixel array must have the length of at least {width * height}."); } this.Width = width; this.Height = height; + + this.ReturnPixels(); this.pixelBuffer = pixels; } @@ -143,17 +184,18 @@ namespace ImageSharp Guard.MustBeGreaterThan(height, 0, nameof(height)); Guard.NotNull(pixels, nameof(pixels)); - if (pixels.Length != width * height) + if (!(pixels.Length >= width * height)) { - throw new ArgumentException("Pixel array must have the length of Width * Height."); + throw new ArgumentException($"Pixel array must have the length of at least {width * height}."); } this.Width = width; this.Height = height; // Copy the pixels. TODO: use Unsafe.Copy. - this.pixelBuffer = new TColor[pixels.Length]; - Array.Copy(pixels, this.pixelBuffer, pixels.Length); + this.ReturnPixels(); + this.RentPixels(); + Array.Copy(pixels, this.pixelBuffer, width * height); } /// @@ -174,5 +216,52 @@ namespace ImageSharp this.Quality = other.Quality; this.FrameDelay = other.FrameDelay; } + + /// + /// Releases any unmanaged resources from the inheriting class. + /// + protected virtual void ReleaseUnmanagedResources() + { + // TODO release unmanaged resources here + } + + /// + /// Disposes the object and frees resources for the Garbage Collector. + /// + /// If true, the object gets disposed. + protected virtual void Dispose(bool disposing) + { + if (this.isDisposed) + { + return; + } + + this.ReleaseUnmanagedResources(); + + if (disposing) + { + this.ReturnPixels(); + } + + // Note disposing is done. + this.isDisposed = true; + } + + /// + /// Rents the pixel array from the pool. + /// + private void RentPixels() + { + this.pixelBuffer = PixelPool.RentPixels(this.Width * this.Height); + } + + /// + /// Returns the rented pixel array back to the pool. + /// + private void ReturnPixels() + { + PixelPool.ReturnPixels(this.pixelBuffer); + this.pixelBuffer = null; + } } } \ No newline at end of file diff --git a/src/ImageSharp/Image/Image{TColor}.cs b/src/ImageSharp/Image/Image{TColor}.cs index 6bde8c3a55..5c83ef9bbd 100644 --- a/src/ImageSharp/Image/Image{TColor}.cs +++ b/src/ImageSharp/Image/Image{TColor}.cs @@ -334,9 +334,9 @@ namespace ImageSharp target.ExifProfile = new ExifProfile(this.ExifProfile); } - foreach (ImageFrame frame in this.Frames) + for (int i = 0; i < this.Frames.Count; i++) { - target.Frames.Add(frame.To()); + target.Frames.Add(this.Frames[i].To()); } return target; @@ -372,6 +372,18 @@ namespace ImageSharp return new ImageFrame(this); } + /// + protected override void Dispose(bool disposing) + { + // ReSharper disable once ForCanBeConvertedToForeach + for (int i = 0; i < this.Frames.Count; i++) + { + this.Frames[i].Dispose(); + } + + base.Dispose(disposing); + } + /// /// Loads the image from the given stream. /// diff --git a/src/ImageSharp/Image/PixelAccessor{TColor}.cs b/src/ImageSharp/Image/PixelAccessor{TColor}.cs index 162891442c..60bf8de784 100644 --- a/src/ImageSharp/Image/PixelAccessor{TColor}.cs +++ b/src/ImageSharp/Image/PixelAccessor{TColor}.cs @@ -76,9 +76,9 @@ namespace ImageSharp Guard.MustBeGreaterThan(width, 0, nameof(width)); Guard.MustBeGreaterThan(height, 0, nameof(height)); - if (pixels.Length != width * height) + if (!(pixels.Length >= width * height)) { - throw new ArgumentException("Pixel array must have the length of Width * Height."); + throw new ArgumentException($"Pixel array must have the length of at least {width * height}."); } this.Width = width; diff --git a/src/ImageSharp/Image/PixelPool{TColor}.cs b/src/ImageSharp/Image/PixelPool{TColor}.cs new file mode 100644 index 0000000000..8673499a88 --- /dev/null +++ b/src/ImageSharp/Image/PixelPool{TColor}.cs @@ -0,0 +1,50 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Buffers; + + /// + /// Provides a resource pool that enables reusing instances of type . + /// + /// The pixel format. + public static class PixelPool + where TColor : struct, IPackedPixel, IEquatable + { + /// + /// The used to pool data. TODO: Choose sensible default size and count + /// + private static readonly ArrayPool ArrayPool = ArrayPool.Create(int.MaxValue, 50); + + /// + /// Rents the pixel array from the pool. + /// + /// The minimum length of the array to return. + /// The + public static TColor[] RentPixels(int minimumLength) + { + return ArrayPool.Rent(minimumLength); + } + + /// + /// Returns the rented pixel array back to the pool. + /// + /// The array to return to the buffer pool. + public static void ReturnPixels(TColor[] array) + { + try + { + ArrayPool.Return(array, true); + } + catch + { + // Do nothing. + // Hacky but it allows us to attempt to return non-pooled arrays and arrays that have already been returned + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Drawing/DrawBeziers.cs b/tests/ImageSharp.Benchmarks/Drawing/DrawBeziers.cs index 2dd3acb0a5..c066ac18c7 100644 --- a/tests/ImageSharp.Benchmarks/Drawing/DrawBeziers.cs +++ b/tests/ImageSharp.Benchmarks/Drawing/DrawBeziers.cs @@ -47,18 +47,22 @@ namespace ImageSharp.Benchmarks [Benchmark(Description = "ImageSharp Draw Beziers")] public void DrawLinesCore() { - CoreImage image = new CoreImage(800, 800); - - image.DrawBeziers(CoreColor.HotPink, 10, new[] { + using (CoreImage image = new CoreImage(800, 800)) + { + image.DrawBeziers( + CoreColor.HotPink, + 10, + new[] { new Vector2(10, 500), new Vector2(30, 10), new Vector2(240, 30), new Vector2(300, 500) - }); + }); - using (MemoryStream ms = new MemoryStream()) - { - image.SaveAsBmp(ms); + using (MemoryStream ms = new MemoryStream()) + { + image.SaveAsBmp(ms); + } } } } diff --git a/tests/ImageSharp.Benchmarks/Drawing/DrawLines.cs b/tests/ImageSharp.Benchmarks/Drawing/DrawLines.cs index b418feb5be..78f71b6606 100644 --- a/tests/ImageSharp.Benchmarks/Drawing/DrawLines.cs +++ b/tests/ImageSharp.Benchmarks/Drawing/DrawLines.cs @@ -46,17 +46,21 @@ namespace ImageSharp.Benchmarks [Benchmark(Description = "ImageSharp Draw Lines")] public void DrawLinesCore() { - CoreImage image = new CoreImage(800, 800); - - image.DrawLines(CoreColor.HotPink, 10, new[] { - new Vector2(10, 10), - new Vector2(550, 50), - new Vector2(200, 400) - }); - - using (MemoryStream ms = new MemoryStream()) + using (CoreImage image = new CoreImage(800, 800)) { - image.SaveAsBmp(ms); + image.DrawLines( + CoreColor.HotPink, + 10, + new[] { + new Vector2(10, 10), + new Vector2(550, 50), + new Vector2(200, 400) + }); + + using (MemoryStream ms = new MemoryStream()) + { + image.SaveAsBmp(ms); + } } } } diff --git a/tests/ImageSharp.Benchmarks/Drawing/DrawPolygon.cs b/tests/ImageSharp.Benchmarks/Drawing/DrawPolygon.cs index 46526330f3..88618b9128 100644 --- a/tests/ImageSharp.Benchmarks/Drawing/DrawPolygon.cs +++ b/tests/ImageSharp.Benchmarks/Drawing/DrawPolygon.cs @@ -45,17 +45,21 @@ namespace ImageSharp.Benchmarks [Benchmark(Description = "ImageSharp Draw Polygon")] public void DrawPolygonCore() { - CoreImage image = new CoreImage(800, 800); - - image.DrawPolygon(CoreColor.HotPink, 10, new[] { - new Vector2(10, 10), - new Vector2(550, 50), - new Vector2(200, 400) - }); - - using (MemoryStream ms = new MemoryStream()) + using (CoreImage image = new CoreImage(800, 800)) { - image.SaveAsBmp(ms); + image.DrawPolygon( + CoreColor.HotPink, + 10, + new[] { + new Vector2(10, 10), + new Vector2(550, 50), + new Vector2(200, 400) + }); + + using (MemoryStream ms = new MemoryStream()) + { + image.SaveAsBmp(ms); + } } } } diff --git a/tests/ImageSharp.Benchmarks/Drawing/FillPolygon.cs b/tests/ImageSharp.Benchmarks/Drawing/FillPolygon.cs index b774d73cb3..1eafbe077f 100644 --- a/tests/ImageSharp.Benchmarks/Drawing/FillPolygon.cs +++ b/tests/ImageSharp.Benchmarks/Drawing/FillPolygon.cs @@ -44,17 +44,20 @@ namespace ImageSharp.Benchmarks [Benchmark(Description = "ImageSharp Fill Polygon")] public void DrawSolidPolygonCore() { - CoreImage image = new CoreImage(800, 800); - image.FillPolygon(CoreColor.HotPink, - new[] { - new Vector2(10, 10), - new Vector2(550, 50), - new Vector2(200, 400) - }); - - using (MemoryStream ms = new MemoryStream()) + using (CoreImage image = new CoreImage(800, 800)) { - image.SaveAsBmp(ms); + image.FillPolygon( + CoreColor.HotPink, + new[] { + new Vector2(10, 10), + new Vector2(550, 50), + new Vector2(200, 400) + }); + + using (MemoryStream ms = new MemoryStream()) + { + image.SaveAsBmp(ms); + } } } } diff --git a/tests/ImageSharp.Benchmarks/Drawing/FillRectangle.cs b/tests/ImageSharp.Benchmarks/Drawing/FillRectangle.cs index d9782249ad..8e5c18d27a 100644 --- a/tests/ImageSharp.Benchmarks/Drawing/FillRectangle.cs +++ b/tests/ImageSharp.Benchmarks/Drawing/FillRectangle.cs @@ -28,7 +28,6 @@ namespace ImageSharp.Benchmarks { graphics.InterpolationMode = InterpolationMode.Default; graphics.SmoothingMode = SmoothingMode.AntiAlias; - var pen = new Pen(Color.HotPink, 10); graphics.FillRectangle(Brushes.HotPink, new Rectangle(10, 10, 190, 140)); } return destination.Size; @@ -38,25 +37,29 @@ namespace ImageSharp.Benchmarks [Benchmark(Description = "ImageSharp Fill Rectangle")] public CoreSize FillRactangleCore() { - CoreImage image = new CoreImage(800, 800); - - image.Fill(CoreColor.HotPink, new ImageSharp.Drawing.Shapes.RectangularPolygon(new CoreRectangle(10, 10, 190, 140))); + using (CoreImage image = new CoreImage(800, 800)) + { + image.Fill(CoreColor.HotPink, new ImageSharp.Drawing.Shapes.RectangularPolygon(new CoreRectangle(10, 10, 190, 140))); - return new CoreSize(image.Width, image.Height); + return new CoreSize(image.Width, image.Height); + } } [Benchmark(Description = "ImageSharp Fill Rectangle - As Polygon")] public CoreSize FillPolygonCore() { - CoreImage image = new CoreImage(800, 800); - - image.FillPolygon(CoreColor.HotPink, new[] { - new Vector2(10, 10), - new Vector2(200, 10), - new Vector2(200, 150), - new Vector2(10, 150) }); + using (CoreImage image = new CoreImage(800, 800)) + { + image.FillPolygon( + CoreColor.HotPink, + new[] { + new Vector2(10, 10), + new Vector2(200, 10), + new Vector2(200, 150), + new Vector2(10, 150) }); - return new CoreSize(image.Width, image.Height); + return new CoreSize(image.Width, image.Height); + } } } } \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Drawing/FillWithPattern.cs b/tests/ImageSharp.Benchmarks/Drawing/FillWithPattern.cs index e5676cd133..718474f1f8 100644 --- a/tests/ImageSharp.Benchmarks/Drawing/FillWithPattern.cs +++ b/tests/ImageSharp.Benchmarks/Drawing/FillWithPattern.cs @@ -38,12 +38,14 @@ namespace ImageSharp.Benchmarks [Benchmark(Description = "ImageSharp Fill with Pattern")] public void DrawPatternPolygon3Core() { - CoreImage image = new CoreImage(800, 800); - image.Fill(CoreBrushes.BackwardDiagonal(CoreColor.HotPink)); - - using (MemoryStream ms = new MemoryStream()) + using (CoreImage image = new CoreImage(800, 800)) { - image.SaveAsBmp(ms); + image.Fill(CoreBrushes.BackwardDiagonal(CoreColor.HotPink)); + + using (MemoryStream ms = new MemoryStream()) + { + image.SaveAsBmp(ms); + } } } } diff --git a/tests/ImageSharp.Benchmarks/Image/CopyPixels.cs b/tests/ImageSharp.Benchmarks/Image/CopyPixels.cs index ae732d0532..335d8247d3 100644 --- a/tests/ImageSharp.Benchmarks/Image/CopyPixels.cs +++ b/tests/ImageSharp.Benchmarks/Image/CopyPixels.cs @@ -17,24 +17,26 @@ namespace ImageSharp.Benchmarks.Image [Benchmark(Description = "Copy by Pixel")] public CoreColor CopyByPixel() { - CoreImage source = new CoreImage(1024, 768); - CoreImage target = new CoreImage(1024, 768); - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock()) + using (CoreImage source = new CoreImage(1024, 768)) + using (CoreImage target = new CoreImage(1024, 768)) { - Parallel.For( - 0, - source.Height, - Configuration.Default.ParallelOptions, - y => - { - for (int x = 0; x < source.Width; x++) - { - targetPixels[x, y] = sourcePixels[x, y]; - } - }); + using (PixelAccessor sourcePixels = source.Lock()) + using (PixelAccessor targetPixels = target.Lock()) + { + Parallel.For( + 0, + source.Height, + Configuration.Default.ParallelOptions, + y => + { + for (int x = 0; x < source.Width; x++) + { + targetPixels[x, y] = sourcePixels[x, y]; + } + }); - return targetPixels[0, 0]; + return targetPixels[0, 0]; + } } } } diff --git a/tests/ImageSharp.Benchmarks/Image/DecodeBmp.cs b/tests/ImageSharp.Benchmarks/Image/DecodeBmp.cs index b8f433a146..431bbeb079 100644 --- a/tests/ImageSharp.Benchmarks/Image/DecodeBmp.cs +++ b/tests/ImageSharp.Benchmarks/Image/DecodeBmp.cs @@ -43,8 +43,10 @@ namespace ImageSharp.Benchmarks.Image { using (MemoryStream memoryStream = new MemoryStream(this.bmpBytes)) { - CoreImage image = new CoreImage(memoryStream); - return new CoreSize(image.Width, image.Height); + using (CoreImage image = new CoreImage(memoryStream)) + { + return new CoreSize(image.Width, image.Height); + } } } } diff --git a/tests/ImageSharp.Benchmarks/Image/DecodeFilteredPng.cs b/tests/ImageSharp.Benchmarks/Image/DecodeFilteredPng.cs index 5f7050fb05..517915bacb 100644 --- a/tests/ImageSharp.Benchmarks/Image/DecodeFilteredPng.cs +++ b/tests/ImageSharp.Benchmarks/Image/DecodeFilteredPng.cs @@ -29,37 +29,40 @@ namespace ImageSharp.Benchmarks.Image this.filter4 = new MemoryStream(File.ReadAllBytes("../ImageSharp.Tests/TestImages/Formats/Png/filter4.png")); } - private Image LoadPng(MemoryStream stream) + private Size LoadPng(MemoryStream stream) { - return new Image(stream); + using (Image image = new Image(stream)) + { + return new Size(image.Width, image.Height); + } } [Benchmark(Baseline = true, Description = "None-filtered PNG file")] - public Image PngFilter0() + public Size PngFilter0() { return LoadPng(filter0); } [Benchmark(Description = "Sub-filtered PNG file")] - public Image PngFilter1() + public Size PngFilter1() { return LoadPng(filter1); } [Benchmark(Description = "Up-filtered PNG file")] - public Image PngFilter2() + public Size PngFilter2() { return LoadPng(filter2); } [Benchmark(Description = "Average-filtered PNG file")] - public Image PngFilter3() + public Size PngFilter3() { return LoadPng(filter3); } [Benchmark(Description = "Paeth-filtered PNG file")] - public Image PngFilter4() + public Size PngFilter4() { return LoadPng(filter4); } diff --git a/tests/ImageSharp.Benchmarks/Image/DecodeGif.cs b/tests/ImageSharp.Benchmarks/Image/DecodeGif.cs index 3da6eb3629..cb70213dac 100644 --- a/tests/ImageSharp.Benchmarks/Image/DecodeGif.cs +++ b/tests/ImageSharp.Benchmarks/Image/DecodeGif.cs @@ -43,8 +43,10 @@ namespace ImageSharp.Benchmarks.Image { using (MemoryStream memoryStream = new MemoryStream(this.gifBytes)) { - CoreImage image = new CoreImage(memoryStream); - return new CoreSize(image.Width, image.Height); + using (CoreImage image = new CoreImage(memoryStream)) + { + return new CoreSize(image.Width, image.Height); + } } } } diff --git a/tests/ImageSharp.Benchmarks/Image/DecodeJpeg.cs b/tests/ImageSharp.Benchmarks/Image/DecodeJpeg.cs index b5a44d9196..cbbe9c9f23 100644 --- a/tests/ImageSharp.Benchmarks/Image/DecodeJpeg.cs +++ b/tests/ImageSharp.Benchmarks/Image/DecodeJpeg.cs @@ -43,8 +43,10 @@ namespace ImageSharp.Benchmarks.Image { using (MemoryStream memoryStream = new MemoryStream(this.jpegBytes)) { - CoreImage image = new CoreImage(memoryStream); - return new CoreSize(image.Width, image.Height); + using (CoreImage image = new CoreImage(memoryStream)) + { + return new CoreSize(image.Width, image.Height); + } } } } diff --git a/tests/ImageSharp.Benchmarks/Image/DecodePng.cs b/tests/ImageSharp.Benchmarks/Image/DecodePng.cs index f3bda758d8..79c8dbc23e 100644 --- a/tests/ImageSharp.Benchmarks/Image/DecodePng.cs +++ b/tests/ImageSharp.Benchmarks/Image/DecodePng.cs @@ -43,8 +43,10 @@ namespace ImageSharp.Benchmarks.Image { using (MemoryStream memoryStream = new MemoryStream(this.pngBytes)) { - CoreImage image = new CoreImage(memoryStream); - return new CoreSize(image.Width, image.Height); + using (CoreImage image = new CoreImage(memoryStream)) + { + return new CoreSize(image.Width, image.Height); + } } } } diff --git a/tests/ImageSharp.Benchmarks/Image/EncodeBmp.cs b/tests/ImageSharp.Benchmarks/Image/EncodeBmp.cs index 5a77fbca07..b0a3b44999 100644 --- a/tests/ImageSharp.Benchmarks/Image/EncodeBmp.cs +++ b/tests/ImageSharp.Benchmarks/Image/EncodeBmp.cs @@ -31,6 +31,14 @@ namespace ImageSharp.Benchmarks.Image } } + [Cleanup] + public void Cleanup() + { + this.bmpStream.Dispose(); + this.bmpCore.Dispose(); + this.bmpDrawing.Dispose(); + } + [Benchmark(Baseline = true, Description = "System.Drawing Bmp")] public void BmpSystemDrawing() { diff --git a/tests/ImageSharp.Benchmarks/Image/EncodeBmpMultiple.cs b/tests/ImageSharp.Benchmarks/Image/EncodeBmpMultiple.cs index 1775f144df..852e175725 100644 --- a/tests/ImageSharp.Benchmarks/Image/EncodeBmpMultiple.cs +++ b/tests/ImageSharp.Benchmarks/Image/EncodeBmpMultiple.cs @@ -18,7 +18,7 @@ namespace ImageSharp.Benchmarks.Image protected override IEnumerable InputImageSubfoldersOrFiles => new[] { "Bmp/", "Jpg/baseline" }; [Benchmark(Description = "EncodeBmpMultiple - ImageSharp")] - public void EncodeGifImageSharp() + public void EncodeBmpImageSharp() { this.ForEachImageSharpImage( (img, ms) => @@ -29,7 +29,7 @@ namespace ImageSharp.Benchmarks.Image } [Benchmark(Baseline = true, Description = "EncodeBmpMultiple - System.Drawing")] - public void EncodeGifSystemDrawing() + public void EncodeBmpSystemDrawing() { this.ForEachSystemDrawingImage( (img, ms) => diff --git a/tests/ImageSharp.Benchmarks/Image/EncodeGif.cs b/tests/ImageSharp.Benchmarks/Image/EncodeGif.cs index 42feb085fa..0810f3fe17 100644 --- a/tests/ImageSharp.Benchmarks/Image/EncodeGif.cs +++ b/tests/ImageSharp.Benchmarks/Image/EncodeGif.cs @@ -31,6 +31,14 @@ namespace ImageSharp.Benchmarks.Image } } + [Cleanup] + public void Cleanup() + { + this.bmpStream.Dispose(); + this.bmpCore.Dispose(); + this.bmpDrawing.Dispose(); + } + [Benchmark(Baseline = true, Description = "System.Drawing Gif")] public void GifSystemDrawing() { diff --git a/tests/ImageSharp.Benchmarks/Image/EncodeJpeg.cs b/tests/ImageSharp.Benchmarks/Image/EncodeJpeg.cs index f1e3b21149..f835f9666f 100644 --- a/tests/ImageSharp.Benchmarks/Image/EncodeJpeg.cs +++ b/tests/ImageSharp.Benchmarks/Image/EncodeJpeg.cs @@ -31,6 +31,14 @@ namespace ImageSharp.Benchmarks.Image } } + [Cleanup] + public void Cleanup() + { + this.bmpStream.Dispose(); + this.bmpCore.Dispose(); + this.bmpDrawing.Dispose(); + } + [Benchmark(Baseline = true, Description = "System.Drawing Jpeg")] public void JpegSystemDrawing() { diff --git a/tests/ImageSharp.Benchmarks/Image/EncodePng.cs b/tests/ImageSharp.Benchmarks/Image/EncodePng.cs index 44ffba679b..dd1882c80d 100644 --- a/tests/ImageSharp.Benchmarks/Image/EncodePng.cs +++ b/tests/ImageSharp.Benchmarks/Image/EncodePng.cs @@ -31,6 +31,14 @@ namespace ImageSharp.Benchmarks.Image } } + [Cleanup] + public void Cleanup() + { + this.bmpStream.Dispose(); + this.bmpCore.Dispose(); + this.bmpDrawing.Dispose(); + } + [Benchmark(Baseline = true, Description = "System.Drawing Png")] public void PngSystemDrawing() { diff --git a/tests/ImageSharp.Benchmarks/Image/GetSetPixel.cs b/tests/ImageSharp.Benchmarks/Image/GetSetPixel.cs index ffd4760072..78295e27d7 100644 --- a/tests/ImageSharp.Benchmarks/Image/GetSetPixel.cs +++ b/tests/ImageSharp.Benchmarks/Image/GetSetPixel.cs @@ -28,11 +28,13 @@ namespace ImageSharp.Benchmarks.Image [Benchmark(Description = "ImageSharp GetSet pixel")] public CoreColor ResizeCore() { - CoreImage image = new CoreImage(400, 400); - using (PixelAccessor imagePixels = image.Lock()) + using (CoreImage image = new CoreImage(400, 400)) { - imagePixels[200, 200] = CoreColor.White; - return imagePixels[200, 200]; + using (PixelAccessor imagePixels = image.Lock()) + { + imagePixels[200, 200] = CoreColor.White; + return imagePixels[200, 200]; + } } } } diff --git a/tests/ImageSharp.Benchmarks/Samplers/Crop.cs b/tests/ImageSharp.Benchmarks/Samplers/Crop.cs index 3a98569e72..a3cdef92eb 100644 --- a/tests/ImageSharp.Benchmarks/Samplers/Crop.cs +++ b/tests/ImageSharp.Benchmarks/Samplers/Crop.cs @@ -38,9 +38,11 @@ namespace ImageSharp.Benchmarks [Benchmark(Description = "ImageSharp Crop")] public CoreSize CropResizeCore() { - CoreImage image = new CoreImage(800, 800); - image.Crop(100, 100); - return new CoreSize(image.Width, image.Height); + using (CoreImage image = new CoreImage(800, 800)) + { + image.Crop(100, 100); + return new CoreSize(image.Width, image.Height); + } } } } diff --git a/tests/ImageSharp.Benchmarks/Samplers/DetectEdges.cs b/tests/ImageSharp.Benchmarks/Samplers/DetectEdges.cs index be76c13fd6..28bec5124d 100644 --- a/tests/ImageSharp.Benchmarks/Samplers/DetectEdges.cs +++ b/tests/ImageSharp.Benchmarks/Samplers/DetectEdges.cs @@ -28,6 +28,12 @@ namespace ImageSharp.Benchmarks } } + [Cleanup] + public void Cleanup() + { + this.image.Dispose(); + } + [Benchmark(Description = "ImageSharp DetectEdges")] public void ImageProcessorCoreDetectEdges() { diff --git a/tests/ImageSharp.Benchmarks/Samplers/Resize.cs b/tests/ImageSharp.Benchmarks/Samplers/Resize.cs index 10fe1fc753..04570ce8f3 100644 --- a/tests/ImageSharp.Benchmarks/Samplers/Resize.cs +++ b/tests/ImageSharp.Benchmarks/Samplers/Resize.cs @@ -37,17 +37,21 @@ namespace ImageSharp.Benchmarks [Benchmark(Description = "ImageSharp Resize")] public CoreSize ResizeCore() { - CoreImage image = new CoreImage(2000, 2000); - image.Resize(400, 400); - return new CoreSize(image.Width, image.Height); + using (CoreImage image = new CoreImage(2000, 2000)) + { + image.Resize(400, 400); + return new CoreSize(image.Width, image.Height); + } } [Benchmark(Description = "ImageSharp Compand Resize")] public CoreSize ResizeCoreCompand() { - CoreImage image = new CoreImage(2000, 2000); - image.Resize(400, 400, true); - return new CoreSize(image.Width, image.Height); + using (CoreImage image = new CoreImage(2000, 2000)) + { + image.Resize(400, 400, true); + return new CoreSize(image.Width, image.Height); + } } } } diff --git a/tests/ImageSharp.Tests/Drawing/BeziersTests.cs b/tests/ImageSharp.Tests/Drawing/BeziersTests.cs index c219f91aa0..a1d4d3fd59 100644 --- a/tests/ImageSharp.Tests/Drawing/BeziersTests.cs +++ b/tests/ImageSharp.Tests/Drawing/BeziersTests.cs @@ -15,89 +15,88 @@ namespace ImageSharp.Tests.Drawing public class Beziers : FileTestBase { - - [Fact] public void ImageShouldBeOverlayedByBezierLine() { - string path = CreateOutputDirectory("Drawing","BezierLine"); -var image = new Image(500, 500); - -using (FileStream output = File.OpenWrite($"{path}/Simple.png")) -{ - image - .BackgroundColor(Color.Blue) - .DrawBeziers(Color.HotPink, 5, new[] { - new Vector2(10, 400), - new Vector2(30, 10), - new Vector2(240, 30), - new Vector2(300, 400) - }) - .Save(output); -} - - using (var sourcePixels = image.Lock()) + string path = this.CreateOutputDirectory("Drawing", "BezierLine"); + using (Image image = new Image(500, 500)) { - //top of curve - Assert.Equal(Color.HotPink, sourcePixels[138,115]); - - //start points - Assert.Equal(Color.HotPink, sourcePixels[10, 400]); - Assert.Equal(Color.HotPink, sourcePixels[300, 400]); - - //curve points should not be never be set - Assert.Equal(Color.Blue, sourcePixels[30, 10]); - Assert.Equal(Color.Blue, sourcePixels[240, 30]); - - // inside shape should be empty - Assert.Equal(Color.Blue, sourcePixels[200, 250]); + using (FileStream output = File.OpenWrite($"{path}/Simple.png")) + { + image.BackgroundColor(Color.Blue) + .DrawBeziers(Color.HotPink, 5, + new[] { + new Vector2(10, 400), + new Vector2(30, 10), + new Vector2(240, 30), + new Vector2(300, 400) + }) + .Save(output); + } + + using (PixelAccessor sourcePixels = image.Lock()) + { + //top of curve + Assert.Equal(Color.HotPink, sourcePixels[138, 115]); + + //start points + Assert.Equal(Color.HotPink, sourcePixels[10, 400]); + Assert.Equal(Color.HotPink, sourcePixels[300, 400]); + + //curve points should not be never be set + Assert.Equal(Color.Blue, sourcePixels[30, 10]); + Assert.Equal(Color.Blue, sourcePixels[240, 30]); + + // inside shape should be empty + Assert.Equal(Color.Blue, sourcePixels[200, 250]); + } } - } [Fact] public void ImageShouldBeOverlayedBezierLineWithOpacity() { - string path = CreateOutputDirectory("Drawing", "BezierLine"); + string path = this.CreateOutputDirectory("Drawing", "BezierLine"); - var color = new Color(Color.HotPink.R, Color.HotPink.G, Color.HotPink.B, 150); + Color color = new Color(Color.HotPink.R, Color.HotPink.G, Color.HotPink.B, 150); - var image = new Image(500, 500); - - using (FileStream output = File.OpenWrite($"{path}/Opacity.png")) + using (Image image = new Image(500, 500)) { - image - .BackgroundColor(Color.Blue) - .DrawBeziers(color, 10, new[] { - new Vector2(10, 400), - new Vector2(30, 10), - new Vector2(240, 30), - new Vector2(300, 400) - }) - .Save(output); + using (FileStream output = File.OpenWrite($"{path}/Opacity.png")) + { + image.BackgroundColor(Color.Blue) + .DrawBeziers(color, + 10, + new[] { + new Vector2(10, 400), + new Vector2(30, 10), + new Vector2(240, 30), + new Vector2(300, 400) + }) + .Save(output); + } + + //shift background color towards forground color by the opacity amount + Color mergedColor = new Color(Vector4.Lerp(Color.Blue.ToVector4(), Color.HotPink.ToVector4(), 150f / 255f)); + + using (PixelAccessor sourcePixels = image.Lock()) + { + //top of curve + Assert.Equal(mergedColor, sourcePixels[138, 115]); + + //start points + Assert.Equal(mergedColor, sourcePixels[10, 400]); + Assert.Equal(mergedColor, sourcePixels[300, 400]); + + //curve points should not be never be set + Assert.Equal(Color.Blue, sourcePixels[30, 10]); + Assert.Equal(Color.Blue, sourcePixels[240, 30]); + + // inside shape should be empty + Assert.Equal(Color.Blue, sourcePixels[200, 250]); + } } - - //shift background color towards forground color by the opacity amount - var mergedColor = new Color(Vector4.Lerp(Color.Blue.ToVector4(), Color.HotPink.ToVector4(), 150f/255f)); - - using (var sourcePixels = image.Lock()) - { - //top of curve - Assert.Equal(mergedColor, sourcePixels[138, 115]); - - //start points - Assert.Equal(mergedColor, sourcePixels[10, 400]); - Assert.Equal(mergedColor, sourcePixels[300, 400]); - - //curve points should not be never be set - Assert.Equal(Color.Blue, sourcePixels[30, 10]); - Assert.Equal(Color.Blue, sourcePixels[240, 30]); - - // inside shape should be empty - Assert.Equal(Color.Blue, sourcePixels[200, 250]); - } - } - + } } } diff --git a/tests/ImageSharp.Tests/Drawing/DrawImageTest.cs b/tests/ImageSharp.Tests/Drawing/DrawImageTest.cs index 5e5a6897f7..3a59de624c 100644 --- a/tests/ImageSharp.Tests/Drawing/DrawImageTest.cs +++ b/tests/ImageSharp.Tests/Drawing/DrawImageTest.cs @@ -6,7 +6,6 @@ namespace ImageSharp.Tests { using System.IO; - using System.Linq; using Xunit; @@ -15,18 +14,20 @@ namespace ImageSharp.Tests [Fact] public void ImageShouldApplyDrawImageFilter() { - string path = CreateOutputDirectory("Drawing", "DrawImage"); + string path = this.CreateOutputDirectory("Drawing", "DrawImage"); - Image blend = TestFile.Create(TestImages.Bmp.Car).CreateImage(); - - foreach (TestFile file in Files) + using (Image blend = TestFile.Create(TestImages.Bmp.Car).CreateImage()) { - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) + foreach (TestFile file in Files) { - image.DrawImage(blend, 75, new Size(image.Width / 2, image.Height / 2), new Point(image.Width / 4, image.Height / 4)) - .Save(output); + using (Image image = file.CreateImage()) + { + using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) + { + image.DrawImage(blend, 75, new Size(image.Width / 2, image.Height / 2), new Point(image.Width / 4, image.Height / 4)) + .Save(output); + } + } } } } diff --git a/tests/ImageSharp.Tests/Drawing/DrawPathTests.cs b/tests/ImageSharp.Tests/Drawing/DrawPathTests.cs index 61080b815b..b619483162 100644 --- a/tests/ImageSharp.Tests/Drawing/DrawPathTests.cs +++ b/tests/ImageSharp.Tests/Drawing/DrawPathTests.cs @@ -20,81 +20,82 @@ namespace ImageSharp.Tests.Drawing [Fact] public void ImageShouldBeOverlayedByPath() { - string path = CreateOutputDirectory("Drawing", "Path"); - var image = new Image(500, 500); - - var linerSegemnt = new LinearLineSegment( - new Vector2(10, 10), - new Vector2(200, 150), - new Vector2(50, 300)); - var bazierSegment = new BezierLineSegment(new Vector2(50, 300), - new Vector2(500, 500), - new Vector2(60, 10), - new Vector2(10, 400)); - - var p = new CorePath(linerSegemnt, bazierSegment); - - using (FileStream output = File.OpenWrite($"{path}/Simple.png")) - { - image - .BackgroundColor(Color.Blue) - .DrawPath(Color.HotPink, 5, p) - .Save(output); - } - - using (var sourcePixels = image.Lock()) + string path = this.CreateOutputDirectory("Drawing", "Path"); + using (Image image = new Image(500, 500)) { - Assert.Equal(Color.HotPink, sourcePixels[9, 9]); - - Assert.Equal(Color.HotPink, sourcePixels[199, 149]); - - Assert.Equal(Color.Blue, sourcePixels[50, 50]); + LinearLineSegment linerSegemnt = new LinearLineSegment( + new Vector2(10, 10), + new Vector2(200, 150), + new Vector2(50, 300)); + BezierLineSegment bazierSegment = new BezierLineSegment(new Vector2(50, 300), + new Vector2(500, 500), + new Vector2(60, 10), + new Vector2(10, 400)); + + CorePath p = new CorePath(linerSegemnt, bazierSegment); + + using (FileStream output = File.OpenWrite($"{path}/Simple.png")) + { + image + .BackgroundColor(Color.Blue) + .DrawPath(Color.HotPink, 5, p) + .Save(output); + } + + using (PixelAccessor sourcePixels = image.Lock()) + { + Assert.Equal(Color.HotPink, sourcePixels[9, 9]); + + Assert.Equal(Color.HotPink, sourcePixels[199, 149]); + + Assert.Equal(Color.Blue, sourcePixels[50, 50]); + } } - } [Fact] public void ImageShouldBeOverlayedPathWithOpacity() { - string path = CreateOutputDirectory("Drawing", "Path"); + string path = this.CreateOutputDirectory("Drawing", "Path"); - var color = new Color(Color.HotPink.R, Color.HotPink.G, Color.HotPink.B, 150); + Color color = new Color(Color.HotPink.R, Color.HotPink.G, Color.HotPink.B, 150); - var linerSegemnt = new LinearLineSegment( + LinearLineSegment linerSegemnt = new LinearLineSegment( new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300) ); - var bazierSegment = new BezierLineSegment(new Vector2(50, 300), + + BezierLineSegment bazierSegment = new BezierLineSegment(new Vector2(50, 300), new Vector2(500, 500), new Vector2(60, 10), new Vector2(10, 400)); - var p = new CorePath(linerSegemnt, bazierSegment); + CorePath p = new CorePath(linerSegemnt, bazierSegment); - var image = new Image(500, 500); - - - using (FileStream output = File.OpenWrite($"{path}/Opacity.png")) + using (Image image = new Image(500, 500)) { - image - .BackgroundColor(Color.Blue) - .DrawPath(color, 10, p) - .Save(output); - } + using (FileStream output = File.OpenWrite($"{path}/Opacity.png")) + { + image + .BackgroundColor(Color.Blue) + .DrawPath(color, 10, p) + .Save(output); + } - //shift background color towards forground color by the opacity amount - var mergedColor = new Color(Vector4.Lerp(Color.Blue.ToVector4(), Color.HotPink.ToVector4(), 150f / 255f)); + //shift background color towards forground color by the opacity amount + Color mergedColor = new Color(Vector4.Lerp(Color.Blue.ToVector4(), Color.HotPink.ToVector4(), 150f / 255f)); - using (var sourcePixels = image.Lock()) - { - Assert.Equal(mergedColor, sourcePixels[9, 9]); + using (PixelAccessor sourcePixels = image.Lock()) + { + Assert.Equal(mergedColor, sourcePixels[9, 9]); - Assert.Equal(mergedColor, sourcePixels[199, 149]); + Assert.Equal(mergedColor, sourcePixels[199, 149]); - Assert.Equal(Color.Blue, sourcePixels[50, 50]); + Assert.Equal(Color.Blue, sourcePixels[50, 50]); + } } } diff --git a/tests/ImageSharp.Tests/Drawing/FillPatternTests.cs b/tests/ImageSharp.Tests/Drawing/FillPatternTests.cs index 5bb93a55c7..d3c1877abd 100644 --- a/tests/ImageSharp.Tests/Drawing/FillPatternTests.cs +++ b/tests/ImageSharp.Tests/Drawing/FillPatternTests.cs @@ -15,55 +15,56 @@ namespace ImageSharp.Tests.Drawing public class FillPatternBrushTests : FileTestBase { - private Image Test(string name, Color background, IBrush brush, Color[,] expectedPattern) + private void Test(string name, Color background, IBrush brush, Color[,] expectedPattern) { - string path = CreateOutputDirectory("Fill", "PatternBrush"); - Image image = new Image(20, 20); - image - .Fill(background) - .Fill(brush); - - using (FileStream output = File.OpenWrite($"{path}/{name}.png")) - { - image.Save(output); - } - using (var sourcePixels = image.Lock()) + string path = this.CreateOutputDirectory("Fill", "PatternBrush"); + using (Image image = new Image(20, 20)) { - // lets pick random spots to start checking - var r = new Random(); - var xStride = expectedPattern.GetLength(1); - var yStride = expectedPattern.GetLength(0); - var offsetX = r.Next(image.Width / xStride) * xStride; - var offsetY = r.Next(image.Height / yStride) * yStride; - for (var x = 0; x < xStride; x++) + image + .Fill(background) + .Fill(brush); + + using (FileStream output = File.OpenWrite($"{path}/{name}.png")) { - for (var y = 0; y < yStride; y++) + image.Save(output); + } + + using (PixelAccessor sourcePixels = image.Lock()) + { + // lets pick random spots to start checking + Random r = new Random(); + int xStride = expectedPattern.GetLength(1); + int yStride = expectedPattern.GetLength(0); + int offsetX = r.Next(image.Width / xStride) * xStride; + int offsetY = r.Next(image.Height / yStride) * yStride; + for (int x = 0; x < xStride; x++) { - var actualX = x + offsetX; - var actualY = y + offsetY; - var expected = expectedPattern[y, x]; // inverted pattern - var actual = sourcePixels[actualX, actualY]; - if (expected != actual) + for (int y = 0; y < yStride; y++) { - Assert.True(false, $"Expected {expected} but found {actual} at ({actualX},{actualY})"); + int actualX = x + offsetX; + int actualY = y + offsetY; + Color expected = expectedPattern[y, x]; // inverted pattern + Color actual = sourcePixels[actualX, actualY]; + if (expected != actual) + { + Assert.True(false, $"Expected {expected} but found {actual} at ({actualX},{actualY})"); + } } } } + using (FileStream output = File.OpenWrite($"{path}/{name}x4.png")) + { + image.Resize(80, 80).Save(output); + } } - using (FileStream output = File.OpenWrite($"{path}/{name}x4.png")) - { - image.Resize(80, 80).Save(output); - } - - - - return image; } [Fact] public void ImageShouldBeFloodFilledWithPercent10() { - Test("Percent10", Color.Blue, Brushes.Percent10(Color.HotPink, Color.LimeGreen), new Color[,] { + this.Test("Percent10", Color.Blue, Brushes.Percent10(Color.HotPink, Color.LimeGreen), + new[,] + { { Color.HotPink , Color.LimeGreen, Color.LimeGreen, Color.LimeGreen}, { Color.LimeGreen, Color.LimeGreen, Color.LimeGreen, Color.LimeGreen}, { Color.LimeGreen, Color.LimeGreen, Color.HotPink , Color.LimeGreen}, diff --git a/tests/ImageSharp.Tests/Drawing/FillSolidBrushTests.cs b/tests/ImageSharp.Tests/Drawing/FillSolidBrushTests.cs index b7551d63a0..bafc84b69f 100644 --- a/tests/ImageSharp.Tests/Drawing/FillSolidBrushTests.cs +++ b/tests/ImageSharp.Tests/Drawing/FillSolidBrushTests.cs @@ -18,71 +18,73 @@ namespace ImageSharp.Tests.Drawing [Fact] public void ImageShouldBeFloodFilledWithColorOnDefaultBackground() { - string path = CreateOutputDirectory("Fill", "SolidBrush"); - var image = new Image(500, 500); - - using (FileStream output = File.OpenWrite($"{path}/DefaultBack.png")) + string path = this.CreateOutputDirectory("Fill", "SolidBrush"); + using (Image image = new Image(500, 500)) { - image - .Fill(Color.HotPink) - .Save(output); - } - - using (var sourcePixels = image.Lock()) - { - Assert.Equal(Color.HotPink, sourcePixels[9, 9]); - - Assert.Equal(Color.HotPink, sourcePixels[199, 149]); + using (FileStream output = File.OpenWrite($"{path}/DefaultBack.png")) + { + image + .Fill(Color.HotPink) + .Save(output); + } + + using (PixelAccessor sourcePixels = image.Lock()) + { + Assert.Equal(Color.HotPink, sourcePixels[9, 9]); + + Assert.Equal(Color.HotPink, sourcePixels[199, 149]); + } } } [Fact] public void ImageShouldBeFloodFilledWithColor() { - string path = CreateOutputDirectory("Fill", "SolidBrush"); - var image = new Image(500, 500); - - using (FileStream output = File.OpenWrite($"{path}/Simple.png")) + string path = this.CreateOutputDirectory("Fill", "SolidBrush"); + using (Image image = new Image(500, 500)) { - image - .BackgroundColor(Color.Blue) - .Fill(Color.HotPink) - .Save(output); - } - - using (var sourcePixels = image.Lock()) - { - Assert.Equal(Color.HotPink, sourcePixels[9, 9]); - - Assert.Equal(Color.HotPink, sourcePixels[199, 149]); + using (FileStream output = File.OpenWrite($"{path}/Simple.png")) + { + image + .BackgroundColor(Color.Blue) + .Fill(Color.HotPink) + .Save(output); + } + + using (PixelAccessor sourcePixels = image.Lock()) + { + Assert.Equal(Color.HotPink, sourcePixels[9, 9]); + + Assert.Equal(Color.HotPink, sourcePixels[199, 149]); + } } } [Fact] public void ImageShouldBeFloodFilledWithColorOpacity() { - string path = CreateOutputDirectory("Fill", "SolidBrush"); - var image = new Image(500, 500); - - var color = new Color(Color.HotPink.R, Color.HotPink.G, Color.HotPink.B, 150); - - using (FileStream output = File.OpenWrite($"{path}/Opacity.png")) - { - image - .BackgroundColor(Color.Blue) - .Fill(color) - .Save(output); - } - //shift background color towards forground color by the opacity amount - var mergedColor = new Color(Vector4.Lerp(Color.Blue.ToVector4(), Color.HotPink.ToVector4(), 150f / 255f)); - - - using (var sourcePixels = image.Lock()) + string path = this.CreateOutputDirectory("Fill", "SolidBrush"); + using (Image image = new Image(500, 500)) { - Assert.Equal(mergedColor, sourcePixels[9, 9]); - Assert.Equal(mergedColor, sourcePixels[199, 149]); + Color color = new Color(Color.HotPink.R, Color.HotPink.G, Color.HotPink.B, 150); + + using (FileStream output = File.OpenWrite($"{path}/Opacity.png")) + { + image + .BackgroundColor(Color.Blue) + .Fill(color) + .Save(output); + } + //shift background color towards forground color by the opacity amount + Color mergedColor = new Color(Vector4.Lerp(Color.Blue.ToVector4(), Color.HotPink.ToVector4(), 150f / 255f)); + + + using (PixelAccessor sourcePixels = image.Lock()) + { + Assert.Equal(mergedColor, sourcePixels[9, 9]); + Assert.Equal(mergedColor, sourcePixels[199, 149]); + } } - } } diff --git a/tests/ImageSharp.Tests/Drawing/LineComplexPolygonTests.cs b/tests/ImageSharp.Tests/Drawing/LineComplexPolygonTests.cs index cdcb5ebe43..9ce93ee899 100644 --- a/tests/ImageSharp.Tests/Drawing/LineComplexPolygonTests.cs +++ b/tests/ImageSharp.Tests/Drawing/LineComplexPolygonTests.cs @@ -5,12 +5,9 @@ namespace ImageSharp.Tests.Drawing { - using System; - using System.Diagnostics.CodeAnalysis; using System.IO; using Xunit; - using Drawing; - using ImageSharp.Drawing; + using System.Numerics; using ImageSharp.Drawing.Shapes; using ImageSharp.Drawing.Pens; @@ -20,98 +17,100 @@ namespace ImageSharp.Tests.Drawing [Fact] public void ImageShouldBeOverlayedByPolygonOutline() { - string path = CreateOutputDirectory("Drawing", "LineComplexPolygon"); - var simplePath = new LinearPolygon( + string path = this.CreateOutputDirectory("Drawing", "LineComplexPolygon"); + LinearPolygon simplePath = new LinearPolygon( new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300)); - var hole1 = new LinearPolygon( + LinearPolygon hole1 = new LinearPolygon( new Vector2(37, 85), new Vector2(93, 85), new Vector2(65, 137)); - var image = new Image(500, 500); - - using (FileStream output = File.OpenWrite($"{path}/Simple.png")) + using (Image image = new Image(500, 500)) { - image - .BackgroundColor(Color.Blue) - .DrawPolygon(Color.HotPink, 5, new ComplexPolygon(simplePath, hole1)) - .Save(output); - } + using (FileStream output = File.OpenWrite($"{path}/Simple.png")) + { + image + .BackgroundColor(Color.Blue) + .DrawPolygon(Color.HotPink, 5, new ComplexPolygon(simplePath, hole1)) + .Save(output); + } - using (var sourcePixels = image.Lock()) - { - Assert.Equal(Color.HotPink, sourcePixels[10, 10]); + using (PixelAccessor sourcePixels = image.Lock()) + { + Assert.Equal(Color.HotPink, sourcePixels[10, 10]); - Assert.Equal(Color.HotPink, sourcePixels[200, 150]); + Assert.Equal(Color.HotPink, sourcePixels[200, 150]); - Assert.Equal(Color.HotPink, sourcePixels[50, 300]); + Assert.Equal(Color.HotPink, sourcePixels[50, 300]); - Assert.Equal(Color.HotPink, sourcePixels[37, 85]); + Assert.Equal(Color.HotPink, sourcePixels[37, 85]); - Assert.Equal(Color.HotPink, sourcePixels[93, 85]); + Assert.Equal(Color.HotPink, sourcePixels[93, 85]); - Assert.Equal(Color.HotPink, sourcePixels[65, 137]); + Assert.Equal(Color.HotPink, sourcePixels[65, 137]); - Assert.Equal(Color.Blue, sourcePixels[2, 2]); + Assert.Equal(Color.Blue, sourcePixels[2, 2]); - //inside hole - Assert.Equal(Color.Blue, sourcePixels[57, 99]); + //inside hole + Assert.Equal(Color.Blue, sourcePixels[57, 99]); - //inside shape - Assert.Equal(Color.Blue, sourcePixels[100, 192]); + //inside shape + Assert.Equal(Color.Blue, sourcePixels[100, 192]); + } } } [Fact] public void ImageShouldBeOverlayedByPolygonOutlineNoOverlapping() { - string path = CreateOutputDirectory("Drawing", "LineComplexPolygon"); - var simplePath = new LinearPolygon( + string path = this.CreateOutputDirectory("Drawing", "LineComplexPolygon"); + LinearPolygon simplePath = new LinearPolygon( new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300)); - var hole1 = new LinearPolygon( + LinearPolygon hole1 = new LinearPolygon( new Vector2(207, 25), new Vector2(263, 25), new Vector2(235, 57)); - var image = new Image(500, 500); - - using (FileStream output = File.OpenWrite($"{path}/SimpleVanishHole.png")) + using (Image image = new Image(500, 500)) { - image - .BackgroundColor(Color.Blue) - .DrawPolygon(Color.HotPink, 5, new ComplexPolygon(simplePath, hole1)) - .Save(output); - } + using (FileStream output = File.OpenWrite($"{path}/SimpleVanishHole.png")) + { + image + .BackgroundColor(Color.Blue) + .DrawPolygon(Color.HotPink, 5, new ComplexPolygon(simplePath, hole1)) + .Save(output); + } - using (var sourcePixels = image.Lock()) - { - Assert.Equal(Color.HotPink, sourcePixels[10, 10]); + using (PixelAccessor sourcePixels = image.Lock()) + { + Assert.Equal(Color.HotPink, sourcePixels[10, 10]); - Assert.Equal(Color.HotPink, sourcePixels[200, 150]); + Assert.Equal(Color.HotPink, sourcePixels[200, 150]); - Assert.Equal(Color.HotPink, sourcePixels[50, 300]); + Assert.Equal(Color.HotPink, sourcePixels[50, 300]); - //Assert.Equal(Color.HotPink, sourcePixels[37, 85]); + //Assert.Equal(Color.HotPink, sourcePixels[37, 85]); - //Assert.Equal(Color.HotPink, sourcePixels[93, 85]); + //Assert.Equal(Color.HotPink, sourcePixels[93, 85]); - //Assert.Equal(Color.HotPink, sourcePixels[65, 137]); + //Assert.Equal(Color.HotPink, sourcePixels[65, 137]); - Assert.Equal(Color.Blue, sourcePixels[2, 2]); + Assert.Equal(Color.Blue, sourcePixels[2, 2]); - //inside hole - Assert.Equal(Color.Blue, sourcePixels[57, 99]); + //inside hole + Assert.Equal(Color.Blue, sourcePixels[57, 99]); - //inside shape - Assert.Equal(Color.Blue, sourcePixels[100, 192]); + //inside shape + Assert.Equal(Color.Blue, sourcePixels[100, 192]); + } } } @@ -119,44 +118,45 @@ namespace ImageSharp.Tests.Drawing [Fact] public void ImageShouldBeOverlayedByPolygonOutlineOverlapping() { - string path = CreateOutputDirectory("Drawing", "LineComplexPolygon"); - var simplePath = new LinearPolygon( + string path = this.CreateOutputDirectory("Drawing", "LineComplexPolygon"); + LinearPolygon simplePath = new LinearPolygon( new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300)); - var hole1 = new LinearPolygon( + LinearPolygon hole1 = new LinearPolygon( new Vector2(37, 85), new Vector2(130, 40), new Vector2(65, 137)); - var image = new Image(500, 500); - - using (FileStream output = File.OpenWrite($"{path}/SimpleOverlapping.png")) + using (Image image = new Image(500, 500)) { - image - .BackgroundColor(Color.Blue) - .DrawPolygon(Color.HotPink, 5, new ComplexPolygon(simplePath, hole1)) - .Save(output); - } + using (FileStream output = File.OpenWrite($"{path}/SimpleOverlapping.png")) + { + image + .BackgroundColor(Color.Blue) + .DrawPolygon(Color.HotPink, 5, new ComplexPolygon(simplePath, hole1)) + .Save(output); + } - using (var sourcePixels = image.Lock()) - { - Assert.Equal(Color.HotPink, sourcePixels[10, 10]); + using (PixelAccessor sourcePixels = image.Lock()) + { + Assert.Equal(Color.HotPink, sourcePixels[10, 10]); - Assert.Equal(Color.HotPink, sourcePixels[200, 150]); + Assert.Equal(Color.HotPink, sourcePixels[200, 150]); - Assert.Equal(Color.HotPink, sourcePixels[50, 300]); + Assert.Equal(Color.HotPink, sourcePixels[50, 300]); - Assert.Equal(Color.Blue, sourcePixels[130, 41]); - - Assert.Equal(Color.Blue, sourcePixels[2, 2]); + Assert.Equal(Color.Blue, sourcePixels[130, 41]); - //inside hole - Assert.Equal(Color.Blue, sourcePixels[57, 99]); + Assert.Equal(Color.Blue, sourcePixels[2, 2]); - //inside shape - Assert.Equal(Color.Blue, sourcePixels[100, 192]); + //inside hole + Assert.Equal(Color.Blue, sourcePixels[57, 99]); + + //inside shape + Assert.Equal(Color.Blue, sourcePixels[100, 192]); + } } } @@ -164,25 +164,26 @@ namespace ImageSharp.Tests.Drawing [Fact] public void ImageShouldBeOverlayedByPolygonOutlineDashed() { - string path = CreateOutputDirectory("Drawing", "LineComplexPolygon"); - var simplePath = new LinearPolygon( + string path = this.CreateOutputDirectory("Drawing", "LineComplexPolygon"); + LinearPolygon simplePath = new LinearPolygon( new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300)); - var hole1 = new LinearPolygon( + LinearPolygon hole1 = new LinearPolygon( new Vector2(37, 85), new Vector2(93, 85), new Vector2(65, 137)); - var image = new Image(500, 500); - - using (FileStream output = File.OpenWrite($"{path}/Dashed.png")) + using (Image image = new Image(500, 500)) { - image - .BackgroundColor(Color.Blue) - .DrawPolygon(Pens.Dash(Color.HotPink, 5), new ComplexPolygon(simplePath, hole1)) - .Save(output); + using (FileStream output = File.OpenWrite($"{path}/Dashed.png")) + { + image + .BackgroundColor(Color.Blue) + .DrawPolygon(Pens.Dash(Color.HotPink, 5), new ComplexPolygon(simplePath, hole1)) + .Save(output); + } } } @@ -190,54 +191,55 @@ namespace ImageSharp.Tests.Drawing [Fact] public void ImageShouldBeOverlayedPolygonOutlineWithOpacity() { - string path = CreateOutputDirectory("Drawing", "LineComplexPolygon"); - var simplePath = new LinearPolygon( + string path = this.CreateOutputDirectory("Drawing", "LineComplexPolygon"); + LinearPolygon simplePath = new LinearPolygon( new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300)); - var hole1 = new LinearPolygon( + LinearPolygon hole1 = new LinearPolygon( new Vector2(37, 85), new Vector2(93, 85), new Vector2(65, 137)); - var color = new Color(Color.HotPink.R, Color.HotPink.G, Color.HotPink.B, 150); + Color color = new Color(Color.HotPink.R, Color.HotPink.G, Color.HotPink.B, 150); - var image = new Image(500, 500); - - using (FileStream output = File.OpenWrite($"{path}/Opacity.png")) + using (Image image = new Image(500, 500)) { - image - .BackgroundColor(Color.Blue) - .DrawPolygon(color, 5, new ComplexPolygon(simplePath, hole1)) - .Save(output); - } + using (FileStream output = File.OpenWrite($"{path}/Opacity.png")) + { + image + .BackgroundColor(Color.Blue) + .DrawPolygon(color, 5, new ComplexPolygon(simplePath, hole1)) + .Save(output); + } - //shift background color towards forground color by the opacity amount - var mergedColor = new Color(Vector4.Lerp(Color.Blue.ToVector4(), Color.HotPink.ToVector4(), 150f / 255f)); + //shift background color towards forground color by the opacity amount + Color mergedColor = new Color(Vector4.Lerp(Color.Blue.ToVector4(), Color.HotPink.ToVector4(), 150f / 255f)); - using (var sourcePixels = image.Lock()) - { - Assert.Equal(mergedColor, sourcePixels[10, 10]); + using (PixelAccessor sourcePixels = image.Lock()) + { + Assert.Equal(mergedColor, sourcePixels[10, 10]); - Assert.Equal(mergedColor, sourcePixels[200, 150]); + Assert.Equal(mergedColor, sourcePixels[200, 150]); - Assert.Equal(mergedColor, sourcePixels[50, 300]); + Assert.Equal(mergedColor, sourcePixels[50, 300]); - Assert.Equal(mergedColor, sourcePixels[37, 85]); + Assert.Equal(mergedColor, sourcePixels[37, 85]); - Assert.Equal(mergedColor, sourcePixels[93, 85]); + Assert.Equal(mergedColor, sourcePixels[93, 85]); - Assert.Equal(mergedColor, sourcePixels[65, 137]); + Assert.Equal(mergedColor, sourcePixels[65, 137]); - Assert.Equal(Color.Blue, sourcePixels[2, 2]); + Assert.Equal(Color.Blue, sourcePixels[2, 2]); - //inside hole - Assert.Equal(Color.Blue, sourcePixels[57, 99]); + //inside hole + Assert.Equal(Color.Blue, sourcePixels[57, 99]); - //inside shape - Assert.Equal(Color.Blue, sourcePixels[100, 192]); + //inside shape + Assert.Equal(Color.Blue, sourcePixels[100, 192]); + } } } } diff --git a/tests/ImageSharp.Tests/Drawing/LineTests.cs b/tests/ImageSharp.Tests/Drawing/LineTests.cs index 46b315164e..81efd933ba 100644 --- a/tests/ImageSharp.Tests/Drawing/LineTests.cs +++ b/tests/ImageSharp.Tests/Drawing/LineTests.cs @@ -5,11 +5,9 @@ namespace ImageSharp.Tests.Drawing { - using Drawing; using ImageSharp.Drawing; using ImageSharp.Drawing.Pens; - using System; - using System.Diagnostics.CodeAnalysis; + using System.IO; using System.Numerics; using Xunit; @@ -19,123 +17,132 @@ namespace ImageSharp.Tests.Drawing [Fact] public void ImageShouldBeOverlayedByPath() { - string path = CreateOutputDirectory("Drawing", "Lines"); - var image = new Image(500, 500); - - using (FileStream output = File.OpenWrite($"{path}/Simple.png")) + string path = this.CreateOutputDirectory("Drawing", "Lines"); + using (Image image = new Image(500, 500)) { - image - .BackgroundColor(Color.Blue) - .DrawLines(Color.HotPink, 5, new[] { + using (FileStream output = File.OpenWrite($"{path}/Simple.png")) + { + image + .BackgroundColor(Color.Blue) + .DrawLines(Color.HotPink, 5, + new[] { new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300) - }) - .Save(output); - } + }) + .Save(output); + } - using (var sourcePixels = image.Lock()) - { - Assert.Equal(Color.HotPink, sourcePixels[9, 9]); + using (PixelAccessor sourcePixels = image.Lock()) + { + Assert.Equal(Color.HotPink, sourcePixels[9, 9]); - Assert.Equal(Color.HotPink, sourcePixels[199, 149]); + Assert.Equal(Color.HotPink, sourcePixels[199, 149]); - Assert.Equal(Color.Blue, sourcePixels[50, 50]); + Assert.Equal(Color.Blue, sourcePixels[50, 50]); + } } } [Fact] public void ImageShouldBeOverlayedByPath_NoAntialias() { - string path = CreateOutputDirectory("Drawing", "Lines"); - var image = new Image(500, 500); - - using (FileStream output = File.OpenWrite($"{path}/Simple_noantialias.png")) + string path = this.CreateOutputDirectory("Drawing", "Lines"); + using (Image image = new Image(500, 500)) { - image - .BackgroundColor(Color.Blue) - .DrawLines(Color.HotPink, 5, new[] { + using (FileStream output = File.OpenWrite($"{path}/Simple_noantialias.png")) + { + image + .BackgroundColor(Color.Blue) + .DrawLines(Color.HotPink, 5, + new[] { new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300) - }, new GraphicsOptions(false)) - .Save(output); - } + }, + new GraphicsOptions(false)) + .Save(output); + } - using (var sourcePixels = image.Lock()) - { - Assert.Equal(Color.HotPink, sourcePixels[9, 9]); + using (PixelAccessor sourcePixels = image.Lock()) + { + Assert.Equal(Color.HotPink, sourcePixels[9, 9]); - Assert.Equal(Color.HotPink, sourcePixels[199, 149]); + Assert.Equal(Color.HotPink, sourcePixels[199, 149]); - Assert.Equal(Color.Blue, sourcePixels[50, 50]); + Assert.Equal(Color.Blue, sourcePixels[50, 50]); + } } } [Fact] public void ImageShouldBeOverlayedByPathDashed() { - string path = CreateOutputDirectory("Drawing", "Lines"); - var image = new Image(500, 500); - - using (FileStream output = File.OpenWrite($"{path}/Dashed.png")) + string path = this.CreateOutputDirectory("Drawing", "Lines"); + using (Image image = new Image(500, 500)) { - image - .BackgroundColor(Color.Blue) - .DrawLines(Pens.Dash(Color.HotPink, 5), new[] { + using (FileStream output = File.OpenWrite($"{path}/Dashed.png")) + { + image + .BackgroundColor(Color.Blue) + .DrawLines(Pens.Dash(Color.HotPink, 5), + new[] { new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300) - }) - .Save(output); + }) + .Save(output); + } } - } [Fact] public void ImageShouldBeOverlayedByPathDotted() { - string path = CreateOutputDirectory("Drawing", "Lines"); - var image = new Image(500, 500); - - using (FileStream output = File.OpenWrite($"{path}/Dot.png")) + string path = this.CreateOutputDirectory("Drawing", "Lines"); + using (Image image = new Image(500, 500)) { - image - .BackgroundColor(Color.Blue) - .DrawLines(Pens.Dot(Color.HotPink, 5), new[] { + using (FileStream output = File.OpenWrite($"{path}/Dot.png")) + { + image + .BackgroundColor(Color.Blue) + .DrawLines(Pens.Dot(Color.HotPink, 5), + new[] { new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300) - }) - .Save(output); + }) + .Save(output); + } } } [Fact] public void ImageShouldBeOverlayedByPathDashDot() { - string path = CreateOutputDirectory("Drawing", "Lines"); - var image = new Image(500, 500); - - using (FileStream output = File.OpenWrite($"{path}/DashDot.png")) + string path = this.CreateOutputDirectory("Drawing", "Lines"); + using (Image image = new Image(500, 500)) { - image - .BackgroundColor(Color.Blue) - .DrawLines(Pens.DashDot(Color.HotPink, 5), new[] { + using (FileStream output = File.OpenWrite($"{path}/DashDot.png")) + { + image + .BackgroundColor(Color.Blue) + .DrawLines(Pens.DashDot(Color.HotPink, 5), + new[] { new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300) - }) - .Save(output); + }) + .Save(output); + } } - } [Fact] public void ImageShouldBeOverlayedByPathDashDotDot() { - string path = CreateOutputDirectory("Drawing", "Lines"); - var image = new Image(500, 500); + string path = this.CreateOutputDirectory("Drawing", "Lines"); + Image image = new Image(500, 500); using (FileStream output = File.OpenWrite($"{path}/DashDotDot.png")) { @@ -153,12 +160,12 @@ namespace ImageSharp.Tests.Drawing [Fact] public void ImageShouldBeOverlayedPathWithOpacity() { - string path = CreateOutputDirectory("Drawing", "Lines"); + string path = this.CreateOutputDirectory("Drawing", "Lines"); - var color = new Color(Color.HotPink.R, Color.HotPink.G, Color.HotPink.B, 150); + Color color = new Color(Color.HotPink.R, Color.HotPink.G, Color.HotPink.B, 150); + + Image image = new Image(500, 500); - var image = new Image(500, 500); - using (FileStream output = File.OpenWrite($"{path}/Opacity.png")) { @@ -173,9 +180,9 @@ namespace ImageSharp.Tests.Drawing } //shift background color towards forground color by the opacity amount - var mergedColor = new Color(Vector4.Lerp(Color.Blue.ToVector4(), Color.HotPink.ToVector4(), 150f/255f)); + Color mergedColor = new Color(Vector4.Lerp(Color.Blue.ToVector4(), Color.HotPink.ToVector4(), 150f/255f)); - using (var sourcePixels = image.Lock()) + using (PixelAccessor sourcePixels = image.Lock()) { Assert.Equal(mergedColor, sourcePixels[9, 9]); @@ -188,9 +195,9 @@ namespace ImageSharp.Tests.Drawing [Fact] public void ImageShouldBeOverlayedByPathOutline() { - string path = CreateOutputDirectory("Drawing", "Lines"); + string path = this.CreateOutputDirectory("Drawing", "Lines"); - var image = new Image(500, 500); + Image image = new Image(500, 500); using (FileStream output = File.OpenWrite($"{path}/Rectangle.png")) { @@ -205,7 +212,7 @@ namespace ImageSharp.Tests.Drawing .Save(output); } - using (var sourcePixels = image.Lock()) + using (PixelAccessor sourcePixels = image.Lock()) { Assert.Equal(Color.HotPink, sourcePixels[8, 8]); @@ -216,6 +223,6 @@ namespace ImageSharp.Tests.Drawing Assert.Equal(Color.Blue, sourcePixels[50, 50]); } } - + } } diff --git a/tests/ImageSharp.Tests/Drawing/PolygonTests.cs b/tests/ImageSharp.Tests/Drawing/PolygonTests.cs index 68027d0bf7..f987440754 100644 --- a/tests/ImageSharp.Tests/Drawing/PolygonTests.cs +++ b/tests/ImageSharp.Tests/Drawing/PolygonTests.cs @@ -18,97 +18,101 @@ namespace ImageSharp.Tests.Drawing [Fact] public void ImageShouldBeOverlayedByPolygonOutline() { - string path = CreateOutputDirectory("Drawing", "Polygons"); + string path = this.CreateOutputDirectory("Drawing", "Polygons"); - var image = new Image(500, 500); - - using (FileStream output = File.OpenWrite($"{path}/Simple.png")) + using (Image image = new Image(500, 500)) { - image - .BackgroundColor(Color.Blue) - .DrawPolygon(Color.HotPink, 5, new[] { - new Vector2(10, 10), - new Vector2(200, 150), - new Vector2(50, 300) - }) - .Save(output); - } + using (FileStream output = File.OpenWrite($"{path}/Simple.png")) + { + image + .BackgroundColor(Color.Blue) + .DrawPolygon(Color.HotPink, 5, + new[] { + new Vector2(10, 10), + new Vector2(200, 150), + new Vector2(50, 300) + }) + .Save(output); + } - using (var sourcePixels = image.Lock()) - { - Assert.Equal(Color.HotPink, sourcePixels[9, 9]); + using (PixelAccessor sourcePixels = image.Lock()) + { + Assert.Equal(Color.HotPink, sourcePixels[9, 9]); - Assert.Equal(Color.HotPink, sourcePixels[199, 149]); + Assert.Equal(Color.HotPink, sourcePixels[199, 149]); - Assert.Equal(Color.Blue, sourcePixels[50, 50]); + Assert.Equal(Color.Blue, sourcePixels[50, 50]); - Assert.Equal(Color.Blue, sourcePixels[2, 2]); + Assert.Equal(Color.Blue, sourcePixels[2, 2]); + } } } [Fact] public void ImageShouldBeOverlayedPolygonOutlineWithOpacity() { - string path = CreateOutputDirectory("Drawing", "Polygons"); - var simplePath = new[] { + string path = this.CreateOutputDirectory("Drawing", "Polygons"); + Vector2[] simplePath = new[] { new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300) }; - var color = new Color(Color.HotPink.R, Color.HotPink.G, Color.HotPink.B, 150); + Color color = new Color(Color.HotPink.R, Color.HotPink.G, Color.HotPink.B, 150); - var image = new Image(500, 500); - - using (FileStream output = File.OpenWrite($"{path}/Opacity.png")) + using (Image image = new Image(500, 500)) { - image - .BackgroundColor(Color.Blue) - .DrawPolygon(color, 10, simplePath) - .Save(output); - } + using (FileStream output = File.OpenWrite($"{path}/Opacity.png")) + { + image + .BackgroundColor(Color.Blue) + .DrawPolygon(color, 10, simplePath) + .Save(output); + } - //shift background color towards forground color by the opacity amount - var mergedColor = new Color(Vector4.Lerp(Color.Blue.ToVector4(), Color.HotPink.ToVector4(), 150f / 255f)); + //shift background color towards forground color by the opacity amount + Color mergedColor = new Color(Vector4.Lerp(Color.Blue.ToVector4(), Color.HotPink.ToVector4(), 150f / 255f)); - using (var sourcePixels = image.Lock()) - { - Assert.Equal(mergedColor, sourcePixels[9, 9]); + using (PixelAccessor sourcePixels = image.Lock()) + { + Assert.Equal(mergedColor, sourcePixels[9, 9]); - Assert.Equal(mergedColor, sourcePixels[199, 149]); + Assert.Equal(mergedColor, sourcePixels[199, 149]); - Assert.Equal(Color.Blue, sourcePixels[50, 50]); + Assert.Equal(Color.Blue, sourcePixels[50, 50]); - Assert.Equal(Color.Blue, sourcePixels[2, 2]); + Assert.Equal(Color.Blue, sourcePixels[2, 2]); + } } } [Fact] public void ImageShouldBeOverlayedByRectangleOutline() { - string path = CreateOutputDirectory("Drawing", "Polygons"); + string path = this.CreateOutputDirectory("Drawing", "Polygons"); - var image = new Image(500, 500); - - using (FileStream output = File.OpenWrite($"{path}/Rectangle.png")) + using (Image image = new Image(500, 500)) { - image - .BackgroundColor(Color.Blue) - .DrawPolygon(Color.HotPink, 10, new Rectangle(10, 10, 190, 140)) - .Save(output); - } + using (FileStream output = File.OpenWrite($"{path}/Rectangle.png")) + { + image + .BackgroundColor(Color.Blue) + .DrawPolygon(Color.HotPink, 10, new Rectangle(10, 10, 190, 140)) + .Save(output); + } - using (var sourcePixels = image.Lock()) - { - Assert.Equal(Color.HotPink, sourcePixels[8, 8]); + using (PixelAccessor sourcePixels = image.Lock()) + { + Assert.Equal(Color.HotPink, sourcePixels[8, 8]); - Assert.Equal(Color.HotPink, sourcePixels[198, 10]); + Assert.Equal(Color.HotPink, sourcePixels[198, 10]); - Assert.Equal(Color.HotPink, sourcePixels[10, 50]); + Assert.Equal(Color.HotPink, sourcePixels[10, 50]); - Assert.Equal(Color.Blue, sourcePixels[50, 50]); + Assert.Equal(Color.Blue, sourcePixels[50, 50]); - Assert.Equal(Color.Blue, sourcePixels[2, 2]); + Assert.Equal(Color.Blue, sourcePixels[2, 2]); + } } } } diff --git a/tests/ImageSharp.Tests/Drawing/RecolorImageTest.cs b/tests/ImageSharp.Tests/Drawing/RecolorImageTest.cs index 2edd05be13..0b450d166e 100644 --- a/tests/ImageSharp.Tests/Drawing/RecolorImageTest.cs +++ b/tests/ImageSharp.Tests/Drawing/RecolorImageTest.cs @@ -16,18 +16,19 @@ namespace ImageSharp.Tests [Fact] public void ImageShouldRecolorYellowToHotPink() { - string path = CreateOutputDirectory("Drawing", "RecolorImage"); + string path = this.CreateOutputDirectory("Drawing", "RecolorImage"); - var brush = new RecolorBrush(Color.Yellow, Color.HotPink, 0.2f); + RecolorBrush brush = new RecolorBrush(Color.Yellow, Color.HotPink, 0.2f); foreach (TestFile file in Files) { - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) + using (Image image = file.CreateImage()) { - image.Fill(brush) - .Save(output); + using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) + { + image.Fill(brush) + .Save(output); + } } } } @@ -35,19 +36,20 @@ namespace ImageSharp.Tests [Fact] public void ImageShouldRecolorYellowToHotPinkInARectangle() { - string path = CreateOutputDirectory("Drawing", "RecolorImage"); + string path = this.CreateOutputDirectory("Drawing", "RecolorImage"); - var brush = new RecolorBrush(Color.Yellow, Color.HotPink, 0.2f); + RecolorBrush brush = new RecolorBrush(Color.Yellow, Color.HotPink, 0.2f); foreach (TestFile file in Files) { - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/Shaped_{file.FileName}")) + using (Image image = file.CreateImage()) { - var imageHeight = image.Height; - image.Fill(brush, new Rectangle(0, imageHeight/2 - imageHeight/4, image.Width, imageHeight/2)) - .Save(output); + using (FileStream output = File.OpenWrite($"{path}/Shaped_{file.FileName}")) + { + int imageHeight = image.Height; + image.Fill(brush, new Rectangle(0, imageHeight/2 - imageHeight/4, image.Width, imageHeight/2)) + .Save(output); + } } } } diff --git a/tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs b/tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs index f6bcf49065..18275ef385 100644 --- a/tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs +++ b/tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs @@ -5,11 +5,8 @@ namespace ImageSharp.Tests.Drawing { - using Drawing; - using ImageSharp.Drawing; using ImageSharp.Drawing.Shapes; - using System; - using System.Diagnostics.CodeAnalysis; + using System.IO; using System.Numerics; using Xunit; @@ -19,83 +16,84 @@ namespace ImageSharp.Tests.Drawing [Fact] public void ImageShouldBeOverlayedByFilledPolygon() { - string path = CreateOutputDirectory("Drawing", "FilledBezier"); - var simplePath = new[] { + string path = this.CreateOutputDirectory("Drawing", "FilledBezier"); + Vector2[] simplePath = new[] { new Vector2(10, 400), new Vector2(30, 10), new Vector2(240, 30), new Vector2(300, 400) }; - var image = new Image(500, 500); - - using (FileStream output = File.OpenWrite($"{path}/Simple.png")) + using (Image image = new Image(500, 500)) { - image - .BackgroundColor(Color.Blue) - .Fill(Color.HotPink,new BezierPolygon(simplePath)) - .Save(output); - } + using (FileStream output = File.OpenWrite($"{path}/Simple.png")) + { + image + .BackgroundColor(Color.Blue) + .Fill(Color.HotPink,new BezierPolygon(simplePath)) + .Save(output); + } - using (var sourcePixels = image.Lock()) - { - //top of curve - Assert.Equal(Color.HotPink, sourcePixels[138, 116]); + using (PixelAccessor sourcePixels = image.Lock()) + { + //top of curve + Assert.Equal(Color.HotPink, sourcePixels[138, 116]); - //start points - Assert.Equal(Color.HotPink, sourcePixels[10, 400]); - Assert.Equal(Color.HotPink, sourcePixels[300, 400]); + //start points + Assert.Equal(Color.HotPink, sourcePixels[10, 400]); + Assert.Equal(Color.HotPink, sourcePixels[300, 400]); - //curve points should not be never be set - Assert.Equal(Color.Blue, sourcePixels[30, 10]); - Assert.Equal(Color.Blue, sourcePixels[240, 30]); + //curve points should not be never be set + Assert.Equal(Color.Blue, sourcePixels[30, 10]); + Assert.Equal(Color.Blue, sourcePixels[240, 30]); - // inside shape should not be empty - Assert.Equal(Color.HotPink, sourcePixels[200, 250]); + // inside shape should not be empty + Assert.Equal(Color.HotPink, sourcePixels[200, 250]); + } } } [Fact] public void ImageShouldBeOverlayedByFilledPolygonOpacity() { - string path = CreateOutputDirectory("Drawing", "FilledBezier"); - var simplePath = new[] { + string path = this.CreateOutputDirectory("Drawing", "FilledBezier"); + Vector2[] simplePath = new[] { new Vector2(10, 400), new Vector2(30, 10), new Vector2(240, 30), new Vector2(300, 400) }; - var color = new Color(Color.HotPink.R, Color.HotPink.G, Color.HotPink.B, 150); + Color color = new Color(Color.HotPink.R, Color.HotPink.G, Color.HotPink.B, 150); - var image = new Image(500, 500); - - using (FileStream output = File.OpenWrite($"{path}/Opacity.png")) + using (Image image = new Image(500, 500)) { - image - .BackgroundColor(Color.Blue) - .Fill(color, new BezierPolygon(simplePath)) - .Save(output); - } + using (FileStream output = File.OpenWrite($"{path}/Opacity.png")) + { + image + .BackgroundColor(Color.Blue) + .Fill(color, new BezierPolygon(simplePath)) + .Save(output); + } - //shift background color towards forground color by the opacity amount - var mergedColor = new Color(Vector4.Lerp(Color.Blue.ToVector4(), Color.HotPink.ToVector4(), 150f / 255f)); + //shift background color towards forground color by the opacity amount + Color mergedColor = new Color(Vector4.Lerp(Color.Blue.ToVector4(), Color.HotPink.ToVector4(), 150f / 255f)); - using (var sourcePixels = image.Lock()) - { - //top of curve - Assert.Equal(mergedColor, sourcePixels[138, 116]); + using (PixelAccessor sourcePixels = image.Lock()) + { + //top of curve + Assert.Equal(mergedColor, sourcePixels[138, 116]); - //start points - Assert.Equal(mergedColor, sourcePixels[10, 400]); - Assert.Equal(mergedColor, sourcePixels[300, 400]); + //start points + Assert.Equal(mergedColor, sourcePixels[10, 400]); + Assert.Equal(mergedColor, sourcePixels[300, 400]); - //curve points should not be never be set - Assert.Equal(Color.Blue, sourcePixels[30, 10]); - Assert.Equal(Color.Blue, sourcePixels[240, 30]); + //curve points should not be never be set + Assert.Equal(Color.Blue, sourcePixels[30, 10]); + Assert.Equal(Color.Blue, sourcePixels[240, 30]); - // inside shape should not be empty - Assert.Equal(mergedColor, sourcePixels[200, 250]); + // inside shape should not be empty + Assert.Equal(mergedColor, sourcePixels[200, 250]); + } } } - } } diff --git a/tests/ImageSharp.Tests/Drawing/SolidComplexPolygonTests.cs b/tests/ImageSharp.Tests/Drawing/SolidComplexPolygonTests.cs index cbd44d7d1c..144a1398d8 100644 --- a/tests/ImageSharp.Tests/Drawing/SolidComplexPolygonTests.cs +++ b/tests/ImageSharp.Tests/Drawing/SolidComplexPolygonTests.cs @@ -5,12 +5,9 @@ namespace ImageSharp.Tests.Drawing { - using System; - using System.Diagnostics.CodeAnalysis; using System.IO; using Xunit; - using Drawing; - using ImageSharp.Drawing; + using System.Numerics; using ImageSharp.Drawing.Shapes; @@ -19,41 +16,42 @@ namespace ImageSharp.Tests.Drawing [Fact] public void ImageShouldBeOverlayedByPolygonOutline() { - string path = CreateOutputDirectory("Drawing", "ComplexPolygon"); - var simplePath = new LinearPolygon( + string path = this.CreateOutputDirectory("Drawing", "ComplexPolygon"); + LinearPolygon simplePath = new LinearPolygon( new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300)); - var hole1 = new LinearPolygon( + LinearPolygon hole1 = new LinearPolygon( new Vector2(37, 85), new Vector2(93, 85), new Vector2(65, 137)); - var image = new Image(500, 500); - - using (FileStream output = File.OpenWrite($"{path}/Simple.png")) + using (Image image = new Image(500, 500)) { - image - .BackgroundColor(Color.Blue) - .Fill(Color.HotPink, new ComplexPolygon(simplePath, hole1)) - .Save(output); - } + using (FileStream output = File.OpenWrite($"{path}/Simple.png")) + { + image + .BackgroundColor(Color.Blue) + .Fill(Color.HotPink, new ComplexPolygon(simplePath, hole1)) + .Save(output); + } - using (var sourcePixels = image.Lock()) - { - Assert.Equal(Color.HotPink, sourcePixels[11, 11]); + using (PixelAccessor sourcePixels = image.Lock()) + { + Assert.Equal(Color.HotPink, sourcePixels[11, 11]); - Assert.Equal(Color.HotPink, sourcePixels[200, 150]); + Assert.Equal(Color.HotPink, sourcePixels[200, 150]); - Assert.Equal(Color.HotPink, sourcePixels[50, 50]); + Assert.Equal(Color.HotPink, sourcePixels[50, 50]); - Assert.Equal(Color.HotPink, sourcePixels[35, 100]); + Assert.Equal(Color.HotPink, sourcePixels[35, 100]); - Assert.Equal(Color.Blue, sourcePixels[2, 2]); + Assert.Equal(Color.Blue, sourcePixels[2, 2]); - //inside hole - Assert.Equal(Color.Blue, sourcePixels[57, 99]); + //inside hole + Assert.Equal(Color.Blue, sourcePixels[57, 99]); + } } } @@ -61,87 +59,89 @@ namespace ImageSharp.Tests.Drawing [Fact] public void ImageShouldBeOverlayedPolygonOutlineWithOverlap() { - string path = CreateOutputDirectory("Drawing", "ComplexPolygon"); - var simplePath = new LinearPolygon( + string path = this.CreateOutputDirectory("Drawing", "ComplexPolygon"); + LinearPolygon simplePath = new LinearPolygon( new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300)); - var hole1 = new LinearPolygon( + LinearPolygon hole1 = new LinearPolygon( new Vector2(37, 85), new Vector2(130, 40), new Vector2(65, 137)); - var image = new Image(500, 500); - - using (FileStream output = File.OpenWrite($"{path}/SimpleOverlapping.png")) + using (Image image = new Image(500, 500)) { - image - .BackgroundColor(Color.Blue) - .Fill(Color.HotPink, new ComplexPolygon(simplePath, hole1)) - .Save(output); - } - - using (var sourcePixels = image.Lock()) - { - Assert.Equal(Color.HotPink, sourcePixels[11, 11]); + using (FileStream output = File.OpenWrite($"{path}/SimpleOverlapping.png")) + { + image + .BackgroundColor(Color.Blue) + .Fill(Color.HotPink, new ComplexPolygon(simplePath, hole1)) + .Save(output); + } + + using (PixelAccessor sourcePixels = image.Lock()) + { + Assert.Equal(Color.HotPink, sourcePixels[11, 11]); - Assert.Equal(Color.HotPink, sourcePixels[200, 150]); + Assert.Equal(Color.HotPink, sourcePixels[200, 150]); - Assert.Equal(Color.HotPink, sourcePixels[50, 50]); + Assert.Equal(Color.HotPink, sourcePixels[50, 50]); - Assert.Equal(Color.HotPink, sourcePixels[35, 100]); + Assert.Equal(Color.HotPink, sourcePixels[35, 100]); - Assert.Equal(Color.Blue, sourcePixels[2, 2]); + Assert.Equal(Color.Blue, sourcePixels[2, 2]); - //inside hole - Assert.Equal(Color.Blue, sourcePixels[57, 99]); + //inside hole + Assert.Equal(Color.Blue, sourcePixels[57, 99]); + } } } [Fact] public void ImageShouldBeOverlayedPolygonOutlineWithOpacity() { - string path = CreateOutputDirectory("Drawing", "ComplexPolygon"); - var simplePath = new LinearPolygon( + string path = this.CreateOutputDirectory("Drawing", "ComplexPolygon"); + LinearPolygon simplePath = new LinearPolygon( new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300)); - var hole1 = new LinearPolygon( + LinearPolygon hole1 = new LinearPolygon( new Vector2(37, 85), new Vector2(93, 85), new Vector2(65, 137)); - var color = new Color(Color.HotPink.R, Color.HotPink.G, Color.HotPink.B, 150); - - var image = new Image(500, 500); + Color color = new Color(Color.HotPink.R, Color.HotPink.G, Color.HotPink.B, 150); - using (FileStream output = File.OpenWrite($"{path}/Opacity.png")) + using (Image image = new Image(500, 500)) { - image - .BackgroundColor(Color.Blue) - .Fill(color, new ComplexPolygon(simplePath, hole1)) - .Save(output); - } + using (FileStream output = File.OpenWrite($"{path}/Opacity.png")) + { + image + .BackgroundColor(Color.Blue) + .Fill(color, new ComplexPolygon(simplePath, hole1)) + .Save(output); + } - //shift background color towards forground color by the opacity amount - var mergedColor = new Color(Vector4.Lerp(Color.Blue.ToVector4(), Color.HotPink.ToVector4(), 150f / 255f)); + //shift background color towards forground color by the opacity amount + Color mergedColor = new Color(Vector4.Lerp(Color.Blue.ToVector4(), Color.HotPink.ToVector4(), 150f / 255f)); - using (var sourcePixels = image.Lock()) - { - Assert.Equal(mergedColor, sourcePixels[11, 11]); + using (PixelAccessor sourcePixels = image.Lock()) + { + Assert.Equal(mergedColor, sourcePixels[11, 11]); - Assert.Equal(mergedColor, sourcePixels[200, 150]); + Assert.Equal(mergedColor, sourcePixels[200, 150]); - Assert.Equal(mergedColor, sourcePixels[50, 50]); + Assert.Equal(mergedColor, sourcePixels[50, 50]); - Assert.Equal(mergedColor, sourcePixels[35, 100]); + Assert.Equal(mergedColor, sourcePixels[35, 100]); - Assert.Equal(Color.Blue, sourcePixels[2, 2]); + Assert.Equal(Color.Blue, sourcePixels[2, 2]); - //inside hole - Assert.Equal(Color.Blue, sourcePixels[57, 99]); + //inside hole + Assert.Equal(Color.Blue, sourcePixels[57, 99]); + } } } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs b/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs index 0d3b721d52..b4cf8e0905 100644 --- a/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs +++ b/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs @@ -5,10 +5,8 @@ namespace ImageSharp.Tests.Drawing { - using Drawing; using ImageSharp.Drawing; - using System; - using System.Diagnostics.CodeAnalysis; + using System.IO; using System.Numerics; using Xunit; @@ -19,155 +17,153 @@ namespace ImageSharp.Tests.Drawing [Fact] public void ImageShouldBeOverlayedByFilledPolygon() { - string path = CreateOutputDirectory("Drawing", "FilledPolygons"); - var simplePath = new[] { + string path = this.CreateOutputDirectory("Drawing", "FilledPolygons"); + Vector2[] simplePath = new[] { new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300) }; - var image = new Image(500, 500); - using (FileStream output = File.OpenWrite($"{path}/Simple.png")) + using (Image image = new Image(500, 500)) { - image - .BackgroundColor(Color.Blue) - .FillPolygon(Color.HotPink, simplePath, new GraphicsOptions(true)) - .Save(output); - } + using (FileStream output = File.OpenWrite($"{path}/Simple.png")) + { + image + .BackgroundColor(Color.Blue) + .FillPolygon(Color.HotPink, simplePath, new GraphicsOptions(true)) + .Save(output); + } - using (var sourcePixels = image.Lock()) - { - Assert.Equal(Color.HotPink, sourcePixels[11, 11]); + using (PixelAccessor sourcePixels = image.Lock()) + { + Assert.Equal(Color.HotPink, sourcePixels[11, 11]); - Assert.Equal(Color.HotPink, sourcePixels[200, 150]); + Assert.Equal(Color.HotPink, sourcePixels[200, 150]); - Assert.Equal(Color.HotPink, sourcePixels[50, 50]); + Assert.Equal(Color.HotPink, sourcePixels[50, 50]); - Assert.Equal(Color.Blue, sourcePixels[2, 2]); + Assert.Equal(Color.Blue, sourcePixels[2, 2]); + } } } [Fact] - public void ImageShouldBeOverlayedByFilledPolygon_NoAntialias() + public void ImageShouldBeOverlayedByFilledPolygonNoAntialias() { - string path = CreateOutputDirectory("Drawing", "FilledPolygons"); - var simplePath = new[] { + string path = this.CreateOutputDirectory("Drawing", "FilledPolygons"); + Vector2[] simplePath = new[] { new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300) }; - var image = new Image(500, 500); + using (Image image = new Image(500, 500)) using (FileStream output = File.OpenWrite($"{path}/Simple_NoAntialias.png")) { image .BackgroundColor(Color.Blue) .FillPolygon(Color.HotPink, simplePath, new GraphicsOptions(false)) .Save(output); - } - using (var sourcePixels = image.Lock()) - { - Assert.Equal(Color.HotPink, sourcePixels[11, 11]); + using (PixelAccessor sourcePixels = image.Lock()) + { + Assert.Equal(Color.HotPink, sourcePixels[11, 11]); - Assert.Equal(Color.HotPink, sourcePixels[200, 150]); + Assert.Equal(Color.HotPink, sourcePixels[200, 150]); - Assert.Equal(Color.HotPink, sourcePixels[50, 50]); + Assert.Equal(Color.HotPink, sourcePixels[50, 50]); - Assert.Equal(Color.Blue, sourcePixels[2, 2]); + Assert.Equal(Color.Blue, sourcePixels[2, 2]); + } } } [Fact] - public void ImageShouldBeOverlayedByFilledPolygon_Image() + public void ImageShouldBeOverlayedByFilledPolygonImage() { - string path = CreateOutputDirectory("Drawing", "FilledPolygons"); - var simplePath = new[] { + string path = this.CreateOutputDirectory("Drawing", "FilledPolygons"); + Vector2[] simplePath = new[] { new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300) }; - var brush = new ImageBrush(TestFile.Create(TestImages.Bmp.Car).CreateImage()); - var image = new Image(500, 500); - + using (Image brushImage = TestFile.Create(TestImages.Bmp.Car).CreateImage()) + using (Image image = new Image(500, 500)) using (FileStream output = File.OpenWrite($"{path}/Image.png")) { + ImageBrush brush = new ImageBrush(brushImage); + image - .BackgroundColor(Color.Blue) - .FillPolygon(brush, simplePath) - .Save(output); + .BackgroundColor(Color.Blue) + .FillPolygon(brush, simplePath) + .Save(output); } } [Fact] public void ImageShouldBeOverlayedByFilledPolygonOpacity() { - string path = CreateOutputDirectory("Drawing", "FilledPolygons"); - var simplePath = new[] { + string path = this.CreateOutputDirectory("Drawing", "FilledPolygons"); + Vector2[] simplePath = new[] { new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300) }; - var color = new Color(Color.HotPink.R, Color.HotPink.G, Color.HotPink.B, 150); - - var image = new Image(500, 500); + Color color = new Color(Color.HotPink.R, Color.HotPink.G, Color.HotPink.B, 150); - using (FileStream output = File.OpenWrite($"{path}/Opacity.png")) + using (Image image = new Image(500, 500)) { - image - .BackgroundColor(Color.Blue) - .FillPolygon(color, simplePath) - .Save(output); - } + using (FileStream output = File.OpenWrite($"{path}/Opacity.png")) + { + image + .BackgroundColor(Color.Blue) + .FillPolygon(color, simplePath) + .Save(output); + } - //shift background color towards forground color by the opacity amount - var mergedColor = new Color(Vector4.Lerp(Color.Blue.ToVector4(), Color.HotPink.ToVector4(), 150f / 255f)); + //shift background color towards forground color by the opacity amount + Color mergedColor = new Color(Vector4.Lerp(Color.Blue.ToVector4(), Color.HotPink.ToVector4(), 150f / 255f)); - using (var sourcePixels = image.Lock()) - { - Assert.Equal(mergedColor, sourcePixels[11, 11]); + using (PixelAccessor sourcePixels = image.Lock()) + { + Assert.Equal(mergedColor, sourcePixels[11, 11]); - Assert.Equal(mergedColor, sourcePixels[200, 150]); + Assert.Equal(mergedColor, sourcePixels[200, 150]); - Assert.Equal(mergedColor, sourcePixels[50, 50]); + Assert.Equal(mergedColor, sourcePixels[50, 50]); - Assert.Equal(Color.Blue, sourcePixels[2, 2]); + Assert.Equal(Color.Blue, sourcePixels[2, 2]); + } } } [Fact] public void ImageShouldBeOverlayedByFilledRectangle() { - string path = CreateOutputDirectory("Drawing", "FilledPolygons"); - var simplePath = new[] { - new Vector2(10, 10), - new Vector2(200, 10), - new Vector2(200, 150), - new Vector2(10, 150) - }; - - var image = new Image(500, 500); - - using (FileStream output = File.OpenWrite($"{path}/Rectangle.png")) + string path = this.CreateOutputDirectory("Drawing", "FilledPolygons"); + using (Image image = new Image(500, 500)) { - image - .BackgroundColor(Color.Blue) - .Fill(Color.HotPink, new ImageSharp.Drawing.Shapes.RectangularPolygon(new Rectangle(10,10, 190, 140))) - .Save(output); - } + using (FileStream output = File.OpenWrite($"{path}/Rectangle.png")) + { + image + .BackgroundColor(Color.Blue) + .Fill(Color.HotPink, new ImageSharp.Drawing.Shapes.RectangularPolygon(new Rectangle(10, 10, 190, 140))) + .Save(output); + } - using (var sourcePixels = image.Lock()) - { - Assert.Equal(Color.HotPink, sourcePixels[11, 11]); + using (PixelAccessor sourcePixels = image.Lock()) + { + Assert.Equal(Color.HotPink, sourcePixels[11, 11]); - Assert.Equal(Color.HotPink, sourcePixels[198, 10]); + Assert.Equal(Color.HotPink, sourcePixels[198, 10]); - Assert.Equal(Color.HotPink, sourcePixels[10, 50]); + Assert.Equal(Color.HotPink, sourcePixels[10, 50]); - Assert.Equal(Color.HotPink, sourcePixels[50, 50]); + Assert.Equal(Color.HotPink, sourcePixels[50, 50]); - Assert.Equal(Color.Blue, sourcePixels[2, 2]); + Assert.Equal(Color.Blue, sourcePixels[2, 2]); + } } } } diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BitmapTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BitmapTests.cs index c91b0ad1b1..2eb81a6232 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BitmapTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BitmapTests.cs @@ -9,8 +9,6 @@ namespace ImageSharp.Tests { using System.IO; - using Formats; - using Xunit; public class BitmapTests : FileTestBase @@ -23,19 +21,20 @@ namespace ImageSharp.Tests }; [Theory] - [MemberData("BitsPerPixel")] + [MemberData(nameof(BitsPerPixel))] public void BitmapCanEncodeDifferentBitRates(BmpBitsPerPixel bitsPerPixel) { - string path = CreateOutputDirectory("Bmp"); + string path = this.CreateOutputDirectory("Bmp"); foreach (TestFile file in Files) { string filename = file.GetFileNameWithoutExtension(bitsPerPixel); - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{filename}.bmp")) + using (Image image = file.CreateImage()) { - image.Save(output, new BmpEncoder { BitsPerPixel = bitsPerPixel }); + using (FileStream output = File.OpenWrite($"{path}/{filename}.bmp")) + { + image.Save(output, new BmpEncoder { BitsPerPixel = bitsPerPixel }); + } } } } diff --git a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs index 9ead4cf567..fc74fe928a 100644 --- a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs +++ b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs @@ -16,17 +16,18 @@ namespace ImageSharp.Tests [Fact] public void ResolutionShouldChange() { - string path = CreateOutputDirectory("Resolution"); + string path = this.CreateOutputDirectory("Resolution"); foreach (TestFile file in Files) { - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) + using (Image image = file.CreateImage()) { - image.VerticalResolution = 150; - image.HorizontalResolution = 150; - image.Save(output); + using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) + { + image.VerticalResolution = 150; + image.HorizontalResolution = 150; + image.Save(output); + } } } } @@ -34,45 +35,31 @@ namespace ImageSharp.Tests [Fact] public void ImageCanEncodeToString() { - string path = CreateOutputDirectory("ToString"); + string path = this.CreateOutputDirectory("ToString"); foreach (TestFile file in Files) { - Image image = file.CreateImage(); - - string filename = path + "/" + file.FileNameWithoutExtension + ".txt"; - File.WriteAllText(filename, image.ToBase64String()); + using (Image image = file.CreateImage()) + { + string filename = path + "/" + file.FileNameWithoutExtension + ".txt"; + File.WriteAllText(filename, image.ToBase64String()); + } } } [Fact] public void DecodeThenEncodeImageFromStreamShouldSucceed() { - string path = CreateOutputDirectory("Encode"); + string path = this.CreateOutputDirectory("Encode"); foreach (TestFile file in Files) { - Image image = file.CreateImage(); - - //Image image = file.CreateImage().To(); - //Image image = file.CreateImage().To(); - //Image image = file.CreateImage().To(); - //Image image = file.CreateImage().To(); - //Image image = file.CreateImage().To(); - //Image image = file.CreateImage().To(); - //Image image = file.CreateImage().To(); - //Image image = file.CreateImage().To(); - //Image image = file.CreateImage().To(); - //Image image = file.CreateImage().To(); - //Image image = file.CreateImage().To(); - //Image image = file.CreateImage().To(); - //Image image = file.CreateImage().To(); - //Image image = file.CreateImage().To(); - //Image image = file.CreateImage().To(); - //Image image = file.CreateImage().To(); - using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) + using (Image image = file.CreateImage()) { - image.Save(output); + using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) + { + image.Save(output); + } } } } @@ -80,35 +67,35 @@ namespace ImageSharp.Tests [Fact] public void QuantizeImageShouldPreserveMaximumColorPrecision() { - string path = CreateOutputDirectory("Quantize"); + string path = this.CreateOutputDirectory("Quantize"); foreach (TestFile file in Files) { - Image image = file.CreateImage(); - - // Copy the original pixels to save decoding time. - Color[] pixels = new Color[image.Width * image.Height]; - Array.Copy(image.Pixels, pixels, image.Pixels.Length); - - using (FileStream output = File.OpenWrite($"{path}/Octree-{file.FileName}")) + using (Image image = file.CreateImage()) { - image.Quantize(Quantization.Octree) - .Save(output, image.CurrentImageFormat); + Color[] pixels = new Color[image.Width * image.Height]; + Array.Copy(image.Pixels, pixels, image.Width * image.Height); - } + using (FileStream output = File.OpenWrite($"{path}/Octree-{file.FileName}")) + { + image.Quantize(Quantization.Octree) + .Save(output, image.CurrentImageFormat); - image.SetPixels(image.Width, image.Height, pixels); - using (FileStream output = File.OpenWrite($"{path}/Wu-{file.FileName}")) - { - image.Quantize(Quantization.Wu) - .Save(output, image.CurrentImageFormat); - } + } - image.SetPixels(image.Width, image.Height, pixels); - using (FileStream output = File.OpenWrite($"{path}/Palette-{file.FileName}")) - { - image.Quantize(Quantization.Palette) - .Save(output, image.CurrentImageFormat); + image.SetPixels(image.Width, image.Height, pixels); + using (FileStream output = File.OpenWrite($"{path}/Wu-{file.FileName}")) + { + image.Quantize(Quantization.Wu) + .Save(output, image.CurrentImageFormat); + } + + image.SetPixels(image.Width, image.Height, pixels); + using (FileStream output = File.OpenWrite($"{path}/Palette-{file.FileName}")) + { + image.Quantize(Quantization.Palette) + .Save(output, image.CurrentImageFormat); + } } } } @@ -116,7 +103,7 @@ namespace ImageSharp.Tests [Fact] public void ImageCanConvertFormat() { - string path = CreateOutputDirectory("Format"); + string path = this.CreateOutputDirectory("Format"); foreach (TestFile file in Files) { @@ -147,7 +134,7 @@ namespace ImageSharp.Tests [Fact] public void ImageShouldPreservePixelByteOrderWhenSerialized() { - string path = CreateOutputDirectory("Serialized"); + string path = this.CreateOutputDirectory("Serialized"); foreach (TestFile file in Files) { diff --git a/tests/ImageSharp.Tests/Formats/Jpg/BadEofJpegTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/BadEofJpegTests.cs index 628bc4ea95..adc568c824 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/BadEofJpegTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/BadEofJpegTests.cs @@ -31,9 +31,11 @@ namespace ImageSharp.Tests public void LoadBaselineImage(TestImageProvider provider) where TColor : struct, IPackedPixel, IEquatable { - var image = provider.GetImage(); - Assert.NotNull(image); - provider.Utility.SaveTestOutputFile(image, "bmp"); + using (Image image = provider.GetImage()) + { + Assert.NotNull(image); + provider.Utility.SaveTestOutputFile(image, "bmp"); + } } [Theory] // TODO: #18 @@ -41,9 +43,11 @@ namespace ImageSharp.Tests public void LoadProgressiveImage(TestImageProvider provider) where TColor : struct, IPackedPixel, IEquatable { - var image = provider.GetImage(); - Assert.NotNull(image); - provider.Utility.SaveTestOutputFile(image, "bmp"); + using (Image image = provider.GetImage()) + { + Assert.NotNull(image); + provider.Utility.SaveTestOutputFile(image, "bmp"); + } } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index 01ffa2eb0c..25329d93a9 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -29,19 +29,21 @@ namespace ImageSharp.Tests public void OpenBaselineJpeg_SaveBmp(TestImageProvider provider) where TColor : struct, IPackedPixel, IEquatable { - Image image = provider.GetImage(); - - provider.Utility.SaveTestOutputFile(image, "bmp"); + using (Image image = provider.GetImage()) + { + provider.Utility.SaveTestOutputFile(image, "bmp"); + } } - + [Theory] [WithFileCollection(nameof(ProgressiveTestJpegs), PixelTypes.Color | PixelTypes.StandardImageClass | PixelTypes.Argb)] public void OpenProgressiveJpeg_SaveBmp(TestImageProvider provider) where TColor : struct, IPackedPixel, IEquatable { - Image image = provider.GetImage(); - - provider.Utility.SaveTestOutputFile(image, "bmp"); + using (Image image = provider.GetImage()) + { + provider.Utility.SaveTestOutputFile(image, "bmp"); + } } [Theory] @@ -53,17 +55,19 @@ namespace ImageSharp.Tests public void DecodeGenerated_SaveBmp( TestImageProvider provider, JpegSubsample subsample, - int quality) + int quality) where TColor : struct, IPackedPixel, IEquatable { - Image image = provider.GetImage(); - - JpegEncoder encoder = new JpegEncoder() { Subsample = subsample, Quality = quality }; - - byte[] data = new byte[65536]; - using (MemoryStream ms = new MemoryStream(data)) + byte[] data; + using (Image image = provider.GetImage()) { - image.Save(ms, encoder); + JpegEncoder encoder = new JpegEncoder() { Subsample = subsample, Quality = quality }; + + data = new byte[65536]; + using (MemoryStream ms = new MemoryStream(data)) + { + image.Save(ms, encoder); + } } // TODO: Automatic image comparers could help here a lot :P @@ -75,23 +79,24 @@ namespace ImageSharp.Tests [Theory] [WithSolidFilledImages(42, 88, 255, 0, 0, PixelTypes.StandardImageClass)] public void DecodeGenerated_MetadataOnly( - TestImageProvider provider) + TestImageProvider provider) where TColor : struct, IPackedPixel, IEquatable { - Image image = provider.GetImage(); - - using (MemoryStream ms = new MemoryStream()) + using (Image image = provider.GetImage()) { - image.Save(ms, new JpegEncoder()); - ms.Seek(0, SeekOrigin.Begin); - - Image mirror = provider.Factory.CreateImage(1, 1); - using (JpegDecoderCore decoder = new JpegDecoderCore()) + using (MemoryStream ms = new MemoryStream()) { - decoder.Decode(mirror, ms, true); - - Assert.Equal(decoder.ImageWidth, image.Width); - Assert.Equal(decoder.ImageHeight, image.Height); + image.Save(ms, new JpegEncoder()); + ms.Seek(0, SeekOrigin.Begin); + + Image mirror = provider.Factory.CreateImage(1, 1); + using (JpegDecoderCore decoder = new JpegDecoderCore()) + { + decoder.Decode(mirror, ms, true); + + Assert.Equal(decoder.ImageWidth, image.Width); + Assert.Equal(decoder.ImageHeight, image.Height); + } } } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs index d1a5e185f5..59e5eba5c1 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs @@ -34,19 +34,16 @@ namespace ImageSharp.Tests public void LoadResizeSave(TestImageProvider provider, int quality, JpegSubsample subsample) where TColor : struct, IPackedPixel, IEquatable { - Image image = provider.GetImage() - .Resize(new ResizeOptions - { - Size = new Size(150, 100), - Mode = ResizeMode.Max - }); - image.Quality = quality; - image.ExifProfile = null; // Reduce the size of the file - JpegEncoder encoder = new JpegEncoder { Subsample = subsample, Quality = quality }; + using (Image image = provider.GetImage().Resize(new ResizeOptions { Size = new Size(150, 100), Mode = ResizeMode.Max })) + { + image.Quality = quality; + image.ExifProfile = null; // Reduce the size of the file + JpegEncoder encoder = new JpegEncoder { Subsample = subsample, Quality = quality }; - provider.Utility.TestName += $"{subsample}_Q{quality}"; - provider.Utility.SaveTestOutputFile(image, "png"); - provider.Utility.SaveTestOutputFile(image, "jpg", encoder); + provider.Utility.TestName += $"{subsample}_Q{quality}"; + provider.Utility.SaveTestOutputFile(image, "png"); + provider.Utility.SaveTestOutputFile(image, "jpg", encoder); + } } [Theory] @@ -55,20 +52,21 @@ namespace ImageSharp.Tests public void OpenBmp_SaveJpeg(TestImageProvider provider, JpegSubsample subSample, int quality) where TColor : struct, IPackedPixel, IEquatable { - Image image = provider.GetImage(); - - ImagingTestCaseUtility utility = provider.Utility; - utility.TestName += "_" + subSample + "_Q" + quality; - - using (var outputStream = File.OpenWrite(utility.GetTestOutputFileName("jpg"))) + using (Image image = provider.GetImage()) { - var encoder = new JpegEncoder() + ImagingTestCaseUtility utility = provider.Utility; + utility.TestName += "_" + subSample + "_Q" + quality; + + using (FileStream outputStream = File.OpenWrite(utility.GetTestOutputFileName("jpg"))) { - Subsample = subSample, - Quality = quality - }; + JpegEncoder encoder = new JpegEncoder() + { + Subsample = subSample, + Quality = quality + }; - image.Save(outputStream, encoder); + image.Save(outputStream, encoder); + } } } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegUtilsTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegUtilsTests.cs index cc7df178be..0956131b14 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegUtilsTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegUtilsTests.cs @@ -19,7 +19,6 @@ namespace ImageSharp.Tests where TColor : struct, IPackedPixel, IEquatable { Image image = factory.CreateImage(10, 10); - using (PixelAccessor pixels = image.Lock()) { for (int i = 0; i < 10; i++) @@ -35,6 +34,7 @@ namespace ImageSharp.Tests } } } + return image; } @@ -43,24 +43,21 @@ namespace ImageSharp.Tests public void CopyStretchedRGBTo_FromOrigo(TestImageProvider provider) where TColor : struct, IPackedPixel, IEquatable { - Image src = provider.GetImage(); - - PixelArea area = new PixelArea(8, 8, ComponentOrder.Xyz); - Image dest = provider.Factory.CreateImage(8, 8); - - using (var s = src.Lock()) + using (Image src = provider.GetImage()) + using (Image dest = provider.Factory.CreateImage(8, 8)) + using (PixelArea area = new PixelArea(8, 8, ComponentOrder.Xyz)) + using (PixelAccessor s = src.Lock()) + using (PixelAccessor d = dest.Lock()) { - using (var d = dest.Lock()) - { - s.CopyRGBBytesStretchedTo(area, 0, 0); - d.CopyFrom(area, 0, 0); + s.CopyRGBBytesStretchedTo(area, 0, 0); + d.CopyFrom(area, 0, 0); - Assert.Equal(s[0, 0], d[0, 0]); - Assert.Equal(s[7, 0], d[7, 0]); - Assert.Equal(s[0, 7], d[0, 7]); - Assert.Equal(s[7, 7], d[7, 7]); - } + Assert.Equal(s[0, 0], d[0, 0]); + Assert.Equal(s[7, 0], d[7, 0]); + Assert.Equal(s[0, 7], d[0, 7]); + Assert.Equal(s[7, 7], d[7, 7]); } + } [Theory] @@ -68,45 +65,41 @@ namespace ImageSharp.Tests public void CopyStretchedRGBTo_WithOffset(TestImageProvider provider) where TColor : struct, IPackedPixel, IEquatable { - Image src = provider.GetImage(); - - PixelArea area = new PixelArea(8, 8, ComponentOrder.Xyz); - Image dest = provider.Factory.CreateImage(8, 8); - + using (Image src = provider.GetImage()) + using (PixelArea area = new PixelArea(8, 8, ComponentOrder.Xyz)) + using (Image dest = provider.Factory.CreateImage(8, 8)) using (PixelAccessor s = src.Lock()) + using (PixelAccessor d = dest.Lock()) { - using (var d = dest.Lock()) - { - s.CopyRGBBytesStretchedTo(area, 7, 6); - d.CopyFrom(area, 0, 0); + s.CopyRGBBytesStretchedTo(area, 7, 6); + d.CopyFrom(area, 0, 0); - Assert.Equal(s[6, 7], d[0, 0]); - Assert.Equal(s[6, 8], d[0, 1]); - Assert.Equal(s[7, 8], d[1, 1]); + Assert.Equal(s[6, 7], d[0, 0]); + Assert.Equal(s[6, 8], d[0, 1]); + Assert.Equal(s[7, 8], d[1, 1]); - Assert.Equal(s[6, 9], d[0, 2]); - Assert.Equal(s[6, 9], d[0, 3]); - Assert.Equal(s[6, 9], d[0, 7]); + Assert.Equal(s[6, 9], d[0, 2]); + Assert.Equal(s[6, 9], d[0, 3]); + Assert.Equal(s[6, 9], d[0, 7]); - Assert.Equal(s[7, 9], d[1, 2]); - Assert.Equal(s[7, 9], d[1, 3]); - Assert.Equal(s[7, 9], d[1, 7]); + Assert.Equal(s[7, 9], d[1, 2]); + Assert.Equal(s[7, 9], d[1, 3]); + Assert.Equal(s[7, 9], d[1, 7]); - Assert.Equal(s[9, 9], d[3, 2]); - Assert.Equal(s[9, 9], d[3, 3]); - Assert.Equal(s[9, 9], d[3, 7]); + Assert.Equal(s[9, 9], d[3, 2]); + Assert.Equal(s[9, 9], d[3, 3]); + Assert.Equal(s[9, 9], d[3, 7]); - Assert.Equal(s[9, 7], d[3, 0]); - Assert.Equal(s[9, 7], d[4, 0]); - Assert.Equal(s[9, 7], d[7, 0]); + Assert.Equal(s[9, 7], d[3, 0]); + Assert.Equal(s[9, 7], d[4, 0]); + Assert.Equal(s[9, 7], d[7, 0]); - Assert.Equal(s[9, 9], d[3, 2]); - Assert.Equal(s[9, 9], d[4, 2]); - Assert.Equal(s[9, 9], d[7, 2]); + Assert.Equal(s[9, 9], d[3, 2]); + Assert.Equal(s[9, 9], d[4, 2]); + Assert.Equal(s[9, 9], d[7, 2]); - Assert.Equal(s[9, 9], d[4, 3]); - Assert.Equal(s[9, 9], d[7, 7]); - } + Assert.Equal(s[9, 9], d[4, 3]); + Assert.Equal(s[9, 9], d[7, 7]); } } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.cs index 10106ae6f1..589317a361 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.cs @@ -6,8 +6,6 @@ // ReSharper disable InconsistentNaming namespace ImageSharp.Tests.Formats.Jpg { - using System.Numerics; - using ImageSharp.Formats; using ImageSharp.Formats.Jpg; using Xunit; @@ -97,7 +95,7 @@ namespace ImageSharp.Tests.Formats.Jpg Assert.Equal(expected, actual, new ApproximateFloatComparer(2f)); } } - + [Theory] [InlineData(42)] [InlineData(1)] diff --git a/tests/ImageSharp.Tests/Formats/Png/PngTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngTests.cs index a76cbc615f..ae6487ea9c 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngTests.cs @@ -23,12 +23,13 @@ namespace ImageSharp.Tests foreach (TestFile file in Files) { - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{file.FileNameWithoutExtension}.png")) + using (Image image = file.CreateImage()) { - image.Quality = 256; - image.Save(output, new PngFormat()); + using (FileStream output = File.OpenWrite($"{path}/{file.FileNameWithoutExtension}.png")) + { + image.Quality = 256; + image.Save(output, new PngFormat()); + } } } } @@ -42,11 +43,12 @@ namespace ImageSharp.Tests Files, file => { - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{file.FileNameWithoutExtension}.png")) + using (Image image = file.CreateImage()) { - image.SaveAsPng(output); + using (FileStream output = File.OpenWrite($"{path}/{file.FileNameWithoutExtension}.png")) + { + image.SaveAsPng(output); + } } }); } diff --git a/tests/ImageSharp.Tests/Image/ImageTests.cs b/tests/ImageSharp.Tests/Image/ImageTests.cs index 829dce7486..c3c092e8eb 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.cs @@ -23,10 +23,11 @@ namespace ImageSharp.Tests }); TestFile file = TestFile.Create(TestImages.Bmp.Car); - Image image = new Image(file.Bytes); - - Assert.Equal(600, image.Width); - Assert.Equal(450, image.Height); + using (Image image = new Image(file.Bytes)) + { + Assert.Equal(600, image.Width); + Assert.Equal(450, image.Height); + } } } } diff --git a/tests/ImageSharp.Tests/Image/PixelAccessorTests.cs b/tests/ImageSharp.Tests/Image/PixelAccessorTests.cs index ce28518d73..a0bf2cf7a6 100644 --- a/tests/ImageSharp.Tests/Image/PixelAccessorTests.cs +++ b/tests/ImageSharp.Tests/Image/PixelAccessorTests.cs @@ -19,8 +19,7 @@ namespace ImageSharp.Tests where TColor : struct, IPackedPixel, IEquatable { Image image = factory.CreateImage(10, 10); - - using (var pixels = image.Lock()) + using (PixelAccessor pixels = image.Lock()) { for (int i = 0; i < 10; i++) { @@ -47,31 +46,33 @@ namespace ImageSharp.Tests public void CopyTo_Then_CopyFrom_OnFullImageRect(TestImageProvider provider, ComponentOrder order) where TColor : struct, IPackedPixel, IEquatable { - var src = provider.GetImage(); - - var dest = new Image(src.Width, src.Height); - - using (PixelArea area = new PixelArea(src.Width, src.Height, order)) + using (Image src = provider.GetImage()) { - using (var srcPixels = src.Lock()) + using (Image dest = new Image(src.Width, src.Height)) { - srcPixels.CopyTo(area, 0, 0); - } + using (PixelArea area = new PixelArea(src.Width, src.Height, order)) + { + using (PixelAccessor srcPixels = src.Lock()) + { + srcPixels.CopyTo(area, 0, 0); + } + + using (PixelAccessor destPixels = dest.Lock()) + { + destPixels.CopyFrom(area, 0, 0); + } + } - using (var destPixels = dest.Lock()) - { - destPixels.CopyFrom(area, 0, 0); + Assert.True(src.IsEquivalentTo(dest, false)); } } - - Assert.True(src.IsEquivalentTo(dest, false)); } // TODO: Need a processor in the library with this signature private static void Fill(Image image, Rectangle region, TColor color) where TColor : struct, IPackedPixel, IEquatable { - using (var pixels = image.Lock()) + using (PixelAccessor pixels = image.Lock()) { for (int y = region.Top; y < region.Bottom; y++) { @@ -88,87 +89,114 @@ namespace ImageSharp.Tests [WithBlankImages(16, 16, PixelTypes.All, ComponentOrder.Zyx)] [WithBlankImages(16, 16, PixelTypes.All, ComponentOrder.Xyzw)] [WithBlankImages(16, 16, PixelTypes.All, ComponentOrder.Zyxw)] - public void CopyTo_Then_CopyFrom_WithOffset(TestImageProvider provider, ComponentOrder order) + public void CopyToThenCopyFromWithOffset(TestImageProvider provider, ComponentOrder order) where TColor : struct, IPackedPixel, IEquatable { - var srcImage = provider.GetImage(); - - var color = default(TColor); - color.PackFromBytes(255, 0, 0, 255); - - Fill(srcImage, new Rectangle(4, 4, 8, 8), color); - - var destImage = new Image(8, 8); - - using (var srcPixels = srcImage.Lock()) + using (Image destImage = new Image(8, 8)) { - using (var area = new PixelArea(8, 8, order)) + TColor color; + using (Image srcImage = provider.GetImage()) { - srcPixels.CopyTo(area, 4, 4); + color = default(TColor); + color.PackFromBytes(255, 0, 0, 255); - using (var destPixels = destImage.Lock()) + Fill(srcImage, new Rectangle(4, 4, 8, 8), color); + using (PixelAccessor srcPixels = srcImage.Lock()) { - destPixels.CopyFrom(area, 0, 0); + using (PixelArea area = new PixelArea(8, 8, order)) + { + srcPixels.CopyTo(area, 4, 4); + + using (PixelAccessor destPixels = destImage.Lock()) + { + destPixels.CopyFrom(area, 0, 0); + } + } } } - } - - provider.Utility.SourceFileOrDescription = order.ToString(); - provider.Utility.SaveTestOutputFile(destImage, "bmp"); - var expectedImage = new Image(8, 8).Fill(color); + provider.Utility.SourceFileOrDescription = order.ToString(); + provider.Utility.SaveTestOutputFile(destImage, "bmp"); - Assert.True(destImage.IsEquivalentTo(expectedImage)); + using (Image expectedImage = new Image(8, 8).Fill(color)) + { + Assert.True(destImage.IsEquivalentTo(expectedImage)); + } + } } [Fact] public void CopyFromZYX() { - CopyFromZYX(new Image(1, 1)); + using (Image image = new Image(1, 1)) + { + CopyFromZYX(image); + } } [Fact] public void CopyFromZYXOptimized() { - CopyFromZYX(new Image(1, 1)); + using (Image image = new Image(1, 1)) + { + CopyFromZYX(image); + } } [Fact] public void CopyFromZYXW() { - CopyFromZYXW(new Image(1, 1)); + using (Image image = new Image(1, 1)) + { + CopyFromZYXW(image); + } } [Fact] public void CopyFromZYXWOptimized() { - CopyFromZYXW(new Image(1, 1)); + using (Image image = new Image(1, 1)) + { + CopyFromZYXW(image); + } } [Fact] public void CopyToZYX() { - CopyToZYX(new Image(1, 1)); + using (Image image = new Image(1, 1)) + { + CopyToZYX(image); + } } [Fact] public void CopyToZYXOptimized() { - CopyToZYX(new Image(1, 1)); + using (Image image = new Image(1, 1)) + { + CopyToZYX(image); + } } [Fact] public void CopyToZYXW() { - CopyToZYXW(new Image(1, 1)); + using (Image image = new Image(1, 1)) + { + CopyToZYXW(image); + } } [Fact] public void CopyToZYXWOptimized() { - CopyToZYXW(new Image(1, 1)); + using (Image image = new Image(1, 1)) + { + CopyToZYXW(image); + } } private static void CopyFromZYX(Image image) diff --git a/tests/ImageSharp.Tests/Processors/Filters/AlphaTest.cs b/tests/ImageSharp.Tests/Processors/Filters/AlphaTest.cs index efbfe75a86..a8aeb33418 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/AlphaTest.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/AlphaTest.cs @@ -19,39 +19,35 @@ namespace ImageSharp.Tests }; [Theory] - [MemberData("AlphaValues")] + [MemberData(nameof(AlphaValues))] public void ImageShouldApplyAlphaFilter(int value) { - string path = CreateOutputDirectory("Alpha"); + string path = this.CreateOutputDirectory("Alpha"); foreach (TestFile file in Files) { string filename = file.GetFileName(value); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - image.Alpha(value) - .Save(output); + image.Alpha(value).Save(output); } } } [Theory] - [MemberData("AlphaValues")] + [MemberData(nameof(AlphaValues))] public void ImageShouldApplyAlphaFilterInBox(int value) { - string path = CreateOutputDirectory("Alpha"); + string path = this.CreateOutputDirectory("Alpha"); foreach (TestFile file in Files) { string filename = file.GetFileName(value); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - image.Alpha(value, new Rectangle(10, 10, image.Width / 2, image.Height / 2)) - .Save(output); + image.Alpha(value, new Rectangle(10, 10, image.Width / 2, image.Height / 2)).Save(output); } } } diff --git a/tests/ImageSharp.Tests/Processors/Filters/AutoOrientTests.cs b/tests/ImageSharp.Tests/Processors/Filters/AutoOrientTests.cs index 10d6253fd3..499bdff823 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/AutoOrientTests.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/AutoOrientTests.cs @@ -26,25 +26,22 @@ namespace ImageSharp.Tests }; [Theory] - [MemberData("OrientationValues")] + [MemberData(nameof(OrientationValues))] public void ImageShouldFlip(RotateType rotateType, FlipType flipType, ushort orientation) { - string path = CreateOutputDirectory("AutoOrient"); + string path = this.CreateOutputDirectory("AutoOrient"); TestFile file = TestFile.Create(TestImages.Bmp.F); - Image image = file.CreateImage(); - image.ExifProfile = new ExifProfile(); - image.ExifProfile.SetValue(ExifTag.Orientation, orientation); - - using (FileStream before = File.OpenWrite($"{path}/before-{file.FileName}")) + using (Image image = file.CreateImage()) { + image.ExifProfile = new ExifProfile(); + image.ExifProfile.SetValue(ExifTag.Orientation, orientation); + + using (FileStream before = File.OpenWrite($"{path}/before-{file.FileName}")) using (FileStream after = File.OpenWrite($"{path}/after-{file.FileName}")) { - image.RotateFlip(rotateType, flipType) - .Save(before) - .AutoOrient() - .Save(after); + image.RotateFlip(rotateType, flipType).Save(before).AutoOrient().Save(after); } } } diff --git a/tests/ImageSharp.Tests/Processors/Filters/BackgroundColorTest.cs b/tests/ImageSharp.Tests/Processors/Filters/BackgroundColorTest.cs index a7ecf6c083..fd08b87a47 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/BackgroundColorTest.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/BackgroundColorTest.cs @@ -14,16 +14,14 @@ namespace ImageSharp.Tests [Fact] public void ImageShouldApplyBackgroundColorFilter() { - string path = CreateOutputDirectory("BackgroundColor"); + string path = this.CreateOutputDirectory("BackgroundColor"); foreach (TestFile file in Files) { - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) { - image.BackgroundColor(Color.HotPink) - .Save(output); + image.BackgroundColor(Color.HotPink).Save(output); } } } diff --git a/tests/ImageSharp.Tests/Processors/Filters/BinaryThreshold.cs b/tests/ImageSharp.Tests/Processors/Filters/BinaryThreshold.cs index 10b174cd56..d7d4eac058 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/BinaryThreshold.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/BinaryThreshold.cs @@ -19,20 +19,18 @@ namespace ImageSharp.Tests }; [Theory] - [MemberData("BinaryThresholdValues")] + [MemberData(nameof(BinaryThresholdValues))] public void ImageShouldApplyBinaryThresholdFilter(float value) { - string path = CreateOutputDirectory("BinaryThreshold"); + string path = this.CreateOutputDirectory("BinaryThreshold"); foreach (TestFile file in Files) { string filename = file.GetFileName(value); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - image.BinaryThreshold(value) - .Save(output); + image.BinaryThreshold(value).Save(output); } } } diff --git a/tests/ImageSharp.Tests/Processors/Filters/BlackWhiteTest.cs b/tests/ImageSharp.Tests/Processors/Filters/BlackWhiteTest.cs index d4af4ad385..6b9a48f72d 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/BlackWhiteTest.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/BlackWhiteTest.cs @@ -14,16 +14,14 @@ namespace ImageSharp.Tests [Fact] public void ImageShouldApplyBlackWhiteFilter() { - string path = CreateOutputDirectory("BlackWhite"); + string path = this.CreateOutputDirectory("BlackWhite"); foreach (TestFile file in Files) { - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) { - image.BlackWhite() - .Save(output); + image.BlackWhite().Save(output); } } } diff --git a/tests/ImageSharp.Tests/Processors/Filters/BoxBlurTest.cs b/tests/ImageSharp.Tests/Processors/Filters/BoxBlurTest.cs index 4755acb1e5..5d4f628eea 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/BoxBlurTest.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/BoxBlurTest.cs @@ -19,20 +19,18 @@ namespace ImageSharp.Tests }; [Theory] - [MemberData("BoxBlurValues")] + [MemberData(nameof(BoxBlurValues))] public void ImageShouldApplyBoxBlurFilter(int value) { - string path = CreateOutputDirectory("BoxBlur"); + string path = this.CreateOutputDirectory("BoxBlur"); foreach (TestFile file in Files) { string filename = file.GetFileName(value); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - image.BoxBlur(value) - .Save(output); + image.BoxBlur(value).Save(output); } } } diff --git a/tests/ImageSharp.Tests/Processors/Filters/BrightnessTest.cs b/tests/ImageSharp.Tests/Processors/Filters/BrightnessTest.cs index e32c7d35e4..e274ef0417 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/BrightnessTest.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/BrightnessTest.cs @@ -19,20 +19,18 @@ namespace ImageSharp.Tests }; [Theory] - [MemberData("BrightnessValues")] + [MemberData(nameof(BrightnessValues))] public void ImageShouldApplyBrightnessFilter(int value) { - string path = CreateOutputDirectory("Brightness"); + string path = this.CreateOutputDirectory("Brightness"); foreach (TestFile file in Files) { string filename = file.GetFileName(value); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - image.Brightness(value) - .Save(output); + image.Brightness(value).Save(output); } } } diff --git a/tests/ImageSharp.Tests/Processors/Filters/ColorBlindnessTest.cs b/tests/ImageSharp.Tests/Processors/Filters/ColorBlindnessTest.cs index 63005733a0..d18f32caf2 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/ColorBlindnessTest.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/ColorBlindnessTest.cs @@ -26,20 +26,18 @@ namespace ImageSharp.Tests }; [Theory] - [MemberData("ColorBlindnessFilters")] + [MemberData(nameof(ColorBlindnessFilters))] public void ImageShouldApplyColorBlindnessFilter(ColorBlindness colorBlindness) { - string path = CreateOutputDirectory("ColorBlindness"); + string path = this.CreateOutputDirectory("ColorBlindness"); foreach (TestFile file in Files) { string filename = file.GetFileName(colorBlindness); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - image.ColorBlindness(colorBlindness) - .Save(output); + image.ColorBlindness(colorBlindness).Save(output); } } } diff --git a/tests/ImageSharp.Tests/Processors/Filters/ContrastTest.cs b/tests/ImageSharp.Tests/Processors/Filters/ContrastTest.cs index 3c83fd892b..09376f2c05 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/ContrastTest.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/ContrastTest.cs @@ -19,19 +19,17 @@ namespace ImageSharp.Tests }; [Theory] - [MemberData("ContrastValues")] + [MemberData(nameof(ContrastValues))] public void ImageShouldApplyContrastFilter(int value) { - string path = CreateOutputDirectory("Contrast"); + string path = this.CreateOutputDirectory("Contrast"); foreach (TestFile file in Files) { - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) { - image.Contrast(value) - .Save(output); + image.Contrast(value).Save(output); } } } diff --git a/tests/ImageSharp.Tests/Processors/Filters/CropTest.cs b/tests/ImageSharp.Tests/Processors/Filters/CropTest.cs index 9e9dd34db2..69c9d9372b 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/CropTest.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/CropTest.cs @@ -14,16 +14,14 @@ namespace ImageSharp.Tests [Fact] public void ImageShouldApplyCropSampler() { - string path = CreateOutputDirectory("Crop"); + string path = this.CreateOutputDirectory("Crop"); foreach (TestFile file in Files) { - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) { - image.Crop(image.Width / 2, image.Height / 2) - .Save(output); + image.Crop(image.Width / 2, image.Height / 2).Save(output); } } } diff --git a/tests/ImageSharp.Tests/Processors/Filters/DetectEdgesTest.cs b/tests/ImageSharp.Tests/Processors/Filters/DetectEdgesTest.cs index 1c3815b9bb..e12440106d 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/DetectEdgesTest.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/DetectEdgesTest.cs @@ -31,32 +31,29 @@ namespace ImageSharp.Tests [MemberData(nameof(DetectEdgesFilters))] public void ImageShouldApplyDetectEdgesFilter(EdgeDetection detector) { - string path = CreateOutputDirectory("DetectEdges"); + string path = this.CreateOutputDirectory("DetectEdges"); foreach (TestFile file in Files) { string filename = file.GetFileName(detector); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - image.DetectEdges(detector) - .Save(output); + image.DetectEdges(detector).Save(output); } } } [Theory] - [MemberData("DetectEdgesFilters")] + [MemberData(nameof(DetectEdgesFilters))] public void ImageShouldApplyDetectEdgesFilterInBox(EdgeDetection detector) { - string path = CreateOutputDirectory("DetectEdges"); + string path = this.CreateOutputDirectory("DetectEdges"); foreach (TestFile file in Files) { string filename = file.GetFileName(detector + "-InBox"); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { image.DetectEdges(detector, new Rectangle(image.Width / 4, image.Height / 4, image.Width / 2, image.Height / 2)) diff --git a/tests/ImageSharp.Tests/Processors/Filters/EntropyCropTest.cs b/tests/ImageSharp.Tests/Processors/Filters/EntropyCropTest.cs index fdbbc5cdec..1299d9814b 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/EntropyCropTest.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/EntropyCropTest.cs @@ -19,20 +19,18 @@ namespace ImageSharp.Tests }; [Theory] - [MemberData("EntropyCropValues")] + [MemberData(nameof(EntropyCropValues))] public void ImageShouldApplyEntropyCropSampler(float value) { - string path = CreateOutputDirectory("EntropyCrop"); + string path = this.CreateOutputDirectory("EntropyCrop"); foreach (TestFile file in Files) { string filename = file.GetFileName(value); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - image.EntropyCrop(value) - .Save(output); + image.EntropyCrop(value).Save(output); } } } diff --git a/tests/ImageSharp.Tests/Processors/Filters/FlipTests.cs b/tests/ImageSharp.Tests/Processors/Filters/FlipTests.cs index 26f9646769..26bc240d57 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/FlipTests.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/FlipTests.cs @@ -20,20 +20,18 @@ namespace ImageSharp.Tests }; [Theory] - [MemberData("FlipValues")] + [MemberData(nameof(FlipValues))] public void ImageShouldFlip(FlipType flipType) { - string path = CreateOutputDirectory("Flip"); + string path = this.CreateOutputDirectory("Flip"); foreach (TestFile file in Files) { string filename = file.GetFileName(flipType); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - image.Flip(flipType) - .Save(output); + image.Flip(flipType).Save(output); } } } diff --git a/tests/ImageSharp.Tests/Processors/Filters/GaussianBlurTest.cs b/tests/ImageSharp.Tests/Processors/Filters/GaussianBlurTest.cs index 6a82796890..809ffa2f56 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/GaussianBlurTest.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/GaussianBlurTest.cs @@ -19,20 +19,18 @@ namespace ImageSharp.Tests }; [Theory] - [MemberData("GaussianBlurValues")] + [MemberData(nameof(GaussianBlurValues))] public void ImageShouldApplyGaussianBlurFilter(int value) { - string path = CreateOutputDirectory("GaussianBlur"); + string path = this.CreateOutputDirectory("GaussianBlur"); foreach (TestFile file in Files) { string filename = file.GetFileName(value); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - image.GaussianBlur(value) - .Save(output); + image.GaussianBlur(value).Save(output); } } } diff --git a/tests/ImageSharp.Tests/Processors/Filters/GaussianSharpenTest.cs b/tests/ImageSharp.Tests/Processors/Filters/GaussianSharpenTest.cs index 69c24ab3ba..c1aa069414 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/GaussianSharpenTest.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/GaussianSharpenTest.cs @@ -19,20 +19,18 @@ namespace ImageSharp.Tests }; [Theory] - [MemberData("GaussianSharpenValues")] + [MemberData(nameof(GaussianSharpenValues))] public void ImageShouldApplyGaussianSharpenFilter(int value) { - string path = CreateOutputDirectory("GaussianSharpen"); + string path = this.CreateOutputDirectory("GaussianSharpen"); foreach (TestFile file in Files) { string filename = file.GetFileName(value); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - image.GaussianSharpen(value) - .Save(output); + image.GaussianSharpen(value).Save(output); } } } diff --git a/tests/ImageSharp.Tests/Processors/Filters/GlowTest.cs b/tests/ImageSharp.Tests/Processors/Filters/GlowTest.cs index 1d317795d4..1afb1300a9 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/GlowTest.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/GlowTest.cs @@ -14,16 +14,14 @@ namespace ImageSharp.Tests [Fact] public void ImageShouldApplyGlowFilter() { - string path = CreateOutputDirectory("Glow"); + string path = this.CreateOutputDirectory("Glow"); foreach (TestFile file in Files) { - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) { - image.Glow() - .Save(output); + image.Glow().Save(output); } } } @@ -31,17 +29,15 @@ namespace ImageSharp.Tests [Fact] public void ImageShouldApplyGlowFilterColor() { - string path = CreateOutputDirectory("Glow"); + string path = this.CreateOutputDirectory("Glow"); foreach (TestFile file in Files) { string filename = file.GetFileName("Color"); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - image.Glow(Color.HotPink) - .Save(output); + image.Glow(Color.HotPink).Save(output); } } } @@ -49,17 +45,15 @@ namespace ImageSharp.Tests [Fact] public void ImageShouldApplyGlowFilterRadius() { - string path = CreateOutputDirectory("Glow"); + string path = this.CreateOutputDirectory("Glow"); foreach (TestFile file in Files) { string filename = file.GetFileName("Radius"); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - image.Glow(image.Width / 4) - .Save(output); + image.Glow(image.Width / 4F).Save(output); } } } @@ -67,17 +61,16 @@ namespace ImageSharp.Tests [Fact] public void ImageShouldApplyGlowFilterInBox() { - string path = CreateOutputDirectory("Glow"); + string path = this.CreateOutputDirectory("Glow"); foreach (TestFile file in Files) { string filename = file.GetFileName("InBox"); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { image.Glow(new Rectangle(image.Width / 4, image.Height / 4, image.Width / 2, image.Height / 2)) - .Save(output); + .Save(output); } } } diff --git a/tests/ImageSharp.Tests/Processors/Filters/GrayscaleTest.cs b/tests/ImageSharp.Tests/Processors/Filters/GrayscaleTest.cs index ee4d0b0278..91f383dd2c 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/GrayscaleTest.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/GrayscaleTest.cs @@ -20,20 +20,18 @@ namespace ImageSharp.Tests }; [Theory] - [MemberData("GrayscaleValues")] + [MemberData(nameof(GrayscaleValues))] public void ImageShouldApplyGrayscaleFilter(GrayscaleMode value) { - string path = CreateOutputDirectory("Grayscale"); + string path = this.CreateOutputDirectory("Grayscale"); foreach (TestFile file in Files) { string filename = file.GetFileName(value); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - image.Grayscale(value) - .Save(output); + image.Grayscale(value).Save(output); } } } diff --git a/tests/ImageSharp.Tests/Processors/Filters/HueTest.cs b/tests/ImageSharp.Tests/Processors/Filters/HueTest.cs index a56aec9ec2..4241dc8333 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/HueTest.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/HueTest.cs @@ -19,20 +19,18 @@ namespace ImageSharp.Tests }; [Theory] - [MemberData("HueValues")] + [MemberData(nameof(HueValues))] public void ImageShouldApplyHueFilter(int value) { - string path = CreateOutputDirectory("Hue"); + string path = this.CreateOutputDirectory("Hue"); foreach (TestFile file in Files) { string filename = file.GetFileName(value); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - image.Hue(value) - .Save(output); + image.Hue(value).Save(output); } } } diff --git a/tests/ImageSharp.Tests/Processors/Filters/InvertTest.cs b/tests/ImageSharp.Tests/Processors/Filters/InvertTest.cs index 55bfa852b1..da672f8307 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/InvertTest.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/InvertTest.cs @@ -14,15 +14,13 @@ namespace ImageSharp.Tests [Fact] public void ImageShouldApplyInvertFilter() { - string path = CreateOutputDirectory("Invert"); + string path = this.CreateOutputDirectory("Invert"); foreach (TestFile file in Files) { - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) { - image.Invert() - .Save(output); + image.Invert().Save(output); } } } @@ -30,17 +28,15 @@ namespace ImageSharp.Tests [Fact] public void ImageShouldApplyInvertFilterInBox() { - string path = CreateOutputDirectory("Invert"); + string path = this.CreateOutputDirectory("Invert"); foreach (TestFile file in Files) { string filename = file.GetFileName("InBox"); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - image.Invert(new Rectangle(10, 10, image.Width / 2, image.Height / 2)) - .Save(output); + image.Invert(new Rectangle(10, 10, image.Width / 2, image.Height / 2)).Save(output); } } } diff --git a/tests/ImageSharp.Tests/Processors/Filters/KodachromeTest.cs b/tests/ImageSharp.Tests/Processors/Filters/KodachromeTest.cs index adb7cb36d3..40734e02a0 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/KodachromeTest.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/KodachromeTest.cs @@ -14,16 +14,14 @@ namespace ImageSharp.Tests [Fact] public void ImageShouldApplyKodachromeFilter() { - string path = CreateOutputDirectory("Kodachrome"); + string path = this.CreateOutputDirectory("Kodachrome"); foreach (TestFile file in Files) { - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) { - image.Kodachrome() - .Save(output); + image.Kodachrome().Save(output); } } } diff --git a/tests/ImageSharp.Tests/Processors/Filters/LomographTest.cs b/tests/ImageSharp.Tests/Processors/Filters/LomographTest.cs index 79a7aa3ba9..57ca72d39e 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/LomographTest.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/LomographTest.cs @@ -14,16 +14,14 @@ namespace ImageSharp.Tests [Fact] public void ImageShouldApplyLomographFilter() { - string path = CreateOutputDirectory("Lomograph"); + string path = this.CreateOutputDirectory("Lomograph"); foreach (TestFile file in Files) { - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) { - image.Lomograph() - .Save(output); + image.Lomograph().Save(output); } } } @@ -31,17 +29,16 @@ namespace ImageSharp.Tests [Fact] public void ImageShouldApplyLomographFilterInBox() { - string path = CreateOutputDirectory("Lomograph"); + string path = this.CreateOutputDirectory("Lomograph"); foreach (TestFile file in Files) { string filename = file.GetFileName("InBox"); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { image.Lomograph(new Rectangle(image.Width / 4, image.Width / 4, image.Width / 2, image.Height / 2)) - .Save(output); + .Save(output); } } } diff --git a/tests/ImageSharp.Tests/Processors/Filters/OilPaintTest.cs b/tests/ImageSharp.Tests/Processors/Filters/OilPaintTest.cs index c177e9423d..a9b552e216 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/OilPaintTest.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/OilPaintTest.cs @@ -23,19 +23,17 @@ namespace ImageSharp.Tests [MemberData(nameof(OilPaintValues))] public void ImageShouldApplyOilPaintFilter(Tuple value) { - string path = CreateOutputDirectory("OilPaint"); + string path = this.CreateOutputDirectory("OilPaint"); foreach (TestFile file in Files) { string filename = file.GetFileName(value); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { if (image.Width > value.Item2 && image.Height > value.Item2) { - image.OilPaint(value.Item1, value.Item2) - .Save(output); + image.OilPaint(value.Item1, value.Item2).Save(output); } } } @@ -45,18 +43,19 @@ namespace ImageSharp.Tests [MemberData(nameof(OilPaintValues))] public void ImageShouldApplyOilPaintFilterInBox(Tuple value) { - string path = CreateOutputDirectory("OilPaint"); + string path = this.CreateOutputDirectory("OilPaint"); foreach (TestFile file in Files) { string filename = file.GetFileName(value + "-InBox"); - Image image = file.CreateImage(); - - if (image.Width > value.Item2 && image.Height > value.Item2) + using (Image image = file.CreateImage()) { - using (FileStream output = File.OpenWrite($"{path}/{filename}")) + if (image.Width > value.Item2 && image.Height > value.Item2) { - image.OilPaint(value.Item1, value.Item2, new Rectangle(image.Width / 4, image.Width / 4, image.Width / 2, image.Height / 2)).Save(output); + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + image.OilPaint(value.Item1, value.Item2, new Rectangle(image.Width / 4, image.Width / 4, image.Width / 2, image.Height / 2)).Save(output); + } } } } diff --git a/tests/ImageSharp.Tests/Processors/Filters/PadTest.cs b/tests/ImageSharp.Tests/Processors/Filters/PadTest.cs index 5a33aac2bf..f00cdd4f36 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/PadTest.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/PadTest.cs @@ -14,16 +14,14 @@ namespace ImageSharp.Tests [Fact] public void ImageShouldApplyPadSampler() { - string path = CreateOutputDirectory("Pad"); + string path = this.CreateOutputDirectory("Pad"); foreach (TestFile file in Files) { - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) { - image.Pad(image.Width + 50, image.Height + 50) - .Save(output); + image.Pad(image.Width + 50, image.Height + 50).Save(output); } } } diff --git a/tests/ImageSharp.Tests/Processors/Filters/PixelateTest.cs b/tests/ImageSharp.Tests/Processors/Filters/PixelateTest.cs index 38ec406ac5..3a5fbc5567 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/PixelateTest.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/PixelateTest.cs @@ -41,17 +41,15 @@ namespace ImageSharp.Tests [MemberData(nameof(PixelateValues))] public void ImageShouldApplyPixelateFilterInBox(int value) { - string path = CreateOutputDirectory("Pixelate"); + string path = this.CreateOutputDirectory("Pixelate"); foreach (TestFile file in Files) { string filename = file.GetFileName(value + "-InBox"); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - image.Pixelate(value, new Rectangle(10, 10, image.Width / 2, image.Height / 2)) - .Save(output); + image.Pixelate(value, new Rectangle(10, 10, image.Width / 2, image.Height / 2)).Save(output); } } } diff --git a/tests/ImageSharp.Tests/Processors/Filters/PolaroidTest.cs b/tests/ImageSharp.Tests/Processors/Filters/PolaroidTest.cs index dc9d3a150c..040f8b4a24 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/PolaroidTest.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/PolaroidTest.cs @@ -14,16 +14,14 @@ namespace ImageSharp.Tests [Fact] public void ImageShouldApplyPolaroidFilter() { - string path = CreateOutputDirectory("Polaroid"); + string path = this.CreateOutputDirectory("Polaroid"); foreach (TestFile file in Files) { - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) { - image.Polaroid() - .Save(output); + image.Polaroid().Save(output); } } } diff --git a/tests/ImageSharp.Tests/Processors/Filters/ResizeTests.cs b/tests/ImageSharp.Tests/Processors/Filters/ResizeTests.cs index a57d7f6c49..5acbe0f3eb 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/ResizeTests.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/ResizeTests.cs @@ -42,12 +42,10 @@ namespace ImageSharp.Tests foreach (TestFile file in Files) { string filename = file.GetFileName(name); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - image.Resize(image.Width / 2, image.Height / 2, sampler, true) - .Save(output); + image.Resize(image.Width / 2, image.Height / 2, sampler, true).Save(output); } } } @@ -63,12 +61,10 @@ namespace ImageSharp.Tests foreach (TestFile file in Files) { string filename = file.GetFileName(name); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - image.Resize(image.Width / 3, 0, sampler, false) - .Save(output); + image.Resize(image.Width / 3, 0, sampler, false).Save(output); } } } @@ -84,12 +80,10 @@ namespace ImageSharp.Tests foreach (TestFile file in Files) { string filename = file.GetFileName(name); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - image.Resize(0, image.Height / 3, sampler, false) - .Save(output); + image.Resize(0, image.Height / 3, sampler, false).Save(output); } } } @@ -105,18 +99,16 @@ namespace ImageSharp.Tests foreach (TestFile file in Files) { string filename = file.GetFileName(name); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - ResizeOptions options = new ResizeOptions() + ResizeOptions options = new ResizeOptions { Sampler = sampler, Size = new Size(image.Width / 2, image.Height) }; - image.Resize(options) - .Save(output); + image.Resize(options).Save(output); } } } @@ -132,18 +124,16 @@ namespace ImageSharp.Tests foreach (TestFile file in Files) { string filename = file.GetFileName(name); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - ResizeOptions options = new ResizeOptions() + ResizeOptions options = new ResizeOptions { Sampler = sampler, Size = new Size(image.Width, image.Height / 2) }; - image.Resize(options) - .Save(output); + image.Resize(options).Save(output); } } } @@ -159,18 +149,16 @@ namespace ImageSharp.Tests foreach (TestFile file in Files) { string filename = file.GetFileName(name); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - ResizeOptions options = new ResizeOptions() + ResizeOptions options = new ResizeOptions { Size = new Size(image.Width + 200, image.Height), Mode = ResizeMode.Pad }; - image.Resize(options) - .Save(output); + image.Resize(options).Save(output); } } } @@ -186,19 +174,17 @@ namespace ImageSharp.Tests foreach (TestFile file in Files) { string filename = file.GetFileName(name); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - ResizeOptions options = new ResizeOptions() + ResizeOptions options = new ResizeOptions { Sampler = sampler, Size = new Size(image.Width + 200, image.Height + 200), Mode = ResizeMode.BoxPad }; - image.Resize(options) - .Save(output); + image.Resize(options).Save(output); } } } @@ -214,19 +200,17 @@ namespace ImageSharp.Tests foreach (TestFile file in Files) { string filename = file.GetFileName(name); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - ResizeOptions options = new ResizeOptions() + ResizeOptions options = new ResizeOptions { Sampler = sampler, Size = new Size(300, 300), Mode = ResizeMode.Max }; - image.Resize(options) - .Save(output); + image.Resize(options).Save(output); } } } @@ -242,19 +226,17 @@ namespace ImageSharp.Tests foreach (TestFile file in Files) { string filename = file.GetFileName(name); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - ResizeOptions options = new ResizeOptions() + ResizeOptions options = new ResizeOptions { Sampler = sampler, - Size = new Size((int)Math.Round(image.Width * .75F), (int)Math.Round(image.Height * 95F)), + Size = new Size((int)Math.Round(image.Width * .75F), (int)Math.Round(image.Height * .95F)), Mode = ResizeMode.Min }; - image.Resize(options) - .Save(output); + image.Resize(options).Save(output); } } } @@ -270,19 +252,17 @@ namespace ImageSharp.Tests foreach (TestFile file in Files) { string filename = file.GetFileName(name); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - ResizeOptions options = new ResizeOptions() + ResizeOptions options = new ResizeOptions { Sampler = sampler, Size = new Size(image.Width / 2, image.Height), Mode = ResizeMode.Stretch }; - image.Resize(options) - .Save(output); + image.Resize(options).Save(output); } } } diff --git a/tests/ImageSharp.Tests/Processors/Filters/RotateFlipTest.cs b/tests/ImageSharp.Tests/Processors/Filters/RotateFlipTest.cs index 1cccef48a3..e235ed229e 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/RotateFlipTest.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/RotateFlipTest.cs @@ -22,20 +22,18 @@ namespace ImageSharp.Tests }; [Theory] - [MemberData("RotateFlipValues")] + [MemberData(nameof(RotateFlipValues))] public void ImageShouldRotateFlip(RotateType rotateType, FlipType flipType) { - string path = CreateOutputDirectory("RotateFlip"); + string path = this.CreateOutputDirectory("RotateFlip"); foreach (TestFile file in Files) { string filename = file.GetFileName(rotateType + "-" + flipType); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - image.RotateFlip(rotateType, flipType) - .Save(output); + image.RotateFlip(rotateType, flipType).Save(output); } } } diff --git a/tests/ImageSharp.Tests/Processors/Filters/RotateTest.cs b/tests/ImageSharp.Tests/Processors/Filters/RotateTest.cs index 406edda37f..a504fd9891 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/RotateTest.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/RotateTest.cs @@ -28,39 +28,35 @@ namespace ImageSharp.Tests }; [Theory] - [MemberData("RotateFloatValues")] + [MemberData(nameof(RotateFloatValues))] public void ImageShouldApplyRotateSampler(float value) { - string path = CreateOutputDirectory("Rotate"); + string path = this.CreateOutputDirectory("Rotate"); foreach (TestFile file in Files) { string filename = file.GetFileName(value); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - image.Rotate(value) - .Save(output); + image.Rotate(value).Save(output); } } } [Theory] - [MemberData("RotateEnumValues")] + [MemberData(nameof(RotateEnumValues))] public void ImageShouldApplyRotateSampler(RotateType value) { - string path = CreateOutputDirectory("Rotate"); + string path = this.CreateOutputDirectory("Rotate"); foreach (TestFile file in Files) { string filename = file.GetFileName(value); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - image.Rotate(value) - .Save(output); + image.Rotate(value).Save(output); } } } diff --git a/tests/ImageSharp.Tests/Processors/Filters/SaturationTest.cs b/tests/ImageSharp.Tests/Processors/Filters/SaturationTest.cs index 5fe4c3e003..abd596d708 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/SaturationTest.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/SaturationTest.cs @@ -19,7 +19,7 @@ namespace ImageSharp.Tests }; [Theory] - [MemberData("SaturationValues")] + [MemberData(nameof(SaturationValues))] public void ImageShouldApplySaturationFilter(int value) { string path = CreateOutputDirectory("Saturation"); @@ -27,12 +27,10 @@ namespace ImageSharp.Tests foreach (TestFile file in Files) { string filename = file.GetFileName(value); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - image.Saturation(value) - .Save(output); + image.Saturation(value).Save(output); } } } diff --git a/tests/ImageSharp.Tests/Processors/Filters/SepiaTest.cs b/tests/ImageSharp.Tests/Processors/Filters/SepiaTest.cs index b5e4d3105f..fbae10fa55 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/SepiaTest.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/SepiaTest.cs @@ -18,12 +18,10 @@ namespace ImageSharp.Tests foreach (TestFile file in Files) { - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) { - image.Sepia() - .Save(output); + image.Sepia().Save(output); } } } diff --git a/tests/ImageSharp.Tests/Processors/Filters/SkewTest.cs b/tests/ImageSharp.Tests/Processors/Filters/SkewTest.cs index 11db7e6910..231f5dae82 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/SkewTest.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/SkewTest.cs @@ -19,22 +19,20 @@ namespace ImageSharp.Tests }; [Theory] - [MemberData("SkewValues")] + [MemberData(nameof(SkewValues))] public void ImageShouldApplySkewSampler(float x, float y) { - string path = CreateOutputDirectory("Skew"); + string path = this.CreateOutputDirectory("Skew"); - // Matches live example + // Matches live example // http://www.w3schools.com/css/tryit.asp?filename=trycss3_transform_skew foreach (TestFile file in Files) { string filename = file.GetFileName(x + "-" + y); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - image.Skew(x, y) - .Save(output); + image.Skew(x, y).Save(output); } } } diff --git a/tests/ImageSharp.Tests/Processors/Filters/VignetteTest.cs b/tests/ImageSharp.Tests/Processors/Filters/VignetteTest.cs index 3fddad1daa..7f40ef1d21 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/VignetteTest.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/VignetteTest.cs @@ -14,16 +14,14 @@ namespace ImageSharp.Tests [Fact] public void ImageShouldApplyVignetteFilter() { - string path = CreateOutputDirectory("Vignette"); + string path = this.CreateOutputDirectory("Vignette"); foreach (TestFile file in Files) { - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) { - image.Vignette() - .Save(output); + image.Vignette().Save(output); } } } @@ -31,17 +29,15 @@ namespace ImageSharp.Tests [Fact] public void ImageShouldApplyVignetteFilterColor() { - string path = CreateOutputDirectory("Vignette"); + string path = this.CreateOutputDirectory("Vignette"); foreach (TestFile file in Files) { string filename = file.GetFileName("Color"); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - image.Vignette(Color.HotPink) - .Save(output); + image.Vignette(Color.HotPink).Save(output); } } } @@ -49,17 +45,15 @@ namespace ImageSharp.Tests [Fact] public void ImageShouldApplyVignetteFilterRadius() { - string path = CreateOutputDirectory("Vignette"); + string path = this.CreateOutputDirectory("Vignette"); foreach (TestFile file in Files) { string filename = file.GetFileName("Radius"); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - image.Vignette(image.Width / 4, image.Height / 4) - .Save(output); + image.Vignette(image.Width / 4F, image.Height / 4F).Save(output); } } } @@ -67,17 +61,16 @@ namespace ImageSharp.Tests [Fact] public void ImageShouldApplyVignetteFilterInBox() { - string path = CreateOutputDirectory("Vignette"); + string path = this.CreateOutputDirectory("Vignette"); foreach (TestFile file in Files) { string filename = file.GetFileName("InBox"); - Image image = file.CreateImage(); - + using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { image.Vignette(new Rectangle(image.Width / 4, image.Height / 4, image.Width / 2, image.Height / 2)) - .Save(output); + .Save(output); } } } diff --git a/tests/ImageSharp.Tests/Profiles/Exif/ExifProfileTests.cs b/tests/ImageSharp.Tests/Profiles/Exif/ExifProfileTests.cs index 13fe24f50c..1900b58c62 100644 --- a/tests/ImageSharp.Tests/Profiles/Exif/ExifProfileTests.cs +++ b/tests/ImageSharp.Tests/Profiles/Exif/ExifProfileTests.cs @@ -31,6 +31,8 @@ namespace ImageSharp.Tests ExifValue value = image.ExifProfile.Values.FirstOrDefault(val => val.Tag == ExifTag.Copyright); TestValue(value, "Dirk Lemstra"); + + } [Fact] @@ -247,6 +249,7 @@ namespace ImageSharp.Tests using (MemoryStream memStream = new MemoryStream()) { image.SaveAsJpeg(memStream); + image.Dispose(); memStream.Position = 0; return new Image(memStream); diff --git a/tests/ImageSharp.Tests/Profiles/Exif/ExifTagDescriptionAttributeTests.cs b/tests/ImageSharp.Tests/Profiles/Exif/ExifTagDescriptionAttributeTests.cs index 4f77dc11ae..daad49b2c7 100644 --- a/tests/ImageSharp.Tests/Profiles/Exif/ExifTagDescriptionAttributeTests.cs +++ b/tests/ImageSharp.Tests/Profiles/Exif/ExifTagDescriptionAttributeTests.cs @@ -10,7 +10,7 @@ namespace ImageSharp.Tests public class ExifDescriptionAttributeTests { [Fact] - public void Test_ExifTag() + public void TestExifTag() { var exifProfile = new ExifProfile(); diff --git a/tests/ImageSharp.Tests/Profiles/Exif/ExifValueTests.cs b/tests/ImageSharp.Tests/Profiles/Exif/ExifValueTests.cs index 5993d1720c..e777d9e3b2 100644 --- a/tests/ImageSharp.Tests/Profiles/Exif/ExifValueTests.cs +++ b/tests/ImageSharp.Tests/Profiles/Exif/ExifValueTests.cs @@ -12,9 +12,12 @@ namespace ImageSharp.Tests { private static ExifValue GetExifValue() { - Image image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateImage(); + ExifProfile profile; + using (Image image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateImage()) + { + profile = image.ExifProfile; + } - ExifProfile profile = image.ExifProfile; Assert.NotNull(profile); return profile.Values.First(); From 41f96faa7b23fb51463541231f0b0faf5aaa1f1c Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 1 Feb 2017 20:42:31 +1100 Subject: [PATCH 005/142] Remove finalizer --- src/ImageSharp/Image/ImageBase{TColor}.cs | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/src/ImageSharp/Image/ImageBase{TColor}.cs b/src/ImageSharp/Image/ImageBase{TColor}.cs index c58f9412d0..02bab249be 100644 --- a/src/ImageSharp/Image/ImageBase{TColor}.cs +++ b/src/ImageSharp/Image/ImageBase{TColor}.cs @@ -15,14 +15,9 @@ namespace ImageSharp /// /// The pixel format. [DebuggerDisplay("Image: {Width}x{Height}")] - public abstract class ImageBase : IImageBase // IImageBase implements IDisposable + public abstract class ImageBase : IImageBase where TColor : struct, IPackedPixel, IEquatable { - /// - /// The used to pool data. TODO: Choose sensible default size and count - /// - private static readonly ArrayPool ArrayPool = ArrayPool.Create(int.MaxValue, 50); - /// /// The image pixels /// @@ -94,14 +89,6 @@ namespace ImageSharp } } - /// - /// Finalizes an instance of the class. - /// - ~ImageBase() - { - this.Dispose(false); - } - /// public int MaxWidth { get; set; } = int.MaxValue; From 94ca0779e3e0c7842742df9bb64c6a405af08a74 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 1 Feb 2017 23:38:32 +1100 Subject: [PATCH 006/142] Add PixelPool tests --- src/ImageSharp/Image/PixelPool{TColor}.cs | 10 +-- .../ImageSharp.Tests/Image/PixelPoolTests.cs | 74 +++++++++++++++++++ 2 files changed, 75 insertions(+), 9 deletions(-) create mode 100644 tests/ImageSharp.Tests/Image/PixelPoolTests.cs diff --git a/src/ImageSharp/Image/PixelPool{TColor}.cs b/src/ImageSharp/Image/PixelPool{TColor}.cs index 8673499a88..1f33926214 100644 --- a/src/ImageSharp/Image/PixelPool{TColor}.cs +++ b/src/ImageSharp/Image/PixelPool{TColor}.cs @@ -36,15 +36,7 @@ namespace ImageSharp /// The array to return to the buffer pool. public static void ReturnPixels(TColor[] array) { - try - { - ArrayPool.Return(array, true); - } - catch - { - // Do nothing. - // Hacky but it allows us to attempt to return non-pooled arrays and arrays that have already been returned - } + ArrayPool.Return(array, true); } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Image/PixelPoolTests.cs b/tests/ImageSharp.Tests/Image/PixelPoolTests.cs new file mode 100644 index 0000000000..0b762cf7c3 --- /dev/null +++ b/tests/ImageSharp.Tests/Image/PixelPoolTests.cs @@ -0,0 +1,74 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System.Linq; + + using Xunit; + + /// + /// Tests the class. + /// + public class PixelPoolTests + { + [Fact] + public void PixelPoolRentsMinimumSize() + { + Color[] pixels = PixelPool.RentPixels(1024); + + Assert.True(pixels.Length >= 1024); + } + + [Fact] + public void PixelPoolRentsEmptyArray() + { + for (int i = 16; i < 1024; i += 16) + { + Color[] pixels = PixelPool.RentPixels(i); + + Assert.True(pixels.All(p => p == default(Color))); + + PixelPool.ReturnPixels(pixels); + } + + for (int i = 16; i < 1024; i += 16) + { + Color[] pixels = PixelPool.RentPixels(i); + + Assert.True(pixels.All(p => p == default(Color))); + + PixelPool.ReturnPixels(pixels); + } + } + + [Fact] + public void PixelPoolDoesNotThrowWhenReturningNonPooled() + { + Color[] pixels = new Color[1024]; + + PixelPool.ReturnPixels(pixels); + + Assert.True(pixels.Length >= 1024); + } + + [Fact] + public void PixelPoolCleansRentedArray() + { + Color[] pixels = PixelPool.RentPixels(256); + + for (int i = 0; i < pixels.Length; i++) + { + pixels[i] = Color.Azure; + } + + Assert.True(pixels.All(p => p == Color.Azure)); + + PixelPool.ReturnPixels(pixels); + + Assert.True(pixels.All(p => p == default(Color))); + } + } +} \ No newline at end of file From 7940683d37b9259871d527f578b324d3ced9082d Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Wed, 1 Feb 2017 12:54:19 +0000 Subject: [PATCH 007/142] Have PixelAccessor manage PixelPool data. --- .../Convolution/Convolution2DProcessor.cs | 109 +++++++------- .../Convolution/Convolution2PassProcessor.cs | 81 +++++----- .../Convolution/ConvolutionProcessor.cs | 83 ++++++----- .../EdgeDetectorCompassProcessor.cs | 94 ++++++------ .../Effects/OilPaintingProcessor.cs | 127 ++++++++-------- .../Processors/Effects/PixelateProcessor.cs | 67 ++++----- .../Transforms/CompandingResizeProcessor.cs | 40 ++--- .../Processors/Transforms/CropProcessor.cs | 32 ++-- .../Transforms/EntropyCropProcessor.cs | 26 ++-- .../Processors/Transforms/FlipProcessor.cs | 72 ++++----- .../Processors/Transforms/ResizeProcessor.cs | 40 ++--- .../Processors/Transforms/RotateProcessor.cs | 140 +++++++++--------- .../Processors/Transforms/SkewProcessor.cs | 35 ++--- src/ImageSharp.Processing/project.json | 3 +- src/ImageSharp/Image/IImageBase{TColor}.cs | 29 ---- src/ImageSharp/Image/ImageBase{TColor}.cs | 53 ++----- src/ImageSharp/Image/PixelAccessor{TColor}.cs | 138 +++++++++++++---- src/ImageSharp/Quantizers/Quantize.cs | 30 ++-- .../Formats/GeneralFormatTests.cs | 34 +++-- 19 files changed, 632 insertions(+), 601 deletions(-) diff --git a/src/ImageSharp.Processing/Processors/Convolution/Convolution2DProcessor.cs b/src/ImageSharp.Processing/Processors/Convolution/Convolution2DProcessor.cs index f77f1f4394..cdea43e854 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/Convolution2DProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/Convolution2DProcessor.cs @@ -54,73 +54,74 @@ namespace ImageSharp.Processing.Processors int maxY = endY - 1; int maxX = endX - 1; - TColor[] target = PixelPool.RentPixels(source.Width * source.Height); - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock(source.Width, source.Height)) + using (PixelAccessor targetPixels = new PixelAccessor(source.Width, source.Height)) { - Parallel.For( - startY, - endY, - this.ParallelOptions, - y => + using (PixelAccessor sourcePixels = source.Lock()) { - for (int x = startX; x < endX; x++) + Parallel.For( + startY, + endY, + this.ParallelOptions, + y => { - float rX = 0; - float gX = 0; - float bX = 0; - float rY = 0; - float gY = 0; - float bY = 0; - - // Apply each matrix multiplier to the color components for each pixel. - for (int fy = 0; fy < kernelYHeight; fy++) + for (int x = startX; x < endX; x++) { - int fyr = fy - radiusY; - int offsetY = y + fyr; - - offsetY = offsetY.Clamp(0, maxY); - - for (int fx = 0; fx < kernelXWidth; fx++) + float rX = 0; + float gX = 0; + float bX = 0; + float rY = 0; + float gY = 0; + float bY = 0; + + // Apply each matrix multiplier to the color components for each pixel. + for (int fy = 0; fy < kernelYHeight; fy++) { - int fxr = fx - radiusX; - int offsetX = x + fxr; - - offsetX = offsetX.Clamp(0, maxX); + int fyr = fy - radiusY; + int offsetY = y + fyr; - Vector4 currentColor = sourcePixels[offsetX, offsetY].ToVector4(); - float r = currentColor.X; - float g = currentColor.Y; - float b = currentColor.Z; - - if (fy < kernelXHeight) - { - rX += this.KernelX[fy][fx] * r; - gX += this.KernelX[fy][fx] * g; - bX += this.KernelX[fy][fx] * b; - } + offsetY = offsetY.Clamp(0, maxY); - if (fx < kernelYWidth) + for (int fx = 0; fx < kernelXWidth; fx++) { - rY += this.KernelY[fy][fx] * r; - gY += this.KernelY[fy][fx] * g; - bY += this.KernelY[fy][fx] * b; + int fxr = fx - radiusX; + int offsetX = x + fxr; + + offsetX = offsetX.Clamp(0, maxX); + + Vector4 currentColor = sourcePixels[offsetX, offsetY].ToVector4(); + float r = currentColor.X; + float g = currentColor.Y; + float b = currentColor.Z; + + if (fy < kernelXHeight) + { + rX += this.KernelX[fy][fx] * r; + gX += this.KernelX[fy][fx] * g; + bX += this.KernelX[fy][fx] * b; + } + + if (fx < kernelYWidth) + { + rY += this.KernelY[fy][fx] * r; + gY += this.KernelY[fy][fx] * g; + bY += this.KernelY[fy][fx] * b; + } } } - } - float red = (float)Math.Sqrt((rX * rX) + (rY * rY)); - float green = (float)Math.Sqrt((gX * gX) + (gY * gY)); - float blue = (float)Math.Sqrt((bX * bX) + (bY * bY)); + float red = (float)Math.Sqrt((rX * rX) + (rY * rY)); + float green = (float)Math.Sqrt((gX * gX) + (gY * gY)); + float blue = (float)Math.Sqrt((bX * bX) + (bY * bY)); - TColor packed = default(TColor); - packed.PackFromVector4(new Vector4(red, green, blue, sourcePixels[x, y].ToVector4().W)); - targetPixels[x, y] = packed; - } - }); - } + TColor packed = default(TColor); + packed.PackFromVector4(new Vector4(red, green, blue, sourcePixels[x, y].ToVector4().W)); + targetPixels[x, y] = packed; + } + }); + } - source.SetPixels(source.Width, source.Height, target); + source.SwapPixelsBuffers(targetPixels); + } } } } \ No newline at end of file diff --git a/src/ImageSharp.Processing/Processors/Convolution/Convolution2PassProcessor.cs b/src/ImageSharp.Processing/Processors/Convolution/Convolution2PassProcessor.cs index ca343b8680..71b8062617 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/Convolution2PassProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/Convolution2PassProcessor.cs @@ -45,19 +45,16 @@ namespace ImageSharp.Processing.Processors int width = source.Width; int height = source.Height; - TColor[] target = PixelPool.RentPixels(width * height); - TColor[] firstPass = PixelPool.RentPixels(width * height); - - try + using (PixelAccessor targetPixels = new PixelAccessor(width, height)) { - this.ApplyConvolution(width, height, firstPass, source.Pixels, sourceRectangle, kernelX); - this.ApplyConvolution(width, height, target, firstPass, sourceRectangle, kernelY); + using (PixelAccessor firstPassPixels = new PixelAccessor(width, height)) + using (PixelAccessor sourcePixels = source.Lock()) + { + this.ApplyConvolution(width, height, firstPassPixels, sourcePixels, sourceRectangle, kernelX); + this.ApplyConvolution(width, height, targetPixels, firstPassPixels, sourceRectangle, kernelY); + } - source.SetPixels(width, height, target); - } - finally - { - PixelPool.ReturnPixels(firstPass); + source.SwapPixelsBuffers(targetPixels); } } @@ -67,13 +64,13 @@ namespace ImageSharp.Processing.Processors /// /// The image width. /// The image height. - /// The target pixels to apply the process to. - /// The source pixels. Cannot be null. + /// The target pixels to apply the process to. + /// The source pixels. Cannot be null. /// /// The structure that specifies the portion of the image object to draw. /// /// The kernel operator. - private void ApplyConvolution(int width, int height, TColor[] target, TColor[] source, Rectangle sourceRectangle, float[][] kernel) + private void ApplyConvolution(int width, int height, PixelAccessor targetPixels, PixelAccessor sourcePixels, Rectangle sourceRectangle, float[][] kernel) { int kernelHeight = kernel.Length; int kernelWidth = kernel[0].Length; @@ -87,45 +84,41 @@ namespace ImageSharp.Processing.Processors int maxY = endY - 1; int maxX = endX - 1; - using (PixelAccessor sourcePixels = source.Lock(width, height)) - using (PixelAccessor targetPixels = target.Lock(width, height)) + Parallel.For( + startY, + endY, + this.ParallelOptions, + y => { - Parallel.For( - startY, - endY, - this.ParallelOptions, - y => + for (int x = startX; x < endX; x++) { - for (int x = startX; x < endX; x++) - { - Vector4 destination = default(Vector4); + Vector4 destination = default(Vector4); - // Apply each matrix multiplier to the color components for each pixel. - for (int fy = 0; fy < kernelHeight; fy++) - { - int fyr = fy - radiusY; - int offsetY = y + fyr; + // Apply each matrix multiplier to the color components for each pixel. + for (int fy = 0; fy < kernelHeight; fy++) + { + int fyr = fy - radiusY; + int offsetY = y + fyr; - offsetY = offsetY.Clamp(0, maxY); + offsetY = offsetY.Clamp(0, maxY); - for (int fx = 0; fx < kernelWidth; fx++) - { - int fxr = fx - radiusX; - int offsetX = x + fxr; + for (int fx = 0; fx < kernelWidth; fx++) + { + int fxr = fx - radiusX; + int offsetX = x + fxr; - offsetX = offsetX.Clamp(0, maxX); + offsetX = offsetX.Clamp(0, maxX); - Vector4 currentColor = sourcePixels[offsetX, offsetY].ToVector4(); - destination += kernel[fy][fx] * currentColor; - } + Vector4 currentColor = sourcePixels[offsetX, offsetY].ToVector4(); + destination += kernel[fy][fx] * currentColor; } - - TColor packed = default(TColor); - packed.PackFromVector4(destination); - targetPixels[x, y] = packed; } - }); - } + + TColor packed = default(TColor); + packed.PackFromVector4(destination); + targetPixels[x, y] = packed; + } + }); } } } \ No newline at end of file diff --git a/src/ImageSharp.Processing/Processors/Convolution/ConvolutionProcessor.cs b/src/ImageSharp.Processing/Processors/Convolution/ConvolutionProcessor.cs index 17d7e2918f..aa49401926 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/ConvolutionProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/ConvolutionProcessor.cs @@ -44,60 +44,61 @@ namespace ImageSharp.Processing.Processors int maxY = endY - 1; int maxX = endX - 1; - TColor[] target = new TColor[source.Width * source.Height]; - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock(source.Width, source.Height)) + using (PixelAccessor targetPixels = new PixelAccessor(source.Width, source.Height)) { - Parallel.For( - startY, - endY, - this.ParallelOptions, - y => + using (PixelAccessor sourcePixels = source.Lock()) { - for (int x = startX; x < endX; x++) + Parallel.For( + startY, + endY, + this.ParallelOptions, + y => { - float rX = 0; - float gX = 0; - float bX = 0; - - // Apply each matrix multiplier to the color components for each pixel. - for (int fy = 0; fy < kernelLength; fy++) + for (int x = startX; x < endX; x++) { - int fyr = fy - radius; - int offsetY = y + fyr; - - offsetY = offsetY.Clamp(0, maxY); + float rX = 0; + float gX = 0; + float bX = 0; - for (int fx = 0; fx < kernelLength; fx++) + // Apply each matrix multiplier to the color components for each pixel. + for (int fy = 0; fy < kernelLength; fy++) { - int fxr = fx - radius; - int offsetX = x + fxr; + int fyr = fy - radius; + int offsetY = y + fyr; + + offsetY = offsetY.Clamp(0, maxY); + + for (int fx = 0; fx < kernelLength; fx++) + { + int fxr = fx - radius; + int offsetX = x + fxr; - offsetX = offsetX.Clamp(0, maxX); + offsetX = offsetX.Clamp(0, maxX); - Vector4 currentColor = sourcePixels[offsetX, offsetY].ToVector4(); - float r = currentColor.X; - float g = currentColor.Y; - float b = currentColor.Z; + Vector4 currentColor = sourcePixels[offsetX, offsetY].ToVector4(); + float r = currentColor.X; + float g = currentColor.Y; + float b = currentColor.Z; - rX += kernelX[fy][fx] * r; - gX += kernelX[fy][fx] * g; - bX += kernelX[fy][fx] * b; + rX += kernelX[fy][fx] * r; + gX += kernelX[fy][fx] * g; + bX += kernelX[fy][fx] * b; + } } - } - float red = rX; - float green = gX; - float blue = bX; + float red = rX; + float green = gX; + float blue = bX; - TColor packed = default(TColor); - packed.PackFromVector4(new Vector4(red, green, blue, sourcePixels[x, y].ToVector4().W)); - targetPixels[x, y] = packed; - } - }); - } + TColor packed = default(TColor); + packed.PackFromVector4(new Vector4(red, green, blue, sourcePixels[x, y].ToVector4().W)); + targetPixels[x, y] = packed; + } + }); + } - source.SetPixels(source.Width, source.Height, target); + source.SwapPixelsBuffers(targetPixels); + } } } } \ No newline at end of file diff --git a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/EdgeDetectorCompassProcessor.cs b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/EdgeDetectorCompassProcessor.cs index 5a1487761f..1a88dbe345 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/EdgeDetectorCompassProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/EdgeDetectorCompassProcessor.cs @@ -75,64 +75,62 @@ namespace ImageSharp.Processing.Processors int minY = Math.Max(0, startY); int maxY = Math.Min(source.Height, endY); - // First run. - ImageBase target = new Image(source.Width, source.Height); - target.ClonePixels(source.Width, source.Height, source.Pixels); - new ConvolutionProcessor(kernels[0]).Apply(target, sourceRectangle); - - if (kernels.Length == 1) + // we need a clean copy for each pass to start from + using (ImageBase cleanCopy = new Image(source)) { - return; - } + new ConvolutionProcessor(kernels[0]).Apply(source, sourceRectangle); - int shiftY = startY; - int shiftX = startX; - - // Reset offset if necessary. - if (minX > 0) - { - shiftX = 0; - } + if (kernels.Length == 1) + { + return; + } - if (minY > 0) - { - shiftY = 0; - } + int shiftY = startY; + int shiftX = startX; - // Additional runs. - // ReSharper disable once ForCanBeConvertedToForeach - for (int i = 1; i < kernels.Length; i++) - { - // Create a clone for each pass and copy the offset pixels across. - ImageBase pass = new Image(source.Width, source.Height); - pass.ClonePixels(source.Width, source.Height, source.Pixels); + // Reset offset if necessary. + if (minX > 0) + { + shiftX = 0; + } - new ConvolutionProcessor(kernels[i]).Apply(pass, sourceRectangle); + if (minY > 0) + { + shiftY = 0; + } - using (PixelAccessor passPixels = pass.Lock()) - using (PixelAccessor targetPixels = target.Lock()) + // Additional runs. + // ReSharper disable once ForCanBeConvertedToForeach + for (int i = 1; i < kernels.Length; i++) { - Parallel.For( - minY, - maxY, - this.ParallelOptions, - y => + using (ImageBase pass = new Image(cleanCopy)) + { + new ConvolutionProcessor(kernels[i]).Apply(pass, sourceRectangle); + + using (PixelAccessor passPixels = pass.Lock()) + using (PixelAccessor targetPixels = source.Lock()) { - int offsetY = y - shiftY; - for (int x = minX; x < maxX; x++) - { - int offsetX = x - shiftX; - - // Grab the max components of the two pixels - TColor packed = default(TColor); - packed.PackFromVector4(Vector4.Max(passPixels[offsetX, offsetY].ToVector4(), targetPixels[offsetX, offsetY].ToVector4())); - targetPixels[offsetX, offsetY] = packed; - } - }); + Parallel.For( + minY, + maxY, + this.ParallelOptions, + y => + { + int offsetY = y - shiftY; + for (int x = minX; x < maxX; x++) + { + int offsetX = x - shiftX; + + // Grab the max components of the two pixels + TColor packed = default(TColor); + packed.PackFromVector4(Vector4.Max(passPixels[offsetX, offsetY].ToVector4(), targetPixels[offsetX, offsetY].ToVector4())); + targetPixels[offsetX, offsetY] = packed; + } + }); + } + } } } - - source.SetPixels(source.Width, source.Height, target.Pixels); } /// diff --git a/src/ImageSharp.Processing/Processors/Effects/OilPaintingProcessor.cs b/src/ImageSharp.Processing/Processors/Effects/OilPaintingProcessor.cs index 8d23402561..5c16af2f7f 100644 --- a/src/ImageSharp.Processing/Processors/Effects/OilPaintingProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Effects/OilPaintingProcessor.cs @@ -67,91 +67,92 @@ namespace ImageSharp.Processing.Processors startX = 0; } - TColor[] target = PixelPool.RentPixels(source.Width * source.Height); - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock(source.Width, source.Height)) + using (PixelAccessor targetPixels = new PixelAccessor(source.Width, source.Height)) { - Parallel.For( - minY, - maxY, - this.ParallelOptions, - y => - { - for (int x = startX; x < endX; x++) + using (PixelAccessor sourcePixels = source.Lock()) + { + Parallel.For( + minY, + maxY, + this.ParallelOptions, + y => { - int maxIntensity = 0; - int maxIndex = 0; - - int[] intensityBin = new int[levels]; - float[] redBin = new float[levels]; - float[] blueBin = new float[levels]; - float[] greenBin = new float[levels]; - - for (int fy = 0; fy <= radius; fy++) + for (int x = startX; x < endX; x++) { - int fyr = fy - radius; - int offsetY = y + fyr; - - // Skip the current row - if (offsetY < minY) - { - continue; - } + int maxIntensity = 0; + int maxIndex = 0; - // Outwith the current bounds so break. - if (offsetY >= maxY) - { - break; - } + int[] intensityBin = new int[levels]; + float[] redBin = new float[levels]; + float[] blueBin = new float[levels]; + float[] greenBin = new float[levels]; - for (int fx = 0; fx <= radius; fx++) + for (int fy = 0; fy <= radius; fy++) { - int fxr = fx - radius; - int offsetX = x + fxr; + int fyr = fy - radius; + int offsetY = y + fyr; - // Skip the column - if (offsetX < 0) + // Skip the current row + if (offsetY < minY) { continue; } - if (offsetX < maxX) + // Outwith the current bounds so break. + if (offsetY >= maxY) { - // ReSharper disable once AccessToDisposedClosure - Vector4 color = sourcePixels[offsetX, offsetY].ToVector4(); - - float sourceRed = color.X; - float sourceBlue = color.Z; - float sourceGreen = color.Y; + break; + } - int currentIntensity = (int)Math.Round((sourceBlue + sourceGreen + sourceRed) / 3.0 * (levels - 1)); + for (int fx = 0; fx <= radius; fx++) + { + int fxr = fx - radius; + int offsetX = x + fxr; - intensityBin[currentIntensity] += 1; - blueBin[currentIntensity] += sourceBlue; - greenBin[currentIntensity] += sourceGreen; - redBin[currentIntensity] += sourceRed; + // Skip the column + if (offsetX < 0) + { + continue; + } - if (intensityBin[currentIntensity] > maxIntensity) + if (offsetX < maxX) { - maxIntensity = intensityBin[currentIntensity]; - maxIndex = currentIntensity; + // ReSharper disable once AccessToDisposedClosure + Vector4 color = sourcePixels[offsetX, offsetY].ToVector4(); + + float sourceRed = color.X; + float sourceBlue = color.Z; + float sourceGreen = color.Y; + + int currentIntensity = (int)Math.Round((sourceBlue + sourceGreen + sourceRed) / 3.0 * (levels - 1)); + + intensityBin[currentIntensity] += 1; + blueBin[currentIntensity] += sourceBlue; + greenBin[currentIntensity] += sourceGreen; + redBin[currentIntensity] += sourceRed; + + if (intensityBin[currentIntensity] > maxIntensity) + { + maxIntensity = intensityBin[currentIntensity]; + maxIndex = currentIntensity; + } } } - } - float red = Math.Abs(redBin[maxIndex] / maxIntensity); - float green = Math.Abs(greenBin[maxIndex] / maxIntensity); - float blue = Math.Abs(blueBin[maxIndex] / maxIntensity); + float red = Math.Abs(redBin[maxIndex] / maxIntensity); + float green = Math.Abs(greenBin[maxIndex] / maxIntensity); + float blue = Math.Abs(blueBin[maxIndex] / maxIntensity); - TColor packed = default(TColor); - packed.PackFromVector4(new Vector4(red, green, blue, sourcePixels[x, y].ToVector4().W)); - targetPixels[x, y] = packed; + TColor packed = default(TColor); + packed.PackFromVector4(new Vector4(red, green, blue, sourcePixels[x, y].ToVector4().W)); + targetPixels[x, y] = packed; + } } - } - }); - } + }); + } - source.SetPixels(source.Width, source.Height, target); + source.SwapPixelsBuffers(targetPixels); + } } } } \ No newline at end of file diff --git a/src/ImageSharp.Processing/Processors/Effects/PixelateProcessor.cs b/src/ImageSharp.Processing/Processors/Effects/PixelateProcessor.cs index d44858061d..c197ce356f 100644 --- a/src/ImageSharp.Processing/Processors/Effects/PixelateProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Effects/PixelateProcessor.cs @@ -63,51 +63,52 @@ namespace ImageSharp.Processing.Processors // Get the range on the y-plane to choose from. IEnumerable range = EnumerableExtensions.SteppedRange(minY, i => i < maxY, size); - TColor[] target = PixelPool.RentPixels(source.Width * source.Height); - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock(source.Width, source.Height)) + using (PixelAccessor targetPixels = new PixelAccessor(source.Width, source.Height)) { - Parallel.ForEach( - range, - this.ParallelOptions, - y => - { - int offsetY = y - startY; - int offsetPy = offset; - - for (int x = minX; x < maxX; x += size) + using (PixelAccessor sourcePixels = source.Lock()) + { + Parallel.ForEach( + range, + this.ParallelOptions, + y => { - int offsetX = x - startX; - int offsetPx = offset; + int offsetY = y - startY; + int offsetPy = offset; - // Make sure that the offset is within the boundary of the image. - while (offsetY + offsetPy >= maxY) + for (int x = minX; x < maxX; x += size) { - offsetPy--; - } + int offsetX = x - startX; + int offsetPx = offset; - while (x + offsetPx >= maxX) - { - offsetPx--; - } + // Make sure that the offset is within the boundary of the image. + while (offsetY + offsetPy >= maxY) + { + offsetPy--; + } - // Get the pixel color in the centre of the soon to be pixelated area. - // ReSharper disable AccessToDisposedClosure - TColor pixel = sourcePixels[offsetX + offsetPx, offsetY + offsetPy]; + while (x + offsetPx >= maxX) + { + offsetPx--; + } - // For each pixel in the pixelate size, set it to the centre color. - for (int l = offsetY; l < offsetY + size && l < maxY; l++) - { - for (int k = offsetX; k < offsetX + size && k < maxX; k++) + // Get the pixel color in the centre of the soon to be pixelated area. + // ReSharper disable AccessToDisposedClosure + TColor pixel = sourcePixels[offsetX + offsetPx, offsetY + offsetPy]; + + // For each pixel in the pixelate size, set it to the centre color. + for (int l = offsetY; l < offsetY + size && l < maxY; l++) { - targetPixels[k, l] = pixel; + for (int k = offsetX; k < offsetX + size && k < maxX; k++) + { + targetPixels[k, l] = pixel; + } } } - } - }); + }); - source.SetPixels(source.Width, source.Height, target); + source.SwapPixelsBuffers(targetPixels); + } } } } diff --git a/src/ImageSharp.Processing/Processors/Transforms/CompandingResizeProcessor.cs b/src/ImageSharp.Processing/Processors/Transforms/CompandingResizeProcessor.cs index d9e4f6675a..ac8a52321e 100644 --- a/src/ImageSharp.Processing/Processors/Transforms/CompandingResizeProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Transforms/CompandingResizeProcessor.cs @@ -66,19 +66,15 @@ namespace ImageSharp.Processing.Processors int minY = Math.Max(0, startY); int maxY = Math.Min(height, endY); - TColor[] firstPass = null; - - try + if (this.Sampler is NearestNeighborResampler) { - TColor[] target = PixelPool.RentPixels(width * height); - if (this.Sampler is NearestNeighborResampler) - { - // Scaling factors - float widthFactor = sourceRectangle.Width / (float)this.ResizeRectangle.Width; - float heightFactor = sourceRectangle.Height / (float)this.ResizeRectangle.Height; + // Scaling factors + float widthFactor = sourceRectangle.Width / (float)this.ResizeRectangle.Width; + float heightFactor = sourceRectangle.Height / (float)this.ResizeRectangle.Height; + using (PixelAccessor targetPixels = new PixelAccessor(width, height)) + { using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock(width, height)) { Parallel.For( minY, @@ -98,18 +94,19 @@ namespace ImageSharp.Processing.Processors } // Break out now. - source.SetPixels(width, height, target); + source.SwapPixelsBuffers(targetPixels); return; } + } - // Interpolate the image using the calculated weights. - // A 2-pass 1D algorithm appears to be faster than splitting a 1-pass 2D algorithm - // First process the columns. Since we are not using multiple threads startY and endY - // are the upper and lower bounds of the source rectangle. - firstPass = PixelPool.RentPixels(width * source.Height); + // Interpolate the image using the calculated weights. + // A 2-pass 1D algorithm appears to be faster than splitting a 1-pass 2D algorithm + // First process the columns. Since we are not using multiple threads startY and endY + // are the upper and lower bounds of the source rectangle. + using (PixelAccessor targetPixels = new PixelAccessor(width, height)) + { using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor firstPassPixels = firstPass.Lock(width, source.Height)) - using (PixelAccessor targetPixels = target.Lock(width, height)) + using (PixelAccessor firstPassPixels = new PixelAccessor(width, source.Height)) { Parallel.For( 0, @@ -165,12 +162,7 @@ namespace ImageSharp.Processing.Processors }); } - source.SetPixels(width, height, target); - } - finally - { - // We don't return target or source pixels as they are handled in the image itself. - PixelPool.ReturnPixels(firstPass); + source.SwapPixelsBuffers(targetPixels); } } } diff --git a/src/ImageSharp.Processing/Processors/Transforms/CropProcessor.cs b/src/ImageSharp.Processing/Processors/Transforms/CropProcessor.cs index 31bd08090d..bdfbc496c9 100644 --- a/src/ImageSharp.Processing/Processors/Transforms/CropProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Transforms/CropProcessor.cs @@ -42,25 +42,25 @@ namespace ImageSharp.Processing.Processors int minX = Math.Max(this.CropRectangle.X, sourceRectangle.X); int maxX = Math.Min(this.CropRectangle.Right, sourceRectangle.Right); - TColor[] target = PixelPool.RentPixels(this.CropRectangle.Width * this.CropRectangle.Height); - - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock(this.CropRectangle.Width, this.CropRectangle.Height)) + using (PixelAccessor targetPixels = new PixelAccessor(this.CropRectangle.Width, this.CropRectangle.Height)) { - Parallel.For( - minY, - maxY, - this.ParallelOptions, - y => - { - for (int x = minX; x < maxX; x++) + using (PixelAccessor sourcePixels = source.Lock()) + { + Parallel.For( + minY, + maxY, + this.ParallelOptions, + y => { - targetPixels[x - minX, y - minY] = sourcePixels[x, y]; - } - }); - } + for (int x = minX; x < maxX; x++) + { + targetPixels[x - minX, y - minY] = sourcePixels[x, y]; + } + }); + } - source.SetPixels(this.CropRectangle.Width, this.CropRectangle.Height, target); + source.SwapPixelsBuffers(targetPixels); + } } } } \ No newline at end of file diff --git a/src/ImageSharp.Processing/Processors/Transforms/EntropyCropProcessor.cs b/src/ImageSharp.Processing/Processors/Transforms/EntropyCropProcessor.cs index e0c6e9b92a..98297eed99 100644 --- a/src/ImageSharp.Processing/Processors/Transforms/EntropyCropProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Transforms/EntropyCropProcessor.cs @@ -36,24 +36,24 @@ namespace ImageSharp.Processing.Processors /// protected override void OnApply(ImageBase source, Rectangle sourceRectangle) { - ImageBase temp = new Image(source.Width, source.Height); - temp.ClonePixels(source.Width, source.Height, source.Pixels); + using (ImageBase temp = new Image(source)) + { + // Detect the edges. + new SobelProcessor().Apply(temp, sourceRectangle); - // Detect the edges. - new SobelProcessor().Apply(temp, sourceRectangle); + // Apply threshold binarization filter. + new BinaryThresholdProcessor(this.Value).Apply(temp, sourceRectangle); - // Apply threshold binarization filter. - new BinaryThresholdProcessor(this.Value).Apply(temp, sourceRectangle); + // Search for the first white pixels + Rectangle rectangle = ImageMaths.GetFilteredBoundingRectangle(temp, 0); - // Search for the first white pixels - Rectangle rectangle = ImageMaths.GetFilteredBoundingRectangle(temp, 0); + if (rectangle == sourceRectangle) + { + return; + } - if (rectangle == sourceRectangle) - { - return; + new CropProcessor(rectangle).Apply(source, sourceRectangle); } - - new CropProcessor(rectangle).Apply(source, sourceRectangle); } } } \ No newline at end of file diff --git a/src/ImageSharp.Processing/Processors/Transforms/FlipProcessor.cs b/src/ImageSharp.Processing/Processors/Transforms/FlipProcessor.cs index 374d54fa28..ad375ce0fb 100644 --- a/src/ImageSharp.Processing/Processors/Transforms/FlipProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Transforms/FlipProcessor.cs @@ -55,27 +55,27 @@ namespace ImageSharp.Processing.Processors int height = source.Height; int halfHeight = (int)Math.Ceiling(source.Height * .5F); - TColor[] target = PixelPool.RentPixels(width * height); - - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock(width, height)) + using (PixelAccessor targetPixels = new PixelAccessor(width, height)) { - Parallel.For( - 0, - halfHeight, - this.ParallelOptions, - y => - { - for (int x = 0; x < width; x++) + using (PixelAccessor sourcePixels = source.Lock()) + { + Parallel.For( + 0, + halfHeight, + this.ParallelOptions, + y => { - int newY = height - y - 1; - targetPixels[x, y] = sourcePixels[x, newY]; - targetPixels[x, newY] = sourcePixels[x, y]; - } - }); - } + for (int x = 0; x < width; x++) + { + int newY = height - y - 1; + targetPixels[x, y] = sourcePixels[x, newY]; + targetPixels[x, newY] = sourcePixels[x, y]; + } + }); + } - source.SetPixels(width, height, target); + source.SwapPixelsBuffers(targetPixels); + } } /// @@ -89,27 +89,27 @@ namespace ImageSharp.Processing.Processors int height = source.Height; int halfWidth = (int)Math.Ceiling(width * .5F); - TColor[] target = PixelPool.RentPixels(width * height); - - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock(width, height)) + using (PixelAccessor targetPixels = new PixelAccessor(width, height)) { - Parallel.For( - 0, - height, - this.ParallelOptions, - y => - { - for (int x = 0; x < halfWidth; x++) + using (PixelAccessor sourcePixels = source.Lock()) + { + Parallel.For( + 0, + height, + this.ParallelOptions, + y => { - int newX = width - x - 1; - targetPixels[x, y] = sourcePixels[newX, y]; - targetPixels[newX, y] = sourcePixels[x, y]; - } - }); - } + for (int x = 0; x < halfWidth; x++) + { + int newX = width - x - 1; + targetPixels[x, y] = sourcePixels[newX, y]; + targetPixels[newX, y] = sourcePixels[x, y]; + } + }); + } - source.SetPixels(width, height, target); + source.SwapPixelsBuffers(targetPixels); + } } } } \ No newline at end of file diff --git a/src/ImageSharp.Processing/Processors/Transforms/ResizeProcessor.cs b/src/ImageSharp.Processing/Processors/Transforms/ResizeProcessor.cs index 108391713d..f5d6308089 100644 --- a/src/ImageSharp.Processing/Processors/Transforms/ResizeProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Transforms/ResizeProcessor.cs @@ -65,19 +65,15 @@ namespace ImageSharp.Processing.Processors int minY = Math.Max(0, startY); int maxY = Math.Min(height, endY); - TColor[] firstPass = null; - - try + if (this.Sampler is NearestNeighborResampler) { - TColor[] target = PixelPool.RentPixels(width * height); - if (this.Sampler is NearestNeighborResampler) - { - // Scaling factors - float widthFactor = sourceRectangle.Width / (float)this.ResizeRectangle.Width; - float heightFactor = sourceRectangle.Height / (float)this.ResizeRectangle.Height; + // Scaling factors + float widthFactor = sourceRectangle.Width / (float)this.ResizeRectangle.Width; + float heightFactor = sourceRectangle.Height / (float)this.ResizeRectangle.Height; + using (PixelAccessor targetPixels = new PixelAccessor(width, height)) + { using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock(width, height)) { Parallel.For( minY, @@ -97,18 +93,19 @@ namespace ImageSharp.Processing.Processors } // Break out now. - source.SetPixels(width, height, target); + source.SwapPixelsBuffers(targetPixels); return; } + } - // Interpolate the image using the calculated weights. - // A 2-pass 1D algorithm appears to be faster than splitting a 1-pass 2D algorithm - // First process the columns. Since we are not using multiple threads startY and endY - // are the upper and lower bounds of the source rectangle. - firstPass = PixelPool.RentPixels(width * source.Height); + // Interpolate the image using the calculated weights. + // A 2-pass 1D algorithm appears to be faster than splitting a 1-pass 2D algorithm + // First process the columns. Since we are not using multiple threads startY and endY + // are the upper and lower bounds of the source rectangle. + using (PixelAccessor targetPixels = new PixelAccessor(width, height)) + { using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor firstPassPixels = firstPass.Lock(width, source.Height)) - using (PixelAccessor targetPixels = target.Lock(width, height)) + using (PixelAccessor firstPassPixels = new PixelAccessor(width, source.Height)) { Parallel.For( 0, @@ -164,12 +161,7 @@ namespace ImageSharp.Processing.Processors }); } - source.SetPixels(width, height, target); - } - finally - { - // We don't return target or source pixels as they are handled in the image itself. - PixelPool.ReturnPixels(firstPass); + source.SwapPixelsBuffers(targetPixels); } } } diff --git a/src/ImageSharp.Processing/Processors/Transforms/RotateProcessor.cs b/src/ImageSharp.Processing/Processors/Transforms/RotateProcessor.cs index 313542adc9..a5a762b911 100644 --- a/src/ImageSharp.Processing/Processors/Transforms/RotateProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Transforms/RotateProcessor.cs @@ -42,29 +42,30 @@ namespace ImageSharp.Processing.Processors int height = this.CanvasRectangle.Height; int width = this.CanvasRectangle.Width; Matrix3x2 matrix = this.GetCenteredMatrix(source, this.processMatrix); - TColor[] target = PixelPool.RentPixels(width * height); - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock(width, height)) + using (PixelAccessor targetPixels = new PixelAccessor(width, height)) { - Parallel.For( - 0, - height, - this.ParallelOptions, - y => - { - for (int x = 0; x < width; x++) + using (PixelAccessor sourcePixels = source.Lock()) + { + Parallel.For( + 0, + height, + this.ParallelOptions, + y => { - Point transformedPoint = Point.Rotate(new Point(x, y), matrix); - if (source.Bounds.Contains(transformedPoint.X, transformedPoint.Y)) + for (int x = 0; x < width; x++) { - targetPixels[x, y] = sourcePixels[transformedPoint.X, transformedPoint.Y]; + Point transformedPoint = Point.Rotate(new Point(x, y), matrix); + if (source.Bounds.Contains(transformedPoint.X, transformedPoint.Y)) + { + targetPixels[x, y] = sourcePixels[transformedPoint.X, transformedPoint.Y]; + } } - } - }); - } + }); + } - source.SetPixels(width, height, target); + source.SwapPixelsBuffers(targetPixels); + } } /// @@ -124,28 +125,29 @@ namespace ImageSharp.Processing.Processors { int width = source.Width; int height = source.Height; - TColor[] target = PixelPool.RentPixels(width * height); - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock(height, width)) + using (PixelAccessor targetPixels = new PixelAccessor(height, width)) { - Parallel.For( - 0, - height, - this.ParallelOptions, - y => - { - for (int x = 0; x < width; x++) + using (PixelAccessor sourcePixels = source.Lock()) + { + Parallel.For( + 0, + height, + this.ParallelOptions, + y => { - int newX = height - y - 1; - newX = height - newX - 1; - int newY = width - x - 1; - targetPixels[newX, newY] = sourcePixels[x, y]; - } - }); - } + for (int x = 0; x < width; x++) + { + int newX = height - y - 1; + newX = height - newX - 1; + int newY = width - x - 1; + targetPixels[newX, newY] = sourcePixels[x, y]; + } + }); + } - source.SetPixels(height, width, target); + source.SwapPixelsBuffers(targetPixels); + } } /// @@ -156,27 +158,28 @@ namespace ImageSharp.Processing.Processors { int width = source.Width; int height = source.Height; - TColor[] target = PixelPool.RentPixels(width * height); - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock(width, height)) + using (PixelAccessor targetPixels = new PixelAccessor(width, height)) { - Parallel.For( - 0, - height, - this.ParallelOptions, - y => - { - for (int x = 0; x < width; x++) + using (PixelAccessor sourcePixels = source.Lock()) + { + Parallel.For( + 0, + height, + this.ParallelOptions, + y => { - int newX = width - x - 1; - int newY = height - y - 1; - targetPixels[newX, newY] = sourcePixels[x, y]; - } - }); - } + for (int x = 0; x < width; x++) + { + int newX = width - x - 1; + int newY = height - y - 1; + targetPixels[newX, newY] = sourcePixels[x, y]; + } + }); + } - source.SetPixels(width, height, target); + source.SwapPixelsBuffers(targetPixels); + } } /// @@ -187,26 +190,27 @@ namespace ImageSharp.Processing.Processors { int width = source.Width; int height = source.Height; - TColor[] target = PixelPool.RentPixels(width * height); - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock(height, width)) + using (PixelAccessor targetPixels = new PixelAccessor(height, width)) { - Parallel.For( - 0, - height, - this.ParallelOptions, - y => - { - for (int x = 0; x < width; x++) + using (PixelAccessor sourcePixels = source.Lock()) + { + Parallel.For( + 0, + height, + this.ParallelOptions, + y => { - int newX = height - y - 1; - targetPixels[newX, x] = sourcePixels[x, y]; - } - }); - } + for (int x = 0; x < width; x++) + { + int newX = height - y - 1; + targetPixels[newX, x] = sourcePixels[x, y]; + } + }); + } - source.SetPixels(height, width, target); + source.SwapPixelsBuffers(targetPixels); + } } } } \ No newline at end of file diff --git a/src/ImageSharp.Processing/Processors/Transforms/SkewProcessor.cs b/src/ImageSharp.Processing/Processors/Transforms/SkewProcessor.cs index d2d2a129df..4daa46491a 100644 --- a/src/ImageSharp.Processing/Processors/Transforms/SkewProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Transforms/SkewProcessor.cs @@ -42,29 +42,30 @@ namespace ImageSharp.Processing.Processors int height = this.CanvasRectangle.Height; int width = this.CanvasRectangle.Width; Matrix3x2 matrix = this.GetCenteredMatrix(source, this.processMatrix); - TColor[] target = PixelPool.RentPixels(width * height); - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock(width, height)) + using (PixelAccessor targetPixels = new PixelAccessor(width, height)) { - Parallel.For( - 0, - height, - this.ParallelOptions, - y => - { - for (int x = 0; x < width; x++) + using (PixelAccessor sourcePixels = source.Lock()) + { + Parallel.For( + 0, + height, + this.ParallelOptions, + y => { - Point transformedPoint = Point.Skew(new Point(x, y), matrix); - if (source.Bounds.Contains(transformedPoint.X, transformedPoint.Y)) + for (int x = 0; x < width; x++) { - targetPixels[x, y] = sourcePixels[transformedPoint.X, transformedPoint.Y]; + Point transformedPoint = Point.Skew(new Point(x, y), matrix); + if (source.Bounds.Contains(transformedPoint.X, transformedPoint.Y)) + { + targetPixels[x, y] = sourcePixels[transformedPoint.X, transformedPoint.Y]; + } } - } - }); - } + }); + } - source.SetPixels(width, height, target); + source.SwapPixelsBuffers(targetPixels); + } } /// diff --git a/src/ImageSharp.Processing/project.json b/src/ImageSharp.Processing/project.json index 2ff224fa4a..b375ca3a05 100644 --- a/src/ImageSharp.Processing/project.json +++ b/src/ImageSharp.Processing/project.json @@ -39,8 +39,7 @@ }, "dependencies": { "ImageSharp": { - "target": "project", - "version": "1.0.0-alpha1" + "target": "project" }, "StyleCop.Analyzers": { "version": "1.1.0-beta001", diff --git a/src/ImageSharp/Image/IImageBase{TColor}.cs b/src/ImageSharp/Image/IImageBase{TColor}.cs index 39f3fba67b..66746c9935 100644 --- a/src/ImageSharp/Image/IImageBase{TColor}.cs +++ b/src/ImageSharp/Image/IImageBase{TColor}.cs @@ -31,35 +31,6 @@ namespace ImageSharp /// void InitPixels(int width, int height); - /// - /// Sets the pixel array of the image to the given value. - /// - /// The new width of the image. Must be greater than zero. - /// The new height of the image. Must be greater than zero. - /// The array with pixels. Must be a multiple of the width and height. - /// - /// Thrown if either or are less than or equal to 0. - /// - /// - /// Thrown if the length is not equal to Width * Height. - /// - void SetPixels(int width, int height, TColor[] pixels); - - /// - /// Sets the pixel array of the image to the given value, creating a copy of - /// the original pixels. - /// - /// The new width of the image. Must be greater than zero. - /// The new height of the image. Must be greater than zero. - /// The array with pixels. Must be a multiple of four times the width and height. - /// - /// Thrown if either or are less than or equal to 0. - /// - /// - /// Thrown if the length is not equal to Width * Height. - /// - void ClonePixels(int width, int height, TColor[] pixels); - /// /// Locks the image providing access to the pixels. /// diff --git a/src/ImageSharp/Image/ImageBase{TColor}.cs b/src/ImageSharp/Image/ImageBase{TColor}.cs index 02bab249be..0cedb7aaaf 100644 --- a/src/ImageSharp/Image/ImageBase{TColor}.cs +++ b/src/ImageSharp/Image/ImageBase{TColor}.cs @@ -146,49 +146,28 @@ namespace ImageSharp } /// - public void SetPixels(int width, int height, TColor[] pixels) + public virtual PixelAccessor Lock() { - Guard.MustBeGreaterThan(width, 0, nameof(width)); - Guard.MustBeGreaterThan(height, 0, nameof(height)); - Guard.NotNull(pixels, nameof(pixels)); - - if (!(pixels.Length >= width * height)) - { - throw new ArgumentException($"Pixel array must have the length of at least {width * height}."); - } - - this.Width = width; - this.Height = height; - - this.ReturnPixels(); - this.pixelBuffer = pixels; + return new PixelAccessor(this); } - /// - public void ClonePixels(int width, int height, TColor[] pixels) + /// + /// Switches the buffers used by the image and the PixelAccessor meaning that the Image will "own" the buffer from the PixelAccessor and the PixelAccessor will now own the Images buffer. + /// + /// The pixel source. + internal void SwapPixelsBuffers(PixelAccessor pixelSource) { - Guard.MustBeGreaterThan(width, 0, nameof(width)); - Guard.MustBeGreaterThan(height, 0, nameof(height)); - Guard.NotNull(pixels, nameof(pixels)); - - if (!(pixels.Length >= width * height)) - { - throw new ArgumentException($"Pixel array must have the length of at least {width * height}."); - } - - this.Width = width; - this.Height = height; + Guard.NotNull(pixelSource, nameof(pixelSource)); + Guard.IsTrue(pixelSource.PooledMemory, nameof(pixelSource.PooledMemory), "pixelSource must be using pooled memory"); - // Copy the pixels. TODO: use Unsafe.Copy. - this.ReturnPixels(); - this.RentPixels(); - Array.Copy(pixels, this.pixelBuffer, width * height); - } + int newWidth = pixelSource.Width; + int newHeight = pixelSource.Height; - /// - public virtual PixelAccessor Lock() - { - return new PixelAccessor(this); + // push my memory into the accessor (which in turn unpins the old puffer ready for the images use) + TColor[] newPixels = pixelSource.ReturnCurrentPixelsAndReplaceThemInternally(this.Width, this.Height, this.pixelBuffer, true); + this.Width = newWidth; + this.Height = newHeight; + this.pixelBuffer = newPixels; } /// diff --git a/src/ImageSharp/Image/PixelAccessor{TColor}.cs b/src/ImageSharp/Image/PixelAccessor{TColor}.cs index 60bf8de784..58cff55c80 100644 --- a/src/ImageSharp/Image/PixelAccessor{TColor}.cs +++ b/src/ImageSharp/Image/PixelAccessor{TColor}.cs @@ -44,6 +44,11 @@ namespace ImageSharp /// private bool isDisposed; + /// + /// The pixels data + /// + private TColor[] pixels; + /// /// Initializes a new instance of the class. /// @@ -54,13 +59,7 @@ namespace ImageSharp Guard.MustBeGreaterThan(image.Width, 0, "image width"); Guard.MustBeGreaterThan(image.Height, 0, "image height"); - this.Width = image.Width; - this.Height = image.Height; - this.pixelsHandle = GCHandle.Alloc(image.Pixels, GCHandleType.Pinned); - this.dataPointer = this.pixelsHandle.AddrOfPinnedObject(); - this.pixelsBase = (byte*)this.dataPointer.ToPointer(); - this.PixelSize = Unsafe.SizeOf(); - this.RowStride = this.Width * this.PixelSize; + this.SetPixelBufferUnsafe(image.Width, image.Height, image.Pixels, false); this.ParallelOptions = image.Configuration.ParallelOptions; } @@ -71,6 +70,28 @@ namespace ImageSharp /// The height of the image represented by the pixel buffer. /// The pixel buffer. public PixelAccessor(int width, int height, TColor[] pixels) + : this(width, height, pixels, false) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Gets the width of the image represented by the pixel buffer. + /// The height of the image represented by the pixel buffer. + public PixelAccessor(int width, int height) + : this(width, height, PixelPool.RentPixels(width * height), true) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Gets the width of the image represented by the pixel buffer. + /// The height of the image represented by the pixel buffer. + /// The pixel buffer. + /// if set to true then the TColor[] is from the PixelPool{TColor} thus should be returned once disposed. + private PixelAccessor(int width, int height, TColor[] pixels, bool pooledMemory) { Guard.NotNull(pixels, nameof(pixels)); Guard.MustBeGreaterThan(width, 0, nameof(width)); @@ -81,13 +102,8 @@ namespace ImageSharp throw new ArgumentException($"Pixel array must have the length of at least {width * height}."); } - this.Width = width; - this.Height = height; - this.pixelsHandle = GCHandle.Alloc(pixels, GCHandleType.Pinned); - this.dataPointer = this.pixelsHandle.AddrOfPinnedObject(); - this.pixelsBase = (byte*)this.dataPointer.ToPointer(); - this.PixelSize = Unsafe.SizeOf(); - this.RowStride = this.Width * this.PixelSize; + this.SetPixelBufferUnsafe(width, height, pixels, pooledMemory); + this.ParallelOptions = Configuration.Default.ParallelOptions; } @@ -99,6 +115,14 @@ namespace ImageSharp this.Dispose(); } + /// + /// Gets a value indicating whether [pooled memory]. + /// + /// + /// true if [pooled memory]; otherwise, false. + /// + public bool PooledMemory { get; private set; } + /// /// Gets the pointer to the pixel buffer. /// @@ -107,22 +131,22 @@ namespace ImageSharp /// /// Gets the size of a single pixel in the number of bytes. /// - public int PixelSize { get; } + public int PixelSize { get; private set; } /// /// Gets the width of one row in the number of bytes. /// - public int RowStride { get; } + public int RowStride { get; private set; } /// /// Gets the width of the image. /// - public int Width { get; } + public int Width { get; private set; } /// /// Gets the height of the image. /// - public int Height { get; } + public int Height { get; private set; } /// /// Gets the global parallel options for processing tasks in parallel. @@ -221,13 +245,7 @@ namespace ImageSharp return; } - if (this.pixelsHandle.IsAllocated) - { - this.pixelsHandle.Free(); - } - - this.dataPointer = IntPtr.Zero; - this.pixelsBase = null; + this.UnPinPixels(); // Note disposing is done. this.isDisposed = true; @@ -238,6 +256,12 @@ namespace ImageSharp // and prevent finalization code for this object // from executing a second time. GC.SuppressFinalize(this); + + if (this.PooledMemory) + { + PixelPool.ReturnPixels(this.pixels); + this.pixels = null; + } } /// @@ -248,6 +272,22 @@ namespace ImageSharp Unsafe.InitBlock(this.pixelsBase, 0, (uint)(this.RowStride * this.Height)); } + /// + /// Sets the pixel buffer in an unsafe manor this should not be used unless you know what its doing!!! + /// + /// The width. + /// The height. + /// The pixels. + /// if set to true [pooled memory]. + /// Returns the old pixel data thats has gust been replaced. + /// If PixelAccessor.PooledMemory is true then caller is responsible for ensuring PixelPool.ReturnPixels() is called. + internal TColor[] ReturnCurrentPixelsAndReplaceThemInternally(int width, int height, TColor[] pixels, bool pooledMemory) + { + TColor[] oldPixels = this.pixels; + this.SetPixelBufferUnsafe(width, height, pixels, pooledMemory); + return oldPixels; + } + /// /// Copies the pixels to another of the same size. /// @@ -472,6 +512,54 @@ namespace ImageSharp return this.pixelsBase + (((y * this.Width) + x) * Unsafe.SizeOf()); } + /// + /// Sets the pixel buffer in an unsafe manor this should not be used unless you know what its doing!!! + /// + /// The width. + /// The height. + /// The pixels. + /// if set to true [pooled memory]. + private void SetPixelBufferUnsafe(int width, int height, TColor[] pixels, bool pooledMemory) + { + this.pixels = pixels; + this.PooledMemory = pooledMemory; + this.Width = width; + this.Height = height; + this.PinPixels(); + this.PixelSize = Unsafe.SizeOf(); + this.RowStride = this.Width * this.PixelSize; + } + + /// + /// Pins the pixels data. + /// + private void PinPixels() + { + // unpin any old pixels just incase + this.UnPinPixels(); + + this.pixelsHandle = GCHandle.Alloc(this.pixels, GCHandleType.Pinned); + this.dataPointer = this.pixelsHandle.AddrOfPinnedObject(); + this.pixelsBase = (byte*)this.dataPointer.ToPointer(); + } + + /// + /// Unpins pixels data. + /// + private void UnPinPixels() + { + if (this.pixelsBase != null) + { + if (this.pixelsHandle.IsAllocated) + { + this.pixelsHandle.Free(); + } + + this.dataPointer = IntPtr.Zero; + this.pixelsBase = null; + } + } + /// /// Copy an area of pixels to the image. /// diff --git a/src/ImageSharp/Quantizers/Quantize.cs b/src/ImageSharp/Quantizers/Quantize.cs index 7a42090c73..a03833b25c 100644 --- a/src/ImageSharp/Quantizers/Quantize.cs +++ b/src/ImageSharp/Quantizers/Quantize.cs @@ -60,20 +60,26 @@ namespace ImageSharp int pixelCount = quantized.Pixels.Length; int palleteCount = quantized.Palette.Length - 1; - TColor[] pixels = new TColor[pixelCount]; - Parallel.For( - 0, - pixelCount, - source.Configuration.ParallelOptions, - i => - { - TColor color = quantized.Palette[Math.Min(palleteCount, quantized.Pixels[i])]; - pixels[i] = color; - }); + using (PixelAccessor pixels = new PixelAccessor(quantized.Width, quantized.Height)) + { + Parallel.For( + 0, + pixels.Height, + source.Configuration.ParallelOptions, + y => + { + for (var x = 0; x < pixels.Width; x++) + { + var i = x + (y * pixels.Width); + TColor color = quantized.Palette[Math.Min(palleteCount, quantized.Pixels[i])]; + pixels[x, y] = color; + } + }); - source.SetPixels(source.Width, source.Height, pixels); - return source; + source.SwapPixelsBuffers(pixels); + return source; + } } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs index fc74fe928a..70163187c3 100644 --- a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs +++ b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs @@ -71,30 +71,34 @@ namespace ImageSharp.Tests foreach (TestFile file in Files) { - using (Image image = file.CreateImage()) + using (Image srcImage = file.CreateImage()) { - Color[] pixels = new Color[image.Width * image.Height]; - Array.Copy(image.Pixels, pixels, image.Width * image.Height); - - using (FileStream output = File.OpenWrite($"{path}/Octree-{file.FileName}")) + using (Image image = new Image(srcImage)) { - image.Quantize(Quantization.Octree) - .Save(output, image.CurrentImageFormat); + using (FileStream output = File.OpenWrite($"{path}/Octree-{file.FileName}")) + { + image.Quantize(Quantization.Octree) + .Save(output, image.CurrentImageFormat); + } } - image.SetPixels(image.Width, image.Height, pixels); - using (FileStream output = File.OpenWrite($"{path}/Wu-{file.FileName}")) + using (Image image = new Image(srcImage)) { - image.Quantize(Quantization.Wu) - .Save(output, image.CurrentImageFormat); + using (FileStream output = File.OpenWrite($"{path}/Wu-{file.FileName}")) + { + image.Quantize(Quantization.Wu) + .Save(output, image.CurrentImageFormat); + } } - image.SetPixels(image.Width, image.Height, pixels); - using (FileStream output = File.OpenWrite($"{path}/Palette-{file.FileName}")) + using (Image image = new Image(srcImage)) { - image.Quantize(Quantization.Palette) - .Save(output, image.CurrentImageFormat); + using (FileStream output = File.OpenWrite($"{path}/Wu-{file.FileName}")) + { + image.Quantize(Quantization.Palette) + .Save(output, image.CurrentImageFormat); + } } } } From d234b1f02eff681f23917b673ac723be5d969d3a Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 2 Feb 2017 09:47:55 +1100 Subject: [PATCH 008/142] Clean up comments --- src/ImageSharp/Image/ImageBase{TColor}.cs | 2 +- src/ImageSharp/Image/PixelAccessor{TColor}.cs | 39 +++++++++---------- 2 files changed, 19 insertions(+), 22 deletions(-) diff --git a/src/ImageSharp/Image/ImageBase{TColor}.cs b/src/ImageSharp/Image/ImageBase{TColor}.cs index 0cedb7aaaf..e6c14f0e9e 100644 --- a/src/ImageSharp/Image/ImageBase{TColor}.cs +++ b/src/ImageSharp/Image/ImageBase{TColor}.cs @@ -163,7 +163,7 @@ namespace ImageSharp int newWidth = pixelSource.Width; int newHeight = pixelSource.Height; - // push my memory into the accessor (which in turn unpins the old puffer ready for the images use) + // Push my memory into the accessor (which in turn unpins the old puffer ready for the images use) TColor[] newPixels = pixelSource.ReturnCurrentPixelsAndReplaceThemInternally(this.Width, this.Height, this.pixelBuffer, true); this.Width = newWidth; this.Height = newHeight; diff --git a/src/ImageSharp/Image/PixelAccessor{TColor}.cs b/src/ImageSharp/Image/PixelAccessor{TColor}.cs index 58cff55c80..f37ba7496f 100644 --- a/src/ImageSharp/Image/PixelAccessor{TColor}.cs +++ b/src/ImageSharp/Image/PixelAccessor{TColor}.cs @@ -45,9 +45,9 @@ namespace ImageSharp private bool isDisposed; /// - /// The pixels data + /// The pixel buffer /// - private TColor[] pixels; + private TColor[] pixelBuffer; /// /// Initializes a new instance of the class. @@ -66,7 +66,7 @@ namespace ImageSharp /// /// Initializes a new instance of the class. /// - /// Gets the width of the image represented by the pixel buffer. + /// The width of the image represented by the pixel buffer. /// The height of the image represented by the pixel buffer. /// The pixel buffer. public PixelAccessor(int width, int height, TColor[] pixels) @@ -77,7 +77,7 @@ namespace ImageSharp /// /// Initializes a new instance of the class. /// - /// Gets the width of the image represented by the pixel buffer. + /// The width of the image represented by the pixel buffer. /// The height of the image represented by the pixel buffer. public PixelAccessor(int width, int height) : this(width, height, PixelPool.RentPixels(width * height), true) @@ -87,10 +87,10 @@ namespace ImageSharp /// /// Initializes a new instance of the class. /// - /// Gets the width of the image represented by the pixel buffer. + /// The width of the image represented by the pixel buffer. /// The height of the image represented by the pixel buffer. /// The pixel buffer. - /// if set to true then the TColor[] is from the PixelPool{TColor} thus should be returned once disposed. + /// if set to true then the is from the thus should be returned once disposed. private PixelAccessor(int width, int height, TColor[] pixels, bool pooledMemory) { Guard.NotNull(pixels, nameof(pixels)); @@ -116,11 +116,8 @@ namespace ImageSharp } /// - /// Gets a value indicating whether [pooled memory]. + /// Gets a value indicating whether the current pixel buffer is from a pooled source. /// - /// - /// true if [pooled memory]; otherwise, false. - /// public bool PooledMemory { get; private set; } /// @@ -259,8 +256,8 @@ namespace ImageSharp if (this.PooledMemory) { - PixelPool.ReturnPixels(this.pixels); - this.pixels = null; + PixelPool.ReturnPixels(this.pixelBuffer); + this.pixelBuffer = null; } } @@ -273,17 +270,17 @@ namespace ImageSharp } /// - /// Sets the pixel buffer in an unsafe manor this should not be used unless you know what its doing!!! + /// Sets the pixel buffer in an unsafe manner. This should not be used unless you know what its doing!!! /// /// The width. /// The height. /// The pixels. - /// if set to true [pooled memory]. + /// If set to true this indicates that the pixel buffer is from a pooled source. /// Returns the old pixel data thats has gust been replaced. - /// If PixelAccessor.PooledMemory is true then caller is responsible for ensuring PixelPool.ReturnPixels() is called. + /// If is true then caller is responsible for ensuring is called. internal TColor[] ReturnCurrentPixelsAndReplaceThemInternally(int width, int height, TColor[] pixels, bool pooledMemory) { - TColor[] oldPixels = this.pixels; + TColor[] oldPixels = this.pixelBuffer; this.SetPixelBufferUnsafe(width, height, pixels, pooledMemory); return oldPixels; } @@ -518,10 +515,10 @@ namespace ImageSharp /// The width. /// The height. /// The pixels. - /// if set to true [pooled memory]. + /// If set to true this indicates that the pixel buffer is from a pooled source. private void SetPixelBufferUnsafe(int width, int height, TColor[] pixels, bool pooledMemory) { - this.pixels = pixels; + this.pixelBuffer = pixels; this.PooledMemory = pooledMemory; this.Width = width; this.Height = height; @@ -538,7 +535,7 @@ namespace ImageSharp // unpin any old pixels just incase this.UnPinPixels(); - this.pixelsHandle = GCHandle.Alloc(this.pixels, GCHandleType.Pinned); + this.pixelsHandle = GCHandle.Alloc(this.pixelBuffer, GCHandleType.Pinned); this.dataPointer = this.pixelsHandle.AddrOfPinnedObject(); this.pixelsBase = (byte*)this.dataPointer.ToPointer(); } @@ -639,13 +636,13 @@ namespace ImageSharp int width = Math.Min(area.Width, this.Width - x); if (width < 1) { - throw new ArgumentOutOfRangeException(nameof(width), width, $"Invalid area size specified."); + throw new ArgumentOutOfRangeException(nameof(width), width, "Invalid area size specified."); } int height = Math.Min(area.Height, this.Height - y); if (height < 1) { - throw new ArgumentOutOfRangeException(nameof(height), height, $"Invalid area size specified."); + throw new ArgumentOutOfRangeException(nameof(height), height, "Invalid area size specified."); } } From db425844e58d496041629e9cf941302b217948d4 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 2 Feb 2017 09:55:05 +1100 Subject: [PATCH 009/142] A little sanitation --- src/ImageSharp.Formats.Bmp/BmpEncoderCore.cs | 18 +++++++++--------- src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs | 8 +++----- src/ImageSharp.Formats.Jpeg/JpegEncoderCore.cs | 1 - src/ImageSharp/Image/ImageBase{TColor}.cs | 1 - 4 files changed, 12 insertions(+), 16 deletions(-) diff --git a/src/ImageSharp.Formats.Bmp/BmpEncoderCore.cs b/src/ImageSharp.Formats.Bmp/BmpEncoderCore.cs index 94f2b3a38b..8abe7cbfce 100644 --- a/src/ImageSharp.Formats.Bmp/BmpEncoderCore.cs +++ b/src/ImageSharp.Formats.Bmp/BmpEncoderCore.cs @@ -34,7 +34,7 @@ namespace ImageSharp.Formats /// The public void Encode(ImageBase image, Stream stream, BmpBitsPerPixel bitsPerPixel) where TColor : struct, IPackedPixel, IEquatable - { + { Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); @@ -119,23 +119,23 @@ namespace ImageSharp.Formats /// Writes the pixel data to the binary stream. /// /// The pixel format. - /// The containing the stream to write to. + /// The containing the stream to write to. /// /// The containing pixel data. /// private void WriteImage(EndianBinaryWriter writer, ImageBase image) where TColor : struct, IPackedPixel, IEquatable - { + { using (PixelAccessor pixels = image.Lock()) { switch (this.bmpBitsPerPixel) { case BmpBitsPerPixel.Pixel32: - this.Write32Bit(writer, pixels); + this.Write32Bit(writer, pixels); break; case BmpBitsPerPixel.Pixel24: - this.Write24Bit(writer, pixels); + this.Write24Bit(writer, pixels); break; } } @@ -145,11 +145,11 @@ namespace ImageSharp.Formats /// Writes the 32bit color palette to the stream. /// /// The pixel format. - /// The containing the stream to write to. + /// The containing the stream to write to. /// The containing pixel data. private void Write32Bit(EndianBinaryWriter writer, PixelAccessor pixels) where TColor : struct, IPackedPixel, IEquatable - { + { using (PixelArea row = new PixelArea(pixels.Width, ComponentOrder.Zyxw, this.padding)) { for (int y = pixels.Height - 1; y >= 0; y--) @@ -164,11 +164,11 @@ namespace ImageSharp.Formats /// Writes the 24bit color palette to the stream. /// /// The pixel format. - /// The containing the stream to write to. + /// The containing the stream to write to. /// The containing pixel data. private void Write24Bit(EndianBinaryWriter writer, PixelAccessor pixels) where TColor : struct, IPackedPixel, IEquatable - { + { using (PixelArea row = new PixelArea(pixels.Width, ComponentOrder.Zyx, this.padding)) { for (int y = pixels.Height - 1; y >= 0; y--) diff --git a/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs b/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs index 72655a5007..1fd9632922 100644 --- a/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs @@ -5,10 +5,8 @@ namespace ImageSharp.Formats { using System; - using System.Buffers; using System.IO; using System.Runtime.CompilerServices; - using System.Runtime.InteropServices; using System.Threading.Tasks; using ImageSharp.Formats.Jpg; @@ -586,7 +584,7 @@ namespace ImageSharp.Formats byte yellow = this.ycbcrImage.CrChannel.Pixels[co + (x / scale)]; TColor packed = default(TColor); - this.PackCmyk(ref packed, cyan, magenta, yellow, x, y); + this.PackCmyk(ref packed, cyan, magenta, yellow, x, y); pixels[x, y] = packed; } }); @@ -744,7 +742,7 @@ namespace ImageSharp.Formats byte cr = this.ycbcrImage.CrChannel.Pixels[co + (x / scale)]; TColor packed = default(TColor); - this.PackYcck(ref packed, yy, cb, cr, x, y); + this.PackYcck(ref packed, yy, cb, cr, x, y); pixels[x, y] = packed; } }); @@ -1037,7 +1035,7 @@ namespace ImageSharp.Formats } this.InputProcessor.ReadFull(this.Temp, 0, remaining); - this.RestartInterval = ((int)this.Temp[0] << 8) + (int)this.Temp[1]; + this.RestartInterval = (this.Temp[0] << 8) + this.Temp[1]; } /// diff --git a/src/ImageSharp.Formats.Jpeg/JpegEncoderCore.cs b/src/ImageSharp.Formats.Jpeg/JpegEncoderCore.cs index a97bc4bcae..984418dd3a 100644 --- a/src/ImageSharp.Formats.Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp.Formats.Jpeg/JpegEncoderCore.cs @@ -8,7 +8,6 @@ namespace ImageSharp.Formats using System; using System.Buffers; using System.IO; - using System.Numerics; using System.Runtime.CompilerServices; using ImageSharp.Formats.Jpg; diff --git a/src/ImageSharp/Image/ImageBase{TColor}.cs b/src/ImageSharp/Image/ImageBase{TColor}.cs index e6c14f0e9e..1b3abd360f 100644 --- a/src/ImageSharp/Image/ImageBase{TColor}.cs +++ b/src/ImageSharp/Image/ImageBase{TColor}.cs @@ -6,7 +6,6 @@ namespace ImageSharp { using System; - using System.Buffers; using System.Diagnostics; /// From d21a7e61e575a81780393fbc06eb1da45a84b2c1 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 2 Feb 2017 10:51:54 +1100 Subject: [PATCH 010/142] Fix quantizer test --- tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs index 70163187c3..6873717ed6 100644 --- a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs +++ b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs @@ -94,7 +94,7 @@ namespace ImageSharp.Tests using (Image image = new Image(srcImage)) { - using (FileStream output = File.OpenWrite($"{path}/Wu-{file.FileName}")) + using (FileStream output = File.OpenWrite($"{path}/Palette-{file.FileName}")) { image.Quantize(Quantization.Palette) .Save(output, image.CurrentImageFormat); From 382d3872ec9794f23953abf2ac328c186a743f44 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 2 Feb 2017 10:55:57 +1100 Subject: [PATCH 011/142] Bump alpha --- src/ImageSharp.Drawing/project.json | 2 +- src/ImageSharp.Formats.Bmp/project.json | 2 +- src/ImageSharp.Formats.Gif/project.json | 2 +- src/ImageSharp.Formats.Jpeg/project.json | 2 +- src/ImageSharp.Formats.Png/project.json | 2 +- src/ImageSharp.Processing/project.json | 2 +- src/ImageSharp/project.json | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/ImageSharp.Drawing/project.json b/src/ImageSharp.Drawing/project.json index 04a5601706..b974684a5d 100644 --- a/src/ImageSharp.Drawing/project.json +++ b/src/ImageSharp.Drawing/project.json @@ -1,5 +1,5 @@ { - "version": "1.0.0-alpha1-*", + "version": "1.0.0-alpha2-*", "title": "ImageSharp.Drawing", "description": "A cross-platform library for the processing of image files; written in C#", "authors": [ diff --git a/src/ImageSharp.Formats.Bmp/project.json b/src/ImageSharp.Formats.Bmp/project.json index 575e414aa1..e66ecaf247 100644 --- a/src/ImageSharp.Formats.Bmp/project.json +++ b/src/ImageSharp.Formats.Bmp/project.json @@ -1,5 +1,5 @@ { - "version": "1.0.0-alpha1-*", + "version": "1.0.0-alpha2-*", "title": "ImageSharp.Formats.Bmp", "description": "A cross-platform library for the processing of image files; written in C#", "authors": [ diff --git a/src/ImageSharp.Formats.Gif/project.json b/src/ImageSharp.Formats.Gif/project.json index e12d3c733f..a501fe1c67 100644 --- a/src/ImageSharp.Formats.Gif/project.json +++ b/src/ImageSharp.Formats.Gif/project.json @@ -1,5 +1,5 @@ { - "version": "1.0.0-alpha1-*", + "version": "1.0.0-alpha2-*", "title": "ImageSharp.Formats.Gif", "description": "A cross-platform library for the processing of image files; written in C#", "authors": [ diff --git a/src/ImageSharp.Formats.Jpeg/project.json b/src/ImageSharp.Formats.Jpeg/project.json index de16f6c1ce..c7e6f7e673 100644 --- a/src/ImageSharp.Formats.Jpeg/project.json +++ b/src/ImageSharp.Formats.Jpeg/project.json @@ -1,5 +1,5 @@ { - "version": "1.0.0-alpha1-*", + "version": "1.0.0-alpha2-*", "title": "ImageSharp.Formats.Jpeg", "description": "A cross-platform library for the processing of image files; written in C#", "authors": [ diff --git a/src/ImageSharp.Formats.Png/project.json b/src/ImageSharp.Formats.Png/project.json index eac71d8c71..bd8c2a323b 100644 --- a/src/ImageSharp.Formats.Png/project.json +++ b/src/ImageSharp.Formats.Png/project.json @@ -1,5 +1,5 @@ { - "version": "1.0.0-alpha1-*", + "version": "1.0.0-alpha2-*", "title": "ImageSharp.Formats.Png", "description": "A cross-platform library for the processing of image files; written in C#", "authors": [ diff --git a/src/ImageSharp.Processing/project.json b/src/ImageSharp.Processing/project.json index b375ca3a05..cf4d2fc240 100644 --- a/src/ImageSharp.Processing/project.json +++ b/src/ImageSharp.Processing/project.json @@ -1,5 +1,5 @@ { - "version": "1.0.0-alpha1-*", + "version": "1.0.0-alpha2-*", "title": "ImageSharp.Processing", "description": "A cross-platform library for the processing of image files; written in C#", "authors": [ diff --git a/src/ImageSharp/project.json b/src/ImageSharp/project.json index 117d320900..f5f0f103bc 100644 --- a/src/ImageSharp/project.json +++ b/src/ImageSharp/project.json @@ -1,5 +1,5 @@ { - "version": "1.0.0-alpha1-*", + "version": "1.0.0-alpha2-*", "title": "ImageSharp", "description": "A cross-platform library for the processing of image files; written in C#", "authors": [ From d40dcec2af9148efd18d3fc3afeff19f7b6867e8 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 2 Feb 2017 22:32:34 +1100 Subject: [PATCH 012/142] Update code sample [skip ci] --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4d499fa35e..9d947f227e 100644 --- a/README.md +++ b/README.md @@ -75,8 +75,8 @@ Here's an example of the code required to resize an image using the default Bicu ```csharp using (FileStream stream = File.OpenRead("foo.jpg")) using (FileStream output = File.OpenWrite("bar.jpg")) +using (Image image = new Image(stream)) { - Image image = new Image(stream); image.Resize(image.Width / 2, image.Height / 2) .Grayscale() .Save(output); @@ -92,7 +92,7 @@ new BrightnessProcessor(50).Apply(sourceImage, sourceImage.Bounds); Setting individual pixel values is perfomed as follows: ```csharp -Image image = new Image(400, 400); +using (image = new Image(400, 400) using (var pixels = image.Lock()) { pixels[200, 200] = Color.White; From 97085fe4656e9687f29d3e86f1b0605dc255ee70 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Sun, 29 Jan 2017 15:31:15 +0000 Subject: [PATCH 013/142] migrate to third party shapes library --- NuGet.config | 1 + src/ImageSharp.Drawing/Draw.cs | 3 +- src/ImageSharp.Drawing/DrawRectangle.cs | 6 +- src/ImageSharp.Drawing/Fill.cs | 4 +- src/ImageSharp.Drawing/FillRectangle.cs | 5 +- .../Paths/BezierLineSegment.cs | 119 - src/ImageSharp.Drawing/Paths/ILineSegment.cs | 21 - src/ImageSharp.Drawing/Paths/IPath.cs | 48 - src/ImageSharp.Drawing/Paths/InternalPath.cs | 516 --- .../Paths/LinearLineSegment.cs | 55 - src/ImageSharp.Drawing/Paths/Path.cs | 51 - src/ImageSharp.Drawing/Pens/Pen{TColor}.cs | 1 - .../Pens/Processors/PenApplicator.cs | 1 - .../{Paths => Pens/Processors}/PointInfo.cs | 18 +- .../Processors/DrawPathProcessor.cs | 30 +- .../Processors/FillShapeProcessor.cs | 4 +- .../Processors/PointInfoExtensions.cs | 37 + .../Processors/RectangleExtensions.cs | 32 + .../Shapes/BezierPolygon.cs | 93 - .../Shapes/ComplexPolygon.cs | 246 -- src/ImageSharp.Drawing/Shapes/IShape.cs | 56 - .../Shapes/LinearPolygon.cs | 93 - src/ImageSharp.Drawing/Shapes/Polygon.cs | 156 - .../Shapes/PolygonClipper/Clipper.cs | 3860 ----------------- .../Shapes/PolygonClipper/ClipperException.cs | 31 - .../Shapes/PolygonClipper/Direction.cs | 31 - .../Shapes/PolygonClipper/EdgeSide.cs | 31 - .../Shapes/PolygonClipper/IntersectNode.cs | 38 - .../PolygonClipper/IntersectNodeSort.cs | 48 - .../Shapes/PolygonClipper/Join.cs | 38 - .../Shapes/PolygonClipper/LocalMinima.cs | 44 - .../Shapes/PolygonClipper/Maxima.cs | 38 - .../Shapes/PolygonClipper/OutPt.cs | 43 - .../Shapes/PolygonClipper/OutRec.cs | 64 - .../Shapes/PolygonClipper/PolyNode.cs | 179 - .../Shapes/PolygonClipper/PolyTree.cs | 81 - .../Shapes/PolygonClipper/PolyType.cs | 31 - .../Shapes/PolygonClipper/README.md | 40 - .../Shapes/PolygonClipper/Scanbeam.cs | 33 - .../Shapes/PolygonClipper/TEdge.cs | 118 - .../Shapes/RectangularPolygon.cs | 281 -- src/ImageSharp.Drawing/project.json | 1 + .../Drawing/FillRectangle.cs | 10 +- 43 files changed, 108 insertions(+), 6528 deletions(-) delete mode 100644 src/ImageSharp.Drawing/Paths/BezierLineSegment.cs delete mode 100644 src/ImageSharp.Drawing/Paths/ILineSegment.cs delete mode 100644 src/ImageSharp.Drawing/Paths/IPath.cs delete mode 100644 src/ImageSharp.Drawing/Paths/InternalPath.cs delete mode 100644 src/ImageSharp.Drawing/Paths/LinearLineSegment.cs delete mode 100644 src/ImageSharp.Drawing/Paths/Path.cs rename src/ImageSharp.Drawing/{Paths => Pens/Processors}/PointInfo.cs (54%) create mode 100644 src/ImageSharp.Drawing/Processors/PointInfoExtensions.cs create mode 100644 src/ImageSharp.Drawing/Processors/RectangleExtensions.cs delete mode 100644 src/ImageSharp.Drawing/Shapes/BezierPolygon.cs delete mode 100644 src/ImageSharp.Drawing/Shapes/ComplexPolygon.cs delete mode 100644 src/ImageSharp.Drawing/Shapes/IShape.cs delete mode 100644 src/ImageSharp.Drawing/Shapes/LinearPolygon.cs delete mode 100644 src/ImageSharp.Drawing/Shapes/Polygon.cs delete mode 100644 src/ImageSharp.Drawing/Shapes/PolygonClipper/Clipper.cs delete mode 100644 src/ImageSharp.Drawing/Shapes/PolygonClipper/ClipperException.cs delete mode 100644 src/ImageSharp.Drawing/Shapes/PolygonClipper/Direction.cs delete mode 100644 src/ImageSharp.Drawing/Shapes/PolygonClipper/EdgeSide.cs delete mode 100644 src/ImageSharp.Drawing/Shapes/PolygonClipper/IntersectNode.cs delete mode 100644 src/ImageSharp.Drawing/Shapes/PolygonClipper/IntersectNodeSort.cs delete mode 100644 src/ImageSharp.Drawing/Shapes/PolygonClipper/Join.cs delete mode 100644 src/ImageSharp.Drawing/Shapes/PolygonClipper/LocalMinima.cs delete mode 100644 src/ImageSharp.Drawing/Shapes/PolygonClipper/Maxima.cs delete mode 100644 src/ImageSharp.Drawing/Shapes/PolygonClipper/OutPt.cs delete mode 100644 src/ImageSharp.Drawing/Shapes/PolygonClipper/OutRec.cs delete mode 100644 src/ImageSharp.Drawing/Shapes/PolygonClipper/PolyNode.cs delete mode 100644 src/ImageSharp.Drawing/Shapes/PolygonClipper/PolyTree.cs delete mode 100644 src/ImageSharp.Drawing/Shapes/PolygonClipper/PolyType.cs delete mode 100644 src/ImageSharp.Drawing/Shapes/PolygonClipper/README.md delete mode 100644 src/ImageSharp.Drawing/Shapes/PolygonClipper/Scanbeam.cs delete mode 100644 src/ImageSharp.Drawing/Shapes/PolygonClipper/TEdge.cs delete mode 100644 src/ImageSharp.Drawing/Shapes/RectangularPolygon.cs diff --git a/NuGet.config b/NuGet.config index b2c967cc97..88d71e7bac 100644 --- a/NuGet.config +++ b/NuGet.config @@ -1,6 +1,7 @@  + diff --git a/src/ImageSharp.Drawing/Draw.cs b/src/ImageSharp.Drawing/Draw.cs index c10665b832..8c3dbb1b5a 100644 --- a/src/ImageSharp.Drawing/Draw.cs +++ b/src/ImageSharp.Drawing/Draw.cs @@ -9,10 +9,9 @@ namespace ImageSharp using System.Numerics; using Drawing; using Drawing.Brushes; - using Drawing.Paths; using Drawing.Pens; using Drawing.Processors; - using Drawing.Shapes; + using SixLabors.Shapes; /// /// Extension methods for the type. diff --git a/src/ImageSharp.Drawing/DrawRectangle.cs b/src/ImageSharp.Drawing/DrawRectangle.cs index 38ed578b65..17d6d5f368 100644 --- a/src/ImageSharp.Drawing/DrawRectangle.cs +++ b/src/ImageSharp.Drawing/DrawRectangle.cs @@ -9,10 +9,10 @@ namespace ImageSharp using Drawing; using Drawing.Brushes; - using Drawing.Paths; using Drawing.Pens; using Drawing.Processors; - using Drawing.Shapes; + + using SixLabors.Shapes; /// /// Extension methods for the type. @@ -33,7 +33,7 @@ namespace ImageSharp public static Image DrawPolygon(this Image source, IPen pen, RectangleF shape, GraphicsOptions options) where TColor : struct, IPackedPixel, IEquatable { - return source.Apply(new DrawPathProcessor(pen, (IPath)new RectangularPolygon(shape), options)); + return source.Apply(new DrawPathProcessor(pen, (IPath)new SixLabors.Shapes.Rectangle(shape.X, shape.Y, shape.Width, shape.Height), options)); } /// diff --git a/src/ImageSharp.Drawing/Fill.cs b/src/ImageSharp.Drawing/Fill.cs index c0f43bdd18..637950e76d 100644 --- a/src/ImageSharp.Drawing/Fill.cs +++ b/src/ImageSharp.Drawing/Fill.cs @@ -9,9 +9,9 @@ namespace ImageSharp using System.Numerics; using Drawing; using Drawing.Brushes; - using Drawing.Paths; using Drawing.Processors; - using Drawing.Shapes; + + using SixLabors.Shapes; /// /// Extension methods for the type. diff --git a/src/ImageSharp.Drawing/FillRectangle.cs b/src/ImageSharp.Drawing/FillRectangle.cs index d29b58e1b8..7749e7c921 100644 --- a/src/ImageSharp.Drawing/FillRectangle.cs +++ b/src/ImageSharp.Drawing/FillRectangle.cs @@ -10,7 +10,6 @@ namespace ImageSharp using Drawing; using Drawing.Brushes; using Drawing.Processors; - using Drawing.Shapes; /// /// Extension methods for the type. @@ -31,7 +30,7 @@ namespace ImageSharp public static Image Fill(this Image source, IBrush brush, RectangleF shape, GraphicsOptions options) where TColor : struct, IPackedPixel, IEquatable { - return source.Apply(new FillShapeProcessor(brush, new RectangularPolygon(shape), options)); + return source.Apply(new FillShapeProcessor(brush, new SixLabors.Shapes.Rectangle(shape.X, shape.Y, shape.Width, shape.Height), options)); } /// @@ -45,7 +44,7 @@ namespace ImageSharp public static Image Fill(this Image source, IBrush brush, RectangleF shape) where TColor : struct, IPackedPixel, IEquatable { - return source.Apply(new FillShapeProcessor(brush, new RectangularPolygon(shape), GraphicsOptions.Default)); + return source.Apply(new FillShapeProcessor(brush, new SixLabors.Shapes.Rectangle(shape.X, shape.Y, shape.Width, shape.Height), GraphicsOptions.Default)); } /// diff --git a/src/ImageSharp.Drawing/Paths/BezierLineSegment.cs b/src/ImageSharp.Drawing/Paths/BezierLineSegment.cs deleted file mode 100644 index 647f97f1e9..0000000000 --- a/src/ImageSharp.Drawing/Paths/BezierLineSegment.cs +++ /dev/null @@ -1,119 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Drawing.Paths -{ - using System.Numerics; - - /// - /// Represents a line segment that conistst of control points that will be rendered as a cubic bezier curve - /// - /// - public class BezierLineSegment : ILineSegment - { - /// - /// The segments per curve. - /// code for this taken from - /// - private const int SegmentsPerCurve = 50; - - /// - /// The line points. - /// - private readonly Vector2[] linePoints; - - /// - /// Initializes a new instance of the class. - /// - /// The points. - public BezierLineSegment(params Vector2[] points) - { - Guard.NotNull(points, nameof(points)); - Guard.MustBeGreaterThanOrEqualTo(points.Length, 4, nameof(points)); - - this.linePoints = this.GetDrawingPoints(points); - } - - /// - /// Returns the current a simple linear path. - /// - /// - /// Returns the current as simple linear path. - /// - public Vector2[] AsSimpleLinearPath() - { - return this.linePoints; - } - - /// - /// Returns the drawing points along the line. - /// - /// The control points. - /// - /// The . - /// - private Vector2[] GetDrawingPoints(Vector2[] controlPoints) - { - // TODO we need to calculate an optimal SegmentsPerCurve value - // depending on the calcualted length of this curve - int curveCount = (controlPoints.Length - 1) / 3; - int finalPointCount = (SegmentsPerCurve * curveCount) + 1; // we have SegmentsPerCurve for each curve plus the origon point; - - Vector2[] drawingPoints = new Vector2[finalPointCount]; - - int position = 0; - int targetPoint = controlPoints.Length - 3; - for (int i = 0; i < targetPoint; i += 3) - { - Vector2 p0 = controlPoints[i]; - Vector2 p1 = controlPoints[i + 1]; - Vector2 p2 = controlPoints[i + 2]; - Vector2 p3 = controlPoints[i + 3]; - - // only do this for the first end point. When i != 0, this coincides with the end point of the previous segment, - if (i == 0) - { - drawingPoints[position++] = this.CalculateBezierPoint(0, p0, p1, p2, p3); - } - - for (int j = 1; j <= SegmentsPerCurve; j++) - { - float t = j / (float)SegmentsPerCurve; - drawingPoints[position++] = this.CalculateBezierPoint(t, p0, p1, p2, p3); - } - } - - return drawingPoints; - } - - /// - /// Calculates the bezier point along the line. - /// - /// The position within the line. - /// The p 0. - /// The p 1. - /// The p 2. - /// The p 3. - /// - /// The . - /// - private Vector2 CalculateBezierPoint(float t, Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3) - { - float u = 1 - t; - float tt = t * t; - float uu = u * u; - float uuu = uu * u; - float ttt = tt * t; - - Vector2 p = uuu * p0; // first term - - p += 3 * uu * t * p1; // second term - p += 3 * u * tt * p2; // third term - p += ttt * p3; // fourth term - - return p; - } - } -} diff --git a/src/ImageSharp.Drawing/Paths/ILineSegment.cs b/src/ImageSharp.Drawing/Paths/ILineSegment.cs deleted file mode 100644 index 380f0bf40c..0000000000 --- a/src/ImageSharp.Drawing/Paths/ILineSegment.cs +++ /dev/null @@ -1,21 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Drawing.Paths -{ - using System.Numerics; - - /// - /// Represents a simple path segment - /// - public interface ILineSegment - { - /// - /// Converts the into a simple linear path.. - /// - /// Returns the current as simple linear path. - Vector2[] AsSimpleLinearPath(); // TODO move this over to ReadonlySpan once available - } -} diff --git a/src/ImageSharp.Drawing/Paths/IPath.cs b/src/ImageSharp.Drawing/Paths/IPath.cs deleted file mode 100644 index 278d972511..0000000000 --- a/src/ImageSharp.Drawing/Paths/IPath.cs +++ /dev/null @@ -1,48 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Drawing.Paths -{ - using System.Numerics; - - /// - /// Represents a logic path that can be drawn - /// - public interface IPath : ILineSegment - { - /// - /// Gets the bounds enclosing the path - /// - /// - /// The bounds. - /// - RectangleF Bounds { get; } - - /// - /// Gets a value indicating whether this instance is closed. - /// - /// - /// true if this instance is closed; otherwise, false. - /// - bool IsClosed { get; } - - /// - /// Gets the length of the path - /// - /// - /// The length. - /// - float Length { get; } - - /// - /// Calculates the distance along and away from the path for a specified point. - /// - /// The point along the path. - /// - /// Returns details about the point and its distance away from the path. - /// - PointInfo Distance(Vector2 point); - } -} diff --git a/src/ImageSharp.Drawing/Paths/InternalPath.cs b/src/ImageSharp.Drawing/Paths/InternalPath.cs deleted file mode 100644 index 36a0704cbe..0000000000 --- a/src/ImageSharp.Drawing/Paths/InternalPath.cs +++ /dev/null @@ -1,516 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// -namespace ImageSharp.Drawing.Paths -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Numerics; - - /// - /// Internal logic for integrating linear paths. - /// - internal class InternalPath - { - /// - /// The maximum vector - /// - private static readonly Vector2 MaxVector = new Vector2(float.MaxValue); - - /// - /// The locker. - /// - private static readonly object Locker = new object(); - - /// - /// The points. - /// - private readonly Vector2[] points; - - /// - /// The closed path. - /// - private readonly bool closedPath; - - /// - /// The total distance. - /// - private readonly Lazy totalDistance; - - /// - /// The constant. - /// - private float[] constant; - - /// - /// The multiples. - /// - private float[] multiple; - - /// - /// The distances. - /// - private float[] distance; - - /// - /// The calculated. - /// - private bool calculated = false; - - /// - /// Initializes a new instance of the class. - /// - /// The segments. - /// if set to true [is closed path]. - internal InternalPath(ILineSegment[] segments, bool isClosedPath) - : this(Simplify(segments), isClosedPath) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The segment. - /// if set to true [is closed path]. - internal InternalPath(ILineSegment segment, bool isClosedPath) - : this(segment.AsSimpleLinearPath(), isClosedPath) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The points. - /// if set to true [is closed path]. - internal InternalPath(Vector2[] points, bool isClosedPath) - { - this.points = points; - this.closedPath = isClosedPath; - - float minX = this.points.Min(x => x.X); - float maxX = this.points.Max(x => x.X); - float minY = this.points.Min(x => x.Y); - float maxY = this.points.Max(x => x.Y); - - this.Bounds = new RectangleF(minX, minY, maxX - minX, maxY - minY); - this.totalDistance = new Lazy(this.CalculateLength); - } - - /// - /// Gets the bounds. - /// - /// - /// The bounds. - /// - public RectangleF Bounds - { - get; - } - - /// - /// Gets the length. - /// - /// - /// The length. - /// - public float Length => this.totalDistance.Value; - - /// - /// Gets the points. - /// - /// - /// The points. - /// - internal Vector2[] Points => this.points; - - /// - /// Calculates the distance from the path. - /// - /// The point. - /// Returns the distance from the path - public PointInfo DistanceFromPath(Vector2 point) - { - this.CalculateConstants(); - - PointInfoInternal internalInfo = default(PointInfoInternal); - internalInfo.DistanceSquared = float.MaxValue; // Set it to max so that CalculateShorterDistance can reduce it back down - - int polyCorners = this.points.Length; - - if (!this.closedPath) - { - polyCorners -= 1; - } - - int closestPoint = 0; - for (int i = 0; i < polyCorners; i++) - { - int next = i + 1; - if (this.closedPath && next == polyCorners) - { - next = 0; - } - - if (this.CalculateShorterDistance(this.points[i], this.points[next], point, ref internalInfo)) - { - closestPoint = i; - } - } - - return new PointInfo - { - DistanceAlongPath = this.distance[closestPoint] + Vector2.Distance(this.points[closestPoint], point), - DistanceFromPath = (float)Math.Sqrt(internalInfo.DistanceSquared), - SearchPoint = point, - ClosestPointOnPath = internalInfo.PointOnLine - }; - } - - /// - /// Based on a line described by and - /// populate a buffer for all points on the path that the line intersects. - /// - /// The start. - /// The end. - /// The buffer. - /// The count. - /// The offset. - /// number iof intersections hit - public int FindIntersections(Vector2 start, Vector2 end, Vector2[] buffer, int count, int offset) - { - int polyCorners = this.points.Length; - - if (!this.closedPath) - { - polyCorners -= 1; - } - - int position = 0; - for (int i = 0; i < polyCorners && count > 0; i++) - { - int next = i + 1; - if (this.closedPath && next == polyCorners) - { - next = 0; - } - - Vector2 point = FindIntersection(this.points[i], this.points[next], start, end); - if (point != MaxVector) - { - buffer[position + offset] = point; - position++; - count--; - } - } - - return position; - } - - /// - /// Determines if the specified point is inside or outside the path. - /// - /// The point. - /// Returns true if the point is inside the closed path. - public bool PointInPolygon(Vector2 point) - { - // You can only be inside a path if its "closed" - if (!this.closedPath) - { - return false; - } - - if (!this.Bounds.Contains(point.X, point.Y)) - { - return false; - } - - this.CalculateConstants(); - - Vector2[] poly = this.points; - int polyCorners = poly.Length; - - int j = polyCorners - 1; - bool oddNodes = false; - - for (int i = 0; i < polyCorners; i++) - { - if ((poly[i].Y < point.Y && poly[j].Y >= point.Y) - || (poly[j].Y < point.Y && poly[i].Y >= point.Y)) - { - oddNodes ^= (point.Y * this.multiple[i]) + this.constant[i] < point.X; - } - - j = i; - } - - return oddNodes; - } - - /// - /// Determins if the bounding box for 2 lines - /// described by and - /// and and overlap. - /// - /// The line1 start. - /// The line1 end. - /// The line2 start. - /// The line2 end. - /// Returns true it the bounding box of the 2 lines intersect - private static bool BoundingBoxesIntersect(Vector2 line1Start, Vector2 line1End, Vector2 line2Start, Vector2 line2End) - { - Vector2 topLeft1 = Vector2.Min(line1Start, line1End); - Vector2 bottomRight1 = Vector2.Max(line1Start, line1End); - - Vector2 topLeft2 = Vector2.Min(line2Start, line2End); - Vector2 bottomRight2 = Vector2.Max(line2Start, line2End); - - float left1 = topLeft1.X; - float right1 = bottomRight1.X; - float top1 = topLeft1.Y; - float bottom1 = bottomRight1.Y; - - float left2 = topLeft2.X; - float right2 = bottomRight2.X; - float top2 = topLeft2.Y; - float bottom2 = bottomRight2.Y; - - return left1 <= right2 && right1 >= left2 - && - top1 <= bottom2 && bottom1 >= top2; - } - - /// - /// Finds the point on line described by and - /// that intersects with line described by and - /// - /// The line1 start. - /// The line1 end. - /// The line2 start. - /// The line2 end. - /// - /// A describing the point that the 2 lines cross or if they do not. - /// - private static Vector2 FindIntersection(Vector2 line1Start, Vector2 line1End, Vector2 line2Start, Vector2 line2End) - { - // do bounding boxes overlap, if not then the lines can't and return fast. - if (!BoundingBoxesIntersect(line1Start, line1End, line2Start, line2End)) - { - return MaxVector; - } - - Vector2 line1Diff = line1End - line1Start; - Vector2 line2Diff = line2End - line2Start; - - Vector2 point; - if (line1Diff.X == 0) - { - float slope = line2Diff.Y / line2Diff.X; - float yinter = line2Start.Y - (slope * line2Start.X); - float y = (line1Start.X * slope) + yinter; - point = new Vector2(line1Start.X, y); - - // horizontal and vertical lines - } - else if (line2Diff.X == 0) - { - float slope = line1Diff.Y / line1Diff.X; - float yinter = line1Start.Y - (slope * line1Start.X); - float y = (line2Start.X * slope) + yinter; - point = new Vector2(line2Start.X, y); - - // horizontal and vertical lines - } - else - { - float slope1 = line1Diff.Y / line1Diff.X; - float slope2 = line2Diff.Y / line2Diff.X; - - float yinter1 = line1Start.Y - (slope1 * line1Start.X); - float yinter2 = line2Start.Y - (slope2 * line2Start.X); - - if (slope1 == slope2 && yinter1 != yinter2) - { - return MaxVector; - } - - float x = (yinter2 - yinter1) / (slope1 - slope2); - float y = (slope1 * x) + yinter1; - - point = new Vector2(x, y); - } - - if (BoundingBoxesIntersect(line1Start, line1End, point, point)) - { - return point; - } - else if (BoundingBoxesIntersect(line2Start, line2End, point, point)) - { - return point; - } - - return MaxVector; - } - - /// - /// Simplifies the collection of segments. - /// - /// The segments. - /// - /// The . - /// - private static Vector2[] Simplify(ILineSegment[] segments) - { - List simplified = new List(); - foreach (ILineSegment seg in segments) - { - simplified.AddRange(seg.AsSimpleLinearPath()); - } - - return simplified.ToArray(); - } - - /// - /// Returns the length of the path. - /// - /// - /// The . - /// - private float CalculateLength() - { - float length = 0; - int polyCorners = this.points.Length; - - if (!this.closedPath) - { - polyCorners -= 1; - } - - for (int i = 0; i < polyCorners; i++) - { - int next = i + 1; - if (this.closedPath && next == polyCorners) - { - next = 0; - } - - length += Vector2.Distance(this.points[i], this.points[next]); - } - - return length; - } - - /// - /// Calculate the constants. - /// - private void CalculateConstants() - { - // http://alienryderflex.com/polygon/ source for point in polygon logic - if (this.calculated) - { - return; - } - - lock (Locker) - { - if (this.calculated) - { - return; - } - - Vector2[] poly = this.points; - int polyCorners = poly.Length; - this.constant = new float[polyCorners]; - this.multiple = new float[polyCorners]; - this.distance = new float[polyCorners]; - int i, j = polyCorners - 1; - - this.distance[0] = 0; - - for (i = 0; i < polyCorners; i++) - { - this.distance[j] = this.distance[i] + Vector2.Distance(poly[i], poly[j]); - if (poly[j].Y == poly[i].Y) - { - this.constant[i] = poly[i].X; - this.multiple[i] = 0; - } - else - { - Vector2 subtracted = poly[j] - poly[i]; - this.constant[i] = (poly[i].X - ((poly[i].Y * poly[j].X) / subtracted.Y)) + ((poly[i].Y * poly[i].X) / subtracted.Y); - this.multiple[i] = subtracted.X / subtracted.Y; - } - - j = i; - } - - this.calculated = true; - } - } - - /// - /// Calculate any shorter distances along the path. - /// - /// The start position. - /// The end position. - /// The current point. - /// The info. - /// - /// The . - /// - private bool CalculateShorterDistance(Vector2 start, Vector2 end, Vector2 point, ref PointInfoInternal info) - { - Vector2 diffEnds = end - start; - - float lengthSquared = diffEnds.LengthSquared(); - Vector2 diff = point - start; - - Vector2 multiplied = diff * diffEnds; - float u = (multiplied.X + multiplied.Y) / lengthSquared; - - if (u > 1) - { - u = 1; - } - else if (u < 0) - { - u = 0; - } - - Vector2 multipliedByU = diffEnds * u; - - Vector2 pointOnLine = start + multipliedByU; - - Vector2 d = pointOnLine - point; - - float dist = d.LengthSquared(); - - if (info.DistanceSquared > dist) - { - info.DistanceSquared = dist; - info.PointOnLine = pointOnLine; - return true; - } - - return false; - } - - /// - /// Contains information about the current point. - /// - private struct PointInfoInternal - { - /// - /// The distance squared. - /// - public float DistanceSquared; - - /// - /// The point on the current line. - /// - public Vector2 PointOnLine; - } - } -} diff --git a/src/ImageSharp.Drawing/Paths/LinearLineSegment.cs b/src/ImageSharp.Drawing/Paths/LinearLineSegment.cs deleted file mode 100644 index 6942ce5a5a..0000000000 --- a/src/ImageSharp.Drawing/Paths/LinearLineSegment.cs +++ /dev/null @@ -1,55 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Drawing.Paths -{ - using System.Linq; - using System.Numerics; - - /// - /// Represents a series of control points that will be joined by straight lines - /// - /// - public class LinearLineSegment : ILineSegment - { - /// - /// The collection of points. - /// - private readonly Vector2[] points; - - /// - /// Initializes a new instance of the class. - /// - /// The start. - /// The end. - public LinearLineSegment(Vector2 start, Vector2 end) - : this(new[] { start, end }) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The points. - public LinearLineSegment(params Vector2[] points) - { - Guard.NotNull(points, nameof(points)); - Guard.MustBeGreaterThanOrEqualTo(points.Count(), 2, nameof(points)); - - this.points = points; - } - - /// - /// Converts the into a simple linear path.. - /// - /// - /// Returns the current as simple linear path. - /// - public Vector2[] AsSimpleLinearPath() - { - return this.points; - } - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Paths/Path.cs b/src/ImageSharp.Drawing/Paths/Path.cs deleted file mode 100644 index eb2ab5e854..0000000000 --- a/src/ImageSharp.Drawing/Paths/Path.cs +++ /dev/null @@ -1,51 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Drawing.Paths -{ - using System.Numerics; - - /// - /// A aggregate of s making a single logical path - /// - /// - public class Path : IPath - { - /// - /// The inner path. - /// - private readonly InternalPath innerPath; - - /// - /// Initializes a new instance of the class. - /// - /// The segment. - public Path(params ILineSegment[] segment) - { - this.innerPath = new InternalPath(segment, false); - } - - /// - public RectangleF Bounds => this.innerPath.Bounds; - - /// - public bool IsClosed => false; - - /// - public float Length => this.innerPath.Length; - - /// - public Vector2[] AsSimpleLinearPath() - { - return this.innerPath.Points; - } - - /// - public PointInfo Distance(Vector2 point) - { - return this.innerPath.DistanceFromPath(point); - } - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Pens/Pen{TColor}.cs b/src/ImageSharp.Drawing/Pens/Pen{TColor}.cs index a08c7a7faf..bbb3c25597 100644 --- a/src/ImageSharp.Drawing/Pens/Pen{TColor}.cs +++ b/src/ImageSharp.Drawing/Pens/Pen{TColor}.cs @@ -9,7 +9,6 @@ namespace ImageSharp.Drawing.Pens using System.Numerics; using ImageSharp.Drawing.Brushes; - using ImageSharp.Drawing.Paths; using Processors; /// diff --git a/src/ImageSharp.Drawing/Pens/Processors/PenApplicator.cs b/src/ImageSharp.Drawing/Pens/Processors/PenApplicator.cs index e07b969495..222598d85f 100644 --- a/src/ImageSharp.Drawing/Pens/Processors/PenApplicator.cs +++ b/src/ImageSharp.Drawing/Pens/Processors/PenApplicator.cs @@ -6,7 +6,6 @@ namespace ImageSharp.Drawing.Processors { using System; - using Paths; /// /// primitive that converts a into a color and a distance away from the drawable part of the path. diff --git a/src/ImageSharp.Drawing/Paths/PointInfo.cs b/src/ImageSharp.Drawing/Pens/Processors/PointInfo.cs similarity index 54% rename from src/ImageSharp.Drawing/Paths/PointInfo.cs rename to src/ImageSharp.Drawing/Pens/Processors/PointInfo.cs index a2cbf1e73c..6fc78b09b6 100644 --- a/src/ImageSharp.Drawing/Paths/PointInfo.cs +++ b/src/ImageSharp.Drawing/Pens/Processors/PointInfo.cs @@ -3,33 +3,29 @@ // Licensed under the Apache License, Version 2.0. // -namespace ImageSharp.Drawing.Paths +namespace ImageSharp.Drawing.Processors { + using System; using System.Numerics; /// - /// Returns meta data about the nearest point on a path from a vector + /// Returns details about how far away from the inside of a shape and the color the pixel could be. /// public struct PointInfo { /// - /// The search point - /// - public Vector2 SearchPoint; - - /// - /// The distance along path is away from the start of the path + /// The distance along path /// public float DistanceAlongPath; /// - /// The distance is away from . + /// The distance from path /// public float DistanceFromPath; /// - /// The closest point to that lies on the path. + /// The search point /// - public Vector2 ClosestPointOnPath; + public Vector2 SearchPoint; } } diff --git a/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs b/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs index f7bdcb6895..d6b2f3eb28 100644 --- a/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs @@ -6,13 +6,13 @@ namespace ImageSharp.Drawing.Processors { using System; + using System.Collections.Generic; using System.Linq; using System.Numerics; using System.Threading.Tasks; using ImageSharp.Processing; - using Paths; using Pens; - using Shapes; + using SixLabors.Shapes; using Rectangle = ImageSharp.Rectangle; /// @@ -38,7 +38,7 @@ namespace ImageSharp.Drawing.Processors /// The shape. /// The options. public DrawPathProcessor(IPen pen, IShape shape, GraphicsOptions options) - : this(pen, shape.ToArray(), options) + : this(pen, shape.Paths, options) { } @@ -59,24 +59,24 @@ namespace ImageSharp.Drawing.Processors /// The pen. /// The paths. /// The options. - public DrawPathProcessor(IPen pen, IPath[] paths, GraphicsOptions options) + public DrawPathProcessor(IPen pen, IEnumerable paths, GraphicsOptions options) { - this.paths = paths; + this.paths = paths.ToArray(); this.pen = pen; this.options = options; - if (paths.Length != 1) + if (this.paths.Length != 1) { - var maxX = paths.Max(x => x.Bounds.Right); - var minX = paths.Min(x => x.Bounds.Left); - var maxY = paths.Max(x => x.Bounds.Bottom); - var minY = paths.Min(x => x.Bounds.Top); + var maxX = this.paths.Max(x => x.Bounds.Right); + var minX = this.paths.Min(x => x.Bounds.Left); + var maxY = this.paths.Max(x => x.Bounds.Bottom); + var minY = this.paths.Min(x => x.Bounds.Top); this.region = new RectangleF(minX, minY, maxX - minX, maxY - minY); } else { - this.region = paths[0].Bounds; + this.region = this.paths[0].Bounds.Convert(); } } @@ -119,7 +119,7 @@ namespace ImageSharp.Drawing.Processors minY, maxY, this.ParallelOptions, - y => + (int y) => { int offsetY = y - polyStartY; var currentPoint = default(Vector2); @@ -131,7 +131,7 @@ namespace ImageSharp.Drawing.Processors var dist = this.Closest(currentPoint); - var color = applicator.GetColor(dist); + var color = applicator.GetColor(dist.Convert()); var opacity = this.Opacity(color.DistanceFromElement); @@ -154,9 +154,9 @@ namespace ImageSharp.Drawing.Processors } } - private PointInfo Closest(Vector2 point) + private SixLabors.Shapes.PointInfo Closest(Vector2 point) { - PointInfo result = default(PointInfo); + SixLabors.Shapes.PointInfo result = default(SixLabors.Shapes.PointInfo); float distance = float.MaxValue; for (int i = 0; i < this.paths.Length; i++) diff --git a/src/ImageSharp.Drawing/Processors/FillShapeProcessor.cs b/src/ImageSharp.Drawing/Processors/FillShapeProcessor.cs index 4696b86132..14e99cba37 100644 --- a/src/ImageSharp.Drawing/Processors/FillShapeProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/FillShapeProcessor.cs @@ -11,7 +11,7 @@ namespace ImageSharp.Drawing.Processors using System.Threading.Tasks; using Drawing; using ImageSharp.Processing; - using Shapes; + using SixLabors.Shapes; using Rectangle = ImageSharp.Rectangle; /// @@ -44,7 +44,7 @@ namespace ImageSharp.Drawing.Processors /// protected override void OnApply(ImageBase source, Rectangle sourceRectangle) { - Rectangle rect = RectangleF.Ceiling(this.poly.Bounds); // rounds the points out away from the center + Rectangle rect = RectangleF.Ceiling(this.poly.Bounds.Convert()); // rounds the points out away from the center int polyStartY = rect.Y - DrawPadding; int polyEndY = rect.Bottom + DrawPadding; diff --git a/src/ImageSharp.Drawing/Processors/PointInfoExtensions.cs b/src/ImageSharp.Drawing/Processors/PointInfoExtensions.cs new file mode 100644 index 0000000000..939a718450 --- /dev/null +++ b/src/ImageSharp.Drawing/Processors/PointInfoExtensions.cs @@ -0,0 +1,37 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Drawing.Processors +{ + using System; + using System.Buffers; + using System.Numerics; + using System.Threading.Tasks; + using Drawing; + using ImageSharp.Processing; + using SixLabors.Shapes; + using Rectangle = ImageSharp.Rectangle; + + /// + /// Extension methods for helping to bridge Shaper2D and ImageSharp primitives. + /// + internal static class PointInfoExtensions + { + /// + /// Converts a to an ImageSharp . + /// + /// The source. + /// A representation of this + public static PointInfo Convert(this SixLabors.Shapes.PointInfo source) + { + return new PointInfo + { + DistanceAlongPath = source.DistanceAlongPath, + DistanceFromPath = source.DistanceFromPath, + SearchPoint = source.SearchPoint + }; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processors/RectangleExtensions.cs b/src/ImageSharp.Drawing/Processors/RectangleExtensions.cs new file mode 100644 index 0000000000..327b618fa4 --- /dev/null +++ b/src/ImageSharp.Drawing/Processors/RectangleExtensions.cs @@ -0,0 +1,32 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Drawing.Processors +{ + using System; + using System.Buffers; + using System.Numerics; + using System.Threading.Tasks; + using Drawing; + using ImageSharp.Processing; + using SixLabors.Shapes; + using Rectangle = ImageSharp.Rectangle; + + /// + /// Extension methods for helping to bridge Shaper2D and ImageSharp primitives. + /// + internal static class RectangleExtensions + { + /// + /// Converts a Shaper2D to an ImageSharp . + /// + /// The source. + /// A representation of this + public static RectangleF Convert(this SixLabors.Shapes.Rectangle source) + { + return new RectangleF(source.Location.X, source.Location.Y, source.Size.Width, source.Size.Height); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Shapes/BezierPolygon.cs b/src/ImageSharp.Drawing/Shapes/BezierPolygon.cs deleted file mode 100644 index b69ded2075..0000000000 --- a/src/ImageSharp.Drawing/Shapes/BezierPolygon.cs +++ /dev/null @@ -1,93 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Drawing.Shapes -{ - using System.Collections; - using System.Collections.Generic; - using System.Numerics; - using Paths; - - /// - /// Represents a polygon made up exclusivly of a single close cubic Bezier curve. - /// - public sealed class BezierPolygon : IShape - { - private Polygon innerPolygon; - - /// - /// Initializes a new instance of the class. - /// - /// The points. - public BezierPolygon(params Vector2[] points) - { - this.innerPolygon = new Polygon(new BezierLineSegment(points)); - } - - /// - /// Gets the bounding box of this shape. - /// - /// - /// The bounds. - /// - public RectangleF Bounds => this.innerPolygon.Bounds; - - /// - /// Gets the maximum number intersections that a shape can have when testing a line. - /// - /// - /// The maximum intersections. - /// - public int MaxIntersections => this.innerPolygon.MaxIntersections; - - /// - /// the distance of the point from the outline of the shape, if the value is negative it is inside the polygon bounds - /// - /// The point. - /// - /// The distance from the shape. - /// - public float Distance(Vector2 point) => this.innerPolygon.Distance(point); - - /// - /// Based on a line described by and - /// populate a buffer for all points on the polygon that the line intersects. - /// - /// The start point of the line. - /// The end point of the line. - /// The buffer that will be populated with intersections. - /// The count. - /// The offset. - /// - /// The number of intersections populated into the buffer. - /// - public int FindIntersections(Vector2 start, Vector2 end, Vector2[] buffer, int count, int offset) - { - return this.innerPolygon.FindIntersections(start, end, buffer, count, offset); - } - - /// - /// Returns an enumerator that iterates through the collection. - /// - /// - /// An enumerator that can be used to iterate through the collection. - /// - public IEnumerator GetEnumerator() - { - return this.innerPolygon.GetEnumerator(); - } - - /// - /// Returns an enumerator that iterates through a collection. - /// - /// - /// An object that can be used to iterate through the collection. - /// - IEnumerator IEnumerable.GetEnumerator() - { - return this.innerPolygon.GetEnumerator(); - } - } -} diff --git a/src/ImageSharp.Drawing/Shapes/ComplexPolygon.cs b/src/ImageSharp.Drawing/Shapes/ComplexPolygon.cs deleted file mode 100644 index fd709719c1..0000000000 --- a/src/ImageSharp.Drawing/Shapes/ComplexPolygon.cs +++ /dev/null @@ -1,246 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Drawing.Shapes -{ - using System; - using System.Collections; - using System.Collections.Generic; - using System.Linq; - using System.Numerics; - - using Paths; - using PolygonClipper; - - /// - /// Represents a complex polygon made up of one or more outline - /// polygons and one or more holes to punch out of them. - /// - /// - public sealed class ComplexPolygon : IShape - { - private const float ClipperScaleFactor = 100f; - private IShape[] shapes; - private IEnumerable paths; - - /// - /// Initializes a new instance of the class. - /// - /// The outline. - /// The holes. - public ComplexPolygon(IShape outline, params IShape[] holes) - : this(new[] { outline }, holes) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The outlines. - /// The holes. - public ComplexPolygon(IShape[] outlines, IShape[] holes) - { - Guard.NotNull(outlines, nameof(outlines)); - Guard.MustBeGreaterThanOrEqualTo(outlines.Length, 1, nameof(outlines)); - - this.MaxIntersections = this.FixAndSetShapes(outlines, holes); - - float minX = this.shapes.Min(x => x.Bounds.Left); - float maxX = this.shapes.Max(x => x.Bounds.Right); - float minY = this.shapes.Min(x => x.Bounds.Top); - float maxY = this.shapes.Max(x => x.Bounds.Bottom); - - this.Bounds = new RectangleF(minX, minY, maxX - minX, maxY - minY); - } - - /// - /// Gets the bounding box of this shape. - /// - /// - /// The bounds. - /// - public RectangleF Bounds { get; } - - /// - /// Gets the maximum number intersections that a shape can have when testing a line. - /// - /// - /// The maximum intersections. - /// - public int MaxIntersections { get; } - - /// - /// the distance of the point from the outline of the shape, if the value is negative it is inside the polygon bounds - /// - /// The point. - /// - /// Returns the distance from thr shape to the point - /// - /// - /// Due to the clipping we did during construction we know that out shapes do not overlap at there edges - /// therefore for apoint to be in more that one we must be in a hole of another, theoretically this could - /// then flip again to be in a outlin inside a hole inside an outline :) - /// - float IShape.Distance(Vector2 point) - { - float dist = float.MaxValue; - bool inside = false; - foreach (IShape shape in this.shapes) - { - float d = shape.Distance(point); - - if (d <= 0) - { - // we are inside a poly - d = -d; // flip the sign - inside ^= true; // flip the inside flag - } - - if (d < dist) - { - dist = d; - } - } - - if (inside) - { - return -dist; - } - - return dist; - } - - /// - /// Based on a line described by and - /// populate a buffer for all points on all the polygons, that make up this complex shape, - /// that the line intersects. - /// - /// The start point of the line. - /// The end point of the line. - /// The buffer that will be populated with intersections. - /// The count. - /// The offset. - /// - /// The number of intersections populated into the buffer. - /// - public int FindIntersections(Vector2 start, Vector2 end, Vector2[] buffer, int count, int offset) - { - int totalAdded = 0; - for (int i = 0; i < this.shapes.Length; i++) - { - int added = this.shapes[i].FindIntersections(start, end, buffer, count, offset); - count -= added; - offset += added; - totalAdded += added; - } - - return totalAdded; - } - - /// - /// Returns an enumerator that iterates through the collection. - /// - /// - /// An enumerator that can be used to iterate through the collection. - /// - public IEnumerator GetEnumerator() - { - return this.paths.GetEnumerator(); - } - - /// - /// Returns an enumerator that iterates through a collection. - /// - /// - /// An object that can be used to iterate through the collection. - /// - IEnumerator IEnumerable.GetEnumerator() - { - return this.GetEnumerator(); - } - - private void AddPoints(Clipper clipper, IShape shape, PolyType polyType) - { - // if the path is already the shape use it directly and skip the path loop. - if (shape is IPath) - { - clipper.AddPath( - (IPath)shape, - polyType); - } - else - { - foreach (IPath path in shape) - { - clipper.AddPath( - path, - polyType); - } - } - } - - private void AddPoints(Clipper clipper, IEnumerable shapes, PolyType polyType) - { - foreach (IShape shape in shapes) - { - this.AddPoints(clipper, shape, polyType); - } - } - - private void ExtractOutlines(PolyNode tree, List shapes, List paths) - { - if (tree.Contour.Any()) - { - // if the source path is set then we clipper retained the full path intact thus we can freely - // use it and get any shape optimisations that are availible. - if (tree.SourcePath != null) - { - shapes.Add((IShape)tree.SourcePath); - paths.Add(tree.SourcePath); - } - else - { - // convert the Clipper Contour from scaled ints back down to the origional size (this is going to be lossy but not significantly) - Polygon polygon = new Polygon(new Paths.LinearLineSegment(tree.Contour.ToArray())); - - shapes.Add(polygon); - paths.Add(polygon); - } - } - - foreach (PolyNode c in tree.Children) - { - this.ExtractOutlines(c, shapes, paths); - } - } - - private int FixAndSetShapes(IEnumerable outlines, IEnumerable holes) - { - Clipper clipper = new Clipper(); - - // add the outlines and the holes to clipper, scaling up from the float source to the int based system clipper uses - this.AddPoints(clipper, outlines, PolyType.Subject); - this.AddPoints(clipper, holes, PolyType.Clip); - - PolyTree tree = clipper.Execute(); - - List shapes = new List(); - List paths = new List(); - - // convert the 'tree' back to paths - this.ExtractOutlines(tree, shapes, paths); - this.shapes = shapes.ToArray(); - this.paths = paths.ToArray(); - - int intersections = 0; - foreach (IShape s in this.shapes) - { - intersections += s.MaxIntersections; - } - - return intersections; - } - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Shapes/IShape.cs b/src/ImageSharp.Drawing/Shapes/IShape.cs deleted file mode 100644 index 242e3bd8ec..0000000000 --- a/src/ImageSharp.Drawing/Shapes/IShape.cs +++ /dev/null @@ -1,56 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Drawing.Shapes -{ - using System.Collections.Generic; - using System.Numerics; - using Paths; - - /// - /// Represents a closed set of paths making up a single shape. - /// - public interface IShape : IEnumerable - { - /// - /// Gets the bounding box of this shape. - /// - /// - /// The bounds. - /// - RectangleF Bounds { get; } - - /// - /// Gets the maximum number intersections that a shape can have when testing a line. - /// - /// - /// The maximum intersections. - /// - int MaxIntersections { get; } - - /// - /// the distance of the point from the outline of the shape, if the value is negative it is inside the polygon bounds - /// - /// The point. - /// - /// Returns the distance from the shape to the point - /// - float Distance(Vector2 point); - - /// - /// Based on a line described by and - /// populate a buffer for all points on the polygon that the line intersects. - /// - /// The start point of the line. - /// The end point of the line. - /// The buffer that will be populated with intersections. - /// The count. - /// The offset. - /// - /// The number of intersections populated into the buffer. - /// - int FindIntersections(Vector2 start, Vector2 end, Vector2[] buffer, int count, int offset); - } -} diff --git a/src/ImageSharp.Drawing/Shapes/LinearPolygon.cs b/src/ImageSharp.Drawing/Shapes/LinearPolygon.cs deleted file mode 100644 index 30a30c20f9..0000000000 --- a/src/ImageSharp.Drawing/Shapes/LinearPolygon.cs +++ /dev/null @@ -1,93 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Drawing.Shapes -{ - using System.Collections; - using System.Collections.Generic; - using System.Numerics; - using Paths; - - /// - /// Represents a polygon made up exclusivly of a single Linear path. - /// - public sealed class LinearPolygon : IShape - { - private Polygon innerPolygon; - - /// - /// Initializes a new instance of the class. - /// - /// The points. - public LinearPolygon(params Vector2[] points) - { - this.innerPolygon = new Polygon(new LinearLineSegment(points)); - } - - /// - /// Gets the bounding box of this shape. - /// - /// - /// The bounds. - /// - public RectangleF Bounds => this.innerPolygon.Bounds; - - /// - /// Gets the maximum number intersections that a shape can have when testing a line. - /// - /// - /// The maximum intersections. - /// - public int MaxIntersections - { - get - { - return this.innerPolygon.MaxIntersections; - } - } - - /// - /// the distance of the point from the outline of the shape, if the value is negative it is inside the polygon bounds - /// - /// The point. - /// - /// Returns the distance from the shape to the point - /// - public float Distance(Vector2 point) => this.innerPolygon.Distance(point); - - /// - /// Based on a line described by and - /// populate a buffer for all points on the polygon that the line intersects. - /// - /// The start point of the line. - /// The end point of the line. - /// The buffer that will be populated with intersections. - /// The count. - /// The offset. - /// - /// The number of intersections populated into the buffer. - /// - public int FindIntersections(Vector2 start, Vector2 end, Vector2[] buffer, int count, int offset) - { - return this.innerPolygon.FindIntersections(start, end, buffer, count, offset); - } - - /// - /// Returns an enumerator that iterates through the collection. - /// - /// - /// An enumerator that can be used to iterate through the collection. - /// - public IEnumerator GetEnumerator() => this.innerPolygon.GetEnumerator(); - - /// - /// Returns an enumerator that iterates through a collection. - /// - /// - /// An object that can be used to iterate through the collection. - /// - IEnumerator IEnumerable.GetEnumerator() => this.innerPolygon.GetEnumerator(); - } -} diff --git a/src/ImageSharp.Drawing/Shapes/Polygon.cs b/src/ImageSharp.Drawing/Shapes/Polygon.cs deleted file mode 100644 index 86c3c9ee43..0000000000 --- a/src/ImageSharp.Drawing/Shapes/Polygon.cs +++ /dev/null @@ -1,156 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Drawing.Shapes -{ - using System.Collections; - using System.Collections.Generic; - using System.Numerics; - - using Paths; - - /// - /// A shape made up of a single path made up of one of more s - /// - public sealed class Polygon : IShape, IPath - { - private readonly InternalPath innerPath; - private readonly IEnumerable pathCollection; - - /// - /// Initializes a new instance of the class. - /// - /// The segments. - public Polygon(params ILineSegment[] segments) - { - this.innerPath = new InternalPath(segments, true); - this.pathCollection = new[] { this }; - } - - /// - /// Initializes a new instance of the class. - /// - /// The segment. - public Polygon(ILineSegment segment) - { - this.innerPath = new InternalPath(segment, true); - this.pathCollection = new[] { this }; - } - - /// - /// Gets the bounding box of this shape. - /// - /// - /// The bounds. - /// - public RectangleF Bounds => this.innerPath.Bounds; - - /// - /// Gets the length of the path - /// - /// - /// The length. - /// - public float Length => this.innerPath.Length; - - /// - /// Gets a value indicating whether this instance is closed. - /// - /// - /// true if this instance is closed; otherwise, false. - /// - public bool IsClosed => true; - - /// - /// Gets the maximum number intersections that a shape can have when testing a line. - /// - /// - /// The maximum intersections. - /// - public int MaxIntersections => this.innerPath.Points.Length; - - /// - /// the distance of the point from the outline of the shape, if the value is negative it is inside the polygon bounds - /// - /// The point. - /// - /// The distance of the point away from the shape - /// - public float Distance(Vector2 point) - { - bool isInside = this.innerPath.PointInPolygon(point); - - float distance = this.innerPath.DistanceFromPath(point).DistanceFromPath; - if (isInside) - { - return -distance; - } - - return distance; - } - - /// - /// Returns an enumerator that iterates through the collection. - /// - /// - /// An enumerator that can be used to iterate through the collection. - /// - public IEnumerator GetEnumerator() - { - return this.pathCollection.GetEnumerator(); - } - - /// - /// Returns an enumerator that iterates through a collection. - /// - /// - /// An object that can be used to iterate through the collection. - /// - IEnumerator IEnumerable.GetEnumerator() - { - return this.pathCollection.GetEnumerator(); - } - - /// - /// Calcualtes the distance along and away from the path for a specified point. - /// - /// The point along the path. - /// - /// distance metadata about the point. - /// - PointInfo IPath.Distance(Vector2 point) - { - return this.innerPath.DistanceFromPath(point); - } - - /// - /// Returns the current shape as a simple linear path. - /// - /// - /// Returns the current as simple linear path. - /// - public Vector2[] AsSimpleLinearPath() - { - return this.innerPath.Points; - } - - /// - /// Based on a line described by and - /// populate a buffer for all points on the polygon that the line intersects. - /// - /// The start point of the line. - /// The end point of the line. - /// The buffer that will be populated with intersections. - /// The count. - /// The offset. - /// - /// The number of intersections populated into the buffer. - /// - public int FindIntersections(Vector2 start, Vector2 end, Vector2[] buffer, int count, int offset) - { - return this.innerPath.FindIntersections(start, end, buffer, count, offset); - } - } -} diff --git a/src/ImageSharp.Drawing/Shapes/PolygonClipper/Clipper.cs b/src/ImageSharp.Drawing/Shapes/PolygonClipper/Clipper.cs deleted file mode 100644 index c13acec8f1..0000000000 --- a/src/ImageSharp.Drawing/Shapes/PolygonClipper/Clipper.cs +++ /dev/null @@ -1,3860 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Drawing.Shapes.PolygonClipper -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Numerics; - using System.Runtime.CompilerServices; - - using Paths; - - /// - /// Library to clip polygons. - /// - internal class Clipper - { - private const double HorizontalDeltaLimit = -3.4E+38; - private const int Skip = -2; - private const int Unassigned = -1; // InitOptions that can be passed to the constructor ... - - private Maxima maxima; - private TEdge sortedEdges; - private List intersectList; - private IComparer intersectNodeComparer = new IntersectNodeSort(); - private bool executeLocked; - - private List joins; - private List ghostJoins; - private bool usingPolyTree; - private LocalMinima minimaList = null; - private LocalMinima currentLM = null; - private List> edges = new List>(); - private Scanbeam scanbeam; - private List polyOuts; - private TEdge activeEdges; - - /// - /// Initializes a new instance of the class. - /// - public Clipper() - { - this.scanbeam = null; - this.maxima = null; - this.activeEdges = null; - this.sortedEdges = null; - this.intersectList = new List(); - this.executeLocked = false; - this.usingPolyTree = false; - this.polyOuts = new List(); - this.joins = new List(); - this.ghostJoins = new List(); - } - - /// - /// Node types - /// - private enum NodeType - { - /// - /// Any - /// - Any, - - /// - /// The open - /// - Open, - - /// - /// The closed - /// - Closed - } - - /// - /// Adds the path. - /// - /// The path. - /// Type of the poly. - /// True if the path was added. - /// AddPath: Open paths have been disabled. - public bool AddPath(IPath path, PolyType polyType) - { - var pg = path.AsSimpleLinearPath(); - - int highI = pg.Length - 1; - while (highI > 0 && (pg[highI] == pg[0])) - { - --highI; - } - - while (highI > 0 && (pg[highI] == pg[highI - 1])) - { - --highI; - } - - if (highI < 2) - { - return false; - } - - // create a new edge array ... - List edges = new List(highI + 1); - for (int i = 0; i <= highI; i++) - { - edges.Add(new TEdge() { SourcePath = path }); - } - - bool isFlat = true; - - // 1. Basic (first) edge initialization ... - edges[1].Curr = pg[1]; - - InitEdge(edges[0], edges[1], edges[highI], pg[0]); - InitEdge(edges[highI], edges[0], edges[highI - 1], pg[highI]); - for (int i = highI - 1; i >= 1; --i) - { - InitEdge(edges[i], edges[i + 1], edges[i - 1], pg[i]); - } - - TEdge eStart = edges[0]; - - // 2. Remove duplicate vertices, and (when closed) collinear edges ... - TEdge edge = eStart, eLoopStop = eStart; - while (true) - { - // nb: allows matching start and end points when not Closed ... - if (edge.Curr == edge.Next.Curr) - { - if (edge == edge.Next) - { - break; - } - - if (edge == eStart) - { - eStart = edge.Next; - } - - edge = RemoveEdge(edge); - eLoopStop = edge; - continue; - } - - if (edge.Prev == edge.Next) - { - break; // only two vertices - } - else if (SlopesEqual(edge.Prev.Curr, edge.Curr, edge.Next.Curr)) - { - // Collinear edges are allowed for open paths but in closed paths - // the default is to merge adjacent collinear edges into a single edge. - // However, if the PreserveCollinear property is enabled, only overlapping - // collinear edges (ie spikes) will be removed from closed paths. - if (edge == eStart) - { - eStart = edge.Next; - } - - edge = RemoveEdge(edge); - edge = edge.Prev; - eLoopStop = edge; - continue; - } - - edge = edge.Next; - if (edge == eLoopStop) - { - break; - } - } - - if (edge.Prev == edge.Next) - { - return false; - } - - // 3. Do second stage of edge initialization ... - edge = eStart; - do - { - this.InitEdge2(edge, polyType); - edge = edge.Next; - if (isFlat && edge.Curr.Y != eStart.Curr.Y) - { - isFlat = false; - } - } - while (edge != eStart); - - // 4. Finally, add edge bounds to LocalMinima list ... - // Totally flat paths must be handled differently when adding them - // to LocalMinima list to avoid endless loops etc ... - if (isFlat) - { - return false; - } - - this.edges.Add(edges); - bool leftBoundIsForward; - TEdge emIn = null; - - // workaround to avoid an endless loop in the while loop below when - // open paths have matching start and end points ... - if (edge.Prev.Bot == edge.Prev.Top) - { - edge = edge.Next; - } - - while (true) - { - edge = FindNextLocMin(edge); - if (edge == emIn) - { - break; - } - else if (emIn == null) - { - emIn = edge; - } - - // E and E.Prev now share a local minima (left aligned if horizontal). - // Compare their slopes to find which starts which bound ... - LocalMinima locMin = new LocalMinima(); - locMin.Next = null; - locMin.Y = edge.Bot.Y; - if (edge.Dx < edge.Prev.Dx) - { - locMin.LeftBound = edge.Prev; - locMin.RightBound = edge; - leftBoundIsForward = false; // Q.nextInLML = Q.prev - } - else - { - locMin.LeftBound = edge; - locMin.RightBound = edge.Prev; - leftBoundIsForward = true; // Q.nextInLML = Q.next - } - - locMin.LeftBound.Side = EdgeSide.Left; - locMin.RightBound.Side = EdgeSide.Right; - - if (locMin.LeftBound.Next == locMin.RightBound) - { - locMin.LeftBound.WindDelta = -1; - } - else - { - locMin.LeftBound.WindDelta = 1; - } - - locMin.RightBound.WindDelta = -locMin.LeftBound.WindDelta; - - edge = this.ProcessBound(locMin.LeftBound, leftBoundIsForward); - if (edge.OutIdx == Skip) - { - edge = this.ProcessBound(edge, leftBoundIsForward); - } - - TEdge edge2 = this.ProcessBound(locMin.RightBound, !leftBoundIsForward); - if (edge2.OutIdx == Skip) - { - edge2 = this.ProcessBound(edge2, !leftBoundIsForward); - } - - if (locMin.LeftBound.OutIdx == Skip) - { - locMin.LeftBound = null; - } - else if (locMin.RightBound.OutIdx == Skip) - { - locMin.RightBound = null; - } - - this.InsertLocalMinima(locMin); - if (!leftBoundIsForward) - { - edge = edge2; - } - } - - return true; - } - - /// - /// Executes the specified clip type. - /// - /// - /// Returns the polytree containing the converted polygons. - /// - public PolyTree Execute() - { - PolyTree polytree = new PolyTree(); - - if (this.executeLocked) - { - return null; - } - - this.executeLocked = true; - this.usingPolyTree = true; - bool succeeded; - try - { - succeeded = this.ExecuteInternal(); - - // build the return polygons ... - if (succeeded) - { - this.BuildResult2(polytree); - } - } - finally - { - this.DisposeAllPolyPts(); - this.executeLocked = false; - } - - if (succeeded) - { - return polytree; - } - - return null; - } - - private static float Round(double value) - { - return value < 0 ? (float)(value - 0.5) : (float)(value + 0.5); - } - - private static float TopX(TEdge edge, float currentY) - { - if (currentY == edge.Top.Y) - { - return edge.Top.X; - } - - return edge.Bot.X + Round(edge.Dx * (currentY - edge.Bot.Y)); - } - - private static void AddPolyNodeToPaths(PolyNode polynode, NodeType nt, List> paths) - { - bool match = true; - switch (nt) - { - case NodeType.Open: return; - case NodeType.Closed: match = !polynode.IsOpen; break; - default: break; - } - - if (polynode.Polygon.Count > 0 && match) - { - paths.Add(polynode.Polygon); - } - - foreach (PolyNode pn in polynode.Children) - { - AddPolyNodeToPaths(pn, nt, paths); - } - } - - private static double DistanceFromLineSqrd(Vector2 pt, Vector2 ln1, Vector2 ln2) - { - // The equation of a line in general form (Ax + By + C = 0) - // given 2 points (x¹,y¹) & (x²,y²) is ... - // (y¹ - y²)x + (x² - x¹)y + (y² - y¹)x¹ - (x² - x¹)y¹ = 0 - // A = (y¹ - y²); B = (x² - x¹); C = (y² - y¹)x¹ - (x² - x¹)y¹ - // perpendicular distance of point (x³,y³) = (Ax³ + By³ + C)/Sqrt(A² + B²) - // see http://en.wikipedia.org/wiki/Perpendicular_distance - double a = ln1.Y - ln2.Y; - double b = ln2.X - ln1.X; - double c = (a * ln1.X) + (b * ln1.Y); - c = (a * pt.X) + (b * pt.Y) - c; - return (c * c) / ((a * a) + (b * b)); - } - - private static bool SlopesNearCollinear(Vector2 pt1, Vector2 pt2, Vector2 pt3, double distSqrd) - { - // this function is more accurate when the point that's GEOMETRICALLY - // between the other 2 points is the one that's tested for distance. - // nb: with 'spikes', either pt1 or pt3 is geometrically between the other pts - if (Math.Abs(pt1.X - pt2.X) > Math.Abs(pt1.Y - pt2.Y)) - { - if ((pt1.X > pt2.X) == (pt1.X < pt3.X)) - { - return DistanceFromLineSqrd(pt1, pt2, pt3) < distSqrd; - } - else if ((pt2.X > pt1.X) == (pt2.X < pt3.X)) - { - return DistanceFromLineSqrd(pt2, pt1, pt3) < distSqrd; - } - else - { - return DistanceFromLineSqrd(pt3, pt1, pt2) < distSqrd; - } - } - else - { - if ((pt1.Y > pt2.Y) == (pt1.Y < pt3.Y)) - { - return DistanceFromLineSqrd(pt1, pt2, pt3) < distSqrd; - } - else if ((pt2.Y > pt1.Y) == (pt2.Y < pt3.Y)) - { - return DistanceFromLineSqrd(pt2, pt1, pt3) < distSqrd; - } - else - { - return DistanceFromLineSqrd(pt3, pt1, pt2) < distSqrd; - } - } - } - - private static bool PointsAreClose(Vector2 pt1, Vector2 pt2, double distSqrd) - { - return Vector2.DistanceSquared(pt1, pt2) <= distSqrd; - } - - private static OutPt ExcludeOp(OutPt op) - { - OutPt result = op.Prev; - result.Next = op.Next; - op.Next.Prev = result; - result.Idx = 0; - return result; - } - - private static void FixHoleLinkage(OutRec outRec) - { - // skip if an outermost polygon or - // already already points to the correct FirstLeft ... - if (outRec.FirstLeft == null || - (outRec.IsHole != outRec.FirstLeft.IsHole && - outRec.FirstLeft.Pts != null)) - { - return; - } - - OutRec orfl = outRec.FirstLeft; - while (orfl != null && ((orfl.IsHole == outRec.IsHole) || orfl.Pts == null)) - { - orfl = orfl.FirstLeft; - } - - outRec.FirstLeft = orfl; - } - - // See "The Point in Polygon Problem for Arbitrary Polygons" by Hormann & Agathos - // http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.88.5498&rep=rep1&type=pdf - private static int PointInPolygon(Vector2 pt, OutPt op) - { - // returns 0 if false, +1 if true, -1 if pt ON polygon boundary - int result = 0; - OutPt startOp = op; - float ptx = pt.X, pty = pt.Y; - float poly0x = op.Pt.X, poly0y = op.Pt.Y; - do - { - op = op.Next; - float poly1x = op.Pt.X, poly1y = op.Pt.Y; - - if (poly1y == pty) - { - if ((poly1x == ptx) || (poly0y == pty && - ((poly1x > ptx) == (poly0x < ptx)))) - { - return -1; - } - } - - if ((poly0y < pty) != (poly1y < pty)) - { - if (poly0x >= ptx) - { - if (poly1x > ptx) - { - result = 1 - result; - } - else - { - double d = (double)((poly0x - ptx) * (poly1y - pty)) - - (double)((poly1x - ptx) * (poly0y - pty)); - if (d == 0) - { - return -1; - } - - if ((d > 0) == (poly1y > poly0y)) - { - result = 1 - result; - } - } - } - else - { - if (poly1x > ptx) - { - double d = (double)((poly0x - ptx) * (poly1y - pty)) - (double)((poly1x - ptx) * (poly0y - pty)); - if (d == 0) - { - return -1; - } - - if ((d > 0) == (poly1y > poly0y)) - { - result = 1 - result; - } - } - } - } - - poly0x = poly1x; - poly0y = poly1y; - } - while (startOp != op); - - return result; - } - - private static bool Poly2ContainsPoly1(OutPt outPt1, OutPt outPt2) - { - OutPt op = outPt1; - do - { - // nb: PointInPolygon returns 0 if false, +1 if true, -1 if pt on polygon - int res = PointInPolygon(op.Pt, outPt2); - if (res >= 0) - { - return res > 0; - } - - op = op.Next; - } - while (op != outPt1); - return true; - } - - private static void SwapSides(TEdge edge1, TEdge edge2) - { - EdgeSide side = edge1.Side; - edge1.Side = edge2.Side; - edge2.Side = side; - } - - private static void SwapPolyIndexes(TEdge edge1, TEdge edge2) - { - int outIdx = edge1.OutIdx; - edge1.OutIdx = edge2.OutIdx; - edge2.OutIdx = outIdx; - } - - private static double GetDx(Vector2 pt1, Vector2 pt2) - { - if (pt1.Y == pt2.Y) - { - return HorizontalDeltaLimit; - } - else - { - return (double)(pt2.X - pt1.X) / (pt2.Y - pt1.Y); - } - } - - private static bool HorzSegmentsOverlap(float seg1a, float seg1b, float seg2a, float seg2b) - { - if (seg1a > seg1b) - { - Swap(ref seg1a, ref seg1b); - } - - if (seg2a > seg2b) - { - Swap(ref seg2a, ref seg2b); - } - - return (seg1a < seg2b) && (seg2a < seg1b); - } - - private static TEdge FindNextLocMin(TEdge edge) - { - TEdge edge2; - while (true) - { - while (edge.Bot != edge.Prev.Bot || edge.Curr == edge.Top) - { - edge = edge.Next; - } - - if (edge.Dx != HorizontalDeltaLimit && edge.Prev.Dx != HorizontalDeltaLimit) - { - break; - } - - while (edge.Prev.Dx == HorizontalDeltaLimit) - { - edge = edge.Prev; - } - - edge2 = edge; - while (edge.Dx == HorizontalDeltaLimit) - { - edge = edge.Next; - } - - if (edge.Top.Y == edge.Prev.Bot.Y) - { - continue; // ie just an intermediate horz. - } - - if (edge2.Prev.Bot.X < edge.Bot.X) - { - edge = edge2; - } - - break; - } - - return edge; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void Swap(ref float val1, ref float val2) - { - float tmp = val1; - val1 = val2; - val2 = tmp; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool IsHorizontal(TEdge e) - { - return e.Delta.Y == 0; - } - - private static bool SlopesEqual(TEdge e1, TEdge e2) - { - return e1.Delta.Y * e2.Delta.X == e1.Delta.X * e2.Delta.Y; - } - - private static bool SlopesEqual(Vector2 pt1, Vector2 pt2, Vector2 pt3) - { - var dif12 = pt1 - pt2; - var dif23 = pt2 - pt3; - return (dif12.Y * dif23.X) - (dif12.X * dif23.Y) == 0; - } - - private static bool SlopesEqual(Vector2 pt1, Vector2 pt2, Vector2 pt3, Vector2 pt4) - { - var dif12 = pt1 - pt2; - var dif34 = pt3 - pt4; - - return (dif12.Y * dif34.X) - (dif12.X * dif34.Y) == 0; - } - - private static void InitEdge(TEdge e, TEdge eNext, TEdge ePrev, Vector2 pt) - { - e.Next = eNext; - e.Prev = ePrev; - e.Curr = pt; - e.OutIdx = Unassigned; - } - - private static OutRec ParseFirstLeft(OutRec firstLeft) - { - while (firstLeft != null && firstLeft.Pts == null) - { - firstLeft = firstLeft.FirstLeft; - } - - return firstLeft; - } - - private static bool Pt2IsBetweenPt1AndPt3(Vector2 pt1, Vector2 pt2, Vector2 pt3) - { - if ((pt1 == pt3) || (pt1 == pt2) || (pt3 == pt2)) - { - return false; - } - else if (pt1.X != pt3.X) - { - return (pt2.X > pt1.X) == (pt2.X < pt3.X); - } - else - { - return (pt2.Y > pt1.Y) == (pt2.Y < pt3.Y); - } - } - - private static TEdge RemoveEdge(TEdge e) - { - // removes e from double_linked_list (but without removing from memory) - e.Prev.Next = e.Next; - e.Next.Prev = e.Prev; - TEdge result = e.Next; - e.Prev = null; // flag as removed (see ClipperBase.Clear) - return result; - } - - private static void ReverseHorizontal(TEdge e) - { - // swap horizontal edges' top and bottom x's so they follow the natural - // progression of the bounds - ie so their xbots will align with the - // adjoining lower edge. [Helpful in the ProcessHorizontal() method.] - Swap(ref e.Top.X, ref e.Bot.X); - } - - private void InsertMaxima(float x) - { - // double-linked list: sorted ascending, ignoring dups. - Maxima newMax = new Maxima(); - newMax.X = x; - if (this.maxima == null) - { - this.maxima = newMax; - this.maxima.Next = null; - this.maxima.Prev = null; - } - else if (x < this.maxima.X) - { - newMax.Next = this.maxima; - newMax.Prev = null; - this.maxima = newMax; - } - else - { - Maxima m = this.maxima; - while (m.Next != null && (x >= m.Next.X)) - { - m = m.Next; - } - - if (x == m.X) - { - return; // ie ignores duplicates (& CG to clean up newMax) - } - - // insert newMax between m and m.Next ... - newMax.Next = m.Next; - newMax.Prev = m; - if (m.Next != null) - { - m.Next.Prev = newMax; - } - - m.Next = newMax; - } - } - - private bool ExecuteInternal() - { - try - { - this.Reset(); - this.sortedEdges = null; - this.maxima = null; - - float botY, topY; - if (!this.PopScanbeam(out botY)) - { - return false; - } - - this.InsertLocalMinimaIntoAEL(botY); - while (this.PopScanbeam(out topY) || this.LocalMinimaPending()) - { - this.ProcessHorizontals(); - this.ghostJoins.Clear(); - if (!this.ProcessIntersections(topY)) - { - return false; - } - - this.ProcessEdgesAtTopOfScanbeam(topY); - botY = topY; - this.InsertLocalMinimaIntoAEL(botY); - } - - // fix orientations ... - foreach (OutRec outRec in this.polyOuts) - { - if (outRec.Pts == null || outRec.IsOpen) - { - continue; - } - } - - this.JoinCommonEdges(); - - foreach (OutRec outRec in this.polyOuts) - { - if (outRec.Pts == null) - { - continue; - } - else if (outRec.IsOpen) - { - this.FixupOutPolyline(outRec); - } - else - { - this.FixupOutPolygon(outRec); - } - } - - return true; - } - finally - { - this.joins.Clear(); - this.ghostJoins.Clear(); - } - } - - private void DisposeAllPolyPts() - { - for (int i = 0; i < this.polyOuts.Count; ++i) - { - this.DisposeOutRec(i); - } - - this.polyOuts.Clear(); - } - - private void AddJoin(OutPt op1, OutPt op2, Vector2 offPt) - { - Join j = new Join(); - j.OutPt1 = op1; - j.OutPt2 = op2; - j.OffPt = offPt; - this.joins.Add(j); - } - - private void AddGhostJoin(OutPt op, Vector2 offPt) - { - Join j = new Join(); - j.OutPt1 = op; - j.OffPt = offPt; - this.ghostJoins.Add(j); - } - - private void InsertLocalMinimaIntoAEL(float botY) - { - LocalMinima lm; - while (this.PopLocalMinima(botY, out lm)) - { - TEdge lb = lm.LeftBound; - TEdge rb = lm.RightBound; - - OutPt op1 = null; - if (lb == null) - { - this.InsertEdgeIntoAEL(rb, null); - this.SetWindingCount(rb); - if (this.IsContributing(rb)) - { - op1 = this.AddOutPt(rb, rb.Bot); - } - } - else if (rb == null) - { - this.InsertEdgeIntoAEL(lb, null); - this.SetWindingCount(lb); - if (this.IsContributing(lb)) - { - op1 = this.AddOutPt(lb, lb.Bot); - } - - this.InsertScanbeam(lb.Top.Y); - } - else - { - this.InsertEdgeIntoAEL(lb, null); - this.InsertEdgeIntoAEL(rb, lb); - this.SetWindingCount(lb); - rb.WindCnt = lb.WindCnt; - rb.WindCnt2 = lb.WindCnt2; - if (this.IsContributing(lb)) - { - op1 = this.AddLocalMinPoly(lb, rb, lb.Bot); - } - - this.InsertScanbeam(lb.Top.Y); - } - - if (rb != null) - { - if (IsHorizontal(rb)) - { - if (rb.NextInLML != null) - { - this.InsertScanbeam(rb.NextInLML.Top.Y); - } - - this.AddEdgeToSEL(rb); - } - else - { - this.InsertScanbeam(rb.Top.Y); - } - } - - if (lb == null || rb == null) - { - continue; - } - - // if output polygons share an Edge with a horizontal rb, they'll need joining later ... - if (op1 != null && IsHorizontal(rb) && - this.ghostJoins.Count > 0 && rb.WindDelta != 0) - { - for (int i = 0; i < this.ghostJoins.Count; i++) - { - // if the horizontal Rb and a 'ghost' horizontal overlap, then convert - // the 'ghost' join to a real join ready for later ... - Join j = this.ghostJoins[i]; - if (HorzSegmentsOverlap(j.OutPt1.Pt.X, j.OffPt.X, rb.Bot.X, rb.Top.X)) - { - this.AddJoin(j.OutPt1, op1, j.OffPt); - } - } - } - - if (lb.OutIdx >= 0 && lb.PrevInAEL != null && - lb.PrevInAEL.Curr.X == lb.Bot.X && - lb.PrevInAEL.OutIdx >= 0 && - SlopesEqual(lb.PrevInAEL.Curr, lb.PrevInAEL.Top, lb.Curr, lb.Top) && - lb.WindDelta != 0 && lb.PrevInAEL.WindDelta != 0) - { - OutPt op2 = this.AddOutPt(lb.PrevInAEL, lb.Bot); - this.AddJoin(op1, op2, lb.Top); - } - - if (lb.NextInAEL != rb) - { - if (rb.OutIdx >= 0 && rb.PrevInAEL.OutIdx >= 0 && - SlopesEqual(rb.PrevInAEL.Curr, rb.PrevInAEL.Top, rb.Curr, rb.Top) && - rb.WindDelta != 0 && rb.PrevInAEL.WindDelta != 0) - { - OutPt op2 = this.AddOutPt(rb.PrevInAEL, rb.Bot); - this.AddJoin(op1, op2, rb.Top); - } - - TEdge e = lb.NextInAEL; - if (e != null) - { - while (e != rb) - { - // nb: For calculating winding counts etc, IntersectEdges() assumes - // that param1 will be to the right of param2 ABOVE the intersection ... - this.IntersectEdges(rb, e, lb.Curr); // order important here - e = e.NextInAEL; - } - } - } - } - } - - private void InsertEdgeIntoAEL(TEdge edge, TEdge startEdge) - { - if (this.activeEdges == null) - { - edge.PrevInAEL = null; - edge.NextInAEL = null; - this.activeEdges = edge; - } - else if (startEdge == null && this.E2InsertsBeforeE1(this.activeEdges, edge)) - { - edge.PrevInAEL = null; - edge.NextInAEL = this.activeEdges; - this.activeEdges.PrevInAEL = edge; - this.activeEdges = edge; - } - else - { - if (startEdge == null) - { - startEdge = this.activeEdges; - } - - while (startEdge.NextInAEL != null && - !this.E2InsertsBeforeE1(startEdge.NextInAEL, edge)) - { - startEdge = startEdge.NextInAEL; - } - - edge.NextInAEL = startEdge.NextInAEL; - if (startEdge.NextInAEL != null) - { - startEdge.NextInAEL.PrevInAEL = edge; - } - - edge.PrevInAEL = startEdge; - startEdge.NextInAEL = edge; - } - } - - private bool E2InsertsBeforeE1(TEdge e1, TEdge e2) - { - if (e2.Curr.X == e1.Curr.X) - { - if (e2.Top.Y > e1.Top.Y) - { - return e2.Top.X < TopX(e1, e2.Top.Y); - } - else - { - return e1.Top.X > TopX(e2, e1.Top.Y); - } - } - else - { - return e2.Curr.X < e1.Curr.X; - } - } - - private bool IsContributing(TEdge edge) - { - // return false if a subj line has been flagged as inside a subj polygon - if (edge.WindDelta == 0 && edge.WindCnt != 1) - { - return false; - } - - if (edge.PolyTyp == PolyType.Subject) - { - return edge.WindCnt2 == 0; - } - else - { - return edge.WindCnt2 != 0; - } - } - - private void SetWindingCount(TEdge edge) - { - TEdge e = edge.PrevInAEL; - - // find the edge of the same polytype that immediately preceeds 'edge' in AEL - while (e != null && ((e.PolyTyp != edge.PolyTyp) || (e.WindDelta == 0))) - { - e = e.PrevInAEL; - } - - if (e == null) - { - if (edge.WindDelta == 0) - { - edge.WindCnt = 1; - } - else - { - edge.WindCnt = edge.WindDelta; - } - - edge.WindCnt2 = 0; - e = this.activeEdges; // ie get ready to calc WindCnt2 - } - else if (edge.WindDelta == 0) - { - edge.WindCnt = 1; - edge.WindCnt2 = e.WindCnt2; - e = e.NextInAEL; // ie get ready to calc WindCnt2 - } - else - { - // EvenOdd filling ... - if (edge.WindDelta == 0) - { - // are we inside a subj polygon ... - bool inside = true; - TEdge e2 = e.PrevInAEL; - while (e2 != null) - { - if (e2.PolyTyp == e.PolyTyp && e2.WindDelta != 0) - { - inside = !inside; - } - - e2 = e2.PrevInAEL; - } - - edge.WindCnt = inside ? 0 : 1; - } - else - { - edge.WindCnt = edge.WindDelta; - } - - edge.WindCnt2 = e.WindCnt2; - e = e.NextInAEL; // ie get ready to calc WindCnt2 - } - - // update WindCnt2 ... - // EvenOdd filling ... - while (e != edge) - { - if (e.WindDelta != 0) - { - edge.WindCnt2 = edge.WindCnt2 == 0 ? 1 : 0; - } - - e = e.NextInAEL; - } - } - - private void AddEdgeToSEL(TEdge edge) - { - // SEL pointers in PEdge are use to build transient lists of horizontal edges. - // However, since we don't need to worry about processing order, all additions - // are made to the front of the list ... - if (this.sortedEdges == null) - { - this.sortedEdges = edge; - edge.PrevInSEL = null; - edge.NextInSEL = null; - } - else - { - edge.NextInSEL = this.sortedEdges; - edge.PrevInSEL = null; - this.sortedEdges.PrevInSEL = edge; - this.sortedEdges = edge; - } - } - - private bool PopEdgeFromSEL(out TEdge e) - { - // Pop edge from front of SEL (ie SEL is a FILO list) - e = this.sortedEdges; - if (e == null) - { - return false; - } - - TEdge oldE = e; - this.sortedEdges = e.NextInSEL; - if (this.sortedEdges != null) - { - this.sortedEdges.PrevInSEL = null; - } - - oldE.NextInSEL = null; - oldE.PrevInSEL = null; - return true; - } - - private void CopyAELToSEL() - { - TEdge e = this.activeEdges; - this.sortedEdges = e; - while (e != null) - { - e.PrevInSEL = e.PrevInAEL; - e.NextInSEL = e.NextInAEL; - e = e.NextInAEL; - } - } - - private void SwapPositionsInSEL(TEdge edge1, TEdge edge2) - { - if (edge1.NextInSEL == null && edge1.PrevInSEL == null) - { - return; - } - - if (edge2.NextInSEL == null && edge2.PrevInSEL == null) - { - return; - } - - if (edge1.NextInSEL == edge2) - { - TEdge next = edge2.NextInSEL; - if (next != null) - { - next.PrevInSEL = edge1; - } - - TEdge prev = edge1.PrevInSEL; - if (prev != null) - { - prev.NextInSEL = edge2; - } - - edge2.PrevInSEL = prev; - edge2.NextInSEL = edge1; - edge1.PrevInSEL = edge2; - edge1.NextInSEL = next; - } - else if (edge2.NextInSEL == edge1) - { - TEdge next = edge1.NextInSEL; - if (next != null) - { - next.PrevInSEL = edge2; - } - - TEdge prev = edge2.PrevInSEL; - if (prev != null) - { - prev.NextInSEL = edge1; - } - - edge1.PrevInSEL = prev; - edge1.NextInSEL = edge2; - edge2.PrevInSEL = edge1; - edge2.NextInSEL = next; - } - else - { - TEdge next = edge1.NextInSEL; - TEdge prev = edge1.PrevInSEL; - edge1.NextInSEL = edge2.NextInSEL; - if (edge1.NextInSEL != null) - { - edge1.NextInSEL.PrevInSEL = edge1; - } - - edge1.PrevInSEL = edge2.PrevInSEL; - if (edge1.PrevInSEL != null) - { - edge1.PrevInSEL.NextInSEL = edge1; - } - - edge2.NextInSEL = next; - if (edge2.NextInSEL != null) - { - edge2.NextInSEL.PrevInSEL = edge2; - } - - edge2.PrevInSEL = prev; - if (edge2.PrevInSEL != null) - { - edge2.PrevInSEL.NextInSEL = edge2; - } - } - - if (edge1.PrevInSEL == null) - { - this.sortedEdges = edge1; - } - else if (edge2.PrevInSEL == null) - { - this.sortedEdges = edge2; - } - } - - private void AddLocalMaxPoly(TEdge e1, TEdge e2, Vector2 pt) - { - this.AddOutPt(e1, pt); - if (e2.WindDelta == 0) - { - this.AddOutPt(e2, pt); - } - - if (e1.OutIdx == e2.OutIdx) - { - e1.OutIdx = Unassigned; - e2.OutIdx = Unassigned; - } - else if (e1.OutIdx < e2.OutIdx) - { - this.AppendPolygon(e1, e2); - } - else - { - this.AppendPolygon(e2, e1); - } - } - - private OutPt AddLocalMinPoly(TEdge e1, TEdge e2, Vector2 pt) - { - OutPt result; - TEdge e, prevE; - if (IsHorizontal(e2) || (e1.Dx > e2.Dx)) - { - result = this.AddOutPt(e1, pt); - e2.OutIdx = e1.OutIdx; - e1.Side = EdgeSide.Left; - e2.Side = EdgeSide.Right; - e = e1; - if (e.PrevInAEL == e2) - { - prevE = e2.PrevInAEL; - } - else - { - prevE = e.PrevInAEL; - } - } - else - { - result = this.AddOutPt(e2, pt); - e1.OutIdx = e2.OutIdx; - e1.Side = EdgeSide.Right; - e2.Side = EdgeSide.Left; - e = e2; - if (e.PrevInAEL == e1) - { - prevE = e1.PrevInAEL; - } - else - { - prevE = e.PrevInAEL; - } - } - - if (prevE != null && prevE.OutIdx >= 0) - { - float xPrev = TopX(prevE, pt.Y); - float xE = TopX(e, pt.Y); - if ((xPrev == xE) && - (e.WindDelta != 0) && - (prevE.WindDelta != 0) && - SlopesEqual(new Vector2(xPrev, pt.Y), prevE.Top, new Vector2(xE, pt.Y), e.Top)) - { - OutPt outPt = this.AddOutPt(prevE, pt); - this.AddJoin(result, outPt, e.Top); - } - } - - return result; - } - - private OutPt AddOutPt(TEdge e, Vector2 pt) - { - if (e.OutIdx < 0) - { - OutRec outRec = this.CreateOutRec(); - outRec.SourcePath = e.SourcePath; // copy source from edge to outrec - outRec.IsOpen = e.WindDelta == 0; - OutPt newOp = new OutPt(); - outRec.Pts = newOp; - newOp.Idx = outRec.Idx; - newOp.Pt = pt; - newOp.Next = newOp; - newOp.Prev = newOp; - if (!outRec.IsOpen) - { - this.SetHoleState(e, outRec); - } - - e.OutIdx = outRec.Idx; // nb: do this after SetZ ! - return newOp; - } - else - { - OutRec outRec = this.polyOuts[e.OutIdx]; - - if (outRec.SourcePath != e.SourcePath) - { - // this edge was from a different/unknown source - outRec.SourcePath = null; // drop source form output - } - - // OutRec.Pts is the 'Left-most' point & OutRec.Pts.Prev is the 'Right-most' - OutPt op = outRec.Pts; - bool toFront = e.Side == EdgeSide.Left; - if (toFront && pt == op.Pt) - { - return op; - } - else if (!toFront && pt == op.Prev.Pt) - { - return op.Prev; - } - - // do we need to move the source to the point??? - OutPt newOp = new OutPt(); - newOp.Idx = outRec.Idx; - newOp.Pt = pt; - newOp.Next = op; - newOp.Prev = op.Prev; - newOp.Prev.Next = newOp; - op.Prev = newOp; - if (toFront) - { - outRec.Pts = newOp; - } - - return newOp; - } - } - - private OutPt GetLastOutPt(TEdge e) - { - OutRec outRec = this.polyOuts[e.OutIdx]; - if (e.Side == EdgeSide.Left) - { - return outRec.Pts; - } - else - { - return outRec.Pts.Prev; - } - } - - private void SetHoleState(TEdge e, OutRec outRec) - { - TEdge e2 = e.PrevInAEL; - TEdge eTmp = null; - while (e2 != null) - { - if (e2.OutIdx >= 0 && e2.WindDelta != 0) - { - if (eTmp == null) - { - eTmp = e2; - } - else if (eTmp.OutIdx == e2.OutIdx) - { - eTmp = null; // paired - } - } - - e2 = e2.PrevInAEL; - } - - if (eTmp == null) - { - outRec.FirstLeft = null; - outRec.IsHole = false; - } - else - { - outRec.FirstLeft = this.polyOuts[eTmp.OutIdx]; - outRec.IsHole = !outRec.FirstLeft.IsHole; - } - } - - private bool FirstIsBottomPt(OutPt btmPt1, OutPt btmPt2) - { - OutPt p = btmPt1.Prev; - while ((p.Pt == btmPt1.Pt) && (p != btmPt1)) - { - p = p.Prev; - } - - double dx1p = Math.Abs(GetDx(btmPt1.Pt, p.Pt)); - p = btmPt1.Next; - while ((p.Pt == btmPt1.Pt) && (p != btmPt1)) - { - p = p.Next; - } - - double dx1n = Math.Abs(GetDx(btmPt1.Pt, p.Pt)); - - p = btmPt2.Prev; - while ((p.Pt == btmPt2.Pt) && (p != btmPt2)) - { - p = p.Prev; - } - - double dx2p = Math.Abs(GetDx(btmPt2.Pt, p.Pt)); - p = btmPt2.Next; - while ((p.Pt == btmPt2.Pt) && (p != btmPt2)) - { - p = p.Next; - } - - double dx2n = Math.Abs(GetDx(btmPt2.Pt, p.Pt)); - - if (Math.Max(dx1p, dx1n) == Math.Max(dx2p, dx2n) && - Math.Min(dx1p, dx1n) == Math.Min(dx2p, dx2n)) - { - return this.Area(btmPt1) > 0; // if otherwise identical use orientation - } - else - { - return (dx1p >= dx2p && dx1p >= dx2n) || (dx1n >= dx2p && dx1n >= dx2n); - } - } - - private OutPt GetBottomPt(OutPt pp) - { - OutPt dups = null; - OutPt p = pp.Next; - while (p != pp) - { - if (p.Pt.Y > pp.Pt.Y) - { - pp = p; - dups = null; - } - else if (p.Pt.Y == pp.Pt.Y && p.Pt.X <= pp.Pt.X) - { - if (p.Pt.X < pp.Pt.X) - { - dups = null; - pp = p; - } - else - { - if (p.Next != pp && p.Prev != pp) - { - dups = p; - } - } - } - - p = p.Next; - } - - if (dups != null) - { - // there appears to be at least 2 vertices at bottomPt so ... - while (dups != p) - { - if (!this.FirstIsBottomPt(p, dups)) - { - pp = dups; - } - - dups = dups.Next; - while (dups.Pt != pp.Pt) - { - dups = dups.Next; - } - } - } - - return pp; - } - - private OutRec GetLowermostRec(OutRec outRec1, OutRec outRec2) - { - // work out which polygon fragment has the correct hole state ... - if (outRec1.BottomPt == null) - { - outRec1.BottomPt = this.GetBottomPt(outRec1.Pts); - } - - if (outRec2.BottomPt == null) - { - outRec2.BottomPt = this.GetBottomPt(outRec2.Pts); - } - - OutPt bPt1 = outRec1.BottomPt; - OutPt bPt2 = outRec2.BottomPt; - if (bPt1.Pt.Y > bPt2.Pt.Y) - { - return outRec1; - } - else if (bPt1.Pt.Y < bPt2.Pt.Y) - { - return outRec2; - } - else if (bPt1.Pt.X < bPt2.Pt.X) - { - return outRec1; - } - else if (bPt1.Pt.X > bPt2.Pt.X) - { - return outRec2; - } - else if (bPt1.Next == bPt1) - { - return outRec2; - } - else if (bPt2.Next == bPt2) - { - return outRec1; - } - else if (this.FirstIsBottomPt(bPt1, bPt2)) - { - return outRec1; - } - else - { - return outRec2; - } - } - - private bool OutRec1RightOfOutRec2(OutRec outRec1, OutRec outRec2) - { - do - { - outRec1 = outRec1.FirstLeft; - if (outRec1 == outRec2) - { - return true; - } - } - while (outRec1 != null); - - return false; - } - - private OutRec GetOutRec(int idx) - { - OutRec outrec = this.polyOuts[idx]; - while (outrec != this.polyOuts[outrec.Idx]) - { - outrec = this.polyOuts[outrec.Idx]; - } - - return outrec; - } - - private void AppendPolygon(TEdge e1, TEdge e2) - { - OutRec outRec1 = this.polyOuts[e1.OutIdx]; - OutRec outRec2 = this.polyOuts[e2.OutIdx]; - - OutRec holeStateRec; - if (this.OutRec1RightOfOutRec2(outRec1, outRec2)) - { - holeStateRec = outRec2; - } - else if (this.OutRec1RightOfOutRec2(outRec2, outRec1)) - { - holeStateRec = outRec1; - } - else - { - holeStateRec = this.GetLowermostRec(outRec1, outRec2); - } - - // get the start and ends of both output polygons and - // join E2 poly onto E1 poly and delete pointers to E2 ... - OutPt p1_lft = outRec1.Pts; - OutPt p1_rt = p1_lft.Prev; - OutPt p2_lft = outRec2.Pts; - OutPt p2_rt = p2_lft.Prev; - - // join e2 poly onto e1 poly and delete pointers to e2 ... - if (e1.Side == EdgeSide.Left) - { - if (e2.Side == EdgeSide.Left) - { - // z y x a b c - this.ReversePolyPtLinks(p2_lft); - p2_lft.Next = p1_lft; - p1_lft.Prev = p2_lft; - p1_rt.Next = p2_rt; - p2_rt.Prev = p1_rt; - outRec1.Pts = p2_rt; - } - else - { - // x y z a b c - p2_rt.Next = p1_lft; - p1_lft.Prev = p2_rt; - p2_lft.Prev = p1_rt; - p1_rt.Next = p2_lft; - outRec1.Pts = p2_lft; - } - } - else - { - if (e2.Side == EdgeSide.Right) - { - // a b c z y x - this.ReversePolyPtLinks(p2_lft); - p1_rt.Next = p2_rt; - p2_rt.Prev = p1_rt; - p2_lft.Next = p1_lft; - p1_lft.Prev = p2_lft; - } - else - { - // a b c x y z - p1_rt.Next = p2_lft; - p2_lft.Prev = p1_rt; - p1_lft.Prev = p2_rt; - p2_rt.Next = p1_lft; - } - } - - outRec1.BottomPt = null; - if (holeStateRec == outRec2) - { - if (outRec2.FirstLeft != outRec1) - { - outRec1.FirstLeft = outRec2.FirstLeft; - } - - outRec1.IsHole = outRec2.IsHole; - } - - outRec2.Pts = null; - outRec2.BottomPt = null; - - outRec2.FirstLeft = outRec1; - - int okIdx = e1.OutIdx; - int obsoleteIdx = e2.OutIdx; - - e1.OutIdx = Unassigned; // nb: safe because we only get here via AddLocalMaxPoly - e2.OutIdx = Unassigned; - - TEdge e = this.activeEdges; - while (e != null) - { - if (e.OutIdx == obsoleteIdx) - { - e.OutIdx = okIdx; - e.Side = e1.Side; - break; - } - - e = e.NextInAEL; - } - - outRec2.Idx = outRec1.Idx; - } - - private void ReversePolyPtLinks(OutPt pp) - { - if (pp == null) - { - return; - } - - OutPt pp1; - OutPt pp2; - pp1 = pp; - do - { - pp2 = pp1.Next; - pp1.Next = pp1.Prev; - pp1.Prev = pp2; - pp1 = pp2; - } - while (pp1 != pp); - } - - private void IntersectEdges(TEdge e1, TEdge e2, Vector2 pt) - { - // e1 will be to the left of e2 BELOW the intersection. Therefore e1 is before - // e2 in AEL except when e1 is being inserted at the intersection point ... - bool e1Contributing = e1.OutIdx >= 0; - bool e2Contributing = e2.OutIdx >= 0; - - // update winding counts... - // assumes that e1 will be to the Right of e2 ABOVE the intersection - if (e1.PolyTyp == e2.PolyTyp) - { - int oldE1WindCnt = e1.WindCnt; - e1.WindCnt = e2.WindCnt; - e2.WindCnt = oldE1WindCnt; - } - else - { - e1.WindCnt2 = (e1.WindCnt2 == 0) ? 1 : 0; - e2.WindCnt2 = (e2.WindCnt2 == 0) ? 1 : 0; - } - - int e1Wc, e2Wc; - e1Wc = Math.Abs(e1.WindCnt); - e2Wc = Math.Abs(e2.WindCnt); - - if (e1Contributing && e2Contributing) - { - if ((e1Wc != 0 && e1Wc != 1) || (e2Wc != 0 && e2Wc != 1) || - (e1.PolyTyp != e2.PolyTyp)) - { - this.AddLocalMaxPoly(e1, e2, pt); - } - else - { - this.AddOutPt(e1, pt); - this.AddOutPt(e2, pt); - SwapSides(e1, e2); - SwapPolyIndexes(e1, e2); - } - } - else if (e1Contributing) - { - if (e2Wc == 0 || e2Wc == 1) - { - this.AddOutPt(e1, pt); - SwapSides(e1, e2); - SwapPolyIndexes(e1, e2); - } - } - else if (e2Contributing) - { - if (e1Wc == 0 || e1Wc == 1) - { - this.AddOutPt(e2, pt); - SwapSides(e1, e2); - SwapPolyIndexes(e1, e2); - } - } - else if ((e1Wc == 0 || e1Wc == 1) && (e2Wc == 0 || e2Wc == 1)) - { - // neither edge is currently contributing ... - float e1Wc2, e2Wc2; - - e1Wc2 = Math.Abs(e1.WindCnt2); - e2Wc2 = Math.Abs(e2.WindCnt2); - - if (e1.PolyTyp != e2.PolyTyp) - { - this.AddLocalMinPoly(e1, e2, pt); - } - else if (e1Wc == 1 && e2Wc == 1) - { - if (((e1.PolyTyp == PolyType.Clip) && (e1Wc2 > 0) && (e2Wc2 > 0)) || - ((e1.PolyTyp == PolyType.Subject) && (e1Wc2 <= 0) && (e2Wc2 <= 0))) - { - this.AddLocalMinPoly(e1, e2, pt); - } - } - else - { - SwapSides(e1, e2); - } - } - } - - private void ProcessHorizontals() - { - TEdge horzEdge; // m_SortedEdges; - while (this.PopEdgeFromSEL(out horzEdge)) - { - this.ProcessHorizontal(horzEdge); - } - } - - private void GetHorzDirection(TEdge horzEdge, out Direction dir, out float left, out float right) - { - if (horzEdge.Bot.X < horzEdge.Top.X) - { - left = horzEdge.Bot.X; - right = horzEdge.Top.X; - dir = Direction.LeftToRight; - } - else - { - left = horzEdge.Top.X; - right = horzEdge.Bot.X; - dir = Direction.RightToLeft; - } - } - - private void ProcessHorizontal(TEdge horzEdge) - { - Direction dir; - float horzLeft, horzRight; - bool isOpen = horzEdge.WindDelta == 0; - - this.GetHorzDirection(horzEdge, out dir, out horzLeft, out horzRight); - - TEdge eLastHorz = horzEdge, eMaxPair = null; - while (eLastHorz.NextInLML != null && IsHorizontal(eLastHorz.NextInLML)) - { - eLastHorz = eLastHorz.NextInLML; - } - - if (eLastHorz.NextInLML == null) - { - eMaxPair = this.GetMaximaPair(eLastHorz); - } - - Maxima currMax = this.maxima; - if (currMax != null) - { - // get the first maxima in range (X) ... - if (dir == Direction.LeftToRight) - { - while (currMax != null && currMax.X <= horzEdge.Bot.X) - { - currMax = currMax.Next; - } - - if (currMax != null && currMax.X >= eLastHorz.Top.X) - { - currMax = null; - } - } - else - { - while (currMax.Next != null && currMax.Next.X < horzEdge.Bot.X) - { - currMax = currMax.Next; - } - - if (currMax.X <= eLastHorz.Top.X) - { - currMax = null; - } - } - } - - OutPt op1 = null; - - // loop through consec. horizontal edges - while (true) - { - bool isLastHorz = horzEdge == eLastHorz; - TEdge e = this.GetNextInAEL(horzEdge, dir); - while (e != null) - { - // this code block inserts extra coords into horizontal edges (in output - // polygons) whereever maxima touch these horizontal edges. This helps - // 'simplifying' polygons (ie if the Simplify property is set). - if (currMax != null) - { - if (dir == Direction.LeftToRight) - { - while (currMax != null && currMax.X < e.Curr.X) - { - if (horzEdge.OutIdx >= 0 && !isOpen) - { - this.AddOutPt(horzEdge, new Vector2(currMax.X, horzEdge.Bot.Y)); - } - - currMax = currMax.Next; - } - } - else - { - while (currMax != null && currMax.X > e.Curr.X) - { - if (horzEdge.OutIdx >= 0 && !isOpen) - { - this.AddOutPt(horzEdge, new Vector2(currMax.X, horzEdge.Bot.Y)); - } - - currMax = currMax.Prev; - } - } - } - - if ((dir == Direction.LeftToRight && e.Curr.X > horzRight) || - (dir == Direction.RightToLeft && e.Curr.X < horzLeft)) - { - break; - } - - // Also break if we've got to the end of an intermediate horizontal edge ... - // nb: Smaller Dx's are to the right of larger Dx's ABOVE the horizontal. - if (e.Curr.X == horzEdge.Top.X && horzEdge.NextInLML != null && - e.Dx < horzEdge.NextInLML.Dx) - { - break; - } - - // note: may be done multiple times - if (horzEdge.OutIdx >= 0 && !isOpen) - { - op1 = this.AddOutPt(horzEdge, e.Curr); - TEdge eNextHorz = this.sortedEdges; - while (eNextHorz != null) - { - if (eNextHorz.OutIdx >= 0 && - HorzSegmentsOverlap(horzEdge.Bot.X, horzEdge.Top.X, eNextHorz.Bot.X, eNextHorz.Top.X)) - { - OutPt op2 = this.GetLastOutPt(eNextHorz); - this.AddJoin(op2, op1, eNextHorz.Top); - } - - eNextHorz = eNextHorz.NextInSEL; - } - - this.AddGhostJoin(op1, horzEdge.Bot); - } - - // OK, so far we're still in range of the horizontal Edge but make sure - // we're at the last of consec. horizontals when matching with eMaxPair - if (e == eMaxPair && isLastHorz) - { - if (horzEdge.OutIdx >= 0) - { - this.AddLocalMaxPoly(horzEdge, eMaxPair, horzEdge.Top); - } - - this.DeleteFromAEL(horzEdge); - this.DeleteFromAEL(eMaxPair); - return; - } - - if (dir == Direction.LeftToRight) - { - Vector2 pt = new Vector2(e.Curr.X, horzEdge.Curr.Y); - this.IntersectEdges(horzEdge, e, pt); - } - else - { - Vector2 pt = new Vector2(e.Curr.X, horzEdge.Curr.Y); - this.IntersectEdges(e, horzEdge, pt); - } - - TEdge eNext = this.GetNextInAEL(e, dir); - this.SwapPositionsInAEL(horzEdge, e); - e = eNext; - } // end while(e != null) - - // Break out of loop if HorzEdge.NextInLML is not also horizontal ... - if (horzEdge.NextInLML == null || !IsHorizontal(horzEdge.NextInLML)) - { - break; - } - - this.UpdateEdgeIntoAEL(ref horzEdge); - if (horzEdge.OutIdx >= 0) - { - this.AddOutPt(horzEdge, horzEdge.Bot); - } - - this.GetHorzDirection(horzEdge, out dir, out horzLeft, out horzRight); - } - - if (horzEdge.OutIdx >= 0 && op1 == null) - { - op1 = this.GetLastOutPt(horzEdge); - TEdge eNextHorz = this.sortedEdges; - while (eNextHorz != null) - { - if (eNextHorz.OutIdx >= 0 && - HorzSegmentsOverlap(horzEdge.Bot.X, horzEdge.Top.X, eNextHorz.Bot.X, eNextHorz.Top.X)) - { - OutPt op2 = this.GetLastOutPt(eNextHorz); - this.AddJoin(op2, op1, eNextHorz.Top); - } - - eNextHorz = eNextHorz.NextInSEL; - } - - this.AddGhostJoin(op1, horzEdge.Top); - } - - if (horzEdge.NextInLML != null) - { - if (horzEdge.OutIdx >= 0) - { - op1 = this.AddOutPt(horzEdge, horzEdge.Top); - - this.UpdateEdgeIntoAEL(ref horzEdge); - if (horzEdge.WindDelta == 0) - { - return; - } - - // nb: HorzEdge is no longer horizontal here - TEdge ePrev = horzEdge.PrevInAEL; - TEdge eNext = horzEdge.NextInAEL; - if (ePrev != null && ePrev.Curr.X == horzEdge.Bot.X && - ePrev.Curr.Y == horzEdge.Bot.Y && ePrev.WindDelta != 0 && - (ePrev.OutIdx >= 0 && ePrev.Curr.Y > ePrev.Top.Y && - SlopesEqual(horzEdge, ePrev))) - { - OutPt op2 = this.AddOutPt(ePrev, horzEdge.Bot); - this.AddJoin(op1, op2, horzEdge.Top); - } - else if (eNext != null && eNext.Curr.X == horzEdge.Bot.X && - eNext.Curr.Y == horzEdge.Bot.Y && eNext.WindDelta != 0 && - eNext.OutIdx >= 0 && eNext.Curr.Y > eNext.Top.Y && - SlopesEqual(horzEdge, eNext)) - { - OutPt op2 = this.AddOutPt(eNext, horzEdge.Bot); - this.AddJoin(op1, op2, horzEdge.Top); - } - } - else - { - this.UpdateEdgeIntoAEL(ref horzEdge); - } - } - else - { - if (horzEdge.OutIdx >= 0) - { - this.AddOutPt(horzEdge, horzEdge.Top); - } - - this.DeleteFromAEL(horzEdge); - } - } - - private TEdge GetNextInAEL(TEdge e, Direction direction) - { - return direction == Direction.LeftToRight ? e.NextInAEL : e.PrevInAEL; - } - - private bool IsMaxima(TEdge e, double y) - { - return e != null && e.Top.Y == y && e.NextInLML == null; - } - - private bool IsIntermediate(TEdge e, double y) - { - return e.Top.Y == y && e.NextInLML != null; - } - - private TEdge GetMaximaPair(TEdge e) - { - if ((e.Next.Top == e.Top) && e.Next.NextInLML == null) - { - return e.Next; - } - else if ((e.Prev.Top == e.Top) && e.Prev.NextInLML == null) - { - return e.Prev; - } - else - { - return null; - } - } - - private TEdge GetMaximaPairEx(TEdge e) - { - // as above but returns null if MaxPair isn't in AEL (unless it's horizontal) - TEdge result = this.GetMaximaPair(e); - if (result == null || result.OutIdx == Skip || - ((result.NextInAEL == result.PrevInAEL) && !IsHorizontal(result))) - { - return null; - } - - return result; - } - - private bool ProcessIntersections(float topY) - { - if (this.activeEdges == null) - { - return true; - } - - try - { - this.BuildIntersectList(topY); - if (this.intersectList.Count == 0) - { - return true; - } - - if (this.intersectList.Count == 1 || this.FixupIntersectionOrder()) - { - this.ProcessIntersectList(); - } - else - { - return false; - } - } - catch - { - this.sortedEdges = null; - this.intersectList.Clear(); - throw new ClipperException("ProcessIntersections error"); - } - - this.sortedEdges = null; - return true; - } - - private void BuildIntersectList(float topY) - { - if (this.activeEdges == null) - { - return; - } - - // prepare for sorting ... - TEdge e = this.activeEdges; - this.sortedEdges = e; - while (e != null) - { - e.PrevInSEL = e.PrevInAEL; - e.NextInSEL = e.NextInAEL; - e.Curr.X = TopX(e, topY); - e = e.NextInAEL; - } - - // bubblesort ... - bool isModified = true; - while (isModified && this.sortedEdges != null) - { - isModified = false; - e = this.sortedEdges; - while (e.NextInSEL != null) - { - TEdge eNext = e.NextInSEL; - Vector2 pt; - if (e.Curr.X > eNext.Curr.X) - { - this.IntersectPoint(e, eNext, out pt); - if (pt.Y < topY) - { - pt = new Vector2(TopX(e, topY), topY); - } - - IntersectNode newNode = new IntersectNode(); - newNode.Edge1 = e; - newNode.Edge2 = eNext; - newNode.Pt = pt; - this.intersectList.Add(newNode); - - this.SwapPositionsInSEL(e, eNext); - isModified = true; - } - else - { - e = eNext; - } - } - - if (e.PrevInSEL != null) - { - e.PrevInSEL.NextInSEL = null; - } - else - { - break; - } - } - - this.sortedEdges = null; - } - - private bool EdgesAdjacent(IntersectNode inode) - { - return (inode.Edge1.NextInSEL == inode.Edge2) || - (inode.Edge1.PrevInSEL == inode.Edge2); - } - - private bool FixupIntersectionOrder() - { - // pre-condition: intersections are sorted bottom-most first. - // Now it's crucial that intersections are made only between adjacent edges, - // so to ensure this the order of intersections may need adjusting ... - this.intersectList.Sort(this.intersectNodeComparer); - - this.CopyAELToSEL(); - int cnt = this.intersectList.Count; - for (int i = 0; i < cnt; i++) - { - if (!this.EdgesAdjacent(this.intersectList[i])) - { - int j = i + 1; - while (j < cnt && !this.EdgesAdjacent(this.intersectList[j])) - { - j++; - } - - if (j == cnt) - { - return false; - } - - IntersectNode tmp = this.intersectList[i]; - this.intersectList[i] = this.intersectList[j]; - this.intersectList[j] = tmp; - } - - this.SwapPositionsInSEL(this.intersectList[i].Edge1, this.intersectList[i].Edge2); - } - - return true; - } - - private void ProcessIntersectList() - { - for (int i = 0; i < this.intersectList.Count; i++) - { - IntersectNode iNode = this.intersectList[i]; - { - this.IntersectEdges(iNode.Edge1, iNode.Edge2, iNode.Pt); - this.SwapPositionsInAEL(iNode.Edge1, iNode.Edge2); - } - } - - this.intersectList.Clear(); - } - - private void IntersectPoint(TEdge edge1, TEdge edge2, out Vector2 ip) - { - ip = default(Vector2); - double b1, b2; - - // nb: with very large coordinate values, it's possible for SlopesEqual() to - // return false but for the edge.Dx value be equal due to double precision rounding. - if (edge1.Dx == edge2.Dx) - { - ip.Y = edge1.Curr.Y; - ip.X = TopX(edge1, ip.Y); - return; - } - - if (edge1.Delta.X == 0) - { - ip.X = edge1.Bot.X; - if (IsHorizontal(edge2)) - { - ip.Y = edge2.Bot.Y; - } - else - { - b2 = edge2.Bot.Y - (edge2.Bot.X / edge2.Dx); - ip.Y = Round((ip.X / edge2.Dx) + b2); - } - } - else if (edge2.Delta.X == 0) - { - ip.X = edge2.Bot.X; - if (IsHorizontal(edge1)) - { - ip.Y = edge1.Bot.Y; - } - else - { - b1 = edge1.Bot.Y - (edge1.Bot.X / edge1.Dx); - ip.Y = Round((ip.X / edge1.Dx) + b1); - } - } - else - { - b1 = edge1.Bot.X - (edge1.Bot.Y * edge1.Dx); - b2 = edge2.Bot.X - (edge2.Bot.Y * edge2.Dx); - double q = (b2 - b1) / (edge1.Dx - edge2.Dx); - ip.Y = Round(q); - if (Math.Abs(edge1.Dx) < Math.Abs(edge2.Dx)) - { - ip.X = Round((edge1.Dx * q) + b1); - } - else - { - ip.X = Round((edge2.Dx * q) + b2); - } - } - - if (ip.Y < edge1.Top.Y || ip.Y < edge2.Top.Y) - { - if (edge1.Top.Y > edge2.Top.Y) - { - ip.Y = edge1.Top.Y; - } - else - { - ip.Y = edge2.Top.Y; - } - - if (Math.Abs(edge1.Dx) < Math.Abs(edge2.Dx)) - { - ip.X = TopX(edge1, ip.Y); - } - else - { - ip.X = TopX(edge2, ip.Y); - } - } - - // finally, don't allow 'ip' to be BELOW curr.Y (ie bottom of scanbeam) ... - if (ip.Y > edge1.Curr.Y) - { - ip.Y = edge1.Curr.Y; - - // better to use the more vertical edge to derive X ... - if (Math.Abs(edge1.Dx) > Math.Abs(edge2.Dx)) - { - ip.X = TopX(edge2, ip.Y); - } - else - { - ip.X = TopX(edge1, ip.Y); - } - } - } - - private void ProcessEdgesAtTopOfScanbeam(float topY) - { - TEdge e = this.activeEdges; - while (e != null) - { - // 1. process maxima, treating them as if they're 'bent' horizontal edges, - // but exclude maxima with horizontal edges. nb: e can't be a horizontal. - bool isMaximaEdge = this.IsMaxima(e, topY); - - if (isMaximaEdge) - { - TEdge eMaxPair = this.GetMaximaPairEx(e); - isMaximaEdge = eMaxPair == null || !IsHorizontal(eMaxPair); - } - - if (isMaximaEdge) - { - TEdge ePrev = e.PrevInAEL; - this.DoMaxima(e); - if (ePrev == null) - { - e = this.activeEdges; - } - else - { - e = ePrev.NextInAEL; - } - } - else - { - // 2. promote horizontal edges, otherwise update Curr.X and Curr.Y ... - if (this.IsIntermediate(e, topY) && IsHorizontal(e.NextInLML)) - { - this.UpdateEdgeIntoAEL(ref e); - if (e.OutIdx >= 0) - { - this.AddOutPt(e, e.Bot); - } - - this.AddEdgeToSEL(e); - } - else - { - e.Curr.X = TopX(e, topY); - e.Curr.Y = topY; - } - - e = e.NextInAEL; - } - } - - // 3. Process horizontals at the Top of the scanbeam ... - this.ProcessHorizontals(); - this.maxima = null; - - // 4. Promote intermediate vertices ... - e = this.activeEdges; - while (e != null) - { - if (this.IsIntermediate(e, topY)) - { - OutPt op = null; - if (e.OutIdx >= 0) - { - op = this.AddOutPt(e, e.Top); - } - - this.UpdateEdgeIntoAEL(ref e); - - // if output polygons share an edge, they'll need joining later ... - TEdge ePrev = e.PrevInAEL; - TEdge eNext = e.NextInAEL; - if (ePrev != null && ePrev.Curr.X == e.Bot.X && - ePrev.Curr.Y == e.Bot.Y && op != null && - ePrev.OutIdx >= 0 && ePrev.Curr.Y > ePrev.Top.Y && - SlopesEqual(e.Curr, e.Top, ePrev.Curr, ePrev.Top) && - (e.WindDelta != 0) && (ePrev.WindDelta != 0)) - { - OutPt op2 = this.AddOutPt(ePrev, e.Bot); - this.AddJoin(op, op2, e.Top); - } - else if (eNext != null && eNext.Curr.X == e.Bot.X && - eNext.Curr.Y == e.Bot.Y && op != null && - eNext.OutIdx >= 0 && eNext.Curr.Y > eNext.Top.Y && - SlopesEqual(e.Curr, e.Top, eNext.Curr, eNext.Top) && - (e.WindDelta != 0) && (eNext.WindDelta != 0)) - { - OutPt op2 = this.AddOutPt(eNext, e.Bot); - this.AddJoin(op, op2, e.Top); - } - } - - e = e.NextInAEL; - } - } - - private void DoMaxima(TEdge e) - { - TEdge eMaxPair = this.GetMaximaPairEx(e); - if (eMaxPair == null) - { - if (e.OutIdx >= 0) - { - this.AddOutPt(e, e.Top); - } - - this.DeleteFromAEL(e); - return; - } - - TEdge eNext = e.NextInAEL; - while (eNext != null && eNext != eMaxPair) - { - this.IntersectEdges(e, eNext, e.Top); - this.SwapPositionsInAEL(e, eNext); - eNext = e.NextInAEL; - } - - if (e.OutIdx == Unassigned && eMaxPair.OutIdx == Unassigned) - { - this.DeleteFromAEL(e); - this.DeleteFromAEL(eMaxPair); - } - else if (e.OutIdx >= 0 && eMaxPair.OutIdx >= 0) - { - if (e.OutIdx >= 0) - { - this.AddLocalMaxPoly(e, eMaxPair, e.Top); - } - - this.DeleteFromAEL(e); - this.DeleteFromAEL(eMaxPair); - } - else - { - throw new ClipperException("DoMaxima error"); - } - } - - private int PointCount(OutPt pts) - { - if (pts == null) - { - return 0; - } - - int result = 0; - OutPt p = pts; - do - { - result++; - p = p.Next; - } - while (p != pts); - return result; - } - - private void BuildResult2(PolyTree polytree) - { - polytree.Clear(); - - // add each output polygon/contour to polytree ... - polytree.AllPolys.Capacity = this.polyOuts.Count; - for (int i = 0; i < this.polyOuts.Count; i++) - { - OutRec outRec = this.polyOuts[i]; - int cnt = this.PointCount(outRec.Pts); - if ((outRec.IsOpen && cnt < 2) || - (!outRec.IsOpen && cnt < 3)) - { - continue; - } - - FixHoleLinkage(outRec); - PolyNode pn = new PolyNode(); - pn.SourcePath = outRec.SourcePath; - polytree.AllPolys.Add(pn); - outRec.PolyNode = pn; - pn.Polygon.Capacity = cnt; - OutPt op = outRec.Pts.Prev; - for (int j = 0; j < cnt; j++) - { - pn.Polygon.Add(op.Pt); - op = op.Prev; - } - } - - // fixup PolyNode links etc ... - polytree.Children.Capacity = this.polyOuts.Count; - for (int i = 0; i < this.polyOuts.Count; i++) - { - OutRec outRec = this.polyOuts[i]; - if (outRec.PolyNode == null) - { - continue; - } - else if (outRec.IsOpen) - { - outRec.PolyNode.IsOpen = true; - polytree.AddChild(outRec.PolyNode); - } - else if (outRec.FirstLeft != null && - outRec.FirstLeft.PolyNode != null) - { - outRec.FirstLeft.PolyNode.AddChild(outRec.PolyNode); - } - else - { - polytree.AddChild(outRec.PolyNode); - } - } - } - - private void FixupOutPolyline(OutRec outrec) - { - OutPt pp = outrec.Pts; - OutPt lastPP = pp.Prev; - while (pp != lastPP) - { - pp = pp.Next; - if (pp.Pt == pp.Prev.Pt) - { - if (pp == lastPP) - { - lastPP = pp.Prev; - } - - OutPt tmpPP = pp.Prev; - tmpPP.Next = pp.Next; - pp.Next.Prev = tmpPP; - pp = tmpPP; - } - } - - if (pp == pp.Prev) - { - outrec.Pts = null; - } - } - - private void FixupOutPolygon(OutRec outRec) - { - // FixupOutPolygon() - removes duplicate points and simplifies consecutive - // parallel edges by removing the middle vertex. - OutPt lastOK = null; - outRec.BottomPt = null; - OutPt pp = outRec.Pts; - while (true) - { - if (pp.Prev == pp || pp.Prev == pp.Next) - { - outRec.Pts = null; - return; - } - - // test for duplicate points and collinear edges ... - if ((pp.Pt == pp.Next.Pt) || (pp.Pt == pp.Prev.Pt) || - SlopesEqual(pp.Prev.Pt, pp.Pt, pp.Next.Pt)) - { - lastOK = null; - pp.Prev.Next = pp.Next; - pp.Next.Prev = pp.Prev; - pp = pp.Prev; - } - else if (pp == lastOK) - { - break; - } - else - { - if (lastOK == null) - { - lastOK = pp; - } - - pp = pp.Next; - } - } - - outRec.Pts = pp; - } - - private OutPt DupOutPt(OutPt outPt, bool insertAfter) - { - OutPt result = new OutPt(); - result.Pt = outPt.Pt; - result.Idx = outPt.Idx; - if (insertAfter) - { - result.Next = outPt.Next; - result.Prev = outPt; - outPt.Next.Prev = result; - outPt.Next = result; - } - else - { - result.Prev = outPt.Prev; - result.Next = outPt; - outPt.Prev.Next = result; - outPt.Prev = result; - } - - return result; - } - - private bool GetOverlap(float a1, float a2, float b1, float b2, out float left, out float right) - { - if (a1 < a2) - { - if (b1 < b2) - { - left = Math.Max(a1, b1); - right = Math.Min(a2, b2); - } - else - { - left = Math.Max(a1, b2); - right = Math.Min(a2, b1); - } - } - else - { - if (b1 < b2) - { - left = Math.Max(a2, b1); - right = Math.Min(a1, b2); - } - else - { - left = Math.Max(a2, b2); - right = Math.Min(a1, b1); - } - } - - return left < right; - } - - private bool JoinHorz(OutPt op1, OutPt op1b, OutPt op2, OutPt op2b, Vector2 pt, bool discardLeft) - { - Direction dir1 = op1.Pt.X > op1b.Pt.X ? Direction.RightToLeft : Direction.LeftToRight; - Direction dir2 = op2.Pt.X > op2b.Pt.X ? Direction.RightToLeft : Direction.LeftToRight; - if (dir1 == dir2) - { - return false; - } - - // When DiscardLeft, we want Op1b to be on the Left of Op1, otherwise we - // want Op1b to be on the Right. (And likewise with Op2 and Op2b.) - // So, to facilitate this while inserting Op1b and Op2b ... - // when DiscardLeft, make sure we're AT or RIGHT of Pt before adding Op1b, - // otherwise make sure we're AT or LEFT of Pt. (Likewise with Op2b.) - if (dir1 == Direction.LeftToRight) - { - while (op1.Next.Pt.X <= pt.X && - op1.Next.Pt.X >= op1.Pt.X && op1.Next.Pt.Y == pt.Y) - { - op1 = op1.Next; - } - - if (discardLeft && (op1.Pt.X != pt.X)) - { - op1 = op1.Next; - } - - op1b = this.DupOutPt(op1, !discardLeft); - if (op1b.Pt != pt) - { - op1 = op1b; - op1.Pt = pt; - op1b = this.DupOutPt(op1, !discardLeft); - } - } - else - { - while (op1.Next.Pt.X >= pt.X && - op1.Next.Pt.X <= op1.Pt.X && - op1.Next.Pt.Y == pt.Y) - { - op1 = op1.Next; - } - - if (!discardLeft && (op1.Pt.X != pt.X)) - { - op1 = op1.Next; - } - - op1b = this.DupOutPt(op1, discardLeft); - if (op1b.Pt != pt) - { - op1 = op1b; - op1.Pt = pt; - op1b = this.DupOutPt(op1, discardLeft); - } - } - - if (dir2 == Direction.LeftToRight) - { - while (op2.Next.Pt.X <= pt.X && - op2.Next.Pt.X >= op2.Pt.X && - op2.Next.Pt.Y == pt.Y) - { - op2 = op2.Next; - } - - if (discardLeft && (op2.Pt.X != pt.X)) - { - op2 = op2.Next; - } - - op2b = this.DupOutPt(op2, !discardLeft); - if (op2b.Pt != pt) - { - op2 = op2b; - op2.Pt = pt; - op2b = this.DupOutPt(op2, !discardLeft); - } - } - else - { - while (op2.Next.Pt.X >= pt.X && - op2.Next.Pt.X <= op2.Pt.X && - op2.Next.Pt.Y == pt.Y) - { - op2 = op2.Next; - } - - if (!discardLeft && (op2.Pt.X != pt.X)) - { - op2 = op2.Next; - } - - op2b = this.DupOutPt(op2, discardLeft); - if (op2b.Pt != pt) - { - op2 = op2b; - op2.Pt = pt; - op2b = this.DupOutPt(op2, discardLeft); - } - } - - if ((dir1 == Direction.LeftToRight) == discardLeft) - { - op1.Prev = op2; - op2.Next = op1; - op1b.Next = op2b; - op2b.Prev = op1b; - } - else - { - op1.Next = op2; - op2.Prev = op1; - op1b.Prev = op2b; - op2b.Next = op1b; - } - - return true; - } - - private bool JoinPoints(Join j, OutRec outRec1, OutRec outRec2) - { - OutPt op1 = j.OutPt1, op1b; - OutPt op2 = j.OutPt2, op2b; - - // There are 3 kinds of joins for output polygons ... - // 1. Horizontal joins where Join.OutPt1 & Join.OutPt2 are vertices anywhere - // along (horizontal) collinear edges (& Join.OffPt is on the same horizontal). - // 2. Non-horizontal joins where Join.OutPt1 & Join.OutPt2 are at the same - // location at the Bottom of the overlapping segment (& Join.OffPt is above). - // 3. StrictlySimple joins where edges touch but are not collinear and where - // Join.OutPt1, Join.OutPt2 & Join.OffPt all share the same point. - bool isHorizontal = j.OutPt1.Pt.Y == j.OffPt.Y; - - if (isHorizontal && (j.OffPt == j.OutPt1.Pt) && (j.OffPt == j.OutPt2.Pt)) - { - // Strictly Simple join ... - if (outRec1 != outRec2) - { - return false; - } - - op1b = j.OutPt1.Next; - while (op1b != op1 && (op1b.Pt == j.OffPt)) - { - op1b = op1b.Next; - } - - bool reverse1 = op1b.Pt.Y > j.OffPt.Y; - op2b = j.OutPt2.Next; - while (op2b != op2 && (op2b.Pt == j.OffPt)) - { - op2b = op2b.Next; - } - - bool reverse2 = op2b.Pt.Y > j.OffPt.Y; - if (reverse1 == reverse2) - { - return false; - } - - if (reverse1) - { - op1b = this.DupOutPt(op1, false); - op2b = this.DupOutPt(op2, true); - op1.Prev = op2; - op2.Next = op1; - op1b.Next = op2b; - op2b.Prev = op1b; - j.OutPt1 = op1; - j.OutPt2 = op1b; - return true; - } - else - { - op1b = this.DupOutPt(op1, true); - op2b = this.DupOutPt(op2, false); - op1.Next = op2; - op2.Prev = op1; - op1b.Prev = op2b; - op2b.Next = op1b; - j.OutPt1 = op1; - j.OutPt2 = op1b; - return true; - } - } - else if (isHorizontal) - { - // treat horizontal joins differently to non-horizontal joins since with - // them we're not yet sure where the overlapping is. OutPt1.Pt & OutPt2.Pt - // may be anywhere along the horizontal edge. - op1b = op1; - while (op1.Prev.Pt.Y == op1.Pt.Y && op1.Prev != op1b && op1.Prev != op2) - { - op1 = op1.Prev; - } - - while (op1b.Next.Pt.Y == op1b.Pt.Y && op1b.Next != op1 && op1b.Next != op2) - { - op1b = op1b.Next; - } - - if (op1b.Next == op1 || op1b.Next == op2) - { - return false; // a flat 'polygon' - } - - op2b = op2; - while (op2.Prev.Pt.Y == op2.Pt.Y && op2.Prev != op2b && op2.Prev != op1b) - { - op2 = op2.Prev; - } - - while (op2b.Next.Pt.Y == op2b.Pt.Y && op2b.Next != op2 && op2b.Next != op1) - { - op2b = op2b.Next; - } - - if (op2b.Next == op2 || op2b.Next == op1) - { - return false; // a flat 'polygon' - } - - float left, right; - - // Op1 -. Op1b & Op2 -. Op2b are the extremites of the horizontal edges - if (!this.GetOverlap(op1.Pt.X, op1b.Pt.X, op2.Pt.X, op2b.Pt.X, out left, out right)) - { - return false; - } - - // DiscardLeftSide: when overlapping edges are joined, a spike will created - // which needs to be cleaned up. However, we don't want Op1 or Op2 caught up - // on the discard Side as either may still be needed for other joins ... - Vector2 pt; - bool discardLeftSide; - if (op1.Pt.X >= left && op1.Pt.X <= right) - { - pt = op1.Pt; - discardLeftSide = op1.Pt.X > op1b.Pt.X; - } - else if (op2.Pt.X >= left && op2.Pt.X <= right) - { - pt = op2.Pt; - discardLeftSide = op2.Pt.X > op2b.Pt.X; - } - else if (op1b.Pt.X >= left && op1b.Pt.X <= right) - { - pt = op1b.Pt; - discardLeftSide = op1b.Pt.X > op1.Pt.X; - } - else - { - pt = op2b.Pt; - discardLeftSide = op2b.Pt.X > op2.Pt.X; - } - - j.OutPt1 = op1; - j.OutPt2 = op2; - return this.JoinHorz(op1, op1b, op2, op2b, pt, discardLeftSide); - } - else - { - // nb: For non-horizontal joins ... - // 1. Jr.OutPt1.Pt.Y == Jr.OutPt2.Pt.Y - // 2. Jr.OutPt1.Pt > Jr.OffPt.Y - - // make sure the polygons are correctly oriented ... - op1b = op1.Next; - while ((op1b.Pt == op1.Pt) && (op1b != op1)) - { - op1b = op1b.Next; - } - - bool reverse1 = (op1b.Pt.Y > op1.Pt.Y) || !SlopesEqual(op1.Pt, op1b.Pt, j.OffPt); - if (reverse1) - { - op1b = op1.Prev; - while ((op1b.Pt == op1.Pt) && (op1b != op1)) - { - op1b = op1b.Prev; - } - - if ((op1b.Pt.Y > op1.Pt.Y) || - !SlopesEqual(op1.Pt, op1b.Pt, j.OffPt)) - { - return false; - } - } - - op2b = op2.Next; - while ((op2b.Pt == op2.Pt) && (op2b != op2)) - { - op2b = op2b.Next; - } - - bool reverse2 = (op2b.Pt.Y > op2.Pt.Y) || !SlopesEqual(op2.Pt, op2b.Pt, j.OffPt); - if (reverse2) - { - op2b = op2.Prev; - while ((op2b.Pt == op2.Pt) && (op2b != op2)) - { - op2b = op2b.Prev; - } - - if ((op2b.Pt.Y > op2.Pt.Y) || - !SlopesEqual(op2.Pt, op2b.Pt, j.OffPt)) - { - return false; - } - } - - if ((op1b == op1) || (op2b == op2) || (op1b == op2b) || - ((outRec1 == outRec2) && (reverse1 == reverse2))) - { - return false; - } - - if (reverse1) - { - op1b = this.DupOutPt(op1, false); - op2b = this.DupOutPt(op2, true); - op1.Prev = op2; - op2.Next = op1; - op1b.Next = op2b; - op2b.Prev = op1b; - j.OutPt1 = op1; - j.OutPt2 = op1b; - return true; - } - else - { - op1b = this.DupOutPt(op1, true); - op2b = this.DupOutPt(op2, false); - op1.Next = op2; - op2.Prev = op1; - op1b.Prev = op2b; - op2b.Next = op1b; - j.OutPt1 = op1; - j.OutPt2 = op1b; - return true; - } - } - } - - private void FixupFirstLefts1(OutRec oldOutRec, OutRec newOutRec) - { - foreach (OutRec outRec in this.polyOuts) - { - OutRec firstLeft = ParseFirstLeft(outRec.FirstLeft); - if (outRec.Pts != null && firstLeft == oldOutRec) - { - if (Poly2ContainsPoly1(outRec.Pts, newOutRec.Pts)) - { - outRec.FirstLeft = newOutRec; - } - } - } - } - - private void FixupFirstLefts2(OutRec innerOutRec, OutRec outerOutRec) - { - // A polygon has split into two such that one is now the inner of the other. - // It's possible that these polygons now wrap around other polygons, so check - // every polygon that's also contained by OuterOutRec's FirstLeft container - // (including nil) to see if they've become inner to the new inner polygon ... - OutRec orfl = outerOutRec.FirstLeft; - foreach (OutRec outRec in this.polyOuts) - { - if (outRec.Pts == null || outRec == outerOutRec || outRec == innerOutRec) - { - continue; - } - - OutRec firstLeft = ParseFirstLeft(outRec.FirstLeft); - if (firstLeft != orfl && firstLeft != innerOutRec && firstLeft != outerOutRec) - { - continue; - } - - if (Poly2ContainsPoly1(outRec.Pts, innerOutRec.Pts)) - { - outRec.FirstLeft = innerOutRec; - } - else if (Poly2ContainsPoly1(outRec.Pts, outerOutRec.Pts)) - { - outRec.FirstLeft = outerOutRec; - } - else if (outRec.FirstLeft == innerOutRec || outRec.FirstLeft == outerOutRec) - { - outRec.FirstLeft = orfl; - } - } - } - - private void FixupFirstLefts3(OutRec oldOutRec, OutRec newOutRec) - { - // same as FixupFirstLefts1 but doesn't call Poly2ContainsPoly1() - foreach (OutRec outRec in this.polyOuts) - { - OutRec firstLeft = ParseFirstLeft(outRec.FirstLeft); - if (outRec.Pts != null && outRec.FirstLeft == oldOutRec) - { - outRec.FirstLeft = newOutRec; - } - } - } - - private void JoinCommonEdges() - { - for (int i = 0; i < this.joins.Count; i++) - { - Join join = this.joins[i]; - - OutRec outRec1 = this.GetOutRec(join.OutPt1.Idx); - OutRec outRec2 = this.GetOutRec(join.OutPt2.Idx); - - if (outRec1.Pts == null || outRec2.Pts == null) - { - continue; - } - - if (outRec1.IsOpen || outRec2.IsOpen) - { - continue; - } - - // get the polygon fragment with the correct hole state (FirstLeft) - // before calling JoinPoints() ... - OutRec holeStateRec; - if (outRec1 == outRec2) - { - holeStateRec = outRec1; - } - else if (this.OutRec1RightOfOutRec2(outRec1, outRec2)) - { - holeStateRec = outRec2; - } - else if (this.OutRec1RightOfOutRec2(outRec2, outRec1)) - { - holeStateRec = outRec1; - } - else - { - holeStateRec = this.GetLowermostRec(outRec1, outRec2); - } - - if (!this.JoinPoints(join, outRec1, outRec2)) - { - continue; - } - - if (outRec1 == outRec2) - { - // instead of joining two polygons, we've just created a new one by - // splitting one polygon into two. - outRec1.Pts = join.OutPt1; - outRec1.BottomPt = null; - outRec2 = this.CreateOutRec(); - outRec2.Pts = join.OutPt2; - - // update all OutRec2.Pts Idx's ... - this.UpdateOutPtIdxs(outRec2); - - if (Poly2ContainsPoly1(outRec2.Pts, outRec1.Pts)) - { - // outRec1 contains outRec2 ... - outRec2.IsHole = !outRec1.IsHole; - outRec2.FirstLeft = outRec1; - - if (this.usingPolyTree) - { - this.FixupFirstLefts2(outRec2, outRec1); - } - } - else if (Poly2ContainsPoly1(outRec1.Pts, outRec2.Pts)) - { - // outRec2 contains outRec1 ... - outRec2.IsHole = outRec1.IsHole; - outRec1.IsHole = !outRec2.IsHole; - outRec2.FirstLeft = outRec1.FirstLeft; - outRec1.FirstLeft = outRec2; - - if (this.usingPolyTree) - { - this.FixupFirstLefts2(outRec1, outRec2); - } - } - else - { - // the 2 polygons are completely separate ... - outRec2.IsHole = outRec1.IsHole; - outRec2.FirstLeft = outRec1.FirstLeft; - - // fixup FirstLeft pointers that may need reassigning to OutRec2 - if (this.usingPolyTree) - { - this.FixupFirstLefts1(outRec1, outRec2); - } - } - } - else - { - // joined 2 polygons together ... - outRec2.Pts = null; - outRec2.BottomPt = null; - outRec2.Idx = outRec1.Idx; - - outRec1.IsHole = holeStateRec.IsHole; - if (holeStateRec == outRec2) - { - outRec1.FirstLeft = outRec2.FirstLeft; - } - - outRec2.FirstLeft = outRec1; - - // fixup FirstLeft pointers that may need reassigning to OutRec1 - if (this.usingPolyTree) - { - this.FixupFirstLefts3(outRec2, outRec1); - } - } - } - } - - private void UpdateOutPtIdxs(OutRec outrec) - { - OutPt op = outrec.Pts; - do - { - op.Idx = outrec.Idx; - op = op.Prev; - } - while (op != outrec.Pts); - } - - private void DoSimplePolygons() - { - int i = 0; - while (i < this.polyOuts.Count) - { - OutRec outrec = this.polyOuts[i++]; - OutPt op = outrec.Pts; - if (op == null || outrec.IsOpen) - { - continue; - } - - do - { - // for each Pt in Polygon until duplicate found do ... - OutPt op2 = op.Next; - while (op2 != outrec.Pts) - { - if ((op.Pt == op2.Pt) && op2.Next != op && op2.Prev != op) - { - // split the polygon into two ... - OutPt op3 = op.Prev; - OutPt op4 = op2.Prev; - op.Prev = op4; - op4.Next = op; - op2.Prev = op3; - op3.Next = op2; - - outrec.Pts = op; - OutRec outrec2 = this.CreateOutRec(); - outrec2.Pts = op2; - this.UpdateOutPtIdxs(outrec2); - if (Poly2ContainsPoly1(outrec2.Pts, outrec.Pts)) - { - // OutRec2 is contained by OutRec1 ... - outrec2.IsHole = !outrec.IsHole; - outrec2.FirstLeft = outrec; - if (this.usingPolyTree) - { - this.FixupFirstLefts2(outrec2, outrec); - } - } - else - if (Poly2ContainsPoly1(outrec.Pts, outrec2.Pts)) - { - // OutRec1 is contained by OutRec2 ... - outrec2.IsHole = outrec.IsHole; - outrec.IsHole = !outrec2.IsHole; - outrec2.FirstLeft = outrec.FirstLeft; - outrec.FirstLeft = outrec2; - if (this.usingPolyTree) - { - this.FixupFirstLefts2(outrec, outrec2); - } - } - else - { - // the 2 polygons are separate ... - outrec2.IsHole = outrec.IsHole; - outrec2.FirstLeft = outrec.FirstLeft; - if (this.usingPolyTree) - { - this.FixupFirstLefts1(outrec, outrec2); - } - } - - op2 = op; // ie get ready for the next iteration - } - - op2 = op2.Next; - } - - op = op.Next; - } - while (op != outrec.Pts); - } - } - - private double Area(OutRec outRec) - { - return this.Area(outRec.Pts); - } - - private double Area(OutPt op) - { - OutPt opFirst = op; - if (op == null) - { - return 0; - } - - double a = 0; - do - { - a = a + ((op.Prev.Pt.X + op.Pt.X) * (op.Prev.Pt.Y - op.Pt.Y)); - op = op.Next; - } - while (op != opFirst); - - return a * 0.5; - } - - private void SetDx(TEdge e) - { - e.Delta.X = e.Top.X - e.Bot.X; - e.Delta.Y = e.Top.Y - e.Bot.Y; - if (e.Delta.Y == 0) - { - e.Dx = HorizontalDeltaLimit; - } - else - { - e.Dx = e.Delta.X / e.Delta.Y; - } - } - - private void InsertLocalMinima(LocalMinima newLm) - { - if (this.minimaList == null) - { - this.minimaList = newLm; - } - else if (newLm.Y >= this.minimaList.Y) - { - newLm.Next = this.minimaList; - this.minimaList = newLm; - } - else - { - LocalMinima tmpLm = this.minimaList; - while (tmpLm.Next != null && (newLm.Y < tmpLm.Next.Y)) - { - tmpLm = tmpLm.Next; - } - - newLm.Next = tmpLm.Next; - tmpLm.Next = newLm; - } - } - - private bool PopLocalMinima(float y, out LocalMinima current) - { - current = this.currentLM; - if (this.currentLM != null && this.currentLM.Y == y) - { - this.currentLM = this.currentLM.Next; - return true; - } - - return false; - } - - private void Reset() - { - this.currentLM = this.minimaList; - if (this.currentLM == null) - { - return; // ie nothing to process - } - - // reset all edges ... - this.scanbeam = null; - LocalMinima lm = this.minimaList; - while (lm != null) - { - this.InsertScanbeam(lm.Y); - TEdge e = lm.LeftBound; - if (e != null) - { - e.Curr = e.Bot; - e.OutIdx = Unassigned; - } - - e = lm.RightBound; - if (e != null) - { - e.Curr = e.Bot; - e.OutIdx = Unassigned; - } - - lm = lm.Next; - } - - this.activeEdges = null; - } - - private void InsertScanbeam(float y) - { - // single-linked list: sorted descending, ignoring dups. - if (this.scanbeam == null) - { - this.scanbeam = new Scanbeam(); - this.scanbeam.Next = null; - this.scanbeam.Y = y; - } - else if (y > this.scanbeam.Y) - { - Scanbeam newSb = new Scanbeam(); - newSb.Y = y; - newSb.Next = this.scanbeam; - this.scanbeam = newSb; - } - else - { - Scanbeam sb2 = this.scanbeam; - while (sb2.Next != null && (y <= sb2.Next.Y)) - { - sb2 = sb2.Next; - } - - if (y == sb2.Y) - { - return; // ie ignores duplicates - } - - Scanbeam newSb = new Scanbeam(); - newSb.Y = y; - newSb.Next = sb2.Next; - sb2.Next = newSb; - } - } - - private bool PopScanbeam(out float y) - { - if (this.scanbeam == null) - { - y = 0; - return false; - } - - y = this.scanbeam.Y; - this.scanbeam = this.scanbeam.Next; - return true; - } - - private bool LocalMinimaPending() - { - return this.currentLM != null; - } - - private OutRec CreateOutRec() - { - OutRec result = new OutRec(); - result.Idx = Unassigned; - result.IsHole = false; - result.IsOpen = false; - result.FirstLeft = null; - result.Pts = null; - result.BottomPt = null; - result.PolyNode = null; - this.polyOuts.Add(result); - result.Idx = this.polyOuts.Count - 1; - return result; - } - - private void DisposeOutRec(int index) - { - OutRec outRec = this.polyOuts[index]; - outRec.Pts = null; - outRec = null; - this.polyOuts[index] = null; - } - - private void UpdateEdgeIntoAEL(ref TEdge e) - { - if (e.NextInLML == null) - { - throw new ClipperException("UpdateEdgeIntoAEL: invalid call"); - } - - TEdge aelPrev = e.PrevInAEL; - TEdge aelNext = e.NextInAEL; - e.NextInLML.OutIdx = e.OutIdx; - if (aelPrev != null) - { - aelPrev.NextInAEL = e.NextInLML; - } - else - { - this.activeEdges = e.NextInLML; - } - - if (aelNext != null) - { - aelNext.PrevInAEL = e.NextInLML; - } - - e.NextInLML.Side = e.Side; - e.NextInLML.WindDelta = e.WindDelta; - e.NextInLML.WindCnt = e.WindCnt; - e.NextInLML.WindCnt2 = e.WindCnt2; - e = e.NextInLML; - e.Curr = e.Bot; - e.PrevInAEL = aelPrev; - e.NextInAEL = aelNext; - if (!IsHorizontal(e)) - { - this.InsertScanbeam(e.Top.Y); - } - } - - private void SwapPositionsInAEL(TEdge edge1, TEdge edge2) - { - // check that one or other edge hasn't already been removed from AEL ... - if (edge1.NextInAEL == edge1.PrevInAEL || - edge2.NextInAEL == edge2.PrevInAEL) - { - return; - } - - if (edge1.NextInAEL == edge2) - { - TEdge next = edge2.NextInAEL; - if (next != null) - { - next.PrevInAEL = edge1; - } - - TEdge prev = edge1.PrevInAEL; - if (prev != null) - { - prev.NextInAEL = edge2; - } - - edge2.PrevInAEL = prev; - edge2.NextInAEL = edge1; - edge1.PrevInAEL = edge2; - edge1.NextInAEL = next; - } - else if (edge2.NextInAEL == edge1) - { - TEdge next = edge1.NextInAEL; - if (next != null) - { - next.PrevInAEL = edge2; - } - - TEdge prev = edge2.PrevInAEL; - if (prev != null) - { - prev.NextInAEL = edge1; - } - - edge1.PrevInAEL = prev; - edge1.NextInAEL = edge2; - edge2.PrevInAEL = edge1; - edge2.NextInAEL = next; - } - else - { - TEdge next = edge1.NextInAEL; - TEdge prev = edge1.PrevInAEL; - edge1.NextInAEL = edge2.NextInAEL; - if (edge1.NextInAEL != null) - { - edge1.NextInAEL.PrevInAEL = edge1; - } - - edge1.PrevInAEL = edge2.PrevInAEL; - if (edge1.PrevInAEL != null) - { - edge1.PrevInAEL.NextInAEL = edge1; - } - - edge2.NextInAEL = next; - if (edge2.NextInAEL != null) - { - edge2.NextInAEL.PrevInAEL = edge2; - } - - edge2.PrevInAEL = prev; - if (edge2.PrevInAEL != null) - { - edge2.PrevInAEL.NextInAEL = edge2; - } - } - - if (edge1.PrevInAEL == null) - { - this.activeEdges = edge1; - } - else if (edge2.PrevInAEL == null) - { - this.activeEdges = edge2; - } - } - - private void DeleteFromAEL(TEdge e) - { - TEdge aelPrev = e.PrevInAEL; - TEdge aelNext = e.NextInAEL; - if (aelPrev == null && aelNext == null && (e != this.activeEdges)) - { - return; // already deleted - } - - if (aelPrev != null) - { - aelPrev.NextInAEL = aelNext; - } - else - { - this.activeEdges = aelNext; - } - - if (aelNext != null) - { - aelNext.PrevInAEL = aelPrev; - } - - e.NextInAEL = null; - e.PrevInAEL = null; - } - - private void InitEdge2(TEdge e, PolyType polyType) - { - if (e.Curr.Y >= e.Next.Curr.Y) - { - e.Bot = e.Curr; - e.Top = e.Next.Curr; - } - else - { - e.Top = e.Curr; - e.Bot = e.Next.Curr; - } - - this.SetDx(e); - e.PolyTyp = polyType; - } - - private TEdge ProcessBound(TEdge edge, bool leftBoundIsForward) - { - TEdge eStart, result = edge; - TEdge horz; - - if (result.OutIdx == Skip) - { - // check if there are edges beyond the skip edge in the bound and if so - // create another LocMin and calling ProcessBound once more ... - edge = result; - if (leftBoundIsForward) - { - while (edge.Top.Y == edge.Next.Bot.Y) - { - edge = edge.Next; - } - - while (edge != result && edge.Dx == HorizontalDeltaLimit) - { - edge = edge.Prev; - } - } - else - { - while (edge.Top.Y == edge.Prev.Bot.Y) - { - edge = edge.Prev; - } - - while (edge != result && edge.Dx == HorizontalDeltaLimit) - { - edge = edge.Next; - } - } - - if (edge == result) - { - if (leftBoundIsForward) - { - result = edge.Next; - } - else - { - result = edge.Prev; - } - } - else - { - // there are more edges in the bound beyond result starting with E - if (leftBoundIsForward) - { - edge = result.Next; - } - else - { - edge = result.Prev; - } - - LocalMinima locMin = new LocalMinima(); - locMin.Next = null; - locMin.Y = edge.Bot.Y; - locMin.LeftBound = null; - locMin.RightBound = edge; - edge.WindDelta = 0; - result = this.ProcessBound(edge, leftBoundIsForward); - this.InsertLocalMinima(locMin); - } - - return result; - } - - if (edge.Dx == HorizontalDeltaLimit) - { - // We need to be careful with open paths because this may not be a - // true local minima (ie E may be following a skip edge). - // Also, consecutive horz. edges may start heading left before going right. - if (leftBoundIsForward) - { - eStart = edge.Prev; - } - else - { - eStart = edge.Next; - } - - // ie an adjoining horizontal skip edge - if (eStart.Dx == HorizontalDeltaLimit) - { - if (eStart.Bot.X != edge.Bot.X && eStart.Top.X != edge.Bot.X) - { - ReverseHorizontal(edge); - } - } - else if (eStart.Bot.X != edge.Bot.X) - { - ReverseHorizontal(edge); - } - } - - eStart = edge; - if (leftBoundIsForward) - { - while (result.Top.Y == result.Next.Bot.Y && result.Next.OutIdx != Skip) - { - result = result.Next; - } - - if (result.Dx == HorizontalDeltaLimit && result.Next.OutIdx != Skip) - { - // nb: at the top of a bound, horizontals are added to the bound - // only when the preceding edge attaches to the horizontal's left vertex - // unless a Skip edge is encountered when that becomes the top divide - horz = result; - while (horz.Prev.Dx == HorizontalDeltaLimit) - { - horz = horz.Prev; - } - - if (horz.Prev.Top.X > result.Next.Top.X) - { - result = horz.Prev; - } - } - - while (edge != result) - { - edge.NextInLML = edge.Next; - if (edge.Dx == HorizontalDeltaLimit && edge != eStart && edge.Bot.X != edge.Prev.Top.X) - { - ReverseHorizontal(edge); - } - - edge = edge.Next; - } - - if (edge.Dx == HorizontalDeltaLimit && edge != eStart && edge.Bot.X != edge.Prev.Top.X) - { - ReverseHorizontal(edge); - } - - result = result.Next; // move to the edge just beyond current bound - } - else - { - while (result.Top.Y == result.Prev.Bot.Y && result.Prev.OutIdx != Skip) - { - result = result.Prev; - } - - if (result.Dx == HorizontalDeltaLimit && result.Prev.OutIdx != Skip) - { - horz = result; - while (horz.Next.Dx == HorizontalDeltaLimit) - { - horz = horz.Next; - } - - if (horz.Next.Top.X == result.Prev.Top.X || horz.Next.Top.X > result.Prev.Top.X) - { - result = horz.Next; - } - } - - while (edge != result) - { - edge.NextInLML = edge.Prev; - if (edge.Dx == HorizontalDeltaLimit && edge != eStart && edge.Bot.X != edge.Next.Top.X) - { - ReverseHorizontal(edge); - } - - edge = edge.Prev; - } - - if (edge.Dx == HorizontalDeltaLimit && edge != eStart && edge.Bot.X != edge.Next.Top.X) - { - ReverseHorizontal(edge); - } - - result = result.Prev; // move to the edge just beyond current bound - } - - return result; - } - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Shapes/PolygonClipper/ClipperException.cs b/src/ImageSharp.Drawing/Shapes/PolygonClipper/ClipperException.cs deleted file mode 100644 index cefd268aff..0000000000 --- a/src/ImageSharp.Drawing/Shapes/PolygonClipper/ClipperException.cs +++ /dev/null @@ -1,31 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Drawing.Shapes.PolygonClipper -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Numerics; - using System.Runtime.CompilerServices; - - using Paths; - - /// - /// Clipper Exception - /// - /// - internal class ClipperException : Exception - { - /// - /// Initializes a new instance of the class. - /// - /// The description. - public ClipperException(string description) - : base(description) - { - } - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Shapes/PolygonClipper/Direction.cs b/src/ImageSharp.Drawing/Shapes/PolygonClipper/Direction.cs deleted file mode 100644 index 5fa877fd4f..0000000000 --- a/src/ImageSharp.Drawing/Shapes/PolygonClipper/Direction.cs +++ /dev/null @@ -1,31 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Drawing.Shapes.PolygonClipper -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Numerics; - using System.Runtime.CompilerServices; - - using Paths; - - /// - /// ??? - /// - internal enum Direction - { - /// - /// The right to left - /// - RightToLeft, - - /// - /// The left to right - /// - LeftToRight - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Shapes/PolygonClipper/EdgeSide.cs b/src/ImageSharp.Drawing/Shapes/PolygonClipper/EdgeSide.cs deleted file mode 100644 index 5093958d15..0000000000 --- a/src/ImageSharp.Drawing/Shapes/PolygonClipper/EdgeSide.cs +++ /dev/null @@ -1,31 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Drawing.Shapes.PolygonClipper -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Numerics; - using System.Runtime.CompilerServices; - - using Paths; - - /// - /// ?? - /// - internal enum EdgeSide - { - /// - /// The left - /// - Left, - - /// - /// The right - /// - Right - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Shapes/PolygonClipper/IntersectNode.cs b/src/ImageSharp.Drawing/Shapes/PolygonClipper/IntersectNode.cs deleted file mode 100644 index 7cd0562b09..0000000000 --- a/src/ImageSharp.Drawing/Shapes/PolygonClipper/IntersectNode.cs +++ /dev/null @@ -1,38 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Drawing.Shapes.PolygonClipper -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Numerics; - using System.Runtime.CompilerServices; - - using Paths; - - /// - /// ?? - /// - internal class IntersectNode - { -#pragma warning disable SA1401 // Field must be private - /// - /// The edge1 - /// - internal TEdge Edge1; - - /// - /// The edge2 - /// - internal TEdge Edge2; - - /// - /// The pt - /// - internal System.Numerics.Vector2 Pt; -#pragma warning restore SA1401 // Field must be private - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Shapes/PolygonClipper/IntersectNodeSort.cs b/src/ImageSharp.Drawing/Shapes/PolygonClipper/IntersectNodeSort.cs deleted file mode 100644 index f4524fa9b7..0000000000 --- a/src/ImageSharp.Drawing/Shapes/PolygonClipper/IntersectNodeSort.cs +++ /dev/null @@ -1,48 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Drawing.Shapes.PolygonClipper -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Numerics; - using System.Runtime.CompilerServices; - - using Paths; - - /// - /// Compares s - /// - internal class IntersectNodeSort : IComparer - { - /// - /// Compares the specified node1. - /// - /// The node1. - /// The node2. - /// - /// 1 if node2 %gt; node1 - /// -1 if node2 $lt; node1 - /// 0 if same - /// - public int Compare(IntersectNode node1, IntersectNode node2) - { - float i = node2.Pt.Y - node1.Pt.Y; - if (i > 0) - { - return 1; - } - else if (i < 0) - { - return -1; - } - else - { - return 0; - } - } - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Shapes/PolygonClipper/Join.cs b/src/ImageSharp.Drawing/Shapes/PolygonClipper/Join.cs deleted file mode 100644 index be948fbf74..0000000000 --- a/src/ImageSharp.Drawing/Shapes/PolygonClipper/Join.cs +++ /dev/null @@ -1,38 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Drawing.Shapes.PolygonClipper -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Numerics; - using System.Runtime.CompilerServices; - - using Paths; - - /// - /// ?? - /// - internal class Join - { -#pragma warning disable SA1401 // Field must be private - /// - /// The out PT1 - /// - internal OutPt OutPt1; - - /// - /// The out PT2 - /// - internal OutPt OutPt2; - - /// - /// The off pt - /// - internal System.Numerics.Vector2 OffPt; -#pragma warning restore SA1401 // Field must be private - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Shapes/PolygonClipper/LocalMinima.cs b/src/ImageSharp.Drawing/Shapes/PolygonClipper/LocalMinima.cs deleted file mode 100644 index b48a53cab2..0000000000 --- a/src/ImageSharp.Drawing/Shapes/PolygonClipper/LocalMinima.cs +++ /dev/null @@ -1,44 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Drawing.Shapes.PolygonClipper -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Numerics; - using System.Runtime.CompilerServices; - - using Paths; - - /// - /// ?? - /// - internal class LocalMinima - { -#pragma warning disable SA1401 // Field must be private - /// - /// The y - /// - internal float Y; - - /// - /// The left bound - /// - internal TEdge LeftBound; - - /// - /// The right bound - /// - internal TEdge RightBound; - - /// - /// The next - /// - internal LocalMinima Next; - -#pragma warning restore SA1401 // Field must be private - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Shapes/PolygonClipper/Maxima.cs b/src/ImageSharp.Drawing/Shapes/PolygonClipper/Maxima.cs deleted file mode 100644 index 85168e8e80..0000000000 --- a/src/ImageSharp.Drawing/Shapes/PolygonClipper/Maxima.cs +++ /dev/null @@ -1,38 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Drawing.Shapes.PolygonClipper -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Numerics; - using System.Runtime.CompilerServices; - - using Paths; - - /// - /// ?? - /// - internal class Maxima - { -#pragma warning disable SA1401 // Field must be private - /// - /// The x - /// - internal float X; - - /// - /// The next - /// - internal Maxima Next; - - /// - /// The previous - /// - internal Maxima Prev; -#pragma warning restore SA1401 // Field must be private - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Shapes/PolygonClipper/OutPt.cs b/src/ImageSharp.Drawing/Shapes/PolygonClipper/OutPt.cs deleted file mode 100644 index 8dae5780ad..0000000000 --- a/src/ImageSharp.Drawing/Shapes/PolygonClipper/OutPt.cs +++ /dev/null @@ -1,43 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Drawing.Shapes.PolygonClipper -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Numerics; - using System.Runtime.CompilerServices; - - using Paths; - - /// - /// ?? - /// - internal class OutPt - { -#pragma warning disable SA1401 // Field must be private - /// - /// The index - /// - internal int Idx; - - /// - /// The pt - /// - internal System.Numerics.Vector2 Pt; - - /// - /// The next - /// - internal OutPt Next; - - /// - /// The previous - /// - internal OutPt Prev; -#pragma warning restore SA1401 // Field must be private - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Shapes/PolygonClipper/OutRec.cs b/src/ImageSharp.Drawing/Shapes/PolygonClipper/OutRec.cs deleted file mode 100644 index 7c2d41a72e..0000000000 --- a/src/ImageSharp.Drawing/Shapes/PolygonClipper/OutRec.cs +++ /dev/null @@ -1,64 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Drawing.Shapes.PolygonClipper -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Numerics; - using System.Runtime.CompilerServices; - - using Paths; - - /// - /// OutRec: contains a path in the clipping solution. Edges in the AEL will - /// carry a pointer to an OutRec when they are part of the clipping solution. - /// - internal class OutRec - { -#pragma warning disable SA1401 // Field must be private - /// - /// The source path - /// - internal IPath SourcePath; - - /// - /// The index - /// - internal int Idx; - - /// - /// The is hole - /// - internal bool IsHole; - - /// - /// The is open - /// - internal bool IsOpen; - - /// - /// The first left - /// - internal OutRec FirstLeft; - - /// - /// The PTS - /// - internal OutPt Pts; - - /// - /// The bottom pt - /// - internal OutPt BottomPt; - - /// - /// The poly node - /// - internal PolyNode PolyNode; -#pragma warning restore SA1401 // Field must be private - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Shapes/PolygonClipper/PolyNode.cs b/src/ImageSharp.Drawing/Shapes/PolygonClipper/PolyNode.cs deleted file mode 100644 index 9d9c355040..0000000000 --- a/src/ImageSharp.Drawing/Shapes/PolygonClipper/PolyNode.cs +++ /dev/null @@ -1,179 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Drawing.Shapes.PolygonClipper -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Numerics; - using System.Runtime.CompilerServices; - - using Paths; - - /// - /// Poly Node - /// - internal class PolyNode - { -#pragma warning disable SA1401 // Field must be private - /// - /// The polygon - /// - internal List Polygon = new List(); - - /// - /// The index - /// - internal int Index; - - /// - /// The childs - /// - protected List children = new List(); - - private PolyNode parent; -#pragma warning restore SA1401 // Field must be private - - /// - /// Gets the child count. - /// - /// - /// The child count. - /// - public int ChildCount - { - get { return this.children.Count; } - } - - /// - /// Gets the contour. - /// - /// - /// The contour. - /// - public List Contour - { - get { return this.Polygon; } - } - - /// - /// Gets the childs. - /// - /// - /// The childs. - /// - public List Children - { - get { return this.children; } - } - - /// - /// Gets or sets the parent. - /// - /// - /// The parent. - /// - public PolyNode Parent - { - get { return this.parent; } - internal set { this.parent = value; } - } - - /// - /// Gets a value indicating whether this instance is hole. - /// - /// - /// true if this instance is hole; otherwise, false. - /// - public bool IsHole - { - get { return this.IsHoleNode(); } - } - - /// - /// Gets or sets a value indicating whether this instance is open. - /// - /// - /// true if this instance is open; otherwise, false. - /// - public bool IsOpen { get; set; } - - /// - /// Gets or sets the source path. - /// - /// - /// The source path. - /// - public IPath SourcePath { get; internal set; } - - /// - /// Gets the next. - /// - /// The next node - public PolyNode GetNext() - { - if (this.children.Count > 0) - { - return this.children[0]; - } - else - { - return this.GetNextSiblingUp(); - } - } - - /// - /// Adds the child. - /// - /// The child. - internal void AddChild(PolyNode child) - { - int cnt = this.children.Count; - this.children.Add(child); - child.parent = this; - child.Index = cnt; - } - - /// - /// Gets the next sibling up. - /// - /// The next sibling up - internal PolyNode GetNextSiblingUp() - { - if (this.parent == null) - { - return null; - } - else if (this.Index == this.parent.children.Count - 1) - { - return this.parent.GetNextSiblingUp(); - } - else - { - return this.parent.Children[this.Index + 1]; - } - } - - /// - /// Determines whether [is hole node]. - /// - /// - /// true if [is hole node]; otherwise, false. - /// - private bool IsHoleNode() - { - bool result = true; - PolyNode node = this.parent; - while (node != null) - { - result = !result; - node = node.parent; - } - - return result; - } - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Shapes/PolygonClipper/PolyTree.cs b/src/ImageSharp.Drawing/Shapes/PolygonClipper/PolyTree.cs deleted file mode 100644 index 3c35f389ce..0000000000 --- a/src/ImageSharp.Drawing/Shapes/PolygonClipper/PolyTree.cs +++ /dev/null @@ -1,81 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Drawing.Shapes.PolygonClipper -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Numerics; - using System.Runtime.CompilerServices; - - using Paths; - - /// - /// Poly Tree - /// - /// - internal class PolyTree : PolyNode - { -#pragma warning disable SA1401 // Field must be private - /// - /// All polys - /// - internal List AllPolys = new List(); -#pragma warning restore SA1401 // Field must be private - - /// - /// Gets the total. - /// - /// - /// The total. - /// - public int Total - { - get - { - int result = this.AllPolys.Count; - - // with negative offsets, ignore the hidden outer polygon ... - if (result > 0 && this.Children[0] != this.AllPolys[0]) - { - result--; - } - - return result; - } - } - - /// - /// Clears this instance. - /// - public void Clear() - { - for (int i = 0; i < this.AllPolys.Count; i++) - { - this.AllPolys[i] = null; - } - - this.AllPolys.Clear(); - this.Children.Clear(); - } - - /// - /// Gets the first. - /// - /// the first node - public PolyNode GetFirst() - { - if (this.Children.Count > 0) - { - return this.Children[0]; - } - else - { - return null; - } - } - } -} diff --git a/src/ImageSharp.Drawing/Shapes/PolygonClipper/PolyType.cs b/src/ImageSharp.Drawing/Shapes/PolygonClipper/PolyType.cs deleted file mode 100644 index 2a130f509a..0000000000 --- a/src/ImageSharp.Drawing/Shapes/PolygonClipper/PolyType.cs +++ /dev/null @@ -1,31 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Drawing.Shapes.PolygonClipper -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Numerics; - using System.Runtime.CompilerServices; - - using Paths; - - /// - /// Poly Type - /// - internal enum PolyType - { - /// - /// The subject - /// - Subject, - - /// - /// The clip - /// - Clip - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Shapes/PolygonClipper/README.md b/src/ImageSharp.Drawing/Shapes/PolygonClipper/README.md deleted file mode 100644 index c0f2ff65fb..0000000000 --- a/src/ImageSharp.Drawing/Shapes/PolygonClipper/README.md +++ /dev/null @@ -1,40 +0,0 @@ -# Clipper - -License details for code in this folder, this is code original written by **Angus Johnson** - -The license header onthe original file which has now be split across multiple files in this folder. - -``` -/******************************************************************************* -* * -* Author : Angus Johnson * -* Version : 6.4.0 * -* Date : 2 July 2015 * -* Website : http://www.angusj.com * -* Copyright : Angus Johnson 2010-2015 * -* * -* License: * -* Use, modification & distribution is subject to Boost Software License Ver 1. * -* http://www.boost.org/LICENSE_1_0.txt * -* * -* Attributions: * -* The code in this library is an extension of Bala Vatti's clipping algorithm: * -* "A generic solution to polygon clipping" * -* Communications of the ACM, Vol 35, Issue 7 (July 1992) pp 56-63. * -* http://portal.acm.org/citation.cfm?id=129906 * -* * -* Computer graphics and geometric modeling: implementation and algorithms * -* By Max K. Agoston * -* Springer; 1 edition (January 4, 2005) * -* http://books.google.com/books?q=vatti+clipping+agoston * -* * -* See also: * -* "Polygon Offsetting by Computing Winding Numbers" * -* Paper no. DETC2005-85513 pp. 565-575 * -* ASME 2005 International Design Engineering Technical Conferences * -* and Computers and Information in Engineering Conference (IDETC/CIE2005) * -* September 24-28, 2005 , Long Beach, California, USA * -* http://www.me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf * -* * -*******************************************************************************/ -``` \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Shapes/PolygonClipper/Scanbeam.cs b/src/ImageSharp.Drawing/Shapes/PolygonClipper/Scanbeam.cs deleted file mode 100644 index 28b341004a..0000000000 --- a/src/ImageSharp.Drawing/Shapes/PolygonClipper/Scanbeam.cs +++ /dev/null @@ -1,33 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Drawing.Shapes.PolygonClipper -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Numerics; - using System.Runtime.CompilerServices; - - using Paths; - - /// - /// Scanbeam - /// - internal class Scanbeam // would this work as a struct? - { -#pragma warning disable SA1401 // Field must be private - /// - /// The y - /// - internal float Y; - - /// - /// The next - /// - internal Scanbeam Next; -#pragma warning restore SA1401 // Field must be private - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Shapes/PolygonClipper/TEdge.cs b/src/ImageSharp.Drawing/Shapes/PolygonClipper/TEdge.cs deleted file mode 100644 index 97f5b2ec7b..0000000000 --- a/src/ImageSharp.Drawing/Shapes/PolygonClipper/TEdge.cs +++ /dev/null @@ -1,118 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Drawing.Shapes.PolygonClipper -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Numerics; - using System.Runtime.CompilerServices; - - using Paths; - - /// - /// TEdge - /// - internal class TEdge - { -#pragma warning disable SA1401 // Field must be private - /// - /// The source path, see if we can link this back later - /// - internal IPath SourcePath; - - /// - /// The bot - /// - internal System.Numerics.Vector2 Bot; - - /// - /// The current (updated for every new scanbeam) - /// - internal System.Numerics.Vector2 Curr; - - /// - /// The top - /// - internal System.Numerics.Vector2 Top; - - /// - /// The delta - /// - internal System.Numerics.Vector2 Delta; - - /// - /// The dx - /// - internal double Dx; - - /// - /// The poly type - /// - internal PolyType PolyTyp; - - /// - /// Side only refers to current side of solution poly - /// - internal EdgeSide Side; - - /// - /// 1 or -1 depending on winding direction - /// - internal int WindDelta; - - /// - /// The winding count - /// - internal int WindCnt; - - /// - /// The winding count of the opposite polytype - /// - internal int WindCnt2; - - /// - /// The out index - /// - internal int OutIdx; - - /// - /// The next - /// - internal TEdge Next; - - /// - /// The previous - /// - internal TEdge Prev; - - /// - /// The next in LML - /// - internal TEdge NextInLML; - - /// - /// The next in ael - /// - internal TEdge NextInAEL; - - /// - /// The previous in ael - /// - internal TEdge PrevInAEL; - - /// - /// The next in sel - /// - internal TEdge NextInSEL; - - /// - /// The previous in sel - /// - internal TEdge PrevInSEL; -#pragma warning restore SA1401 // Field must be - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Shapes/RectangularPolygon.cs b/src/ImageSharp.Drawing/Shapes/RectangularPolygon.cs deleted file mode 100644 index 5002bee406..0000000000 --- a/src/ImageSharp.Drawing/Shapes/RectangularPolygon.cs +++ /dev/null @@ -1,281 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Drawing.Shapes -{ - using System; - using System.Collections; - using System.Collections.Generic; - using System.Linq; - using System.Numerics; - using System.Threading.Tasks; - using Paths; - - /// - /// A way of optermising drawing rectangles. - /// - /// - public class RectangularPolygon : IShape, IPath - { - private readonly RectangleF rectangle; - private readonly Vector2 topLeft; - private readonly Vector2 bottomRight; - private readonly Vector2[] points; - private readonly IEnumerable pathCollection; - private readonly float halfLength; - - /// - /// Initializes a new instance of the class. - /// - /// The rect. - public RectangularPolygon(ImageSharp.Rectangle rect) - : this((RectangleF)rect) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The rect. - public RectangularPolygon(ImageSharp.RectangleF rect) - { - this.rectangle = rect; - this.points = new Vector2[4] - { - this.topLeft = new Vector2(rect.Left, rect.Top), - new Vector2(rect.Right, rect.Top), - this.bottomRight = new Vector2(rect.Right, rect.Bottom), - new Vector2(rect.Left, rect.Bottom) - }; - - this.halfLength = this.rectangle.Width + this.rectangle.Height; - this.Length = this.halfLength * 2; - this.pathCollection = new[] { this }; - } - - /// - /// Gets the bounding box of this shape. - /// - /// - /// The bounds. - /// - public RectangleF Bounds => this.rectangle; - - /// - /// Gets a value indicating whether this instance is closed. - /// - /// - /// true if this instance is closed; otherwise, false. - /// - public bool IsClosed => true; - - /// - /// Gets the length of the path - /// - /// - /// The length. - /// - public float Length { get; } - - /// - /// Gets the maximum number intersections that a shape can have when testing a line. - /// - /// - /// The maximum intersections. - /// - public int MaxIntersections => 4; - - /// - /// Calculates the distance along and away from the path for a specified point. - /// - /// The point along the path. - /// - /// Returns details about the point and its distance away from the path. - /// - PointInfo IPath.Distance(Vector2 point) - { - bool inside; // dont care about inside/outside for paths just distance - return this.Distance(point, false, out inside); - } - - /// - /// the distance of the point from the outline of the shape, if the value is negative it is inside the polygon bounds - /// - /// The point. - /// - /// Returns the distance from the shape to the point - /// - public float Distance(Vector2 point) - { - bool insidePoly; - PointInfo result = this.Distance(point, true, out insidePoly); - - // invert the distance from path when inside - return insidePoly ? -result.DistanceFromPath : result.DistanceFromPath; - } - - /// - /// Returns an enumerator that iterates through the collection. - /// - /// - /// An enumerator that can be used to iterate through the collection. - /// - public IEnumerator GetEnumerator() - { - return this.pathCollection.GetEnumerator(); - } - - /// - /// Returns an enumerator that iterates through a collection. - /// - /// - /// An object that can be used to iterate through the collection. - /// - IEnumerator IEnumerable.GetEnumerator() - { - return this.pathCollection.GetEnumerator(); - } - - /// - /// Converts the into a simple linear path.. - /// - /// - /// Returns the current as simple linear path. - /// - public Vector2[] AsSimpleLinearPath() - { - return this.points; - } - - /// - /// Based on a line described by and - /// populate a buffer for all points on the edges of the - /// that the line intersects. - /// - /// The start point of the line. - /// The end point of the line. - /// The buffer that will be populated with intersections. - /// The count. - /// The offset. - /// - /// The number of intersections populated into the buffer. - /// - public int FindIntersections(Vector2 start, Vector2 end, Vector2[] buffer, int count, int offset) - { - int discovered = 0; - Vector2 startPoint = Vector2.Clamp(start, this.topLeft, this.bottomRight); - Vector2 endPoint = Vector2.Clamp(end, this.topLeft, this.bottomRight); - - if (startPoint == Vector2.Clamp(startPoint, start, end)) - { - // if start closest is within line then its a valid point - discovered++; - buffer[offset++] = startPoint; - } - - if (endPoint == Vector2.Clamp(endPoint, start, end)) - { - // if start closest is within line then its a valid point - discovered++; - buffer[offset++] = endPoint; - } - - return discovered; - } - - private PointInfo Distance(Vector2 point, bool getDistanceAwayOnly, out bool isInside) - { - // point in rectangle - // if after its clamped by the extreams its still the same then it must be inside :) - Vector2 clamped = Vector2.Clamp(point, this.topLeft, this.bottomRight); - isInside = clamped == point; - - float distanceFromEdge = float.MaxValue; - float distanceAlongEdge = 0f; - - if (isInside) - { - // get the absolute distances from the extreams - Vector2 topLeftDist = Vector2.Abs(point - this.topLeft); - Vector2 bottomRightDist = Vector2.Abs(point - this.bottomRight); - - // get the min components - Vector2 minDists = Vector2.Min(topLeftDist, bottomRightDist); - - // and then the single smallest (dont have to worry about direction) - distanceFromEdge = Math.Min(minDists.X, minDists.Y); - - if (!getDistanceAwayOnly) - { - // we need to make clamped the closest point - if (this.topLeft.X + distanceFromEdge == point.X) - { - // closer to lhf - clamped.X = this.topLeft.X; // y is already the same - - // distance along edge is length minus the amout down we are from the top of the rect - distanceAlongEdge = this.Length - (clamped.Y - this.topLeft.Y); - } - else if (this.topLeft.Y + distanceFromEdge == point.Y) - { - // closer to top - clamped.Y = this.topLeft.Y; // x is already the same - - distanceAlongEdge = clamped.X - this.topLeft.X; - } - else if (this.bottomRight.Y - distanceFromEdge == point.Y) - { - // closer to bottom - clamped.Y = this.bottomRight.Y; // x is already the same - - distanceAlongEdge = (this.bottomRight.X - clamped.X) + this.halfLength; - } - else if (this.bottomRight.X - distanceFromEdge == point.X) - { - // closer to rhs - clamped.X = this.bottomRight.X; // x is already the same - - distanceAlongEdge = (this.bottomRight.Y - clamped.Y) + this.rectangle.Width; - } - } - } - else - { - // clamped is the point on the path thats closest no matter what - distanceFromEdge = (clamped - point).Length(); - - if (!getDistanceAwayOnly) - { - // we need to figure out whats the cloests edge now and thus what distance/poitn is closest - if (this.topLeft.X == clamped.X) - { - // distance along edge is length minus the amout down we are from the top of the rect - distanceAlongEdge = this.Length - (clamped.Y - this.topLeft.Y); - } - else if (this.topLeft.Y == clamped.Y) - { - distanceAlongEdge = clamped.X - this.topLeft.X; - } - else if (this.bottomRight.Y == clamped.Y) - { - distanceAlongEdge = (this.bottomRight.X - clamped.X) + this.halfLength; - } - else if (this.bottomRight.X == clamped.X) - { - distanceAlongEdge = (this.bottomRight.Y - clamped.Y) + this.rectangle.Width; - } - } - } - - return new PointInfo - { - SearchPoint = point, - DistanceFromPath = distanceFromEdge, - ClosestPointOnPath = clamped, - DistanceAlongPath = distanceAlongEdge - }; - } - } -} diff --git a/src/ImageSharp.Drawing/project.json b/src/ImageSharp.Drawing/project.json index b974684a5d..f0d4c62432 100644 --- a/src/ImageSharp.Drawing/project.json +++ b/src/ImageSharp.Drawing/project.json @@ -46,6 +46,7 @@ "target": "project", "version": "1.0.0-*" }, + "SixLabors.Shapes": "0.1.0-ci0043", "StyleCop.Analyzers": { "version": "1.0.0", "type": "build" diff --git a/tests/ImageSharp.Benchmarks/Drawing/FillRectangle.cs b/tests/ImageSharp.Benchmarks/Drawing/FillRectangle.cs index 8e5c18d27a..defb0e65e7 100644 --- a/tests/ImageSharp.Benchmarks/Drawing/FillRectangle.cs +++ b/tests/ImageSharp.Benchmarks/Drawing/FillRectangle.cs @@ -39,7 +39,7 @@ namespace ImageSharp.Benchmarks { using (CoreImage image = new CoreImage(800, 800)) { - image.Fill(CoreColor.HotPink, new ImageSharp.Drawing.Shapes.RectangularPolygon(new CoreRectangle(10, 10, 190, 140))); + image.Fill(CoreColor.HotPink, new SixLabors.Shapes.Rectangle(10, 10, 190, 140)); return new CoreSize(image.Width, image.Height); } @@ -53,10 +53,10 @@ namespace ImageSharp.Benchmarks image.FillPolygon( CoreColor.HotPink, new[] { - new Vector2(10, 10), - new Vector2(200, 10), - new Vector2(200, 150), - new Vector2(10, 150) }); + new Vector2(10, 10), + new Vector2(200, 10), + new Vector2(200, 150), + new Vector2(10, 150) }); return new CoreSize(image.Width, image.Height); } From 4e745d728068a4dcdfb4d5bc7e5ca05db4170a38 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Mon, 30 Jan 2017 21:43:14 +0000 Subject: [PATCH 014/142] added IRegion and droped use of vectors in brushes --- .travis.yml | 4 +- ConsoleApp1/ConsoleApp1.csproj | 8 + ConsoleApp1/Program.cs | 9 + .../Brushes/ImageBrush{TColor}.cs | 20 +- .../Brushes/PatternBrush{TColor}.cs | 18 +- .../Brushes/Processors/BrushApplicator.cs | 16 +- .../Brushes/RecolorBrush{TColor}.cs | 31 +-- .../Brushes/SolidBrush{TColor}.cs | 8 +- src/ImageSharp.Drawing/Draw.cs | 6 +- src/ImageSharp.Drawing/DrawRectangle.cs | 2 +- src/ImageSharp.Drawing/Fill.cs | 4 +- src/ImageSharp.Drawing/FillRectangle.cs | 4 +- src/ImageSharp.Drawing/IDrawableRegion.cs | 24 +++ src/ImageSharp.Drawing/IRegion.cs | 51 +++++ src/ImageSharp.Drawing/Pens/Pen{TColor}.cs | 8 +- .../Pens/Processors/PenApplicator.cs | 9 +- .../{Pens/Processors => }/PointInfo.cs | 7 +- .../Processors/DrawPathProcessor.cs | 75 +------- .../Processors/FillProcessor.cs | 7 +- .../Processors/FillShapeProcessor.cs | 153 +++++---------- .../Processors/PointInfoExtensions.cs | 5 +- src/ImageSharp.Drawing/ShapeRegion.cs | 180 ++++++++++++++++++ src/ImageSharp.Drawing/project.json | 8 +- .../ImageSharp.Sandbox46.csproj | 3 + .../ImageSharp.Tests/Drawing/DrawPathTests.cs | 5 +- .../Drawing/Helpers/BezierPolygon.cs | 71 +++++++ .../Drawing/Helpers/LinearPolygon.cs | 71 +++++++ .../Drawing/LineComplexPolygonTests.cs | 22 ++- .../Drawing/SolidBezierTests.cs | 3 + .../Drawing/SolidComplexPolygonTests.cs | 16 +- .../Drawing/SolidPolygonTests.cs | 9 +- 31 files changed, 596 insertions(+), 261 deletions(-) create mode 100644 ConsoleApp1/ConsoleApp1.csproj create mode 100644 ConsoleApp1/Program.cs create mode 100644 src/ImageSharp.Drawing/IDrawableRegion.cs create mode 100644 src/ImageSharp.Drawing/IRegion.cs rename src/ImageSharp.Drawing/{Pens/Processors => }/PointInfo.cs (81%) create mode 100644 src/ImageSharp.Drawing/ShapeRegion.cs create mode 100644 tests/ImageSharp.Tests/Drawing/Helpers/BezierPolygon.cs create mode 100644 tests/ImageSharp.Tests/Drawing/Helpers/LinearPolygon.cs diff --git a/.travis.yml b/.travis.yml index 172079df24..ca8b90a06a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,8 +20,8 @@ branches: script: - dotnet restore - - dotnet build -c Release src/*/project.json - - dotnet test tests/ImageSharp.Tests/project.json -c Release -f "netcoreapp1.1" + - dotnet build -c Release src/*/project.csproj + - dotnet test tests/ImageSharp.Tests/project.csproj -c Release -f "netcoreapp1.1" env: global: diff --git a/ConsoleApp1/ConsoleApp1.csproj b/ConsoleApp1/ConsoleApp1.csproj new file mode 100644 index 0000000000..68b6f24237 --- /dev/null +++ b/ConsoleApp1/ConsoleApp1.csproj @@ -0,0 +1,8 @@ + + + + Exe + netcoreapp1.0 + + + \ No newline at end of file diff --git a/ConsoleApp1/Program.cs b/ConsoleApp1/Program.cs new file mode 100644 index 0000000000..104ecf0262 --- /dev/null +++ b/ConsoleApp1/Program.cs @@ -0,0 +1,9 @@ +using System; + +class Program +{ + static void Main(string[] args) + { + Console.WriteLine("Hello World!"); + } +} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs index 9ce235a847..b9bcc2f9a0 100644 --- a/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs +++ b/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs @@ -82,18 +82,24 @@ namespace ImageSharp.Drawing.Brushes /// /// Gets the color for a single pixel. /// - /// The point. + /// The x. + /// The y. /// /// The color /// - public override TColor GetColor(Vector2 point) + public override TColor this[int x, int y] { - // Offset the requested pixel by the value in the rectangle (the shapes position) - point = point - this.offset; - int x = (int)point.X % this.xLength; - int y = (int)point.Y % this.yLength; + get + { + var point = new Vector2(x, y); - return this.source[x, y]; + // Offset the requested pixel by the value in the rectangle (the shapes position) + point = point - this.offset; + x = (int)point.X % this.xLength; + y = (int)point.Y % this.yLength; + + return this.source[x, y]; + } } /// diff --git a/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs index 7749f5ba84..3fea53052d 100644 --- a/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs +++ b/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs @@ -134,17 +134,21 @@ namespace ImageSharp.Drawing.Brushes /// /// Gets the color for a single pixel. - /// - /// The point. + /// # + /// The x. + /// The y. /// - /// The color + /// The Color. /// - public override TColor GetColor(Vector2 point) + public override TColor this[int x, int y] { - int x = (int)point.X % this.xLength; - int y = (int)point.Y % this.stride; + get + { + x = x % this.xLength; + y = y % this.stride; - return this.pattern[x][y]; + return this.pattern[x][y]; + } } /// diff --git a/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs b/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs index 885be57157..9103dfdf66 100644 --- a/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs +++ b/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs @@ -16,16 +16,20 @@ namespace ImageSharp.Drawing.Processors public abstract class BrushApplicator : IDisposable // disposable will be required if/when there is an ImageBrush where TColor : struct, IPackedPixel, IEquatable { + /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// Gets the color for a single pixel. /// - public abstract void Dispose(); + /// The x. + /// The y. + /// + /// The color + /// + public abstract TColor this[int x, int y] { get; } /// - /// Gets the color for a single pixel. + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// - /// The point. - /// The color - public abstract TColor GetColor(Vector2 point); + public abstract void Dispose(); } } diff --git a/src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs index 7149f22a01..33403facb2 100644 --- a/src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs +++ b/src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs @@ -109,24 +109,31 @@ namespace ImageSharp.Drawing.Brushes /// /// Gets the color for a single pixel. /// - /// The point. + /// The x. + /// The y. /// /// The color /// - public override TColor GetColor(Vector2 point) + public override TColor this[int x, int y] { - // Offset the requested pixel by the value in the rectangle (the shapes position) - TColor result = this.source[(int)point.X, (int)point.Y]; - Vector4 background = result.ToVector4(); - float distance = Vector4.DistanceSquared(background, this.sourceColor); - if (distance <= this.threshold) + get { - var lerpAmount = (this.threshold - distance) / this.threshold; - Vector4 blended = Vector4BlendTransforms.PremultipliedLerp(background, this.targetColor, lerpAmount); - result.PackFromVector4(blended); + // Offset the requested pixel by the value in the rectangle (the shapes position) + TColor result = this.source[x, y]; + Vector4 background = result.ToVector4(); + float distance = Vector4.DistanceSquared(background, this.sourceColor); + if (distance <= this.threshold) + { + var lerpAmount = (this.threshold - distance) / this.threshold; + Vector4 blended = Vector4BlendTransforms.PremultipliedLerp( + background, + this.targetColor, + lerpAmount); + result.PackFromVector4(blended); + } + + return result; } - - return result; } /// diff --git a/src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs index c3e3113992..018c272b7f 100644 --- a/src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs +++ b/src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs @@ -67,14 +67,12 @@ namespace ImageSharp.Drawing.Brushes /// /// Gets the color for a single pixel. /// - /// The point. + /// The x. + /// The y. /// /// The color /// - public override TColor GetColor(Vector2 point) - { - return this.color; - } + public override TColor this[int x, int y] => this.color; /// public override void Dispose() diff --git a/src/ImageSharp.Drawing/Draw.cs b/src/ImageSharp.Drawing/Draw.cs index 8c3dbb1b5a..55dba9257d 100644 --- a/src/ImageSharp.Drawing/Draw.cs +++ b/src/ImageSharp.Drawing/Draw.cs @@ -32,7 +32,7 @@ namespace ImageSharp public static Image DrawPolygon(this Image source, IPen pen, IShape shape, GraphicsOptions options) where TColor : struct, IPackedPixel, IEquatable { - return source.Apply(new DrawPathProcessor(pen, shape, options)); + return source.Apply(new DrawPathProcessor(pen, new ShapeRegion(shape), options)); } /// @@ -226,7 +226,7 @@ namespace ImageSharp public static Image DrawPath(this Image source, IPen pen, IPath path, GraphicsOptions options) where TColor : struct, IPackedPixel, IEquatable { - return source.Apply(new DrawPathProcessor(pen, path, options)); + return source.Apply(new DrawPathProcessor(pen, new ShapeRegion(path), options)); } /// @@ -240,7 +240,7 @@ namespace ImageSharp public static Image DrawPath(this Image source, IPen pen, IPath path) where TColor : struct, IPackedPixel, IEquatable { - return source.Apply(new DrawPathProcessor(pen, path, GraphicsOptions.Default)); + return source.Apply(new DrawPathProcessor(pen, new ShapeRegion(path), GraphicsOptions.Default)); } /// diff --git a/src/ImageSharp.Drawing/DrawRectangle.cs b/src/ImageSharp.Drawing/DrawRectangle.cs index 17d6d5f368..243aa082d4 100644 --- a/src/ImageSharp.Drawing/DrawRectangle.cs +++ b/src/ImageSharp.Drawing/DrawRectangle.cs @@ -33,7 +33,7 @@ namespace ImageSharp public static Image DrawPolygon(this Image source, IPen pen, RectangleF shape, GraphicsOptions options) where TColor : struct, IPackedPixel, IEquatable { - return source.Apply(new DrawPathProcessor(pen, (IPath)new SixLabors.Shapes.Rectangle(shape.X, shape.Y, shape.Width, shape.Height), options)); + return source.Apply(new DrawPathProcessor(pen, new ShapeRegion(new SixLabors.Shapes.Rectangle(shape.X, shape.Y, shape.Width, shape.Height)), options)); } /// diff --git a/src/ImageSharp.Drawing/Fill.cs b/src/ImageSharp.Drawing/Fill.cs index 637950e76d..f94d9ca6f9 100644 --- a/src/ImageSharp.Drawing/Fill.cs +++ b/src/ImageSharp.Drawing/Fill.cs @@ -56,7 +56,7 @@ namespace ImageSharp public static Image Fill(this Image source, IBrush brush, IShape shape, GraphicsOptions options) where TColor : struct, IPackedPixel, IEquatable { - return source.Apply(new FillShapeProcessor(brush, shape, options)); + return source.Apply(new FillShapeProcessor(brush, new ShapeRegion(shape), options)); } /// @@ -70,7 +70,7 @@ namespace ImageSharp public static Image Fill(this Image source, IBrush brush, IShape shape) where TColor : struct, IPackedPixel, IEquatable { - return source.Apply(new FillShapeProcessor(brush, shape, GraphicsOptions.Default)); + return source.Apply(new FillShapeProcessor(brush, new ShapeRegion(shape), GraphicsOptions.Default)); } /// diff --git a/src/ImageSharp.Drawing/FillRectangle.cs b/src/ImageSharp.Drawing/FillRectangle.cs index 7749e7c921..d60a5a19d8 100644 --- a/src/ImageSharp.Drawing/FillRectangle.cs +++ b/src/ImageSharp.Drawing/FillRectangle.cs @@ -30,7 +30,7 @@ namespace ImageSharp public static Image Fill(this Image source, IBrush brush, RectangleF shape, GraphicsOptions options) where TColor : struct, IPackedPixel, IEquatable { - return source.Apply(new FillShapeProcessor(brush, new SixLabors.Shapes.Rectangle(shape.X, shape.Y, shape.Width, shape.Height), options)); + return source.Fill(brush, new SixLabors.Shapes.Rectangle(shape.X, shape.Y, shape.Width, shape.Height), options); } /// @@ -44,7 +44,7 @@ namespace ImageSharp public static Image Fill(this Image source, IBrush brush, RectangleF shape) where TColor : struct, IPackedPixel, IEquatable { - return source.Apply(new FillShapeProcessor(brush, new SixLabors.Shapes.Rectangle(shape.X, shape.Y, shape.Width, shape.Height), GraphicsOptions.Default)); + return source.Fill(brush, new SixLabors.Shapes.Rectangle(shape.X, shape.Y, shape.Width, shape.Height)); } /// diff --git a/src/ImageSharp.Drawing/IDrawableRegion.cs b/src/ImageSharp.Drawing/IDrawableRegion.cs new file mode 100644 index 0000000000..82e9c39acf --- /dev/null +++ b/src/ImageSharp.Drawing/IDrawableRegion.cs @@ -0,0 +1,24 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Drawing +{ + using System.Numerics; + + /// + /// Represents a region with knowledge about its outline. + /// + /// + public interface IDrawableRegion : IRegion + { + /// + /// Gets the point information for the specified x and y location. + /// + /// The x. + /// The y. + /// Information about the point in relation to a drawable edge + PointInfo GetPointInfo(int x, int y); + } +} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/IRegion.cs b/src/ImageSharp.Drawing/IRegion.cs new file mode 100644 index 0000000000..2264a91bee --- /dev/null +++ b/src/ImageSharp.Drawing/IRegion.cs @@ -0,0 +1,51 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Drawing +{ + using System.Numerics; + + /// + /// Represents a region of an image. + /// + public interface IRegion + { + /// + /// Gets the maximum number of intersections to could be returned. + /// + /// + /// The maximum intersections. + /// + int MaxIntersections { get; } + + /// + /// Gets the bounds. + /// + /// + /// The bounds. + /// + Rectangle Bounds { get; } + + /// + /// Scans the X axis for intersections. + /// + /// The x. + /// The buffer. + /// The length. + /// The offset. + /// The number of intersections found. + int ScanX(int x, float[] buffer, int length, int offset); + + /// + /// Scans the Y axis for intersections. + /// + /// The position along the y axis to find intersections. + /// The buffer. + /// The length. + /// The offset. + /// The number of intersections found. + int ScanY(int y, float[] buffer, int length, int offset); + } +} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Pens/Pen{TColor}.cs b/src/ImageSharp.Drawing/Pens/Pen{TColor}.cs index bbb3c25597..d0ea55c1e7 100644 --- a/src/ImageSharp.Drawing/Pens/Pen{TColor}.cs +++ b/src/ImageSharp.Drawing/Pens/Pen{TColor}.cs @@ -144,10 +144,10 @@ namespace ImageSharp.Drawing.Pens this.brush.Dispose(); } - public override ColoredPointInfo GetColor(PointInfo info) + public override ColoredPointInfo GetColor(int x, int y, PointInfo info) { var result = default(ColoredPointInfo); - result.Color = this.brush.GetColor(info.SearchPoint); + result.Color = this.brush[x, y]; if (info.DistanceFromPath < this.halfWidth) { @@ -197,7 +197,7 @@ namespace ImageSharp.Drawing.Pens this.brush.Dispose(); } - public override ColoredPointInfo GetColor(PointInfo info) + public override ColoredPointInfo GetColor(int x, int y, PointInfo info) { var infoResult = default(ColoredPointInfo); infoResult.DistanceFromElement = float.MaxValue; // is really outside the element @@ -207,7 +207,7 @@ namespace ImageSharp.Drawing.Pens // we can treat the DistanceAlongPath and DistanceFromPath as x,y coords for the pattern // we need to calcualte the distance from the outside edge of the pattern // and set them on the ColoredPointInfo along with the color. - infoResult.Color = this.brush.GetColor(info.SearchPoint); + infoResult.Color = this.brush[x, y]; float distanceWAway = 0; diff --git a/src/ImageSharp.Drawing/Pens/Processors/PenApplicator.cs b/src/ImageSharp.Drawing/Pens/Processors/PenApplicator.cs index 222598d85f..de680c809c 100644 --- a/src/ImageSharp.Drawing/Pens/Processors/PenApplicator.cs +++ b/src/ImageSharp.Drawing/Pens/Processors/PenApplicator.cs @@ -6,6 +6,7 @@ namespace ImageSharp.Drawing.Processors { using System; + using System.Numerics; /// /// primitive that converts a into a color and a distance away from the drawable part of the path. @@ -30,8 +31,12 @@ namespace ImageSharp.Drawing.Processors /// /// Gets a from a point represented by a . /// + /// The x. + /// The y. /// The information to extract color details about. - /// Returns the color details and distance from a solid bit of the line. - public abstract ColoredPointInfo GetColor(PointInfo info); + /// + /// Returns the color details and distance from a solid bit of the line. + /// + public abstract ColoredPointInfo GetColor(int x, int y, PointInfo info); } } diff --git a/src/ImageSharp.Drawing/Pens/Processors/PointInfo.cs b/src/ImageSharp.Drawing/PointInfo.cs similarity index 81% rename from src/ImageSharp.Drawing/Pens/Processors/PointInfo.cs rename to src/ImageSharp.Drawing/PointInfo.cs index 6fc78b09b6..e222a81469 100644 --- a/src/ImageSharp.Drawing/Pens/Processors/PointInfo.cs +++ b/src/ImageSharp.Drawing/PointInfo.cs @@ -3,7 +3,7 @@ // Licensed under the Apache License, Version 2.0. // -namespace ImageSharp.Drawing.Processors +namespace ImageSharp.Drawing { using System; using System.Numerics; @@ -22,10 +22,5 @@ namespace ImageSharp.Drawing.Processors /// The distance from path /// public float DistanceFromPath; - - /// - /// The search point - /// - public Vector2 SearchPoint; } } diff --git a/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs b/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs index d6b2f3eb28..bbb5e1ab6c 100644 --- a/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs @@ -27,64 +27,27 @@ namespace ImageSharp.Drawing.Processors private const int PaddingFactor = 1; // needs to been the same or greater than AntialiasFactor private readonly IPen pen; - private readonly IPath[] paths; - private readonly RectangleF region; + private readonly IDrawableRegion region; private readonly GraphicsOptions options; /// /// Initializes a new instance of the class. /// /// The pen. - /// The shape. + /// The region. /// The options. - public DrawPathProcessor(IPen pen, IShape shape, GraphicsOptions options) - : this(pen, shape.Paths, options) + public DrawPathProcessor(IPen pen, IDrawableRegion region, GraphicsOptions options) { - } - - /// - /// Initializes a new instance of the class. - /// - /// The pen. - /// The path. - /// The options. - public DrawPathProcessor(IPen pen, IPath path, GraphicsOptions options) - : this(pen, new[] { path }, options) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The pen. - /// The paths. - /// The options. - public DrawPathProcessor(IPen pen, IEnumerable paths, GraphicsOptions options) - { - this.paths = paths.ToArray(); + this.region = region; this.pen = pen; this.options = options; - - if (this.paths.Length != 1) - { - var maxX = this.paths.Max(x => x.Bounds.Right); - var minX = this.paths.Min(x => x.Bounds.Left); - var maxY = this.paths.Max(x => x.Bounds.Bottom); - var minY = this.paths.Min(x => x.Bounds.Top); - - this.region = new RectangleF(minX, minY, maxX - minX, maxY - minY); - } - else - { - this.region = this.paths[0].Bounds.Convert(); - } } /// protected override void OnApply(ImageBase source, Rectangle sourceRectangle) { using (PixelAccessor sourcePixels = source.Lock()) - using (PenApplicator applicator = this.pen.CreateApplicator(sourcePixels, this.region)) + using (PenApplicator applicator = this.pen.CreateApplicator(sourcePixels, this.region.Bounds)) { var rect = RectangleF.Ceiling(applicator.RequiredRegion); @@ -122,16 +85,14 @@ namespace ImageSharp.Drawing.Processors (int y) => { int offsetY = y - polyStartY; - var currentPoint = default(Vector2); + for (int x = minX; x < maxX; x++) { + // TODO add find intersections code to skip and scan large regions of this. int offsetX = x - startX; - currentPoint.X = offsetX; - currentPoint.Y = offsetY; + var info = this.region.GetPointInfo(offsetX, offsetY); - var dist = this.Closest(currentPoint); - - var color = applicator.GetColor(dist.Convert()); + var color = applicator.GetColor(offsetX, offsetY, info); var opacity = this.Opacity(color.DistanceFromElement); @@ -154,24 +115,6 @@ namespace ImageSharp.Drawing.Processors } } - private SixLabors.Shapes.PointInfo Closest(Vector2 point) - { - SixLabors.Shapes.PointInfo result = default(SixLabors.Shapes.PointInfo); - float distance = float.MaxValue; - - for (int i = 0; i < this.paths.Length; i++) - { - var p = this.paths[i].Distance(point); - if (p.DistanceFromPath < distance) - { - distance = p.DistanceFromPath; - result = p; - } - } - - return result; - } - private float Opacity(float distance) { if (distance <= 0) diff --git a/src/ImageSharp.Drawing/Processors/FillProcessor.cs b/src/ImageSharp.Drawing/Processors/FillProcessor.cs index dc87e6da6c..37bdac90f9 100644 --- a/src/ImageSharp.Drawing/Processors/FillProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/FillProcessor.cs @@ -71,17 +71,12 @@ namespace ImageSharp.Drawing.Processors y => { int offsetY = y - startY; - - Vector2 currentPoint = default(Vector2); for (int x = minX; x < maxX; x++) { int offsetX = x - startX; - int offsetColorX = x - minX; - currentPoint.X = offsetX; - currentPoint.Y = offsetY; Vector4 backgroundVector = sourcePixels[offsetX, offsetY].ToVector4(); - Vector4 sourceVector = applicator.GetColor(currentPoint).ToVector4(); + Vector4 sourceVector = applicator[offsetX, offsetY].ToVector4(); Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, 1); diff --git a/src/ImageSharp.Drawing/Processors/FillShapeProcessor.cs b/src/ImageSharp.Drawing/Processors/FillShapeProcessor.cs index 14e99cba37..bdec022d49 100644 --- a/src/ImageSharp.Drawing/Processors/FillShapeProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/FillShapeProcessor.cs @@ -25,18 +25,18 @@ namespace ImageSharp.Drawing.Processors private const float AntialiasFactor = 1f; private const int DrawPadding = 1; private readonly IBrush fillColor; - private readonly IShape poly; + private readonly IRegion region; private readonly GraphicsOptions options; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The brush. - /// The shape. + /// The region. /// The options. - public FillShapeProcessor(IBrush brush, IShape shape, GraphicsOptions options) + public FillShapeProcessor(IBrush brush, IRegion region, GraphicsOptions options) { - this.poly = shape; + this.region = region; this.fillColor = brush; this.options = options; } @@ -44,12 +44,12 @@ namespace ImageSharp.Drawing.Processors /// protected override void OnApply(ImageBase source, Rectangle sourceRectangle) { - Rectangle rect = RectangleF.Ceiling(this.poly.Bounds.Convert()); // rounds the points out away from the center + Rectangle rect = RectangleF.Ceiling(this.region.Bounds); // rounds the points out away from the center - int polyStartY = rect.Y - DrawPadding; - int polyEndY = rect.Bottom + DrawPadding; - int startX = rect.X - DrawPadding; - int endX = rect.Right + DrawPadding; + int polyStartY = sourceRectangle.Y - DrawPadding; + int polyEndY = sourceRectangle.Bottom + DrawPadding; + int startX = sourceRectangle.X - DrawPadding; + int endX = sourceRectangle.Right + DrawPadding; int minX = Math.Max(sourceRectangle.Left, startX); int maxX = Math.Min(sourceRectangle.Right - 1, endX); @@ -62,9 +62,9 @@ namespace ImageSharp.Drawing.Processors minY = Math.Max(0, minY); maxY = Math.Min(source.Height, maxY); - ArrayPool arrayPool = ArrayPool.Shared; + ArrayPool arrayPool = ArrayPool.Shared; - int maxIntersections = this.poly.MaxIntersections; + int maxIntersections = this.region.MaxIntersections; using (PixelAccessor sourcePixels = source.Lock()) using (BrushApplicator applicator = this.fillColor.CreateApplicator(sourcePixels, rect)) @@ -73,27 +73,26 @@ namespace ImageSharp.Drawing.Processors minY, maxY, this.ParallelOptions, - y => + (int y) => { - Vector2[] buffer = arrayPool.Rent(maxIntersections); + float[] buffer = arrayPool.Rent(maxIntersections); try { - Vector2 left = new Vector2(startX, y); - Vector2 right = new Vector2(endX, y); + float right = endX; // foreach line we get all the points where this line crosses the polygon - int pointsFound = this.poly.FindIntersections(left, right, buffer, maxIntersections, 0); + int pointsFound = this.region.ScanY(y, buffer, maxIntersections, 0); if (pointsFound == 0) { // nothign on this line skip return; } - QuickSortX(buffer, pointsFound); + QuickSort(buffer, pointsFound); int currentIntersection = 0; - float nextPoint = buffer[0].X; + float nextPoint = buffer[0]; float lastPoint = float.MinValue; bool isInside = false; @@ -108,7 +107,7 @@ namespace ImageSharp.Drawing.Processors { if (x < (nextPoint - DrawPadding) && x > (lastPoint + DrawPadding)) { - if (nextPoint == right.X) + if (nextPoint == right) { // we are in the ends run skip it x = maxX; @@ -129,11 +128,11 @@ namespace ImageSharp.Drawing.Processors lastPoint = nextPoint; if (currentIntersection == pointsFound) { - nextPoint = right.X; + nextPoint = right; } else { - nextPoint = buffer[currentIntersection].X; + nextPoint = buffer[currentIntersection]; // double point from a corner flip the bit back and move on again if (nextPoint == lastPoint) @@ -143,11 +142,11 @@ namespace ImageSharp.Drawing.Processors currentIntersection++; if (currentIntersection == pointsFound) { - nextPoint = right.X; + nextPoint = right; } else { - nextPoint = buffer[currentIntersection].X; + nextPoint = buffer[currentIntersection]; } } } @@ -192,7 +191,7 @@ namespace ImageSharp.Drawing.Processors if (opacity > Constants.Epsilon) { Vector4 backgroundVector = sourcePixels[x, y].ToVector4(); - Vector4 sourceVector = applicator.GetColor(currentPoint).ToVector4(); + Vector4 sourceVector = applicator[x, y].ToVector4(); Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity); finalColor.W = backgroundVector.W; @@ -216,42 +215,37 @@ namespace ImageSharp.Drawing.Processors minX, maxX, this.ParallelOptions, - x => + (int x) => { - Vector2[] buffer = arrayPool.Rent(maxIntersections); + float[] buffer = arrayPool.Rent(maxIntersections); try { - Vector2 left = new Vector2(x, polyStartY); - Vector2 right = new Vector2(x, polyEndY); + float left = polyStartY; + float right = polyEndY; // foreach line we get all the points where this line crosses the polygon - int pointsFound = this.poly.FindIntersections(left, right, buffer, maxIntersections, 0); + int pointsFound = this.region.ScanX(x, buffer, maxIntersections, 0); if (pointsFound == 0) { // nothign on this line skip return; } - QuickSortY(buffer, pointsFound); + QuickSort(buffer, pointsFound); int currentIntersection = 0; - float nextPoint = buffer[0].Y; - float lastPoint = left.Y; + float nextPoint = buffer[0]; + float lastPoint = left; bool isInside = false; - // every odd point is the start of a line - Vector2 currentPoint = default(Vector2); - for (int y = minY; y < maxY; y++) { - currentPoint.X = x; - currentPoint.Y = y; if (!isInside) { if (y < (nextPoint - DrawPadding) && y > (lastPoint + DrawPadding)) { - if (nextPoint == right.Y) + if (nextPoint == right) { // we are in the ends run skip it y = maxY; @@ -266,7 +260,7 @@ namespace ImageSharp.Drawing.Processors { if (y < nextPoint - DrawPadding) { - if (nextPoint == right.Y) + if (nextPoint == right) { // we are in the ends run skip it y = maxY; @@ -286,11 +280,11 @@ namespace ImageSharp.Drawing.Processors lastPoint = nextPoint; if (currentIntersection == pointsFound) { - nextPoint = right.Y; + nextPoint = right; } else { - nextPoint = buffer[currentIntersection].Y; + nextPoint = buffer[currentIntersection]; // double point from a corner flip the bit back and move on again if (nextPoint == lastPoint) @@ -300,11 +294,11 @@ namespace ImageSharp.Drawing.Processors currentIntersection++; if (currentIntersection == pointsFound) { - nextPoint = right.Y; + nextPoint = right; } else { - nextPoint = buffer[currentIntersection].Y; + nextPoint = buffer[currentIntersection]; } } } @@ -350,8 +344,7 @@ namespace ImageSharp.Drawing.Processors if (opacity > Constants.Epsilon && opacity < 1) { Vector4 backgroundVector = sourcePixels[x, y].ToVector4(); - Vector4 sourceVector = applicator.GetColor(currentPoint).ToVector4(); - + Vector4 sourceVector = applicator[x, y].ToVector4(); Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity); finalColor.W = backgroundVector.W; @@ -370,76 +363,32 @@ namespace ImageSharp.Drawing.Processors } } - private static void Swap(Vector2[] data, int left, int right) + private static void Swap(float[] data, int left, int right) { - Vector2 tmp = data[left]; + float tmp = data[left]; data[left] = data[right]; data[right] = tmp; } - private static void QuickSortY(Vector2[] data, int size) - { - int hi = Math.Min(data.Length - 1, size - 1); - QuickSortY(data, 0, hi); - } - - private static void QuickSortY(Vector2[] data, int lo, int hi) - { - if (lo < hi) - { - int p = PartitionY(data, lo, hi); - QuickSortY(data, lo, p); - QuickSortY(data, p + 1, hi); - } - } - - private static void QuickSortX(Vector2[] data, int size) + private static void QuickSort(float[] data, int size) { int hi = Math.Min(data.Length - 1, size - 1); - QuickSortX(data, 0, hi); + QuickSort(data, 0, hi); } - private static void QuickSortX(Vector2[] data, int lo, int hi) + private static void QuickSort(float[] data, int lo, int hi) { if (lo < hi) { - int p = PartitionX(data, lo, hi); - QuickSortX(data, lo, p); - QuickSortX(data, p + 1, hi); - } - } - - private static int PartitionX(Vector2[] data, int lo, int hi) - { - float pivot = data[lo].X; - int i = lo - 1; - int j = hi + 1; - while (true) - { - do - { - i = i + 1; - } - while (data[i].X < pivot && i < hi); - - do - { - j = j - 1; - } - while (data[j].X > pivot && j > lo); - - if (i >= j) - { - return j; - } - - Swap(data, i, j); + int p = Partition(data, lo, hi); + QuickSort(data, lo, p); + QuickSort(data, p + 1, hi); } } - private static int PartitionY(Vector2[] data, int lo, int hi) + private static int Partition(float[] data, int lo, int hi) { - float pivot = data[lo].Y; + float pivot = data[lo]; int i = lo - 1; int j = hi + 1; while (true) @@ -448,13 +397,13 @@ namespace ImageSharp.Drawing.Processors { i = i + 1; } - while (data[i].Y < pivot && i < hi); + while (data[i] < pivot && i < hi); do { j = j - 1; } - while (data[j].Y > pivot && j > lo); + while (data[j] > pivot && j > lo); if (i >= j) { diff --git a/src/ImageSharp.Drawing/Processors/PointInfoExtensions.cs b/src/ImageSharp.Drawing/Processors/PointInfoExtensions.cs index 939a718450..6613d15acb 100644 --- a/src/ImageSharp.Drawing/Processors/PointInfoExtensions.cs +++ b/src/ImageSharp.Drawing/Processors/PointInfoExtensions.cs @@ -12,6 +12,8 @@ namespace ImageSharp.Drawing.Processors using Drawing; using ImageSharp.Processing; using SixLabors.Shapes; + + using PointInfo = ImageSharp.Drawing.PointInfo; using Rectangle = ImageSharp.Rectangle; /// @@ -29,8 +31,7 @@ namespace ImageSharp.Drawing.Processors return new PointInfo { DistanceAlongPath = source.DistanceAlongPath, - DistanceFromPath = source.DistanceFromPath, - SearchPoint = source.SearchPoint + DistanceFromPath = source.DistanceFromPath }; } } diff --git a/src/ImageSharp.Drawing/ShapeRegion.cs b/src/ImageSharp.Drawing/ShapeRegion.cs new file mode 100644 index 0000000000..6bd0703672 --- /dev/null +++ b/src/ImageSharp.Drawing/ShapeRegion.cs @@ -0,0 +1,180 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Drawing +{ + using System.Buffers; + using System.Collections.Immutable; + using System.Numerics; + + using ImageSharp.Drawing.Processors; + + using SixLabors.Shapes; + + using Rectangle = ImageSharp.Rectangle; + + /// + /// A drawable mapping between a / and a drawable/fillable region. + /// + /// + internal class ShapeRegion : IDrawableRegion + { + /// + /// The fillable shape + /// + private readonly IShape shape; + + /// + /// The drawable paths + /// + private readonly ImmutableArray paths; + + /// + /// Initializes a new instance of the class. + /// + /// The path. + public ShapeRegion(IPath path) + : this(ImmutableArray.Create(path)) + { + this.shape = path.AsShape(); + this.Bounds = RectangleF.Ceiling(path.Bounds.Convert()); + } + + /// + /// Initializes a new instance of the class. + /// + /// The shape. + public ShapeRegion(IShape shape) + : this(shape.Paths) + { + this.shape = shape; + this.Bounds = RectangleF.Ceiling(shape.Bounds.Convert()); + } + + /// + /// Initializes a new instance of the class. + /// + /// The paths. + private ShapeRegion(ImmutableArray paths) + { + this.paths = paths; + } + + /// + /// Gets the maximum number of intersections to could be returned. + /// + /// + /// The maximum intersections. + /// + public int MaxIntersections => this.shape.MaxIntersections; + + /// + /// Gets the bounds. + /// + /// + /// The bounds. + /// + public Rectangle Bounds { get; } + + /// + /// Scans the X axis for intersections. + /// + /// The x. + /// The buffer. + /// The length. + /// The offset. + /// + /// The number of intersections found. + /// + public int ScanX(int x, float[] buffer, int length, int offset) + { + var start = new Vector2(x, this.Bounds.Top - 1); + var end = new Vector2(x, this.Bounds.Bottom + 1); + Vector2[] innerbuffer = ArrayPool.Shared.Rent(length); + try + { + int count = this.shape.FindIntersections( + start, + end, + innerbuffer, + length, + 0); + + for (var i = 0; i < count; i++) + { + buffer[i + offset] = innerbuffer[i].Y; + } + + return count; + } + finally + { + ArrayPool.Shared.Return(innerbuffer); + } + } + + /// + /// Scans the Y axis for intersections. + /// + /// The position along the y axis to find intersections. + /// The buffer. + /// The length. + /// The offset. + /// + /// The number of intersections found. + /// + public int ScanY(int y, float[] buffer, int length, int offset) + { + var start = new Vector2(this.Bounds.Left - 1, y); + var end = new Vector2(this.Bounds.Right + 1, y); + Vector2[] innerbuffer = ArrayPool.Shared.Rent(length); + try + { + int count = this.shape.FindIntersections( + start, + end, + innerbuffer, + length, + 0); + + for (var i = 0; i < count; i++) + { + buffer[i + offset] = innerbuffer[i].X; + } + + return count; + } + finally + { + ArrayPool.Shared.Return(innerbuffer); + } + } + + /// + /// Gets the point information for the specified x and y location. + /// + /// The x. + /// The y. + /// Information about the the point + public PointInfo GetPointInfo(int x, int y) + { + var point = new Vector2(x, y); + SixLabors.Shapes.PointInfo result = default(SixLabors.Shapes.PointInfo); + float distance = float.MaxValue; + + for (int i = 0; i < this.paths.Length; i++) + { + var p = this.paths[i].Distance(point); + if (p.DistanceFromPath < distance) + { + distance = p.DistanceFromPath; + result = p; + } + } + + return result.Convert(); + } + } +} diff --git a/src/ImageSharp.Drawing/project.json b/src/ImageSharp.Drawing/project.json index f0d4c62432..5fcee9b41e 100644 --- a/src/ImageSharp.Drawing/project.json +++ b/src/ImageSharp.Drawing/project.json @@ -39,14 +39,12 @@ }, "dependencies": { "ImageSharp": { - "target": "project", - "version": "1.0.0-*" + "target": "project" }, "ImageSharp.Processing": { - "target": "project", - "version": "1.0.0-*" + "target": "project" }, - "SixLabors.Shapes": "0.1.0-ci0043", + "SixLabors.Shapes": "0.1.0-ci0047", "StyleCop.Analyzers": { "version": "1.0.0", "type": "build" diff --git a/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj b/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj index 305fac6369..75212d3617 100644 --- a/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj +++ b/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj @@ -179,6 +179,9 @@ ..\..\src\ImageSharp.Drawing\bin\$(Configuration)\net461\ImageSharp.Drawing.dll + + ..\..\src\ImageSharp.Drawing\bin\$(Configuration)\net461\SixLabors.Shapes.dll + ..\..\src\ImageSharp.Formats.Bmp\bin\$(Configuration)\net461\ImageSharp.Formats.Bmp.dll diff --git a/tests/ImageSharp.Tests/Drawing/DrawPathTests.cs b/tests/ImageSharp.Tests/Drawing/DrawPathTests.cs index b619483162..31aa87d4b7 100644 --- a/tests/ImageSharp.Tests/Drawing/DrawPathTests.cs +++ b/tests/ImageSharp.Tests/Drawing/DrawPathTests.cs @@ -7,12 +7,13 @@ namespace ImageSharp.Tests.Drawing { using Drawing; using ImageSharp.Drawing; - using CorePath = ImageSharp.Drawing.Paths.Path; - using ImageSharp.Drawing.Paths; + using CorePath = SixLabors.Shapes.Path; + using SixLabors.Shapes; using System; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Numerics; + using Xunit; public class DrawPathTests : FileTestBase diff --git a/tests/ImageSharp.Tests/Drawing/Helpers/BezierPolygon.cs b/tests/ImageSharp.Tests/Drawing/Helpers/BezierPolygon.cs new file mode 100644 index 0000000000..070e555016 --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/Helpers/BezierPolygon.cs @@ -0,0 +1,71 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace SixLabors.Shapes +{ + using System.Collections.Generic; + using System.Collections.Immutable; + using System.Numerics; + + using SixLabors.Shapes; + public class BezierPolygon : IShape + { + private Polygon polygon; + + public BezierPolygon(params Vector2[] points) + { + this.polygon = new Polygon(new BezierLineSegment(points)); + } + + public float Distance(Vector2 point) + { + return this.polygon.Distance(point); + } + + public bool Contains(Vector2 point) + { + return this.polygon.Contains(point); + } + + public int FindIntersections(Vector2 start, Vector2 end, Vector2[] buffer, int count, int offset) + { + return this.polygon.FindIntersections(start, end, buffer, count, offset); + } + + public IEnumerable FindIntersections(Vector2 start, Vector2 end) + { + return this.polygon.FindIntersections(start, end); + } + + public IShape Transform(Matrix3x2 matrix) + { + return ((IShape)this.polygon).Transform(matrix); + } + + public Rectangle Bounds + { + get + { + return this.polygon.Bounds; + } + } + + public ImmutableArray Paths + { + get + { + return this.polygon.Paths; + } + } + + public int MaxIntersections + { + get + { + return this.polygon.MaxIntersections; + } + } + } +} diff --git a/tests/ImageSharp.Tests/Drawing/Helpers/LinearPolygon.cs b/tests/ImageSharp.Tests/Drawing/Helpers/LinearPolygon.cs new file mode 100644 index 0000000000..fa94882665 --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/Helpers/LinearPolygon.cs @@ -0,0 +1,71 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace SixLabors.Shapes +{ + using System.Collections.Generic; + using System.Collections.Immutable; + using System.Numerics; + + using SixLabors.Shapes; + public class LinearPolygon : IShape + { + private Polygon polygon; + + public LinearPolygon(params Vector2[] points) + { + this.polygon = new Polygon(new LinearLineSegment(points)); + } + + public float Distance(Vector2 point) + { + return this.polygon.Distance(point); + } + + public bool Contains(Vector2 point) + { + return this.polygon.Contains(point); + } + + public int FindIntersections(Vector2 start, Vector2 end, Vector2[] buffer, int count, int offset) + { + return this.polygon.FindIntersections(start, end, buffer, count, offset); + } + + public IEnumerable FindIntersections(Vector2 start, Vector2 end) + { + return this.polygon.FindIntersections(start, end); + } + + public IShape Transform(Matrix3x2 matrix) + { + return ((IShape)this.polygon).Transform(matrix); + } + + public Rectangle Bounds + { + get + { + return this.polygon.Bounds; + } + } + + public ImmutableArray Paths + { + get + { + return this.polygon.Paths; + } + } + + public int MaxIntersections + { + get + { + return this.polygon.MaxIntersections; + } + } + } +} diff --git a/tests/ImageSharp.Tests/Drawing/LineComplexPolygonTests.cs b/tests/ImageSharp.Tests/Drawing/LineComplexPolygonTests.cs index 9ce93ee899..dd2ea5249c 100644 --- a/tests/ImageSharp.Tests/Drawing/LineComplexPolygonTests.cs +++ b/tests/ImageSharp.Tests/Drawing/LineComplexPolygonTests.cs @@ -7,26 +7,29 @@ namespace ImageSharp.Tests.Drawing { using System.IO; using Xunit; - + using Drawing; + using ImageSharp.Drawing; using System.Numerics; - using ImageSharp.Drawing.Shapes; using ImageSharp.Drawing.Pens; + using SixLabors.Shapes; + public class LineComplexPolygonTests : FileTestBase { [Fact] public void ImageShouldBeOverlayedByPolygonOutline() { string path = this.CreateOutputDirectory("Drawing", "LineComplexPolygon"); - LinearPolygon simplePath = new LinearPolygon( + + var simplePath = new Polygon(new LinearLineSegment( new Vector2(10, 10), new Vector2(200, 150), - new Vector2(50, 300)); + new Vector2(50, 300))); - LinearPolygon hole1 = new LinearPolygon( + var hole1 = new Polygon(new LinearLineSegment( new Vector2(37, 85), new Vector2(93, 85), - new Vector2(65, 137)); + new Vector2(65, 137))); using (Image image = new Image(500, 500)) { @@ -34,8 +37,8 @@ namespace ImageSharp.Tests.Drawing { image .BackgroundColor(Color.Blue) - .DrawPolygon(Color.HotPink, 5, new ComplexPolygon(simplePath, hole1)) - .Save(output); + .DrawPolygon(Color.HotPink, 5, new ComplexPolygon(simplePath, hole1)) + .Save(output); } using (PixelAccessor sourcePixels = image.Lock()) @@ -128,6 +131,7 @@ namespace ImageSharp.Tests.Drawing new Vector2(37, 85), new Vector2(130, 40), new Vector2(65, 137)); + var clipped = simplePath.Clip(hole1); using (Image image = new Image(500, 500)) { @@ -135,7 +139,7 @@ namespace ImageSharp.Tests.Drawing { image .BackgroundColor(Color.Blue) - .DrawPolygon(Color.HotPink, 5, new ComplexPolygon(simplePath, hole1)) + .DrawPolygon(Color.HotPink, 5, clipped) .Save(output); } diff --git a/tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs b/tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs index 18275ef385..82e4ac33e2 100644 --- a/tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs +++ b/tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs @@ -9,6 +9,9 @@ namespace ImageSharp.Tests.Drawing using System.IO; using System.Numerics; + + using SixLabors.Shapes; + using Xunit; public class SolidBezierTests : FileTestBase diff --git a/tests/ImageSharp.Tests/Drawing/SolidComplexPolygonTests.cs b/tests/ImageSharp.Tests/Drawing/SolidComplexPolygonTests.cs index 144a1398d8..a1973a280b 100644 --- a/tests/ImageSharp.Tests/Drawing/SolidComplexPolygonTests.cs +++ b/tests/ImageSharp.Tests/Drawing/SolidComplexPolygonTests.cs @@ -7,9 +7,11 @@ namespace ImageSharp.Tests.Drawing { using System.IO; using Xunit; - + using Drawing; + using ImageSharp.Drawing; using System.Numerics; - using ImageSharp.Drawing.Shapes; + + using SixLabors.Shapes; public class SolidComplexPolygonTests : FileTestBase { @@ -33,7 +35,7 @@ namespace ImageSharp.Tests.Drawing { image .BackgroundColor(Color.Blue) - .Fill(Color.HotPink, new ComplexPolygon(simplePath, hole1)) + .Fill(Color.HotPink, simplePath.Clip(hole1)) .Save(output); } @@ -76,7 +78,7 @@ namespace ImageSharp.Tests.Drawing { image .BackgroundColor(Color.Blue) - .Fill(Color.HotPink, new ComplexPolygon(simplePath, hole1)) + .Fill(Color.HotPink, simplePath.Clip(hole1)) .Save(output); } @@ -119,8 +121,8 @@ namespace ImageSharp.Tests.Drawing { image .BackgroundColor(Color.Blue) - .Fill(color, new ComplexPolygon(simplePath, hole1)) - .Save(output); + .Fill(color, simplePath.Clip(hole1)) + .Save(output); } //shift background color towards forground color by the opacity amount @@ -144,4 +146,4 @@ namespace ImageSharp.Tests.Drawing } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs b/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs index b4cf8e0905..3e20b3a09a 100644 --- a/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs +++ b/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs @@ -5,8 +5,10 @@ namespace ImageSharp.Tests.Drawing { + using Drawing; using ImageSharp.Drawing; - + using System; + using System.Diagnostics.CodeAnalysis; using System.IO; using System.Numerics; using Xunit; @@ -142,14 +144,15 @@ namespace ImageSharp.Tests.Drawing public void ImageShouldBeOverlayedByFilledRectangle() { string path = this.CreateOutputDirectory("Drawing", "FilledPolygons"); + using (Image image = new Image(500, 500)) { using (FileStream output = File.OpenWrite($"{path}/Rectangle.png")) { image .BackgroundColor(Color.Blue) - .Fill(Color.HotPink, new ImageSharp.Drawing.Shapes.RectangularPolygon(new Rectangle(10, 10, 190, 140))) - .Save(output); + .Fill(Color.HotPink, new SixLabors.Shapes.Rectangle(10,10, 190, 140)) + .Save(output); } using (PixelAccessor sourcePixels = image.Lock()) From 884dfab3e1905f01106dc2535ca88a3d7a33f94c Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Tue, 31 Jan 2017 22:12:26 +0000 Subject: [PATCH 015/142] split out paths from basic drawing --- ImageSharp.sln | 8 + src/ImageSharp.Drawing.Paths/DrawBeziers.cs | 120 +++++ src/ImageSharp.Drawing.Paths/DrawLines.cs | 120 +++++ src/ImageSharp.Drawing.Paths/DrawPath.cs | 124 +++++ src/ImageSharp.Drawing.Paths/DrawPolygon.cs | 104 ++++ .../DrawRectangle.cs | 24 +- src/ImageSharp.Drawing.Paths/DrawShape.cs | 118 ++++ src/ImageSharp.Drawing.Paths/FillPaths.cs | 87 +++ src/ImageSharp.Drawing.Paths/FillPolygon.cs | 83 +++ .../FillRectangle.cs | 8 +- src/ImageSharp.Drawing.Paths/FillShapes.cs | 83 +++ .../ImageSharp.Drawing.Paths.xproj | 25 + .../PointInfoExtensions.cs | 21 +- .../Properties/AssemblyInfo.cs | 6 + .../RectangleExtensions.cs | 12 +- .../ShapePath.cs} | 31 +- src/ImageSharp.Drawing.Paths/ShapeRegion.cs | 137 +++++ src/ImageSharp.Drawing.Paths/project.json | 96 ++++ .../Brushes/Processors/BrushApplicator.cs | 1 - src/ImageSharp.Drawing/Draw.cs | 506 ------------------ src/ImageSharp.Drawing/DrawPath.cs | 123 +++++ src/ImageSharp.Drawing/Fill.cs | 173 ------ src/ImageSharp.Drawing/FillRegion.cs | 111 ++++ src/ImageSharp.Drawing/IDrawableRegion.cs | 24 - src/ImageSharp.Drawing/Path.cs | 57 ++ .../Processors/DrawPathProcessor.cs | 5 +- ...apeProcessor.cs => FillRegionProcessor.cs} | 29 +- .../{IRegion.cs => Region.cs} | 14 +- src/ImageSharp.Drawing/project.json | 1 - .../Drawing/FillRectangle.cs | 2 +- tests/ImageSharp.Benchmarks/project.json | 24 +- .../ImageSharp.Sandbox46.csproj | 5 +- .../ImageSharp.Tests/Drawing/DrawPathTests.cs | 10 +- .../Drawing/LineComplexPolygonTests.cs | 12 +- .../ImageSharp.Tests/Drawing/PolygonTests.cs | 2 +- .../Drawing/SolidPolygonTests.cs | 2 +- tests/ImageSharp.Tests/project.json | 24 +- 37 files changed, 1509 insertions(+), 823 deletions(-) create mode 100644 src/ImageSharp.Drawing.Paths/DrawBeziers.cs create mode 100644 src/ImageSharp.Drawing.Paths/DrawLines.cs create mode 100644 src/ImageSharp.Drawing.Paths/DrawPath.cs create mode 100644 src/ImageSharp.Drawing.Paths/DrawPolygon.cs rename src/{ImageSharp.Drawing => ImageSharp.Drawing.Paths}/DrawRectangle.cs (73%) create mode 100644 src/ImageSharp.Drawing.Paths/DrawShape.cs create mode 100644 src/ImageSharp.Drawing.Paths/FillPaths.cs create mode 100644 src/ImageSharp.Drawing.Paths/FillPolygon.cs rename src/{ImageSharp.Drawing => ImageSharp.Drawing.Paths}/FillRectangle.cs (93%) create mode 100644 src/ImageSharp.Drawing.Paths/FillShapes.cs create mode 100644 src/ImageSharp.Drawing.Paths/ImageSharp.Drawing.Paths.xproj rename src/{ImageSharp.Drawing/Processors => ImageSharp.Drawing.Paths}/PointInfoExtensions.cs (62%) create mode 100644 src/ImageSharp.Drawing.Paths/Properties/AssemblyInfo.cs rename src/{ImageSharp.Drawing/Processors => ImageSharp.Drawing.Paths}/RectangleExtensions.cs (58%) rename src/{ImageSharp.Drawing/ShapeRegion.cs => ImageSharp.Drawing.Paths/ShapePath.cs} (83%) create mode 100644 src/ImageSharp.Drawing.Paths/ShapeRegion.cs create mode 100644 src/ImageSharp.Drawing.Paths/project.json delete mode 100644 src/ImageSharp.Drawing/Draw.cs create mode 100644 src/ImageSharp.Drawing/DrawPath.cs delete mode 100644 src/ImageSharp.Drawing/Fill.cs create mode 100644 src/ImageSharp.Drawing/FillRegion.cs delete mode 100644 src/ImageSharp.Drawing/IDrawableRegion.cs create mode 100644 src/ImageSharp.Drawing/Path.cs rename src/ImageSharp.Drawing/Processors/{FillShapeProcessor.cs => FillRegionProcessor.cs} (95%) rename src/ImageSharp.Drawing/{IRegion.cs => Region.cs} (78%) diff --git a/ImageSharp.sln b/ImageSharp.sln index f1e9fb1045..503a5b8601 100644 --- a/ImageSharp.sln +++ b/ImageSharp.sln @@ -60,11 +60,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageSharp.Sandbox46", "tes {2AA31A1F-142C-43F4-8687-09ABCA4B3A26} = {2AA31A1F-142C-43F4-8687-09ABCA4B3A26} {27AD4B5F-ECC4-4C63-9ECB-04EC772FDB6F} = {27AD4B5F-ECC4-4C63-9ECB-04EC772FDB6F} {7213767C-0003-41CA-AB18-0223CFA7CE4B} = {7213767C-0003-41CA-AB18-0223CFA7CE4B} + {E5BD4F96-28A8-410C-8B63-1C5731948549} = {E5BD4F96-28A8-410C-8B63-1C5731948549} {C77661B9-F793-422E-8E27-AC60ECC5F215} = {C77661B9-F793-422E-8E27-AC60ECC5F215} {556ABDCF-ED93-4327-BE98-F6815F78B9B8} = {556ABDCF-ED93-4327-BE98-F6815F78B9B8} {A623CFE9-9D2B-4528-AD1F-2E834B061134} = {A623CFE9-9D2B-4528-AD1F-2E834B061134} EndProjectSection EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ImageSharp.Drawing.Paths", "src\ImageSharp.Drawing.Paths\ImageSharp.Drawing.Paths.xproj", "{E5BD4F96-28A8-410C-8B63-1C5731948549}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -115,6 +118,10 @@ Global {96188137-5FA6-4924-AB6E-4EFF79C6E0BB}.Debug|Any CPU.Build.0 = Debug|Any CPU {96188137-5FA6-4924-AB6E-4EFF79C6E0BB}.Release|Any CPU.ActiveCfg = Release|Any CPU {96188137-5FA6-4924-AB6E-4EFF79C6E0BB}.Release|Any CPU.Build.0 = Release|Any CPU + {E5BD4F96-28A8-410C-8B63-1C5731948549}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E5BD4F96-28A8-410C-8B63-1C5731948549}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E5BD4F96-28A8-410C-8B63-1C5731948549}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E5BD4F96-28A8-410C-8B63-1C5731948549}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -132,5 +139,6 @@ Global {A623CFE9-9D2B-4528-AD1F-2E834B061134} = {815C0625-CD3D-440F-9F80-2D83856AB7AE} {9E574A07-F879-4811-9C41-5CBDC6BAFDB7} = {815C0625-CD3D-440F-9F80-2D83856AB7AE} {96188137-5FA6-4924-AB6E-4EFF79C6E0BB} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC} + {E5BD4F96-28A8-410C-8B63-1C5731948549} = {815C0625-CD3D-440F-9F80-2D83856AB7AE} EndGlobalSection EndGlobal diff --git a/src/ImageSharp.Drawing.Paths/DrawBeziers.cs b/src/ImageSharp.Drawing.Paths/DrawBeziers.cs new file mode 100644 index 0000000000..6515db5771 --- /dev/null +++ b/src/ImageSharp.Drawing.Paths/DrawBeziers.cs @@ -0,0 +1,120 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Numerics; + using Drawing; + using Drawing.Brushes; + using Drawing.Pens; + using Drawing.Processors; + using SixLabors.Shapes; + + using Path = SixLabors.Shapes.Path; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Draws the provided Points as an open Bezier path at the provided thickness with the supplied brush + /// + /// The type of the color. + /// The source. + /// The brush. + /// The thickness. + /// The points. + /// The options. + /// + /// The Image + /// + public static Image DrawBeziers(this Image source, IBrush brush, float thickness, Vector2[] points, GraphicsOptions options) + where TColor : struct, IPackedPixel, IEquatable + { + return source.Draw(new Pen(brush, thickness), new Path(new BezierLineSegment(points)), options); + } + + /// + /// Draws the provided Points as an open Bezier path at the provided thickness with the supplied brush + /// + /// The type of the color. + /// The source. + /// The brush. + /// The thickness. + /// The points. + /// The Image + public static Image DrawBeziers(this Image source, IBrush brush, float thickness, Vector2[] points) + where TColor : struct, IPackedPixel, IEquatable + { + return source.Draw(new Pen(brush, thickness), new Path(new BezierLineSegment(points))); + } + + /// + /// Draws the provided Points as an open Bezier path at the provided thickness with the supplied brush + /// + /// The type of the color. + /// The source. + /// The color. + /// The thickness. + /// The points. + /// The Image + public static Image DrawBeziers(this Image source, TColor color, float thickness, Vector2[] points) + where TColor : struct, IPackedPixel, IEquatable + { + return source.DrawBeziers(new SolidBrush(color), thickness, points); + } + + /// + /// Draws the provided Points as an open Bezier path at the provided thickness with the supplied brush + /// + /// The type of the color. + /// The source. + /// The color. + /// The thickness. + /// The points. + /// The options. + /// + /// The Image + /// + public static Image DrawBeziers(this Image source, TColor color, float thickness, Vector2[] points, GraphicsOptions options) + where TColor : struct, IPackedPixel, IEquatable + { + return source.DrawBeziers(new SolidBrush(color), thickness, points, options); + } + + /// + /// Draws the provided Points as an open Bezier path with the supplied pen + /// + /// The type of the color. + /// The source. + /// The pen. + /// The points. + /// The options. + /// + /// The Image + /// + public static Image DrawBeziers(this Image source, IPen pen, Vector2[] points, GraphicsOptions options) + where TColor : struct, IPackedPixel, IEquatable + { + return source.Draw(pen, new Path(new BezierLineSegment(points)), options); + } + + /// + /// Draws the provided Points as an open Bezier path with the supplied pen + /// + /// The type of the color. + /// The source. + /// The pen. + /// The points. + /// The Image + public static Image DrawBeziers(this Image source, IPen pen, Vector2[] points) + where TColor : struct, IPackedPixel, IEquatable + { + return source.Draw(pen, new Path(new BezierLineSegment(points))); + } + } +} diff --git a/src/ImageSharp.Drawing.Paths/DrawLines.cs b/src/ImageSharp.Drawing.Paths/DrawLines.cs new file mode 100644 index 0000000000..2e4f849870 --- /dev/null +++ b/src/ImageSharp.Drawing.Paths/DrawLines.cs @@ -0,0 +1,120 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Numerics; + using Drawing; + using Drawing.Brushes; + using Drawing.Pens; + using Drawing.Processors; + using SixLabors.Shapes; + + using Path = SixLabors.Shapes.Path; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Draws the provided Points as an open Linear path at the provided thickness with the supplied brush + /// + /// The type of the color. + /// The source. + /// The brush. + /// The thickness. + /// The points. + /// The options. + /// + /// The Image + /// + public static Image DrawLines(this Image source, IBrush brush, float thickness, Vector2[] points, GraphicsOptions options) + where TColor : struct, IPackedPixel, IEquatable + { + return source.Draw(new Pen(brush, thickness), new Path(new LinearLineSegment(points)), options); + } + + /// + /// Draws the provided Points as an open Linear path at the provided thickness with the supplied brush + /// + /// The type of the color. + /// The source. + /// The brush. + /// The thickness. + /// The points. + /// The Image + public static Image DrawLines(this Image source, IBrush brush, float thickness, Vector2[] points) + where TColor : struct, IPackedPixel, IEquatable + { + return source.Draw(new Pen(brush, thickness), new Path(new LinearLineSegment(points))); + } + + /// + /// Draws the provided Points as an open Linear path at the provided thickness with the supplied brush + /// + /// The type of the color. + /// The source. + /// The color. + /// The thickness. + /// The points. + /// The Image + public static Image DrawLines(this Image source, TColor color, float thickness, Vector2[] points) + where TColor : struct, IPackedPixel, IEquatable + { + return source.DrawLines(new SolidBrush(color), thickness, points); + } + + /// + /// Draws the provided Points as an open Linear path at the provided thickness with the supplied brush + /// + /// The type of the color. + /// The source. + /// The color. + /// The thickness. + /// The points. + /// The options. + /// + /// The Image + /// + public static Image DrawLines(this Image source, TColor color, float thickness, Vector2[] points, GraphicsOptions options) + where TColor : struct, IPackedPixel, IEquatable + { + return source.DrawLines(new SolidBrush(color), thickness, points, options); + } + + /// + /// Draws the provided Points as an open Linear path with the supplied pen + /// + /// The type of the color. + /// The source. + /// The pen. + /// The points. + /// The options. + /// + /// The Image + /// + public static Image DrawLines(this Image source, IPen pen, Vector2[] points, GraphicsOptions options) + where TColor : struct, IPackedPixel, IEquatable + { + return source.Draw(pen, new Path(new LinearLineSegment(points)), options); + } + + /// + /// Draws the provided Points as an open Linear path with the supplied pen + /// + /// The type of the color. + /// The source. + /// The pen. + /// The points. + /// The Image + public static Image DrawLines(this Image source, IPen pen, Vector2[] points) + where TColor : struct, IPackedPixel, IEquatable + { + return source.Draw(pen, new Path(new LinearLineSegment(points))); + } + } +} diff --git a/src/ImageSharp.Drawing.Paths/DrawPath.cs b/src/ImageSharp.Drawing.Paths/DrawPath.cs new file mode 100644 index 0000000000..201984e0ac --- /dev/null +++ b/src/ImageSharp.Drawing.Paths/DrawPath.cs @@ -0,0 +1,124 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Numerics; + using Drawing; + using Drawing.Brushes; + using Drawing.Pens; + using Drawing.Processors; + using SixLabors.Shapes; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Draws the outline of the polygon with the provided pen. + /// + /// The type of the color. + /// The source. + /// The pen. + /// The path. + /// The options. + /// + /// The Image + /// + public static Image Draw(this Image source, IPen pen, IPath path, GraphicsOptions options) + where TColor : struct, IPackedPixel, IEquatable + { + return source.Draw(pen, new ShapePath(path), options); + } + + /// + /// Draws the outline of the polygon with the provided pen. + /// + /// The type of the color. + /// The source. + /// The pen. + /// The path. + /// + /// The Image + /// + public static Image Draw(this Image source, IPen pen, IPath path) + where TColor : struct, IPackedPixel, IEquatable + { + return source.Draw(pen, path, GraphicsOptions.Default); + } + + /// + /// Draws the outline of the polygon with the provided brush at the provided thickness. + /// + /// The type of the color. + /// The source. + /// The brush. + /// The thickness. + /// The shape. + /// The options. + /// + /// The Image + /// + public static Image Draw(this Image source, IBrush brush, float thickness, IPath path, GraphicsOptions options) + where TColor : struct, IPackedPixel, IEquatable + { + return source.Draw(new Pen(brush, thickness), path, options); + } + + /// + /// Draws the outline of the polygon with the provided brush at the provided thickness. + /// + /// The type of the color. + /// The source. + /// The brush. + /// The thickness. + /// The path. + /// + /// The Image + /// + public static Image Draw(this Image source, IBrush brush, float thickness, IPath path) + where TColor : struct, IPackedPixel, IEquatable + { + return source.Draw(new Pen(brush, thickness), path); + } + + /// + /// Draws the outline of the polygon with the provided brush at the provided thickness. + /// + /// The type of the color. + /// The source. + /// The color. + /// The thickness. + /// The path. + /// The options. + /// + /// The Image + /// + public static Image Draw(this Image source, TColor color, float thickness, IPath path, GraphicsOptions options) + where TColor : struct, IPackedPixel, IEquatable + { + return source.Draw(new SolidBrush(color), thickness, path, options); + } + + /// + /// Draws the outline of the polygon with the provided brush at the provided thickness. + /// + /// The type of the color. + /// The source. + /// The color. + /// The thickness. + /// The path. + /// + /// The Image + /// + public static Image Draw(this Image source, TColor color, float thickness, IPath path) + where TColor : struct, IPackedPixel, IEquatable + { + return source.Draw(new SolidBrush(color), thickness, path); + } + } +} diff --git a/src/ImageSharp.Drawing.Paths/DrawPolygon.cs b/src/ImageSharp.Drawing.Paths/DrawPolygon.cs new file mode 100644 index 0000000000..73d8ec0e7f --- /dev/null +++ b/src/ImageSharp.Drawing.Paths/DrawPolygon.cs @@ -0,0 +1,104 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Numerics; + using Drawing; + using Drawing.Brushes; + using Drawing.Pens; + using Drawing.Processors; + using SixLabors.Shapes; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Draws the provided Points as a closed Linear Polygon with the provided brush at the provided thickness. + /// + /// The type of the color. + /// The source. + /// The brush. + /// The thickness. + /// The points. + /// The options. + /// + /// The Image + /// + public static Image DrawPolygon(this Image source, IBrush brush, float thickness, Vector2[] points, GraphicsOptions options) + where TColor : struct, IPackedPixel, IEquatable + { + return source.Draw(new Pen(brush, thickness), new Polygon(new LinearLineSegment(points)), options); + } + + /// + /// Draws the provided Points as a closed Linear Polygon with the provided brush at the provided thickness. + /// + /// The type of the color. + /// The source. + /// The brush. + /// The thickness. + /// The points. + /// The Image + public static Image DrawPolygon(this Image source, IBrush brush, float thickness, Vector2[] points) + where TColor : struct, IPackedPixel, IEquatable + { + return source.Draw(new Pen(brush, thickness), new Polygon(new LinearLineSegment(points))); + } + + /// + /// Draws the provided Points as a closed Linear Polygon with the provided brush at the provided thickness. + /// + /// The type of the color. + /// The source. + /// The color. + /// The thickness. + /// The points. + /// The Image + public static Image DrawPolygon(this Image source, TColor color, float thickness, Vector2[] points) + where TColor : struct, IPackedPixel, IEquatable + { + return source.DrawPolygon(new SolidBrush(color), thickness, points); + } + + /// + /// Draws the provided Points as a closed Linear Polygon with the provided brush at the provided thickness. + /// + /// The type of the color. + /// The source. + /// The color. + /// The thickness. + /// The points. + /// The options. + /// + /// The Image + /// + public static Image DrawPolygon(this Image source, TColor color, float thickness, Vector2[] points, GraphicsOptions options) + where TColor : struct, IPackedPixel, IEquatable + { + return source.DrawPolygon(new SolidBrush(color), thickness, points, options); + } + + /// + /// Draws the provided Points as a closed Linear Polygon with the provided Pen. + /// + /// The type of the color. + /// The source. + /// The pen. + /// The points. + /// The options. + /// + /// The Image + /// + public static Image DrawPolygon(this Image source, IPen pen, Vector2[] points, GraphicsOptions options) + where TColor : struct, IPackedPixel, IEquatable + { + return source.Draw(pen, new Polygon(new LinearLineSegment(points)), options); + } + } +} diff --git a/src/ImageSharp.Drawing/DrawRectangle.cs b/src/ImageSharp.Drawing.Paths/DrawRectangle.cs similarity index 73% rename from src/ImageSharp.Drawing/DrawRectangle.cs rename to src/ImageSharp.Drawing.Paths/DrawRectangle.cs index 243aa082d4..28ea3d6e03 100644 --- a/src/ImageSharp.Drawing/DrawRectangle.cs +++ b/src/ImageSharp.Drawing.Paths/DrawRectangle.cs @@ -30,10 +30,10 @@ namespace ImageSharp /// /// The Image /// - public static Image DrawPolygon(this Image source, IPen pen, RectangleF shape, GraphicsOptions options) + public static Image Draw(this Image source, IPen pen, Rectangle shape, GraphicsOptions options) where TColor : struct, IPackedPixel, IEquatable { - return source.Apply(new DrawPathProcessor(pen, new ShapeRegion(new SixLabors.Shapes.Rectangle(shape.X, shape.Y, shape.Width, shape.Height)), options)); + return source.Draw(pen, new SixLabors.Shapes.Rectangle(shape.X, shape.Y, shape.Width, shape.Height), options); } /// @@ -44,10 +44,10 @@ namespace ImageSharp /// The pen. /// The shape. /// The Image - public static Image DrawPolygon(this Image source, IPen pen, RectangleF shape) + public static Image Draw(this Image source, IPen pen, Rectangle shape) where TColor : struct, IPackedPixel, IEquatable { - return source.DrawPolygon(pen, shape, GraphicsOptions.Default); + return source.Draw(pen, shape, GraphicsOptions.Default); } /// @@ -62,10 +62,10 @@ namespace ImageSharp /// /// The Image /// - public static Image DrawPolygon(this Image source, IBrush brush, float thickness, RectangleF shape, GraphicsOptions options) + public static Image Draw(this Image source, IBrush brush, float thickness, Rectangle shape, GraphicsOptions options) where TColor : struct, IPackedPixel, IEquatable { - return source.DrawPolygon(new Pen(brush, thickness), shape, options); + return source.Draw(new Pen(brush, thickness), shape, options); } /// @@ -77,10 +77,10 @@ namespace ImageSharp /// The thickness. /// The shape. /// The Image - public static Image DrawPolygon(this Image source, IBrush brush, float thickness, RectangleF shape) + public static Image Draw(this Image source, IBrush brush, float thickness, Rectangle shape) where TColor : struct, IPackedPixel, IEquatable { - return source.DrawPolygon(new Pen(brush, thickness), shape); + return source.Draw(new Pen(brush, thickness), shape); } /// @@ -95,10 +95,10 @@ namespace ImageSharp /// /// The Image /// - public static Image DrawPolygon(this Image source, TColor color, float thickness, RectangleF shape, GraphicsOptions options) + public static Image Draw(this Image source, TColor color, float thickness, Rectangle shape, GraphicsOptions options) where TColor : struct, IPackedPixel, IEquatable { - return source.DrawPolygon(new SolidBrush(color), thickness, shape, options); + return source.Draw(new SolidBrush(color), thickness, shape, options); } /// @@ -110,10 +110,10 @@ namespace ImageSharp /// The thickness. /// The shape. /// The Image - public static Image DrawPolygon(this Image source, TColor color, float thickness, RectangleF shape) + public static Image Draw(this Image source, TColor color, float thickness, Rectangle shape) where TColor : struct, IPackedPixel, IEquatable { - return source.DrawPolygon(new SolidBrush(color), thickness, shape); + return source.Draw(new SolidBrush(color), thickness, shape); } } } diff --git a/src/ImageSharp.Drawing.Paths/DrawShape.cs b/src/ImageSharp.Drawing.Paths/DrawShape.cs new file mode 100644 index 0000000000..15ae2b1797 --- /dev/null +++ b/src/ImageSharp.Drawing.Paths/DrawShape.cs @@ -0,0 +1,118 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Numerics; + using Drawing; + using Drawing.Brushes; + using Drawing.Pens; + using Drawing.Processors; + using SixLabors.Shapes; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Draws the outline of the polygon with the provided pen. + /// + /// The type of the color. + /// The source. + /// The pen. + /// The shape. + /// The options. + /// + /// The Image + /// + public static Image Draw(this Image source, IPen pen, IShape shape, GraphicsOptions options) + where TColor : struct, IPackedPixel, IEquatable + { + return source.Draw(pen, new ShapePath(shape), options); + } + + /// + /// Draws the outline of the polygon with the provided pen. + /// + /// The type of the color. + /// The source. + /// The pen. + /// The shape. + /// The Image + public static Image Draw(this Image source, IPen pen, IShape shape) + where TColor : struct, IPackedPixel, IEquatable + { + return source.Draw(pen, shape, GraphicsOptions.Default); + } + + /// + /// Draws the outline of the polygon with the provided brush at the provided thickness. + /// + /// The type of the color. + /// The source. + /// The brush. + /// The thickness. + /// The shape. + /// The options. + /// + /// The Image + /// + public static Image Draw(this Image source, IBrush brush, float thickness, IShape shape, GraphicsOptions options) + where TColor : struct, IPackedPixel, IEquatable + { + return source.Draw(new Pen(brush, thickness), shape, options); + } + + /// + /// Draws the outline of the polygon with the provided brush at the provided thickness. + /// + /// The type of the color. + /// The source. + /// The brush. + /// The thickness. + /// The shape. + /// The Image + public static Image Draw(this Image source, IBrush brush, float thickness, IShape shape) + where TColor : struct, IPackedPixel, IEquatable + { + return source.Draw(new Pen(brush, thickness), shape); + } + + /// + /// Draws the outline of the polygon with the provided brush at the provided thickness. + /// + /// The type of the color. + /// The source. + /// The color. + /// The thickness. + /// The shape. + /// The options. + /// + /// The Image + /// + public static Image Draw(this Image source, TColor color, float thickness, IShape shape, GraphicsOptions options) + where TColor : struct, IPackedPixel, IEquatable + { + return source.Draw(new SolidBrush(color), thickness, shape, options); + } + + /// + /// Draws the outline of the polygon with the provided brush at the provided thickness. + /// + /// The type of the color. + /// The source. + /// The color. + /// The thickness. + /// The shape. + /// The Image + public static Image Draw(this Image source, TColor color, float thickness, IShape shape) + where TColor : struct, IPackedPixel, IEquatable + { + return source.Draw(new SolidBrush(color), thickness, shape); + } + } +} diff --git a/src/ImageSharp.Drawing.Paths/FillPaths.cs b/src/ImageSharp.Drawing.Paths/FillPaths.cs new file mode 100644 index 0000000000..3095ee7cdf --- /dev/null +++ b/src/ImageSharp.Drawing.Paths/FillPaths.cs @@ -0,0 +1,87 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Numerics; + using Drawing; + using Drawing.Brushes; + using Drawing.Processors; + + using SixLabors.Shapes; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Flood fills the image in the shape of the provided polygon with the specified brush.. + /// + /// The type of the color. + /// The source. + /// The brush. + /// The shape. + /// The graphics options. + /// + /// The Image + /// + public static Image Fill(this Image source, IBrush brush, IPath path, GraphicsOptions options) + where TColor : struct, IPackedPixel, IEquatable + { + return source.Fill(brush, new ShapeRegion(path), options); + } + + /// + /// Flood fills the image in the shape of the provided polygon with the specified brush. + /// + /// The type of the color. + /// The source. + /// The brush. + /// The path. + /// + /// The Image + /// + public static Image Fill(this Image source, IBrush brush, IPath path) + where TColor : struct, IPackedPixel, IEquatable + { + return source.Fill(brush, new ShapeRegion(path), GraphicsOptions.Default); + } + + /// + /// Flood fills the image in the shape of the provided polygon with the specified brush.. + /// + /// The type of the color. + /// The source. + /// The color. + /// The path. + /// The options. + /// + /// The Image + /// + public static Image Fill(this Image source, TColor color, IPath path, GraphicsOptions options) + where TColor : struct, IPackedPixel, IEquatable + { + return source.Fill(new SolidBrush(color), path, options); + } + + /// + /// Flood fills the image in the shape of the provided polygon with the specified brush.. + /// + /// The type of the color. + /// The source. + /// The color. + /// The path. + /// + /// The Image + /// + public static Image Fill(this Image source, TColor color, IPath path) + where TColor : struct, IPackedPixel, IEquatable + { + return source.Fill(new SolidBrush(color), path); + } + } +} diff --git a/src/ImageSharp.Drawing.Paths/FillPolygon.cs b/src/ImageSharp.Drawing.Paths/FillPolygon.cs new file mode 100644 index 0000000000..4092e5fc64 --- /dev/null +++ b/src/ImageSharp.Drawing.Paths/FillPolygon.cs @@ -0,0 +1,83 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Numerics; + using Drawing; + using Drawing.Brushes; + using Drawing.Processors; + + using SixLabors.Shapes; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Flood fills the image in the shape of a Linear polygon described by the points + /// + /// The type of the color. + /// The source. + /// The brush. + /// The points. + /// The options. + /// + /// The Image + /// + public static Image FillPolygon(this Image source, IBrush brush, Vector2[] points, GraphicsOptions options) + where TColor : struct, IPackedPixel, IEquatable + { + return source.Fill(brush, new Polygon(new LinearLineSegment(points)), options); + } + + /// + /// Flood fills the image in the shape of a Linear polygon described by the points + /// + /// The type of the color. + /// The source. + /// The brush. + /// The points. + /// The Image + public static Image FillPolygon(this Image source, IBrush brush, Vector2[] points) + where TColor : struct, IPackedPixel, IEquatable + { + return source.Fill(brush, new Polygon(new LinearLineSegment(points))); + } + + /// + /// Flood fills the image in the shape of a Linear polygon described by the points + /// + /// The type of the color. + /// The source. + /// The color. + /// The points. + /// The options. + /// + /// The Image + /// + public static Image FillPolygon(this Image source, TColor color, Vector2[] points, GraphicsOptions options) + where TColor : struct, IPackedPixel, IEquatable + { + return source.Fill(new SolidBrush(color), new Polygon(new LinearLineSegment(points)), options); + } + + /// + /// Flood fills the image in the shape of a Linear polygon described by the points + /// + /// The type of the color. + /// The source. + /// The color. + /// The points. + /// The Image + public static Image FillPolygon(this Image source, TColor color, Vector2[] points) + where TColor : struct, IPackedPixel, IEquatable + { + return source.Fill(new SolidBrush(color), new Polygon(new LinearLineSegment(points))); + } + } +} diff --git a/src/ImageSharp.Drawing/FillRectangle.cs b/src/ImageSharp.Drawing.Paths/FillRectangle.cs similarity index 93% rename from src/ImageSharp.Drawing/FillRectangle.cs rename to src/ImageSharp.Drawing.Paths/FillRectangle.cs index d60a5a19d8..aef777edd2 100644 --- a/src/ImageSharp.Drawing/FillRectangle.cs +++ b/src/ImageSharp.Drawing.Paths/FillRectangle.cs @@ -27,7 +27,7 @@ namespace ImageSharp /// /// The Image /// - public static Image Fill(this Image source, IBrush brush, RectangleF shape, GraphicsOptions options) + public static Image Fill(this Image source, IBrush brush, Rectangle shape, GraphicsOptions options) where TColor : struct, IPackedPixel, IEquatable { return source.Fill(brush, new SixLabors.Shapes.Rectangle(shape.X, shape.Y, shape.Width, shape.Height), options); @@ -41,7 +41,7 @@ namespace ImageSharp /// The brush. /// The shape. /// The Image - public static Image Fill(this Image source, IBrush brush, RectangleF shape) + public static Image Fill(this Image source, IBrush brush, Rectangle shape) where TColor : struct, IPackedPixel, IEquatable { return source.Fill(brush, new SixLabors.Shapes.Rectangle(shape.X, shape.Y, shape.Width, shape.Height)); @@ -58,7 +58,7 @@ namespace ImageSharp /// /// The Image /// - public static Image Fill(this Image source, TColor color, RectangleF shape, GraphicsOptions options) + public static Image Fill(this Image source, TColor color, Rectangle shape, GraphicsOptions options) where TColor : struct, IPackedPixel, IEquatable { return source.Fill(new SolidBrush(color), shape, options); @@ -72,7 +72,7 @@ namespace ImageSharp /// The color. /// The shape. /// The Image - public static Image Fill(this Image source, TColor color, RectangleF shape) + public static Image Fill(this Image source, TColor color, Rectangle shape) where TColor : struct, IPackedPixel, IEquatable { return source.Fill(new SolidBrush(color), shape); diff --git a/src/ImageSharp.Drawing.Paths/FillShapes.cs b/src/ImageSharp.Drawing.Paths/FillShapes.cs new file mode 100644 index 0000000000..9fe473ee1c --- /dev/null +++ b/src/ImageSharp.Drawing.Paths/FillShapes.cs @@ -0,0 +1,83 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Numerics; + using Drawing; + using Drawing.Brushes; + using Drawing.Processors; + + using SixLabors.Shapes; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Flood fills the image in the shape of the provided polygon with the specified brush.. + /// + /// The type of the color. + /// The source. + /// The brush. + /// The shape. + /// The graphics options. + /// + /// The Image + /// + public static Image Fill(this Image source, IBrush brush, IShape shape, GraphicsOptions options) + where TColor : struct, IPackedPixel, IEquatable + { + return source.Fill(brush, new ShapeRegion(shape), options); + } + + /// + /// Flood fills the image in the shape of the provided polygon with the specified brush. + /// + /// The type of the color. + /// The source. + /// The brush. + /// The shape. + /// The Image + public static Image Fill(this Image source, IBrush brush, IShape shape) + where TColor : struct, IPackedPixel, IEquatable + { + return source.Fill(brush, new ShapeRegion(shape), GraphicsOptions.Default); + } + + /// + /// Flood fills the image in the shape of the provided polygon with the specified brush.. + /// + /// The type of the color. + /// The source. + /// The color. + /// The shape. + /// The options. + /// + /// The Image + /// + public static Image Fill(this Image source, TColor color, IShape shape, GraphicsOptions options) + where TColor : struct, IPackedPixel, IEquatable + { + return source.Fill(new SolidBrush(color), shape, options); + } + + /// + /// Flood fills the image in the shape of the provided polygon with the specified brush.. + /// + /// The type of the color. + /// The source. + /// The color. + /// The shape. + /// The Image + public static Image Fill(this Image source, TColor color, IShape shape) + where TColor : struct, IPackedPixel, IEquatable + { + return source.Fill(new SolidBrush(color), shape); + } + } +} diff --git a/src/ImageSharp.Drawing.Paths/ImageSharp.Drawing.Paths.xproj b/src/ImageSharp.Drawing.Paths/ImageSharp.Drawing.Paths.xproj new file mode 100644 index 0000000000..196f7bc1bc --- /dev/null +++ b/src/ImageSharp.Drawing.Paths/ImageSharp.Drawing.Paths.xproj @@ -0,0 +1,25 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + e5bd4f96-28a8-410c-8b63-1c5731948549 + ImageSharp.Drawing + .\obj + .\bin\ + v4.5.1 + + + 2.0 + + + True + + + + + + \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processors/PointInfoExtensions.cs b/src/ImageSharp.Drawing.Paths/PointInfoExtensions.cs similarity index 62% rename from src/ImageSharp.Drawing/Processors/PointInfoExtensions.cs rename to src/ImageSharp.Drawing.Paths/PointInfoExtensions.cs index 6613d15acb..ea38dfb9d3 100644 --- a/src/ImageSharp.Drawing/Processors/PointInfoExtensions.cs +++ b/src/ImageSharp.Drawing.Paths/PointInfoExtensions.cs @@ -3,19 +3,8 @@ // Licensed under the Apache License, Version 2.0. // -namespace ImageSharp.Drawing.Processors +namespace ImageSharp.Drawing { - using System; - using System.Buffers; - using System.Numerics; - using System.Threading.Tasks; - using Drawing; - using ImageSharp.Processing; - using SixLabors.Shapes; - - using PointInfo = ImageSharp.Drawing.PointInfo; - using Rectangle = ImageSharp.Rectangle; - /// /// Extension methods for helping to bridge Shaper2D and ImageSharp primitives. /// @@ -29,10 +18,10 @@ namespace ImageSharp.Drawing.Processors public static PointInfo Convert(this SixLabors.Shapes.PointInfo source) { return new PointInfo - { - DistanceAlongPath = source.DistanceAlongPath, - DistanceFromPath = source.DistanceFromPath - }; + { + DistanceAlongPath = source.DistanceAlongPath, + DistanceFromPath = source.DistanceFromPath + }; } } } \ No newline at end of file diff --git a/src/ImageSharp.Drawing.Paths/Properties/AssemblyInfo.cs b/src/ImageSharp.Drawing.Paths/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..fba25a9dba --- /dev/null +++ b/src/ImageSharp.Drawing.Paths/Properties/AssemblyInfo.cs @@ -0,0 +1,6 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +// Common values read from `AssemblyInfo.Common.cs` diff --git a/src/ImageSharp.Drawing/Processors/RectangleExtensions.cs b/src/ImageSharp.Drawing.Paths/RectangleExtensions.cs similarity index 58% rename from src/ImageSharp.Drawing/Processors/RectangleExtensions.cs rename to src/ImageSharp.Drawing.Paths/RectangleExtensions.cs index 327b618fa4..05d6bb6ced 100644 --- a/src/ImageSharp.Drawing/Processors/RectangleExtensions.cs +++ b/src/ImageSharp.Drawing.Paths/RectangleExtensions.cs @@ -20,13 +20,17 @@ namespace ImageSharp.Drawing.Processors internal static class RectangleExtensions { /// - /// Converts a Shaper2D to an ImageSharp . + /// Converts a Shaper2D to an ImageSharp by creating a the entirely surrounds the source. /// /// The source. - /// A representation of this - public static RectangleF Convert(this SixLabors.Shapes.Rectangle source) + /// A representation of this + public static Rectangle Convert(this SixLabors.Shapes.Rectangle source) { - return new RectangleF(source.Location.X, source.Location.Y, source.Size.Width, source.Size.Height); + int y = (int)Math.Floor(source.Y); + int width = (int)Math.Ceiling(source.Size.Width); + int x = (int)Math.Floor(source.X); + int height = (int)Math.Ceiling(source.Size.Height); + return new Rectangle(x, y, width, height); } } } \ No newline at end of file diff --git a/src/ImageSharp.Drawing/ShapeRegion.cs b/src/ImageSharp.Drawing.Paths/ShapePath.cs similarity index 83% rename from src/ImageSharp.Drawing/ShapeRegion.cs rename to src/ImageSharp.Drawing.Paths/ShapePath.cs index 6bd0703672..63d5fc9e1b 100644 --- a/src/ImageSharp.Drawing/ShapeRegion.cs +++ b/src/ImageSharp.Drawing.Paths/ShapePath.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // @@ -18,8 +18,7 @@ namespace ImageSharp.Drawing /// /// A drawable mapping between a / and a drawable/fillable region. /// - /// - internal class ShapeRegion : IDrawableRegion + internal class ShapePath : ImageSharp.Drawing.Path { /// /// The fillable shape @@ -32,10 +31,10 @@ namespace ImageSharp.Drawing private readonly ImmutableArray paths; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The path. - public ShapeRegion(IPath path) + public ShapePath(IPath path) : this(ImmutableArray.Create(path)) { this.shape = path.AsShape(); @@ -43,10 +42,10 @@ namespace ImageSharp.Drawing } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The shape. - public ShapeRegion(IShape shape) + public ShapePath(IShape shape) : this(shape.Paths) { this.shape = shape; @@ -54,10 +53,10 @@ namespace ImageSharp.Drawing } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The paths. - private ShapeRegion(ImmutableArray paths) + private ShapePath(ImmutableArray paths) { this.paths = paths; } @@ -68,7 +67,7 @@ namespace ImageSharp.Drawing /// /// The maximum intersections. /// - public int MaxIntersections => this.shape.MaxIntersections; + public override int MaxIntersections => this.shape.MaxIntersections; /// /// Gets the bounds. @@ -76,7 +75,7 @@ namespace ImageSharp.Drawing /// /// The bounds. /// - public Rectangle Bounds { get; } + public override Rectangle Bounds { get; } /// /// Scans the X axis for intersections. @@ -88,7 +87,7 @@ namespace ImageSharp.Drawing /// /// The number of intersections found. /// - public int ScanX(int x, float[] buffer, int length, int offset) + public override int ScanX(int x, float[] buffer, int length, int offset) { var start = new Vector2(x, this.Bounds.Top - 1); var end = new Vector2(x, this.Bounds.Bottom + 1); @@ -125,10 +124,10 @@ namespace ImageSharp.Drawing /// /// The number of intersections found. /// - public int ScanY(int y, float[] buffer, int length, int offset) + public override int ScanY(int y, float[] buffer, int length, int offset) { - var start = new Vector2(this.Bounds.Left - 1, y); - var end = new Vector2(this.Bounds.Right + 1, y); + var start = new Vector2(float.MinValue, y); + var end = new Vector2(float.MaxValue, y); Vector2[] innerbuffer = ArrayPool.Shared.Rent(length); try { @@ -158,7 +157,7 @@ namespace ImageSharp.Drawing /// The x. /// The y. /// Information about the the point - public PointInfo GetPointInfo(int x, int y) + public override PointInfo GetPointInfo(int x, int y) { var point = new Vector2(x, y); SixLabors.Shapes.PointInfo result = default(SixLabors.Shapes.PointInfo); diff --git a/src/ImageSharp.Drawing.Paths/ShapeRegion.cs b/src/ImageSharp.Drawing.Paths/ShapeRegion.cs new file mode 100644 index 0000000000..d1a59a99d9 --- /dev/null +++ b/src/ImageSharp.Drawing.Paths/ShapeRegion.cs @@ -0,0 +1,137 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Drawing +{ + using System.Buffers; + using System.Collections.Immutable; + using System.Numerics; + + using ImageSharp.Drawing.Processors; + + using SixLabors.Shapes; + + using Rectangle = ImageSharp.Rectangle; + + /// + /// A drawable mapping between a / and a drawable/fillable region. + /// + internal class ShapeRegion : Region + { + /// + /// The fillable shape + /// + private readonly IShape shape; + + /// + /// Initializes a new instance of the class. + /// + /// The path. + public ShapeRegion(IPath path) + : this(path.AsShape()) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The shape. + public ShapeRegion(IShape shape) + { + this.shape = shape; + this.Bounds = shape.Bounds.Convert(); + } + + /// + /// Gets the maximum number of intersections to could be returned. + /// + /// + /// The maximum intersections. + /// + public override int MaxIntersections => this.shape.MaxIntersections; + + /// + /// Gets the bounds. + /// + /// + /// The bounds. + /// + public override Rectangle Bounds { get; } + + /// + /// Scans the X axis for intersections. + /// + /// The x. + /// The buffer. + /// The length. + /// The offset. + /// + /// The number of intersections found. + /// + public override int ScanX(int x, float[] buffer, int length, int offset) + { + var start = new Vector2(x, this.Bounds.Top - 1); + var end = new Vector2(x, this.Bounds.Bottom + 1); + Vector2[] innerbuffer = ArrayPool.Shared.Rent(length); + try + { + int count = this.shape.FindIntersections( + start, + end, + innerbuffer, + length, + 0); + + for (var i = 0; i < count; i++) + { + buffer[i + offset] = innerbuffer[i].Y; + } + + return count; + } + finally + { + ArrayPool.Shared.Return(innerbuffer); + } + } + + /// + /// Scans the Y axis for intersections. + /// + /// The position along the y axis to find intersections. + /// The buffer. + /// The length. + /// The offset. + /// + /// The number of intersections found. + /// + public override int ScanY(int y, float[] buffer, int length, int offset) + { + var start = new Vector2(float.MinValue, y); + var end = new Vector2(float.MaxValue, y); + Vector2[] innerbuffer = ArrayPool.Shared.Rent(length); + try + { + int count = this.shape.FindIntersections( + start, + end, + innerbuffer, + length, + 0); + + for (var i = 0; i < count; i++) + { + buffer[i + offset] = innerbuffer[i].X; + } + + return count; + } + finally + { + ArrayPool.Shared.Return(innerbuffer); + } + } + } +} diff --git a/src/ImageSharp.Drawing.Paths/project.json b/src/ImageSharp.Drawing.Paths/project.json new file mode 100644 index 0000000000..cdc5d2244f --- /dev/null +++ b/src/ImageSharp.Drawing.Paths/project.json @@ -0,0 +1,96 @@ +{ + "version": "1.0.0-alpha1-*", + "title": "ImageSharp.Drawing.Paths", + "description": "A cross-platform library for the processing of image files; written in C#", + "authors": [ + "James Jackson-South and contributors" + ], + "packOptions": { + "owners": [ + "James Jackson-South and contributors" + ], + "projectUrl": "https://github.com/JimBobSquarePants/ImageSharp", + "licenseUrl": "http://www.apache.org/licenses/LICENSE-2.0", + "iconUrl": "https://raw.githubusercontent.com/JimBobSquarePants/ImageSharp/master/build/icons/imagesharp-logo-128.png", + "requireLicenseAcceptance": false, + "repository": { + "type": "git", + "url": "https://github.com/JimBobSquarePants/ImageSharp" + }, + "tags": [ + "Image Resize Crop Gif Jpg Jpeg Bitmap Png Core" + ] + }, + "buildOptions": { + "allowUnsafe": true, + "xmlDoc": true, + "additionalArguments": [ "/additionalfile:../Shared/stylecop.json", "/ruleset:../../ImageSharp.ruleset" ], + "compile": [ + "../Shared/*.cs" + ] + }, + "configurations": { + "Release": { + "buildOptions": { + "warningsAsErrors": true, + "optimize": true + } + } + }, + "dependencies": { + "ImageSharp": { + "target": "project" + }, + "ImageSharp.Drawing": { + "target": "project" + }, + "SixLabors.Shapes": "0.1.0-ci0050", + "StyleCop.Analyzers": { + "version": "1.0.0", + "type": "build" + }, + "System.Buffers": "4.0.0", + "System.Runtime.CompilerServices.Unsafe": "4.0.0" + }, + "frameworks": { + "netstandard1.1": { + "dependencies": { + "System.Collections": "4.0.11", + "System.Diagnostics.Debug": "4.0.11", + "System.Diagnostics.Tools": "4.0.1", + "System.IO": "4.1.0", + "System.IO.Compression": "4.1.0", + "System.Linq": "4.1.0", + "System.Numerics.Vectors": "4.1.1", + "System.ObjectModel": "4.0.12", + "System.Resources.ResourceManager": "4.0.1", + "System.Runtime.Extensions": "4.1.0", + "System.Runtime.InteropServices": "4.1.0", + "System.Runtime.Numerics": "4.0.1", + "System.Text.Encoding.Extensions": "4.0.11", + "System.Threading": "4.0.11", + "System.Threading.Tasks": "4.0.11", + "System.Threading.Tasks.Parallel": "4.0.1" + } + }, + "net45": { + "dependencies": { + "System.Numerics.Vectors": "4.1.1", + "System.Threading.Tasks.Parallel": "4.0.0" + }, + "frameworkAssemblies": { + "System.Runtime": { "type": "build" } + } + }, + "net461": { + "dependencies": { + "System.Threading.Tasks.Parallel": "4.0.0" + }, + "frameworkAssemblies": { + "System.Runtime": { "type": "build" }, + "System.Numerics": "4.0.0.0", + "System.Numerics.Vectors": "4.0.0.0" + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs b/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs index 9103dfdf66..97d3f840cb 100644 --- a/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs +++ b/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs @@ -16,7 +16,6 @@ namespace ImageSharp.Drawing.Processors public abstract class BrushApplicator : IDisposable // disposable will be required if/when there is an ImageBrush where TColor : struct, IPackedPixel, IEquatable { - /// /// Gets the color for a single pixel. /// diff --git a/src/ImageSharp.Drawing/Draw.cs b/src/ImageSharp.Drawing/Draw.cs deleted file mode 100644 index 55dba9257d..0000000000 --- a/src/ImageSharp.Drawing/Draw.cs +++ /dev/null @@ -1,506 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - using System; - using System.Numerics; - using Drawing; - using Drawing.Brushes; - using Drawing.Pens; - using Drawing.Processors; - using SixLabors.Shapes; - - /// - /// Extension methods for the type. - /// - public static partial class ImageExtensions - { - /// - /// Draws the outline of the polygon with the provided pen. - /// - /// The type of the color. - /// The source. - /// The pen. - /// The shape. - /// The options. - /// - /// The Image - /// - public static Image DrawPolygon(this Image source, IPen pen, IShape shape, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable - { - return source.Apply(new DrawPathProcessor(pen, new ShapeRegion(shape), options)); - } - - /// - /// Draws the outline of the polygon with the provided pen. - /// - /// The type of the color. - /// The source. - /// The pen. - /// The shape. - /// The Image - public static Image DrawPolygon(this Image source, IPen pen, IShape shape) - where TColor : struct, IPackedPixel, IEquatable - { - return source.DrawPolygon(pen, shape, GraphicsOptions.Default); - } - - /// - /// Draws the outline of the polygon with the provided brush at the provided thickness. - /// - /// The type of the color. - /// The source. - /// The brush. - /// The thickness. - /// The shape. - /// The options. - /// - /// The Image - /// - public static Image DrawPolygon(this Image source, IBrush brush, float thickness, IShape shape, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable - { - return source.DrawPolygon(new Pen(brush, thickness), shape, options); - } - - /// - /// Draws the outline of the polygon with the provided brush at the provided thickness. - /// - /// The type of the color. - /// The source. - /// The brush. - /// The thickness. - /// The shape. - /// The Image - public static Image DrawPolygon(this Image source, IBrush brush, float thickness, IShape shape) - where TColor : struct, IPackedPixel, IEquatable - { - return source.DrawPolygon(new Pen(brush, thickness), shape); - } - - /// - /// Draws the outline of the polygon with the provided brush at the provided thickness. - /// - /// The type of the color. - /// The source. - /// The color. - /// The thickness. - /// The shape. - /// The options. - /// - /// The Image - /// - public static Image DrawPolygon(this Image source, TColor color, float thickness, IShape shape, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable - { - return source.DrawPolygon(new SolidBrush(color), thickness, shape, options); - } - - /// - /// Draws the outline of the polygon with the provided brush at the provided thickness. - /// - /// The type of the color. - /// The source. - /// The color. - /// The thickness. - /// The shape. - /// The Image - public static Image DrawPolygon(this Image source, TColor color, float thickness, IShape shape) - where TColor : struct, IPackedPixel, IEquatable - { - return source.DrawPolygon(new SolidBrush(color), thickness, shape); - } - - /// - /// Draws the provided Points as a closed Linear Polygon with the provided brush at the provided thickness. - /// - /// The type of the color. - /// The source. - /// The brush. - /// The thickness. - /// The points. - /// The options. - /// - /// The Image - /// - public static Image DrawPolygon(this Image source, IBrush brush, float thickness, Vector2[] points, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable - { - return source.DrawPolygon(new Pen(brush, thickness), new Polygon(new LinearLineSegment(points)), options); - } - - /// - /// Draws the provided Points as a closed Linear Polygon with the provided brush at the provided thickness. - /// - /// The type of the color. - /// The source. - /// The brush. - /// The thickness. - /// The points. - /// The Image - public static Image DrawPolygon(this Image source, IBrush brush, float thickness, Vector2[] points) - where TColor : struct, IPackedPixel, IEquatable - { - return source.DrawPolygon(new Pen(brush, thickness), new Polygon(new LinearLineSegment(points))); - } - - /// - /// Draws the provided Points as a closed Linear Polygon with the provided brush at the provided thickness. - /// - /// The type of the color. - /// The source. - /// The color. - /// The thickness. - /// The points. - /// The Image - public static Image DrawPolygon(this Image source, TColor color, float thickness, Vector2[] points) - where TColor : struct, IPackedPixel, IEquatable - { - return source.DrawPolygon(new SolidBrush(color), thickness, points); - } - - /// - /// Draws the provided Points as a closed Linear Polygon with the provided brush at the provided thickness. - /// - /// The type of the color. - /// The source. - /// The color. - /// The thickness. - /// The points. - /// The options. - /// - /// The Image - /// - public static Image DrawPolygon(this Image source, TColor color, float thickness, Vector2[] points, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable - { - return source.DrawPolygon(new SolidBrush(color), thickness, points, options); - } - - /// - /// Draws the provided Points as a closed Linear Polygon with the provided Pen. - /// - /// The type of the color. - /// The source. - /// The pen. - /// The points. - /// The options. - /// - /// The Image - /// - public static Image DrawPolygon(this Image source, IPen pen, Vector2[] points, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable - { - return source.DrawPolygon(pen, new Polygon(new LinearLineSegment(points)), options); - } - - /// - /// Draws the provided Points as a closed Linear Polygon with the provided Pen. - /// - /// The type of the color. - /// The source. - /// The pen. - /// The points. - /// The Image - public static Image DrawPolygon(this Image source, IPen pen, Vector2[] points) - where TColor : struct, IPackedPixel, IEquatable - { - return source.DrawPolygon(pen, new Polygon(new LinearLineSegment(points))); - } - - /// - /// Draws the path with the provided pen. - /// - /// The type of the color. - /// The source. - /// The pen. - /// The path. - /// The options. - /// - /// The Image - /// - public static Image DrawPath(this Image source, IPen pen, IPath path, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable - { - return source.Apply(new DrawPathProcessor(pen, new ShapeRegion(path), options)); - } - - /// - /// Draws the path with the provided pen. - /// - /// The type of the color. - /// The source. - /// The pen. - /// The path. - /// The Image - public static Image DrawPath(this Image source, IPen pen, IPath path) - where TColor : struct, IPackedPixel, IEquatable - { - return source.Apply(new DrawPathProcessor(pen, new ShapeRegion(path), GraphicsOptions.Default)); - } - - /// - /// Draws the path with the bursh at the privdied thickness. - /// - /// The type of the color. - /// The source. - /// The brush. - /// The thickness. - /// The path. - /// The options. - /// - /// The Image - /// - public static Image DrawPath(this Image source, IBrush brush, float thickness, IPath path, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable - { - return source.DrawPath(new Pen(brush, thickness), path, options); - } - - /// - /// Draws the path with the bursh at the privdied thickness. - /// - /// The type of the color. - /// The source. - /// The brush. - /// The thickness. - /// The path. - /// The Image - public static Image DrawPath(this Image source, IBrush brush, float thickness, IPath path) - where TColor : struct, IPackedPixel, IEquatable - { - return source.DrawPath(new Pen(brush, thickness), path); - } - - /// - /// Draws the path with the bursh at the privdied thickness. - /// - /// The type of the color. - /// The source. - /// The color. - /// The thickness. - /// The path. - /// The options. - /// - /// The Image - /// - public static Image DrawPath(this Image source, TColor color, float thickness, IPath path, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable - { - return source.DrawPath(new SolidBrush(color), thickness, path, options); - } - - /// - /// Draws the path with the bursh at the privdied thickness. - /// - /// The type of the color. - /// The source. - /// The color. - /// The thickness. - /// The path. - /// The Image - public static Image DrawPath(this Image source, TColor color, float thickness, IPath path) - where TColor : struct, IPackedPixel, IEquatable - { - return source.DrawPath(new SolidBrush(color), thickness, path); - } - - /// - /// Draws the provided Points as an open Linear path at the provided thickness with the supplied brush - /// - /// The type of the color. - /// The source. - /// The brush. - /// The thickness. - /// The points. - /// The options. - /// - /// The Image - /// - public static Image DrawLines(this Image source, IBrush brush, float thickness, Vector2[] points, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable - { - return source.DrawPath(new Pen(brush, thickness), new Path(new LinearLineSegment(points)), options); - } - - /// - /// Draws the provided Points as an open Linear path at the provided thickness with the supplied brush - /// - /// The type of the color. - /// The source. - /// The brush. - /// The thickness. - /// The points. - /// The Image - public static Image DrawLines(this Image source, IBrush brush, float thickness, Vector2[] points) - where TColor : struct, IPackedPixel, IEquatable - { - return source.DrawPath(new Pen(brush, thickness), new Path(new LinearLineSegment(points))); - } - - /// - /// Draws the provided Points as an open Linear path at the provided thickness with the supplied brush - /// - /// The type of the color. - /// The source. - /// The color. - /// The thickness. - /// The points. - /// The Image - public static Image DrawLines(this Image source, TColor color, float thickness, Vector2[] points) - where TColor : struct, IPackedPixel, IEquatable - { - return source.DrawLines(new SolidBrush(color), thickness, points); - } - - /// - /// Draws the provided Points as an open Linear path at the provided thickness with the supplied brush - /// - /// The type of the color. - /// The source. - /// The color. - /// The thickness. - /// The points. - /// The options. - /// - /// The Image - /// - public static Image DrawLines(this Image source, TColor color, float thickness, Vector2[] points, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable - { - return source.DrawLines(new SolidBrush(color), thickness, points, options); - } - - /// - /// Draws the provided Points as an open Linear path with the supplied pen - /// - /// The type of the color. - /// The source. - /// The pen. - /// The points. - /// The options. - /// - /// The Image - /// - public static Image DrawLines(this Image source, IPen pen, Vector2[] points, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable - { - return source.DrawPath(pen, new Path(new LinearLineSegment(points)), options); - } - - /// - /// Draws the provided Points as an open Linear path with the supplied pen - /// - /// The type of the color. - /// The source. - /// The pen. - /// The points. - /// The Image - public static Image DrawLines(this Image source, IPen pen, Vector2[] points) - where TColor : struct, IPackedPixel, IEquatable - { - return source.DrawPath(pen, new Path(new LinearLineSegment(points))); - } - - /// - /// Draws the provided Points as an open Bezier path at the provided thickness with the supplied brush - /// - /// The type of the color. - /// The source. - /// The brush. - /// The thickness. - /// The points. - /// The options. - /// - /// The Image - /// - public static Image DrawBeziers(this Image source, IBrush brush, float thickness, Vector2[] points, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable - { - return source.DrawPath(new Pen(brush, thickness), new Path(new BezierLineSegment(points)), options); - } - - /// - /// Draws the provided Points as an open Bezier path at the provided thickness with the supplied brush - /// - /// The type of the color. - /// The source. - /// The brush. - /// The thickness. - /// The points. - /// The Image - public static Image DrawBeziers(this Image source, IBrush brush, float thickness, Vector2[] points) - where TColor : struct, IPackedPixel, IEquatable - { - return source.DrawPath(new Pen(brush, thickness), new Path(new BezierLineSegment(points))); - } - - /// - /// Draws the provided Points as an open Bezier path at the provided thickness with the supplied brush - /// - /// The type of the color. - /// The source. - /// The color. - /// The thickness. - /// The points. - /// The Image - public static Image DrawBeziers(this Image source, TColor color, float thickness, Vector2[] points) - where TColor : struct, IPackedPixel, IEquatable - { - return source.DrawBeziers(new SolidBrush(color), thickness, points); - } - - /// - /// Draws the provided Points as an open Bezier path at the provided thickness with the supplied brush - /// - /// The type of the color. - /// The source. - /// The color. - /// The thickness. - /// The points. - /// The options. - /// - /// The Image - /// - public static Image DrawBeziers(this Image source, TColor color, float thickness, Vector2[] points, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable - { - return source.DrawBeziers(new SolidBrush(color), thickness, points, options); - } - - /// - /// Draws the provided Points as an open Bezier path with the supplied pen - /// - /// The type of the color. - /// The source. - /// The pen. - /// The points. - /// The options. - /// - /// The Image - /// - public static Image DrawBeziers(this Image source, IPen pen, Vector2[] points, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable - { - return source.DrawPath(pen, new Path(new BezierLineSegment(points)), options); - } - - /// - /// Draws the provided Points as an open Bezier path with the supplied pen - /// - /// The type of the color. - /// The source. - /// The pen. - /// The points. - /// The Image - public static Image DrawBeziers(this Image source, IPen pen, Vector2[] points) - where TColor : struct, IPackedPixel, IEquatable - { - return source.DrawPath(pen, new Path(new BezierLineSegment(points))); - } - } -} diff --git a/src/ImageSharp.Drawing/DrawPath.cs b/src/ImageSharp.Drawing/DrawPath.cs new file mode 100644 index 0000000000..828d50fb33 --- /dev/null +++ b/src/ImageSharp.Drawing/DrawPath.cs @@ -0,0 +1,123 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Numerics; + using Drawing; + using Drawing.Brushes; + using Drawing.Pens; + using Drawing.Processors; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Draws the outline of the region with the provided pen. + /// + /// The type of the color. + /// The source. + /// The pen. + /// The path. + /// The options. + /// + /// The Image + /// + public static Image Draw(this Image source, IPen pen, Path path, GraphicsOptions options) + where TColor : struct, IPackedPixel, IEquatable + { + return source.Apply(new DrawPathProcessor(pen, path, options)); + } + + /// + /// Draws the outline of the polygon with the provided pen. + /// + /// The type of the color. + /// The source. + /// The pen. + /// The path. + /// + /// The Image + /// + public static Image Draw(this Image source, IPen pen, Path path) + where TColor : struct, IPackedPixel, IEquatable + { + return source.Draw(pen, path, GraphicsOptions.Default); + } + + /// + /// Draws the outline of the polygon with the provided brush at the provided thickness. + /// + /// The type of the color. + /// The source. + /// The brush. + /// The thickness. + /// The path. + /// The options. + /// + /// The Image + /// + public static Image Draw(this Image source, IBrush brush, float thickness, Path path, GraphicsOptions options) + where TColor : struct, IPackedPixel, IEquatable + { + return source.Draw(new Pen(brush, thickness), path, options); + } + + /// + /// Draws the outline of the polygon with the provided brush at the provided thickness. + /// + /// The type of the color. + /// The source. + /// The brush. + /// The thickness. + /// The path. + /// + /// The Image + /// + public static Image Draw(this Image source, IBrush brush, float thickness, Path path) + where TColor : struct, IPackedPixel, IEquatable + { + return source.Draw(new Pen(brush, thickness), path); + } + + /// + /// Draws the outline of the polygon with the provided brush at the provided thickness. + /// + /// The type of the color. + /// The source. + /// The color. + /// The thickness. + /// The path. + /// The options. + /// + /// The Image + /// + public static Image Draw(this Image source, TColor color, float thickness, Path path, GraphicsOptions options) + where TColor : struct, IPackedPixel, IEquatable + { + return source.Draw(new SolidBrush(color), thickness, path, options); + } + + /// + /// Draws the outline of the polygon with the provided brush at the provided thickness. + /// + /// The type of the color. + /// The source. + /// The color. + /// The thickness. + /// The path. + /// + /// The Image + /// + public static Image Draw(this Image source, TColor color, float thickness, Path path) + where TColor : struct, IPackedPixel, IEquatable + { + return source.Draw(new SolidBrush(color), thickness, path); + } + } +} diff --git a/src/ImageSharp.Drawing/Fill.cs b/src/ImageSharp.Drawing/Fill.cs deleted file mode 100644 index f94d9ca6f9..0000000000 --- a/src/ImageSharp.Drawing/Fill.cs +++ /dev/null @@ -1,173 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - using System; - using System.Numerics; - using Drawing; - using Drawing.Brushes; - using Drawing.Processors; - - using SixLabors.Shapes; - - /// - /// Extension methods for the type. - /// - public static partial class ImageExtensions - { - /// - /// Flood fills the image with the specified brush. - /// - /// The type of the color. - /// The source. - /// The brush. - /// The Image - public static Image Fill(this Image source, IBrush brush) - where TColor : struct, IPackedPixel, IEquatable - { - return source.Apply(new FillProcessor(brush)); - } - - /// - /// Flood fills the image with the specified color. - /// - /// The type of the color. - /// The source. - /// The color. - /// The Image - public static Image Fill(this Image source, TColor color) - where TColor : struct, IPackedPixel, IEquatable - { - return source.Fill(new SolidBrush(color)); - } - - /// - /// Flood fills the image in the shape o fhte provided polygon with the specified brush.. - /// - /// The type of the color. - /// The source. - /// The brush. - /// The shape. - /// The graphics options. - /// The Image - public static Image Fill(this Image source, IBrush brush, IShape shape, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable - { - return source.Apply(new FillShapeProcessor(brush, new ShapeRegion(shape), options)); - } - - /// - /// Flood fills the image in the shape of the provided polygon with the specified brush. - /// - /// The type of the color. - /// The source. - /// The brush. - /// The shape. - /// The Image - public static Image Fill(this Image source, IBrush brush, IShape shape) - where TColor : struct, IPackedPixel, IEquatable - { - return source.Apply(new FillShapeProcessor(brush, new ShapeRegion(shape), GraphicsOptions.Default)); - } - - /// - /// Flood fills the image in the shape o fhte provided polygon with the specified brush.. - /// - /// The type of the color. - /// The source. - /// The color. - /// The shape. - /// The options. - /// - /// The Image - /// - public static Image Fill(this Image source, TColor color, IShape shape, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable - { - return source.Fill(new SolidBrush(color), shape, options); - } - - /// - /// Flood fills the image in the shape o fhte provided polygon with the specified brush.. - /// - /// The type of the color. - /// The source. - /// The color. - /// The shape. - /// The Image - public static Image Fill(this Image source, TColor color, IShape shape) - where TColor : struct, IPackedPixel, IEquatable - { - return source.Fill(new SolidBrush(color), shape); - } - - /// - /// Flood fills the image in the shape of a Linear polygon described by the points - /// - /// The type of the color. - /// The source. - /// The brush. - /// The points. - /// The options. - /// - /// The Image - /// - public static Image FillPolygon(this Image source, IBrush brush, Vector2[] points, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable - { - // using Polygon directly instead of LinearPolygon as its will have less indirection - return source.Fill(brush, new Polygon(new LinearLineSegment(points)), options); - } - - /// - /// Flood fills the image in the shape of a Linear polygon described by the points - /// - /// The type of the color. - /// The source. - /// The brush. - /// The points. - /// The Image - public static Image FillPolygon(this Image source, IBrush brush, Vector2[] points) - where TColor : struct, IPackedPixel, IEquatable - { - // using Polygon directly instead of LinearPolygon as its will have less indirection - return source.Fill(brush, new Polygon(new LinearLineSegment(points))); - } - - /// - /// Flood fills the image in the shape of a Linear polygon described by the points - /// - /// The type of the color. - /// The source. - /// The color. - /// The points. - /// The options. - /// - /// The Image - /// - public static Image FillPolygon(this Image source, TColor color, Vector2[] points, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable - { - // using Polygon directly instead of LinearPolygon as its will have less indirection - return source.Fill(new SolidBrush(color), new Polygon(new LinearLineSegment(points)), options); - } - - /// - /// Flood fills the image in the shape of a Linear polygon described by the points - /// - /// The type of the color. - /// The source. - /// The color. - /// The points. - /// The Image - public static Image FillPolygon(this Image source, TColor color, Vector2[] points) - where TColor : struct, IPackedPixel, IEquatable - { - // using Polygon directly instead of LinearPolygon as its will have less indirection - return source.Fill(new SolidBrush(color), new Polygon(new LinearLineSegment(points))); - } - } -} diff --git a/src/ImageSharp.Drawing/FillRegion.cs b/src/ImageSharp.Drawing/FillRegion.cs new file mode 100644 index 0000000000..8d4f20b673 --- /dev/null +++ b/src/ImageSharp.Drawing/FillRegion.cs @@ -0,0 +1,111 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Numerics; + using Drawing; + using Drawing.Brushes; + using Drawing.Processors; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Flood fills the image with the specified brush. + /// + /// The type of the color. + /// The source. + /// The brush. + /// The Image + public static Image Fill(this Image source, IBrush brush) + where TColor : struct, IPackedPixel, IEquatable + { + return source.Apply(new FillProcessor(brush)); + } + + /// + /// Flood fills the image with the specified color. + /// + /// The type of the color. + /// The source. + /// The color. + /// The Image + public static Image Fill(this Image source, TColor color) + where TColor : struct, IPackedPixel, IEquatable + { + return source.Fill(new SolidBrush(color)); + } + + /// + /// Flood fills the image with in the region with the specified brush. + /// + /// The type of the color. + /// The source. + /// The brush. + /// The region. + /// The graphics options. + /// + /// The Image + /// + public static Image Fill(this Image source, IBrush brush, Region region, GraphicsOptions options) + where TColor : struct, IPackedPixel, IEquatable + { + return source.Apply(new FillRegionProcessor(brush, region, options)); + } + + /// + /// Flood fills the image with in the region with the specified brush. + /// + /// The type of the color. + /// The source. + /// The brush. + /// The region. + /// + /// The Image + /// + public static Image Fill(this Image source, IBrush brush, Region region) + where TColor : struct, IPackedPixel, IEquatable + { + return source.Fill(brush, region, GraphicsOptions.Default); + } + + /// + /// Flood fills the image with in the region with the specified color. + /// + /// The type of the color. + /// The source. + /// The color. + /// The region. + /// The options. + /// + /// The Image + /// + public static Image Fill(this Image source, TColor color, Region region, GraphicsOptions options) + where TColor : struct, IPackedPixel, IEquatable + { + return source.Fill(new SolidBrush(color), region, options); + } + + /// + /// Flood fills the image with in the region with the specified color. + /// + /// The type of the color. + /// The source. + /// The color. + /// The region. + /// + /// The Image + /// + public static Image Fill(this Image source, TColor color, Region region) + where TColor : struct, IPackedPixel, IEquatable + { + return source.Fill(new SolidBrush(color), region); + } + } +} diff --git a/src/ImageSharp.Drawing/IDrawableRegion.cs b/src/ImageSharp.Drawing/IDrawableRegion.cs deleted file mode 100644 index 82e9c39acf..0000000000 --- a/src/ImageSharp.Drawing/IDrawableRegion.cs +++ /dev/null @@ -1,24 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Drawing -{ - using System.Numerics; - - /// - /// Represents a region with knowledge about its outline. - /// - /// - public interface IDrawableRegion : IRegion - { - /// - /// Gets the point information for the specified x and y location. - /// - /// The x. - /// The y. - /// Information about the point in relation to a drawable edge - PointInfo GetPointInfo(int x, int y); - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Path.cs b/src/ImageSharp.Drawing/Path.cs new file mode 100644 index 0000000000..a997fa18f7 --- /dev/null +++ b/src/ImageSharp.Drawing/Path.cs @@ -0,0 +1,57 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Drawing +{ + /// + /// Represents a something that has knowledge about its outline. + /// + public abstract class Path + { + /// + /// Gets the maximum number of intersections to could be returned. + /// + /// + /// The maximum intersections. + /// + public abstract int MaxIntersections { get; } + + /// + /// Gets the bounds. + /// + /// + /// The bounds. + /// + public abstract Rectangle Bounds { get; } + + /// + /// Gets the point information for the specified x and y location. + /// + /// The x. + /// The y. + /// Information about the point in relation to a drawable edge + public abstract PointInfo GetPointInfo(int x, int y); + + /// + /// Scans the X axis for intersections. + /// + /// The x. + /// The buffer. + /// The length. + /// The offset. + /// The number of intersections found. + public abstract int ScanX(int x, float[] buffer, int length, int offset); + + /// + /// Scans the Y axis for intersections. + /// + /// The position along the y axis to find intersections. + /// The buffer. + /// The length. + /// The offset. + /// The number of intersections found. + public abstract int ScanY(int y, float[] buffer, int length, int offset); + } +} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs b/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs index bbb5e1ab6c..1513443c4e 100644 --- a/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs @@ -12,7 +12,6 @@ namespace ImageSharp.Drawing.Processors using System.Threading.Tasks; using ImageSharp.Processing; using Pens; - using SixLabors.Shapes; using Rectangle = ImageSharp.Rectangle; /// @@ -27,7 +26,7 @@ namespace ImageSharp.Drawing.Processors private const int PaddingFactor = 1; // needs to been the same or greater than AntialiasFactor private readonly IPen pen; - private readonly IDrawableRegion region; + private readonly Path region; private readonly GraphicsOptions options; /// @@ -36,7 +35,7 @@ namespace ImageSharp.Drawing.Processors /// The pen. /// The region. /// The options. - public DrawPathProcessor(IPen pen, IDrawableRegion region, GraphicsOptions options) + public DrawPathProcessor(IPen pen, Path region, GraphicsOptions options) { this.region = region; this.pen = pen; diff --git a/src/ImageSharp.Drawing/Processors/FillShapeProcessor.cs b/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs similarity index 95% rename from src/ImageSharp.Drawing/Processors/FillShapeProcessor.cs rename to src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs index bdec022d49..2f6f8af7ba 100644 --- a/src/ImageSharp.Drawing/Processors/FillShapeProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // @@ -11,30 +11,28 @@ namespace ImageSharp.Drawing.Processors using System.Threading.Tasks; using Drawing; using ImageSharp.Processing; - using SixLabors.Shapes; - using Rectangle = ImageSharp.Rectangle; /// /// Usinf a brsuh and a shape fills shape with contents of brush the /// /// The type of the color. /// - public class FillShapeProcessor : ImageProcessor + public class FillRegionProcessor : ImageProcessor where TColor : struct, IPackedPixel, IEquatable { private const float AntialiasFactor = 1f; private const int DrawPadding = 1; private readonly IBrush fillColor; - private readonly IRegion region; + private readonly Region region; private readonly GraphicsOptions options; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The brush. /// The region. /// The options. - public FillShapeProcessor(IBrush brush, IRegion region, GraphicsOptions options) + public FillRegionProcessor(IBrush brush, Region region, GraphicsOptions options) { this.region = region; this.fillColor = brush; @@ -89,6 +87,12 @@ namespace ImageSharp.Drawing.Processors return; } + if (pointsFound % 2 == 1) + { + // we seem to have just clipped a corner lets just skip it + return; + } + QuickSort(buffer, pointsFound); int currentIntersection = 0; @@ -96,13 +100,8 @@ namespace ImageSharp.Drawing.Processors float lastPoint = float.MinValue; bool isInside = false; - // every odd point is the start of a line - Vector2 currentPoint = default(Vector2); - for (int x = minX; x < maxX; x++) { - currentPoint.X = x; - currentPoint.Y = y; if (!isInside) { if (x < (nextPoint - DrawPadding) && x > (lastPoint + DrawPadding)) @@ -232,6 +231,12 @@ namespace ImageSharp.Drawing.Processors return; } + if (pointsFound % 2 == 1) + { + // we seem to have just clipped a corner lets just skip it + return; + } + QuickSort(buffer, pointsFound); int currentIntersection = 0; diff --git a/src/ImageSharp.Drawing/IRegion.cs b/src/ImageSharp.Drawing/Region.cs similarity index 78% rename from src/ImageSharp.Drawing/IRegion.cs rename to src/ImageSharp.Drawing/Region.cs index 2264a91bee..95f0e17480 100644 --- a/src/ImageSharp.Drawing/IRegion.cs +++ b/src/ImageSharp.Drawing/Region.cs @@ -1,16 +1,14 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // namespace ImageSharp.Drawing { - using System.Numerics; - /// /// Represents a region of an image. /// - public interface IRegion + public abstract class Region { /// /// Gets the maximum number of intersections to could be returned. @@ -18,7 +16,7 @@ namespace ImageSharp.Drawing /// /// The maximum intersections. /// - int MaxIntersections { get; } + public abstract int MaxIntersections { get; } /// /// Gets the bounds. @@ -26,7 +24,7 @@ namespace ImageSharp.Drawing /// /// The bounds. /// - Rectangle Bounds { get; } + public abstract Rectangle Bounds { get; } /// /// Scans the X axis for intersections. @@ -36,7 +34,7 @@ namespace ImageSharp.Drawing /// The length. /// The offset. /// The number of intersections found. - int ScanX(int x, float[] buffer, int length, int offset); + public abstract int ScanX(int x, float[] buffer, int length, int offset); /// /// Scans the Y axis for intersections. @@ -46,6 +44,6 @@ namespace ImageSharp.Drawing /// The length. /// The offset. /// The number of intersections found. - int ScanY(int y, float[] buffer, int length, int offset); + public abstract int ScanY(int y, float[] buffer, int length, int offset); } } \ No newline at end of file diff --git a/src/ImageSharp.Drawing/project.json b/src/ImageSharp.Drawing/project.json index 5fcee9b41e..ff2900d9fa 100644 --- a/src/ImageSharp.Drawing/project.json +++ b/src/ImageSharp.Drawing/project.json @@ -44,7 +44,6 @@ "ImageSharp.Processing": { "target": "project" }, - "SixLabors.Shapes": "0.1.0-ci0047", "StyleCop.Analyzers": { "version": "1.0.0", "type": "build" diff --git a/tests/ImageSharp.Benchmarks/Drawing/FillRectangle.cs b/tests/ImageSharp.Benchmarks/Drawing/FillRectangle.cs index defb0e65e7..691955e8ed 100644 --- a/tests/ImageSharp.Benchmarks/Drawing/FillRectangle.cs +++ b/tests/ImageSharp.Benchmarks/Drawing/FillRectangle.cs @@ -39,7 +39,7 @@ namespace ImageSharp.Benchmarks { using (CoreImage image = new CoreImage(800, 800)) { - image.Fill(CoreColor.HotPink, new SixLabors.Shapes.Rectangle(10, 10, 190, 140)); + image.Fill(CoreColor.HotPink, new CoreRectangle(10, 10, 190, 140)); return new CoreSize(image.Width, image.Height); } diff --git a/tests/ImageSharp.Benchmarks/project.json b/tests/ImageSharp.Benchmarks/project.json index 8650a8af41..866a36faae 100644 --- a/tests/ImageSharp.Benchmarks/project.json +++ b/tests/ImageSharp.Benchmarks/project.json @@ -16,32 +16,28 @@ "dependencies": { "BenchmarkDotNet.Diagnostics.Windows": "0.10.1", "ImageSharp": { - "target": "project", - "version": "1.0.0-*" + "target": "project" }, "ImageSharp.Drawing": { - "target": "project", - "version": "1.0.0-*" + "target": "project" + }, + "ImageSharp.Drawing.Paths": { + "target": "project" }, "ImageSharp.Formats.Jpeg": { - "target": "project", - "version": "1.0.0-*" + "target": "project" }, "ImageSharp.Formats.Png": { - "target": "project", - "version": "1.0.0-*" + "target": "project" }, "ImageSharp.Formats.Bmp": { - "target": "project", - "version": "1.0.0-*" + "target": "project" }, "ImageSharp.Formats.Gif": { - "target": "project", - "version": "1.0.0-*" + "target": "project" }, "ImageSharp.Processing": { - "target": "project", - "version": "1.0.0-*" + "target": "project" } }, "commands": { diff --git a/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj b/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj index 75212d3617..1bce8003ea 100644 --- a/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj +++ b/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj @@ -179,8 +179,11 @@ ..\..\src\ImageSharp.Drawing\bin\$(Configuration)\net461\ImageSharp.Drawing.dll + + ..\..\src\ImageSharp.Drawing.Paths\bin\$(Configuration)\net461\ImageSharp.Drawing.Paths.dll + - ..\..\src\ImageSharp.Drawing\bin\$(Configuration)\net461\SixLabors.Shapes.dll + ..\..\src\ImageSharp.Drawing.Paths\bin\$(Configuration)\net461\SixLabors.Shapes.dll ..\..\src\ImageSharp.Formats.Bmp\bin\$(Configuration)\net461\ImageSharp.Formats.Bmp.dll diff --git a/tests/ImageSharp.Tests/Drawing/DrawPathTests.cs b/tests/ImageSharp.Tests/Drawing/DrawPathTests.cs index 31aa87d4b7..f4465027df 100644 --- a/tests/ImageSharp.Tests/Drawing/DrawPathTests.cs +++ b/tests/ImageSharp.Tests/Drawing/DrawPathTests.cs @@ -7,7 +7,7 @@ namespace ImageSharp.Tests.Drawing { using Drawing; using ImageSharp.Drawing; - using CorePath = SixLabors.Shapes.Path; + using ShapePath = SixLabors.Shapes.Path; using SixLabors.Shapes; using System; using System.Diagnostics.CodeAnalysis; @@ -33,13 +33,13 @@ namespace ImageSharp.Tests.Drawing new Vector2(60, 10), new Vector2(10, 400)); - CorePath p = new CorePath(linerSegemnt, bazierSegment); + ShapePath p = new ShapePath(linerSegemnt, bazierSegment); using (FileStream output = File.OpenWrite($"{path}/Simple.png")) { image .BackgroundColor(Color.Blue) - .DrawPath(Color.HotPink, 5, p) + .Draw(Color.HotPink, 5, p) .Save(output); } @@ -74,7 +74,7 @@ namespace ImageSharp.Tests.Drawing new Vector2(60, 10), new Vector2(10, 400)); - CorePath p = new CorePath(linerSegemnt, bazierSegment); + ShapePath p = new ShapePath(linerSegemnt, bazierSegment); using (Image image = new Image(500, 500)) { @@ -82,7 +82,7 @@ namespace ImageSharp.Tests.Drawing { image .BackgroundColor(Color.Blue) - .DrawPath(color, 10, p) + .Draw(color, 10, p) .Save(output); } diff --git a/tests/ImageSharp.Tests/Drawing/LineComplexPolygonTests.cs b/tests/ImageSharp.Tests/Drawing/LineComplexPolygonTests.cs index dd2ea5249c..03ec5d0c89 100644 --- a/tests/ImageSharp.Tests/Drawing/LineComplexPolygonTests.cs +++ b/tests/ImageSharp.Tests/Drawing/LineComplexPolygonTests.cs @@ -37,7 +37,7 @@ namespace ImageSharp.Tests.Drawing { image .BackgroundColor(Color.Blue) - .DrawPolygon(Color.HotPink, 5, new ComplexPolygon(simplePath, hole1)) + .Draw(Color.HotPink, 5, simplePath.Clip(hole1)) .Save(output); } @@ -87,7 +87,7 @@ namespace ImageSharp.Tests.Drawing { image .BackgroundColor(Color.Blue) - .DrawPolygon(Color.HotPink, 5, new ComplexPolygon(simplePath, hole1)) + .Draw(Color.HotPink, 5, simplePath.Clip(hole1)) .Save(output); } @@ -131,7 +131,7 @@ namespace ImageSharp.Tests.Drawing new Vector2(37, 85), new Vector2(130, 40), new Vector2(65, 137)); - var clipped = simplePath.Clip(hole1); + using (Image image = new Image(500, 500)) { @@ -139,7 +139,7 @@ namespace ImageSharp.Tests.Drawing { image .BackgroundColor(Color.Blue) - .DrawPolygon(Color.HotPink, 5, clipped) + .Draw(Color.HotPink, 5, simplePath.Clip(hole1)) .Save(output); } @@ -185,7 +185,7 @@ namespace ImageSharp.Tests.Drawing { image .BackgroundColor(Color.Blue) - .DrawPolygon(Pens.Dash(Color.HotPink, 5), new ComplexPolygon(simplePath, hole1)) + .Draw(Pens.Dash(Color.HotPink, 5), simplePath.Clip(hole1)) .Save(output); } } @@ -213,7 +213,7 @@ namespace ImageSharp.Tests.Drawing { image .BackgroundColor(Color.Blue) - .DrawPolygon(color, 5, new ComplexPolygon(simplePath, hole1)) + .Draw(color, 5, simplePath.Clip(hole1)) .Save(output); } diff --git a/tests/ImageSharp.Tests/Drawing/PolygonTests.cs b/tests/ImageSharp.Tests/Drawing/PolygonTests.cs index f987440754..a166464c9e 100644 --- a/tests/ImageSharp.Tests/Drawing/PolygonTests.cs +++ b/tests/ImageSharp.Tests/Drawing/PolygonTests.cs @@ -97,7 +97,7 @@ namespace ImageSharp.Tests.Drawing { image .BackgroundColor(Color.Blue) - .DrawPolygon(Color.HotPink, 10, new Rectangle(10, 10, 190, 140)) + .Draw(Color.HotPink, 10, new Rectangle(10, 10, 190, 140)) .Save(output); } diff --git a/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs b/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs index 3e20b3a09a..d5b2067239 100644 --- a/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs +++ b/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs @@ -71,7 +71,7 @@ namespace ImageSharp.Tests.Drawing { Assert.Equal(Color.HotPink, sourcePixels[11, 11]); - Assert.Equal(Color.HotPink, sourcePixels[200, 150]); + Assert.Equal(Color.HotPink, sourcePixels[199, 150]); Assert.Equal(Color.HotPink, sourcePixels[50, 50]); diff --git a/tests/ImageSharp.Tests/project.json b/tests/ImageSharp.Tests/project.json index 9f9c0c7150..2a01aff847 100644 --- a/tests/ImageSharp.Tests/project.json +++ b/tests/ImageSharp.Tests/project.json @@ -21,34 +21,30 @@ }, "dependencies": { "ImageSharp": { - "target": "project", - "version": "1.0.0-*" + "target": "project" }, "xunit": "2.2.0-*", "dotnet-test-xunit": "2.2.0-*", "ImageSharp.Drawing": { - "target": "project", - "version": "1.0.0-*" + "target": "project" + }, + "ImageSharp.Drawing.Paths": { + "target": "project" }, "ImageSharp.Formats.Png": { - "target": "project", - "version": "1.0.0-*" + "target": "project" }, "ImageSharp.Formats.Jpeg": { - "target": "project", - "version": "1.0.0-*" + "target": "project" }, "ImageSharp.Formats.Bmp": { - "target": "project", - "version": "1.0.0-*" + "target": "project" }, "ImageSharp.Formats.Gif": { - "target": "project", - "version": "1.0.0-*" + "target": "project" }, "ImageSharp.Processing": { - "target": "project", - "version": "1.0.0-*" + "target": "project" } }, "frameworks": { From caffde38f4d74b9177711801ea020412e9b20148 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Thu, 2 Feb 2017 10:41:07 +0000 Subject: [PATCH 016/142] use nuget.org feed --- NuGet.config | 1 - src/ImageSharp.Drawing.Paths/project.json | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/NuGet.config b/NuGet.config index 88d71e7bac..b2c967cc97 100644 --- a/NuGet.config +++ b/NuGet.config @@ -1,7 +1,6 @@  - diff --git a/src/ImageSharp.Drawing.Paths/project.json b/src/ImageSharp.Drawing.Paths/project.json index cdc5d2244f..85ad75148b 100644 --- a/src/ImageSharp.Drawing.Paths/project.json +++ b/src/ImageSharp.Drawing.Paths/project.json @@ -44,7 +44,7 @@ "ImageSharp.Drawing": { "target": "project" }, - "SixLabors.Shapes": "0.1.0-ci0050", + "SixLabors.Shapes": "0.1.0-alpha0001", "StyleCop.Analyzers": { "version": "1.0.0", "type": "build" From 46a4411af6b161c5c19bf2e2a3e781cff1ebde46 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Thu, 2 Feb 2017 11:10:51 +0000 Subject: [PATCH 017/142] update readme for new package --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9d947f227e..96d14c8be3 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,9 @@ Packages include: Contains methods like Resize, Crop, Skew, Rotate - Anything that alters the dimensions of the image. Contains methods like Gaussian Blur, Pixelate, Edge Detection - Anything that maintains the original image dimensions. - **ImageSharp.Drawing** - Brushes and various drawing algorithms. + Brushes and various drawing algorithms, including drawing Images + - **ImageSharp.Drawing.Paths** + various vector drawing methods for drawing paths, polygons etc. ### Manual build From 13f643cb679eef22964adec57c57cd5c48a6d110 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Thu, 2 Feb 2017 13:22:24 +0000 Subject: [PATCH 018/142] update to alpha2 to fix beziers --- src/ImageSharp.Drawing.Paths/project.json | 2 +- tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/ImageSharp.Drawing.Paths/project.json b/src/ImageSharp.Drawing.Paths/project.json index 85ad75148b..a186f362c7 100644 --- a/src/ImageSharp.Drawing.Paths/project.json +++ b/src/ImageSharp.Drawing.Paths/project.json @@ -44,7 +44,7 @@ "ImageSharp.Drawing": { "target": "project" }, - "SixLabors.Shapes": "0.1.0-alpha0001", + "SixLabors.Shapes": "0.1.0-alpha0002", "StyleCop.Analyzers": { "version": "1.0.0", "type": "build" diff --git a/tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs b/tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs index 82e4ac33e2..f63d90aa6e 100644 --- a/tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs +++ b/tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs @@ -5,8 +5,6 @@ namespace ImageSharp.Tests.Drawing { - using ImageSharp.Drawing.Shapes; - using System.IO; using System.Numerics; From bf522fdfcbf03ee67273c93e3597ad4d7a82b532 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Thu, 2 Feb 2017 19:28:19 +0000 Subject: [PATCH 019/142] added unit tests for Paths --- src/ImageSharp.Drawing.Paths/DrawPolygon.cs | 16 ++ .../{FillShapes.cs => FillShape.cs} | 2 +- .../RectangleExtensions.cs | 10 +- src/ImageSharp.Drawing.Paths/ShapePath.cs | 19 +- src/ImageSharp.Drawing.Paths/ShapeRegion.cs | 18 +- .../Processors/DrawPathProcessor.cs | 40 +++- .../Processors/FillRegionProcessor.cs | 49 +++-- src/ImageSharp/Image/ImageBase{TColor}.cs | 11 + .../Image/ImageProcessingExtensions.cs | 8 +- src/ImageSharp/Image/Image{TColor}.cs | 16 ++ .../Drawing/Paths/DrawBeziersTests.cs | 168 ++++++++++++++++ .../Drawing/Paths/DrawLinesTests.cs | 168 ++++++++++++++++ .../Drawing/Paths/DrawPath.cs | 156 +++++++++++++++ .../Drawing/Paths/DrawPolygon.cs | 168 ++++++++++++++++ .../Drawing/Paths/DrawRectangle.cs | 187 +++++++++++++++++ .../Drawing/Paths/DrawShape.cs | 156 +++++++++++++++ .../Drawing/Paths/Extensions.cs | 49 +++++ .../Drawing/Paths/FillPath.cs | 112 +++++++++++ .../Drawing/Paths/FillPolygon.cs | 111 +++++++++++ .../Drawing/Paths/FillRectangle.cs | 119 +++++++++++ .../Drawing/Paths/FillShape.cs | 108 ++++++++++ .../Drawing/Paths/ProcessorWatchingImage.cs | 38 ++++ .../Drawing/Paths/ShapePathTests.cs | 188 ++++++++++++++++++ .../Drawing/Paths/ShapeRegionTests.cs | 170 ++++++++++++++++ tests/ImageSharp.Tests/project.json | 4 +- 25 files changed, 2036 insertions(+), 55 deletions(-) rename src/ImageSharp.Drawing.Paths/{FillShapes.cs => FillShape.cs} (98%) create mode 100644 tests/ImageSharp.Tests/Drawing/Paths/DrawBeziersTests.cs create mode 100644 tests/ImageSharp.Tests/Drawing/Paths/DrawLinesTests.cs create mode 100644 tests/ImageSharp.Tests/Drawing/Paths/DrawPath.cs create mode 100644 tests/ImageSharp.Tests/Drawing/Paths/DrawPolygon.cs create mode 100644 tests/ImageSharp.Tests/Drawing/Paths/DrawRectangle.cs create mode 100644 tests/ImageSharp.Tests/Drawing/Paths/DrawShape.cs create mode 100644 tests/ImageSharp.Tests/Drawing/Paths/Extensions.cs create mode 100644 tests/ImageSharp.Tests/Drawing/Paths/FillPath.cs create mode 100644 tests/ImageSharp.Tests/Drawing/Paths/FillPolygon.cs create mode 100644 tests/ImageSharp.Tests/Drawing/Paths/FillRectangle.cs create mode 100644 tests/ImageSharp.Tests/Drawing/Paths/FillShape.cs create mode 100644 tests/ImageSharp.Tests/Drawing/Paths/ProcessorWatchingImage.cs create mode 100644 tests/ImageSharp.Tests/Drawing/Paths/ShapePathTests.cs create mode 100644 tests/ImageSharp.Tests/Drawing/Paths/ShapeRegionTests.cs diff --git a/src/ImageSharp.Drawing.Paths/DrawPolygon.cs b/src/ImageSharp.Drawing.Paths/DrawPolygon.cs index 73d8ec0e7f..571b13c1ee 100644 --- a/src/ImageSharp.Drawing.Paths/DrawPolygon.cs +++ b/src/ImageSharp.Drawing.Paths/DrawPolygon.cs @@ -84,6 +84,22 @@ namespace ImageSharp return source.DrawPolygon(new SolidBrush(color), thickness, points, options); } + /// + /// Draws the provided Points as a closed Linear Polygon with the provided Pen. + /// + /// The type of the color. + /// The source. + /// The pen. + /// The points. + /// + /// The Image + /// + public static Image DrawPolygon(this Image source, IPen pen, Vector2[] points) + where TColor : struct, IPackedPixel, IEquatable + { + return source.Draw(pen, new Polygon(new LinearLineSegment(points)), GraphicsOptions.Default); + } + /// /// Draws the provided Points as a closed Linear Polygon with the provided Pen. /// diff --git a/src/ImageSharp.Drawing.Paths/FillShapes.cs b/src/ImageSharp.Drawing.Paths/FillShape.cs similarity index 98% rename from src/ImageSharp.Drawing.Paths/FillShapes.cs rename to src/ImageSharp.Drawing.Paths/FillShape.cs index 9fe473ee1c..6e50f73771 100644 --- a/src/ImageSharp.Drawing.Paths/FillShapes.cs +++ b/src/ImageSharp.Drawing.Paths/FillShape.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // diff --git a/src/ImageSharp.Drawing.Paths/RectangleExtensions.cs b/src/ImageSharp.Drawing.Paths/RectangleExtensions.cs index 05d6bb6ced..8369cc83f6 100644 --- a/src/ImageSharp.Drawing.Paths/RectangleExtensions.cs +++ b/src/ImageSharp.Drawing.Paths/RectangleExtensions.cs @@ -26,11 +26,11 @@ namespace ImageSharp.Drawing.Processors /// A representation of this public static Rectangle Convert(this SixLabors.Shapes.Rectangle source) { - int y = (int)Math.Floor(source.Y); - int width = (int)Math.Ceiling(source.Size.Width); - int x = (int)Math.Floor(source.X); - int height = (int)Math.Ceiling(source.Size.Height); - return new Rectangle(x, y, width, height); + int left = (int)Math.Floor(source.Left); + int right = (int)Math.Ceiling(source.Right); + int top = (int)Math.Floor(source.Top); + int bottom = (int)Math.Ceiling(source.Bottom); + return new Rectangle(left, top, right - left, bottom - top); } } } \ No newline at end of file diff --git a/src/ImageSharp.Drawing.Paths/ShapePath.cs b/src/ImageSharp.Drawing.Paths/ShapePath.cs index 63d5fc9e1b..cd994515ef 100644 --- a/src/ImageSharp.Drawing.Paths/ShapePath.cs +++ b/src/ImageSharp.Drawing.Paths/ShapePath.cs @@ -25,11 +25,6 @@ namespace ImageSharp.Drawing /// private readonly IShape shape; - /// - /// The drawable paths - /// - private readonly ImmutableArray paths; - /// /// Initializes a new instance of the class. /// @@ -58,9 +53,17 @@ namespace ImageSharp.Drawing /// The paths. private ShapePath(ImmutableArray paths) { - this.paths = paths; + this.Paths = paths; } + /// + /// Gets the drawable paths + /// + /// + /// The paths. + /// + public ImmutableArray Paths { get; } + /// /// Gets the maximum number of intersections to could be returned. /// @@ -163,9 +166,9 @@ namespace ImageSharp.Drawing SixLabors.Shapes.PointInfo result = default(SixLabors.Shapes.PointInfo); float distance = float.MaxValue; - for (int i = 0; i < this.paths.Length; i++) + for (int i = 0; i < this.Paths.Length; i++) { - var p = this.paths[i].Distance(point); + var p = this.Paths[i].Distance(point); if (p.DistanceFromPath < distance) { distance = p.DistanceFromPath; diff --git a/src/ImageSharp.Drawing.Paths/ShapeRegion.cs b/src/ImageSharp.Drawing.Paths/ShapeRegion.cs index d1a59a99d9..b43ad26b46 100644 --- a/src/ImageSharp.Drawing.Paths/ShapeRegion.cs +++ b/src/ImageSharp.Drawing.Paths/ShapeRegion.cs @@ -20,11 +20,6 @@ namespace ImageSharp.Drawing /// internal class ShapeRegion : Region { - /// - /// The fillable shape - /// - private readonly IShape shape; - /// /// Initializes a new instance of the class. /// @@ -40,17 +35,22 @@ namespace ImageSharp.Drawing /// The shape. public ShapeRegion(IShape shape) { - this.shape = shape; + this.Shape = shape; this.Bounds = shape.Bounds.Convert(); } + /// + /// Gets the fillable shape + /// + public IShape Shape { get; } + /// /// Gets the maximum number of intersections to could be returned. /// /// /// The maximum intersections. /// - public override int MaxIntersections => this.shape.MaxIntersections; + public override int MaxIntersections => this.Shape.MaxIntersections; /// /// Gets the bounds. @@ -77,7 +77,7 @@ namespace ImageSharp.Drawing Vector2[] innerbuffer = ArrayPool.Shared.Rent(length); try { - int count = this.shape.FindIntersections( + int count = this.Shape.FindIntersections( start, end, innerbuffer, @@ -114,7 +114,7 @@ namespace ImageSharp.Drawing Vector2[] innerbuffer = ArrayPool.Shared.Rent(length); try { - int count = this.shape.FindIntersections( + int count = this.Shape.FindIntersections( start, end, innerbuffer, diff --git a/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs b/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs index 1513443c4e..3fe5570807 100644 --- a/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs @@ -25,10 +25,6 @@ namespace ImageSharp.Drawing.Processors private const float AntialiasFactor = 1f; private const int PaddingFactor = 1; // needs to been the same or greater than AntialiasFactor - private readonly IPen pen; - private readonly Path region; - private readonly GraphicsOptions options; - /// /// Initializes a new instance of the class. /// @@ -37,16 +33,40 @@ namespace ImageSharp.Drawing.Processors /// The options. public DrawPathProcessor(IPen pen, Path region, GraphicsOptions options) { - this.region = region; - this.pen = pen; - this.options = options; + this.Path = region; + this.Pen = pen; + this.Options = options; } + /// + /// Gets the options. + /// + /// + /// The options. + /// + public GraphicsOptions Options { get; } + + /// + /// Gets the pen. + /// + /// + /// The pen. + /// + public IPen Pen { get; } + + /// + /// Gets the path. + /// + /// + /// The path. + /// + public Path Path { get; } + /// protected override void OnApply(ImageBase source, Rectangle sourceRectangle) { using (PixelAccessor sourcePixels = source.Lock()) - using (PenApplicator applicator = this.pen.CreateApplicator(sourcePixels, this.region.Bounds)) + using (PenApplicator applicator = this.Pen.CreateApplicator(sourcePixels, this.Path.Bounds)) { var rect = RectangleF.Ceiling(applicator.RequiredRegion); @@ -89,7 +109,7 @@ namespace ImageSharp.Drawing.Processors { // TODO add find intersections code to skip and scan large regions of this. int offsetX = x - startX; - var info = this.region.GetPointInfo(offsetX, offsetY); + var info = this.Path.GetPointInfo(offsetX, offsetY); var color = applicator.GetColor(offsetX, offsetY, info); @@ -120,7 +140,7 @@ namespace ImageSharp.Drawing.Processors { return 1; } - else if (this.options.Antialias && distance < AntialiasFactor) + else if (this.Options.Antialias && distance < AntialiasFactor) { return 1 - (distance / AntialiasFactor); } diff --git a/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs b/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs index 2f6f8af7ba..6719d365a9 100644 --- a/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs @@ -22,9 +22,6 @@ namespace ImageSharp.Drawing.Processors { private const float AntialiasFactor = 1f; private const int DrawPadding = 1; - private readonly IBrush fillColor; - private readonly Region region; - private readonly GraphicsOptions options; /// /// Initializes a new instance of the class. @@ -34,15 +31,39 @@ namespace ImageSharp.Drawing.Processors /// The options. public FillRegionProcessor(IBrush brush, Region region, GraphicsOptions options) { - this.region = region; - this.fillColor = brush; - this.options = options; + this.Region = region; + this.Brush = brush; + this.Options = options; } + /// + /// Gets the brush. + /// + /// + /// The brush. + /// + public IBrush Brush { get; } + + /// + /// Gets the region. + /// + /// + /// The region. + /// + public Region Region { get; } + + /// + /// Gets the options. + /// + /// + /// The options. + /// + public GraphicsOptions Options { get; } + /// protected override void OnApply(ImageBase source, Rectangle sourceRectangle) { - Rectangle rect = RectangleF.Ceiling(this.region.Bounds); // rounds the points out away from the center + Rectangle rect = RectangleF.Ceiling(this.Region.Bounds); // rounds the points out away from the center int polyStartY = sourceRectangle.Y - DrawPadding; int polyEndY = sourceRectangle.Bottom + DrawPadding; @@ -62,10 +83,10 @@ namespace ImageSharp.Drawing.Processors ArrayPool arrayPool = ArrayPool.Shared; - int maxIntersections = this.region.MaxIntersections; + int maxIntersections = this.Region.MaxIntersections; using (PixelAccessor sourcePixels = source.Lock()) - using (BrushApplicator applicator = this.fillColor.CreateApplicator(sourcePixels, rect)) + using (BrushApplicator applicator = this.Brush.CreateApplicator(sourcePixels, rect)) { Parallel.For( minY, @@ -80,7 +101,7 @@ namespace ImageSharp.Drawing.Processors float right = endX; // foreach line we get all the points where this line crosses the polygon - int pointsFound = this.region.ScanY(y, buffer, maxIntersections, 0); + int pointsFound = this.Region.ScanY(y, buffer, maxIntersections, 0); if (pointsFound == 0) { // nothign on this line skip @@ -156,7 +177,7 @@ namespace ImageSharp.Drawing.Processors float opacity = 1; if (!isInside && !onCorner) { - if (this.options.Antialias) + if (this.Options.Antialias) { float distance = float.MaxValue; if (x == lastPoint || x == nextPoint) @@ -207,7 +228,7 @@ namespace ImageSharp.Drawing.Processors } }); - if (this.options.Antialias) + if (this.Options.Antialias) { // we only need to do the X can for antialiasing purposes Parallel.For( @@ -224,7 +245,7 @@ namespace ImageSharp.Drawing.Processors float right = polyEndY; // foreach line we get all the points where this line crosses the polygon - int pointsFound = this.region.ScanX(x, buffer, maxIntersections, 0); + int pointsFound = this.Region.ScanX(x, buffer, maxIntersections, 0); if (pointsFound == 0) { // nothign on this line skip @@ -314,7 +335,7 @@ namespace ImageSharp.Drawing.Processors float opacity = 1; if (!isInside && !onCorner) { - if (this.options.Antialias) + if (this.Options.Antialias) { float distance = float.MaxValue; if (y == lastPoint || y == nextPoint) diff --git a/src/ImageSharp/Image/ImageBase{TColor}.cs b/src/ImageSharp/Image/ImageBase{TColor}.cs index 1b3abd360f..b179d1158e 100644 --- a/src/ImageSharp/Image/ImageBase{TColor}.cs +++ b/src/ImageSharp/Image/ImageBase{TColor}.cs @@ -7,6 +7,7 @@ namespace ImageSharp { using System; using System.Diagnostics; + using Processing; /// /// The base class of all images. Encapsulates the basic properties and methods required to manipulate @@ -120,6 +121,16 @@ namespace ImageSharp /// public Configuration Configuration { get; private set; } + /// + /// Applies the processor. + /// + /// The processor. + /// The rectangle. + public virtual void ApplyProcessor(IImageProcessor processor, Rectangle rectangle) + { + processor.Apply(this, rectangle); + } + /// public void Dispose() { diff --git a/src/ImageSharp/Image/ImageProcessingExtensions.cs b/src/ImageSharp/Image/ImageProcessingExtensions.cs index d9073ad16f..db07afe2ab 100644 --- a/src/ImageSharp/Image/ImageProcessingExtensions.cs +++ b/src/ImageSharp/Image/ImageProcessingExtensions.cs @@ -41,13 +41,7 @@ namespace ImageSharp public static Image Apply(this Image source, Rectangle sourceRectangle, IImageProcessor processor) where TColor : struct, IPackedPixel, IEquatable { - processor.Apply(source, sourceRectangle); - - foreach (ImageFrame sourceFrame in source.Frames) - { - processor.Apply(sourceFrame, sourceRectangle); - } - + source.ApplyProcessor(processor, sourceRectangle); return source; } } diff --git a/src/ImageSharp/Image/Image{TColor}.cs b/src/ImageSharp/Image/Image{TColor}.cs index 5c83ef9bbd..84bae39cca 100644 --- a/src/ImageSharp/Image/Image{TColor}.cs +++ b/src/ImageSharp/Image/Image{TColor}.cs @@ -16,6 +16,7 @@ namespace ImageSharp using System.Threading.Tasks; using Formats; + using Processing; /// /// Encapsulates an image, which consists of the pixel data for a graphics image and its attributes. @@ -219,6 +220,21 @@ namespace ImageSharp /// public ExifProfile ExifProfile { get; set; } + /// + /// Applies the processor to the image. + /// + /// The processor to apply to the image. + /// The structure that specifies the portion of the image object to draw. + public override void ApplyProcessor(IImageProcessor processor, Rectangle rectangle) + { + // we want to put this on on here as it gives us a really go place to test/verify processor settings + base.ApplyProcessor(processor, rectangle); + foreach (ImageFrame sourceFrame in this.Frames) + { + sourceFrame.ApplyProcessor(processor, rectangle); + } + } + /// /// Saves the image to the given stream using the currently loaded image format. /// diff --git a/tests/ImageSharp.Tests/Drawing/Paths/DrawBeziersTests.cs b/tests/ImageSharp.Tests/Drawing/Paths/DrawBeziersTests.cs new file mode 100644 index 0000000000..967cd1970c --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/Paths/DrawBeziersTests.cs @@ -0,0 +1,168 @@ + +namespace ImageSharp.Tests.Drawing.Paths +{ + using System; + using System.IO; + using ImageSharp; + using ImageSharp.Drawing.Brushes; + using Processing; + using System.Collections.Generic; + using Xunit; + using ImageSharp.Drawing; + using System.Numerics; + using SixLabors.Shapes; + using ImageSharp.Drawing.Processors; + using ImageSharp.Drawing.Pens; + + public class DrawBeziersTests : IDisposable + { + float thickness = 7.2f; + GraphicsOptions noneDefault = new GraphicsOptions(); + Color color = Color.HotPink; + SolidBrush brush = Brushes.Solid(Color.HotPink); + Pen pen = new Pen(Color.Firebrick, 99.9f); + Vector2[] points = new Vector2[] { + new Vector2(10,10), + new Vector2(20,10), + new Vector2(20,10), + new Vector2(30,10), + }; + private ProcessorWatchingImage img; + + public DrawBeziersTests() + { + this.img = new Paths.ProcessorWatchingImage(10, 10); + } + + public void Dispose() + { + img.Dispose(); + } + + [Fact] + public void Brush_Thickness_points() + { + img.DrawBeziers(brush, thickness, points); + + Assert.NotEmpty(img.ProcessorApplications); + var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(GraphicsOptions.Default, processor.Options); + + var path = Assert.IsType(processor.Path); + Assert.NotEmpty(path.Paths); + + var vector = Assert.IsType(path.Paths[0]); + var segment = Assert.IsType(vector.LineSegments[0]); + + var pen = Assert.IsType>(processor.Pen); + Assert.Equal(brush, pen.Brush); + Assert.Equal(thickness, pen.Width); + } + + [Fact] + public void Brush_Thickness_points_options() + { + img.DrawBeziers(brush, thickness, points, noneDefault); + + Assert.NotEmpty(img.ProcessorApplications); + var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(noneDefault, processor.Options); + + var path = Assert.IsType(processor.Path); + Assert.NotEmpty(path.Paths); + + var vector = Assert.IsType(path.Paths[0]); + var segment = Assert.IsType(vector.LineSegments[0]); + + var pen = Assert.IsType>(processor.Pen); + Assert.Equal(brush, pen.Brush); + Assert.Equal(thickness, pen.Width); + } + + [Fact] + public void color_Thickness_points() + { + img.DrawBeziers(color, thickness, points); + + Assert.NotEmpty(img.ProcessorApplications); + var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(GraphicsOptions.Default, processor.Options); + + var path = Assert.IsType(processor.Path); + Assert.NotEmpty(path.Paths); + + var vector = Assert.IsType(path.Paths[0]); + var segment = Assert.IsType(vector.LineSegments[0]); + + var pen = Assert.IsType>(processor.Pen); + Assert.Equal(thickness, pen.Width); + + var brush = Assert.IsType>(pen.Brush); + Assert.Equal(color, brush.Color); + } + + [Fact] + public void color_Thickness_points_options() + { + img.DrawBeziers(color, thickness, points, noneDefault); + + Assert.NotEmpty(img.ProcessorApplications); + var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(noneDefault, processor.Options); + + var path = Assert.IsType(processor.Path); + Assert.NotEmpty(path.Paths); + + var vector = Assert.IsType(path.Paths[0]); + var segment = Assert.IsType(vector.LineSegments[0]); + + var pen = Assert.IsType>(processor.Pen); + Assert.Equal(thickness, pen.Width); + + var brush = Assert.IsType>(pen.Brush); + Assert.Equal(color, brush.Color); + } + + [Fact] + public void pen_points() + { + img.DrawBeziers(pen, points); + + Assert.NotEmpty(img.ProcessorApplications); + var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(GraphicsOptions.Default, processor.Options); + + var path = Assert.IsType(processor.Path); + Assert.NotEmpty(path.Paths); + + var vector = Assert.IsType(path.Paths[0]); + var segment = Assert.IsType(vector.LineSegments[0]); + + Assert.Equal(pen, processor.Pen); + } + + [Fact] + public void pen_points_options() + { + img.DrawBeziers(pen, points, noneDefault); + + Assert.NotEmpty(img.ProcessorApplications); + var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(noneDefault, processor.Options); + + var path = Assert.IsType(processor.Path); + Assert.NotEmpty(path.Paths); + + var vector = Assert.IsType(path.Paths[0]); + var segment = Assert.IsType(vector.LineSegments[0]); + + Assert.Equal(pen, processor.Pen); + } + } +} diff --git a/tests/ImageSharp.Tests/Drawing/Paths/DrawLinesTests.cs b/tests/ImageSharp.Tests/Drawing/Paths/DrawLinesTests.cs new file mode 100644 index 0000000000..3b203cdd88 --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/Paths/DrawLinesTests.cs @@ -0,0 +1,168 @@ + +namespace ImageSharp.Tests.Drawing.Paths +{ + using System; + using System.IO; + using ImageSharp; + using ImageSharp.Drawing.Brushes; + using Processing; + using System.Collections.Generic; + using Xunit; + using ImageSharp.Drawing; + using System.Numerics; + using SixLabors.Shapes; + using ImageSharp.Drawing.Processors; + using ImageSharp.Drawing.Pens; + + public class DrawLinesTests : IDisposable + { + float thickness = 7.2f; + GraphicsOptions noneDefault = new GraphicsOptions(); + Color color = Color.HotPink; + SolidBrush brush = Brushes.Solid(Color.HotPink); + Pen pen = new Pen(Color.Gray, 99.9f); + Vector2[] points = new Vector2[] { + new Vector2(10,10), + new Vector2(20,10), + new Vector2(20,10), + new Vector2(30,10), + }; + private ProcessorWatchingImage img; + + public DrawLinesTests() + { + this.img = new Paths.ProcessorWatchingImage(10, 10); + } + + public void Dispose() + { + img.Dispose(); + } + + [Fact] + public void Brush_Thickness_points() + { + img.DrawLines(brush, thickness, points); + + Assert.NotEmpty(img.ProcessorApplications); + var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(GraphicsOptions.Default, processor.Options); + + var path = Assert.IsType(processor.Path); + Assert.NotEmpty(path.Paths); + + var vector = Assert.IsType(path.Paths[0]); + var segment = Assert.IsType(vector.LineSegments[0]); + + var pen = Assert.IsType>(processor.Pen); + Assert.Equal(brush, pen.Brush); + Assert.Equal(thickness, pen.Width); + } + + [Fact] + public void Brush_Thickness_points_options() + { + img.DrawLines(brush, thickness, points, noneDefault); + + Assert.NotEmpty(img.ProcessorApplications); + var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(noneDefault, processor.Options); + + var path = Assert.IsType(processor.Path); + Assert.NotEmpty(path.Paths); + + var vector = Assert.IsType(path.Paths[0]); + var segment = Assert.IsType(vector.LineSegments[0]); + + var pen = Assert.IsType>(processor.Pen); + Assert.Equal(brush, pen.Brush); + Assert.Equal(thickness, pen.Width); + } + + [Fact] + public void color_Thickness_points() + { + img.DrawLines(color, thickness, points); + + Assert.NotEmpty(img.ProcessorApplications); + var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(GraphicsOptions.Default, processor.Options); + + var path = Assert.IsType(processor.Path); + Assert.NotEmpty(path.Paths); + + var vector = Assert.IsType(path.Paths[0]); + var segment = Assert.IsType(vector.LineSegments[0]); + + var pen = Assert.IsType>(processor.Pen); + Assert.Equal(thickness, pen.Width); + + var brush = Assert.IsType>(pen.Brush); + Assert.Equal(color, brush.Color); + } + + [Fact] + public void color_Thickness_points_options() + { + img.DrawLines(color, thickness, points, noneDefault); + + Assert.NotEmpty(img.ProcessorApplications); + var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(noneDefault, processor.Options); + + var path = Assert.IsType(processor.Path); + Assert.NotEmpty(path.Paths); + + var vector = Assert.IsType(path.Paths[0]); + var segment = Assert.IsType(vector.LineSegments[0]); + + var pen = Assert.IsType>(processor.Pen); + Assert.Equal(thickness, pen.Width); + + var brush = Assert.IsType>(pen.Brush); + Assert.Equal(color, brush.Color); + } + + [Fact] + public void pen_points() + { + img.DrawLines(pen, points); + + Assert.NotEmpty(img.ProcessorApplications); + var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(GraphicsOptions.Default, processor.Options); + + var path = Assert.IsType(processor.Path); + Assert.NotEmpty(path.Paths); + + var vector = Assert.IsType(path.Paths[0]); + var segment = Assert.IsType(vector.LineSegments[0]); + + Assert.Equal(pen, processor.Pen); + } + + [Fact] + public void pen_points_options() + { + img.DrawLines(pen, points, noneDefault); + + Assert.NotEmpty(img.ProcessorApplications); + var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(noneDefault, processor.Options); + + var path = Assert.IsType(processor.Path); + Assert.NotEmpty(path.Paths); + + var vector = Assert.IsType(path.Paths[0]); + var segment = Assert.IsType(vector.LineSegments[0]); + + Assert.Equal(pen, processor.Pen); + } + } +} diff --git a/tests/ImageSharp.Tests/Drawing/Paths/DrawPath.cs b/tests/ImageSharp.Tests/Drawing/Paths/DrawPath.cs new file mode 100644 index 0000000000..8ce7dcba26 --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/Paths/DrawPath.cs @@ -0,0 +1,156 @@ + +namespace ImageSharp.Tests.Drawing.Paths +{ + using System; + using System.IO; + using ImageSharp; + using ImageSharp.Drawing.Brushes; + using Processing; + using System.Collections.Generic; + using Xunit; + using ImageSharp.Drawing; + using System.Numerics; + using SixLabors.Shapes; + using ImageSharp.Drawing.Processors; + using ImageSharp.Drawing.Pens; + + public class DrawPath : IDisposable + { + float thickness = 7.2f; + GraphicsOptions noneDefault = new GraphicsOptions(); + Color color = Color.HotPink; + SolidBrush brush = Brushes.Solid(Color.HotPink); + Pen pen = new Pen(Color.Gray, 99.9f); + IPath path = new SixLabors.Shapes.Path(new LinearLineSegment(new Vector2[] { + new Vector2(10,10), + new Vector2(20,10), + new Vector2(20,10), + new Vector2(30,10), + })); + private ProcessorWatchingImage img; + + public DrawPath() + { + this.img = new Paths.ProcessorWatchingImage(10, 10); + } + + public void Dispose() + { + img.Dispose(); + } + + [Fact] + public void Brush_Thickness_path() + { + img.Draw(brush, thickness, path); + + Assert.NotEmpty(img.ProcessorApplications); + var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(GraphicsOptions.Default, processor.Options); + + var shapepath = Assert.IsType(processor.Path); + Assert.NotEmpty(shapepath.Paths); + Assert.Equal(path, shapepath.Paths[0]); + + var pen = Assert.IsType>(processor.Pen); + Assert.Equal(brush, pen.Brush); + Assert.Equal(thickness, pen.Width); + } + + [Fact] + public void Brush_Thickness_path_options() + { + img.Draw(brush, thickness, path, noneDefault); + + Assert.NotEmpty(img.ProcessorApplications); + var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(noneDefault, processor.Options); + + var shapepath = Assert.IsType(processor.Path); + Assert.NotEmpty(shapepath.Paths); + Assert.Equal(path, shapepath.Paths[0]); + + var pen = Assert.IsType>(processor.Pen); + Assert.Equal(brush, pen.Brush); + Assert.Equal(thickness, pen.Width); + } + + [Fact] + public void color_Thickness_path() + { + img.Draw(color, thickness, path); + + Assert.NotEmpty(img.ProcessorApplications); + var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(GraphicsOptions.Default, processor.Options); + + var shapepath = Assert.IsType(processor.Path); + Assert.NotEmpty(shapepath.Paths); + Assert.Equal(path, shapepath.Paths[0]); + + var pen = Assert.IsType>(processor.Pen); + Assert.Equal(thickness, pen.Width); + + var brush = Assert.IsType>(pen.Brush); + Assert.Equal(color, brush.Color); + } + + [Fact] + public void color_Thickness_path_options() + { + img.Draw(color, thickness, path, noneDefault); + + Assert.NotEmpty(img.ProcessorApplications); + var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(noneDefault, processor.Options); + + var shapepath = Assert.IsType(processor.Path); + Assert.NotEmpty(shapepath.Paths); + Assert.Equal(path, shapepath.Paths[0]); + + var pen = Assert.IsType>(processor.Pen); + Assert.Equal(thickness, pen.Width); + + var brush = Assert.IsType>(pen.Brush); + Assert.Equal(color, brush.Color); + } + + [Fact] + public void pen_path() + { + img.Draw(pen, path); + + Assert.NotEmpty(img.ProcessorApplications); + var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(GraphicsOptions.Default, processor.Options); + + var shapepath = Assert.IsType(processor.Path); + Assert.NotEmpty(shapepath.Paths); + Assert.Equal(path, shapepath.Paths[0]); + + Assert.Equal(pen, processor.Pen); + } + + [Fact] + public void pen_path_options() + { + img.Draw(pen, path, noneDefault); + + Assert.NotEmpty(img.ProcessorApplications); + var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(noneDefault, processor.Options); + + var shapepath = Assert.IsType(processor.Path); + Assert.NotEmpty(shapepath.Paths); + Assert.Equal(path, shapepath.Paths[0]); + + Assert.Equal(pen, processor.Pen); + } + } +} diff --git a/tests/ImageSharp.Tests/Drawing/Paths/DrawPolygon.cs b/tests/ImageSharp.Tests/Drawing/Paths/DrawPolygon.cs new file mode 100644 index 0000000000..9a931470dd --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/Paths/DrawPolygon.cs @@ -0,0 +1,168 @@ + +namespace ImageSharp.Tests.Drawing.Paths +{ + using System; + using System.IO; + using ImageSharp; + using ImageSharp.Drawing.Brushes; + using Processing; + using System.Collections.Generic; + using Xunit; + using ImageSharp.Drawing; + using System.Numerics; + using SixLabors.Shapes; + using ImageSharp.Drawing.Processors; + using ImageSharp.Drawing.Pens; + + public class DrawPolygon : IDisposable + { + float thickness = 7.2f; + GraphicsOptions noneDefault = new GraphicsOptions(); + Color color = Color.HotPink; + SolidBrush brush = Brushes.Solid(Color.HotPink); + Pen pen = new Pen(Color.Gray, 99.9f); + Vector2[] points = new Vector2[] { + new Vector2(10,10), + new Vector2(20,10), + new Vector2(20,10), + new Vector2(30,10), + }; + private ProcessorWatchingImage img; + + public DrawPolygon() + { + this.img = new Paths.ProcessorWatchingImage(10, 10); + } + + public void Dispose() + { + img.Dispose(); + } + + [Fact] + public void Brush_Thickness_points() + { + img.DrawPolygon(brush, thickness, points); + + Assert.NotEmpty(img.ProcessorApplications); + var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(GraphicsOptions.Default, processor.Options); + + var path = Assert.IsType(processor.Path); + Assert.NotEmpty(path.Paths); + + var vector = Assert.IsType(path.Paths[0].AsShape()); + var segment = Assert.IsType(vector.LineSegments[0]); + + var pen = Assert.IsType>(processor.Pen); + Assert.Equal(brush, pen.Brush); + Assert.Equal(thickness, pen.Width); + } + + [Fact] + public void Brush_Thickness_points_options() + { + img.DrawPolygon(brush, thickness, points, noneDefault); + + Assert.NotEmpty(img.ProcessorApplications); + var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(noneDefault, processor.Options); + + var path = Assert.IsType(processor.Path); + Assert.NotEmpty(path.Paths); + + var vector = Assert.IsType(path.Paths[0].AsShape()); + var segment = Assert.IsType(vector.LineSegments[0]); + + var pen = Assert.IsType>(processor.Pen); + Assert.Equal(brush, pen.Brush); + Assert.Equal(thickness, pen.Width); + } + + [Fact] + public void color_Thickness_points() + { + img.DrawPolygon(color, thickness, points); + + Assert.NotEmpty(img.ProcessorApplications); + var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(GraphicsOptions.Default, processor.Options); + + var path = Assert.IsType(processor.Path); + Assert.NotEmpty(path.Paths); + + var vector = Assert.IsType(path.Paths[0].AsShape()); + var segment = Assert.IsType(vector.LineSegments[0]); + + var pen = Assert.IsType>(processor.Pen); + Assert.Equal(thickness, pen.Width); + + var brush = Assert.IsType>(pen.Brush); + Assert.Equal(color, brush.Color); + } + + [Fact] + public void color_Thickness_points_options() + { + img.DrawPolygon(color, thickness, points, noneDefault); + + Assert.NotEmpty(img.ProcessorApplications); + var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(noneDefault, processor.Options); + + var path = Assert.IsType(processor.Path); + Assert.NotEmpty(path.Paths); + + var vector = Assert.IsType(path.Paths[0].AsShape()); + var segment = Assert.IsType(vector.LineSegments[0]); + + var pen = Assert.IsType>(processor.Pen); + Assert.Equal(thickness, pen.Width); + + var brush = Assert.IsType>(pen.Brush); + Assert.Equal(color, brush.Color); + } + + [Fact] + public void pen_points() + { + img.DrawPolygon(pen, points); + + Assert.NotEmpty(img.ProcessorApplications); + var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(GraphicsOptions.Default, processor.Options); + + var path = Assert.IsType(processor.Path); + Assert.NotEmpty(path.Paths); + + var vector = Assert.IsType(path.Paths[0].AsShape()); + var segment = Assert.IsType(vector.LineSegments[0]); + + Assert.Equal(pen, processor.Pen); + } + + [Fact] + public void pen_points_options() + { + img.DrawPolygon(pen, points, noneDefault); + + Assert.NotEmpty(img.ProcessorApplications); + var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(noneDefault, processor.Options); + + var path = Assert.IsType(processor.Path); + Assert.NotEmpty(path.Paths); + + var vector = Assert.IsType(path.Paths[0].AsShape()); + var segment = Assert.IsType(vector.LineSegments[0]); + + Assert.Equal(pen, processor.Pen); + } + } +} diff --git a/tests/ImageSharp.Tests/Drawing/Paths/DrawRectangle.cs b/tests/ImageSharp.Tests/Drawing/Paths/DrawRectangle.cs new file mode 100644 index 0000000000..f6680bc9c7 --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/Paths/DrawRectangle.cs @@ -0,0 +1,187 @@ + +namespace ImageSharp.Tests.Drawing.Paths +{ + using System; + using System.IO; + using ImageSharp; + using ImageSharp.Drawing.Brushes; + using Processing; + using System.Collections.Generic; + using Xunit; + using ImageSharp.Drawing; + using System.Numerics; + using SixLabors.Shapes; + using ImageSharp.Drawing.Processors; + using ImageSharp.Drawing.Pens; + + public class DrawRectangle : IDisposable + { + float thickness = 7.2f; + GraphicsOptions noneDefault = new GraphicsOptions(); + Color color = Color.HotPink; + SolidBrush brush = Brushes.Solid(Color.HotPink); + Pen pen = new Pen(Color.Gray, 99.9f); + ImageSharp.Rectangle rectangle = new ImageSharp.Rectangle(10, 10, 98, 324); + + private ProcessorWatchingImage img; + + public DrawRectangle() + { + this.img = new Paths.ProcessorWatchingImage(10, 10); + } + + public void Dispose() + { + img.Dispose(); + } + + [Fact] + public void Brush_Thickness_rectangle() + { + img.Draw(brush, thickness, rectangle); + + Assert.NotEmpty(img.ProcessorApplications); + var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(GraphicsOptions.Default, processor.Options); + + var shapepath = Assert.IsType(processor.Path); + Assert.NotEmpty(shapepath.Paths); + var rect = Assert.IsType(shapepath.Paths[0].AsShape()); + + Assert.Equal(rect.Location.X, rectangle.X); + Assert.Equal(rect.Location.Y, rectangle.Y); + Assert.Equal(rect.Size.Width, rectangle.Width); + Assert.Equal(rect.Size.Height, rectangle.Height); + + var pen = Assert.IsType>(processor.Pen); + Assert.Equal(brush, pen.Brush); + Assert.Equal(thickness, pen.Width); + } + + [Fact] + public void Brush_Thickness_rectangle_options() + { + img.Draw(brush, thickness, rectangle, noneDefault); + + Assert.NotEmpty(img.ProcessorApplications); + var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(noneDefault, processor.Options); + + var shapepath = Assert.IsType(processor.Path); + Assert.NotEmpty(shapepath.Paths); + + var rect = Assert.IsType(shapepath.Paths[0].AsShape()); + + Assert.Equal(rect.Location.X, rectangle.X); + Assert.Equal(rect.Location.Y, rectangle.Y); + Assert.Equal(rect.Size.Width, rectangle.Width); + Assert.Equal(rect.Size.Height, rectangle.Height); + + var pen = Assert.IsType>(processor.Pen); + Assert.Equal(brush, pen.Brush); + Assert.Equal(thickness, pen.Width); + } + + [Fact] + public void color_Thickness_rectangle() + { + img.Draw(color, thickness, rectangle); + + Assert.NotEmpty(img.ProcessorApplications); + var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(GraphicsOptions.Default, processor.Options); + + var shapepath = Assert.IsType(processor.Path); + Assert.NotEmpty(shapepath.Paths); + + var rect = Assert.IsType(shapepath.Paths[0].AsShape()); + + Assert.Equal(rect.Location.X, rectangle.X); + Assert.Equal(rect.Location.Y, rectangle.Y); + Assert.Equal(rect.Size.Width, rectangle.Width); + Assert.Equal(rect.Size.Height, rectangle.Height); + + var pen = Assert.IsType>(processor.Pen); + Assert.Equal(thickness, pen.Width); + + var brush = Assert.IsType>(pen.Brush); + Assert.Equal(color, brush.Color); + } + + [Fact] + public void color_Thickness_rectangle_options() + { + img.Draw(color, thickness, rectangle, noneDefault); + + Assert.NotEmpty(img.ProcessorApplications); + var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(noneDefault, processor.Options); + + var shapepath = Assert.IsType(processor.Path); + Assert.NotEmpty(shapepath.Paths); + + var rect = Assert.IsType(shapepath.Paths[0].AsShape()); + + Assert.Equal(rect.Location.X, rectangle.X); + Assert.Equal(rect.Location.Y, rectangle.Y); + Assert.Equal(rect.Size.Width, rectangle.Width); + Assert.Equal(rect.Size.Height, rectangle.Height); + + var pen = Assert.IsType>(processor.Pen); + Assert.Equal(thickness, pen.Width); + + var brush = Assert.IsType>(pen.Brush); + Assert.Equal(color, brush.Color); + } + + [Fact] + public void pen_rectangle() + { + img.Draw(pen, rectangle); + + Assert.NotEmpty(img.ProcessorApplications); + var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(GraphicsOptions.Default, processor.Options); + + var shapepath = Assert.IsType(processor.Path); + Assert.NotEmpty(shapepath.Paths); + + var rect = Assert.IsType(shapepath.Paths[0].AsShape()); + + Assert.Equal(rect.Location.X, rectangle.X); + Assert.Equal(rect.Location.Y, rectangle.Y); + Assert.Equal(rect.Size.Width, rectangle.Width); + Assert.Equal(rect.Size.Height, rectangle.Height); + + Assert.Equal(pen, processor.Pen); + } + + [Fact] + public void pen_rectangle_options() + { + img.Draw(pen, rectangle, noneDefault); + + Assert.NotEmpty(img.ProcessorApplications); + var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(noneDefault, processor.Options); + + var shapepath = Assert.IsType(processor.Path); + Assert.NotEmpty(shapepath.Paths); + + var rect = Assert.IsType(shapepath.Paths[0].AsShape()); + + Assert.Equal(rect.Location.X, rectangle.X); + Assert.Equal(rect.Location.Y, rectangle.Y); + Assert.Equal(rect.Size.Width, rectangle.Width); + Assert.Equal(rect.Size.Height, rectangle.Height); + + Assert.Equal(pen, processor.Pen); + } + } +} diff --git a/tests/ImageSharp.Tests/Drawing/Paths/DrawShape.cs b/tests/ImageSharp.Tests/Drawing/Paths/DrawShape.cs new file mode 100644 index 0000000000..8572099db6 --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/Paths/DrawShape.cs @@ -0,0 +1,156 @@ + +namespace ImageSharp.Tests.Drawing.Paths +{ + using System; + using System.IO; + using ImageSharp; + using ImageSharp.Drawing.Brushes; + using Processing; + using System.Collections.Generic; + using Xunit; + using ImageSharp.Drawing; + using System.Numerics; + using SixLabors.Shapes; + using ImageSharp.Drawing.Processors; + using ImageSharp.Drawing.Pens; + + public class DrawShape: IDisposable + { + float thickness = 7.2f; + GraphicsOptions noneDefault = new GraphicsOptions(); + Color color = Color.HotPink; + SolidBrush brush = Brushes.Solid(Color.HotPink); + Pen pen = new Pen(Color.Gray, 99.9f); + IShape shape = new SixLabors.Shapes.Polygon(new LinearLineSegment(new Vector2[] { + new Vector2(10,10), + new Vector2(20,10), + new Vector2(20,10), + new Vector2(30,10), + })); + private ProcessorWatchingImage img; + + public DrawShape() + { + this.img = new Paths.ProcessorWatchingImage(10, 10); + } + + public void Dispose() + { + img.Dispose(); + } + + [Fact] + public void Brush_Thickness_shape() + { + img.Draw(brush, thickness, shape); + + Assert.NotEmpty(img.ProcessorApplications); + var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(GraphicsOptions.Default, processor.Options); + + var shapepath = Assert.IsType(processor.Path); + Assert.NotEmpty(shapepath.Paths); + Assert.Equal(shape, shapepath.Paths[0].AsShape()); + + var pen = Assert.IsType>(processor.Pen); + Assert.Equal(brush, pen.Brush); + Assert.Equal(thickness, pen.Width); + } + + [Fact] + public void Brush_Thickness_shape_options() + { + img.Draw(brush, thickness, shape, noneDefault); + + Assert.NotEmpty(img.ProcessorApplications); + var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(noneDefault, processor.Options); + + var shapepath = Assert.IsType(processor.Path); + Assert.NotEmpty(shapepath.Paths); + Assert.Equal(shape, shapepath.Paths[0].AsShape()); + + var pen = Assert.IsType>(processor.Pen); + Assert.Equal(brush, pen.Brush); + Assert.Equal(thickness, pen.Width); + } + + [Fact] + public void color_Thickness_shape() + { + img.Draw(color, thickness, shape); + + Assert.NotEmpty(img.ProcessorApplications); + var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(GraphicsOptions.Default, processor.Options); + + var shapepath = Assert.IsType(processor.Path); + Assert.NotEmpty(shapepath.Paths); + Assert.Equal(shape, shapepath.Paths[0].AsShape()); + + var pen = Assert.IsType>(processor.Pen); + Assert.Equal(thickness, pen.Width); + + var brush = Assert.IsType>(pen.Brush); + Assert.Equal(color, brush.Color); + } + + [Fact] + public void color_Thickness_shape_options() + { + img.Draw(color, thickness, shape, noneDefault); + + Assert.NotEmpty(img.ProcessorApplications); + var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(noneDefault, processor.Options); + + var shapepath = Assert.IsType(processor.Path); + Assert.NotEmpty(shapepath.Paths); + Assert.Equal(shape, shapepath.Paths[0].AsShape()); + + var pen = Assert.IsType>(processor.Pen); + Assert.Equal(thickness, pen.Width); + + var brush = Assert.IsType>(pen.Brush); + Assert.Equal(color, brush.Color); + } + + [Fact] + public void pen_shape() + { + img.Draw(pen, shape); + + Assert.NotEmpty(img.ProcessorApplications); + var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(GraphicsOptions.Default, processor.Options); + + var shapepath = Assert.IsType(processor.Path); + Assert.NotEmpty(shapepath.Paths); + Assert.Equal(shape, shapepath.Paths[0].AsShape()); + + Assert.Equal(pen, processor.Pen); + } + + [Fact] + public void pen_path_options() + { + img.Draw(pen, shape, noneDefault); + + Assert.NotEmpty(img.ProcessorApplications); + var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(noneDefault, processor.Options); + + var shapepath = Assert.IsType(processor.Path); + Assert.NotEmpty(shapepath.Paths); + Assert.Equal(shape, shapepath.Paths[0].AsShape()); + + Assert.Equal(pen, processor.Pen); + } + } +} diff --git a/tests/ImageSharp.Tests/Drawing/Paths/Extensions.cs b/tests/ImageSharp.Tests/Drawing/Paths/Extensions.cs new file mode 100644 index 0000000000..37720253db --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/Paths/Extensions.cs @@ -0,0 +1,49 @@ + +namespace ImageSharp.Tests.Drawing.Paths +{ + using System; + using System.IO; + using ImageSharp; + using ImageSharp.Drawing.Brushes; + using Processing; + using System.Collections.Generic; + using Xunit; + using ImageSharp.Drawing; + using System.Numerics; + using SixLabors.Shapes; + using ImageSharp.Drawing.Processors; + using ImageSharp.Drawing.Pens; + + public class Extensions + { + [Fact] + public void ConvertPointInfo() + { + SixLabors.Shapes.PointInfo src = new SixLabors.Shapes.PointInfo + { + ClosestPointOnPath = Vector2.UnitX, + SearchPoint = Vector2.UnitY, + DistanceAlongPath = 99f, + DistanceFromPath = 82f + }; + ImageSharp.Drawing.PointInfo info = src.Convert(); + + Assert.Equal(src.DistanceAlongPath, info.DistanceAlongPath); + Assert.Equal(src.DistanceFromPath, info.DistanceFromPath); + } + + [Theory] + [InlineData(0.5, 0.5, 5, 5, 0,0,6,6)] + [InlineData(1, 1, 5, 5, 1,1,5,5)] + public void ConvertRectangle(float x, float y, float width, float height, int expectedX, int expectedY, int expectedWidth, int expectedHeight) + { + SixLabors.Shapes.Rectangle src = new SixLabors.Shapes.Rectangle(x, y, width, height); + ImageSharp.Rectangle actual = src.Convert(); + + Assert.Equal(expectedX, actual.X); + Assert.Equal(expectedY, actual.Y); + Assert.Equal(expectedWidth, actual.Width); + Assert.Equal(expectedHeight, actual.Height); + } + } +} diff --git a/tests/ImageSharp.Tests/Drawing/Paths/FillPath.cs b/tests/ImageSharp.Tests/Drawing/Paths/FillPath.cs new file mode 100644 index 0000000000..04a2a82abb --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/Paths/FillPath.cs @@ -0,0 +1,112 @@ + +namespace ImageSharp.Tests.Drawing.Paths +{ + using System; + using System.IO; + using ImageSharp; + using ImageSharp.Drawing.Brushes; + using Processing; + using System.Collections.Generic; + using Xunit; + using ImageSharp.Drawing; + using System.Numerics; + using SixLabors.Shapes; + using ImageSharp.Drawing.Processors; + using ImageSharp.Drawing.Pens; + + public class FillPath : IDisposable + { + GraphicsOptions noneDefault = new GraphicsOptions(); + Color color = Color.HotPink; + SolidBrush brush = Brushes.Solid(Color.HotPink); + IPath path = new SixLabors.Shapes.Path(new LinearLineSegment(new Vector2[] { + new Vector2(10,10), + new Vector2(20,10), + new Vector2(20,10), + new Vector2(30,10), + })); + private ProcessorWatchingImage img; + + public FillPath() + { + this.img = new Paths.ProcessorWatchingImage(10, 10); + } + + public void Dispose() + { + img.Dispose(); + } + + [Fact] + public void Brush_path() + { + img.Fill(brush, path); + + Assert.NotEmpty(img.ProcessorApplications); + var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(GraphicsOptions.Default, processor.Options); + + var region = Assert.IsType(processor.Region); + var polygon = Assert.IsType(region.Shape); + var segments = Assert.IsType(polygon.LineSegments[0]); + + Assert.Equal(brush, processor.Brush); + } + + [Fact] + public void Brush_path_options() + { + img.Fill(brush, path, noneDefault); + + Assert.NotEmpty(img.ProcessorApplications); + var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(noneDefault, processor.Options); + + var region = Assert.IsType(processor.Region); + var polygon = Assert.IsType(region.Shape); + var segments = Assert.IsType(polygon.LineSegments[0]); + + Assert.Equal(brush, processor.Brush); + } + + [Fact] + public void color_path() + { + img.Fill(color, path); + + Assert.NotEmpty(img.ProcessorApplications); + var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(GraphicsOptions.Default, processor.Options); + + var region = Assert.IsType(processor.Region); + var polygon = Assert.IsType(region.Shape); + var segments = Assert.IsType(polygon.LineSegments[0]); + + var brush = Assert.IsType>(processor.Brush); + Assert.Equal(color, brush.Color); + } + + [Fact] + public void color_path_options() + { + img.Fill(color, path, noneDefault); + + + Assert.NotEmpty(img.ProcessorApplications); + var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(noneDefault, processor.Options); + + var region = Assert.IsType(processor.Region); + var polygon = Assert.IsType(region.Shape); + var segments = Assert.IsType(polygon.LineSegments[0]); + + var brush = Assert.IsType>(processor.Brush); + Assert.Equal(color, brush.Color); + + } + } +} diff --git a/tests/ImageSharp.Tests/Drawing/Paths/FillPolygon.cs b/tests/ImageSharp.Tests/Drawing/Paths/FillPolygon.cs new file mode 100644 index 0000000000..2c95eb2094 --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/Paths/FillPolygon.cs @@ -0,0 +1,111 @@ + +namespace ImageSharp.Tests.Drawing.Paths +{ + using System; + using System.IO; + using ImageSharp; + using ImageSharp.Drawing.Brushes; + using Processing; + using System.Collections.Generic; + using Xunit; + using ImageSharp.Drawing; + using System.Numerics; + using SixLabors.Shapes; + using ImageSharp.Drawing.Processors; + using ImageSharp.Drawing.Pens; + + public class FillPolygon : IDisposable + { + GraphicsOptions noneDefault = new GraphicsOptions(); + Color color = Color.HotPink; + SolidBrush brush = Brushes.Solid(Color.HotPink); + Vector2[] path = new Vector2[] { + new Vector2(10,10), + new Vector2(20,10), + new Vector2(20,10), + new Vector2(30,10), + }; + private ProcessorWatchingImage img; + + public FillPolygon() + { + this.img = new Paths.ProcessorWatchingImage(10, 10); + } + + public void Dispose() + { + img.Dispose(); + } + + [Fact] + public void Brush_path() + { + img.FillPolygon(brush, path); + + Assert.NotEmpty(img.ProcessorApplications); + var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(GraphicsOptions.Default, processor.Options); + + var region = Assert.IsType(processor.Region); + var polygon = Assert.IsType(region.Shape); + var segemnt = Assert.IsType(polygon.LineSegments[0]); + + Assert.Equal(brush, processor.Brush); + } + + [Fact] + public void Brush_path_options() + { + img.FillPolygon(brush, path, noneDefault); + + Assert.NotEmpty(img.ProcessorApplications); + var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(noneDefault, processor.Options); + + var region = Assert.IsType(processor.Region); + var polygon = Assert.IsType(region.Shape); + var segemnt = Assert.IsType(polygon.LineSegments[0]); + + Assert.Equal(brush, processor.Brush); + } + + [Fact] + public void color_path() + { + img.FillPolygon(color, path); + + Assert.NotEmpty(img.ProcessorApplications); + var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(GraphicsOptions.Default, processor.Options); + + var region = Assert.IsType(processor.Region); + var polygon = Assert.IsType(region.Shape); + var segemnt = Assert.IsType(polygon.LineSegments[0]); + + var brush = Assert.IsType>(processor.Brush); + Assert.Equal(color, brush.Color); + } + + [Fact] + public void color_path_options() + { + img.FillPolygon(color, path, noneDefault); + + + Assert.NotEmpty(img.ProcessorApplications); + var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(noneDefault, processor.Options); + + var region = Assert.IsType(processor.Region); + var polygon = Assert.IsType(region.Shape); + var segemnt = Assert.IsType(polygon.LineSegments[0]); + + var brush = Assert.IsType>(processor.Brush); + Assert.Equal(color, brush.Color); + } + } +} diff --git a/tests/ImageSharp.Tests/Drawing/Paths/FillRectangle.cs b/tests/ImageSharp.Tests/Drawing/Paths/FillRectangle.cs new file mode 100644 index 0000000000..520b107b9a --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/Paths/FillRectangle.cs @@ -0,0 +1,119 @@ + +namespace ImageSharp.Tests.Drawing.Paths +{ + using System; + using System.IO; + using ImageSharp; + using ImageSharp.Drawing.Brushes; + using Processing; + using System.Collections.Generic; + using Xunit; + using ImageSharp.Drawing; + using System.Numerics; + using SixLabors.Shapes; + using ImageSharp.Drawing.Processors; + using ImageSharp.Drawing.Pens; + + public class FillRectangle : IDisposable + { + GraphicsOptions noneDefault = new GraphicsOptions(); + Color color = Color.HotPink; + SolidBrush brush = Brushes.Solid(Color.HotPink); + ImageSharp.Rectangle rectangle = new ImageSharp.Rectangle(10, 10, 77, 76); + + private ProcessorWatchingImage img; + + public FillRectangle() + { + this.img = new Paths.ProcessorWatchingImage(10, 10); + } + + public void Dispose() + { + img.Dispose(); + } + + [Fact] + public void Brush_path() + { + img.Fill(brush, rectangle); + + Assert.NotEmpty(img.ProcessorApplications); + var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(GraphicsOptions.Default, processor.Options); + + var region = Assert.IsType(processor.Region); + var rect = Assert.IsType(region.Shape); + Assert.Equal(rect.Location.X, rectangle.X); + Assert.Equal(rect.Location.Y, rectangle.Y); + Assert.Equal(rect.Size.Width, rectangle.Width); + Assert.Equal(rect.Size.Height, rectangle.Height); + + Assert.Equal(brush, processor.Brush); + } + + [Fact] + public void Brush_path_options() + { + img.Fill(brush, rectangle, noneDefault); + + Assert.NotEmpty(img.ProcessorApplications); + var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(noneDefault, processor.Options); + + var region = Assert.IsType(processor.Region); + var rect = Assert.IsType(region.Shape); + Assert.Equal(rect.Location.X, rectangle.X); + Assert.Equal(rect.Location.Y, rectangle.Y); + Assert.Equal(rect.Size.Width, rectangle.Width); + Assert.Equal(rect.Size.Height, rectangle.Height); + + Assert.Equal(brush, processor.Brush); + } + + [Fact] + public void color_path() + { + img.Fill(color, rectangle); + + Assert.NotEmpty(img.ProcessorApplications); + var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(GraphicsOptions.Default, processor.Options); + + var region = Assert.IsType(processor.Region); + var rect = Assert.IsType(region.Shape); + Assert.Equal(rect.Location.X, rectangle.X); + Assert.Equal(rect.Location.Y, rectangle.Y); + Assert.Equal(rect.Size.Width, rectangle.Width); + Assert.Equal(rect.Size.Height, rectangle.Height); + + var brush = Assert.IsType>(processor.Brush); + Assert.Equal(color, brush.Color); + } + + [Fact] + public void color_path_options() + { + img.Fill(color, rectangle, noneDefault); + + + Assert.NotEmpty(img.ProcessorApplications); + var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(noneDefault, processor.Options); + + var region = Assert.IsType(processor.Region); + var rect = Assert.IsType(region.Shape); + Assert.Equal(rect.Location.X, rectangle.X); + Assert.Equal(rect.Location.Y, rectangle.Y); + Assert.Equal(rect.Size.Width, rectangle.Width); + Assert.Equal(rect.Size.Height, rectangle.Height); + + var brush = Assert.IsType>(processor.Brush); + Assert.Equal(color, brush.Color); + } + } +} diff --git a/tests/ImageSharp.Tests/Drawing/Paths/FillShape.cs b/tests/ImageSharp.Tests/Drawing/Paths/FillShape.cs new file mode 100644 index 0000000000..26bf61b5c2 --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/Paths/FillShape.cs @@ -0,0 +1,108 @@ + +namespace ImageSharp.Tests.Drawing.Paths +{ + using System; + using System.IO; + using ImageSharp; + using ImageSharp.Drawing.Brushes; + using Processing; + using System.Collections.Generic; + using Xunit; + using ImageSharp.Drawing; + using System.Numerics; + using SixLabors.Shapes; + using ImageSharp.Drawing.Processors; + using ImageSharp.Drawing.Pens; + + public class FillShape : IDisposable + { + GraphicsOptions noneDefault = new GraphicsOptions(); + Color color = Color.HotPink; + SolidBrush brush = Brushes.Solid(Color.HotPink); + IShape shape = new SixLabors.Shapes.Polygon(new LinearLineSegment(new Vector2[] { + new Vector2(10,10), + new Vector2(20,10), + new Vector2(20,10), + new Vector2(30,10), + })); + private ProcessorWatchingImage img; + + public FillShape() + { + this.img = new Paths.ProcessorWatchingImage(10, 10); + } + + public void Dispose() + { + img.Dispose(); + } + + [Fact] + public void Brush_shape() + { + img.Fill(brush, shape); + + Assert.NotEmpty(img.ProcessorApplications); + var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(GraphicsOptions.Default, processor.Options); + + var region = Assert.IsType(processor.Region); + Assert.Equal(shape, region.Shape); + + Assert.Equal(brush, processor.Brush); + } + + [Fact] + public void Brush_shape_options() + { + img.Fill(brush, shape, noneDefault); + + Assert.NotEmpty(img.ProcessorApplications); + var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(noneDefault, processor.Options); + + var region = Assert.IsType(processor.Region); + Assert.Equal(shape, region.Shape); + + Assert.Equal(brush, processor.Brush); + } + + [Fact] + public void color_shape() + { + img.Fill(color, shape); + + Assert.NotEmpty(img.ProcessorApplications); + var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(GraphicsOptions.Default, processor.Options); + + var region = Assert.IsType(processor.Region); + Assert.Equal(shape, region.Shape); + + var brush = Assert.IsType>(processor.Brush); + Assert.Equal(color, brush.Color); + } + + [Fact] + public void color_shape_options() + { + img.Fill(color, shape, noneDefault); + + + Assert.NotEmpty(img.ProcessorApplications); + var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + Assert.Equal(noneDefault, processor.Options); + + var region = Assert.IsType(processor.Region); + Assert.Equal(shape, region.Shape); + + var brush = Assert.IsType>(processor.Brush); + Assert.Equal(color, brush.Color); + + } + } +} diff --git a/tests/ImageSharp.Tests/Drawing/Paths/ProcessorWatchingImage.cs b/tests/ImageSharp.Tests/Drawing/Paths/ProcessorWatchingImage.cs new file mode 100644 index 0000000000..ad3d41bc66 --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/Paths/ProcessorWatchingImage.cs @@ -0,0 +1,38 @@ + +namespace ImageSharp.Tests.Drawing.Paths +{ + using System; + using System.IO; + using ImageSharp; + using Processing; + using System.Collections.Generic; + + /// + /// Watches but does not actually run the processors against the image. + /// + /// + public class ProcessorWatchingImage : Image + { + public List ProcessorApplications { get; } = new List(); + + public ProcessorWatchingImage(int width, int height) + : base(width, height, Configuration.CreateDefaultInstance()) + { + } + + public override void ApplyProcessor(IImageProcessor processor, Rectangle rectangle) + { + ProcessorApplications.Add(new ProcessorDetails + { + processor = processor, + rectangle = rectangle + }); + } + + public struct ProcessorDetails + { + public IImageProcessor processor; + public Rectangle rectangle; + } + } +} diff --git a/tests/ImageSharp.Tests/Drawing/Paths/ShapePathTests.cs b/tests/ImageSharp.Tests/Drawing/Paths/ShapePathTests.cs new file mode 100644 index 0000000000..24e921ccb6 --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/Paths/ShapePathTests.cs @@ -0,0 +1,188 @@ + +namespace ImageSharp.Tests.Drawing.Paths +{ + using System; + using System.IO; + using ImageSharp; + using ImageSharp.Drawing.Brushes; + using Processing; + using System.Collections.Generic; + using Xunit; + using ImageSharp.Drawing; + using System.Numerics; + using SixLabors.Shapes; + using ImageSharp.Drawing.Processors; + using ImageSharp.Drawing.Pens; + using Moq; + using System.Collections.Immutable; + + public class ShapePathTests + { + private readonly Mock pathMock1; + private readonly Mock pathMock2; + private readonly Mock shapeMock1; + private readonly SixLabors.Shapes.Rectangle bounds1; + + public ShapePathTests() + { + this.shapeMock1 = new Mock(); + this.pathMock2 = new Mock(); + this.pathMock1 = new Mock(); + + this.bounds1 = new SixLabors.Shapes.Rectangle(10.5f, 10, 10, 10); + pathMock1.Setup(x => x.Bounds).Returns(this.bounds1); + pathMock2.Setup(x => x.Bounds).Returns(this.bounds1); + shapeMock1.Setup(x => x.Bounds).Returns(this.bounds1); + // wire up the 2 mocks to reference eachother + pathMock1.Setup(x => x.AsShape()).Returns(() => shapeMock1.Object); + shapeMock1.Setup(x => x.Paths).Returns(() => ImmutableArray.Create(pathMock1.Object, pathMock2.Object)); + } + + [Fact] + public void ShapePathWithPath_CallsAsShape() + { + new ShapePath(pathMock1.Object); + + pathMock1.Verify(x => x.AsShape()); + } + + [Fact] + public void ShapePathFromPathConvertsBoundsDoesNotProxyToShape() + { + ShapePath region = new ShapePath(pathMock1.Object); + + Assert.Equal(Math.Floor(bounds1.Left), region.Bounds.Left); + Assert.Equal(Math.Ceiling(bounds1.Right), region.Bounds.Right); + + pathMock1.Verify(x => x.Bounds); + } + + [Fact] + public void ShapePathFromPathMaxIntersectionsProxyToShape() + { + ShapePath region = new ShapePath(pathMock1.Object); + + int i = region.MaxIntersections; + shapeMock1.Verify(x => x.MaxIntersections); + } + + [Fact] + public void ShapePathFromPathScanXProxyToShape() + { + int xToScan = 10; + ShapePath region = new ShapePath(pathMock1.Object); + + shapeMock1.Setup(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((s, e, b, c, o) => { + Assert.Equal(xToScan, s.X); + Assert.Equal(xToScan, e.X); + Assert.True(s.Y < bounds1.Top); + Assert.True(e.Y > bounds1.Bottom); + }).Returns(0); + + int i = region.ScanX(xToScan, new float[0], 0, 0); + + shapeMock1.Verify(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + } + + [Fact] + public void ShapePathFromPathScanYProxyToShape() + { + int yToScan = 10; + ShapePath region = new ShapePath(pathMock1.Object); + + shapeMock1.Setup(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((s, e, b, c, o) => { + Assert.Equal(yToScan, s.Y); + Assert.Equal(yToScan, e.Y); + Assert.True(s.X < bounds1.Left); + Assert.True(e.X > bounds1.Right); + }).Returns(0); + + int i = region.ScanY(yToScan, new float[0], 0, 0); + + shapeMock1.Verify(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + } + + + [Fact] + public void ShapePathFromShapeScanXProxyToShape() + { + int xToScan = 10; + ShapePath region = new ShapePath(shapeMock1.Object); + + shapeMock1.Setup(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((s, e, b, c, o) => { + Assert.Equal(xToScan, s.X); + Assert.Equal(xToScan, e.X); + Assert.True(s.Y < bounds1.Top); + Assert.True(e.Y > bounds1.Bottom); + }).Returns(0); + + int i = region.ScanX(xToScan, new float[0], 0, 0); + + shapeMock1.Verify(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + } + + [Fact] + public void ShapePathFromShapeScanYProxyToShape() + { + int yToScan = 10; + ShapePath region = new ShapePath(shapeMock1.Object); + + shapeMock1.Setup(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((s, e, b, c, o) => { + Assert.Equal(yToScan, s.Y); + Assert.Equal(yToScan, e.Y); + Assert.True(s.X < bounds1.Left); + Assert.True(e.X > bounds1.Right); + }).Returns(0); + + int i = region.ScanY(yToScan, new float[0], 0, 0); + + shapeMock1.Verify(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + } + + [Fact] + public void ShapePathFromShapeConvertsBoundsProxyToShape() + { + ShapePath region = new ShapePath(shapeMock1.Object); + + Assert.Equal(Math.Floor(bounds1.Left), region.Bounds.Left); + Assert.Equal(Math.Ceiling(bounds1.Right), region.Bounds.Right); + + shapeMock1.Verify(x => x.Bounds); + } + + [Fact] + public void ShapePathFromShapeMaxIntersectionsProxyToShape() + { + ShapePath region = new ShapePath(shapeMock1.Object); + + int i = region.MaxIntersections; + shapeMock1.Verify(x => x.MaxIntersections); + } + + [Fact] + public void GetPointInfoCallAllPathsForShape() + { + ShapePath region = new ShapePath(shapeMock1.Object); + + ImageSharp.Drawing.PointInfo info = region.GetPointInfo(10, 1); + + pathMock1.Verify(x => x.Distance(new Vector2(10, 1)), Times.Once); + pathMock2.Verify(x => x.Distance(new Vector2(10, 1)), Times.Once); + } + + [Fact] + public void GetPointInfoCallSinglePathForPath() + { + ShapePath region = new ShapePath(pathMock1.Object); + + ImageSharp.Drawing.PointInfo info = region.GetPointInfo(10, 1); + + pathMock1.Verify(x => x.Distance(new Vector2(10, 1)), Times.Once); + pathMock2.Verify(x => x.Distance(new Vector2(10, 1)), Times.Never); + } + } +} diff --git a/tests/ImageSharp.Tests/Drawing/Paths/ShapeRegionTests.cs b/tests/ImageSharp.Tests/Drawing/Paths/ShapeRegionTests.cs new file mode 100644 index 0000000000..e983dce669 --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/Paths/ShapeRegionTests.cs @@ -0,0 +1,170 @@ + +namespace ImageSharp.Tests.Drawing.Paths +{ + using System; + using System.IO; + using ImageSharp; + using ImageSharp.Drawing.Brushes; + using Processing; + using System.Collections.Generic; + using Xunit; + using ImageSharp.Drawing; + using System.Numerics; + using SixLabors.Shapes; + using ImageSharp.Drawing.Processors; + using ImageSharp.Drawing.Pens; + using Moq; + using System.Collections.Immutable; + + public class ShapeRegionTests + { + private readonly Mock pathMock; + private readonly Mock shapeMock; + private readonly SixLabors.Shapes.Rectangle bounds; + + public ShapeRegionTests() + { + this.shapeMock = new Mock(); + this.pathMock = new Mock(); + + this.bounds = new SixLabors.Shapes.Rectangle(10.5f, 10, 10, 10); + shapeMock.Setup(x => x.Bounds).Returns(this.bounds); + // wire up the 2 mocks to reference eachother + pathMock.Setup(x => x.AsShape()).Returns(() => shapeMock.Object); + shapeMock.Setup(x => x.Paths).Returns(() => ImmutableArray.Create(pathMock.Object)); + } + + [Fact] + public void ShapeRegionWithPath_CallsAsShape() + { + new ShapeRegion(pathMock.Object); + + pathMock.Verify(x => x.AsShape()); + } + + [Fact] + public void ShapeRegionWithPath_RetainsShape() + { + ShapeRegion region = new ShapeRegion(pathMock.Object); + + Assert.Equal(shapeMock.Object, region.Shape); + } + + [Fact] + public void ShapeRegionFromPathConvertsBoundsProxyToShape() + { + ShapeRegion region = new ShapeRegion(pathMock.Object); + + Assert.Equal(Math.Floor(bounds.Left), region.Bounds.Left); + Assert.Equal(Math.Ceiling(bounds.Right), region.Bounds.Right); + + shapeMock.Verify(x => x.Bounds); + } + + [Fact] + public void ShapeRegionFromPathMaxIntersectionsProxyToShape() + { + ShapeRegion region = new ShapeRegion(pathMock.Object); + + int i = region.MaxIntersections; + shapeMock.Verify(x => x.MaxIntersections); + } + + [Fact] + public void ShapeRegionFromPathScanXProxyToShape() + { + int xToScan = 10; + ShapeRegion region = new ShapeRegion(pathMock.Object); + + shapeMock.Setup(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((s, e, b, c, o) => { + Assert.Equal(xToScan, s.X); + Assert.Equal(xToScan, e.X); + Assert.True(s.Y < bounds.Top); + Assert.True(e.Y > bounds.Bottom); + }).Returns(0); + + int i = region.ScanX(xToScan, new float[0], 0, 0); + + shapeMock.Verify(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + } + + [Fact] + public void ShapeRegionFromPathScanYProxyToShape() + { + int yToScan = 10; + ShapeRegion region = new ShapeRegion(pathMock.Object); + + shapeMock.Setup(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((s, e, b, c, o) => { + Assert.Equal(yToScan, s.Y); + Assert.Equal(yToScan, e.Y); + Assert.True(s.X < bounds.Left); + Assert.True(e.X > bounds.Right); + }).Returns(0); + + int i = region.ScanY(yToScan, new float[0], 0, 0); + + shapeMock.Verify(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + } + + + [Fact] + public void ShapeRegionFromShapeScanXProxyToShape() + { + int xToScan = 10; + ShapeRegion region = new ShapeRegion(shapeMock.Object); + + shapeMock.Setup(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((s, e, b, c, o) => { + Assert.Equal(xToScan, s.X); + Assert.Equal(xToScan, e.X); + Assert.True(s.Y < bounds.Top); + Assert.True(e.Y > bounds.Bottom); + }).Returns(0); + + int i = region.ScanX(xToScan, new float[0], 0, 0); + + shapeMock.Verify(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + } + + [Fact] + public void ShapeRegionFromShapeScanYProxyToShape() + { + int yToScan = 10; + ShapeRegion region = new ShapeRegion(shapeMock.Object); + + shapeMock.Setup(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((s, e, b, c, o) => { + Assert.Equal(yToScan, s.Y); + Assert.Equal(yToScan, e.Y); + Assert.True(s.X < bounds.Left); + Assert.True(e.X > bounds.Right); + }).Returns(0); + + int i = region.ScanY(yToScan, new float[0], 0, 0); + + shapeMock.Verify(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + } + + [Fact] + public void ShapeRegionFromShapeConvertsBoundsProxyToShape() + { + ShapeRegion region = new ShapeRegion(shapeMock.Object); + + Assert.Equal(Math.Floor(bounds.Left), region.Bounds.Left); + Assert.Equal(Math.Ceiling(bounds.Right), region.Bounds.Right); + + shapeMock.Verify(x => x.Bounds); + } + + [Fact] + public void ShapeRegionFromShapeMaxIntersectionsProxyToShape() + { + ShapeRegion region = new ShapeRegion(shapeMock.Object); + + int i = region.MaxIntersections; + shapeMock.Verify(x => x.MaxIntersections); + } + } +} diff --git a/tests/ImageSharp.Tests/project.json b/tests/ImageSharp.Tests/project.json index 2a01aff847..3761bb3858 100644 --- a/tests/ImageSharp.Tests/project.json +++ b/tests/ImageSharp.Tests/project.json @@ -45,7 +45,9 @@ }, "ImageSharp.Processing": { "target": "project" - } + }, + //alpha supports netstandard + "Moq": "4.6.38-alpha" }, "frameworks": { "netcoreapp1.1": { From 2e22596ca2e2f0350bc0fe036ea0e249c4d87354 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Thu, 2 Feb 2017 19:44:33 +0000 Subject: [PATCH 020/142] revert accident additions --- .travis.yml | 4 ++-- ConsoleApp1/ConsoleApp1.csproj | 8 -------- ConsoleApp1/Program.cs | 9 --------- 3 files changed, 2 insertions(+), 19 deletions(-) delete mode 100644 ConsoleApp1/ConsoleApp1.csproj delete mode 100644 ConsoleApp1/Program.cs diff --git a/.travis.yml b/.travis.yml index ca8b90a06a..172079df24 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,8 +20,8 @@ branches: script: - dotnet restore - - dotnet build -c Release src/*/project.csproj - - dotnet test tests/ImageSharp.Tests/project.csproj -c Release -f "netcoreapp1.1" + - dotnet build -c Release src/*/project.json + - dotnet test tests/ImageSharp.Tests/project.json -c Release -f "netcoreapp1.1" env: global: diff --git a/ConsoleApp1/ConsoleApp1.csproj b/ConsoleApp1/ConsoleApp1.csproj deleted file mode 100644 index 68b6f24237..0000000000 --- a/ConsoleApp1/ConsoleApp1.csproj +++ /dev/null @@ -1,8 +0,0 @@ - - - - Exe - netcoreapp1.0 - - - \ No newline at end of file diff --git a/ConsoleApp1/Program.cs b/ConsoleApp1/Program.cs deleted file mode 100644 index 104ecf0262..0000000000 --- a/ConsoleApp1/Program.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; - -class Program -{ - static void Main(string[] args) - { - Console.WriteLine("Hello World!"); - } -} \ No newline at end of file From eef8263be7f334f22dc344207134daa0acb098c5 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Fri, 3 Feb 2017 09:17:52 +0000 Subject: [PATCH 021/142] fix test names & vars --- .../Drawing/Helpers/BezierPolygon.cs | 71 ------------------ .../Drawing/Helpers/LinearPolygon.cs | 71 ------------------ .../Drawing/LineComplexPolygonTests.cs | 32 ++++----- .../Drawing/Paths/DrawBeziersTests.cs | 72 +++++++++---------- .../Drawing/Paths/DrawLinesTests.cs | 72 +++++++++---------- .../Drawing/Paths/DrawPath.cs | 48 ++++++------- .../Drawing/Paths/DrawPolygon.cs | 72 +++++++++---------- .../Drawing/Paths/DrawRectangle.cs | 60 ++++++++-------- .../Drawing/Paths/DrawShape.cs | 48 ++++++------- .../Drawing/Paths/FillPath.cs | 52 +++++++------- .../Drawing/Paths/FillPolygon.cs | 45 ++++++------ .../Drawing/Paths/FillRectangle.cs | 37 +++++----- .../Drawing/Paths/FillShape.cs | 31 ++++---- .../Drawing/Paths/ProcessorWatchingImage.cs | 4 ++ .../Drawing/Paths/ShapePathTests.cs | 2 +- .../Drawing/Paths/ShapeRegionTests.cs | 4 +- .../Drawing/SolidBezierTests.cs | 4 +- .../Drawing/SolidComplexPolygonTests.cs | 24 +++---- 18 files changed, 304 insertions(+), 445 deletions(-) delete mode 100644 tests/ImageSharp.Tests/Drawing/Helpers/BezierPolygon.cs delete mode 100644 tests/ImageSharp.Tests/Drawing/Helpers/LinearPolygon.cs diff --git a/tests/ImageSharp.Tests/Drawing/Helpers/BezierPolygon.cs b/tests/ImageSharp.Tests/Drawing/Helpers/BezierPolygon.cs deleted file mode 100644 index 070e555016..0000000000 --- a/tests/ImageSharp.Tests/Drawing/Helpers/BezierPolygon.cs +++ /dev/null @@ -1,71 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace SixLabors.Shapes -{ - using System.Collections.Generic; - using System.Collections.Immutable; - using System.Numerics; - - using SixLabors.Shapes; - public class BezierPolygon : IShape - { - private Polygon polygon; - - public BezierPolygon(params Vector2[] points) - { - this.polygon = new Polygon(new BezierLineSegment(points)); - } - - public float Distance(Vector2 point) - { - return this.polygon.Distance(point); - } - - public bool Contains(Vector2 point) - { - return this.polygon.Contains(point); - } - - public int FindIntersections(Vector2 start, Vector2 end, Vector2[] buffer, int count, int offset) - { - return this.polygon.FindIntersections(start, end, buffer, count, offset); - } - - public IEnumerable FindIntersections(Vector2 start, Vector2 end) - { - return this.polygon.FindIntersections(start, end); - } - - public IShape Transform(Matrix3x2 matrix) - { - return ((IShape)this.polygon).Transform(matrix); - } - - public Rectangle Bounds - { - get - { - return this.polygon.Bounds; - } - } - - public ImmutableArray Paths - { - get - { - return this.polygon.Paths; - } - } - - public int MaxIntersections - { - get - { - return this.polygon.MaxIntersections; - } - } - } -} diff --git a/tests/ImageSharp.Tests/Drawing/Helpers/LinearPolygon.cs b/tests/ImageSharp.Tests/Drawing/Helpers/LinearPolygon.cs deleted file mode 100644 index fa94882665..0000000000 --- a/tests/ImageSharp.Tests/Drawing/Helpers/LinearPolygon.cs +++ /dev/null @@ -1,71 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace SixLabors.Shapes -{ - using System.Collections.Generic; - using System.Collections.Immutable; - using System.Numerics; - - using SixLabors.Shapes; - public class LinearPolygon : IShape - { - private Polygon polygon; - - public LinearPolygon(params Vector2[] points) - { - this.polygon = new Polygon(new LinearLineSegment(points)); - } - - public float Distance(Vector2 point) - { - return this.polygon.Distance(point); - } - - public bool Contains(Vector2 point) - { - return this.polygon.Contains(point); - } - - public int FindIntersections(Vector2 start, Vector2 end, Vector2[] buffer, int count, int offset) - { - return this.polygon.FindIntersections(start, end, buffer, count, offset); - } - - public IEnumerable FindIntersections(Vector2 start, Vector2 end) - { - return this.polygon.FindIntersections(start, end); - } - - public IShape Transform(Matrix3x2 matrix) - { - return ((IShape)this.polygon).Transform(matrix); - } - - public Rectangle Bounds - { - get - { - return this.polygon.Bounds; - } - } - - public ImmutableArray Paths - { - get - { - return this.polygon.Paths; - } - } - - public int MaxIntersections - { - get - { - return this.polygon.MaxIntersections; - } - } - } -} diff --git a/tests/ImageSharp.Tests/Drawing/LineComplexPolygonTests.cs b/tests/ImageSharp.Tests/Drawing/LineComplexPolygonTests.cs index 03ec5d0c89..0a4f2c4a06 100644 --- a/tests/ImageSharp.Tests/Drawing/LineComplexPolygonTests.cs +++ b/tests/ImageSharp.Tests/Drawing/LineComplexPolygonTests.cs @@ -71,15 +71,15 @@ namespace ImageSharp.Tests.Drawing public void ImageShouldBeOverlayedByPolygonOutlineNoOverlapping() { string path = this.CreateOutputDirectory("Drawing", "LineComplexPolygon"); - LinearPolygon simplePath = new LinearPolygon( + Polygon simplePath = new Polygon(new LinearLineSegment( new Vector2(10, 10), new Vector2(200, 150), - new Vector2(50, 300)); + new Vector2(50, 300))); - LinearPolygon hole1 = new LinearPolygon( + Polygon hole1 = new Polygon(new LinearLineSegment( new Vector2(207, 25), new Vector2(263, 25), - new Vector2(235, 57)); + new Vector2(235, 57))); using (Image image = new Image(500, 500)) { @@ -122,15 +122,15 @@ namespace ImageSharp.Tests.Drawing public void ImageShouldBeOverlayedByPolygonOutlineOverlapping() { string path = this.CreateOutputDirectory("Drawing", "LineComplexPolygon"); - LinearPolygon simplePath = new LinearPolygon( + Polygon simplePath = new Polygon(new LinearLineSegment( new Vector2(10, 10), new Vector2(200, 150), - new Vector2(50, 300)); + new Vector2(50, 300))); - LinearPolygon hole1 = new LinearPolygon( + Polygon hole1 = new Polygon(new LinearLineSegment( new Vector2(37, 85), new Vector2(130, 40), - new Vector2(65, 137)); + new Vector2(65, 137))); using (Image image = new Image(500, 500)) @@ -169,15 +169,15 @@ namespace ImageSharp.Tests.Drawing public void ImageShouldBeOverlayedByPolygonOutlineDashed() { string path = this.CreateOutputDirectory("Drawing", "LineComplexPolygon"); - LinearPolygon simplePath = new LinearPolygon( + Polygon simplePath = new Polygon(new LinearLineSegment( new Vector2(10, 10), new Vector2(200, 150), - new Vector2(50, 300)); + new Vector2(50, 300))); - LinearPolygon hole1 = new LinearPolygon( + Polygon hole1 = new Polygon(new LinearLineSegment( new Vector2(37, 85), new Vector2(93, 85), - new Vector2(65, 137)); + new Vector2(65, 137))); using (Image image = new Image(500, 500)) { @@ -196,15 +196,15 @@ namespace ImageSharp.Tests.Drawing public void ImageShouldBeOverlayedPolygonOutlineWithOpacity() { string path = this.CreateOutputDirectory("Drawing", "LineComplexPolygon"); - LinearPolygon simplePath = new LinearPolygon( + Polygon simplePath = new Polygon(new LinearLineSegment( new Vector2(10, 10), new Vector2(200, 150), - new Vector2(50, 300)); + new Vector2(50, 300))); - LinearPolygon hole1 = new LinearPolygon( + Polygon hole1 = new Polygon(new LinearLineSegment( new Vector2(37, 85), new Vector2(93, 85), - new Vector2(65, 137)); + new Vector2(65, 137))); Color color = new Color(Color.HotPink.R, Color.HotPink.G, Color.HotPink.B, 150); using (Image image = new Image(500, 500)) diff --git a/tests/ImageSharp.Tests/Drawing/Paths/DrawBeziersTests.cs b/tests/ImageSharp.Tests/Drawing/Paths/DrawBeziersTests.cs index 967cd1970c..0d3b469812 100644 --- a/tests/ImageSharp.Tests/Drawing/Paths/DrawBeziersTests.cs +++ b/tests/ImageSharp.Tests/Drawing/Paths/DrawBeziersTests.cs @@ -40,127 +40,127 @@ namespace ImageSharp.Tests.Drawing.Paths } [Fact] - public void Brush_Thickness_points() + public void CorrectlySetsBrushThicknessAndPoints() { img.DrawBeziers(brush, thickness, points); Assert.NotEmpty(img.ProcessorApplications); - var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); Assert.Equal(GraphicsOptions.Default, processor.Options); - var path = Assert.IsType(processor.Path); + ShapePath path = Assert.IsType(processor.Path); Assert.NotEmpty(path.Paths); - var vector = Assert.IsType(path.Paths[0]); - var segment = Assert.IsType(vector.LineSegments[0]); + SixLabors.Shapes.Path vector = Assert.IsType(path.Paths[0]); + BezierLineSegment segment = Assert.IsType(vector.LineSegments[0]); - var pen = Assert.IsType>(processor.Pen); + Pen pen = Assert.IsType>(processor.Pen); Assert.Equal(brush, pen.Brush); Assert.Equal(thickness, pen.Width); } [Fact] - public void Brush_Thickness_points_options() + public void CorrectlySetsBrushThicknessPointsAndOptions() { img.DrawBeziers(brush, thickness, points, noneDefault); Assert.NotEmpty(img.ProcessorApplications); - var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); Assert.Equal(noneDefault, processor.Options); - var path = Assert.IsType(processor.Path); + ShapePath path = Assert.IsType(processor.Path); Assert.NotEmpty(path.Paths); - var vector = Assert.IsType(path.Paths[0]); - var segment = Assert.IsType(vector.LineSegments[0]); + SixLabors.Shapes.Path vector = Assert.IsType(path.Paths[0]); + BezierLineSegment segment = Assert.IsType(vector.LineSegments[0]); - var pen = Assert.IsType>(processor.Pen); + Pen pen = Assert.IsType>(processor.Pen); Assert.Equal(brush, pen.Brush); Assert.Equal(thickness, pen.Width); } [Fact] - public void color_Thickness_points() + public void CorrectlySetsColorThicknessAndPoints() { img.DrawBeziers(color, thickness, points); Assert.NotEmpty(img.ProcessorApplications); - var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); Assert.Equal(GraphicsOptions.Default, processor.Options); - var path = Assert.IsType(processor.Path); + ShapePath path = Assert.IsType(processor.Path); Assert.NotEmpty(path.Paths); - var vector = Assert.IsType(path.Paths[0]); - var segment = Assert.IsType(vector.LineSegments[0]); + SixLabors.Shapes.Path vector = Assert.IsType(path.Paths[0]); + BezierLineSegment segment = Assert.IsType(vector.LineSegments[0]); - var pen = Assert.IsType>(processor.Pen); + Pen pen = Assert.IsType>(processor.Pen); Assert.Equal(thickness, pen.Width); - var brush = Assert.IsType>(pen.Brush); + SolidBrush brush = Assert.IsType>(pen.Brush); Assert.Equal(color, brush.Color); } [Fact] - public void color_Thickness_points_options() + public void CorrectlySetsColorThicknessPointsAndOptions() { img.DrawBeziers(color, thickness, points, noneDefault); Assert.NotEmpty(img.ProcessorApplications); - var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); Assert.Equal(noneDefault, processor.Options); - var path = Assert.IsType(processor.Path); + ShapePath path = Assert.IsType(processor.Path); Assert.NotEmpty(path.Paths); - var vector = Assert.IsType(path.Paths[0]); - var segment = Assert.IsType(vector.LineSegments[0]); + SixLabors.Shapes.Path vector = Assert.IsType(path.Paths[0]); + BezierLineSegment segment = Assert.IsType(vector.LineSegments[0]); - var pen = Assert.IsType>(processor.Pen); + Pen pen = Assert.IsType>(processor.Pen); Assert.Equal(thickness, pen.Width); - var brush = Assert.IsType>(pen.Brush); + SolidBrush brush = Assert.IsType>(pen.Brush); Assert.Equal(color, brush.Color); } [Fact] - public void pen_points() + public void CorrectlySetsPenAndPoints() { img.DrawBeziers(pen, points); Assert.NotEmpty(img.ProcessorApplications); - var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); Assert.Equal(GraphicsOptions.Default, processor.Options); - var path = Assert.IsType(processor.Path); + ShapePath path = Assert.IsType(processor.Path); Assert.NotEmpty(path.Paths); - var vector = Assert.IsType(path.Paths[0]); - var segment = Assert.IsType(vector.LineSegments[0]); + SixLabors.Shapes.Path vector = Assert.IsType(path.Paths[0]); + BezierLineSegment segment = Assert.IsType(vector.LineSegments[0]); Assert.Equal(pen, processor.Pen); } [Fact] - public void pen_points_options() + public void CorrectlySetsPenPointsAndOptions() { img.DrawBeziers(pen, points, noneDefault); Assert.NotEmpty(img.ProcessorApplications); - var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); Assert.Equal(noneDefault, processor.Options); - var path = Assert.IsType(processor.Path); + ShapePath path = Assert.IsType(processor.Path); Assert.NotEmpty(path.Paths); - var vector = Assert.IsType(path.Paths[0]); - var segment = Assert.IsType(vector.LineSegments[0]); + SixLabors.Shapes.Path vector = Assert.IsType(path.Paths[0]); + BezierLineSegment segment = Assert.IsType(vector.LineSegments[0]); Assert.Equal(pen, processor.Pen); } diff --git a/tests/ImageSharp.Tests/Drawing/Paths/DrawLinesTests.cs b/tests/ImageSharp.Tests/Drawing/Paths/DrawLinesTests.cs index 3b203cdd88..bbed3cae67 100644 --- a/tests/ImageSharp.Tests/Drawing/Paths/DrawLinesTests.cs +++ b/tests/ImageSharp.Tests/Drawing/Paths/DrawLinesTests.cs @@ -40,127 +40,127 @@ namespace ImageSharp.Tests.Drawing.Paths } [Fact] - public void Brush_Thickness_points() + public void CorrectlySetsBrushThicknessAndPoints() { img.DrawLines(brush, thickness, points); Assert.NotEmpty(img.ProcessorApplications); - var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); Assert.Equal(GraphicsOptions.Default, processor.Options); - var path = Assert.IsType(processor.Path); + ShapePath path = Assert.IsType(processor.Path); Assert.NotEmpty(path.Paths); - var vector = Assert.IsType(path.Paths[0]); - var segment = Assert.IsType(vector.LineSegments[0]); + SixLabors.Shapes.Path vector = Assert.IsType(path.Paths[0]); + LinearLineSegment segment = Assert.IsType(vector.LineSegments[0]); - var pen = Assert.IsType>(processor.Pen); + Pen pen = Assert.IsType>(processor.Pen); Assert.Equal(brush, pen.Brush); Assert.Equal(thickness, pen.Width); } [Fact] - public void Brush_Thickness_points_options() + public void CorrectlySetsBrushThicknessPointsAndOptions() { img.DrawLines(brush, thickness, points, noneDefault); Assert.NotEmpty(img.ProcessorApplications); - var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); Assert.Equal(noneDefault, processor.Options); - var path = Assert.IsType(processor.Path); + ShapePath path = Assert.IsType(processor.Path); Assert.NotEmpty(path.Paths); - var vector = Assert.IsType(path.Paths[0]); - var segment = Assert.IsType(vector.LineSegments[0]); + SixLabors.Shapes.Path vector = Assert.IsType(path.Paths[0]); + LinearLineSegment segment = Assert.IsType(vector.LineSegments[0]); - var pen = Assert.IsType>(processor.Pen); + Pen pen = Assert.IsType>(processor.Pen); Assert.Equal(brush, pen.Brush); Assert.Equal(thickness, pen.Width); } [Fact] - public void color_Thickness_points() + public void CorrectlySetsColorThicknessAndPoints() { img.DrawLines(color, thickness, points); Assert.NotEmpty(img.ProcessorApplications); - var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); Assert.Equal(GraphicsOptions.Default, processor.Options); - var path = Assert.IsType(processor.Path); + ShapePath path = Assert.IsType(processor.Path); Assert.NotEmpty(path.Paths); - var vector = Assert.IsType(path.Paths[0]); - var segment = Assert.IsType(vector.LineSegments[0]); + SixLabors.Shapes.Path vector = Assert.IsType(path.Paths[0]); + LinearLineSegment segment = Assert.IsType(vector.LineSegments[0]); - var pen = Assert.IsType>(processor.Pen); + Pen pen = Assert.IsType>(processor.Pen); Assert.Equal(thickness, pen.Width); - var brush = Assert.IsType>(pen.Brush); + SolidBrush brush = Assert.IsType>(pen.Brush); Assert.Equal(color, brush.Color); } [Fact] - public void color_Thickness_points_options() + public void CorrectlySetsColorThicknessPointsAndOptions() { img.DrawLines(color, thickness, points, noneDefault); Assert.NotEmpty(img.ProcessorApplications); - var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); Assert.Equal(noneDefault, processor.Options); - var path = Assert.IsType(processor.Path); + ShapePath path = Assert.IsType(processor.Path); Assert.NotEmpty(path.Paths); - var vector = Assert.IsType(path.Paths[0]); - var segment = Assert.IsType(vector.LineSegments[0]); + SixLabors.Shapes.Path vector = Assert.IsType(path.Paths[0]); + LinearLineSegment segment = Assert.IsType(vector.LineSegments[0]); - var pen = Assert.IsType>(processor.Pen); + Pen pen = Assert.IsType>(processor.Pen); Assert.Equal(thickness, pen.Width); - var brush = Assert.IsType>(pen.Brush); + SolidBrush brush = Assert.IsType>(pen.Brush); Assert.Equal(color, brush.Color); } [Fact] - public void pen_points() + public void CorrectlySetsPenAndPoints() { img.DrawLines(pen, points); Assert.NotEmpty(img.ProcessorApplications); - var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); Assert.Equal(GraphicsOptions.Default, processor.Options); - var path = Assert.IsType(processor.Path); + ShapePath path = Assert.IsType(processor.Path); Assert.NotEmpty(path.Paths); - var vector = Assert.IsType(path.Paths[0]); - var segment = Assert.IsType(vector.LineSegments[0]); + SixLabors.Shapes.Path vector = Assert.IsType(path.Paths[0]); + LinearLineSegment segment = Assert.IsType(vector.LineSegments[0]); Assert.Equal(pen, processor.Pen); } [Fact] - public void pen_points_options() + public void CorrectlySetsPenPointsAndOptions() { img.DrawLines(pen, points, noneDefault); Assert.NotEmpty(img.ProcessorApplications); - var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); Assert.Equal(noneDefault, processor.Options); - var path = Assert.IsType(processor.Path); + ShapePath path = Assert.IsType(processor.Path); Assert.NotEmpty(path.Paths); - var vector = Assert.IsType(path.Paths[0]); - var segment = Assert.IsType(vector.LineSegments[0]); + SixLabors.Shapes.Path vector = Assert.IsType(path.Paths[0]); + LinearLineSegment segment = Assert.IsType(vector.LineSegments[0]); Assert.Equal(pen, processor.Pen); } diff --git a/tests/ImageSharp.Tests/Drawing/Paths/DrawPath.cs b/tests/ImageSharp.Tests/Drawing/Paths/DrawPath.cs index 8ce7dcba26..531546370d 100644 --- a/tests/ImageSharp.Tests/Drawing/Paths/DrawPath.cs +++ b/tests/ImageSharp.Tests/Drawing/Paths/DrawPath.cs @@ -40,96 +40,96 @@ namespace ImageSharp.Tests.Drawing.Paths } [Fact] - public void Brush_Thickness_path() + public void CorrectlySetsBrushThicknessAndPath() { img.Draw(brush, thickness, path); Assert.NotEmpty(img.ProcessorApplications); - var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); Assert.Equal(GraphicsOptions.Default, processor.Options); - var shapepath = Assert.IsType(processor.Path); + ShapePath shapepath = Assert.IsType(processor.Path); Assert.NotEmpty(shapepath.Paths); Assert.Equal(path, shapepath.Paths[0]); - var pen = Assert.IsType>(processor.Pen); + Pen pen = Assert.IsType>(processor.Pen); Assert.Equal(brush, pen.Brush); Assert.Equal(thickness, pen.Width); } [Fact] - public void Brush_Thickness_path_options() + public void CorrectlySetsBrushThicknessPathAndOptions() { img.Draw(brush, thickness, path, noneDefault); Assert.NotEmpty(img.ProcessorApplications); - var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); Assert.Equal(noneDefault, processor.Options); - var shapepath = Assert.IsType(processor.Path); + ShapePath shapepath = Assert.IsType(processor.Path); Assert.NotEmpty(shapepath.Paths); Assert.Equal(path, shapepath.Paths[0]); - var pen = Assert.IsType>(processor.Pen); + Pen pen = Assert.IsType>(processor.Pen); Assert.Equal(brush, pen.Brush); Assert.Equal(thickness, pen.Width); } [Fact] - public void color_Thickness_path() + public void CorrectlySetsColorThicknessAndPath() { img.Draw(color, thickness, path); Assert.NotEmpty(img.ProcessorApplications); - var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); Assert.Equal(GraphicsOptions.Default, processor.Options); - var shapepath = Assert.IsType(processor.Path); + ShapePath shapepath = Assert.IsType(processor.Path); Assert.NotEmpty(shapepath.Paths); Assert.Equal(path, shapepath.Paths[0]); - var pen = Assert.IsType>(processor.Pen); + Pen pen = Assert.IsType>(processor.Pen); Assert.Equal(thickness, pen.Width); - var brush = Assert.IsType>(pen.Brush); + SolidBrush brush = Assert.IsType>(pen.Brush); Assert.Equal(color, brush.Color); } [Fact] - public void color_Thickness_path_options() + public void CorrectlySetsColorThicknessPathAndOptions() { img.Draw(color, thickness, path, noneDefault); Assert.NotEmpty(img.ProcessorApplications); - var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); Assert.Equal(noneDefault, processor.Options); - var shapepath = Assert.IsType(processor.Path); + ShapePath shapepath = Assert.IsType(processor.Path); Assert.NotEmpty(shapepath.Paths); Assert.Equal(path, shapepath.Paths[0]); - var pen = Assert.IsType>(processor.Pen); + Pen pen = Assert.IsType>(processor.Pen); Assert.Equal(thickness, pen.Width); - var brush = Assert.IsType>(pen.Brush); + SolidBrush brush = Assert.IsType>(pen.Brush); Assert.Equal(color, brush.Color); } [Fact] - public void pen_path() + public void CorrectlySetsPenAndPath() { img.Draw(pen, path); Assert.NotEmpty(img.ProcessorApplications); - var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); Assert.Equal(GraphicsOptions.Default, processor.Options); - var shapepath = Assert.IsType(processor.Path); + ShapePath shapepath = Assert.IsType(processor.Path); Assert.NotEmpty(shapepath.Paths); Assert.Equal(path, shapepath.Paths[0]); @@ -137,16 +137,16 @@ namespace ImageSharp.Tests.Drawing.Paths } [Fact] - public void pen_path_options() + public void CorrectlySetsPenPathAndOptions() { img.Draw(pen, path, noneDefault); Assert.NotEmpty(img.ProcessorApplications); - var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); Assert.Equal(noneDefault, processor.Options); - var shapepath = Assert.IsType(processor.Path); + ShapePath shapepath = Assert.IsType(processor.Path); Assert.NotEmpty(shapepath.Paths); Assert.Equal(path, shapepath.Paths[0]); diff --git a/tests/ImageSharp.Tests/Drawing/Paths/DrawPolygon.cs b/tests/ImageSharp.Tests/Drawing/Paths/DrawPolygon.cs index 9a931470dd..399e79dae1 100644 --- a/tests/ImageSharp.Tests/Drawing/Paths/DrawPolygon.cs +++ b/tests/ImageSharp.Tests/Drawing/Paths/DrawPolygon.cs @@ -40,127 +40,127 @@ namespace ImageSharp.Tests.Drawing.Paths } [Fact] - public void Brush_Thickness_points() + public void CorrectlySetsBrushThicknessAndPoints() { img.DrawPolygon(brush, thickness, points); Assert.NotEmpty(img.ProcessorApplications); - var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); Assert.Equal(GraphicsOptions.Default, processor.Options); - var path = Assert.IsType(processor.Path); + ShapePath path = Assert.IsType(processor.Path); Assert.NotEmpty(path.Paths); - var vector = Assert.IsType(path.Paths[0].AsShape()); - var segment = Assert.IsType(vector.LineSegments[0]); + Polygon vector = Assert.IsType(path.Paths[0].AsShape()); + LinearLineSegment segment = Assert.IsType(vector.LineSegments[0]); - var pen = Assert.IsType>(processor.Pen); + Pen pen = Assert.IsType>(processor.Pen); Assert.Equal(brush, pen.Brush); Assert.Equal(thickness, pen.Width); } [Fact] - public void Brush_Thickness_points_options() + public void CorrectlySetsBrushThicknessPointsAndOptions() { img.DrawPolygon(brush, thickness, points, noneDefault); Assert.NotEmpty(img.ProcessorApplications); - var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); Assert.Equal(noneDefault, processor.Options); - var path = Assert.IsType(processor.Path); + ShapePath path = Assert.IsType(processor.Path); Assert.NotEmpty(path.Paths); - var vector = Assert.IsType(path.Paths[0].AsShape()); - var segment = Assert.IsType(vector.LineSegments[0]); + Polygon vector = Assert.IsType(path.Paths[0].AsShape()); + LinearLineSegment segment = Assert.IsType(vector.LineSegments[0]); - var pen = Assert.IsType>(processor.Pen); + Pen pen = Assert.IsType>(processor.Pen); Assert.Equal(brush, pen.Brush); Assert.Equal(thickness, pen.Width); } [Fact] - public void color_Thickness_points() + public void CorrectlySetsColorThicknessAndPoints() { img.DrawPolygon(color, thickness, points); Assert.NotEmpty(img.ProcessorApplications); - var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); Assert.Equal(GraphicsOptions.Default, processor.Options); - var path = Assert.IsType(processor.Path); + ShapePath path = Assert.IsType(processor.Path); Assert.NotEmpty(path.Paths); - var vector = Assert.IsType(path.Paths[0].AsShape()); - var segment = Assert.IsType(vector.LineSegments[0]); + Polygon vector = Assert.IsType(path.Paths[0].AsShape()); + LinearLineSegment segment = Assert.IsType(vector.LineSegments[0]); - var pen = Assert.IsType>(processor.Pen); + Pen pen = Assert.IsType>(processor.Pen); Assert.Equal(thickness, pen.Width); - var brush = Assert.IsType>(pen.Brush); + SolidBrush brush = Assert.IsType>(pen.Brush); Assert.Equal(color, brush.Color); } [Fact] - public void color_Thickness_points_options() + public void CorrectlySetsColorThicknessPointsAndOptions() { img.DrawPolygon(color, thickness, points, noneDefault); Assert.NotEmpty(img.ProcessorApplications); - var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); Assert.Equal(noneDefault, processor.Options); - var path = Assert.IsType(processor.Path); + ShapePath path = Assert.IsType(processor.Path); Assert.NotEmpty(path.Paths); - var vector = Assert.IsType(path.Paths[0].AsShape()); - var segment = Assert.IsType(vector.LineSegments[0]); + Polygon vector = Assert.IsType(path.Paths[0].AsShape()); + LinearLineSegment segment = Assert.IsType(vector.LineSegments[0]); - var pen = Assert.IsType>(processor.Pen); + Pen pen = Assert.IsType>(processor.Pen); Assert.Equal(thickness, pen.Width); - var brush = Assert.IsType>(pen.Brush); + SolidBrush brush = Assert.IsType>(pen.Brush); Assert.Equal(color, brush.Color); } [Fact] - public void pen_points() + public void CorrectlySetsPenAndPoints() { img.DrawPolygon(pen, points); Assert.NotEmpty(img.ProcessorApplications); - var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); Assert.Equal(GraphicsOptions.Default, processor.Options); - var path = Assert.IsType(processor.Path); + ShapePath path = Assert.IsType(processor.Path); Assert.NotEmpty(path.Paths); - var vector = Assert.IsType(path.Paths[0].AsShape()); - var segment = Assert.IsType(vector.LineSegments[0]); + Polygon vector = Assert.IsType(path.Paths[0].AsShape()); + LinearLineSegment segment = Assert.IsType(vector.LineSegments[0]); Assert.Equal(pen, processor.Pen); } [Fact] - public void pen_points_options() + public void CorrectlySetsPenPointsAndOptions() { img.DrawPolygon(pen, points, noneDefault); Assert.NotEmpty(img.ProcessorApplications); - var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); Assert.Equal(noneDefault, processor.Options); - var path = Assert.IsType(processor.Path); + ShapePath path = Assert.IsType(processor.Path); Assert.NotEmpty(path.Paths); - var vector = Assert.IsType(path.Paths[0].AsShape()); - var segment = Assert.IsType(vector.LineSegments[0]); + Polygon vector = Assert.IsType(path.Paths[0].AsShape()); + LinearLineSegment segment = Assert.IsType(vector.LineSegments[0]); Assert.Equal(pen, processor.Pen); } diff --git a/tests/ImageSharp.Tests/Drawing/Paths/DrawRectangle.cs b/tests/ImageSharp.Tests/Drawing/Paths/DrawRectangle.cs index f6680bc9c7..3842063b3e 100644 --- a/tests/ImageSharp.Tests/Drawing/Paths/DrawRectangle.cs +++ b/tests/ImageSharp.Tests/Drawing/Paths/DrawRectangle.cs @@ -36,122 +36,122 @@ namespace ImageSharp.Tests.Drawing.Paths } [Fact] - public void Brush_Thickness_rectangle() + public void CorrectlySetsBrushThicknessAndRectangle() { img.Draw(brush, thickness, rectangle); Assert.NotEmpty(img.ProcessorApplications); - var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); Assert.Equal(GraphicsOptions.Default, processor.Options); - var shapepath = Assert.IsType(processor.Path); + ShapePath shapepath = Assert.IsType(processor.Path); Assert.NotEmpty(shapepath.Paths); - var rect = Assert.IsType(shapepath.Paths[0].AsShape()); + SixLabors.Shapes.Rectangle rect = Assert.IsType(shapepath.Paths[0].AsShape()); Assert.Equal(rect.Location.X, rectangle.X); Assert.Equal(rect.Location.Y, rectangle.Y); Assert.Equal(rect.Size.Width, rectangle.Width); Assert.Equal(rect.Size.Height, rectangle.Height); - var pen = Assert.IsType>(processor.Pen); + Pen pen = Assert.IsType>(processor.Pen); Assert.Equal(brush, pen.Brush); Assert.Equal(thickness, pen.Width); } [Fact] - public void Brush_Thickness_rectangle_options() + public void CorrectlySetsBrushThicknessRectangleAndOptions() { img.Draw(brush, thickness, rectangle, noneDefault); Assert.NotEmpty(img.ProcessorApplications); - var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); Assert.Equal(noneDefault, processor.Options); - var shapepath = Assert.IsType(processor.Path); + ShapePath shapepath = Assert.IsType(processor.Path); Assert.NotEmpty(shapepath.Paths); - var rect = Assert.IsType(shapepath.Paths[0].AsShape()); + SixLabors.Shapes.Rectangle rect = Assert.IsType(shapepath.Paths[0].AsShape()); Assert.Equal(rect.Location.X, rectangle.X); Assert.Equal(rect.Location.Y, rectangle.Y); Assert.Equal(rect.Size.Width, rectangle.Width); Assert.Equal(rect.Size.Height, rectangle.Height); - var pen = Assert.IsType>(processor.Pen); + Pen pen = Assert.IsType>(processor.Pen); Assert.Equal(brush, pen.Brush); Assert.Equal(thickness, pen.Width); } [Fact] - public void color_Thickness_rectangle() + public void CorrectlySetsColorThicknessAndRectangle() { img.Draw(color, thickness, rectangle); Assert.NotEmpty(img.ProcessorApplications); - var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); Assert.Equal(GraphicsOptions.Default, processor.Options); - var shapepath = Assert.IsType(processor.Path); + ShapePath shapepath = Assert.IsType(processor.Path); Assert.NotEmpty(shapepath.Paths); - var rect = Assert.IsType(shapepath.Paths[0].AsShape()); + SixLabors.Shapes.Rectangle rect = Assert.IsType(shapepath.Paths[0].AsShape()); Assert.Equal(rect.Location.X, rectangle.X); Assert.Equal(rect.Location.Y, rectangle.Y); Assert.Equal(rect.Size.Width, rectangle.Width); Assert.Equal(rect.Size.Height, rectangle.Height); - var pen = Assert.IsType>(processor.Pen); + Pen pen = Assert.IsType>(processor.Pen); Assert.Equal(thickness, pen.Width); - var brush = Assert.IsType>(pen.Brush); + SolidBrush brush = Assert.IsType>(pen.Brush); Assert.Equal(color, brush.Color); } [Fact] - public void color_Thickness_rectangle_options() + public void CorrectlySetsColorThicknessRectangleAndOptions() { img.Draw(color, thickness, rectangle, noneDefault); Assert.NotEmpty(img.ProcessorApplications); - var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); Assert.Equal(noneDefault, processor.Options); - var shapepath = Assert.IsType(processor.Path); + ShapePath shapepath = Assert.IsType(processor.Path); Assert.NotEmpty(shapepath.Paths); - var rect = Assert.IsType(shapepath.Paths[0].AsShape()); + SixLabors.Shapes.Rectangle rect = Assert.IsType(shapepath.Paths[0].AsShape()); Assert.Equal(rect.Location.X, rectangle.X); Assert.Equal(rect.Location.Y, rectangle.Y); Assert.Equal(rect.Size.Width, rectangle.Width); Assert.Equal(rect.Size.Height, rectangle.Height); - var pen = Assert.IsType>(processor.Pen); + Pen pen = Assert.IsType>(processor.Pen); Assert.Equal(thickness, pen.Width); - var brush = Assert.IsType>(pen.Brush); + SolidBrush brush = Assert.IsType>(pen.Brush); Assert.Equal(color, brush.Color); } [Fact] - public void pen_rectangle() + public void CorrectlySetsPenAndRectangle() { img.Draw(pen, rectangle); Assert.NotEmpty(img.ProcessorApplications); - var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); Assert.Equal(GraphicsOptions.Default, processor.Options); - var shapepath = Assert.IsType(processor.Path); + ShapePath shapepath = Assert.IsType(processor.Path); Assert.NotEmpty(shapepath.Paths); - var rect = Assert.IsType(shapepath.Paths[0].AsShape()); + SixLabors.Shapes.Rectangle rect = Assert.IsType(shapepath.Paths[0].AsShape()); Assert.Equal(rect.Location.X, rectangle.X); Assert.Equal(rect.Location.Y, rectangle.Y); @@ -162,19 +162,19 @@ namespace ImageSharp.Tests.Drawing.Paths } [Fact] - public void pen_rectangle_options() + public void CorrectlySetsPenRectangleAndOptions() { img.Draw(pen, rectangle, noneDefault); Assert.NotEmpty(img.ProcessorApplications); - var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); Assert.Equal(noneDefault, processor.Options); - var shapepath = Assert.IsType(processor.Path); + ShapePath shapepath = Assert.IsType(processor.Path); Assert.NotEmpty(shapepath.Paths); - var rect = Assert.IsType(shapepath.Paths[0].AsShape()); + SixLabors.Shapes.Rectangle rect = Assert.IsType(shapepath.Paths[0].AsShape()); Assert.Equal(rect.Location.X, rectangle.X); Assert.Equal(rect.Location.Y, rectangle.Y); diff --git a/tests/ImageSharp.Tests/Drawing/Paths/DrawShape.cs b/tests/ImageSharp.Tests/Drawing/Paths/DrawShape.cs index 8572099db6..30a6646f40 100644 --- a/tests/ImageSharp.Tests/Drawing/Paths/DrawShape.cs +++ b/tests/ImageSharp.Tests/Drawing/Paths/DrawShape.cs @@ -40,96 +40,96 @@ namespace ImageSharp.Tests.Drawing.Paths } [Fact] - public void Brush_Thickness_shape() + public void CorrectlySetsBrushThicknessAndShape() { img.Draw(brush, thickness, shape); Assert.NotEmpty(img.ProcessorApplications); - var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); Assert.Equal(GraphicsOptions.Default, processor.Options); - var shapepath = Assert.IsType(processor.Path); + ShapePath shapepath = Assert.IsType(processor.Path); Assert.NotEmpty(shapepath.Paths); Assert.Equal(shape, shapepath.Paths[0].AsShape()); - var pen = Assert.IsType>(processor.Pen); + Pen pen = Assert.IsType>(processor.Pen); Assert.Equal(brush, pen.Brush); Assert.Equal(thickness, pen.Width); } [Fact] - public void Brush_Thickness_shape_options() + public void CorrectlySetsBrushThicknessShapeAndOptions() { img.Draw(brush, thickness, shape, noneDefault); Assert.NotEmpty(img.ProcessorApplications); - var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); Assert.Equal(noneDefault, processor.Options); - var shapepath = Assert.IsType(processor.Path); + ShapePath shapepath = Assert.IsType(processor.Path); Assert.NotEmpty(shapepath.Paths); Assert.Equal(shape, shapepath.Paths[0].AsShape()); - var pen = Assert.IsType>(processor.Pen); + Pen pen = Assert.IsType>(processor.Pen); Assert.Equal(brush, pen.Brush); Assert.Equal(thickness, pen.Width); } [Fact] - public void color_Thickness_shape() + public void CorrectlySetsColorThicknessAndShape() { img.Draw(color, thickness, shape); Assert.NotEmpty(img.ProcessorApplications); - var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); Assert.Equal(GraphicsOptions.Default, processor.Options); - var shapepath = Assert.IsType(processor.Path); + ShapePath shapepath = Assert.IsType(processor.Path); Assert.NotEmpty(shapepath.Paths); Assert.Equal(shape, shapepath.Paths[0].AsShape()); - var pen = Assert.IsType>(processor.Pen); + Pen pen = Assert.IsType>(processor.Pen); Assert.Equal(thickness, pen.Width); - var brush = Assert.IsType>(pen.Brush); + SolidBrush brush = Assert.IsType>(pen.Brush); Assert.Equal(color, brush.Color); } [Fact] - public void color_Thickness_shape_options() + public void CorrectlySetsColorThicknessShapeAndOptions() { img.Draw(color, thickness, shape, noneDefault); Assert.NotEmpty(img.ProcessorApplications); - var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); Assert.Equal(noneDefault, processor.Options); - var shapepath = Assert.IsType(processor.Path); + ShapePath shapepath = Assert.IsType(processor.Path); Assert.NotEmpty(shapepath.Paths); Assert.Equal(shape, shapepath.Paths[0].AsShape()); - var pen = Assert.IsType>(processor.Pen); + Pen pen = Assert.IsType>(processor.Pen); Assert.Equal(thickness, pen.Width); - var brush = Assert.IsType>(pen.Brush); + SolidBrush brush = Assert.IsType>(pen.Brush); Assert.Equal(color, brush.Color); } [Fact] - public void pen_shape() + public void CorrectlySetsPenAndShape() { img.Draw(pen, shape); Assert.NotEmpty(img.ProcessorApplications); - var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); Assert.Equal(GraphicsOptions.Default, processor.Options); - var shapepath = Assert.IsType(processor.Path); + ShapePath shapepath = Assert.IsType(processor.Path); Assert.NotEmpty(shapepath.Paths); Assert.Equal(shape, shapepath.Paths[0].AsShape()); @@ -137,16 +137,16 @@ namespace ImageSharp.Tests.Drawing.Paths } [Fact] - public void pen_path_options() + public void CorrectlySetsPenShapeAndOptions() { img.Draw(pen, shape, noneDefault); Assert.NotEmpty(img.ProcessorApplications); - var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); Assert.Equal(noneDefault, processor.Options); - var shapepath = Assert.IsType(processor.Path); + ShapePath shapepath = Assert.IsType(processor.Path); Assert.NotEmpty(shapepath.Paths); Assert.Equal(shape, shapepath.Paths[0].AsShape()); diff --git a/tests/ImageSharp.Tests/Drawing/Paths/FillPath.cs b/tests/ImageSharp.Tests/Drawing/Paths/FillPath.cs index 04a2a82abb..5ba6580bd7 100644 --- a/tests/ImageSharp.Tests/Drawing/Paths/FillPath.cs +++ b/tests/ImageSharp.Tests/Drawing/Paths/FillPath.cs @@ -29,7 +29,7 @@ namespace ImageSharp.Tests.Drawing.Paths public FillPath() { - this.img = new Paths.ProcessorWatchingImage(10, 10); + this.img = new ProcessorWatchingImage(10, 10); } public void Dispose() @@ -38,75 +38,75 @@ namespace ImageSharp.Tests.Drawing.Paths } [Fact] - public void Brush_path() + public void CorrectlySetsBrushAndPath() { img.Fill(brush, path); Assert.NotEmpty(img.ProcessorApplications); - var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + FillRegionProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); Assert.Equal(GraphicsOptions.Default, processor.Options); - var region = Assert.IsType(processor.Region); - var polygon = Assert.IsType(region.Shape); - var segments = Assert.IsType(polygon.LineSegments[0]); + ShapeRegion region = Assert.IsType(processor.Region); + + // path is converted to a polygon before filling + Polygon polygon = Assert.IsType(region.Shape); + LinearLineSegment segments = Assert.IsType(polygon.LineSegments[0]); Assert.Equal(brush, processor.Brush); } [Fact] - public void Brush_path_options() + public void CorrectlySetsBrushPathOptions() { img.Fill(brush, path, noneDefault); Assert.NotEmpty(img.ProcessorApplications); - var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + FillRegionProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); Assert.Equal(noneDefault, processor.Options); - var region = Assert.IsType(processor.Region); - var polygon = Assert.IsType(region.Shape); - var segments = Assert.IsType(polygon.LineSegments[0]); + ShapeRegion region = Assert.IsType(processor.Region); + Polygon polygon = Assert.IsType(region.Shape); + LinearLineSegment segments = Assert.IsType(polygon.LineSegments[0]); Assert.Equal(brush, processor.Brush); } [Fact] - public void color_path() + public void CorrectlySetsColorAndPath() { img.Fill(color, path); - + Assert.NotEmpty(img.ProcessorApplications); - var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + FillRegionProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); Assert.Equal(GraphicsOptions.Default, processor.Options); - var region = Assert.IsType(processor.Region); - var polygon = Assert.IsType(region.Shape); - var segments = Assert.IsType(polygon.LineSegments[0]); + ShapeRegion region = Assert.IsType(processor.Region); + Polygon polygon = Assert.IsType(region.Shape); + LinearLineSegment segments = Assert.IsType(polygon.LineSegments[0]); - var brush = Assert.IsType>(processor.Brush); + SolidBrush brush = Assert.IsType>(processor.Brush); Assert.Equal(color, brush.Color); } [Fact] - public void color_path_options() + public void CorrectlySetsColorPathAndOptions() { img.Fill(color, path, noneDefault); - Assert.NotEmpty(img.ProcessorApplications); - var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + FillRegionProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); Assert.Equal(noneDefault, processor.Options); - var region = Assert.IsType(processor.Region); - var polygon = Assert.IsType(region.Shape); - var segments = Assert.IsType(polygon.LineSegments[0]); + ShapeRegion region = Assert.IsType(processor.Region); + Polygon polygon = Assert.IsType(region.Shape); + LinearLineSegment segments = Assert.IsType(polygon.LineSegments[0]); - var brush = Assert.IsType>(processor.Brush); + SolidBrush brush = Assert.IsType>(processor.Brush); Assert.Equal(color, brush.Color); - } } } diff --git a/tests/ImageSharp.Tests/Drawing/Paths/FillPolygon.cs b/tests/ImageSharp.Tests/Drawing/Paths/FillPolygon.cs index 2c95eb2094..ad72d4c4ee 100644 --- a/tests/ImageSharp.Tests/Drawing/Paths/FillPolygon.cs +++ b/tests/ImageSharp.Tests/Drawing/Paths/FillPolygon.cs @@ -38,73 +38,72 @@ namespace ImageSharp.Tests.Drawing.Paths } [Fact] - public void Brush_path() + public void CorrectlySetsBrushAndPath() { img.FillPolygon(brush, path); Assert.NotEmpty(img.ProcessorApplications); - var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + FillRegionProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); Assert.Equal(GraphicsOptions.Default, processor.Options); - var region = Assert.IsType(processor.Region); - var polygon = Assert.IsType(region.Shape); - var segemnt = Assert.IsType(polygon.LineSegments[0]); + ShapeRegion region = Assert.IsType(processor.Region); + Polygon polygon = Assert.IsType(region.Shape); + LinearLineSegment segemnt = Assert.IsType(polygon.LineSegments[0]); Assert.Equal(brush, processor.Brush); } [Fact] - public void Brush_path_options() + public void CorrectlySetsBrushPathAndOptions() { img.FillPolygon(brush, path, noneDefault); Assert.NotEmpty(img.ProcessorApplications); - var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + FillRegionProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); Assert.Equal(noneDefault, processor.Options); - var region = Assert.IsType(processor.Region); - var polygon = Assert.IsType(region.Shape); - var segemnt = Assert.IsType(polygon.LineSegments[0]); + ShapeRegion region = Assert.IsType(processor.Region); + Polygon polygon = Assert.IsType(region.Shape); + LinearLineSegment segemnt = Assert.IsType(polygon.LineSegments[0]); Assert.Equal(brush, processor.Brush); } [Fact] - public void color_path() + public void CorrectlySetsColorAndPath() { img.FillPolygon(color, path); Assert.NotEmpty(img.ProcessorApplications); - var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + FillRegionProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); Assert.Equal(GraphicsOptions.Default, processor.Options); - var region = Assert.IsType(processor.Region); - var polygon = Assert.IsType(region.Shape); - var segemnt = Assert.IsType(polygon.LineSegments[0]); + ShapeRegion region = Assert.IsType(processor.Region); + Polygon polygon = Assert.IsType(region.Shape); + LinearLineSegment segemnt = Assert.IsType(polygon.LineSegments[0]); - var brush = Assert.IsType>(processor.Brush); + SolidBrush brush = Assert.IsType>(processor.Brush); Assert.Equal(color, brush.Color); } [Fact] - public void color_path_options() + public void CorrectlySetsColorPathAndOptions() { img.FillPolygon(color, path, noneDefault); - Assert.NotEmpty(img.ProcessorApplications); - var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + FillRegionProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); Assert.Equal(noneDefault, processor.Options); - var region = Assert.IsType(processor.Region); - var polygon = Assert.IsType(region.Shape); - var segemnt = Assert.IsType(polygon.LineSegments[0]); + ShapeRegion region = Assert.IsType(processor.Region); + Polygon polygon = Assert.IsType(region.Shape); + LinearLineSegment segemnt = Assert.IsType(polygon.LineSegments[0]); - var brush = Assert.IsType>(processor.Brush); + SolidBrush brush = Assert.IsType>(processor.Brush); Assert.Equal(color, brush.Color); } } diff --git a/tests/ImageSharp.Tests/Drawing/Paths/FillRectangle.cs b/tests/ImageSharp.Tests/Drawing/Paths/FillRectangle.cs index 520b107b9a..f6b1c4adef 100644 --- a/tests/ImageSharp.Tests/Drawing/Paths/FillRectangle.cs +++ b/tests/ImageSharp.Tests/Drawing/Paths/FillRectangle.cs @@ -34,17 +34,17 @@ namespace ImageSharp.Tests.Drawing.Paths } [Fact] - public void Brush_path() + public void CorrectlySetsBrushAndRectangle() { img.Fill(brush, rectangle); Assert.NotEmpty(img.ProcessorApplications); - var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + FillRegionProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); Assert.Equal(GraphicsOptions.Default, processor.Options); - var region = Assert.IsType(processor.Region); - var rect = Assert.IsType(region.Shape); + ShapeRegion region = Assert.IsType(processor.Region); + SixLabors.Shapes.Rectangle rect = Assert.IsType(region.Shape); Assert.Equal(rect.Location.X, rectangle.X); Assert.Equal(rect.Location.Y, rectangle.Y); Assert.Equal(rect.Size.Width, rectangle.Width); @@ -54,17 +54,17 @@ namespace ImageSharp.Tests.Drawing.Paths } [Fact] - public void Brush_path_options() + public void CorrectlySetsBrushRectangleAndOptions() { img.Fill(brush, rectangle, noneDefault); Assert.NotEmpty(img.ProcessorApplications); - var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + FillRegionProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); Assert.Equal(noneDefault, processor.Options); - var region = Assert.IsType(processor.Region); - var rect = Assert.IsType(region.Shape); + ShapeRegion region = Assert.IsType(processor.Region); + SixLabors.Shapes.Rectangle rect = Assert.IsType(region.Shape); Assert.Equal(rect.Location.X, rectangle.X); Assert.Equal(rect.Location.Y, rectangle.Y); Assert.Equal(rect.Size.Width, rectangle.Width); @@ -74,45 +74,44 @@ namespace ImageSharp.Tests.Drawing.Paths } [Fact] - public void color_path() + public void CorrectlySetsColorAndRectangle() { img.Fill(color, rectangle); Assert.NotEmpty(img.ProcessorApplications); - var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + FillRegionProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); Assert.Equal(GraphicsOptions.Default, processor.Options); - var region = Assert.IsType(processor.Region); - var rect = Assert.IsType(region.Shape); + ShapeRegion region = Assert.IsType(processor.Region); + SixLabors.Shapes.Rectangle rect = Assert.IsType(region.Shape); Assert.Equal(rect.Location.X, rectangle.X); Assert.Equal(rect.Location.Y, rectangle.Y); Assert.Equal(rect.Size.Width, rectangle.Width); Assert.Equal(rect.Size.Height, rectangle.Height); - var brush = Assert.IsType>(processor.Brush); + SolidBrush brush = Assert.IsType>(processor.Brush); Assert.Equal(color, brush.Color); } [Fact] - public void color_path_options() + public void CorrectlySetsColorRectangleAndOptions() { img.Fill(color, rectangle, noneDefault); - Assert.NotEmpty(img.ProcessorApplications); - var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + FillRegionProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); Assert.Equal(noneDefault, processor.Options); - var region = Assert.IsType(processor.Region); - var rect = Assert.IsType(region.Shape); + ShapeRegion region = Assert.IsType(processor.Region); + SixLabors.Shapes.Rectangle rect = Assert.IsType(region.Shape); Assert.Equal(rect.Location.X, rectangle.X); Assert.Equal(rect.Location.Y, rectangle.Y); Assert.Equal(rect.Size.Width, rectangle.Width); Assert.Equal(rect.Size.Height, rectangle.Height); - var brush = Assert.IsType>(processor.Brush); + SolidBrush brush = Assert.IsType>(processor.Brush); Assert.Equal(color, brush.Color); } } diff --git a/tests/ImageSharp.Tests/Drawing/Paths/FillShape.cs b/tests/ImageSharp.Tests/Drawing/Paths/FillShape.cs index 26bf61b5c2..140870a3c2 100644 --- a/tests/ImageSharp.Tests/Drawing/Paths/FillShape.cs +++ b/tests/ImageSharp.Tests/Drawing/Paths/FillShape.cs @@ -19,7 +19,7 @@ namespace ImageSharp.Tests.Drawing.Paths GraphicsOptions noneDefault = new GraphicsOptions(); Color color = Color.HotPink; SolidBrush brush = Brushes.Solid(Color.HotPink); - IShape shape = new SixLabors.Shapes.Polygon(new LinearLineSegment(new Vector2[] { + IShape shape = new Polygon(new LinearLineSegment(new Vector2[] { new Vector2(10,10), new Vector2(20,10), new Vector2(20,10), @@ -38,69 +38,68 @@ namespace ImageSharp.Tests.Drawing.Paths } [Fact] - public void Brush_shape() + public void CorrectlySetsBrushAndShape() { img.Fill(brush, shape); Assert.NotEmpty(img.ProcessorApplications); - var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + FillRegionProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); Assert.Equal(GraphicsOptions.Default, processor.Options); - var region = Assert.IsType(processor.Region); + ShapeRegion region = Assert.IsType(processor.Region); Assert.Equal(shape, region.Shape); Assert.Equal(brush, processor.Brush); } [Fact] - public void Brush_shape_options() + public void CorrectlySetsBrushShapeAndOptions() { img.Fill(brush, shape, noneDefault); Assert.NotEmpty(img.ProcessorApplications); - var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + FillRegionProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); Assert.Equal(noneDefault, processor.Options); - var region = Assert.IsType(processor.Region); + ShapeRegion region = Assert.IsType(processor.Region); Assert.Equal(shape, region.Shape); Assert.Equal(brush, processor.Brush); } [Fact] - public void color_shape() + public void CorrectlySetsColorAndShape() { img.Fill(color, shape); Assert.NotEmpty(img.ProcessorApplications); - var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + FillRegionProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); Assert.Equal(GraphicsOptions.Default, processor.Options); - var region = Assert.IsType(processor.Region); + ShapeRegion region = Assert.IsType(processor.Region); Assert.Equal(shape, region.Shape); - var brush = Assert.IsType>(processor.Brush); + SolidBrush brush = Assert.IsType>(processor.Brush); Assert.Equal(color, brush.Color); } [Fact] - public void color_shape_options() + public void CorrectlySetsColorShapeAndOptions() { img.Fill(color, shape, noneDefault); - Assert.NotEmpty(img.ProcessorApplications); - var processor = Assert.IsType>(img.ProcessorApplications[0].processor); + FillRegionProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); Assert.Equal(noneDefault, processor.Options); - var region = Assert.IsType(processor.Region); + ShapeRegion region = Assert.IsType(processor.Region); Assert.Equal(shape, region.Shape); - var brush = Assert.IsType>(processor.Brush); + SolidBrush brush = Assert.IsType>(processor.Brush); Assert.Equal(color, brush.Color); } diff --git a/tests/ImageSharp.Tests/Drawing/Paths/ProcessorWatchingImage.cs b/tests/ImageSharp.Tests/Drawing/Paths/ProcessorWatchingImage.cs index ad3d41bc66..3bb3b3e777 100644 --- a/tests/ImageSharp.Tests/Drawing/Paths/ProcessorWatchingImage.cs +++ b/tests/ImageSharp.Tests/Drawing/Paths/ProcessorWatchingImage.cs @@ -27,6 +27,10 @@ namespace ImageSharp.Tests.Drawing.Paths processor = processor, rectangle = rectangle }); + + // doesn't really apply the processor to the fake images as this is supposed + // to be just used to test which processor was finally applied and to interogate + // its settings } public struct ProcessorDetails diff --git a/tests/ImageSharp.Tests/Drawing/Paths/ShapePathTests.cs b/tests/ImageSharp.Tests/Drawing/Paths/ShapePathTests.cs index 24e921ccb6..5fc9678461 100644 --- a/tests/ImageSharp.Tests/Drawing/Paths/ShapePathTests.cs +++ b/tests/ImageSharp.Tests/Drawing/Paths/ShapePathTests.cs @@ -39,7 +39,7 @@ namespace ImageSharp.Tests.Drawing.Paths } [Fact] - public void ShapePathWithPath_CallsAsShape() + public void ShapePathWithPathCallsAsShape() { new ShapePath(pathMock1.Object); diff --git a/tests/ImageSharp.Tests/Drawing/Paths/ShapeRegionTests.cs b/tests/ImageSharp.Tests/Drawing/Paths/ShapeRegionTests.cs index e983dce669..6754949518 100644 --- a/tests/ImageSharp.Tests/Drawing/Paths/ShapeRegionTests.cs +++ b/tests/ImageSharp.Tests/Drawing/Paths/ShapeRegionTests.cs @@ -35,7 +35,7 @@ namespace ImageSharp.Tests.Drawing.Paths } [Fact] - public void ShapeRegionWithPath_CallsAsShape() + public void ShapeRegionWithPathCallsAsShape() { new ShapeRegion(pathMock.Object); @@ -43,7 +43,7 @@ namespace ImageSharp.Tests.Drawing.Paths } [Fact] - public void ShapeRegionWithPath_RetainsShape() + public void ShapeRegionWithPathRetainsShape() { ShapeRegion region = new ShapeRegion(pathMock.Object); diff --git a/tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs b/tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs index f63d90aa6e..80713468d4 100644 --- a/tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs +++ b/tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs @@ -30,7 +30,7 @@ namespace ImageSharp.Tests.Drawing { image .BackgroundColor(Color.Blue) - .Fill(Color.HotPink,new BezierPolygon(simplePath)) + .Fill(Color.HotPink, new Polygon(new BezierLineSegment(simplePath))) .Save(output); } @@ -71,7 +71,7 @@ namespace ImageSharp.Tests.Drawing { image .BackgroundColor(Color.Blue) - .Fill(color, new BezierPolygon(simplePath)) + .Fill(color, new Polygon(new BezierLineSegment(simplePath))) .Save(output); } diff --git a/tests/ImageSharp.Tests/Drawing/SolidComplexPolygonTests.cs b/tests/ImageSharp.Tests/Drawing/SolidComplexPolygonTests.cs index a1973a280b..45ee59d665 100644 --- a/tests/ImageSharp.Tests/Drawing/SolidComplexPolygonTests.cs +++ b/tests/ImageSharp.Tests/Drawing/SolidComplexPolygonTests.cs @@ -19,15 +19,15 @@ namespace ImageSharp.Tests.Drawing public void ImageShouldBeOverlayedByPolygonOutline() { string path = this.CreateOutputDirectory("Drawing", "ComplexPolygon"); - LinearPolygon simplePath = new LinearPolygon( + Polygon simplePath = new Polygon(new LinearLineSegment( new Vector2(10, 10), new Vector2(200, 150), - new Vector2(50, 300)); + new Vector2(50, 300))); - LinearPolygon hole1 = new LinearPolygon( + Polygon hole1 = new Polygon(new LinearLineSegment( new Vector2(37, 85), new Vector2(93, 85), - new Vector2(65, 137)); + new Vector2(65, 137))); using (Image image = new Image(500, 500)) { @@ -62,15 +62,15 @@ namespace ImageSharp.Tests.Drawing public void ImageShouldBeOverlayedPolygonOutlineWithOverlap() { string path = this.CreateOutputDirectory("Drawing", "ComplexPolygon"); - LinearPolygon simplePath = new LinearPolygon( + Polygon simplePath = new Polygon(new LinearLineSegment( new Vector2(10, 10), new Vector2(200, 150), - new Vector2(50, 300)); + new Vector2(50, 300))); - LinearPolygon hole1 = new LinearPolygon( + Polygon hole1 = new Polygon(new LinearLineSegment( new Vector2(37, 85), new Vector2(130, 40), - new Vector2(65, 137)); + new Vector2(65, 137))); using (Image image = new Image(500, 500)) { @@ -104,15 +104,15 @@ namespace ImageSharp.Tests.Drawing public void ImageShouldBeOverlayedPolygonOutlineWithOpacity() { string path = this.CreateOutputDirectory("Drawing", "ComplexPolygon"); - LinearPolygon simplePath = new LinearPolygon( + Polygon simplePath = new Polygon(new LinearLineSegment( new Vector2(10, 10), new Vector2(200, 150), - new Vector2(50, 300)); + new Vector2(50, 300))); - LinearPolygon hole1 = new LinearPolygon( + Polygon hole1 = new Polygon(new LinearLineSegment( new Vector2(37, 85), new Vector2(93, 85), - new Vector2(65, 137)); + new Vector2(65, 137))); Color color = new Color(Color.HotPink.R, Color.HotPink.G, Color.HotPink.B, 150); using (Image image = new Image(500, 500)) From 8994d9853baa42ecb29c7ac642e2ab3b2565c873 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Fri, 3 Feb 2017 09:30:16 +0000 Subject: [PATCH 022/142] some cleanup --- src/ImageSharp.Drawing.Paths/ShapePath.cs | 18 +++++++++--------- src/ImageSharp.Drawing.Paths/ShapeRegion.cs | 12 ++++++------ src/ImageSharp.Drawing/DrawPath.cs | 12 ++++++------ .../{Path.cs => Drawable.cs} | 6 +++--- .../Processors/DrawPathProcessor.cs | 4 ++-- .../ImageSharp.Tests/Drawing/DrawPathTests.cs | 6 +++--- .../Drawing/LineComplexPolygonTests.cs | 13 ++++++------- tests/ImageSharp.Tests/Drawing/PolygonTests.cs | 2 +- .../Drawing/SolidComplexPolygonTests.cs | 8 ++++---- .../Drawing/SolidPolygonTests.cs | 6 +++--- 10 files changed, 43 insertions(+), 44 deletions(-) rename src/ImageSharp.Drawing/{Path.cs => Drawable.cs} (91%) diff --git a/src/ImageSharp.Drawing.Paths/ShapePath.cs b/src/ImageSharp.Drawing.Paths/ShapePath.cs index cd994515ef..ee1f6e9d95 100644 --- a/src/ImageSharp.Drawing.Paths/ShapePath.cs +++ b/src/ImageSharp.Drawing.Paths/ShapePath.cs @@ -18,7 +18,7 @@ namespace ImageSharp.Drawing /// /// A drawable mapping between a / and a drawable/fillable region. /// - internal class ShapePath : ImageSharp.Drawing.Path + internal class ShapePath : ImageSharp.Drawing.Drawable { /// /// The fillable shape @@ -92,8 +92,8 @@ namespace ImageSharp.Drawing /// public override int ScanX(int x, float[] buffer, int length, int offset) { - var start = new Vector2(x, this.Bounds.Top - 1); - var end = new Vector2(x, this.Bounds.Bottom + 1); + Vector2 start = new Vector2(x, this.Bounds.Top - 1); + Vector2 end = new Vector2(x, this.Bounds.Bottom + 1); Vector2[] innerbuffer = ArrayPool.Shared.Rent(length); try { @@ -104,7 +104,7 @@ namespace ImageSharp.Drawing length, 0); - for (var i = 0; i < count; i++) + for (int i = 0; i < count; i++) { buffer[i + offset] = innerbuffer[i].Y; } @@ -129,8 +129,8 @@ namespace ImageSharp.Drawing /// public override int ScanY(int y, float[] buffer, int length, int offset) { - var start = new Vector2(float.MinValue, y); - var end = new Vector2(float.MaxValue, y); + Vector2 start = new Vector2(float.MinValue, y); + Vector2 end = new Vector2(float.MaxValue, y); Vector2[] innerbuffer = ArrayPool.Shared.Rent(length); try { @@ -141,7 +141,7 @@ namespace ImageSharp.Drawing length, 0); - for (var i = 0; i < count; i++) + for (int i = 0; i < count; i++) { buffer[i + offset] = innerbuffer[i].X; } @@ -162,13 +162,13 @@ namespace ImageSharp.Drawing /// Information about the the point public override PointInfo GetPointInfo(int x, int y) { - var point = new Vector2(x, y); + Vector2 point = new Vector2(x, y); SixLabors.Shapes.PointInfo result = default(SixLabors.Shapes.PointInfo); float distance = float.MaxValue; for (int i = 0; i < this.Paths.Length; i++) { - var p = this.Paths[i].Distance(point); + SixLabors.Shapes.PointInfo p = this.Paths[i].Distance(point); if (p.DistanceFromPath < distance) { distance = p.DistanceFromPath; diff --git a/src/ImageSharp.Drawing.Paths/ShapeRegion.cs b/src/ImageSharp.Drawing.Paths/ShapeRegion.cs index b43ad26b46..b6921f16ee 100644 --- a/src/ImageSharp.Drawing.Paths/ShapeRegion.cs +++ b/src/ImageSharp.Drawing.Paths/ShapeRegion.cs @@ -72,8 +72,8 @@ namespace ImageSharp.Drawing /// public override int ScanX(int x, float[] buffer, int length, int offset) { - var start = new Vector2(x, this.Bounds.Top - 1); - var end = new Vector2(x, this.Bounds.Bottom + 1); + Vector2 start = new Vector2(x, this.Bounds.Top - 1); + Vector2 end = new Vector2(x, this.Bounds.Bottom + 1); Vector2[] innerbuffer = ArrayPool.Shared.Rent(length); try { @@ -84,7 +84,7 @@ namespace ImageSharp.Drawing length, 0); - for (var i = 0; i < count; i++) + for (int i = 0; i < count; i++) { buffer[i + offset] = innerbuffer[i].Y; } @@ -109,8 +109,8 @@ namespace ImageSharp.Drawing /// public override int ScanY(int y, float[] buffer, int length, int offset) { - var start = new Vector2(float.MinValue, y); - var end = new Vector2(float.MaxValue, y); + Vector2 start = new Vector2(float.MinValue, y); + Vector2 end = new Vector2(float.MaxValue, y); Vector2[] innerbuffer = ArrayPool.Shared.Rent(length); try { @@ -121,7 +121,7 @@ namespace ImageSharp.Drawing length, 0); - for (var i = 0; i < count; i++) + for (int i = 0; i < count; i++) { buffer[i + offset] = innerbuffer[i].X; } diff --git a/src/ImageSharp.Drawing/DrawPath.cs b/src/ImageSharp.Drawing/DrawPath.cs index 828d50fb33..fe833e3af9 100644 --- a/src/ImageSharp.Drawing/DrawPath.cs +++ b/src/ImageSharp.Drawing/DrawPath.cs @@ -28,7 +28,7 @@ namespace ImageSharp /// /// The Image /// - public static Image Draw(this Image source, IPen pen, Path path, GraphicsOptions options) + public static Image Draw(this Image source, IPen pen, Drawable path, GraphicsOptions options) where TColor : struct, IPackedPixel, IEquatable { return source.Apply(new DrawPathProcessor(pen, path, options)); @@ -44,7 +44,7 @@ namespace ImageSharp /// /// The Image /// - public static Image Draw(this Image source, IPen pen, Path path) + public static Image Draw(this Image source, IPen pen, Drawable path) where TColor : struct, IPackedPixel, IEquatable { return source.Draw(pen, path, GraphicsOptions.Default); @@ -62,7 +62,7 @@ namespace ImageSharp /// /// The Image /// - public static Image Draw(this Image source, IBrush brush, float thickness, Path path, GraphicsOptions options) + public static Image Draw(this Image source, IBrush brush, float thickness, Drawable path, GraphicsOptions options) where TColor : struct, IPackedPixel, IEquatable { return source.Draw(new Pen(brush, thickness), path, options); @@ -79,7 +79,7 @@ namespace ImageSharp /// /// The Image /// - public static Image Draw(this Image source, IBrush brush, float thickness, Path path) + public static Image Draw(this Image source, IBrush brush, float thickness, Drawable path) where TColor : struct, IPackedPixel, IEquatable { return source.Draw(new Pen(brush, thickness), path); @@ -97,7 +97,7 @@ namespace ImageSharp /// /// The Image /// - public static Image Draw(this Image source, TColor color, float thickness, Path path, GraphicsOptions options) + public static Image Draw(this Image source, TColor color, float thickness, Drawable path, GraphicsOptions options) where TColor : struct, IPackedPixel, IEquatable { return source.Draw(new SolidBrush(color), thickness, path, options); @@ -114,7 +114,7 @@ namespace ImageSharp /// /// The Image /// - public static Image Draw(this Image source, TColor color, float thickness, Path path) + public static Image Draw(this Image source, TColor color, float thickness, Drawable path) where TColor : struct, IPackedPixel, IEquatable { return source.Draw(new SolidBrush(color), thickness, path); diff --git a/src/ImageSharp.Drawing/Path.cs b/src/ImageSharp.Drawing/Drawable.cs similarity index 91% rename from src/ImageSharp.Drawing/Path.cs rename to src/ImageSharp.Drawing/Drawable.cs index a997fa18f7..b818273313 100644 --- a/src/ImageSharp.Drawing/Path.cs +++ b/src/ImageSharp.Drawing/Drawable.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // @@ -6,9 +6,9 @@ namespace ImageSharp.Drawing { /// - /// Represents a something that has knowledge about its outline. + /// Represents a path or set of paths that can be drawn as an outline. /// - public abstract class Path + public abstract class Drawable { /// /// Gets the maximum number of intersections to could be returned. diff --git a/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs b/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs index 3fe5570807..53f0408d47 100644 --- a/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs @@ -31,7 +31,7 @@ namespace ImageSharp.Drawing.Processors /// The pen. /// The region. /// The options. - public DrawPathProcessor(IPen pen, Path region, GraphicsOptions options) + public DrawPathProcessor(IPen pen, Drawable region, GraphicsOptions options) { this.Path = region; this.Pen = pen; @@ -60,7 +60,7 @@ namespace ImageSharp.Drawing.Processors /// /// The path. /// - public Path Path { get; } + public Drawable Path { get; } /// protected override void OnApply(ImageBase source, Rectangle sourceRectangle) diff --git a/tests/ImageSharp.Tests/Drawing/DrawPathTests.cs b/tests/ImageSharp.Tests/Drawing/DrawPathTests.cs index f4465027df..fc231a89d5 100644 --- a/tests/ImageSharp.Tests/Drawing/DrawPathTests.cs +++ b/tests/ImageSharp.Tests/Drawing/DrawPathTests.cs @@ -33,13 +33,13 @@ namespace ImageSharp.Tests.Drawing new Vector2(60, 10), new Vector2(10, 400)); - ShapePath p = new ShapePath(linerSegemnt, bazierSegment); + ShapePath p = new ShapePath(linerSegemnt, bazierSegment); using (FileStream output = File.OpenWrite($"{path}/Simple.png")) { image .BackgroundColor(Color.Blue) - .Draw(Color.HotPink, 5, p) + .Draw(Color.HotPink, 5, p) .Save(output); } @@ -82,7 +82,7 @@ namespace ImageSharp.Tests.Drawing { image .BackgroundColor(Color.Blue) - .Draw(color, 10, p) + .Draw(color, 10, p) .Save(output); } diff --git a/tests/ImageSharp.Tests/Drawing/LineComplexPolygonTests.cs b/tests/ImageSharp.Tests/Drawing/LineComplexPolygonTests.cs index 0a4f2c4a06..6153cb3105 100644 --- a/tests/ImageSharp.Tests/Drawing/LineComplexPolygonTests.cs +++ b/tests/ImageSharp.Tests/Drawing/LineComplexPolygonTests.cs @@ -37,8 +37,8 @@ namespace ImageSharp.Tests.Drawing { image .BackgroundColor(Color.Blue) - .Draw(Color.HotPink, 5, simplePath.Clip(hole1)) - .Save(output); + .Draw(Color.HotPink, 5, simplePath.Clip(hole1)) + .Save(output); } using (PixelAccessor sourcePixels = image.Lock()) @@ -87,7 +87,7 @@ namespace ImageSharp.Tests.Drawing { image .BackgroundColor(Color.Blue) - .Draw(Color.HotPink, 5, simplePath.Clip(hole1)) + .Draw(Color.HotPink, 5, simplePath.Clip(hole1)) .Save(output); } @@ -131,7 +131,6 @@ namespace ImageSharp.Tests.Drawing new Vector2(37, 85), new Vector2(130, 40), new Vector2(65, 137))); - using (Image image = new Image(500, 500)) { @@ -139,7 +138,7 @@ namespace ImageSharp.Tests.Drawing { image .BackgroundColor(Color.Blue) - .Draw(Color.HotPink, 5, simplePath.Clip(hole1)) + .Draw(Color.HotPink, 5, simplePath.Clip(hole1)) .Save(output); } @@ -185,7 +184,7 @@ namespace ImageSharp.Tests.Drawing { image .BackgroundColor(Color.Blue) - .Draw(Pens.Dash(Color.HotPink, 5), simplePath.Clip(hole1)) + .Draw(Pens.Dash(Color.HotPink, 5), simplePath.Clip(hole1)) .Save(output); } } @@ -213,7 +212,7 @@ namespace ImageSharp.Tests.Drawing { image .BackgroundColor(Color.Blue) - .Draw(color, 5, simplePath.Clip(hole1)) + .Draw(color, 5, simplePath.Clip(hole1)) .Save(output); } diff --git a/tests/ImageSharp.Tests/Drawing/PolygonTests.cs b/tests/ImageSharp.Tests/Drawing/PolygonTests.cs index a166464c9e..3e06ca918e 100644 --- a/tests/ImageSharp.Tests/Drawing/PolygonTests.cs +++ b/tests/ImageSharp.Tests/Drawing/PolygonTests.cs @@ -97,7 +97,7 @@ namespace ImageSharp.Tests.Drawing { image .BackgroundColor(Color.Blue) - .Draw(Color.HotPink, 10, new Rectangle(10, 10, 190, 140)) + .Draw(Color.HotPink, 10, new Rectangle(10, 10, 190, 140)) .Save(output); } diff --git a/tests/ImageSharp.Tests/Drawing/SolidComplexPolygonTests.cs b/tests/ImageSharp.Tests/Drawing/SolidComplexPolygonTests.cs index 45ee59d665..98ec9ff83e 100644 --- a/tests/ImageSharp.Tests/Drawing/SolidComplexPolygonTests.cs +++ b/tests/ImageSharp.Tests/Drawing/SolidComplexPolygonTests.cs @@ -35,7 +35,7 @@ namespace ImageSharp.Tests.Drawing { image .BackgroundColor(Color.Blue) - .Fill(Color.HotPink, simplePath.Clip(hole1)) + .Fill(Color.HotPink, simplePath.Clip(hole1)) .Save(output); } @@ -78,7 +78,7 @@ namespace ImageSharp.Tests.Drawing { image .BackgroundColor(Color.Blue) - .Fill(Color.HotPink, simplePath.Clip(hole1)) + .Fill(Color.HotPink, simplePath.Clip(hole1)) .Save(output); } @@ -121,8 +121,8 @@ namespace ImageSharp.Tests.Drawing { image .BackgroundColor(Color.Blue) - .Fill(color, simplePath.Clip(hole1)) - .Save(output); + .Fill(color, simplePath.Clip(hole1)) + .Save(output); } //shift background color towards forground color by the opacity amount diff --git a/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs b/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs index d5b2067239..5533fbc0a5 100644 --- a/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs +++ b/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs @@ -71,7 +71,7 @@ namespace ImageSharp.Tests.Drawing { Assert.Equal(Color.HotPink, sourcePixels[11, 11]); - Assert.Equal(Color.HotPink, sourcePixels[199, 150]); + Assert.Equal(Color.HotPink, sourcePixels[199, 150]); Assert.Equal(Color.HotPink, sourcePixels[50, 50]); @@ -144,14 +144,14 @@ namespace ImageSharp.Tests.Drawing public void ImageShouldBeOverlayedByFilledRectangle() { string path = this.CreateOutputDirectory("Drawing", "FilledPolygons"); - + using (Image image = new Image(500, 500)) { using (FileStream output = File.OpenWrite($"{path}/Rectangle.png")) { image .BackgroundColor(Color.Blue) - .Fill(Color.HotPink, new SixLabors.Shapes.Rectangle(10,10, 190, 140)) + .Fill(Color.HotPink, new SixLabors.Shapes.Rectangle(10,10, 190, 140)) .Save(output); } From 923425dc511fce94e7662385533aadf691f4f66b Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Fri, 3 Feb 2017 09:51:54 +0000 Subject: [PATCH 023/142] removed extension method --- src/ImageSharp.Drawing.Paths/DrawLines.cs | 2 -- src/ImageSharp.Drawing/DrawImage.cs | 3 ++- .../Binarization/BinaryThreshold.cs | 3 ++- .../ColorMatrix/BlackWhite.cs | 3 ++- .../ColorMatrix/ColorBlindness.cs | 3 ++- .../ColorMatrix/Grayscale.cs | 3 ++- src/ImageSharp.Processing/ColorMatrix/Hue.cs | 3 ++- .../ColorMatrix/Kodachrome.cs | 3 ++- .../ColorMatrix/Lomograph.cs | 3 ++- .../ColorMatrix/Polaroid.cs | 3 ++- .../ColorMatrix/Saturation.cs | 3 ++- .../ColorMatrix/Sepia.cs | 3 ++- .../Convolution/BoxBlur.cs | 3 ++- .../Convolution/DetectEdges.cs | 3 ++- .../Convolution/GaussianBlur.cs | 3 ++- .../Convolution/GaussianSharpen.cs | 3 ++- src/ImageSharp.Processing/Effects/Alpha.cs | 3 ++- .../Effects/BackgroundColor.cs | 3 ++- .../Effects/Brightness.cs | 5 +++-- src/ImageSharp.Processing/Effects/Contrast.cs | 3 ++- src/ImageSharp.Processing/Effects/Invert.cs | 3 ++- .../Effects/OilPainting.cs | 3 ++- src/ImageSharp.Processing/Effects/Pixelate.cs | 3 ++- src/ImageSharp.Processing/Overlays/Glow.cs | 3 ++- .../Overlays/Vignette.cs | 3 ++- src/ImageSharp.Processing/Transforms/Crop.cs | 4 +++- .../Transforms/EntropyCrop.cs | 4 +++- src/ImageSharp.Processing/Transforms/Flip.cs | 4 +++- .../Transforms/Resize.cs | 3 ++- .../Transforms/Rotate.cs | 4 +++- src/ImageSharp.Processing/Transforms/Skew.cs | 4 +++- .../Image/ImageProcessingExtensions.cs | 19 +------------------ 32 files changed, 67 insertions(+), 51 deletions(-) diff --git a/src/ImageSharp.Drawing.Paths/DrawLines.cs b/src/ImageSharp.Drawing.Paths/DrawLines.cs index 2e4f849870..1ff58fd1de 100644 --- a/src/ImageSharp.Drawing.Paths/DrawLines.cs +++ b/src/ImageSharp.Drawing.Paths/DrawLines.cs @@ -13,8 +13,6 @@ namespace ImageSharp using Drawing.Processors; using SixLabors.Shapes; - using Path = SixLabors.Shapes.Path; - /// /// Extension methods for the type. /// diff --git a/src/ImageSharp.Drawing/DrawImage.cs b/src/ImageSharp.Drawing/DrawImage.cs index 4b3fd491da..2fba227ee6 100644 --- a/src/ImageSharp.Drawing/DrawImage.cs +++ b/src/ImageSharp.Drawing/DrawImage.cs @@ -51,7 +51,8 @@ namespace ImageSharp location = Point.Empty; } - return source.Apply(source.Bounds, new DrawImageProcessor(image, size, location, percent)); + source.ApplyProcessor(new DrawImageProcessor(image, size, location, percent), source.Bounds); + return source; } } } \ No newline at end of file diff --git a/src/ImageSharp.Processing/Binarization/BinaryThreshold.cs b/src/ImageSharp.Processing/Binarization/BinaryThreshold.cs index 1b5b6c9bbc..e59369349f 100644 --- a/src/ImageSharp.Processing/Binarization/BinaryThreshold.cs +++ b/src/ImageSharp.Processing/Binarization/BinaryThreshold.cs @@ -40,7 +40,8 @@ namespace ImageSharp public static Image BinaryThreshold(this Image source, float threshold, Rectangle rectangle) where TColor : struct, IPackedPixel, IEquatable { - return source.Apply(rectangle, new BinaryThresholdProcessor(threshold)); + source.ApplyProcessor(new BinaryThresholdProcessor(threshold), rectangle); + return source; } } } diff --git a/src/ImageSharp.Processing/ColorMatrix/BlackWhite.cs b/src/ImageSharp.Processing/ColorMatrix/BlackWhite.cs index e172a21be0..05fb8f19c8 100644 --- a/src/ImageSharp.Processing/ColorMatrix/BlackWhite.cs +++ b/src/ImageSharp.Processing/ColorMatrix/BlackWhite.cs @@ -39,7 +39,8 @@ namespace ImageSharp public static Image BlackWhite(this Image source, Rectangle rectangle) where TColor : struct, IPackedPixel, IEquatable { - return source.Apply(rectangle, new BlackWhiteProcessor()); + source.ApplyProcessor(new BlackWhiteProcessor(), rectangle); + return source; } } } diff --git a/src/ImageSharp.Processing/ColorMatrix/ColorBlindness.cs b/src/ImageSharp.Processing/ColorMatrix/ColorBlindness.cs index 2e90b059e7..30b28861d0 100644 --- a/src/ImageSharp.Processing/ColorMatrix/ColorBlindness.cs +++ b/src/ImageSharp.Processing/ColorMatrix/ColorBlindness.cs @@ -78,7 +78,8 @@ namespace ImageSharp break; } - return source.Apply(rectangle, processor); + source.ApplyProcessor(processor, rectangle); + return source; } } } diff --git a/src/ImageSharp.Processing/ColorMatrix/Grayscale.cs b/src/ImageSharp.Processing/ColorMatrix/Grayscale.cs index f1a17c02b0..b2aadc46db 100644 --- a/src/ImageSharp.Processing/ColorMatrix/Grayscale.cs +++ b/src/ImageSharp.Processing/ColorMatrix/Grayscale.cs @@ -45,7 +45,8 @@ namespace ImageSharp ? (IImageProcessor)new GrayscaleBt709Processor() : new GrayscaleBt601Processor(); - return source.Apply(rectangle, processor); + source.ApplyProcessor(processor, rectangle); + return source; } } } diff --git a/src/ImageSharp.Processing/ColorMatrix/Hue.cs b/src/ImageSharp.Processing/ColorMatrix/Hue.cs index f03f65692a..25fcc6c559 100644 --- a/src/ImageSharp.Processing/ColorMatrix/Hue.cs +++ b/src/ImageSharp.Processing/ColorMatrix/Hue.cs @@ -41,7 +41,8 @@ namespace ImageSharp public static Image Hue(this Image source, float degrees, Rectangle rectangle) where TColor : struct, IPackedPixel, IEquatable { - return source.Apply(rectangle, new HueProcessor(degrees)); + source.ApplyProcessor(new HueProcessor(degrees), rectangle); + return source; } } } diff --git a/src/ImageSharp.Processing/ColorMatrix/Kodachrome.cs b/src/ImageSharp.Processing/ColorMatrix/Kodachrome.cs index 2592d80909..dab0224c3a 100644 --- a/src/ImageSharp.Processing/ColorMatrix/Kodachrome.cs +++ b/src/ImageSharp.Processing/ColorMatrix/Kodachrome.cs @@ -39,7 +39,8 @@ namespace ImageSharp public static Image Kodachrome(this Image source, Rectangle rectangle) where TColor : struct, IPackedPixel, IEquatable { - return source.Apply(rectangle, new KodachromeProcessor()); + source.ApplyProcessor(new KodachromeProcessor(), rectangle); + return source; } } } diff --git a/src/ImageSharp.Processing/ColorMatrix/Lomograph.cs b/src/ImageSharp.Processing/ColorMatrix/Lomograph.cs index 2605bc3011..df34dc52e9 100644 --- a/src/ImageSharp.Processing/ColorMatrix/Lomograph.cs +++ b/src/ImageSharp.Processing/ColorMatrix/Lomograph.cs @@ -39,7 +39,8 @@ namespace ImageSharp public static Image Lomograph(this Image source, Rectangle rectangle) where TColor : struct, IPackedPixel, IEquatable { - return source.Apply(rectangle, new LomographProcessor()); + source.ApplyProcessor(new LomographProcessor(), rectangle); + return source; } } } diff --git a/src/ImageSharp.Processing/ColorMatrix/Polaroid.cs b/src/ImageSharp.Processing/ColorMatrix/Polaroid.cs index 5c51a710bf..4bb8f82a31 100644 --- a/src/ImageSharp.Processing/ColorMatrix/Polaroid.cs +++ b/src/ImageSharp.Processing/ColorMatrix/Polaroid.cs @@ -39,7 +39,8 @@ namespace ImageSharp public static Image Polaroid(this Image source, Rectangle rectangle) where TColor : struct, IPackedPixel, IEquatable { - return source.Apply(rectangle, new PolaroidProcessor()); + source.ApplyProcessor(new PolaroidProcessor(), rectangle); + return source; } } } diff --git a/src/ImageSharp.Processing/ColorMatrix/Saturation.cs b/src/ImageSharp.Processing/ColorMatrix/Saturation.cs index 773329ea6c..a92483c9c3 100644 --- a/src/ImageSharp.Processing/ColorMatrix/Saturation.cs +++ b/src/ImageSharp.Processing/ColorMatrix/Saturation.cs @@ -41,7 +41,8 @@ namespace ImageSharp public static Image Saturation(this Image source, int amount, Rectangle rectangle) where TColor : struct, IPackedPixel, IEquatable { - return source.Apply(rectangle, new SaturationProcessor(amount)); + source.ApplyProcessor(new SaturationProcessor(amount), rectangle); + return source; } } } diff --git a/src/ImageSharp.Processing/ColorMatrix/Sepia.cs b/src/ImageSharp.Processing/ColorMatrix/Sepia.cs index 3f29b93e59..1a8ec4b95e 100644 --- a/src/ImageSharp.Processing/ColorMatrix/Sepia.cs +++ b/src/ImageSharp.Processing/ColorMatrix/Sepia.cs @@ -39,7 +39,8 @@ namespace ImageSharp public static Image Sepia(this Image source, Rectangle rectangle) where TColor : struct, IPackedPixel, IEquatable { - return source.Apply(rectangle, new SepiaProcessor()); + source.ApplyProcessor(new SepiaProcessor(), rectangle); + return source; } } } \ No newline at end of file diff --git a/src/ImageSharp.Processing/Convolution/BoxBlur.cs b/src/ImageSharp.Processing/Convolution/BoxBlur.cs index e16c30516d..a68c2fa44d 100644 --- a/src/ImageSharp.Processing/Convolution/BoxBlur.cs +++ b/src/ImageSharp.Processing/Convolution/BoxBlur.cs @@ -41,7 +41,8 @@ namespace ImageSharp public static Image BoxBlur(this Image source, int radius, Rectangle rectangle) where TColor : struct, IPackedPixel, IEquatable { - return source.Apply(rectangle, new BoxBlurProcessor(radius)); + source.ApplyProcessor(new BoxBlurProcessor(), rectangle); + return source; } } } diff --git a/src/ImageSharp.Processing/Convolution/DetectEdges.cs b/src/ImageSharp.Processing/Convolution/DetectEdges.cs index 32fc167f1d..a61726e74e 100644 --- a/src/ImageSharp.Processing/Convolution/DetectEdges.cs +++ b/src/ImageSharp.Processing/Convolution/DetectEdges.cs @@ -146,7 +146,8 @@ namespace ImageSharp public static Image DetectEdges(this Image source, Rectangle rectangle, IEdgeDetectorProcessor filter) where TColor : struct, IPackedPixel, IEquatable { - return source.Apply(rectangle, filter); + source.ApplyProcessor(filter, rectangle); + return source; } } } \ No newline at end of file diff --git a/src/ImageSharp.Processing/Convolution/GaussianBlur.cs b/src/ImageSharp.Processing/Convolution/GaussianBlur.cs index 7e8b9a4032..893ebb2646 100644 --- a/src/ImageSharp.Processing/Convolution/GaussianBlur.cs +++ b/src/ImageSharp.Processing/Convolution/GaussianBlur.cs @@ -41,7 +41,8 @@ namespace ImageSharp public static Image GaussianBlur(this Image source, float sigma, Rectangle rectangle) where TColor : struct, IPackedPixel, IEquatable { - return source.Apply(rectangle, new GaussianBlurProcessor(sigma)); + source.ApplyProcessor(new GaussianBlurProcessor(sigma), rectangle); + return source; } } } \ No newline at end of file diff --git a/src/ImageSharp.Processing/Convolution/GaussianSharpen.cs b/src/ImageSharp.Processing/Convolution/GaussianSharpen.cs index ef49104599..e3f8e995bc 100644 --- a/src/ImageSharp.Processing/Convolution/GaussianSharpen.cs +++ b/src/ImageSharp.Processing/Convolution/GaussianSharpen.cs @@ -41,7 +41,8 @@ namespace ImageSharp public static Image GaussianSharpen(this Image source, float sigma, Rectangle rectangle) where TColor : struct, IPackedPixel, IEquatable { - return source.Apply(rectangle, new GaussianSharpenProcessor(sigma)); + source.ApplyProcessor(new GaussianSharpenProcessor(sigma), rectangle); + return source; } } } diff --git a/src/ImageSharp.Processing/Effects/Alpha.cs b/src/ImageSharp.Processing/Effects/Alpha.cs index 856276a89e..21b6dfbcbc 100644 --- a/src/ImageSharp.Processing/Effects/Alpha.cs +++ b/src/ImageSharp.Processing/Effects/Alpha.cs @@ -40,7 +40,8 @@ namespace ImageSharp public static Image Alpha(this Image source, int percent, Rectangle rectangle) where TColor : struct, IPackedPixel, IEquatable { - return source.Apply(rectangle, new AlphaProcessor(percent)); + source.ApplyProcessor(new AlphaProcessor(percent), rectangle); + return source; } } } diff --git a/src/ImageSharp.Processing/Effects/BackgroundColor.cs b/src/ImageSharp.Processing/Effects/BackgroundColor.cs index ac1add3513..0f724c08c0 100644 --- a/src/ImageSharp.Processing/Effects/BackgroundColor.cs +++ b/src/ImageSharp.Processing/Effects/BackgroundColor.cs @@ -24,7 +24,8 @@ namespace ImageSharp public static Image BackgroundColor(this Image source, TColor color) where TColor : struct, IPackedPixel, IEquatable { - return source.Apply(source.Bounds, new BackgroundColorProcessor(color)); + source.ApplyProcessor(new BackgroundColorProcessor(color), source.Bounds); + return source; } } } diff --git a/src/ImageSharp.Processing/Effects/Brightness.cs b/src/ImageSharp.Processing/Effects/Brightness.cs index 8c9ff8946f..bf5500faf5 100644 --- a/src/ImageSharp.Processing/Effects/Brightness.cs +++ b/src/ImageSharp.Processing/Effects/Brightness.cs @@ -39,8 +39,9 @@ namespace ImageSharp /// The . public static Image Brightness(this Image source, int amount, Rectangle rectangle) where TColor : struct, IPackedPixel, IEquatable - { - return source.Apply(rectangle, new BrightnessProcessor(amount)); + { + source.ApplyProcessor(new BrightnessProcessor(amount), rectangle); + return source; } } } diff --git a/src/ImageSharp.Processing/Effects/Contrast.cs b/src/ImageSharp.Processing/Effects/Contrast.cs index 8310286825..f05eea6399 100644 --- a/src/ImageSharp.Processing/Effects/Contrast.cs +++ b/src/ImageSharp.Processing/Effects/Contrast.cs @@ -40,7 +40,8 @@ namespace ImageSharp public static Image Contrast(this Image source, int amount, Rectangle rectangle) where TColor : struct, IPackedPixel, IEquatable { - return source.Apply(rectangle, new ContrastProcessor(amount)); + source.ApplyProcessor(new ContrastProcessor(amount), rectangle); + return source; } } } diff --git a/src/ImageSharp.Processing/Effects/Invert.cs b/src/ImageSharp.Processing/Effects/Invert.cs index 31e524000e..fff3108df4 100644 --- a/src/ImageSharp.Processing/Effects/Invert.cs +++ b/src/ImageSharp.Processing/Effects/Invert.cs @@ -38,7 +38,8 @@ namespace ImageSharp public static Image Invert(this Image source, Rectangle rectangle) where TColor : struct, IPackedPixel, IEquatable { - return source.Apply(rectangle, new InvertProcessor()); + source.ApplyProcessor(new InvertProcessor(), rectangle); + return source; } } } diff --git a/src/ImageSharp.Processing/Effects/OilPainting.cs b/src/ImageSharp.Processing/Effects/OilPainting.cs index 463cfd675c..fbd7777110 100644 --- a/src/ImageSharp.Processing/Effects/OilPainting.cs +++ b/src/ImageSharp.Processing/Effects/OilPainting.cs @@ -49,7 +49,8 @@ namespace ImageSharp throw new ArgumentOutOfRangeException(nameof(brushSize)); } - return source.Apply(rectangle, new OilPaintingProcessor(levels, brushSize)); + source.ApplyProcessor(new OilPaintingProcessor(levels, brushSize), rectangle); + return source; } } } \ No newline at end of file diff --git a/src/ImageSharp.Processing/Effects/Pixelate.cs b/src/ImageSharp.Processing/Effects/Pixelate.cs index 92d1fdd698..77b014e73d 100644 --- a/src/ImageSharp.Processing/Effects/Pixelate.cs +++ b/src/ImageSharp.Processing/Effects/Pixelate.cs @@ -45,7 +45,8 @@ namespace ImageSharp throw new ArgumentOutOfRangeException(nameof(size)); } - return source.Apply(rectangle, new PixelateProcessor(size)); + source.ApplyProcessor(new PixelateProcessor(size), rectangle); + return source; } } } \ No newline at end of file diff --git a/src/ImageSharp.Processing/Overlays/Glow.cs b/src/ImageSharp.Processing/Overlays/Glow.cs index 6511407dad..91fbfc7f62 100644 --- a/src/ImageSharp.Processing/Overlays/Glow.cs +++ b/src/ImageSharp.Processing/Overlays/Glow.cs @@ -88,7 +88,8 @@ namespace ImageSharp processor.GlowColor = color; } - return source.Apply(rectangle, processor); + source.ApplyProcessor(processor, rectangle); + return source; } } } diff --git a/src/ImageSharp.Processing/Overlays/Vignette.cs b/src/ImageSharp.Processing/Overlays/Vignette.cs index f728a3e1c6..c6cd0ab1e8 100644 --- a/src/ImageSharp.Processing/Overlays/Vignette.cs +++ b/src/ImageSharp.Processing/Overlays/Vignette.cs @@ -90,7 +90,8 @@ namespace ImageSharp processor.VignetteColor = color; } - return source.Apply(rectangle, processor); + source.ApplyProcessor(processor, rectangle); + return source; } } } \ No newline at end of file diff --git a/src/ImageSharp.Processing/Transforms/Crop.cs b/src/ImageSharp.Processing/Transforms/Crop.cs index 09309a8053..bdcbe51d8a 100644 --- a/src/ImageSharp.Processing/Transforms/Crop.cs +++ b/src/ImageSharp.Processing/Transforms/Crop.cs @@ -41,7 +41,9 @@ namespace ImageSharp where TColor : struct, IPackedPixel, IEquatable { CropProcessor processor = new CropProcessor(cropRectangle); - return source.Apply(source.Bounds, processor); + + source.ApplyProcessor(processor, source.Bounds); + return source; } } } \ No newline at end of file diff --git a/src/ImageSharp.Processing/Transforms/EntropyCrop.cs b/src/ImageSharp.Processing/Transforms/EntropyCrop.cs index 8ba6baf19b..fcfcf3813e 100644 --- a/src/ImageSharp.Processing/Transforms/EntropyCrop.cs +++ b/src/ImageSharp.Processing/Transforms/EntropyCrop.cs @@ -25,7 +25,9 @@ namespace ImageSharp where TColor : struct, IPackedPixel, IEquatable { EntropyCropProcessor processor = new EntropyCropProcessor(threshold); - return source.Apply(source.Bounds, processor); + + source.ApplyProcessor(processor, source.Bounds); + return source; } } } \ No newline at end of file diff --git a/src/ImageSharp.Processing/Transforms/Flip.cs b/src/ImageSharp.Processing/Transforms/Flip.cs index 4b4c1b7d62..82b664eb14 100644 --- a/src/ImageSharp.Processing/Transforms/Flip.cs +++ b/src/ImageSharp.Processing/Transforms/Flip.cs @@ -26,7 +26,9 @@ namespace ImageSharp where TColor : struct, IPackedPixel, IEquatable { FlipProcessor processor = new FlipProcessor(flipType); - return source.Apply(source.Bounds, processor); + + source.ApplyProcessor(processor, source.Bounds); + return source; } } } \ No newline at end of file diff --git a/src/ImageSharp.Processing/Transforms/Resize.cs b/src/ImageSharp.Processing/Transforms/Resize.cs index 35ffc8b7dd..aa4bf2439f 100644 --- a/src/ImageSharp.Processing/Transforms/Resize.cs +++ b/src/ImageSharp.Processing/Transforms/Resize.cs @@ -167,7 +167,8 @@ namespace ImageSharp processor = new ResizeProcessor(sampler, width, height, targetRectangle); } - return source.Apply(sourceRectangle, processor); + source.ApplyProcessor(processor, sourceRectangle); + return source; } } } diff --git a/src/ImageSharp.Processing/Transforms/Rotate.cs b/src/ImageSharp.Processing/Transforms/Rotate.cs index b35bbc58ad..e9ed4e97c1 100644 --- a/src/ImageSharp.Processing/Transforms/Rotate.cs +++ b/src/ImageSharp.Processing/Transforms/Rotate.cs @@ -53,7 +53,9 @@ namespace ImageSharp where TColor : struct, IPackedPixel, IEquatable { RotateProcessor processor = new RotateProcessor { Angle = degrees, Expand = expand }; - return source.Apply(source.Bounds, processor); + + source.ApplyProcessor(processor, source.Bounds); + return source; } } } diff --git a/src/ImageSharp.Processing/Transforms/Skew.cs b/src/ImageSharp.Processing/Transforms/Skew.cs index 825dce5556..5a662c3004 100644 --- a/src/ImageSharp.Processing/Transforms/Skew.cs +++ b/src/ImageSharp.Processing/Transforms/Skew.cs @@ -41,7 +41,9 @@ namespace ImageSharp where TColor : struct, IPackedPixel, IEquatable { SkewProcessor processor = new SkewProcessor { AngleX = degreesX, AngleY = degreesY, Expand = expand }; - return source.Apply(source.Bounds, processor); + + source.ApplyProcessor(processor, source.Bounds); + return source; } } } diff --git a/src/ImageSharp/Image/ImageProcessingExtensions.cs b/src/ImageSharp/Image/ImageProcessingExtensions.cs index db07afe2ab..db8fb4baa7 100644 --- a/src/ImageSharp/Image/ImageProcessingExtensions.cs +++ b/src/ImageSharp/Image/ImageProcessingExtensions.cs @@ -24,24 +24,7 @@ namespace ImageSharp public static Image Apply(this Image source, IImageProcessor processor) where TColor : struct, IPackedPixel, IEquatable { - return Apply(source, source.Bounds, processor); - } - - /// - /// Applies the processor to the image. - /// This method does not resize the target image. - /// - /// The pixel format. - /// The image this method extends. - /// - /// The structure that specifies the portion of the image object to draw. - /// - /// The processors to apply to the image. - /// The . - public static Image Apply(this Image source, Rectangle sourceRectangle, IImageProcessor processor) - where TColor : struct, IPackedPixel, IEquatable - { - source.ApplyProcessor(processor, sourceRectangle); + source.ApplyProcessor(processor, source.Bounds); return source; } } From 0d252b0af15077249355bda035b4eb68b0eec010 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Fri, 3 Feb 2017 09:52:24 +0000 Subject: [PATCH 024/142] case fix --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 96d14c8be3..79f8994629 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ Packages include: - **ImageSharp.Drawing** Brushes and various drawing algorithms, including drawing Images - **ImageSharp.Drawing.Paths** - various vector drawing methods for drawing paths, polygons etc. + Various vector drawing methods for drawing paths, polygons etc. ### Manual build From a5b3aa496f0d61c0f4c1ce12b036cb24a70d6758 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Fri, 3 Feb 2017 10:21:50 +0000 Subject: [PATCH 025/142] xml doc fixes --- src/ImageSharp.Drawing.Paths/DrawBeziers.cs | 30 ++++++------- src/ImageSharp.Drawing.Paths/DrawLines.cs | 30 ++++++------- src/ImageSharp.Drawing.Paths/DrawPath.cs | 36 ++++++---------- src/ImageSharp.Drawing.Paths/DrawPolygon.cs | 32 ++++++-------- src/ImageSharp.Drawing.Paths/DrawRectangle.cs | 30 ++++++------- src/ImageSharp.Drawing.Paths/DrawShape.cs | 30 ++++++------- src/ImageSharp.Drawing.Paths/FillPaths.cs | 24 ++++------- src/ImageSharp.Drawing.Paths/FillPolygon.cs | 20 ++++----- src/ImageSharp.Drawing.Paths/FillRectangle.cs | 20 ++++----- src/ImageSharp.Drawing.Paths/FillShape.cs | 20 ++++----- .../PointInfoExtensions.cs | 2 +- .../RectangleExtensions.cs | 2 +- src/ImageSharp.Drawing.Paths/ShapePath.cs | 43 +++---------------- src/ImageSharp.Drawing.Paths/ShapeRegion.cs | 36 ++-------------- src/ImageSharp.Drawing/DrawPath.cs | 36 ++++++---------- src/ImageSharp.Drawing/Drawable.cs | 6 --- src/ImageSharp.Drawing/FillRegion.cs | 18 ++++---- .../Processors/DrawPathProcessor.cs | 10 ++--- .../Processors/FillRegionProcessor.cs | 6 +-- src/ImageSharp.Drawing/Region.cs | 13 +++--- 20 files changed, 149 insertions(+), 295 deletions(-) diff --git a/src/ImageSharp.Drawing.Paths/DrawBeziers.cs b/src/ImageSharp.Drawing.Paths/DrawBeziers.cs index 6515db5771..1a511d84d7 100644 --- a/src/ImageSharp.Drawing.Paths/DrawBeziers.cs +++ b/src/ImageSharp.Drawing.Paths/DrawBeziers.cs @@ -24,14 +24,12 @@ namespace ImageSharp /// Draws the provided Points as an open Bezier path at the provided thickness with the supplied brush /// /// The type of the color. - /// The source. + /// The image this method extends. /// The brush. /// The thickness. /// The points. /// The options. - /// - /// The Image - /// + /// The . public static Image DrawBeziers(this Image source, IBrush brush, float thickness, Vector2[] points, GraphicsOptions options) where TColor : struct, IPackedPixel, IEquatable { @@ -42,11 +40,11 @@ namespace ImageSharp /// Draws the provided Points as an open Bezier path at the provided thickness with the supplied brush /// /// The type of the color. - /// The source. + /// The image this method extends. /// The brush. /// The thickness. /// The points. - /// The Image + /// The . public static Image DrawBeziers(this Image source, IBrush brush, float thickness, Vector2[] points) where TColor : struct, IPackedPixel, IEquatable { @@ -57,11 +55,11 @@ namespace ImageSharp /// Draws the provided Points as an open Bezier path at the provided thickness with the supplied brush /// /// The type of the color. - /// The source. + /// The image this method extends. /// The color. /// The thickness. /// The points. - /// The Image + /// The . public static Image DrawBeziers(this Image source, TColor color, float thickness, Vector2[] points) where TColor : struct, IPackedPixel, IEquatable { @@ -72,14 +70,12 @@ namespace ImageSharp /// Draws the provided Points as an open Bezier path at the provided thickness with the supplied brush /// /// The type of the color. - /// The source. + /// The image this method extends. /// The color. /// The thickness. /// The points. /// The options. - /// - /// The Image - /// + /// The . public static Image DrawBeziers(this Image source, TColor color, float thickness, Vector2[] points, GraphicsOptions options) where TColor : struct, IPackedPixel, IEquatable { @@ -90,13 +86,11 @@ namespace ImageSharp /// Draws the provided Points as an open Bezier path with the supplied pen /// /// The type of the color. - /// The source. + /// The image this method extends. /// The pen. /// The points. /// The options. - /// - /// The Image - /// + /// The . public static Image DrawBeziers(this Image source, IPen pen, Vector2[] points, GraphicsOptions options) where TColor : struct, IPackedPixel, IEquatable { @@ -107,10 +101,10 @@ namespace ImageSharp /// Draws the provided Points as an open Bezier path with the supplied pen /// /// The type of the color. - /// The source. + /// The image this method extends. /// The pen. /// The points. - /// The Image + /// The . public static Image DrawBeziers(this Image source, IPen pen, Vector2[] points) where TColor : struct, IPackedPixel, IEquatable { diff --git a/src/ImageSharp.Drawing.Paths/DrawLines.cs b/src/ImageSharp.Drawing.Paths/DrawLines.cs index 1ff58fd1de..f6f8d8f6c9 100644 --- a/src/ImageSharp.Drawing.Paths/DrawLines.cs +++ b/src/ImageSharp.Drawing.Paths/DrawLines.cs @@ -22,14 +22,12 @@ namespace ImageSharp /// Draws the provided Points as an open Linear path at the provided thickness with the supplied brush /// /// The type of the color. - /// The source. + /// The image this method extends. /// The brush. /// The thickness. /// The points. /// The options. - /// - /// The Image - /// + /// The . public static Image DrawLines(this Image source, IBrush brush, float thickness, Vector2[] points, GraphicsOptions options) where TColor : struct, IPackedPixel, IEquatable { @@ -40,11 +38,11 @@ namespace ImageSharp /// Draws the provided Points as an open Linear path at the provided thickness with the supplied brush /// /// The type of the color. - /// The source. + /// The image this method extends. /// The brush. /// The thickness. /// The points. - /// The Image + /// The . public static Image DrawLines(this Image source, IBrush brush, float thickness, Vector2[] points) where TColor : struct, IPackedPixel, IEquatable { @@ -55,11 +53,11 @@ namespace ImageSharp /// Draws the provided Points as an open Linear path at the provided thickness with the supplied brush /// /// The type of the color. - /// The source. + /// The image this method extends. /// The color. /// The thickness. /// The points. - /// The Image + /// The . public static Image DrawLines(this Image source, TColor color, float thickness, Vector2[] points) where TColor : struct, IPackedPixel, IEquatable { @@ -70,14 +68,12 @@ namespace ImageSharp /// Draws the provided Points as an open Linear path at the provided thickness with the supplied brush /// /// The type of the color. - /// The source. + /// The image this method extends. /// The color. /// The thickness. /// The points. /// The options. - /// - /// The Image - /// + /// The .> public static Image DrawLines(this Image source, TColor color, float thickness, Vector2[] points, GraphicsOptions options) where TColor : struct, IPackedPixel, IEquatable { @@ -88,13 +84,11 @@ namespace ImageSharp /// Draws the provided Points as an open Linear path with the supplied pen /// /// The type of the color. - /// The source. + /// The image this method extends. /// The pen. /// The points. /// The options. - /// - /// The Image - /// + /// The . public static Image DrawLines(this Image source, IPen pen, Vector2[] points, GraphicsOptions options) where TColor : struct, IPackedPixel, IEquatable { @@ -105,10 +99,10 @@ namespace ImageSharp /// Draws the provided Points as an open Linear path with the supplied pen /// /// The type of the color. - /// The source. + /// The image this method extends. /// The pen. /// The points. - /// The Image + /// The . public static Image DrawLines(this Image source, IPen pen, Vector2[] points) where TColor : struct, IPackedPixel, IEquatable { diff --git a/src/ImageSharp.Drawing.Paths/DrawPath.cs b/src/ImageSharp.Drawing.Paths/DrawPath.cs index 201984e0ac..4e4275df6e 100644 --- a/src/ImageSharp.Drawing.Paths/DrawPath.cs +++ b/src/ImageSharp.Drawing.Paths/DrawPath.cs @@ -22,13 +22,11 @@ namespace ImageSharp /// Draws the outline of the polygon with the provided pen. /// /// The type of the color. - /// The source. + /// The image this method extends. /// The pen. /// The path. /// The options. - /// - /// The Image - /// + /// The . public static Image Draw(this Image source, IPen pen, IPath path, GraphicsOptions options) where TColor : struct, IPackedPixel, IEquatable { @@ -39,12 +37,10 @@ namespace ImageSharp /// Draws the outline of the polygon with the provided pen. /// /// The type of the color. - /// The source. + /// The image this method extends. /// The pen. /// The path. - /// - /// The Image - /// + /// The . public static Image Draw(this Image source, IPen pen, IPath path) where TColor : struct, IPackedPixel, IEquatable { @@ -55,14 +51,12 @@ namespace ImageSharp /// Draws the outline of the polygon with the provided brush at the provided thickness. /// /// The type of the color. - /// The source. + /// The image this method extends. /// The brush. /// The thickness. /// The shape. /// The options. - /// - /// The Image - /// + /// The . public static Image Draw(this Image source, IBrush brush, float thickness, IPath path, GraphicsOptions options) where TColor : struct, IPackedPixel, IEquatable { @@ -73,13 +67,11 @@ namespace ImageSharp /// Draws the outline of the polygon with the provided brush at the provided thickness. /// /// The type of the color. - /// The source. + /// The image this method extends. /// The brush. /// The thickness. /// The path. - /// - /// The Image - /// + /// The . public static Image Draw(this Image source, IBrush brush, float thickness, IPath path) where TColor : struct, IPackedPixel, IEquatable { @@ -90,14 +82,12 @@ namespace ImageSharp /// Draws the outline of the polygon with the provided brush at the provided thickness. /// /// The type of the color. - /// The source. + /// The image this method extends. /// The color. /// The thickness. /// The path. /// The options. - /// - /// The Image - /// + /// The . public static Image Draw(this Image source, TColor color, float thickness, IPath path, GraphicsOptions options) where TColor : struct, IPackedPixel, IEquatable { @@ -108,13 +98,11 @@ namespace ImageSharp /// Draws the outline of the polygon with the provided brush at the provided thickness. /// /// The type of the color. - /// The source. + /// The image this method extends. /// The color. /// The thickness. /// The path. - /// - /// The Image - /// + /// The . public static Image Draw(this Image source, TColor color, float thickness, IPath path) where TColor : struct, IPackedPixel, IEquatable { diff --git a/src/ImageSharp.Drawing.Paths/DrawPolygon.cs b/src/ImageSharp.Drawing.Paths/DrawPolygon.cs index 571b13c1ee..28785e5cbf 100644 --- a/src/ImageSharp.Drawing.Paths/DrawPolygon.cs +++ b/src/ImageSharp.Drawing.Paths/DrawPolygon.cs @@ -22,14 +22,12 @@ namespace ImageSharp /// Draws the provided Points as a closed Linear Polygon with the provided brush at the provided thickness. /// /// The type of the color. - /// The source. + /// The image this method extends. /// The brush. /// The thickness. /// The points. /// The options. - /// - /// The Image - /// + /// The . public static Image DrawPolygon(this Image source, IBrush brush, float thickness, Vector2[] points, GraphicsOptions options) where TColor : struct, IPackedPixel, IEquatable { @@ -40,11 +38,11 @@ namespace ImageSharp /// Draws the provided Points as a closed Linear Polygon with the provided brush at the provided thickness. /// /// The type of the color. - /// The source. + /// The image this method extends. /// The brush. /// The thickness. /// The points. - /// The Image + /// The . public static Image DrawPolygon(this Image source, IBrush brush, float thickness, Vector2[] points) where TColor : struct, IPackedPixel, IEquatable { @@ -55,11 +53,11 @@ namespace ImageSharp /// Draws the provided Points as a closed Linear Polygon with the provided brush at the provided thickness. /// /// The type of the color. - /// The source. + /// The image this method extends. /// The color. /// The thickness. /// The points. - /// The Image + /// The . public static Image DrawPolygon(this Image source, TColor color, float thickness, Vector2[] points) where TColor : struct, IPackedPixel, IEquatable { @@ -70,14 +68,12 @@ namespace ImageSharp /// Draws the provided Points as a closed Linear Polygon with the provided brush at the provided thickness. /// /// The type of the color. - /// The source. + /// The image this method extends. /// The color. /// The thickness. /// The points. /// The options. - /// - /// The Image - /// + /// The . public static Image DrawPolygon(this Image source, TColor color, float thickness, Vector2[] points, GraphicsOptions options) where TColor : struct, IPackedPixel, IEquatable { @@ -88,12 +84,10 @@ namespace ImageSharp /// Draws the provided Points as a closed Linear Polygon with the provided Pen. /// /// The type of the color. - /// The source. + /// The image this method extends. /// The pen. /// The points. - /// - /// The Image - /// + /// The . public static Image DrawPolygon(this Image source, IPen pen, Vector2[] points) where TColor : struct, IPackedPixel, IEquatable { @@ -104,13 +98,11 @@ namespace ImageSharp /// Draws the provided Points as a closed Linear Polygon with the provided Pen. /// /// The type of the color. - /// The source. + /// The image this method extends. /// The pen. /// The points. /// The options. - /// - /// The Image - /// + /// The . public static Image DrawPolygon(this Image source, IPen pen, Vector2[] points, GraphicsOptions options) where TColor : struct, IPackedPixel, IEquatable { diff --git a/src/ImageSharp.Drawing.Paths/DrawRectangle.cs b/src/ImageSharp.Drawing.Paths/DrawRectangle.cs index 28ea3d6e03..4e8ec7135b 100644 --- a/src/ImageSharp.Drawing.Paths/DrawRectangle.cs +++ b/src/ImageSharp.Drawing.Paths/DrawRectangle.cs @@ -23,13 +23,11 @@ namespace ImageSharp /// Draws the outline of the polygon with the provided pen. /// /// The type of the color. - /// The source. + /// The image this method extends. /// The pen. /// The shape. /// The options. - /// - /// The Image - /// + /// The . public static Image Draw(this Image source, IPen pen, Rectangle shape, GraphicsOptions options) where TColor : struct, IPackedPixel, IEquatable { @@ -40,10 +38,10 @@ namespace ImageSharp /// Draws the outline of the polygon with the provided pen. /// /// The type of the color. - /// The source. + /// The image this method extends. /// The pen. /// The shape. - /// The Image + /// The . public static Image Draw(this Image source, IPen pen, Rectangle shape) where TColor : struct, IPackedPixel, IEquatable { @@ -54,14 +52,12 @@ namespace ImageSharp /// Draws the outline of the polygon with the provided brush at the provided thickness. /// /// The type of the color. - /// The source. + /// The image this method extends. /// The brush. /// The thickness. /// The shape. /// The options. - /// - /// The Image - /// + /// The . public static Image Draw(this Image source, IBrush brush, float thickness, Rectangle shape, GraphicsOptions options) where TColor : struct, IPackedPixel, IEquatable { @@ -72,11 +68,11 @@ namespace ImageSharp /// Draws the outline of the polygon with the provided brush at the provided thickness. /// /// The type of the color. - /// The source. + /// The image this method extends. /// The brush. /// The thickness. /// The shape. - /// The Image + /// The . public static Image Draw(this Image source, IBrush brush, float thickness, Rectangle shape) where TColor : struct, IPackedPixel, IEquatable { @@ -87,14 +83,12 @@ namespace ImageSharp /// Draws the outline of the polygon with the provided brush at the provided thickness. /// /// The type of the color. - /// The source. + /// The image this method extends. /// The color. /// The thickness. /// The shape. /// The options. - /// - /// The Image - /// + /// The . public static Image Draw(this Image source, TColor color, float thickness, Rectangle shape, GraphicsOptions options) where TColor : struct, IPackedPixel, IEquatable { @@ -105,11 +99,11 @@ namespace ImageSharp /// Draws the outline of the polygon with the provided brush at the provided thickness. /// /// The type of the color. - /// The source. + /// The image this method extends. /// The color. /// The thickness. /// The shape. - /// The Image + /// The . public static Image Draw(this Image source, TColor color, float thickness, Rectangle shape) where TColor : struct, IPackedPixel, IEquatable { diff --git a/src/ImageSharp.Drawing.Paths/DrawShape.cs b/src/ImageSharp.Drawing.Paths/DrawShape.cs index 15ae2b1797..6ddce65b98 100644 --- a/src/ImageSharp.Drawing.Paths/DrawShape.cs +++ b/src/ImageSharp.Drawing.Paths/DrawShape.cs @@ -22,13 +22,11 @@ namespace ImageSharp /// Draws the outline of the polygon with the provided pen. /// /// The type of the color. - /// The source. + /// The image this method extends. /// The pen. /// The shape. /// The options. - /// - /// The Image - /// + /// The . public static Image Draw(this Image source, IPen pen, IShape shape, GraphicsOptions options) where TColor : struct, IPackedPixel, IEquatable { @@ -39,10 +37,10 @@ namespace ImageSharp /// Draws the outline of the polygon with the provided pen. /// /// The type of the color. - /// The source. + /// The image this method extends. /// The pen. /// The shape. - /// The Image + /// The . public static Image Draw(this Image source, IPen pen, IShape shape) where TColor : struct, IPackedPixel, IEquatable { @@ -53,14 +51,12 @@ namespace ImageSharp /// Draws the outline of the polygon with the provided brush at the provided thickness. /// /// The type of the color. - /// The source. + /// The image this method extends. /// The brush. /// The thickness. /// The shape. /// The options. - /// - /// The Image - /// + /// The . public static Image Draw(this Image source, IBrush brush, float thickness, IShape shape, GraphicsOptions options) where TColor : struct, IPackedPixel, IEquatable { @@ -71,11 +67,11 @@ namespace ImageSharp /// Draws the outline of the polygon with the provided brush at the provided thickness. /// /// The type of the color. - /// The source. + /// The image this method extends. /// The brush. /// The thickness. /// The shape. - /// The Image + /// The . public static Image Draw(this Image source, IBrush brush, float thickness, IShape shape) where TColor : struct, IPackedPixel, IEquatable { @@ -86,14 +82,12 @@ namespace ImageSharp /// Draws the outline of the polygon with the provided brush at the provided thickness. /// /// The type of the color. - /// The source. + /// The image this method extends. /// The color. /// The thickness. /// The shape. /// The options. - /// - /// The Image - /// + /// The . public static Image Draw(this Image source, TColor color, float thickness, IShape shape, GraphicsOptions options) where TColor : struct, IPackedPixel, IEquatable { @@ -104,11 +98,11 @@ namespace ImageSharp /// Draws the outline of the polygon with the provided brush at the provided thickness. /// /// The type of the color. - /// The source. + /// The image this method extends. /// The color. /// The thickness. /// The shape. - /// The Image + /// The . public static Image Draw(this Image source, TColor color, float thickness, IShape shape) where TColor : struct, IPackedPixel, IEquatable { diff --git a/src/ImageSharp.Drawing.Paths/FillPaths.cs b/src/ImageSharp.Drawing.Paths/FillPaths.cs index 3095ee7cdf..09a799794c 100644 --- a/src/ImageSharp.Drawing.Paths/FillPaths.cs +++ b/src/ImageSharp.Drawing.Paths/FillPaths.cs @@ -22,13 +22,11 @@ namespace ImageSharp /// Flood fills the image in the shape of the provided polygon with the specified brush.. /// /// The type of the color. - /// The source. + /// The image this method extends. /// The brush. /// The shape. /// The graphics options. - /// - /// The Image - /// + /// The . public static Image Fill(this Image source, IBrush brush, IPath path, GraphicsOptions options) where TColor : struct, IPackedPixel, IEquatable { @@ -39,12 +37,10 @@ namespace ImageSharp /// Flood fills the image in the shape of the provided polygon with the specified brush. /// /// The type of the color. - /// The source. + /// The image this method extends. /// The brush. /// The path. - /// - /// The Image - /// + /// The . public static Image Fill(this Image source, IBrush brush, IPath path) where TColor : struct, IPackedPixel, IEquatable { @@ -55,13 +51,11 @@ namespace ImageSharp /// Flood fills the image in the shape of the provided polygon with the specified brush.. /// /// The type of the color. - /// The source. + /// The image this method extends. /// The color. /// The path. /// The options. - /// - /// The Image - /// + /// The . public static Image Fill(this Image source, TColor color, IPath path, GraphicsOptions options) where TColor : struct, IPackedPixel, IEquatable { @@ -72,12 +66,10 @@ namespace ImageSharp /// Flood fills the image in the shape of the provided polygon with the specified brush.. /// /// The type of the color. - /// The source. + /// The image this method extends. /// The color. /// The path. - /// - /// The Image - /// + /// The . public static Image Fill(this Image source, TColor color, IPath path) where TColor : struct, IPackedPixel, IEquatable { diff --git a/src/ImageSharp.Drawing.Paths/FillPolygon.cs b/src/ImageSharp.Drawing.Paths/FillPolygon.cs index 4092e5fc64..a609ceed24 100644 --- a/src/ImageSharp.Drawing.Paths/FillPolygon.cs +++ b/src/ImageSharp.Drawing.Paths/FillPolygon.cs @@ -22,13 +22,11 @@ namespace ImageSharp /// Flood fills the image in the shape of a Linear polygon described by the points /// /// The type of the color. - /// The source. + /// The image this method extends. /// The brush. /// The points. /// The options. - /// - /// The Image - /// + /// The . public static Image FillPolygon(this Image source, IBrush brush, Vector2[] points, GraphicsOptions options) where TColor : struct, IPackedPixel, IEquatable { @@ -39,10 +37,10 @@ namespace ImageSharp /// Flood fills the image in the shape of a Linear polygon described by the points /// /// The type of the color. - /// The source. + /// The image this method extends. /// The brush. /// The points. - /// The Image + /// The . public static Image FillPolygon(this Image source, IBrush brush, Vector2[] points) where TColor : struct, IPackedPixel, IEquatable { @@ -53,13 +51,11 @@ namespace ImageSharp /// Flood fills the image in the shape of a Linear polygon described by the points /// /// The type of the color. - /// The source. + /// The image this method extends. /// The color. /// The points. /// The options. - /// - /// The Image - /// + /// The . public static Image FillPolygon(this Image source, TColor color, Vector2[] points, GraphicsOptions options) where TColor : struct, IPackedPixel, IEquatable { @@ -70,10 +66,10 @@ namespace ImageSharp /// Flood fills the image in the shape of a Linear polygon described by the points /// /// The type of the color. - /// The source. + /// The image this method extends. /// The color. /// The points. - /// The Image + /// The . public static Image FillPolygon(this Image source, TColor color, Vector2[] points) where TColor : struct, IPackedPixel, IEquatable { diff --git a/src/ImageSharp.Drawing.Paths/FillRectangle.cs b/src/ImageSharp.Drawing.Paths/FillRectangle.cs index aef777edd2..579a288a48 100644 --- a/src/ImageSharp.Drawing.Paths/FillRectangle.cs +++ b/src/ImageSharp.Drawing.Paths/FillRectangle.cs @@ -20,13 +20,11 @@ namespace ImageSharp /// Flood fills the image in the shape of the provided polygon with the specified brush.. /// /// The type of the color. - /// The source. + /// The image this method extends. /// The brush. /// The shape. /// The options. - /// - /// The Image - /// + /// The . public static Image Fill(this Image source, IBrush brush, Rectangle shape, GraphicsOptions options) where TColor : struct, IPackedPixel, IEquatable { @@ -37,10 +35,10 @@ namespace ImageSharp /// Flood fills the image in the shape of the provided polygon with the specified brush.. /// /// The type of the color. - /// The source. + /// The image this method extends. /// The brush. /// The shape. - /// The Image + /// The . public static Image Fill(this Image source, IBrush brush, Rectangle shape) where TColor : struct, IPackedPixel, IEquatable { @@ -51,13 +49,11 @@ namespace ImageSharp /// Flood fills the image in the shape of the provided polygon with the specified brush.. /// /// The type of the color. - /// The source. + /// The image this method extends. /// The color. /// The shape. /// The options. - /// - /// The Image - /// + /// The . public static Image Fill(this Image source, TColor color, Rectangle shape, GraphicsOptions options) where TColor : struct, IPackedPixel, IEquatable { @@ -68,10 +64,10 @@ namespace ImageSharp /// Flood fills the image in the shape of the provided polygon with the specified brush.. /// /// The type of the color. - /// The source. + /// The image this method extends. /// The color. /// The shape. - /// The Image + /// The . public static Image Fill(this Image source, TColor color, Rectangle shape) where TColor : struct, IPackedPixel, IEquatable { diff --git a/src/ImageSharp.Drawing.Paths/FillShape.cs b/src/ImageSharp.Drawing.Paths/FillShape.cs index 6e50f73771..52a3c60dd5 100644 --- a/src/ImageSharp.Drawing.Paths/FillShape.cs +++ b/src/ImageSharp.Drawing.Paths/FillShape.cs @@ -22,13 +22,11 @@ namespace ImageSharp /// Flood fills the image in the shape of the provided polygon with the specified brush.. /// /// The type of the color. - /// The source. + /// The image this method extends. /// The brush. /// The shape. /// The graphics options. - /// - /// The Image - /// + /// The . public static Image Fill(this Image source, IBrush brush, IShape shape, GraphicsOptions options) where TColor : struct, IPackedPixel, IEquatable { @@ -39,10 +37,10 @@ namespace ImageSharp /// Flood fills the image in the shape of the provided polygon with the specified brush. /// /// The type of the color. - /// The source. + /// The image this method extends. /// The brush. /// The shape. - /// The Image + /// The . public static Image Fill(this Image source, IBrush brush, IShape shape) where TColor : struct, IPackedPixel, IEquatable { @@ -53,13 +51,11 @@ namespace ImageSharp /// Flood fills the image in the shape of the provided polygon with the specified brush.. /// /// The type of the color. - /// The source. + /// The image this method extends. /// The color. /// The shape. /// The options. - /// - /// The Image - /// + /// The . public static Image Fill(this Image source, TColor color, IShape shape, GraphicsOptions options) where TColor : struct, IPackedPixel, IEquatable { @@ -70,10 +66,10 @@ namespace ImageSharp /// Flood fills the image in the shape of the provided polygon with the specified brush.. /// /// The type of the color. - /// The source. + /// The image this method extends. /// The color. /// The shape. - /// The Image + /// The . public static Image Fill(this Image source, TColor color, IShape shape) where TColor : struct, IPackedPixel, IEquatable { diff --git a/src/ImageSharp.Drawing.Paths/PointInfoExtensions.cs b/src/ImageSharp.Drawing.Paths/PointInfoExtensions.cs index ea38dfb9d3..18cd4e66c0 100644 --- a/src/ImageSharp.Drawing.Paths/PointInfoExtensions.cs +++ b/src/ImageSharp.Drawing.Paths/PointInfoExtensions.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Drawing /// /// Converts a to an ImageSharp . /// - /// The source. + /// The image this method extends. /// A representation of this public static PointInfo Convert(this SixLabors.Shapes.PointInfo source) { diff --git a/src/ImageSharp.Drawing.Paths/RectangleExtensions.cs b/src/ImageSharp.Drawing.Paths/RectangleExtensions.cs index 8369cc83f6..1faa6469ad 100644 --- a/src/ImageSharp.Drawing.Paths/RectangleExtensions.cs +++ b/src/ImageSharp.Drawing.Paths/RectangleExtensions.cs @@ -22,7 +22,7 @@ namespace ImageSharp.Drawing.Processors /// /// Converts a Shaper2D to an ImageSharp by creating a the entirely surrounds the source. /// - /// The source. + /// The image this method extends. /// A representation of this public static Rectangle Convert(this SixLabors.Shapes.Rectangle source) { diff --git a/src/ImageSharp.Drawing.Paths/ShapePath.cs b/src/ImageSharp.Drawing.Paths/ShapePath.cs index ee1f6e9d95..f2f07ea3cb 100644 --- a/src/ImageSharp.Drawing.Paths/ShapePath.cs +++ b/src/ImageSharp.Drawing.Paths/ShapePath.cs @@ -64,32 +64,13 @@ namespace ImageSharp.Drawing /// public ImmutableArray Paths { get; } - /// - /// Gets the maximum number of intersections to could be returned. - /// - /// - /// The maximum intersections. - /// + /// public override int MaxIntersections => this.shape.MaxIntersections; - /// - /// Gets the bounds. - /// - /// - /// The bounds. - /// + /// public override Rectangle Bounds { get; } - /// - /// Scans the X axis for intersections. - /// - /// The x. - /// The buffer. - /// The length. - /// The offset. - /// - /// The number of intersections found. - /// + /// public override int ScanX(int x, float[] buffer, int length, int offset) { Vector2 start = new Vector2(x, this.Bounds.Top - 1); @@ -117,16 +98,7 @@ namespace ImageSharp.Drawing } } - /// - /// Scans the Y axis for intersections. - /// - /// The position along the y axis to find intersections. - /// The buffer. - /// The length. - /// The offset. - /// - /// The number of intersections found. - /// + /// public override int ScanY(int y, float[] buffer, int length, int offset) { Vector2 start = new Vector2(float.MinValue, y); @@ -154,12 +126,7 @@ namespace ImageSharp.Drawing } } - /// - /// Gets the point information for the specified x and y location. - /// - /// The x. - /// The y. - /// Information about the the point + /// public override PointInfo GetPointInfo(int x, int y) { Vector2 point = new Vector2(x, y); diff --git a/src/ImageSharp.Drawing.Paths/ShapeRegion.cs b/src/ImageSharp.Drawing.Paths/ShapeRegion.cs index b6921f16ee..a1fdd7b8c0 100644 --- a/src/ImageSharp.Drawing.Paths/ShapeRegion.cs +++ b/src/ImageSharp.Drawing.Paths/ShapeRegion.cs @@ -44,32 +44,13 @@ namespace ImageSharp.Drawing /// public IShape Shape { get; } - /// - /// Gets the maximum number of intersections to could be returned. - /// - /// - /// The maximum intersections. - /// + /// public override int MaxIntersections => this.Shape.MaxIntersections; - /// - /// Gets the bounds. - /// - /// - /// The bounds. - /// + /// public override Rectangle Bounds { get; } - /// - /// Scans the X axis for intersections. - /// - /// The x. - /// The buffer. - /// The length. - /// The offset. - /// - /// The number of intersections found. - /// + /// public override int ScanX(int x, float[] buffer, int length, int offset) { Vector2 start = new Vector2(x, this.Bounds.Top - 1); @@ -97,16 +78,7 @@ namespace ImageSharp.Drawing } } - /// - /// Scans the Y axis for intersections. - /// - /// The position along the y axis to find intersections. - /// The buffer. - /// The length. - /// The offset. - /// - /// The number of intersections found. - /// + /// public override int ScanY(int y, float[] buffer, int length, int offset) { Vector2 start = new Vector2(float.MinValue, y); diff --git a/src/ImageSharp.Drawing/DrawPath.cs b/src/ImageSharp.Drawing/DrawPath.cs index fe833e3af9..d92731270f 100644 --- a/src/ImageSharp.Drawing/DrawPath.cs +++ b/src/ImageSharp.Drawing/DrawPath.cs @@ -21,13 +21,11 @@ namespace ImageSharp /// Draws the outline of the region with the provided pen. /// /// The type of the color. - /// The source. + /// The image this method extends. /// The pen. /// The path. /// The options. - /// - /// The Image - /// + /// The . public static Image Draw(this Image source, IPen pen, Drawable path, GraphicsOptions options) where TColor : struct, IPackedPixel, IEquatable { @@ -38,12 +36,10 @@ namespace ImageSharp /// Draws the outline of the polygon with the provided pen. /// /// The type of the color. - /// The source. + /// The image this method extends. /// The pen. /// The path. - /// - /// The Image - /// + /// The . public static Image Draw(this Image source, IPen pen, Drawable path) where TColor : struct, IPackedPixel, IEquatable { @@ -54,14 +50,12 @@ namespace ImageSharp /// Draws the outline of the polygon with the provided brush at the provided thickness. /// /// The type of the color. - /// The source. + /// The image this method extends. /// The brush. /// The thickness. /// The path. /// The options. - /// - /// The Image - /// + /// The . public static Image Draw(this Image source, IBrush brush, float thickness, Drawable path, GraphicsOptions options) where TColor : struct, IPackedPixel, IEquatable { @@ -72,13 +66,11 @@ namespace ImageSharp /// Draws the outline of the polygon with the provided brush at the provided thickness. /// /// The type of the color. - /// The source. + /// The image this method extends. /// The brush. /// The thickness. /// The path. - /// - /// The Image - /// + /// The . public static Image Draw(this Image source, IBrush brush, float thickness, Drawable path) where TColor : struct, IPackedPixel, IEquatable { @@ -89,14 +81,12 @@ namespace ImageSharp /// Draws the outline of the polygon with the provided brush at the provided thickness. /// /// The type of the color. - /// The source. + /// The image this method extends. /// The color. /// The thickness. /// The path. /// The options. - /// - /// The Image - /// + /// The . public static Image Draw(this Image source, TColor color, float thickness, Drawable path, GraphicsOptions options) where TColor : struct, IPackedPixel, IEquatable { @@ -107,13 +97,11 @@ namespace ImageSharp /// Draws the outline of the polygon with the provided brush at the provided thickness. /// /// The type of the color. - /// The source. + /// The image this method extends. /// The color. /// The thickness. /// The path. - /// - /// The Image - /// + /// The . public static Image Draw(this Image source, TColor color, float thickness, Drawable path) where TColor : struct, IPackedPixel, IEquatable { diff --git a/src/ImageSharp.Drawing/Drawable.cs b/src/ImageSharp.Drawing/Drawable.cs index b818273313..62f5e62c14 100644 --- a/src/ImageSharp.Drawing/Drawable.cs +++ b/src/ImageSharp.Drawing/Drawable.cs @@ -13,17 +13,11 @@ namespace ImageSharp.Drawing /// /// Gets the maximum number of intersections to could be returned. /// - /// - /// The maximum intersections. - /// public abstract int MaxIntersections { get; } /// /// Gets the bounds. /// - /// - /// The bounds. - /// public abstract Rectangle Bounds { get; } /// diff --git a/src/ImageSharp.Drawing/FillRegion.cs b/src/ImageSharp.Drawing/FillRegion.cs index 8d4f20b673..c634d6e64b 100644 --- a/src/ImageSharp.Drawing/FillRegion.cs +++ b/src/ImageSharp.Drawing/FillRegion.cs @@ -20,9 +20,9 @@ namespace ImageSharp /// Flood fills the image with the specified brush. /// /// The type of the color. - /// The source. - /// The brush. - /// The Image + /// The image this method extends. + /// The details how to fill the region of interest. + /// The . public static Image Fill(this Image source, IBrush brush) where TColor : struct, IPackedPixel, IEquatable { @@ -33,9 +33,9 @@ namespace ImageSharp /// Flood fills the image with the specified color. /// /// The type of the color. - /// The source. + /// The image this method extends. /// The color. - /// The Image + /// The . public static Image Fill(this Image source, TColor color) where TColor : struct, IPackedPixel, IEquatable { @@ -46,7 +46,7 @@ namespace ImageSharp /// Flood fills the image with in the region with the specified brush. /// /// The type of the color. - /// The source. + /// The image this method extends. /// The brush. /// The region. /// The graphics options. @@ -63,7 +63,7 @@ namespace ImageSharp /// Flood fills the image with in the region with the specified brush. /// /// The type of the color. - /// The source. + /// The image this method extends. /// The brush. /// The region. /// @@ -79,7 +79,7 @@ namespace ImageSharp /// Flood fills the image with in the region with the specified color. /// /// The type of the color. - /// The source. + /// The image this method extends. /// The color. /// The region. /// The options. @@ -96,7 +96,7 @@ namespace ImageSharp /// Flood fills the image with in the region with the specified color. /// /// The type of the color. - /// The source. + /// The image this method extends. /// The color. /// The region. /// diff --git a/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs b/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs index 53f0408d47..b4aa7fba5a 100644 --- a/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs @@ -28,12 +28,12 @@ namespace ImageSharp.Drawing.Processors /// /// Initializes a new instance of the class. /// - /// The pen. - /// The region. - /// The options. - public DrawPathProcessor(IPen pen, Drawable region, GraphicsOptions options) + /// The details how to draw the outline/path. + /// The details of the paths and outlines to draw. + /// The drawing configuration options. + public DrawPathProcessor(IPen pen, Drawable drawable, GraphicsOptions options) { - this.Path = region; + this.Path = drawable; this.Pen = pen; this.Options = options; } diff --git a/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs b/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs index 6719d365a9..67c3ad176c 100644 --- a/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs @@ -26,9 +26,9 @@ namespace ImageSharp.Drawing.Processors /// /// Initializes a new instance of the class. /// - /// The brush. - /// The region. - /// The options. + /// The details how to fill the region of interest. + /// The region of interest to be filled. + /// The configuration options. public FillRegionProcessor(IBrush brush, Region region, GraphicsOptions options) { this.Region = region; diff --git a/src/ImageSharp.Drawing/Region.cs b/src/ImageSharp.Drawing/Region.cs index 95f0e17480..81f3dca5bd 100644 --- a/src/ImageSharp.Drawing/Region.cs +++ b/src/ImageSharp.Drawing/Region.cs @@ -13,23 +13,20 @@ namespace ImageSharp.Drawing /// /// Gets the maximum number of intersections to could be returned. /// - /// - /// The maximum intersections. - /// public abstract int MaxIntersections { get; } /// - /// Gets the bounds. + /// Gets the bounding box that entirly surrounds this region. /// - /// - /// The bounds. - /// + /// + /// This should always contains all possible points returned from eather or . + /// public abstract Rectangle Bounds { get; } /// /// Scans the X axis for intersections. /// - /// The x. + /// The position along the X axies to find intersections. /// The buffer. /// The length. /// The offset. From ae5ec4ccf1c9b3ce133637325f1e57a990e70208 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Fri, 3 Feb 2017 10:26:50 +0000 Subject: [PATCH 026/142] xml doc fixes --- src/ImageSharp.Drawing/FillRegion.cs | 16 ++++------------ .../Processors/FillRegionProcessor.cs | 8 +------- 2 files changed, 5 insertions(+), 19 deletions(-) diff --git a/src/ImageSharp.Drawing/FillRegion.cs b/src/ImageSharp.Drawing/FillRegion.cs index c634d6e64b..fbbf659d1c 100644 --- a/src/ImageSharp.Drawing/FillRegion.cs +++ b/src/ImageSharp.Drawing/FillRegion.cs @@ -50,9 +50,7 @@ namespace ImageSharp /// The brush. /// The region. /// The graphics options. - /// - /// The Image - /// + /// The . public static Image Fill(this Image source, IBrush brush, Region region, GraphicsOptions options) where TColor : struct, IPackedPixel, IEquatable { @@ -66,9 +64,7 @@ namespace ImageSharp /// The image this method extends. /// The brush. /// The region. - /// - /// The Image - /// + /// The . public static Image Fill(this Image source, IBrush brush, Region region) where TColor : struct, IPackedPixel, IEquatable { @@ -83,9 +79,7 @@ namespace ImageSharp /// The color. /// The region. /// The options. - /// - /// The Image - /// + /// The . public static Image Fill(this Image source, TColor color, Region region, GraphicsOptions options) where TColor : struct, IPackedPixel, IEquatable { @@ -99,9 +93,7 @@ namespace ImageSharp /// The image this method extends. /// The color. /// The region. - /// - /// The Image - /// + /// The . public static Image Fill(this Image source, TColor color, Region region) where TColor : struct, IPackedPixel, IEquatable { diff --git a/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs b/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs index 67c3ad176c..6a37a1ae5b 100644 --- a/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs @@ -39,17 +39,11 @@ namespace ImageSharp.Drawing.Processors /// /// Gets the brush. /// - /// - /// The brush. - /// public IBrush Brush { get; } /// - /// Gets the region. + /// Gets the region that this processor applies to. /// - /// - /// The region. - /// public Region Region { get; } /// From 7335f52ab161e35d33819cf7f53e045d6bb465f8 Mon Sep 17 00:00:00 2001 From: Dirk Lemstra Date: Fri, 3 Feb 2017 11:29:31 +0100 Subject: [PATCH 027/142] Removed obsolete IImageFrame. --- src/ImageSharp/Image/IImageFrame{TColor}.cs | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 src/ImageSharp/Image/IImageFrame{TColor}.cs diff --git a/src/ImageSharp/Image/IImageFrame{TColor}.cs b/src/ImageSharp/Image/IImageFrame{TColor}.cs deleted file mode 100644 index 6ebda36c81..0000000000 --- a/src/ImageSharp/Image/IImageFrame{TColor}.cs +++ /dev/null @@ -1,18 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - using System; - - /// - /// Represents a single frame in a animation. - /// - /// The pixel format. - public interface IImageFrame : IImageBase - where TColor : struct, IPackedPixel, IEquatable - { - } -} From 6792f46b729cb378dfb6495e189192327a4a0aa9 Mon Sep 17 00:00:00 2001 From: Dirk Lemstra Date: Fri, 3 Feb 2017 11:32:15 +0100 Subject: [PATCH 028/142] Moved IImageBase to a separate file. --- src/ImageSharp/Image/IImageBase.cs | 56 ++++++++++++++++++++++ src/ImageSharp/Image/IImageBase{TColor}.cs | 49 ------------------- 2 files changed, 56 insertions(+), 49 deletions(-) create mode 100644 src/ImageSharp/Image/IImageBase.cs diff --git a/src/ImageSharp/Image/IImageBase.cs b/src/ImageSharp/Image/IImageBase.cs new file mode 100644 index 0000000000..4f9425a1ae --- /dev/null +++ b/src/ImageSharp/Image/IImageBase.cs @@ -0,0 +1,56 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// Encapsulates the basic properties and methods required to manipulate images. + /// + public interface IImageBase + { + /// + /// Gets the representing the bounds of the image. + /// + Rectangle Bounds { get; } + + /// + /// Gets or sets the quality of the image. This affects the output quality of lossy image formats. + /// + int Quality { get; set; } + + /// + /// Gets or sets the frame delay for animated images. + /// If not 0, this field specifies the number of hundredths (1/100) of a second to + /// wait before continuing with the processing of the Data Stream. + /// The clock starts ticking immediately after the graphic is rendered. + /// + int FrameDelay { get; set; } + + /// + /// Gets or sets the maximum allowable width in pixels. + /// + int MaxWidth { get; set; } + + /// + /// Gets or sets the maximum allowable height in pixels. + /// + int MaxHeight { get; set; } + + /// + /// Gets the width in pixels. + /// + int Width { get; } + + /// + /// Gets the height in pixels. + /// + int Height { get; } + + /// + /// Gets the pixel ratio made up of the width and height. + /// + double PixelRatio { get; } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Image/IImageBase{TColor}.cs b/src/ImageSharp/Image/IImageBase{TColor}.cs index 66746c9935..f01a4b7028 100644 --- a/src/ImageSharp/Image/IImageBase{TColor}.cs +++ b/src/ImageSharp/Image/IImageBase{TColor}.cs @@ -40,53 +40,4 @@ namespace ImageSharp /// The PixelAccessor Lock(); } - - /// - /// Encapsulates the basic properties and methods required to manipulate images. - /// - public interface IImageBase - { - /// - /// Gets the representing the bounds of the image. - /// - Rectangle Bounds { get; } - - /// - /// Gets or sets the quality of the image. This affects the output quality of lossy image formats. - /// - int Quality { get; set; } - - /// - /// Gets or sets the frame delay for animated images. - /// If not 0, this field specifies the number of hundredths (1/100) of a second to - /// wait before continuing with the processing of the Data Stream. - /// The clock starts ticking immediately after the graphic is rendered. - /// - int FrameDelay { get; set; } - - /// - /// Gets or sets the maximum allowable width in pixels. - /// - int MaxWidth { get; set; } - - /// - /// Gets or sets the maximum allowable height in pixels. - /// - int MaxHeight { get; set; } - - /// - /// Gets the width in pixels. - /// - int Width { get; } - - /// - /// Gets the height in pixels. - /// - int Height { get; } - - /// - /// Gets the pixel ratio made up of the width and height. - /// - double PixelRatio { get; } - } } \ No newline at end of file From 6303d97ba958597f116030323f6dfc0d62d3ad1c Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Fri, 3 Feb 2017 10:32:54 +0000 Subject: [PATCH 029/142] xml doc fixes --- .../Brushes/Processors/BrushApplicator.cs | 12 ++++-------- .../Pens/Processors/PenApplicator.cs | 6 ++---- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs b/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs index 97d3f840cb..37af8cd046 100644 --- a/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs +++ b/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs @@ -19,16 +19,12 @@ namespace ImageSharp.Drawing.Processors /// /// Gets the color for a single pixel. /// - /// The x. - /// The y. - /// - /// The color - /// + /// The x cordinate. + /// The y cordinate. + /// The a that should be applied to the pixel. public abstract TColor this[int x, int y] { get; } - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// + /// public abstract void Dispose(); } } diff --git a/src/ImageSharp.Drawing/Pens/Processors/PenApplicator.cs b/src/ImageSharp.Drawing/Pens/Processors/PenApplicator.cs index de680c809c..cba4d9fdc5 100644 --- a/src/ImageSharp.Drawing/Pens/Processors/PenApplicator.cs +++ b/src/ImageSharp.Drawing/Pens/Processors/PenApplicator.cs @@ -22,10 +22,8 @@ namespace ImageSharp.Drawing.Processors /// The required region. /// public abstract RectangleF RequiredRegion { get; } - - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// + + /// public abstract void Dispose(); /// From 7f082f91ffc1c39e324c3d56d9bb5d28a1611501 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Fri, 3 Feb 2017 10:57:00 +0000 Subject: [PATCH 030/142] fix stylecop issue --- src/ImageSharp.Drawing/Pens/Processors/PenApplicator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp.Drawing/Pens/Processors/PenApplicator.cs b/src/ImageSharp.Drawing/Pens/Processors/PenApplicator.cs index cba4d9fdc5..86283b5bb6 100644 --- a/src/ImageSharp.Drawing/Pens/Processors/PenApplicator.cs +++ b/src/ImageSharp.Drawing/Pens/Processors/PenApplicator.cs @@ -22,7 +22,7 @@ namespace ImageSharp.Drawing.Processors /// The required region. /// public abstract RectangleF RequiredRegion { get; } - + /// public abstract void Dispose(); From 19a6c3a560a985d6f1a0506b3da1670434d785d9 Mon Sep 17 00:00:00 2001 From: Dirk Lemstra Date: Fri, 3 Feb 2017 12:10:52 +0100 Subject: [PATCH 031/142] Corrected header. --- src/ImageSharp/Image/IImageBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Image/IImageBase.cs b/src/ImageSharp/Image/IImageBase.cs index 4f9425a1ae..113b94a186 100644 --- a/src/ImageSharp/Image/IImageBase.cs +++ b/src/ImageSharp/Image/IImageBase.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // From 2f8f8bd2c505236039fcbf2b2ddf36db12fa9bc4 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Fri, 3 Feb 2017 11:29:08 +0000 Subject: [PATCH 032/142] reduce calls --- .../PointInfoExtensions.cs | 27 ------------------- src/ImageSharp.Drawing.Paths/ShapePath.cs | 16 ++++++----- 2 files changed, 10 insertions(+), 33 deletions(-) delete mode 100644 src/ImageSharp.Drawing.Paths/PointInfoExtensions.cs diff --git a/src/ImageSharp.Drawing.Paths/PointInfoExtensions.cs b/src/ImageSharp.Drawing.Paths/PointInfoExtensions.cs deleted file mode 100644 index 18cd4e66c0..0000000000 --- a/src/ImageSharp.Drawing.Paths/PointInfoExtensions.cs +++ /dev/null @@ -1,27 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Drawing -{ - /// - /// Extension methods for helping to bridge Shaper2D and ImageSharp primitives. - /// - internal static class PointInfoExtensions - { - /// - /// Converts a to an ImageSharp . - /// - /// The image this method extends. - /// A representation of this - public static PointInfo Convert(this SixLabors.Shapes.PointInfo source) - { - return new PointInfo - { - DistanceAlongPath = source.DistanceAlongPath, - DistanceFromPath = source.DistanceFromPath - }; - } - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing.Paths/ShapePath.cs b/src/ImageSharp.Drawing.Paths/ShapePath.cs index f2f07ea3cb..8c9500e55f 100644 --- a/src/ImageSharp.Drawing.Paths/ShapePath.cs +++ b/src/ImageSharp.Drawing.Paths/ShapePath.cs @@ -130,20 +130,24 @@ namespace ImageSharp.Drawing public override PointInfo GetPointInfo(int x, int y) { Vector2 point = new Vector2(x, y); - SixLabors.Shapes.PointInfo result = default(SixLabors.Shapes.PointInfo); - float distance = float.MaxValue; + float distanceFromPath = float.MaxValue; + float distanceAlongPath = 0; for (int i = 0; i < this.Paths.Length; i++) { SixLabors.Shapes.PointInfo p = this.Paths[i].Distance(point); - if (p.DistanceFromPath < distance) + if (p.DistanceFromPath < distanceFromPath) { - distance = p.DistanceFromPath; - result = p; + distanceFromPath = p.DistanceFromPath; + distanceAlongPath = p.DistanceAlongPath; } } - return result.Convert(); + return new PointInfo + { + DistanceAlongPath = distanceAlongPath, + DistanceFromPath = distanceFromPath + }; } } } From 3d8e86cb3ac8eaab31297566640a1a81222c3450 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Fri, 3 Feb 2017 11:36:37 +0000 Subject: [PATCH 033/142] remove old test --- .../ImageSharp.Tests/Drawing/Paths/Extensions.cs | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/tests/ImageSharp.Tests/Drawing/Paths/Extensions.cs b/tests/ImageSharp.Tests/Drawing/Paths/Extensions.cs index 37720253db..a18dd90bc2 100644 --- a/tests/ImageSharp.Tests/Drawing/Paths/Extensions.cs +++ b/tests/ImageSharp.Tests/Drawing/Paths/Extensions.cs @@ -16,22 +16,6 @@ namespace ImageSharp.Tests.Drawing.Paths public class Extensions { - [Fact] - public void ConvertPointInfo() - { - SixLabors.Shapes.PointInfo src = new SixLabors.Shapes.PointInfo - { - ClosestPointOnPath = Vector2.UnitX, - SearchPoint = Vector2.UnitY, - DistanceAlongPath = 99f, - DistanceFromPath = 82f - }; - ImageSharp.Drawing.PointInfo info = src.Convert(); - - Assert.Equal(src.DistanceAlongPath, info.DistanceAlongPath); - Assert.Equal(src.DistanceFromPath, info.DistanceFromPath); - } - [Theory] [InlineData(0.5, 0.5, 5, 5, 0,0,6,6)] [InlineData(1, 1, 5, 5, 1,1,5,5)] From 06ff61040d1fb6a20664c55ad5db3e1a825f593d Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Fri, 3 Feb 2017 19:39:40 +0000 Subject: [PATCH 034/142] fixes some corner clipping issues --- src/ImageSharp.Drawing.Paths/ShapePath.cs | 4 +- src/ImageSharp.Drawing.Paths/ShapeRegion.cs | 4 +- src/ImageSharp.Drawing.Paths/project.json | 2 +- .../Processors/FillRegionProcessor.cs | 16 +++- .../Drawing/SolidPolygonTests.cs | 76 ++++++++++++++++++- 5 files changed, 95 insertions(+), 7 deletions(-) diff --git a/src/ImageSharp.Drawing.Paths/ShapePath.cs b/src/ImageSharp.Drawing.Paths/ShapePath.cs index 8c9500e55f..34e77b8074 100644 --- a/src/ImageSharp.Drawing.Paths/ShapePath.cs +++ b/src/ImageSharp.Drawing.Paths/ShapePath.cs @@ -101,8 +101,8 @@ namespace ImageSharp.Drawing /// public override int ScanY(int y, float[] buffer, int length, int offset) { - Vector2 start = new Vector2(float.MinValue, y); - Vector2 end = new Vector2(float.MaxValue, y); + Vector2 start = new Vector2(this.Bounds.Left - 1, y); + Vector2 end = new Vector2(this.Bounds.Right + 1, y); Vector2[] innerbuffer = ArrayPool.Shared.Rent(length); try { diff --git a/src/ImageSharp.Drawing.Paths/ShapeRegion.cs b/src/ImageSharp.Drawing.Paths/ShapeRegion.cs index a1fdd7b8c0..5adec67908 100644 --- a/src/ImageSharp.Drawing.Paths/ShapeRegion.cs +++ b/src/ImageSharp.Drawing.Paths/ShapeRegion.cs @@ -81,8 +81,8 @@ namespace ImageSharp.Drawing /// public override int ScanY(int y, float[] buffer, int length, int offset) { - Vector2 start = new Vector2(float.MinValue, y); - Vector2 end = new Vector2(float.MaxValue, y); + Vector2 start = new Vector2(this.Bounds.Left - 1, y); + Vector2 end = new Vector2(this.Bounds.Right + 1, y); Vector2[] innerbuffer = ArrayPool.Shared.Rent(length); try { diff --git a/src/ImageSharp.Drawing.Paths/project.json b/src/ImageSharp.Drawing.Paths/project.json index a186f362c7..1bd998ea79 100644 --- a/src/ImageSharp.Drawing.Paths/project.json +++ b/src/ImageSharp.Drawing.Paths/project.json @@ -44,7 +44,7 @@ "ImageSharp.Drawing": { "target": "project" }, - "SixLabors.Shapes": "0.1.0-alpha0002", + "SixLabors.Shapes": "0.1.0-alpha0003", "StyleCop.Analyzers": { "version": "1.0.0", "type": "build" diff --git a/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs b/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs index 6a37a1ae5b..4f1ee34ac1 100644 --- a/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs @@ -57,7 +57,7 @@ namespace ImageSharp.Drawing.Processors /// protected override void OnApply(ImageBase source, Rectangle sourceRectangle) { - Rectangle rect = RectangleF.Ceiling(this.Region.Bounds); // rounds the points out away from the center + Rectangle rect = this.Region.Bounds; int polyStartY = sourceRectangle.Y - DrawPadding; int polyEndY = sourceRectangle.Bottom + DrawPadding; @@ -102,6 +102,13 @@ namespace ImageSharp.Drawing.Processors return; } + if (pointsFound == 1 && maxIntersections > 1) + { + // we must have clipped a corner lets just duplicate it into point 2 and continue :) + buffer[1] = buffer[0]; + pointsFound++; + } + if (pointsFound % 2 == 1) { // we seem to have just clipped a corner lets just skip it @@ -246,6 +253,13 @@ namespace ImageSharp.Drawing.Processors return; } + if (pointsFound == 1 && maxIntersections > 1) + { + // we must have clipped a corner lets just duplicate it into point 2 and continue :) + buffer[1] = buffer[0]; + pointsFound++; + } + if (pointsFound % 2 == 1) { // we seem to have just clipped a corner lets just skip it diff --git a/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs b/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs index 5533fbc0a5..a41afd3334 100644 --- a/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs +++ b/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs @@ -13,6 +13,7 @@ namespace ImageSharp.Tests.Drawing using System.Numerics; using Xunit; using ImageSharp.Drawing.Brushes; + using SixLabors.Shapes; public class SolidPolygonTests : FileTestBase { @@ -151,7 +152,7 @@ namespace ImageSharp.Tests.Drawing { image .BackgroundColor(Color.Blue) - .Fill(Color.HotPink, new SixLabors.Shapes.Rectangle(10,10, 190, 140)) + .Fill(Color.HotPink, new SixLabors.Shapes.Rectangle(10, 10, 190, 140)) .Save(output); } @@ -169,5 +170,78 @@ namespace ImageSharp.Tests.Drawing } } } + + [Fact] + public void ImageShouldBeOverlayedByFilledTriangle() + { + string path = this.CreateOutputDirectory("Drawing", "FilledPolygons"); + + using (Image image = new Image(100, 100)) + { + using (FileStream output = File.OpenWrite($"{path}/Triangle.png")) + { + image + .BackgroundColor(Color.Blue) + .Fill(Color.HotPink, new RegularPolygon(50, 50, 3, 30)) + .Save(output); + } + + using (PixelAccessor sourcePixels = image.Lock()) + { + Assert.Equal(Color.HotPink, sourcePixels[25, 35]); + + Assert.Equal(Color.HotPink, sourcePixels[50, 79]); + + Assert.Equal(Color.HotPink, sourcePixels[75, 35]); + + Assert.Equal(Color.HotPink, sourcePixels[50, 50]); + + Assert.Equal(Color.Blue, sourcePixels[2, 2]); + + Assert.Equal(Color.Blue, sourcePixels[28, 60]); + + Assert.Equal(Color.Blue, sourcePixels[67, 67]); + } + } + } + + [Fact] + public void ImageShouldBeOverlayedByFilledSeptagon() + { + string path = this.CreateOutputDirectory("Drawing", "FilledPolygons"); + + var config = Configuration.CreateDefaultInstance(); + config.ParallelOptions.MaxDegreeOfParallelism = 1; + using (Image image = new Image(100, 100, config)) + { + using (FileStream output = File.OpenWrite($"{path}/Septagon.png")) + { + image + .BackgroundColor(Color.Blue) + .Fill(Color.HotPink, new RegularPolygon(50, 50, 7, 30, -(float)Math.PI)) + .Save(output); + } + } + } + + [Fact] + public void ImageShouldBeOverlayedByFilledEllipse() + { + string path = this.CreateOutputDirectory("Drawing", "FilledPolygons"); + + var config = Configuration.CreateDefaultInstance(); + config.ParallelOptions.MaxDegreeOfParallelism = 1; + using (Image image = new Image(100, 100, config)) + { + using (FileStream output = File.OpenWrite($"{path}/ellipse.png")) + { + image + .BackgroundColor(Color.Blue) + .Fill(Color.HotPink, new Ellipse(50, 50, 30, 50) + .Rotate((float)(Math.PI / 3))) + .Save(output); + } + } + } } } From 7a799fe3111cfc488f0179b2930ddba8b509cedd Mon Sep 17 00:00:00 2001 From: Dirk Lemstra Date: Sun, 5 Feb 2017 11:44:30 +0100 Subject: [PATCH 035/142] Moved the meta data of the image to a new MetaData property. --- ImageSharp.ruleset | 1 + src/ImageSharp.Formats.Gif/GifDecoderCore.cs | 4 +- src/ImageSharp.Formats.Gif/GifEncoderCore.cs | 4 +- .../JpegDecoderCore.cs | 6 +- src/ImageSharp.Formats.Jpeg/JpegEncoder.cs | 4 +- .../JpegEncoderCore.cs | 4 +- src/ImageSharp.Formats.Png/PngDecoderCore.cs | 8 +- src/ImageSharp.Formats.Png/PngEncoderCore.cs | 12 +- .../Transforms/AutoOrient.cs | 6 +- src/ImageSharp/Image/IImage.cs | 25 ++++ src/ImageSharp/Image/IImageBase.cs | 10 +- src/ImageSharp/Image/ImageBase{TColor}.cs | 14 +- src/ImageSharp/Image/ImageFrame{TColor}.cs | 7 +- src/ImageSharp/Image/Image{TColor}.cs | 113 +++------------ src/ImageSharp/Metadata/ImageMetaData.cs | 130 ++++++++++++++++++ .../{Image => Metadata}/ImageProperty.cs | 16 +++ .../Profiles/Exif/ExifDataType.cs | 0 .../{ => Metadata}/Profiles/Exif/ExifParts.cs | 0 .../Profiles/Exif/ExifProfile.cs | 0 .../Profiles/Exif/ExifReader.cs | 0 .../{ => Metadata}/Profiles/Exif/ExifTag.cs | 0 .../Exif/ExifTagDescriptionAttribute.cs | 0 .../{ => Metadata}/Profiles/Exif/ExifValue.cs | 0 .../Profiles/Exif/ExifWriter.cs | 0 .../{ => Metadata}/Profiles/Exif/README.md | 0 .../Formats/GeneralFormatTests.cs | 4 +- .../Formats/Jpg/JpegEncoderTests.cs | 4 +- .../ImageSharp.Tests/Formats/Png/PngTests.cs | 2 +- .../{Image => Metadata}/ImagePropertyTests.cs | 0 .../Profiles/Exif/ExifProfileTests.cs | 90 ++++++------ .../Exif/ExifTagDescriptionAttributeTests.cs | 0 .../Profiles/Exif/ExifValueTests.cs | 2 +- .../Processors/Filters/AutoOrientTests.cs | 4 +- 33 files changed, 282 insertions(+), 188 deletions(-) create mode 100644 src/ImageSharp/Image/IImage.cs create mode 100644 src/ImageSharp/Metadata/ImageMetaData.cs rename src/ImageSharp/{Image => Metadata}/ImageProperty.cs (91%) rename src/ImageSharp/{ => Metadata}/Profiles/Exif/ExifDataType.cs (100%) rename src/ImageSharp/{ => Metadata}/Profiles/Exif/ExifParts.cs (100%) rename src/ImageSharp/{ => Metadata}/Profiles/Exif/ExifProfile.cs (100%) rename src/ImageSharp/{ => Metadata}/Profiles/Exif/ExifReader.cs (100%) rename src/ImageSharp/{ => Metadata}/Profiles/Exif/ExifTag.cs (100%) rename src/ImageSharp/{ => Metadata}/Profiles/Exif/ExifTagDescriptionAttribute.cs (100%) rename src/ImageSharp/{ => Metadata}/Profiles/Exif/ExifValue.cs (100%) rename src/ImageSharp/{ => Metadata}/Profiles/Exif/ExifWriter.cs (100%) rename src/ImageSharp/{ => Metadata}/Profiles/Exif/README.md (100%) rename tests/ImageSharp.Tests/{Image => Metadata}/ImagePropertyTests.cs (100%) rename tests/ImageSharp.Tests/{ => Metadata}/Profiles/Exif/ExifProfileTests.cs (68%) rename tests/ImageSharp.Tests/{ => Metadata}/Profiles/Exif/ExifTagDescriptionAttributeTests.cs (100%) rename tests/ImageSharp.Tests/{ => Metadata}/Profiles/Exif/ExifValueTests.cs (96%) diff --git a/ImageSharp.ruleset b/ImageSharp.ruleset index 554dc16dd8..2daf6243ae 100644 --- a/ImageSharp.ruleset +++ b/ImageSharp.ruleset @@ -1,6 +1,7 @@  + diff --git a/src/ImageSharp.Formats.Gif/GifDecoderCore.cs b/src/ImageSharp.Formats.Gif/GifDecoderCore.cs index 2be8aed379..9c367c15a6 100644 --- a/src/ImageSharp.Formats.Gif/GifDecoderCore.cs +++ b/src/ImageSharp.Formats.Gif/GifDecoderCore.cs @@ -239,7 +239,7 @@ namespace ImageSharp.Formats try { this.currentStream.Read(flagBuffer, 0, flag); - this.decodedImage.Properties.Add(new ImageProperty("Comments", BitConverter.ToString(flagBuffer, 0, flag))); + this.decodedImage.MetaData.Properties.Add(new ImageProperty("Comments", BitConverter.ToString(flagBuffer, 0, flag))); } finally { @@ -323,7 +323,7 @@ namespace ImageSharp.Formats { image = this.decodedImage; - image.Quality = colorTableLength / 3; + this.decodedImage.MetaData.Quality = colorTableLength / 3; // This initializes the image to become fully transparent because the alpha channel is zero. image.InitPixels(imageWidth, imageHeight); diff --git a/src/ImageSharp.Formats.Gif/GifEncoderCore.cs b/src/ImageSharp.Formats.Gif/GifEncoderCore.cs index e5b8ba08ae..c5923c1a54 100644 --- a/src/ImageSharp.Formats.Gif/GifEncoderCore.cs +++ b/src/ImageSharp.Formats.Gif/GifEncoderCore.cs @@ -65,7 +65,7 @@ namespace ImageSharp.Formats EndianBinaryWriter writer = new EndianBinaryWriter(Endianness.LittleEndian, stream); // Ensure that quality can be set but has a fallback. - int quality = this.Quality > 0 ? this.Quality : image.Quality; + int quality = this.Quality > 0 ? this.Quality : image.MetaData.Quality; this.Quality = quality > 0 ? quality.Clamp(1, 256) : 256; // Get the number of bits. @@ -91,7 +91,7 @@ namespace ImageSharp.Formats // Write additional frames. if (image.Frames.Any()) { - this.WriteApplicationExtension(writer, image.RepeatCount, image.Frames.Count); + this.WriteApplicationExtension(writer, image.MetaData.RepeatCount, image.Frames.Count); // ReSharper disable once ForCanBeConvertedToForeach for (int i = 0; i < image.Frames.Count; i++) diff --git a/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs b/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs index 1fd9632922..9050c20e1c 100644 --- a/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs @@ -547,8 +547,8 @@ namespace ImageSharp.Formats { if (this.isJfif && this.horizontalResolution > 0 && this.verticalResolution > 0) { - image.HorizontalResolution = this.horizontalResolution; - image.VerticalResolution = this.verticalResolution; + image.MetaData.HorizontalResolution = this.horizontalResolution; + image.MetaData.VerticalResolution = this.verticalResolution; } } @@ -951,7 +951,7 @@ namespace ImageSharp.Formats if (profile[0] == 'E' && profile[1] == 'x' && profile[2] == 'i' && profile[3] == 'f' && profile[4] == '\0' && profile[5] == '\0') { - image.ExifProfile = new ExifProfile(profile); + image.MetaData.ExifProfile = new ExifProfile(profile); } } diff --git a/src/ImageSharp.Formats.Jpeg/JpegEncoder.cs b/src/ImageSharp.Formats.Jpeg/JpegEncoder.cs index 6f404c9bb3..e56a9f2e81 100644 --- a/src/ImageSharp.Formats.Jpeg/JpegEncoder.cs +++ b/src/ImageSharp.Formats.Jpeg/JpegEncoder.cs @@ -65,9 +65,9 @@ namespace ImageSharp.Formats where TColor : struct, IPackedPixel, IEquatable { // Ensure that quality can be set but has a fallback. - if (image.Quality > 0) + if (image.MetaData.Quality > 0) { - this.Quality = image.Quality; + this.Quality = image.MetaData.Quality; } JpegEncoderCore encode = new JpegEncoderCore(); diff --git a/src/ImageSharp.Formats.Jpeg/JpegEncoderCore.cs b/src/ImageSharp.Formats.Jpeg/JpegEncoderCore.cs index 984418dd3a..9cc51c7772 100644 --- a/src/ImageSharp.Formats.Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp.Formats.Jpeg/JpegEncoderCore.cs @@ -200,7 +200,7 @@ namespace ImageSharp.Formats int componentCount = 3; // Write the Start Of Image marker. - this.WriteApplicationHeader((short)image.HorizontalResolution, (short)image.VerticalResolution); + this.WriteApplicationHeader((short)image.MetaData.HorizontalResolution, (short)image.MetaData.VerticalResolution); this.WriteProfiles(image); @@ -706,7 +706,7 @@ namespace ImageSharp.Formats private void WriteProfiles(Image image) where TColor : struct, IPackedPixel, IEquatable { - this.WriteProfile(image.ExifProfile); + this.WriteProfile(image.MetaData.ExifProfile); } /// diff --git a/src/ImageSharp.Formats.Png/PngDecoderCore.cs b/src/ImageSharp.Formats.Png/PngDecoderCore.cs index ffc037b62f..3eaa8fde3f 100644 --- a/src/ImageSharp.Formats.Png/PngDecoderCore.cs +++ b/src/ImageSharp.Formats.Png/PngDecoderCore.cs @@ -174,7 +174,7 @@ namespace ImageSharp.Formats byte[] pal = new byte[currentChunk.Length]; Buffer.BlockCopy(currentChunk.Data, 0, pal, 0, currentChunk.Length); this.palette = pal; - image.Quality = pal.Length / 3; + image.MetaData.Quality = pal.Length / 3; break; case PngChunkTypes.PaletteAlpha: byte[] alpha = new byte[currentChunk.Length]; @@ -268,8 +268,8 @@ namespace ImageSharp.Formats data.ReverseBytes(4, 4); // 39.3700787 = inches in a meter. - image.HorizontalResolution = BitConverter.ToInt32(data, 0) / 39.3700787d; - image.VerticalResolution = BitConverter.ToInt32(data, 4) / 39.3700787d; + image.MetaData.HorizontalResolution = BitConverter.ToInt32(data, 0) / 39.3700787d; + image.MetaData.VerticalResolution = BitConverter.ToInt32(data, 4) / 39.3700787d; } /// @@ -777,7 +777,7 @@ namespace ImageSharp.Formats string name = Encoding.Unicode.GetString(data, 0, zeroIndex); string value = Encoding.Unicode.GetString(data, zeroIndex + 1, length - zeroIndex - 1); - image.Properties.Add(new ImageProperty(name, value)); + image.MetaData.Properties.Add(new ImageProperty(name, value)); } /// diff --git a/src/ImageSharp.Formats.Png/PngEncoderCore.cs b/src/ImageSharp.Formats.Png/PngEncoderCore.cs index 2ab42623d1..46aa2187bf 100644 --- a/src/ImageSharp.Formats.Png/PngEncoderCore.cs +++ b/src/ImageSharp.Formats.Png/PngEncoderCore.cs @@ -126,12 +126,12 @@ namespace ImageSharp.Formats public byte Threshold { get; set; } /// - /// Encodes the image to the specified stream from the . + /// Encodes the image to the specified stream from the . /// /// The pixel format. /// The to encode from. /// The to encode the image data to. - public void Encode(ImageBase image, Stream stream) + public void Encode(Image image, Stream stream) where TColor : struct, IPackedPixel, IEquatable { Guard.NotNull(image, nameof(image)); @@ -153,7 +153,7 @@ namespace ImageSharp.Formats stream.Write(this.chunkDataBuffer, 0, 8); // Ensure that quality can be set but has a fallback. - int quality = this.Quality > 0 ? this.Quality : image.Quality; + int quality = this.Quality > 0 ? this.Quality : image.MetaData.Quality; this.Quality = quality > 0 ? quality.Clamp(1, int.MaxValue) : int.MaxValue; // Set correct color type if the color count is 256 or less. @@ -557,11 +557,11 @@ namespace ImageSharp.Formats where TColor : struct, IPackedPixel, IEquatable { Image image = imageBase as Image; - if (image != null && image.HorizontalResolution > 0 && image.VerticalResolution > 0) + if (image != null && image.MetaData.HorizontalResolution > 0 && image.MetaData.VerticalResolution > 0) { // 39.3700787 = inches in a meter. - int dpmX = (int)Math.Round(image.HorizontalResolution * 39.3700787D); - int dpmY = (int)Math.Round(image.VerticalResolution * 39.3700787D); + int dpmX = (int)Math.Round(image.MetaData.HorizontalResolution * 39.3700787D); + int dpmY = (int)Math.Round(image.MetaData.VerticalResolution * 39.3700787D); WriteInteger(this.chunkDataBuffer, 0, dpmX); WriteInteger(this.chunkDataBuffer, 4, dpmY); diff --git a/src/ImageSharp.Processing/Transforms/AutoOrient.cs b/src/ImageSharp.Processing/Transforms/AutoOrient.cs index 8d86ae8142..cb4e72a948 100644 --- a/src/ImageSharp.Processing/Transforms/AutoOrient.cs +++ b/src/ImageSharp.Processing/Transforms/AutoOrient.cs @@ -66,12 +66,12 @@ namespace ImageSharp private static Orientation GetExifOrientation(Image source) where TColor : struct, IPackedPixel, IEquatable { - if (source.ExifProfile == null) + if (source.MetaData.ExifProfile == null) { return Orientation.Unknown; } - ExifValue value = source.ExifProfile.GetValue(ExifTag.Orientation); + ExifValue value = source.MetaData.ExifProfile.GetValue(ExifTag.Orientation); if (value == null) { return Orientation.Unknown; @@ -79,7 +79,7 @@ namespace ImageSharp Orientation orientation = (Orientation)value.Value; - source.ExifProfile.SetValue(ExifTag.Orientation, (ushort)Orientation.TopLeft); + source.MetaData.ExifProfile.SetValue(ExifTag.Orientation, (ushort)Orientation.TopLeft); return orientation; } diff --git a/src/ImageSharp/Image/IImage.cs b/src/ImageSharp/Image/IImage.cs new file mode 100644 index 0000000000..55abdb244f --- /dev/null +++ b/src/ImageSharp/Image/IImage.cs @@ -0,0 +1,25 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using Formats; + + /// + /// Encapsulates the basic properties and methods required to manipulate images. + /// + internal interface IImage : IImageBase + { + /// + /// Gets the currently loaded image format. + /// + IImageFormat CurrentImageFormat { get; } + + /// + /// Gets the meta data of the image. + /// + ImageMetaData MetaData { get; } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Image/IImageBase.cs b/src/ImageSharp/Image/IImageBase.cs index 113b94a186..effbd60063 100644 --- a/src/ImageSharp/Image/IImageBase.cs +++ b/src/ImageSharp/Image/IImageBase.cs @@ -15,11 +15,6 @@ namespace ImageSharp /// Rectangle Bounds { get; } - /// - /// Gets or sets the quality of the image. This affects the output quality of lossy image formats. - /// - int Quality { get; set; } - /// /// Gets or sets the frame delay for animated images. /// If not 0, this field specifies the number of hundredths (1/100) of a second to @@ -52,5 +47,10 @@ namespace ImageSharp /// Gets the pixel ratio made up of the width and height. /// double PixelRatio { get; } + + /// + /// Gets the configuration providing initialization code which allows extending the library. + /// + Configuration Configuration { get; } } } \ No newline at end of file diff --git a/src/ImageSharp/Image/ImageBase{TColor}.cs b/src/ImageSharp/Image/ImageBase{TColor}.cs index b179d1158e..6fcd7a3b7f 100644 --- a/src/ImageSharp/Image/ImageBase{TColor}.cs +++ b/src/ImageSharp/Image/ImageBase{TColor}.cs @@ -110,9 +110,6 @@ namespace ImageSharp /// public Rectangle Bounds => new Rectangle(0, 0, this.Width, this.Height); - /// - public int Quality { get; set; } - /// public int FrameDelay { get; set; } @@ -181,16 +178,17 @@ namespace ImageSharp } /// - /// Copies the properties from the other . + /// Copies the properties from the other . /// /// - /// The other to copy the properties from. + /// The other to copy the properties from. /// - protected void CopyProperties(ImageBase other) + protected void CopyProperties(IImageBase other) { - this.Configuration = other.Configuration; - this.Quality = other.Quality; + Debug.Assert(other != null); + this.FrameDelay = other.FrameDelay; + this.Configuration = other.Configuration; } /// diff --git a/src/ImageSharp/Image/ImageFrame{TColor}.cs b/src/ImageSharp/Image/ImageFrame{TColor}.cs index 809eca1a4a..b06b10bc49 100644 --- a/src/ImageSharp/Image/ImageFrame{TColor}.cs +++ b/src/ImageSharp/Image/ImageFrame{TColor}.cs @@ -55,11 +55,8 @@ namespace ImageSharp { scaleFunc = PackedPixelConverterHelper.ComputeScaleFunction(scaleFunc); - ImageFrame target = new ImageFrame(this.Width, this.Height, this.Configuration) - { - Quality = this.Quality, - FrameDelay = this.FrameDelay - }; + ImageFrame target = new ImageFrame(this.Width, this.Height, this.Configuration); + target.CopyProperties(this); using (PixelAccessor pixels = this.Lock()) using (PixelAccessor targetPixels = target.Lock()) diff --git a/src/ImageSharp/Image/Image{TColor}.cs b/src/ImageSharp/Image/Image{TColor}.cs index 84bae39cca..7223bc1d8d 100644 --- a/src/ImageSharp/Image/Image{TColor}.cs +++ b/src/ImageSharp/Image/Image{TColor}.cs @@ -23,21 +23,9 @@ namespace ImageSharp /// /// The pixel format. [DebuggerDisplay("Image: {Width}x{Height}")] - public class Image : ImageBase + public class Image : ImageBase, IImage where TColor : struct, IPackedPixel, IEquatable { - /// - /// The default horizontal resolution value (dots per inch) in x direction. - /// The default value is 96 dots per inch. - /// - public const double DefaultHorizontalResolution = 96; - - /// - /// The default vertical resolution value (dots per inch) in y direction. - /// The default value is 96 dots per inch. - /// - public const double DefaultVerticalResolution = 96; - /// /// Initializes a new instance of the class /// with the height and the width of the image. @@ -129,18 +117,9 @@ namespace ImageSharp } /// - /// Gets or sets the resolution of the image in x- direction. It is defined as - /// number of dots per inch and should be an positive value. - /// - /// The density of the image in x- direction. - public double HorizontalResolution { get; set; } = DefaultHorizontalResolution; - - /// - /// Gets or sets the resolution of the image in y- direction. It is defined as - /// number of dots per inch and should be an positive value. + /// Gets the meta data of the image. /// - /// The density of the image in y- direction. - public double VerticalResolution { get; set; } = DefaultVerticalResolution; + public ImageMetaData MetaData { get; private set; } = new ImageMetaData(); /// /// Gets the width of the image in inches. It is calculated as the width of the image @@ -152,14 +131,7 @@ namespace ImageSharp { get { - double resolution = this.HorizontalResolution; - - if (resolution <= 0) - { - resolution = DefaultHorizontalResolution; - } - - return this.Width / resolution; + return this.Width / this.MetaData.HorizontalResolution; } } @@ -173,14 +145,7 @@ namespace ImageSharp { get { - double resolution = this.VerticalResolution; - - if (resolution <= 0) - { - resolution = DefaultVerticalResolution; - } - - return this.Height / resolution; + return this.Height / this.MetaData.VerticalResolution; } } @@ -192,34 +157,17 @@ namespace ImageSharp /// public bool IsAnimated => this.Frames.Count > 0; - /// - /// Gets or sets the number of times any animation is repeated. - /// 0 means to repeat indefinitely. - /// - public ushort RepeatCount { get; set; } - /// /// Gets the other frames for the animation. /// /// The list of frame images. public IList> Frames { get; } = new List>(); - /// - /// Gets the list of properties for storing meta information about this image. - /// - /// A list of image properties. - public IList Properties { get; } = new List(); - /// /// Gets the currently loaded image format. /// public IImageFormat CurrentImageFormat { get; internal set; } - /// - /// Gets or sets the Exif profile. - /// - public ExifProfile ExifProfile { get; set; } - /// /// Applies the processor to the image. /// @@ -317,15 +265,8 @@ namespace ImageSharp { scaleFunc = PackedPixelConverterHelper.ComputeScaleFunction(scaleFunc); - Image target = new Image(this.Width, this.Height, this.Configuration) - { - Quality = this.Quality, - FrameDelay = this.FrameDelay, - HorizontalResolution = this.HorizontalResolution, - VerticalResolution = this.VerticalResolution, - CurrentImageFormat = this.CurrentImageFormat, - RepeatCount = this.RepeatCount - }; + Image target = new Image(this.Width, this.Height, this.Configuration); + target.CopyProperties(this); using (PixelAccessor pixels = this.Lock()) using (PixelAccessor targetPixels = target.Lock()) @@ -345,11 +286,6 @@ namespace ImageSharp }); } - if (this.ExifProfile != null) - { - target.ExifProfile = new ExifProfile(this.ExifProfile); - } - for (int i = 0; i < this.Frames.Count; i++) { target.Frames.Add(this.Frames[i].To()); @@ -358,27 +294,6 @@ namespace ImageSharp return target; } - /// - /// Copies the properties from the other . - /// - /// - /// The other to copy the properties from. - /// - internal void CopyProperties(Image other) - { - base.CopyProperties(other); - - this.HorizontalResolution = other.HorizontalResolution; - this.VerticalResolution = other.VerticalResolution; - this.CurrentImageFormat = other.CurrentImageFormat; - this.RepeatCount = other.RepeatCount; - - if (other.ExifProfile != null) - { - this.ExifProfile = new ExifProfile(other.ExifProfile); - } - } - /// /// Creates a new from this instance /// @@ -400,6 +315,20 @@ namespace ImageSharp base.Dispose(disposing); } + /// + /// Copies the properties from the other . + /// + /// + /// The other to copy the properties from. + /// + private void CopyProperties(IImage other) + { + base.CopyProperties(other); + + this.CurrentImageFormat = other.CurrentImageFormat; + this.MetaData = new ImageMetaData(other.MetaData); + } + /// /// Loads the image from the given stream. /// diff --git a/src/ImageSharp/Metadata/ImageMetaData.cs b/src/ImageSharp/Metadata/ImageMetaData.cs new file mode 100644 index 0000000000..30bd2c348e --- /dev/null +++ b/src/ImageSharp/Metadata/ImageMetaData.cs @@ -0,0 +1,130 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System.Collections.Generic; + using System.Diagnostics; + + /// + /// Encapsulates the metadata of an image. + /// + public sealed class ImageMetaData + { + /// + /// The default horizontal resolution value (dots per inch) in x direction. + /// The default value is 96 dots per inch. + /// + public const double DefaultHorizontalResolution = 96; + + /// + /// The default vertical resolution value (dots per inch) in y direction. + /// The default value is 96 dots per inch. + /// + public const double DefaultVerticalResolution = 96; + + private double horizontalResolution; + private double verticalResolution; + + /// + /// Initializes a new instance of the class. + /// + internal ImageMetaData() + { + this.horizontalResolution = DefaultHorizontalResolution; + this.verticalResolution = DefaultVerticalResolution; + } + + /// + /// Initializes a new instance of the class + /// by making a copy from other metadata. + /// + /// + /// The other to create this instance from. + /// + internal ImageMetaData(ImageMetaData other) + { + Debug.Assert(other != null); + + this.HorizontalResolution = other.HorizontalResolution; + this.VerticalResolution = other.VerticalResolution; + this.Quality = other.Quality; + + foreach (ImageProperty property in other.Properties) + { + this.Properties.Add(new ImageProperty(property)); + } + + if (other.ExifProfile != null) + { + this.ExifProfile = new ExifProfile(other.ExifProfile); + } + } + + /// + /// Gets or sets the resolution of the image in x- direction. It is defined as + /// number of dots per inch and should be an positive value. + /// + /// The density of the image in x- direction. + public double HorizontalResolution + { + get + { + return this.horizontalResolution; + } + + set + { + if (value >= 0) + { + this.horizontalResolution = value; + } + } + } + + /// + /// Gets or sets the resolution of the image in y- direction. It is defined as + /// number of dots per inch and should be an positive value. + /// + /// The density of the image in y- direction. + public double VerticalResolution + { + get + { + return this.verticalResolution; + } + + set + { + if (value >= 0) + { + this.verticalResolution = value; + } + } + } + + /// + /// Gets or sets the Exif profile. + /// + public ExifProfile ExifProfile { get; set; } + + /// + /// Gets the list of properties for storing meta information about this image. + /// + /// A list of image properties. + public IList Properties { get; } = new List(); + + /// + /// Gets or sets the quality of the image. This affects the output quality of lossy image formats. + /// + public int Quality { get; set; } + + /// + /// Gets or sets the number of times any animation is repeated. + /// 0 means to repeat indefinitely. + /// + public ushort RepeatCount { get; set; } + } +} diff --git a/src/ImageSharp/Image/ImageProperty.cs b/src/ImageSharp/Metadata/ImageProperty.cs similarity index 91% rename from src/ImageSharp/Image/ImageProperty.cs rename to src/ImageSharp/Metadata/ImageProperty.cs index 7fda749e95..c8bd0b23d1 100644 --- a/src/ImageSharp/Image/ImageProperty.cs +++ b/src/ImageSharp/Metadata/ImageProperty.cs @@ -6,6 +6,7 @@ namespace ImageSharp { using System; + using System.Diagnostics; /// /// Stores meta information about a image, like the name of the author, @@ -27,6 +28,21 @@ namespace ImageSharp this.Value = value; } + /// + /// Initializes a new instance of the class + /// by making a copy from another property. + /// + /// + /// The other to create this instance from. + /// + internal ImageProperty(ImageProperty other) + { + Debug.Assert(other != null); + + this.Name = other.Name; + this.Value = other.Value; + } + /// /// Gets the name of this indicating which kind of /// information this property stores. diff --git a/src/ImageSharp/Profiles/Exif/ExifDataType.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifDataType.cs similarity index 100% rename from src/ImageSharp/Profiles/Exif/ExifDataType.cs rename to src/ImageSharp/Metadata/Profiles/Exif/ExifDataType.cs diff --git a/src/ImageSharp/Profiles/Exif/ExifParts.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifParts.cs similarity index 100% rename from src/ImageSharp/Profiles/Exif/ExifParts.cs rename to src/ImageSharp/Metadata/Profiles/Exif/ExifParts.cs diff --git a/src/ImageSharp/Profiles/Exif/ExifProfile.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs similarity index 100% rename from src/ImageSharp/Profiles/Exif/ExifProfile.cs rename to src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs diff --git a/src/ImageSharp/Profiles/Exif/ExifReader.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs similarity index 100% rename from src/ImageSharp/Profiles/Exif/ExifReader.cs rename to src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs diff --git a/src/ImageSharp/Profiles/Exif/ExifTag.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifTag.cs similarity index 100% rename from src/ImageSharp/Profiles/Exif/ExifTag.cs rename to src/ImageSharp/Metadata/Profiles/Exif/ExifTag.cs diff --git a/src/ImageSharp/Profiles/Exif/ExifTagDescriptionAttribute.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifTagDescriptionAttribute.cs similarity index 100% rename from src/ImageSharp/Profiles/Exif/ExifTagDescriptionAttribute.cs rename to src/ImageSharp/Metadata/Profiles/Exif/ExifTagDescriptionAttribute.cs diff --git a/src/ImageSharp/Profiles/Exif/ExifValue.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifValue.cs similarity index 100% rename from src/ImageSharp/Profiles/Exif/ExifValue.cs rename to src/ImageSharp/Metadata/Profiles/Exif/ExifValue.cs diff --git a/src/ImageSharp/Profiles/Exif/ExifWriter.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs similarity index 100% rename from src/ImageSharp/Profiles/Exif/ExifWriter.cs rename to src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs diff --git a/src/ImageSharp/Profiles/Exif/README.md b/src/ImageSharp/Metadata/Profiles/Exif/README.md similarity index 100% rename from src/ImageSharp/Profiles/Exif/README.md rename to src/ImageSharp/Metadata/Profiles/Exif/README.md diff --git a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs index 6873717ed6..97bd34def8 100644 --- a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs +++ b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs @@ -24,8 +24,8 @@ namespace ImageSharp.Tests { using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) { - image.VerticalResolution = 150; - image.HorizontalResolution = 150; + image.MetaData.VerticalResolution = 150; + image.MetaData.HorizontalResolution = 150; image.Save(output); } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs index 59e5eba5c1..ef6671931f 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs @@ -36,8 +36,8 @@ namespace ImageSharp.Tests { using (Image image = provider.GetImage().Resize(new ResizeOptions { Size = new Size(150, 100), Mode = ResizeMode.Max })) { - image.Quality = quality; - image.ExifProfile = null; // Reduce the size of the file + image.MetaData.Quality = quality; + image.MetaData.ExifProfile = null; // Reduce the size of the file JpegEncoder encoder = new JpegEncoder { Subsample = subsample, Quality = quality }; provider.Utility.TestName += $"{subsample}_Q{quality}"; diff --git a/tests/ImageSharp.Tests/Formats/Png/PngTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngTests.cs index ae6487ea9c..cf485c593f 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngTests.cs @@ -27,7 +27,7 @@ namespace ImageSharp.Tests { using (FileStream output = File.OpenWrite($"{path}/{file.FileNameWithoutExtension}.png")) { - image.Quality = 256; + image.MetaData.Quality = 256; image.Save(output, new PngFormat()); } } diff --git a/tests/ImageSharp.Tests/Image/ImagePropertyTests.cs b/tests/ImageSharp.Tests/Metadata/ImagePropertyTests.cs similarity index 100% rename from tests/ImageSharp.Tests/Image/ImagePropertyTests.cs rename to tests/ImageSharp.Tests/Metadata/ImagePropertyTests.cs diff --git a/tests/ImageSharp.Tests/Profiles/Exif/ExifProfileTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs similarity index 68% rename from tests/ImageSharp.Tests/Profiles/Exif/ExifProfileTests.cs rename to tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs index 1900b58c62..61307da205 100644 --- a/tests/ImageSharp.Tests/Profiles/Exif/ExifProfileTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs @@ -19,20 +19,18 @@ namespace ImageSharp.Tests { Image image = TestFile.Create(TestImages.Jpeg.Baseline.Calliphora).CreateImage(); - Assert.Null(image.ExifProfile); + Assert.Null(image.MetaData.ExifProfile); - image.ExifProfile = new ExifProfile(); - image.ExifProfile.SetValue(ExifTag.Copyright, "Dirk Lemstra"); + image.MetaData.ExifProfile = new ExifProfile(); + image.MetaData.ExifProfile.SetValue(ExifTag.Copyright, "Dirk Lemstra"); image = WriteAndRead(image); - Assert.NotNull(image.ExifProfile); - Assert.Equal(1, image.ExifProfile.Values.Count()); + Assert.NotNull(image.MetaData.ExifProfile); + Assert.Equal(1, image.MetaData.ExifProfile.Values.Count()); - ExifValue value = image.ExifProfile.Values.FirstOrDefault(val => val.Tag == ExifTag.Copyright); + ExifValue value = image.MetaData.ExifProfile.Values.FirstOrDefault(val => val.Tag == ExifTag.Copyright); TestValue(value, "Dirk Lemstra"); - - } [Fact] @@ -70,14 +68,14 @@ namespace ImageSharp.Tests profile.SetValue(ExifTag.ExposureTime, new Rational(exposureTime)); Image image = new Image(1, 1); - image.ExifProfile = profile; + image.MetaData.ExifProfile = profile; image.SaveAsJpeg(memStream); memStream.Position = 0; image = new Image(memStream); - profile = image.ExifProfile; + profile = image.MetaData.ExifProfile; Assert.NotNull(profile); ExifValue value = profile.GetValue(ExifTag.ExposureTime); @@ -88,14 +86,14 @@ namespace ImageSharp.Tests profile = GetExifProfile(); profile.SetValue(ExifTag.ExposureTime, new Rational(exposureTime, true)); - image.ExifProfile = profile; + image.MetaData.ExifProfile = profile; image.SaveAsJpeg(memStream); memStream.Position = 0; image = new Image(memStream); - profile = image.ExifProfile; + profile = image.MetaData.ExifProfile; Assert.NotNull(profile); value = profile.GetValue(ExifTag.ExposureTime); @@ -107,24 +105,24 @@ namespace ImageSharp.Tests public void ReadWriteInfinity() { Image image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateImage(); - image.ExifProfile.SetValue(ExifTag.ExposureBiasValue, new SignedRational(double.PositiveInfinity)); + image.MetaData.ExifProfile.SetValue(ExifTag.ExposureBiasValue, new SignedRational(double.PositiveInfinity)); image = WriteAndRead(image); - ExifValue value = image.ExifProfile.GetValue(ExifTag.ExposureBiasValue); + ExifValue value = image.MetaData.ExifProfile.GetValue(ExifTag.ExposureBiasValue); Assert.NotNull(value); Assert.Equal(new SignedRational(double.PositiveInfinity), value.Value); - image.ExifProfile.SetValue(ExifTag.ExposureBiasValue, new SignedRational(double.NegativeInfinity)); + image.MetaData.ExifProfile.SetValue(ExifTag.ExposureBiasValue, new SignedRational(double.NegativeInfinity)); image = WriteAndRead(image); - value = image.ExifProfile.GetValue(ExifTag.ExposureBiasValue); + value = image.MetaData.ExifProfile.GetValue(ExifTag.ExposureBiasValue); Assert.NotNull(value); Assert.Equal(new SignedRational(double.NegativeInfinity), value.Value); - image.ExifProfile.SetValue(ExifTag.FlashEnergy, new Rational(double.NegativeInfinity)); + image.MetaData.ExifProfile.SetValue(ExifTag.FlashEnergy, new Rational(double.NegativeInfinity)); image = WriteAndRead(image); - value = image.ExifProfile.GetValue(ExifTag.FlashEnergy); + value = image.MetaData.ExifProfile.GetValue(ExifTag.FlashEnergy); Assert.NotNull(value); Assert.Equal(new Rational(double.PositiveInfinity), value.Value); } @@ -135,71 +133,71 @@ namespace ImageSharp.Tests Rational[] latitude = new Rational[] { new Rational(12.3), new Rational(4.56), new Rational(789.0) }; Image image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateImage(); - image.ExifProfile.SetValue(ExifTag.Software, "ImageSharp"); + image.MetaData.ExifProfile.SetValue(ExifTag.Software, "ImageSharp"); - ExifValue value = image.ExifProfile.GetValue(ExifTag.Software); + ExifValue value = image.MetaData.ExifProfile.GetValue(ExifTag.Software); TestValue(value, "ImageSharp"); Assert.Throws(() => { value.Value = 15; }); - image.ExifProfile.SetValue(ExifTag.ShutterSpeedValue, new SignedRational(75.55)); + image.MetaData.ExifProfile.SetValue(ExifTag.ShutterSpeedValue, new SignedRational(75.55)); - value = image.ExifProfile.GetValue(ExifTag.ShutterSpeedValue); + value = image.MetaData.ExifProfile.GetValue(ExifTag.ShutterSpeedValue); TestValue(value, new SignedRational(7555, 100)); Assert.Throws(() => { value.Value = 75; }); - image.ExifProfile.SetValue(ExifTag.XResolution, new Rational(150.0)); + image.MetaData.ExifProfile.SetValue(ExifTag.XResolution, new Rational(150.0)); - value = image.ExifProfile.GetValue(ExifTag.XResolution); + value = image.MetaData.ExifProfile.GetValue(ExifTag.XResolution); TestValue(value, new Rational(150, 1)); Assert.Throws(() => { value.Value = "ImageSharp"; }); - image.ExifProfile.SetValue(ExifTag.ReferenceBlackWhite, null); + image.MetaData.ExifProfile.SetValue(ExifTag.ReferenceBlackWhite, null); - value = image.ExifProfile.GetValue(ExifTag.ReferenceBlackWhite); + value = image.MetaData.ExifProfile.GetValue(ExifTag.ReferenceBlackWhite); TestValue(value, (string)null); - image.ExifProfile.SetValue(ExifTag.GPSLatitude, latitude); + image.MetaData.ExifProfile.SetValue(ExifTag.GPSLatitude, latitude); - value = image.ExifProfile.GetValue(ExifTag.GPSLatitude); + value = image.MetaData.ExifProfile.GetValue(ExifTag.GPSLatitude); TestValue(value, latitude); image = WriteAndRead(image); - Assert.NotNull(image.ExifProfile); - Assert.Equal(17, image.ExifProfile.Values.Count()); + Assert.NotNull(image.MetaData.ExifProfile); + Assert.Equal(17, image.MetaData.ExifProfile.Values.Count()); - value = image.ExifProfile.GetValue(ExifTag.Software); + value = image.MetaData.ExifProfile.GetValue(ExifTag.Software); TestValue(value, "ImageSharp"); - value = image.ExifProfile.GetValue(ExifTag.ShutterSpeedValue); + value = image.MetaData.ExifProfile.GetValue(ExifTag.ShutterSpeedValue); TestValue(value, new SignedRational(75.55)); - value = image.ExifProfile.GetValue(ExifTag.XResolution); + value = image.MetaData.ExifProfile.GetValue(ExifTag.XResolution); TestValue(value, new Rational(150.0)); - value = image.ExifProfile.GetValue(ExifTag.ReferenceBlackWhite); + value = image.MetaData.ExifProfile.GetValue(ExifTag.ReferenceBlackWhite); Assert.Null(value); - value = image.ExifProfile.GetValue(ExifTag.GPSLatitude); + value = image.MetaData.ExifProfile.GetValue(ExifTag.GPSLatitude); TestValue(value, latitude); - image.ExifProfile.Parts = ExifParts.ExifTags; + image.MetaData.ExifProfile.Parts = ExifParts.ExifTags; image = WriteAndRead(image); - Assert.NotNull(image.ExifProfile); - Assert.Equal(8, image.ExifProfile.Values.Count()); + Assert.NotNull(image.MetaData.ExifProfile); + Assert.Equal(8, image.MetaData.ExifProfile.Values.Count()); - Assert.NotNull(image.ExifProfile.GetValue(ExifTag.ColorSpace)); - Assert.True(image.ExifProfile.RemoveValue(ExifTag.ColorSpace)); - Assert.False(image.ExifProfile.RemoveValue(ExifTag.ColorSpace)); - Assert.Null(image.ExifProfile.GetValue(ExifTag.ColorSpace)); + Assert.NotNull(image.MetaData.ExifProfile.GetValue(ExifTag.ColorSpace)); + Assert.True(image.MetaData.ExifProfile.RemoveValue(ExifTag.ColorSpace)); + Assert.False(image.MetaData.ExifProfile.RemoveValue(ExifTag.ColorSpace)); + Assert.Null(image.MetaData.ExifProfile.GetValue(ExifTag.ColorSpace)); - Assert.Equal(7, image.ExifProfile.Values.Count()); + Assert.Equal(7, image.MetaData.ExifProfile.Values.Count()); } [Fact] @@ -225,8 +223,8 @@ namespace ImageSharp.Tests } Image image = new Image(100, 100); - image.ExifProfile = new ExifProfile(); - image.ExifProfile.SetValue(ExifTag.ImageDescription, junk.ToString()); + image.MetaData.ExifProfile = new ExifProfile(); + image.MetaData.ExifProfile.SetValue(ExifTag.ImageDescription, junk.ToString()); using (MemoryStream memStream = new MemoryStream()) { @@ -238,7 +236,7 @@ namespace ImageSharp.Tests { Image image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateImage(); - ExifProfile profile = image.ExifProfile; + ExifProfile profile = image.MetaData.ExifProfile; Assert.NotNull(profile); return profile; diff --git a/tests/ImageSharp.Tests/Profiles/Exif/ExifTagDescriptionAttributeTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifTagDescriptionAttributeTests.cs similarity index 100% rename from tests/ImageSharp.Tests/Profiles/Exif/ExifTagDescriptionAttributeTests.cs rename to tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifTagDescriptionAttributeTests.cs diff --git a/tests/ImageSharp.Tests/Profiles/Exif/ExifValueTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifValueTests.cs similarity index 96% rename from tests/ImageSharp.Tests/Profiles/Exif/ExifValueTests.cs rename to tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifValueTests.cs index e777d9e3b2..2014d08dc9 100644 --- a/tests/ImageSharp.Tests/Profiles/Exif/ExifValueTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifValueTests.cs @@ -15,7 +15,7 @@ namespace ImageSharp.Tests ExifProfile profile; using (Image image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateImage()) { - profile = image.ExifProfile; + profile = image.MetaData.ExifProfile; } Assert.NotNull(profile); diff --git a/tests/ImageSharp.Tests/Processors/Filters/AutoOrientTests.cs b/tests/ImageSharp.Tests/Processors/Filters/AutoOrientTests.cs index 499bdff823..ef183480c3 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/AutoOrientTests.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/AutoOrientTests.cs @@ -35,8 +35,8 @@ namespace ImageSharp.Tests using (Image image = file.CreateImage()) { - image.ExifProfile = new ExifProfile(); - image.ExifProfile.SetValue(ExifTag.Orientation, orientation); + image.MetaData.ExifProfile = new ExifProfile(); + image.MetaData.ExifProfile.SetValue(ExifTag.Orientation, orientation); using (FileStream before = File.OpenWrite($"{path}/before-{file.FileName}")) using (FileStream after = File.OpenWrite($"{path}/after-{file.FileName}")) From bacac401151810ae2a79c8e7e04c364a465ed8f9 Mon Sep 17 00:00:00 2001 From: Dirk Lemstra Date: Sun, 5 Feb 2017 12:18:03 +0100 Subject: [PATCH 036/142] Added metadata class for image frames. --- src/ImageSharp.Formats.Gif/GifDecoderCore.cs | 27 ++++++++---- src/ImageSharp.Formats.Gif/GifEncoderCore.cs | 31 ++++++++++++- src/ImageSharp/Image/IImageBase.cs | 8 ---- src/ImageSharp/Image/IImageFrame.cs | 18 ++++++++ src/ImageSharp/Image/ImageBase{TColor}.cs | 4 -- src/ImageSharp/Image/ImageFrame{TColor}.cs | 20 ++++++++- src/ImageSharp/Metadata/IMetaData.cs | 21 +++++++++ src/ImageSharp/Metadata/ImageFrameMetaData.cs | 44 +++++++++++++++++++ src/ImageSharp/Metadata/ImageMetaData.cs | 12 ++++- .../Metadata/ImageFrameMetaDataTests.cs | 26 +++++++++++ .../Metadata/ImageMetaDataTests.cs | 42 ++++++++++++++++++ 11 files changed, 229 insertions(+), 24 deletions(-) create mode 100644 src/ImageSharp/Image/IImageFrame.cs create mode 100644 src/ImageSharp/Metadata/IMetaData.cs create mode 100644 src/ImageSharp/Metadata/ImageFrameMetaData.cs create mode 100644 tests/ImageSharp.Tests/Metadata/ImageFrameMetaDataTests.cs create mode 100644 tests/ImageSharp.Tests/Metadata/ImageMetaDataTests.cs diff --git a/src/ImageSharp.Formats.Gif/GifDecoderCore.cs b/src/ImageSharp.Formats.Gif/GifDecoderCore.cs index 9c367c15a6..e6f7b6136f 100644 --- a/src/ImageSharp.Formats.Gif/GifDecoderCore.cs +++ b/src/ImageSharp.Formats.Gif/GifDecoderCore.cs @@ -321,12 +321,14 @@ namespace ImageSharp.Formats if (this.previousFrame == null) { - image = this.decodedImage; - this.decodedImage.MetaData.Quality = colorTableLength / 3; // This initializes the image to become fully transparent because the alpha channel is zero. - image.InitPixels(imageWidth, imageHeight); + this.decodedImage.InitPixels(imageWidth, imageHeight); + + this.SetFrameDelay(this.decodedImage.MetaData); + + image = this.decodedImage; } else { @@ -338,6 +340,8 @@ namespace ImageSharp.Formats currentFrame = this.previousFrame.Clone(); + this.SetFrameDelay(currentFrame.MetaData); + image = currentFrame; this.RestoreToBackground(image); @@ -345,11 +349,6 @@ namespace ImageSharp.Formats this.decodedImage.Frames.Add(currentFrame); } - if (this.graphicsControlExtension != null && this.graphicsControlExtension.DelayTime > 0) - { - image.FrameDelay = this.graphicsControlExtension.DelayTime; - } - int i = 0; int interlacePass = 0; // The interlace pass int interlaceIncrement = 8; // The interlacing line increment @@ -465,5 +464,17 @@ namespace ImageSharp.Formats this.restoreArea = null; } + + /// + /// Sets the frame delay in the metadata. + /// + /// The meta data. + private void SetFrameDelay(IMetaData metaData) + { + if (this.graphicsControlExtension != null && this.graphicsControlExtension.DelayTime > 0) + { + metaData.FrameDelay = this.graphicsControlExtension.DelayTime; + } + } } } \ No newline at end of file diff --git a/src/ImageSharp.Formats.Gif/GifEncoderCore.cs b/src/ImageSharp.Formats.Gif/GifEncoderCore.cs index c5923c1a54..80c9ee36bc 100644 --- a/src/ImageSharp.Formats.Gif/GifEncoderCore.cs +++ b/src/ImageSharp.Formats.Gif/GifEncoderCore.cs @@ -229,14 +229,41 @@ namespace ImageSharp.Formats } } + /// + /// Writes the graphics control extension to the stream. + /// + /// The pixel format. + /// The to encode. + /// The stream to write to. + /// The index of the color in the color palette to make transparent. + private void WriteGraphicalControlExtension(Image image, EndianBinaryWriter writer, int transparencyIndex) + where TColor : struct, IPackedPixel, IEquatable + { + this.WriteGraphicalControlExtension(image, image.MetaData, writer, transparencyIndex); + } + + /// + /// Writes the graphics control extension to the stream. + /// + /// The pixel format. + /// The to encode. + /// The stream to write to. + /// The index of the color in the color palette to make transparent. + private void WriteGraphicalControlExtension(ImageFrame imageFrame, EndianBinaryWriter writer, int transparencyIndex) + where TColor : struct, IPackedPixel, IEquatable + { + this.WriteGraphicalControlExtension(imageFrame, imageFrame.MetaData, writer, transparencyIndex); + } + /// /// Writes the graphics control extension to the stream. /// /// The pixel format. /// The to encode. + /// The metadata of the image or frame. /// The stream to write to. /// The index of the color in the color palette to make transparent. - private void WriteGraphicalControlExtension(ImageBase image, EndianBinaryWriter writer, int transparencyIndex) + private void WriteGraphicalControlExtension(ImageBase image, IMetaData metaData, EndianBinaryWriter writer, int transparencyIndex) where TColor : struct, IPackedPixel, IEquatable { // TODO: Check transparency logic. @@ -250,7 +277,7 @@ namespace ImageSharp.Formats DisposalMethod = disposalMethod, TransparencyFlag = hasTransparent, TransparencyIndex = transparencyIndex, - DelayTime = image.FrameDelay + DelayTime = metaData.FrameDelay }; // Write the intro. diff --git a/src/ImageSharp/Image/IImageBase.cs b/src/ImageSharp/Image/IImageBase.cs index effbd60063..707fea235d 100644 --- a/src/ImageSharp/Image/IImageBase.cs +++ b/src/ImageSharp/Image/IImageBase.cs @@ -15,14 +15,6 @@ namespace ImageSharp /// Rectangle Bounds { get; } - /// - /// Gets or sets the frame delay for animated images. - /// If not 0, this field specifies the number of hundredths (1/100) of a second to - /// wait before continuing with the processing of the Data Stream. - /// The clock starts ticking immediately after the graphic is rendered. - /// - int FrameDelay { get; set; } - /// /// Gets or sets the maximum allowable width in pixels. /// diff --git a/src/ImageSharp/Image/IImageFrame.cs b/src/ImageSharp/Image/IImageFrame.cs new file mode 100644 index 0000000000..bf3261d93c --- /dev/null +++ b/src/ImageSharp/Image/IImageFrame.cs @@ -0,0 +1,18 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// Encapsulates the basic properties and methods required to manipulate images. + /// + internal interface IImageFrame : IImageBase + { + /// + /// Gets the meta data of the image. + /// + ImageFrameMetaData MetaData { get; } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Image/ImageBase{TColor}.cs b/src/ImageSharp/Image/ImageBase{TColor}.cs index 6fcd7a3b7f..fd82f77308 100644 --- a/src/ImageSharp/Image/ImageBase{TColor}.cs +++ b/src/ImageSharp/Image/ImageBase{TColor}.cs @@ -110,9 +110,6 @@ namespace ImageSharp /// public Rectangle Bounds => new Rectangle(0, 0, this.Width, this.Height); - /// - public int FrameDelay { get; set; } - /// /// Gets the configuration providing initialization code which allows extending the library. /// @@ -187,7 +184,6 @@ namespace ImageSharp { Debug.Assert(other != null); - this.FrameDelay = other.FrameDelay; this.Configuration = other.Configuration; } diff --git a/src/ImageSharp/Image/ImageFrame{TColor}.cs b/src/ImageSharp/Image/ImageFrame{TColor}.cs index b06b10bc49..02e5b71618 100644 --- a/src/ImageSharp/Image/ImageFrame{TColor}.cs +++ b/src/ImageSharp/Image/ImageFrame{TColor}.cs @@ -13,7 +13,7 @@ namespace ImageSharp /// Represents a single frame in a animation. /// /// The pixel format. - public class ImageFrame : ImageBase + public class ImageFrame : ImageBase, IImageFrame where TColor : struct, IPackedPixel, IEquatable { /// @@ -38,6 +38,11 @@ namespace ImageSharp { } + /// + /// Gets the meta data of the frame. + /// + public ImageFrameMetaData MetaData { get; private set; } = new ImageFrameMetaData(); + /// public override string ToString() { @@ -87,5 +92,18 @@ namespace ImageSharp { return new ImageFrame(this); } + + /// + /// Copies the properties from the other . + /// + /// + /// The other to copy the properties from. + /// + private void CopyProperties(IImageFrame other) + { + base.CopyProperties(other); + + this.MetaData = new ImageFrameMetaData(other.MetaData); + } } } \ No newline at end of file diff --git a/src/ImageSharp/Metadata/IMetaData.cs b/src/ImageSharp/Metadata/IMetaData.cs new file mode 100644 index 0000000000..38fd313493 --- /dev/null +++ b/src/ImageSharp/Metadata/IMetaData.cs @@ -0,0 +1,21 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// Encapsulates the metadata of an image frame. + /// + internal interface IMetaData + { + /// + /// Gets or sets the frame delay for animated images. + /// If not 0, this field specifies the number of hundredths (1/100) of a second to + /// wait before continuing with the processing of the Data Stream. + /// The clock starts ticking immediately after the graphic is rendered. + /// + int FrameDelay { get; set; } + } +} diff --git a/src/ImageSharp/Metadata/ImageFrameMetaData.cs b/src/ImageSharp/Metadata/ImageFrameMetaData.cs new file mode 100644 index 0000000000..c2277686f1 --- /dev/null +++ b/src/ImageSharp/Metadata/ImageFrameMetaData.cs @@ -0,0 +1,44 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System.Diagnostics; + + /// + /// Encapsulates the metadata of an image frame. + /// + public sealed class ImageFrameMetaData : IMetaData + { + /// + /// Initializes a new instance of the class. + /// + internal ImageFrameMetaData() + { + } + + /// + /// Initializes a new instance of the class + /// by making a copy from other metadata. + /// + /// + /// The other to create this instance from. + /// + internal ImageFrameMetaData(ImageFrameMetaData other) + { + Debug.Assert(other != null); + + this.FrameDelay = other.FrameDelay; + } + + /// + /// Gets or sets the frame delay for animated images. + /// If not 0, this field specifies the number of hundredths (1/100) of a second to + /// wait before continuing with the processing of the Data Stream. + /// The clock starts ticking immediately after the graphic is rendered. + /// + public int FrameDelay { get; set; } + } +} diff --git a/src/ImageSharp/Metadata/ImageMetaData.cs b/src/ImageSharp/Metadata/ImageMetaData.cs index 30bd2c348e..a38899840e 100644 --- a/src/ImageSharp/Metadata/ImageMetaData.cs +++ b/src/ImageSharp/Metadata/ImageMetaData.cs @@ -11,7 +11,7 @@ namespace ImageSharp /// /// Encapsulates the metadata of an image. /// - public sealed class ImageMetaData + public sealed class ImageMetaData : IMetaData { /// /// The default horizontal resolution value (dots per inch) in x direction. @@ -51,6 +51,8 @@ namespace ImageSharp this.HorizontalResolution = other.HorizontalResolution; this.VerticalResolution = other.VerticalResolution; this.Quality = other.Quality; + this.FrameDelay = other.FrameDelay; + this.RepeatCount = other.RepeatCount; foreach (ImageProperty property in other.Properties) { @@ -110,6 +112,14 @@ namespace ImageSharp /// public ExifProfile ExifProfile { get; set; } + /// + /// Gets or sets the frame delay for animated images. + /// If not 0, this field specifies the number of hundredths (1/100) of a second to + /// wait before continuing with the processing of the Data Stream. + /// The clock starts ticking immediately after the graphic is rendered. + /// + public int FrameDelay { get; set; } + /// /// Gets the list of properties for storing meta information about this image. /// diff --git a/tests/ImageSharp.Tests/Metadata/ImageFrameMetaDataTests.cs b/tests/ImageSharp.Tests/Metadata/ImageFrameMetaDataTests.cs new file mode 100644 index 0000000000..24dd2eac56 --- /dev/null +++ b/tests/ImageSharp.Tests/Metadata/ImageFrameMetaDataTests.cs @@ -0,0 +1,26 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using Xunit; + + /// + /// Tests the class. + /// + public class ImageFrameMetaDataTests + { + [Fact] + public void ConstructorImageFrameMetaData() + { + ImageFrameMetaData metaData = new ImageFrameMetaData(); + metaData.FrameDelay = 42; + + ImageFrameMetaData clone = new ImageFrameMetaData(metaData); + + Assert.Equal(42, clone.FrameDelay); + } + } +} diff --git a/tests/ImageSharp.Tests/Metadata/ImageMetaDataTests.cs b/tests/ImageSharp.Tests/Metadata/ImageMetaDataTests.cs new file mode 100644 index 0000000000..9034b88c09 --- /dev/null +++ b/tests/ImageSharp.Tests/Metadata/ImageMetaDataTests.cs @@ -0,0 +1,42 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using Xunit; + + /// + /// Tests the class. + /// + public class ImageMetaDataTests + { + [Fact] + public void ConstructorImageMetaData() + { + ImageMetaData metaData = new ImageMetaData(); + + ExifProfile exifProfile = new ExifProfile(); + ImageProperty imageProperty = new ImageProperty("name", "value"); + + metaData.ExifProfile = exifProfile; + metaData.FrameDelay = 42; + metaData.HorizontalResolution = 4; + metaData.VerticalResolution = 2; + metaData.Properties.Add(imageProperty); + metaData.Quality = 24; + metaData.RepeatCount = 1; + + ImageMetaData clone = new ImageMetaData(metaData); + + Assert.Equal(exifProfile.ToByteArray(), clone.ExifProfile.ToByteArray()); + Assert.Equal(42, clone.FrameDelay); + Assert.Equal(4, clone.HorizontalResolution); + Assert.Equal(2, clone.VerticalResolution); + Assert.Equal(imageProperty, clone.Properties[0]); + Assert.Equal(24, clone.Quality); + Assert.Equal(1, clone.RepeatCount); + } + } +} From f7dcff1895c5353261ddb4449d9308878422b9aa Mon Sep 17 00:00:00 2001 From: Dirk Lemstra Date: Sun, 5 Feb 2017 12:34:36 +0100 Subject: [PATCH 037/142] Fixed folder name. --- src/ImageSharp/{Metadata => MetaData}/IMetaData.cs | 0 src/ImageSharp/{Metadata => MetaData}/ImageFrameMetaData.cs | 0 src/ImageSharp/{Metadata => MetaData}/ImageMetaData.cs | 0 src/ImageSharp/{Metadata => MetaData}/ImageProperty.cs | 0 .../{Metadata => MetaData}/Profiles/Exif/ExifDataType.cs | 0 src/ImageSharp/{Metadata => MetaData}/Profiles/Exif/ExifParts.cs | 0 .../{Metadata => MetaData}/Profiles/Exif/ExifProfile.cs | 0 src/ImageSharp/{Metadata => MetaData}/Profiles/Exif/ExifReader.cs | 0 src/ImageSharp/{Metadata => MetaData}/Profiles/Exif/ExifTag.cs | 0 .../Profiles/Exif/ExifTagDescriptionAttribute.cs | 0 src/ImageSharp/{Metadata => MetaData}/Profiles/Exif/ExifValue.cs | 0 src/ImageSharp/{Metadata => MetaData}/Profiles/Exif/ExifWriter.cs | 0 src/ImageSharp/{Metadata => MetaData}/Profiles/Exif/README.md | 0 .../{Metadata => MetaData}/ImageFrameMetaDataTests.cs | 0 .../ImageSharp.Tests/{Metadata => MetaData}/ImageMetaDataTests.cs | 0 .../ImageSharp.Tests/{Metadata => MetaData}/ImagePropertyTests.cs | 0 .../{Metadata => MetaData}/Profiles/Exif/ExifProfileTests.cs | 0 .../Profiles/Exif/ExifTagDescriptionAttributeTests.cs | 0 .../{Metadata => MetaData}/Profiles/Exif/ExifValueTests.cs | 0 19 files changed, 0 insertions(+), 0 deletions(-) rename src/ImageSharp/{Metadata => MetaData}/IMetaData.cs (100%) rename src/ImageSharp/{Metadata => MetaData}/ImageFrameMetaData.cs (100%) rename src/ImageSharp/{Metadata => MetaData}/ImageMetaData.cs (100%) rename src/ImageSharp/{Metadata => MetaData}/ImageProperty.cs (100%) rename src/ImageSharp/{Metadata => MetaData}/Profiles/Exif/ExifDataType.cs (100%) rename src/ImageSharp/{Metadata => MetaData}/Profiles/Exif/ExifParts.cs (100%) rename src/ImageSharp/{Metadata => MetaData}/Profiles/Exif/ExifProfile.cs (100%) rename src/ImageSharp/{Metadata => MetaData}/Profiles/Exif/ExifReader.cs (100%) rename src/ImageSharp/{Metadata => MetaData}/Profiles/Exif/ExifTag.cs (100%) rename src/ImageSharp/{Metadata => MetaData}/Profiles/Exif/ExifTagDescriptionAttribute.cs (100%) rename src/ImageSharp/{Metadata => MetaData}/Profiles/Exif/ExifValue.cs (100%) rename src/ImageSharp/{Metadata => MetaData}/Profiles/Exif/ExifWriter.cs (100%) rename src/ImageSharp/{Metadata => MetaData}/Profiles/Exif/README.md (100%) rename tests/ImageSharp.Tests/{Metadata => MetaData}/ImageFrameMetaDataTests.cs (100%) rename tests/ImageSharp.Tests/{Metadata => MetaData}/ImageMetaDataTests.cs (100%) rename tests/ImageSharp.Tests/{Metadata => MetaData}/ImagePropertyTests.cs (100%) rename tests/ImageSharp.Tests/{Metadata => MetaData}/Profiles/Exif/ExifProfileTests.cs (100%) rename tests/ImageSharp.Tests/{Metadata => MetaData}/Profiles/Exif/ExifTagDescriptionAttributeTests.cs (100%) rename tests/ImageSharp.Tests/{Metadata => MetaData}/Profiles/Exif/ExifValueTests.cs (100%) diff --git a/src/ImageSharp/Metadata/IMetaData.cs b/src/ImageSharp/MetaData/IMetaData.cs similarity index 100% rename from src/ImageSharp/Metadata/IMetaData.cs rename to src/ImageSharp/MetaData/IMetaData.cs diff --git a/src/ImageSharp/Metadata/ImageFrameMetaData.cs b/src/ImageSharp/MetaData/ImageFrameMetaData.cs similarity index 100% rename from src/ImageSharp/Metadata/ImageFrameMetaData.cs rename to src/ImageSharp/MetaData/ImageFrameMetaData.cs diff --git a/src/ImageSharp/Metadata/ImageMetaData.cs b/src/ImageSharp/MetaData/ImageMetaData.cs similarity index 100% rename from src/ImageSharp/Metadata/ImageMetaData.cs rename to src/ImageSharp/MetaData/ImageMetaData.cs diff --git a/src/ImageSharp/Metadata/ImageProperty.cs b/src/ImageSharp/MetaData/ImageProperty.cs similarity index 100% rename from src/ImageSharp/Metadata/ImageProperty.cs rename to src/ImageSharp/MetaData/ImageProperty.cs diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifDataType.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifDataType.cs similarity index 100% rename from src/ImageSharp/Metadata/Profiles/Exif/ExifDataType.cs rename to src/ImageSharp/MetaData/Profiles/Exif/ExifDataType.cs diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifParts.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifParts.cs similarity index 100% rename from src/ImageSharp/Metadata/Profiles/Exif/ExifParts.cs rename to src/ImageSharp/MetaData/Profiles/Exif/ExifParts.cs diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs similarity index 100% rename from src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs rename to src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifReader.cs similarity index 100% rename from src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs rename to src/ImageSharp/MetaData/Profiles/Exif/ExifReader.cs diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifTag.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifTag.cs similarity index 100% rename from src/ImageSharp/Metadata/Profiles/Exif/ExifTag.cs rename to src/ImageSharp/MetaData/Profiles/Exif/ExifTag.cs diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifTagDescriptionAttribute.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifTagDescriptionAttribute.cs similarity index 100% rename from src/ImageSharp/Metadata/Profiles/Exif/ExifTagDescriptionAttribute.cs rename to src/ImageSharp/MetaData/Profiles/Exif/ExifTagDescriptionAttribute.cs diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifValue.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifValue.cs similarity index 100% rename from src/ImageSharp/Metadata/Profiles/Exif/ExifValue.cs rename to src/ImageSharp/MetaData/Profiles/Exif/ExifValue.cs diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifWriter.cs similarity index 100% rename from src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs rename to src/ImageSharp/MetaData/Profiles/Exif/ExifWriter.cs diff --git a/src/ImageSharp/Metadata/Profiles/Exif/README.md b/src/ImageSharp/MetaData/Profiles/Exif/README.md similarity index 100% rename from src/ImageSharp/Metadata/Profiles/Exif/README.md rename to src/ImageSharp/MetaData/Profiles/Exif/README.md diff --git a/tests/ImageSharp.Tests/Metadata/ImageFrameMetaDataTests.cs b/tests/ImageSharp.Tests/MetaData/ImageFrameMetaDataTests.cs similarity index 100% rename from tests/ImageSharp.Tests/Metadata/ImageFrameMetaDataTests.cs rename to tests/ImageSharp.Tests/MetaData/ImageFrameMetaDataTests.cs diff --git a/tests/ImageSharp.Tests/Metadata/ImageMetaDataTests.cs b/tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs similarity index 100% rename from tests/ImageSharp.Tests/Metadata/ImageMetaDataTests.cs rename to tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs diff --git a/tests/ImageSharp.Tests/Metadata/ImagePropertyTests.cs b/tests/ImageSharp.Tests/MetaData/ImagePropertyTests.cs similarity index 100% rename from tests/ImageSharp.Tests/Metadata/ImagePropertyTests.cs rename to tests/ImageSharp.Tests/MetaData/ImagePropertyTests.cs diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs similarity index 100% rename from tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs rename to tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifTagDescriptionAttributeTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifTagDescriptionAttributeTests.cs similarity index 100% rename from tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifTagDescriptionAttributeTests.cs rename to tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifTagDescriptionAttributeTests.cs diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifValueTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifValueTests.cs similarity index 100% rename from tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifValueTests.cs rename to tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifValueTests.cs From 75b541b33fae28e256d2ef72b95aa39e23895247 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 5 Feb 2017 23:01:25 +1100 Subject: [PATCH 038/142] Remove incorrect Vectors facade. --- src/ImageSharp.Drawing.Paths/project.json | 3 +-- src/ImageSharp.Drawing/project.json | 3 +-- src/ImageSharp.Formats.Bmp/project.json | 3 +-- src/ImageSharp.Formats.Gif/project.json | 3 +-- src/ImageSharp.Formats.Jpeg/project.json | 3 +-- src/ImageSharp.Formats.Png/project.json | 3 +-- src/ImageSharp.Processing/project.json | 3 +-- src/ImageSharp/project.json | 3 +-- 8 files changed, 8 insertions(+), 16 deletions(-) diff --git a/src/ImageSharp.Drawing.Paths/project.json b/src/ImageSharp.Drawing.Paths/project.json index 1bd998ea79..ee3239e797 100644 --- a/src/ImageSharp.Drawing.Paths/project.json +++ b/src/ImageSharp.Drawing.Paths/project.json @@ -88,8 +88,7 @@ }, "frameworkAssemblies": { "System.Runtime": { "type": "build" }, - "System.Numerics": "4.0.0.0", - "System.Numerics.Vectors": "4.0.0.0" + "System.Numerics": "4.0.0.0" } } } diff --git a/src/ImageSharp.Drawing/project.json b/src/ImageSharp.Drawing/project.json index ff2900d9fa..98cf20c1d7 100644 --- a/src/ImageSharp.Drawing/project.json +++ b/src/ImageSharp.Drawing/project.json @@ -87,8 +87,7 @@ }, "frameworkAssemblies": { "System.Runtime": { "type": "build" }, - "System.Numerics": "4.0.0.0", - "System.Numerics.Vectors": "4.0.0.0" + "System.Numerics": "4.0.0.0" } } } diff --git a/src/ImageSharp.Formats.Bmp/project.json b/src/ImageSharp.Formats.Bmp/project.json index e66ecaf247..0c96fd7df5 100644 --- a/src/ImageSharp.Formats.Bmp/project.json +++ b/src/ImageSharp.Formats.Bmp/project.json @@ -85,8 +85,7 @@ }, "frameworkAssemblies": { "System.Runtime": { "type": "build" }, - "System.Numerics": "4.0.0.0", - "System.Numerics.Vectors": "4.0.0.0" + "System.Numerics": "4.0.0.0" } } } diff --git a/src/ImageSharp.Formats.Gif/project.json b/src/ImageSharp.Formats.Gif/project.json index a501fe1c67..6fe75dbcb1 100644 --- a/src/ImageSharp.Formats.Gif/project.json +++ b/src/ImageSharp.Formats.Gif/project.json @@ -85,8 +85,7 @@ }, "frameworkAssemblies": { "System.Runtime": { "type": "build" }, - "System.Numerics": "4.0.0.0", - "System.Numerics.Vectors": "4.0.0.0" + "System.Numerics": "4.0.0.0" } } } diff --git a/src/ImageSharp.Formats.Jpeg/project.json b/src/ImageSharp.Formats.Jpeg/project.json index c7e6f7e673..2d84ff053c 100644 --- a/src/ImageSharp.Formats.Jpeg/project.json +++ b/src/ImageSharp.Formats.Jpeg/project.json @@ -85,8 +85,7 @@ }, "frameworkAssemblies": { "System.Runtime": { "type": "build" }, - "System.Numerics": "4.0.0.0", - "System.Numerics.Vectors": "4.0.0.0" + "System.Numerics": "4.0.0.0" } } } diff --git a/src/ImageSharp.Formats.Png/project.json b/src/ImageSharp.Formats.Png/project.json index bd8c2a323b..a56397fb02 100644 --- a/src/ImageSharp.Formats.Png/project.json +++ b/src/ImageSharp.Formats.Png/project.json @@ -85,8 +85,7 @@ }, "frameworkAssemblies": { "System.Runtime": { "type": "build" }, - "System.Numerics": "4.0.0.0", - "System.Numerics.Vectors": "4.0.0.0" + "System.Numerics": "4.0.0.0" } } } diff --git a/src/ImageSharp.Processing/project.json b/src/ImageSharp.Processing/project.json index cf4d2fc240..910d5fc830 100644 --- a/src/ImageSharp.Processing/project.json +++ b/src/ImageSharp.Processing/project.json @@ -84,8 +84,7 @@ }, "frameworkAssemblies": { "System.Runtime": { "type": "build" }, - "System.Numerics": "4.0.0.0", - "System.Numerics.Vectors": "4.0.0.0" + "System.Numerics": "4.0.0.0" } } } diff --git a/src/ImageSharp/project.json b/src/ImageSharp/project.json index f5f0f103bc..8ad3fd71ab 100644 --- a/src/ImageSharp/project.json +++ b/src/ImageSharp/project.json @@ -81,8 +81,7 @@ }, "frameworkAssemblies": { "System.Runtime": { "type": "build" }, - "System.Numerics": "4.0.0.0", - "System.Numerics.Vectors": "4.0.0.0" + "System.Numerics": "4.0.0.0" } } } From 00f08396d7ee12689dc3f6438d47667f08745296 Mon Sep 17 00:00:00 2001 From: Dirk Lemstra Date: Sun, 5 Feb 2017 13:14:35 +0100 Subject: [PATCH 039/142] Added method to update the profile data (based on #96) --- .../JpegEncoderCore.cs | 1 + src/ImageSharp/MetaData/ImageMetaData.cs | 29 ++++++++++++++++ .../MetaData/ImageMetaDataTests.cs | 33 +++++++++++++++++++ .../Profiles/Exif/ExifProfileTests.cs | 3 ++ 4 files changed, 66 insertions(+) diff --git a/src/ImageSharp.Formats.Jpeg/JpegEncoderCore.cs b/src/ImageSharp.Formats.Jpeg/JpegEncoderCore.cs index 9cc51c7772..657470f5cf 100644 --- a/src/ImageSharp.Formats.Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp.Formats.Jpeg/JpegEncoderCore.cs @@ -706,6 +706,7 @@ namespace ImageSharp.Formats private void WriteProfiles(Image image) where TColor : struct, IPackedPixel, IEquatable { + image.MetaData.SyncProfiles(); this.WriteProfile(image.MetaData.ExifProfile); } diff --git a/src/ImageSharp/MetaData/ImageMetaData.cs b/src/ImageSharp/MetaData/ImageMetaData.cs index a38899840e..0bd0d0f789 100644 --- a/src/ImageSharp/MetaData/ImageMetaData.cs +++ b/src/ImageSharp/MetaData/ImageMetaData.cs @@ -136,5 +136,34 @@ namespace ImageSharp /// 0 means to repeat indefinitely. /// public ushort RepeatCount { get; set; } + + /// + /// Synchronizes the profiles with the current meta data. + /// + internal void SyncProfiles() + { + this.SyncExifProfile(); + } + + private void SyncExifProfile() + { + if (this.ExifProfile == null) + { + return; + } + + this.SyncExifResolution(ExifTag.XResolution, this.HorizontalResolution); + this.SyncExifResolution(ExifTag.YResolution, this.VerticalResolution); + } + + private void SyncExifResolution(ExifTag tag, double resolution) + { + ExifValue value = this.ExifProfile.GetValue(tag); + if (value != null) + { + Rational newResolution = new Rational(resolution, false); + this.ExifProfile.SetValue(tag, newResolution); + } + } } } diff --git a/tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs b/tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs index 9034b88c09..d9bd52c730 100644 --- a/tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs +++ b/tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs @@ -38,5 +38,38 @@ namespace ImageSharp.Tests Assert.Equal(24, clone.Quality); Assert.Equal(1, clone.RepeatCount); } + + [Fact] + public void SyncProfiles() + { + ExifProfile exifProfile = new ExifProfile(); + exifProfile.SetValue(ExifTag.XResolution, new Rational(200)); + exifProfile.SetValue(ExifTag.YResolution, new Rational(300)); + + Image image = new Image(1, 1); + image.MetaData.ExifProfile = exifProfile; + image.MetaData.HorizontalResolution = 200; + image.MetaData.VerticalResolution = 300; + + image.MetaData.HorizontalResolution = 100; + + Assert.Equal(200, ((Rational)image.MetaData.ExifProfile.GetValue(ExifTag.XResolution).Value).ToDouble()); + Assert.Equal(300, ((Rational)image.MetaData.ExifProfile.GetValue(ExifTag.YResolution).Value).ToDouble()); + + image.MetaData.SyncProfiles(); + + Assert.Equal(100, ((Rational)image.MetaData.ExifProfile.GetValue(ExifTag.XResolution).Value).ToDouble()); + Assert.Equal(300, ((Rational)image.MetaData.ExifProfile.GetValue(ExifTag.YResolution).Value).ToDouble()); + + image.MetaData.VerticalResolution = 150; + + Assert.Equal(100, ((Rational)image.MetaData.ExifProfile.GetValue(ExifTag.XResolution).Value).ToDouble()); + Assert.Equal(300, ((Rational)image.MetaData.ExifProfile.GetValue(ExifTag.YResolution).Value).ToDouble()); + + image.MetaData.SyncProfiles(); + + Assert.Equal(100, ((Rational)image.MetaData.ExifProfile.GetValue(ExifTag.XResolution).Value).ToDouble()); + Assert.Equal(150, ((Rational)image.MetaData.ExifProfile.GetValue(ExifTag.YResolution).Value).ToDouble()); + } } } diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs index 61307da205..f4a2bcf401 100644 --- a/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs +++ b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs @@ -150,6 +150,9 @@ namespace ImageSharp.Tests image.MetaData.ExifProfile.SetValue(ExifTag.XResolution, new Rational(150.0)); + // We also need to change this value because this overrides XResolution when the image is written. + image.MetaData.HorizontalResolution = 150.0; + value = image.MetaData.ExifProfile.GetValue(ExifTag.XResolution); TestValue(value, new Rational(150, 1)); From 6b9d853b845c36714008b2f1d22ec72b46131d0f Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 5 Feb 2017 23:34:47 +1100 Subject: [PATCH 040/142] A little cleanup --- src/ImageSharp.Drawing.Paths/DrawBeziers.cs | 4 +- src/ImageSharp.Drawing.Paths/DrawLines.cs | 2 +- src/ImageSharp.Drawing.Paths/DrawPath.cs | 4 +- src/ImageSharp.Drawing.Paths/DrawPolygon.cs | 2 +- src/ImageSharp.Drawing.Paths/DrawRectangle.cs | 3 - src/ImageSharp.Drawing.Paths/DrawShape.cs | 4 +- src/ImageSharp.Drawing.Paths/FillPaths.cs | 3 +- src/ImageSharp.Drawing.Paths/FillPolygon.cs | 1 - src/ImageSharp.Drawing.Paths/FillRectangle.cs | 1 - src/ImageSharp.Drawing.Paths/FillShape.cs | 5 +- .../RectangleExtensions.cs | 9 +-- src/ImageSharp.Drawing.Paths/ShapePath.cs | 24 ++---- src/ImageSharp.Drawing.Paths/ShapeRegion.cs | 19 +---- src/ImageSharp.Drawing/DrawPath.cs | 2 +- src/ImageSharp.Drawing/FillRegion.cs | 2 +- src/ImageSharp.Drawing/PointInfo.cs | 3 - .../Processors/DrawPathProcessor.cs | 73 +++++++++---------- src/ImageSharp.Drawing/Region.cs | 6 +- 18 files changed, 57 insertions(+), 110 deletions(-) diff --git a/src/ImageSharp.Drawing.Paths/DrawBeziers.cs b/src/ImageSharp.Drawing.Paths/DrawBeziers.cs index 1a511d84d7..ef11a3bac5 100644 --- a/src/ImageSharp.Drawing.Paths/DrawBeziers.cs +++ b/src/ImageSharp.Drawing.Paths/DrawBeziers.cs @@ -10,10 +10,8 @@ namespace ImageSharp using Drawing; using Drawing.Brushes; using Drawing.Pens; - using Drawing.Processors; - using SixLabors.Shapes; - using Path = SixLabors.Shapes.Path; + using SixLabors.Shapes; /// /// Extension methods for the type. diff --git a/src/ImageSharp.Drawing.Paths/DrawLines.cs b/src/ImageSharp.Drawing.Paths/DrawLines.cs index f6f8d8f6c9..fc046d6276 100644 --- a/src/ImageSharp.Drawing.Paths/DrawLines.cs +++ b/src/ImageSharp.Drawing.Paths/DrawLines.cs @@ -10,7 +10,7 @@ namespace ImageSharp using Drawing; using Drawing.Brushes; using Drawing.Pens; - using Drawing.Processors; + using SixLabors.Shapes; /// diff --git a/src/ImageSharp.Drawing.Paths/DrawPath.cs b/src/ImageSharp.Drawing.Paths/DrawPath.cs index 4e4275df6e..29b389fa24 100644 --- a/src/ImageSharp.Drawing.Paths/DrawPath.cs +++ b/src/ImageSharp.Drawing.Paths/DrawPath.cs @@ -6,11 +6,11 @@ namespace ImageSharp { using System; - using System.Numerics; + using Drawing; using Drawing.Brushes; using Drawing.Pens; - using Drawing.Processors; + using SixLabors.Shapes; /// diff --git a/src/ImageSharp.Drawing.Paths/DrawPolygon.cs b/src/ImageSharp.Drawing.Paths/DrawPolygon.cs index 28785e5cbf..6b3a6f1f36 100644 --- a/src/ImageSharp.Drawing.Paths/DrawPolygon.cs +++ b/src/ImageSharp.Drawing.Paths/DrawPolygon.cs @@ -10,7 +10,7 @@ namespace ImageSharp using Drawing; using Drawing.Brushes; using Drawing.Pens; - using Drawing.Processors; + using SixLabors.Shapes; /// diff --git a/src/ImageSharp.Drawing.Paths/DrawRectangle.cs b/src/ImageSharp.Drawing.Paths/DrawRectangle.cs index 4e8ec7135b..d42fee5dc1 100644 --- a/src/ImageSharp.Drawing.Paths/DrawRectangle.cs +++ b/src/ImageSharp.Drawing.Paths/DrawRectangle.cs @@ -10,9 +10,6 @@ namespace ImageSharp using Drawing; using Drawing.Brushes; using Drawing.Pens; - using Drawing.Processors; - - using SixLabors.Shapes; /// /// Extension methods for the type. diff --git a/src/ImageSharp.Drawing.Paths/DrawShape.cs b/src/ImageSharp.Drawing.Paths/DrawShape.cs index 6ddce65b98..748bb6a11d 100644 --- a/src/ImageSharp.Drawing.Paths/DrawShape.cs +++ b/src/ImageSharp.Drawing.Paths/DrawShape.cs @@ -6,11 +6,11 @@ namespace ImageSharp { using System; - using System.Numerics; + using Drawing; using Drawing.Brushes; using Drawing.Pens; - using Drawing.Processors; + using SixLabors.Shapes; /// diff --git a/src/ImageSharp.Drawing.Paths/FillPaths.cs b/src/ImageSharp.Drawing.Paths/FillPaths.cs index 09a799794c..1dd0b0a3a8 100644 --- a/src/ImageSharp.Drawing.Paths/FillPaths.cs +++ b/src/ImageSharp.Drawing.Paths/FillPaths.cs @@ -6,10 +6,9 @@ namespace ImageSharp { using System; - using System.Numerics; + using Drawing; using Drawing.Brushes; - using Drawing.Processors; using SixLabors.Shapes; diff --git a/src/ImageSharp.Drawing.Paths/FillPolygon.cs b/src/ImageSharp.Drawing.Paths/FillPolygon.cs index a609ceed24..b41267b9e2 100644 --- a/src/ImageSharp.Drawing.Paths/FillPolygon.cs +++ b/src/ImageSharp.Drawing.Paths/FillPolygon.cs @@ -9,7 +9,6 @@ namespace ImageSharp using System.Numerics; using Drawing; using Drawing.Brushes; - using Drawing.Processors; using SixLabors.Shapes; diff --git a/src/ImageSharp.Drawing.Paths/FillRectangle.cs b/src/ImageSharp.Drawing.Paths/FillRectangle.cs index 579a288a48..3b2cef6131 100644 --- a/src/ImageSharp.Drawing.Paths/FillRectangle.cs +++ b/src/ImageSharp.Drawing.Paths/FillRectangle.cs @@ -9,7 +9,6 @@ namespace ImageSharp using Drawing; using Drawing.Brushes; - using Drawing.Processors; /// /// Extension methods for the type. diff --git a/src/ImageSharp.Drawing.Paths/FillShape.cs b/src/ImageSharp.Drawing.Paths/FillShape.cs index 52a3c60dd5..f143e75b72 100644 --- a/src/ImageSharp.Drawing.Paths/FillShape.cs +++ b/src/ImageSharp.Drawing.Paths/FillShape.cs @@ -6,10 +6,9 @@ namespace ImageSharp { using System; - using System.Numerics; + using Drawing; using Drawing.Brushes; - using Drawing.Processors; using SixLabors.Shapes; @@ -19,7 +18,7 @@ namespace ImageSharp public static partial class ImageExtensions { /// - /// Flood fills the image in the shape of the provided polygon with the specified brush.. + /// Flood fills the image in the shape of the provided polygon with the specified brush. /// /// The type of the color. /// The image this method extends. diff --git a/src/ImageSharp.Drawing.Paths/RectangleExtensions.cs b/src/ImageSharp.Drawing.Paths/RectangleExtensions.cs index 1faa6469ad..2fa5fe43f2 100644 --- a/src/ImageSharp.Drawing.Paths/RectangleExtensions.cs +++ b/src/ImageSharp.Drawing.Paths/RectangleExtensions.cs @@ -3,16 +3,9 @@ // Licensed under the Apache License, Version 2.0. // -namespace ImageSharp.Drawing.Processors +namespace ImageSharp.Drawing { using System; - using System.Buffers; - using System.Numerics; - using System.Threading.Tasks; - using Drawing; - using ImageSharp.Processing; - using SixLabors.Shapes; - using Rectangle = ImageSharp.Rectangle; /// /// Extension methods for helping to bridge Shaper2D and ImageSharp primitives. diff --git a/src/ImageSharp.Drawing.Paths/ShapePath.cs b/src/ImageSharp.Drawing.Paths/ShapePath.cs index 34e77b8074..5ea38a365b 100644 --- a/src/ImageSharp.Drawing.Paths/ShapePath.cs +++ b/src/ImageSharp.Drawing.Paths/ShapePath.cs @@ -9,16 +9,14 @@ namespace ImageSharp.Drawing using System.Collections.Immutable; using System.Numerics; - using ImageSharp.Drawing.Processors; - using SixLabors.Shapes; using Rectangle = ImageSharp.Rectangle; /// - /// A drawable mapping between a / and a drawable/fillable region. + /// A drawable mapping between a / and a drawable/fillable region. /// - internal class ShapePath : ImageSharp.Drawing.Drawable + internal class ShapePath : Drawable { /// /// The fillable shape @@ -59,9 +57,6 @@ namespace ImageSharp.Drawing /// /// Gets the drawable paths /// - /// - /// The paths. - /// public ImmutableArray Paths { get; } /// @@ -78,12 +73,7 @@ namespace ImageSharp.Drawing Vector2[] innerbuffer = ArrayPool.Shared.Rent(length); try { - int count = this.shape.FindIntersections( - start, - end, - innerbuffer, - length, - 0); + int count = this.shape.FindIntersections(start, end, innerbuffer, length, 0); for (int i = 0; i < count; i++) { @@ -106,12 +96,7 @@ namespace ImageSharp.Drawing Vector2[] innerbuffer = ArrayPool.Shared.Rent(length); try { - int count = this.shape.FindIntersections( - start, - end, - innerbuffer, - length, - 0); + int count = this.shape.FindIntersections(start, end, innerbuffer, length, 0); for (int i = 0; i < count; i++) { @@ -133,6 +118,7 @@ namespace ImageSharp.Drawing float distanceFromPath = float.MaxValue; float distanceAlongPath = 0; + // ReSharper disable once ForCanBeConvertedToForeach for (int i = 0; i < this.Paths.Length; i++) { SixLabors.Shapes.PointInfo p = this.Paths[i].Distance(point); diff --git a/src/ImageSharp.Drawing.Paths/ShapeRegion.cs b/src/ImageSharp.Drawing.Paths/ShapeRegion.cs index 5adec67908..dc035e3b05 100644 --- a/src/ImageSharp.Drawing.Paths/ShapeRegion.cs +++ b/src/ImageSharp.Drawing.Paths/ShapeRegion.cs @@ -6,17 +6,14 @@ namespace ImageSharp.Drawing { using System.Buffers; - using System.Collections.Immutable; using System.Numerics; - using ImageSharp.Drawing.Processors; - using SixLabors.Shapes; using Rectangle = ImageSharp.Rectangle; /// - /// A drawable mapping between a / and a drawable/fillable region. + /// A drawable mapping between a / and a drawable/fillable region. /// internal class ShapeRegion : Region { @@ -58,12 +55,7 @@ namespace ImageSharp.Drawing Vector2[] innerbuffer = ArrayPool.Shared.Rent(length); try { - int count = this.Shape.FindIntersections( - start, - end, - innerbuffer, - length, - 0); + int count = this.Shape.FindIntersections(start, end, innerbuffer, length, 0); for (int i = 0; i < count; i++) { @@ -86,12 +78,7 @@ namespace ImageSharp.Drawing Vector2[] innerbuffer = ArrayPool.Shared.Rent(length); try { - int count = this.Shape.FindIntersections( - start, - end, - innerbuffer, - length, - 0); + int count = this.Shape.FindIntersections(start, end, innerbuffer, length, 0); for (int i = 0; i < count; i++) { diff --git a/src/ImageSharp.Drawing/DrawPath.cs b/src/ImageSharp.Drawing/DrawPath.cs index d92731270f..75a0d81575 100644 --- a/src/ImageSharp.Drawing/DrawPath.cs +++ b/src/ImageSharp.Drawing/DrawPath.cs @@ -6,7 +6,7 @@ namespace ImageSharp { using System; - using System.Numerics; + using Drawing; using Drawing.Brushes; using Drawing.Pens; diff --git a/src/ImageSharp.Drawing/FillRegion.cs b/src/ImageSharp.Drawing/FillRegion.cs index fbbf659d1c..6faf519af1 100644 --- a/src/ImageSharp.Drawing/FillRegion.cs +++ b/src/ImageSharp.Drawing/FillRegion.cs @@ -6,7 +6,7 @@ namespace ImageSharp { using System; - using System.Numerics; + using Drawing; using Drawing.Brushes; using Drawing.Processors; diff --git a/src/ImageSharp.Drawing/PointInfo.cs b/src/ImageSharp.Drawing/PointInfo.cs index e222a81469..7eff24fac8 100644 --- a/src/ImageSharp.Drawing/PointInfo.cs +++ b/src/ImageSharp.Drawing/PointInfo.cs @@ -5,9 +5,6 @@ namespace ImageSharp.Drawing { - using System; - using System.Numerics; - /// /// Returns details about how far away from the inside of a shape and the color the pixel could be. /// diff --git a/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs b/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs index b4aa7fba5a..83ae9521cb 100644 --- a/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs @@ -6,13 +6,11 @@ namespace ImageSharp.Drawing.Processors { using System; - using System.Collections.Generic; - using System.Linq; using System.Numerics; using System.Threading.Tasks; + using ImageSharp.Processing; using Pens; - using Rectangle = ImageSharp.Rectangle; /// /// Draws a path using the processor pipeline @@ -39,27 +37,18 @@ namespace ImageSharp.Drawing.Processors } /// - /// Gets the options. + /// Gets the graphics options. /// - /// - /// The options. - /// public GraphicsOptions Options { get; } /// /// Gets the pen. /// - /// - /// The pen. - /// public IPen Pen { get; } /// /// Gets the path. /// - /// - /// The path. - /// public Drawable Path { get; } /// @@ -68,7 +57,7 @@ namespace ImageSharp.Drawing.Processors using (PixelAccessor sourcePixels = source.Lock()) using (PenApplicator applicator = this.Pen.CreateApplicator(sourcePixels, this.Path.Bounds)) { - var rect = RectangleF.Ceiling(applicator.RequiredRegion); + Rectangle rect = RectangleF.Ceiling(applicator.RequiredRegion); int polyStartY = rect.Y - PaddingFactor; int polyEndY = rect.Bottom + PaddingFactor; @@ -98,49 +87,53 @@ namespace ImageSharp.Drawing.Processors } Parallel.For( - minY, - maxY, - this.ParallelOptions, - (int y) => - { - int offsetY = y - polyStartY; - - for (int x = minX; x < maxX; x++) + minY, + maxY, + this.ParallelOptions, + y => { - // TODO add find intersections code to skip and scan large regions of this. - int offsetX = x - startX; - var info = this.Path.GetPointInfo(offsetX, offsetY); + int offsetY = y - polyStartY; - var color = applicator.GetColor(offsetX, offsetY, info); + for (int x = minX; x < maxX; x++) + { + // TODO add find intersections code to skip and scan large regions of this. + int offsetX = x - startX; + PointInfo info = this.Path.GetPointInfo(offsetX, offsetY); - var opacity = this.Opacity(color.DistanceFromElement); + ColoredPointInfo color = applicator.GetColor(offsetX, offsetY, info); - if (opacity > Constants.Epsilon) - { - int offsetColorX = x - minX; + float opacity = this.Opacity(color.DistanceFromElement); - Vector4 backgroundVector = sourcePixels[offsetX, offsetY].ToVector4(); - Vector4 sourceVector = color.Color.ToVector4(); + if (opacity > Constants.Epsilon) + { + Vector4 backgroundVector = sourcePixels[offsetX, offsetY].ToVector4(); + Vector4 sourceVector = color.Color.ToVector4(); - var finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity); - finalColor.W = backgroundVector.W; + Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity); + finalColor.W = backgroundVector.W; - TColor packed = default(TColor); - packed.PackFromVector4(finalColor); - sourcePixels[offsetX, offsetY] = packed; + TColor packed = default(TColor); + packed.PackFromVector4(finalColor); + sourcePixels[offsetX, offsetY] = packed; + } } - } - }); + }); } } + /// + /// Returns the correct opacity for the given distance. + /// + /// Thw distance from the central point. + /// The private float Opacity(float distance) { if (distance <= 0) { return 1; } - else if (this.Options.Antialias && distance < AntialiasFactor) + + if (this.Options.Antialias && distance < AntialiasFactor) { return 1 - (distance / AntialiasFactor); } diff --git a/src/ImageSharp.Drawing/Region.cs b/src/ImageSharp.Drawing/Region.cs index 81f3dca5bd..fe1dc52221 100644 --- a/src/ImageSharp.Drawing/Region.cs +++ b/src/ImageSharp.Drawing/Region.cs @@ -16,17 +16,17 @@ namespace ImageSharp.Drawing public abstract int MaxIntersections { get; } /// - /// Gets the bounding box that entirly surrounds this region. + /// Gets the bounding box that entirely surrounds this region. /// /// - /// This should always contains all possible points returned from eather or . + /// This should always contains all possible points returned from either or . /// public abstract Rectangle Bounds { get; } /// /// Scans the X axis for intersections. /// - /// The position along the X axies to find intersections. + /// The position along the X axis to find intersections. /// The buffer. /// The length. /// The offset. From 50edd0c7994c40d2b4dc7df4ccf8a7f3830ea2df Mon Sep 17 00:00:00 2001 From: Dirk Lemstra Date: Sun, 5 Feb 2017 14:54:43 +0100 Subject: [PATCH 041/142] Added unit test for Horizontal and Vertical resolution. --- src/ImageSharp/MetaData/ImageMetaData.cs | 4 +-- .../MetaData/ImageMetaDataTests.cs | 32 +++++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/MetaData/ImageMetaData.cs b/src/ImageSharp/MetaData/ImageMetaData.cs index 0bd0d0f789..f4aef98632 100644 --- a/src/ImageSharp/MetaData/ImageMetaData.cs +++ b/src/ImageSharp/MetaData/ImageMetaData.cs @@ -79,7 +79,7 @@ namespace ImageSharp set { - if (value >= 0) + if (value > 0) { this.horizontalResolution = value; } @@ -100,7 +100,7 @@ namespace ImageSharp set { - if (value >= 0) + if (value > 0) { this.verticalResolution = value; } diff --git a/tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs b/tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs index d9bd52c730..60e04e18db 100644 --- a/tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs +++ b/tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs @@ -39,6 +39,38 @@ namespace ImageSharp.Tests Assert.Equal(1, clone.RepeatCount); } + [Fact] + public void HorizontalResolution() + { + ImageMetaData metaData = new ImageMetaData(); + Assert.Equal(96, metaData.HorizontalResolution); + + metaData.HorizontalResolution=0; + Assert.Equal(96, metaData.HorizontalResolution); + + metaData.HorizontalResolution=-1; + Assert.Equal(96, metaData.HorizontalResolution); + + metaData.HorizontalResolution=1; + Assert.Equal(1, metaData.HorizontalResolution); + } + + [Fact] + public void VerticalResolution() + { + ImageMetaData metaData = new ImageMetaData(); + Assert.Equal(96, metaData.VerticalResolution); + + metaData.VerticalResolution = 0; + Assert.Equal(96, metaData.VerticalResolution); + + metaData.VerticalResolution = -1; + Assert.Equal(96, metaData.VerticalResolution); + + metaData.VerticalResolution = 1; + Assert.Equal(1, metaData.VerticalResolution); + } + [Fact] public void SyncProfiles() { From 351f92812aabfc8827ae7e3c37c64c672aa3bf33 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Sun, 5 Feb 2017 20:35:49 +0000 Subject: [PATCH 042/142] update SixLabors.Shapes version fixes missing scan lane while clipping internal corner. --- src/ImageSharp.Drawing.Paths/project.json | 2 +- .../Processors/FillRegionProcessor.cs | 28 +------------------ .../Drawing/SolidComplexPolygonTests.cs | 7 +++-- 3 files changed, 7 insertions(+), 30 deletions(-) diff --git a/src/ImageSharp.Drawing.Paths/project.json b/src/ImageSharp.Drawing.Paths/project.json index ee3239e797..c903f805af 100644 --- a/src/ImageSharp.Drawing.Paths/project.json +++ b/src/ImageSharp.Drawing.Paths/project.json @@ -44,7 +44,7 @@ "ImageSharp.Drawing": { "target": "project" }, - "SixLabors.Shapes": "0.1.0-alpha0003", + "SixLabors.Shapes": "0.1.0-alpha0004", "StyleCop.Analyzers": { "version": "1.0.0", "type": "build" diff --git a/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs b/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs index 4f1ee34ac1..67313d84b6 100644 --- a/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs @@ -98,20 +98,7 @@ namespace ImageSharp.Drawing.Processors int pointsFound = this.Region.ScanY(y, buffer, maxIntersections, 0); if (pointsFound == 0) { - // nothign on this line skip - return; - } - - if (pointsFound == 1 && maxIntersections > 1) - { - // we must have clipped a corner lets just duplicate it into point 2 and continue :) - buffer[1] = buffer[0]; - pointsFound++; - } - - if (pointsFound % 2 == 1) - { - // we seem to have just clipped a corner lets just skip it + // nothing on this line skip return; } @@ -253,19 +240,6 @@ namespace ImageSharp.Drawing.Processors return; } - if (pointsFound == 1 && maxIntersections > 1) - { - // we must have clipped a corner lets just duplicate it into point 2 and continue :) - buffer[1] = buffer[0]; - pointsFound++; - } - - if (pointsFound % 2 == 1) - { - // we seem to have just clipped a corner lets just skip it - return; - } - QuickSort(buffer, pointsFound); int currentIntersection = 0; diff --git a/tests/ImageSharp.Tests/Drawing/SolidComplexPolygonTests.cs b/tests/ImageSharp.Tests/Drawing/SolidComplexPolygonTests.cs index 98ec9ff83e..cd3182d6c2 100644 --- a/tests/ImageSharp.Tests/Drawing/SolidComplexPolygonTests.cs +++ b/tests/ImageSharp.Tests/Drawing/SolidComplexPolygonTests.cs @@ -28,14 +28,15 @@ namespace ImageSharp.Tests.Drawing new Vector2(37, 85), new Vector2(93, 85), new Vector2(65, 137))); - + var clipped = simplePath.Clip(hole1); + // var clipped = new Rectangle(10, 10, 100, 100).Clip(new Rectangle(20, 0, 20, 20)); using (Image image = new Image(500, 500)) { using (FileStream output = File.OpenWrite($"{path}/Simple.png")) { image .BackgroundColor(Color.Blue) - .Fill(Color.HotPink, simplePath.Clip(hole1)) + .Fill(Color.HotPink, clipped) .Save(output); } @@ -45,6 +46,8 @@ namespace ImageSharp.Tests.Drawing Assert.Equal(Color.HotPink, sourcePixels[200, 150]); + Assert.Equal(Color.HotPink, sourcePixels[70, 137]); + Assert.Equal(Color.HotPink, sourcePixels[50, 50]); Assert.Equal(Color.HotPink, sourcePixels[35, 100]); From 2b80068673213028d889deb22e84b017c90a88dd Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 7 Feb 2017 02:10:13 +0100 Subject: [PATCH 043/142] [MethodImpl(MethodImplOptions.AggressiveInlining)] on packedpixel primitives --- src/ImageSharp/Colors/Color.cs | 23 ++++++++++++++++++ src/ImageSharp/Colors/PackedPixel/Alpha8.cs | 13 ++++++++++ src/ImageSharp/Colors/PackedPixel/Argb.cs | 24 +++++++++++++++++++ src/ImageSharp/Colors/PackedPixel/Bgr565.cs | 14 +++++++++++ src/ImageSharp/Colors/PackedPixel/Bgra4444.cs | 13 ++++++++++ src/ImageSharp/Colors/PackedPixel/Bgra5551.cs | 13 ++++++++++ src/ImageSharp/Colors/PackedPixel/Byte4.cs | 14 +++++++++++ .../Colors/PackedPixel/HalfSingle.cs | 13 ++++++++++ .../Colors/PackedPixel/HalfTypeHelper.cs | 2 ++ .../Colors/PackedPixel/HalfVector2.cs | 14 +++++++++++ .../Colors/PackedPixel/HalfVector4.cs | 13 ++++++++++ .../Colors/PackedPixel/NormalizedByte2.cs | 14 +++++++++++ .../Colors/PackedPixel/NormalizedByte4.cs | 13 ++++++++++ .../Colors/PackedPixel/NormalizedShort2.cs | 14 +++++++++++ .../Colors/PackedPixel/NormalizedShort4.cs | 13 ++++++++++ src/ImageSharp/Colors/PackedPixel/Rg32.cs | 14 +++++++++++ .../Colors/PackedPixel/Rgba1010102.cs | 13 ++++++++++ src/ImageSharp/Colors/PackedPixel/Rgba64.cs | 13 ++++++++++ src/ImageSharp/Colors/PackedPixel/Short2.cs | 14 +++++++++++ src/ImageSharp/Colors/PackedPixel/Short4.cs | 13 ++++++++++ 20 files changed, 277 insertions(+) diff --git a/src/ImageSharp/Colors/Color.cs b/src/ImageSharp/Colors/Color.cs index 036ee14c15..8d6ce954c2 100644 --- a/src/ImageSharp/Colors/Color.cs +++ b/src/ImageSharp/Colors/Color.cs @@ -8,6 +8,7 @@ namespace ImageSharp using System; using System.Globalization; using System.Numerics; + using System.Runtime.CompilerServices; /// /// Packed pixel type containing four 8-bit unsigned normalized values ranging from 0 to 255. @@ -138,11 +139,13 @@ namespace ImageSharp /// public byte R { + [MethodImpl(MethodImplOptions.AggressiveInlining)] get { return (byte)(this.packedValue >> RedShift); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] set { this.packedValue = this.packedValue & 0xFFFFFF00 | (uint)value << RedShift; @@ -154,11 +157,13 @@ namespace ImageSharp /// public byte G { + [MethodImpl(MethodImplOptions.AggressiveInlining)] get { return (byte)(this.packedValue >> GreenShift); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] set { this.packedValue = this.packedValue & 0xFFFF00FF | (uint)value << GreenShift; @@ -170,11 +175,13 @@ namespace ImageSharp /// public byte B { + [MethodImpl(MethodImplOptions.AggressiveInlining)] get { return (byte)(this.packedValue >> BlueShift); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] set { this.packedValue = this.packedValue & 0xFF00FFFF | (uint)value << BlueShift; @@ -186,11 +193,13 @@ namespace ImageSharp /// public byte A { + [MethodImpl(MethodImplOptions.AggressiveInlining)] get { return (byte)(this.packedValue >> AlphaShift); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] set { this.packedValue = this.packedValue & 0x00FFFFFF | (uint)value << AlphaShift; @@ -223,6 +232,7 @@ namespace ImageSharp /// /// True if the parameter is equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(Color left, Color right) { return left.packedValue == right.packedValue; @@ -236,6 +246,7 @@ namespace ImageSharp /// /// True if the parameter is not equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(Color left, Color right) { return left.packedValue != right.packedValue; @@ -257,6 +268,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromBytes(byte x, byte y, byte z, byte w) { this.packedValue = Pack(x, y, z, w); @@ -273,6 +285,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzBytes(byte[] bytes, int startIndex) { bytes[startIndex] = this.R; @@ -281,6 +294,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzwBytes(byte[] bytes, int startIndex) { bytes[startIndex] = this.R; @@ -290,6 +304,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxBytes(byte[] bytes, int startIndex) { bytes[startIndex] = this.B; @@ -298,6 +313,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxwBytes(byte[] bytes, int startIndex) { bytes[startIndex] = this.B; @@ -307,12 +323,14 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromVector4(Vector4 vector) { this.packedValue = Pack(ref vector); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ToVector4() { return new Vector4(this.R, this.G, this.B, this.A) / MaxBytes; @@ -325,6 +343,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(Color other) { return this.packedValue == other.packedValue; @@ -350,6 +369,7 @@ namespace ImageSharp /// /// The vector containing the values to pack. /// The containing the packed values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static uint Pack(ref Vector4 vector) { vector = Vector4.Clamp(vector, Vector4.Zero, Vector4.One); @@ -366,6 +386,7 @@ namespace ImageSharp /// /// The vector containing the values to pack. /// The containing the packed values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static uint Pack(ref Vector3 vector) { Vector4 value = new Vector4(vector, 1); @@ -380,6 +401,7 @@ namespace ImageSharp /// The z-component /// The w-component /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static uint Pack(float x, float y, float z, float w) { Vector4 value = new Vector4(x, y, z, w); @@ -394,6 +416,7 @@ namespace ImageSharp /// The z-component /// The w-component /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static uint Pack(byte x, byte y, byte z, byte w) { return (uint)(x << RedShift | y << GreenShift | z << BlueShift | w << AlphaShift); diff --git a/src/ImageSharp/Colors/PackedPixel/Alpha8.cs b/src/ImageSharp/Colors/PackedPixel/Alpha8.cs index 361fe5b9e2..95e620df01 100644 --- a/src/ImageSharp/Colors/PackedPixel/Alpha8.cs +++ b/src/ImageSharp/Colors/PackedPixel/Alpha8.cs @@ -7,6 +7,7 @@ namespace ImageSharp { using System; using System.Numerics; + using System.Runtime.CompilerServices; /// /// Packed pixel type containing a single 8 bit normalized W values that is ranging from 0 to 1. @@ -37,6 +38,7 @@ namespace ImageSharp /// /// True if the parameter is equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(Alpha8 left, Alpha8 right) { return left.PackedValue == right.PackedValue; @@ -50,30 +52,35 @@ namespace ImageSharp /// /// True if the parameter is not equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(Alpha8 left, Alpha8 right) { return left.PackedValue != right.PackedValue; } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromVector4(Vector4 vector) { this.PackedValue = Pack(vector.W); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ToVector4() { return new Vector4(0, 0, 0, this.PackedValue / 255F); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromBytes(byte x, byte y, byte z, byte w) { this.PackedValue = w; } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzBytes(byte[] bytes, int startIndex) { bytes[startIndex] = 0; @@ -82,6 +89,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzwBytes(byte[] bytes, int startIndex) { bytes[startIndex] = 0; @@ -91,6 +99,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxBytes(byte[] bytes, int startIndex) { bytes[startIndex] = 0; @@ -99,6 +108,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxwBytes(byte[] bytes, int startIndex) { bytes[startIndex] = 0; @@ -122,6 +132,7 @@ namespace ImageSharp /// /// The Alpha8 packed vector to compare. /// True if the packed vectors are equal. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(Alpha8 other) { return this.PackedValue == other.PackedValue; @@ -137,6 +148,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode() { return this.PackedValue.GetHashCode(); @@ -147,6 +159,7 @@ namespace ImageSharp /// /// The float containing the value to pack. /// The containing the packed values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static byte Pack(float alpha) { return (byte)Math.Round(alpha.Clamp(0, 1) * 255F); diff --git a/src/ImageSharp/Colors/PackedPixel/Argb.cs b/src/ImageSharp/Colors/PackedPixel/Argb.cs index 8ab8e3f43f..432011702d 100644 --- a/src/ImageSharp/Colors/PackedPixel/Argb.cs +++ b/src/ImageSharp/Colors/PackedPixel/Argb.cs @@ -7,6 +7,7 @@ namespace ImageSharp { using System; using System.Numerics; + using System.Runtime.CompilerServices; /// /// Packed pixel type containing four 8-bit unsigned normalized values ranging from 0 to 255. @@ -113,11 +114,13 @@ namespace ImageSharp /// public byte R { + [MethodImpl(MethodImplOptions.AggressiveInlining)] get { return (byte)(this.PackedValue >> RedShift); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] set { this.PackedValue = this.PackedValue & 0xFF00FFFF | (uint)value << RedShift; @@ -129,11 +132,13 @@ namespace ImageSharp /// public byte G { + [MethodImpl(MethodImplOptions.AggressiveInlining)] get { return (byte)(this.PackedValue >> GreenShift); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] set { this.PackedValue = this.PackedValue & 0xFFFF00FF | (uint)value << GreenShift; @@ -145,11 +150,13 @@ namespace ImageSharp /// public byte B { + [MethodImpl(MethodImplOptions.AggressiveInlining)] get { return (byte)(this.PackedValue >> BlueShift); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] set { this.PackedValue = this.PackedValue & 0xFFFFFF00 | (uint)value << BlueShift; @@ -161,11 +168,13 @@ namespace ImageSharp /// public byte A { + [MethodImpl(MethodImplOptions.AggressiveInlining)] get { return (byte)(this.PackedValue >> AlphaShift); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] set { this.PackedValue = this.PackedValue & 0x00FFFFFF | (uint)value << AlphaShift; @@ -184,6 +193,7 @@ namespace ImageSharp /// /// True if the parameter is equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(Argb left, Argb right) { return left.PackedValue == right.PackedValue; @@ -197,30 +207,35 @@ namespace ImageSharp /// /// True if the parameter is not equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(Argb left, Argb right) { return left.PackedValue != right.PackedValue; } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromVector4(Vector4 vector) { this.PackedValue = Pack(ref vector); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ToVector4() { return new Vector4(this.R, this.G, this.B, this.A) / MaxBytes; } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromBytes(byte x, byte y, byte z, byte w) { this.PackedValue = Pack(x, y, z, w); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzBytes(byte[] bytes, int startIndex) { bytes[startIndex] = this.R; @@ -229,6 +244,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzwBytes(byte[] bytes, int startIndex) { bytes[startIndex] = this.R; @@ -238,6 +254,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxBytes(byte[] bytes, int startIndex) { bytes[startIndex] = this.B; @@ -246,6 +263,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxwBytes(byte[] bytes, int startIndex) { bytes[startIndex] = this.B; @@ -261,12 +279,14 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(Argb other) { return this.PackedValue == other.PackedValue; } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode() { // ReSharper disable once NonReadonlyMemberInGetHashCode @@ -281,6 +301,7 @@ namespace ImageSharp /// The z-component /// The w-component /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static uint Pack(float x, float y, float z, float w) { var value = new Vector4(x, y, z, w); @@ -295,6 +316,7 @@ namespace ImageSharp /// The z-component /// The w-component /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static uint Pack(byte x, byte y, byte z, byte w) { return (uint)(x << RedShift | y << GreenShift | z << BlueShift | w << AlphaShift); @@ -305,6 +327,7 @@ namespace ImageSharp /// /// The vector containing the values to pack. /// The containing the packed values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static uint Pack(ref Vector3 vector) { var value = new Vector4(vector, 1); @@ -316,6 +339,7 @@ namespace ImageSharp /// /// The vector containing the values to pack. /// The containing the packed values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static uint Pack(ref Vector4 vector) { vector = Vector4.Clamp(vector, Vector4.Zero, Vector4.One); diff --git a/src/ImageSharp/Colors/PackedPixel/Bgr565.cs b/src/ImageSharp/Colors/PackedPixel/Bgr565.cs index 1f1ce0a17e..19c7f3d14b 100644 --- a/src/ImageSharp/Colors/PackedPixel/Bgr565.cs +++ b/src/ImageSharp/Colors/PackedPixel/Bgr565.cs @@ -7,6 +7,7 @@ namespace ImageSharp { using System; using System.Numerics; + using System.Runtime.CompilerServices; /// /// Packed pixel type containing unsigned normalized values ranging from 0 to 1. The x and z components use 5 bits, and the y component uses 6 bits. @@ -46,6 +47,7 @@ namespace ImageSharp /// /// True if the parameter is equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(Bgr565 left, Bgr565 right) { return left.PackedValue == right.PackedValue; @@ -59,6 +61,7 @@ namespace ImageSharp /// /// True if the parameter is not equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(Bgr565 left, Bgr565 right) { return left.PackedValue != right.PackedValue; @@ -69,6 +72,7 @@ namespace ImageSharp /// The vector components are typically expanded in least to greatest significance order. /// /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector3 ToVector3() { return new Vector3( @@ -78,24 +82,28 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromVector4(Vector4 vector) { this.PackedValue = Pack(vector.X, vector.Y, vector.Z); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ToVector4() { return new Vector4(this.ToVector3(), 1F); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromBytes(byte x, byte y, byte z, byte w) { this.PackFromVector4(new Vector4(x, y, z, w) / 255F); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4() * 255F; @@ -105,6 +113,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzwBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4() * 255F; @@ -115,6 +124,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4() * 255F; @@ -124,6 +134,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxwBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4() * 255F; @@ -140,6 +151,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(Bgr565 other) { return this.PackedValue == other.PackedValue; @@ -152,6 +164,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode() { return this.PackedValue.GetHashCode(); @@ -164,6 +177,7 @@ namespace ImageSharp /// The y-component /// The z-component /// The containing the packed values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static ushort Pack(float x, float y, float z) { return (ushort)((((int)Math.Round(x.Clamp(0, 1) * 31F) & 0x1F) << 11) | diff --git a/src/ImageSharp/Colors/PackedPixel/Bgra4444.cs b/src/ImageSharp/Colors/PackedPixel/Bgra4444.cs index 1f33cb7912..41f7cba040 100644 --- a/src/ImageSharp/Colors/PackedPixel/Bgra4444.cs +++ b/src/ImageSharp/Colors/PackedPixel/Bgra4444.cs @@ -7,6 +7,7 @@ namespace ImageSharp { using System; using System.Numerics; + using System.Runtime.CompilerServices; /// /// Packed pixel type containing unsigned normalized values, ranging from 0 to 1, using 4 bits each for x, y, z, and w. @@ -45,6 +46,7 @@ namespace ImageSharp /// /// True if the parameter is equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(Bgra4444 left, Bgra4444 right) { return left.PackedValue == right.PackedValue; @@ -58,12 +60,14 @@ namespace ImageSharp /// /// True if the parameter is not equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(Bgra4444 left, Bgra4444 right) { return left.PackedValue != right.PackedValue; } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ToVector4() { const float Max = 1 / 15F; @@ -76,18 +80,21 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromVector4(Vector4 vector) { this.PackedValue = Pack(vector.X, vector.Y, vector.Z, vector.W); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromBytes(byte x, byte y, byte z, byte w) { this.PackFromVector4(new Vector4(x, y, z, w) / 255F); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4() * 255F; @@ -97,6 +104,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzwBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4() * 255F; @@ -107,6 +115,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4() * 255F; @@ -116,6 +125,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxwBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4() * 255F; @@ -132,6 +142,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(Bgra4444 other) { return this.PackedValue == other.PackedValue; @@ -144,6 +155,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode() { return this.PackedValue.GetHashCode(); @@ -157,6 +169,7 @@ namespace ImageSharp /// The z-component /// The w-component /// The containing the packed values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static ushort Pack(float x, float y, float z, float w) { return (ushort)((((int)Math.Round(w.Clamp(0, 1) * 15F) & 0x0F) << 12) | diff --git a/src/ImageSharp/Colors/PackedPixel/Bgra5551.cs b/src/ImageSharp/Colors/PackedPixel/Bgra5551.cs index d0c52068dc..800cb0bf16 100644 --- a/src/ImageSharp/Colors/PackedPixel/Bgra5551.cs +++ b/src/ImageSharp/Colors/PackedPixel/Bgra5551.cs @@ -7,6 +7,7 @@ namespace ImageSharp { using System; using System.Numerics; + using System.Runtime.CompilerServices; /// /// Packed pixel type containing unsigned normalized values ranging from 0 to 1. The x , y and z components use 5 bits, and the w component uses 1 bit. @@ -47,6 +48,7 @@ namespace ImageSharp /// /// True if the parameter is equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(Bgra5551 left, Bgra5551 right) { return left.PackedValue == right.PackedValue; @@ -60,12 +62,14 @@ namespace ImageSharp /// /// True if the parameter is not equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(Bgra5551 left, Bgra5551 right) { return left.PackedValue != right.PackedValue; } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ToVector4() { return new Vector4( @@ -76,18 +80,21 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromVector4(Vector4 vector) { this.PackedValue = Pack(vector.X, vector.Y, vector.Z, vector.W); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromBytes(byte x, byte y, byte z, byte w) { this.PackFromVector4(new Vector4(x, y, z, w) / 255F); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4() * 255F; @@ -97,6 +104,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzwBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4() * 255F; @@ -107,6 +115,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4() * 255F; @@ -116,6 +125,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxwBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4() * 255F; @@ -132,6 +142,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(Bgra5551 other) { return this.PackedValue == other.PackedValue; @@ -150,6 +161,7 @@ namespace ImageSharp /// Gets a hash code of the packed vector. /// /// The hash code for the packed vector. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode() { return this.PackedValue.GetHashCode(); @@ -163,6 +175,7 @@ namespace ImageSharp /// The z-component /// The w-component /// The containing the packed values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static ushort Pack(float x, float y, float z, float w) { return (ushort)( diff --git a/src/ImageSharp/Colors/PackedPixel/Byte4.cs b/src/ImageSharp/Colors/PackedPixel/Byte4.cs index 69c69ecaf4..644acc5093 100644 --- a/src/ImageSharp/Colors/PackedPixel/Byte4.cs +++ b/src/ImageSharp/Colors/PackedPixel/Byte4.cs @@ -7,6 +7,7 @@ namespace ImageSharp { using System; using System.Numerics; + using System.Runtime.CompilerServices; /// /// Packed pixel type containing four 8-bit unsigned integer values, ranging from 0 to 255. @@ -48,6 +49,7 @@ namespace ImageSharp /// /// True if the parameter is equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(Byte4 left, Byte4 right) { return left.PackedValue == right.PackedValue; @@ -61,6 +63,7 @@ namespace ImageSharp /// /// True if the parameter is not equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(Byte4 left, Byte4 right) { return left.PackedValue != right.PackedValue; @@ -70,6 +73,7 @@ namespace ImageSharp /// Sets the packed representation from a Vector4. /// /// The vector to create the packed representation from. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromVector4(Vector4 vector) { this.PackedValue = Pack(ref vector); @@ -79,6 +83,7 @@ namespace ImageSharp /// Expands the packed representation into a Vector4. /// /// The expanded vector. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ToVector4() { return new Vector4( @@ -89,12 +94,14 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromBytes(byte x, byte y, byte z, byte w) { this.PackFromVector4(new Vector4(x, y, z, w)); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -104,6 +111,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzwBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -114,6 +122,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -123,6 +132,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxwBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -139,12 +149,14 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(Byte4 other) { return this == other; } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode() { return this.PackedValue.GetHashCode(); @@ -164,12 +176,14 @@ namespace ImageSharp /// /// The vector containing the values to pack. /// The containing the packed values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static uint Pack(ref Vector4 vector) { const float Max = 255F; const float Min = 0F; // Clamp the value between min and max values + // TODO: Use Vector4.Clamp() here! uint byte4 = (uint)Math.Round(vector.X.Clamp(Min, Max)) & 0xFF; uint byte3 = ((uint)Math.Round(vector.Y.Clamp(Min, Max)) & 0xFF) << 0x8; uint byte2 = ((uint)Math.Round(vector.Z.Clamp(Min, Max)) & 0xFF) << 0x10; diff --git a/src/ImageSharp/Colors/PackedPixel/HalfSingle.cs b/src/ImageSharp/Colors/PackedPixel/HalfSingle.cs index 22e32aa569..29a55095b9 100644 --- a/src/ImageSharp/Colors/PackedPixel/HalfSingle.cs +++ b/src/ImageSharp/Colors/PackedPixel/HalfSingle.cs @@ -7,6 +7,7 @@ namespace ImageSharp { using System; using System.Numerics; + using System.Runtime.CompilerServices; /// /// Packed pixel type containing a single 16 bit floating point value. @@ -47,6 +48,7 @@ namespace ImageSharp /// /// True if the parameter is equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(HalfSingle left, HalfSingle right) { return left.PackedValue == right.PackedValue; @@ -64,6 +66,7 @@ namespace ImageSharp /// /// True if the parameter is not equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(HalfSingle left, HalfSingle right) { return left.PackedValue != right.PackedValue; @@ -73,30 +76,35 @@ namespace ImageSharp /// Expands the packed representation into a . /// /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] public float ToSingle() { return HalfTypeHelper.Unpack(this.PackedValue); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromVector4(Vector4 vector) { this.PackedValue = HalfTypeHelper.Pack(vector.X); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ToVector4() { return new Vector4(this.ToSingle(), 0, 0, 1); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromBytes(byte x, byte y, byte z, byte w) { this.PackFromVector4(new Vector4(x, y, z, w) / MaxBytes); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -110,6 +118,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzwBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -124,6 +133,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -137,6 +147,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxwBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -157,6 +168,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(HalfSingle other) { return this.PackedValue == other.PackedValue; @@ -169,6 +181,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode() { return this.PackedValue.GetHashCode(); diff --git a/src/ImageSharp/Colors/PackedPixel/HalfTypeHelper.cs b/src/ImageSharp/Colors/PackedPixel/HalfTypeHelper.cs index d6d680ed45..a19b513230 100644 --- a/src/ImageSharp/Colors/PackedPixel/HalfTypeHelper.cs +++ b/src/ImageSharp/Colors/PackedPixel/HalfTypeHelper.cs @@ -5,6 +5,7 @@ namespace ImageSharp { + using System.Runtime.CompilerServices; using System.Runtime.InteropServices; /// @@ -17,6 +18,7 @@ namespace ImageSharp /// /// The float to pack /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static ushort Pack(float value) { Uif uif = new Uif { F = value }; diff --git a/src/ImageSharp/Colors/PackedPixel/HalfVector2.cs b/src/ImageSharp/Colors/PackedPixel/HalfVector2.cs index 7c17dcea8f..02e93b2503 100644 --- a/src/ImageSharp/Colors/PackedPixel/HalfVector2.cs +++ b/src/ImageSharp/Colors/PackedPixel/HalfVector2.cs @@ -7,6 +7,7 @@ namespace ImageSharp { using System; using System.Numerics; + using System.Runtime.CompilerServices; /// /// Packed pixel type containing two 16-bit floating-point values. @@ -57,6 +58,7 @@ namespace ImageSharp /// /// True if the parameter is equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(HalfVector2 left, HalfVector2 right) { return left.Equals(right); @@ -74,6 +76,7 @@ namespace ImageSharp /// /// True if the parameter is not equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(HalfVector2 left, HalfVector2 right) { return !left.Equals(right); @@ -83,6 +86,7 @@ namespace ImageSharp /// Expands the packed representation into a . /// /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector2 ToVector2() { Vector2 vector; @@ -92,12 +96,14 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromVector4(Vector4 vector) { this.PackedValue = Pack(vector.X, vector.Y); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ToVector4() { Vector2 vector = this.ToVector2(); @@ -105,12 +111,14 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromBytes(byte x, byte y, byte z, byte w) { this.PackFromVector4(new Vector4(x, y, z, w) / MaxBytes); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -124,6 +132,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzwBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -138,6 +147,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -151,6 +161,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxwBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -171,6 +182,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode() { return this.PackedValue.GetHashCode(); @@ -183,6 +195,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(HalfVector2 other) { return this.PackedValue.Equals(other.PackedValue); @@ -194,6 +207,7 @@ namespace ImageSharp /// The x-component /// The y-component /// The containing the packed values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static uint Pack(float x, float y) { uint num2 = HalfTypeHelper.Pack(x); diff --git a/src/ImageSharp/Colors/PackedPixel/HalfVector4.cs b/src/ImageSharp/Colors/PackedPixel/HalfVector4.cs index a7f10fc71d..38787acea8 100644 --- a/src/ImageSharp/Colors/PackedPixel/HalfVector4.cs +++ b/src/ImageSharp/Colors/PackedPixel/HalfVector4.cs @@ -7,6 +7,7 @@ namespace ImageSharp { using System; using System.Numerics; + using System.Runtime.CompilerServices; /// /// Packed pixel type containing four 16-bit floating-point values. @@ -60,6 +61,7 @@ namespace ImageSharp /// /// True if the parameter is equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(HalfVector4 left, HalfVector4 right) { return left.Equals(right); @@ -77,18 +79,21 @@ namespace ImageSharp /// /// True if the parameter is not equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(HalfVector4 left, HalfVector4 right) { return !left.Equals(right); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromVector4(Vector4 vector) { this.PackedValue = Pack(ref vector); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ToVector4() { return new Vector4( @@ -99,12 +104,14 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromBytes(byte x, byte y, byte z, byte w) { this.PackFromVector4(new Vector4(x, y, z, w) / MaxBytes); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -118,6 +125,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzwBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -132,6 +140,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -145,6 +154,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxwBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -165,6 +175,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode() { return this.PackedValue.GetHashCode(); @@ -177,6 +188,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(HalfVector4 other) { return this.PackedValue.Equals(other.PackedValue); @@ -187,6 +199,7 @@ namespace ImageSharp /// /// The vector containing the values to pack. /// The containing the packed values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static ulong Pack(ref Vector4 vector) { ulong num4 = HalfTypeHelper.Pack(vector.X); diff --git a/src/ImageSharp/Colors/PackedPixel/NormalizedByte2.cs b/src/ImageSharp/Colors/PackedPixel/NormalizedByte2.cs index f1dae10667..5196cbfaf4 100644 --- a/src/ImageSharp/Colors/PackedPixel/NormalizedByte2.cs +++ b/src/ImageSharp/Colors/PackedPixel/NormalizedByte2.cs @@ -7,6 +7,7 @@ namespace ImageSharp { using System; using System.Numerics; + using System.Runtime.CompilerServices; /// /// Packed packed pixel type containing two 8-bit signed normalized values, ranging from −1 to 1. @@ -62,6 +63,7 @@ namespace ImageSharp /// /// True if the parameter is equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(NormalizedByte2 left, NormalizedByte2 right) { return left.PackedValue == right.PackedValue; @@ -79,6 +81,7 @@ namespace ImageSharp /// /// True if the parameter is not equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(NormalizedByte2 left, NormalizedByte2 right) { return left.PackedValue != right.PackedValue; @@ -89,6 +92,7 @@ namespace ImageSharp /// The vector components are typically expanded in least to greatest significance order. /// /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector2 ToVector2() { return new Vector2( @@ -97,18 +101,21 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromVector4(Vector4 vector) { this.PackedValue = Pack(vector.X, vector.Y); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ToVector4() { return new Vector4(this.ToVector2(), 0F, 1F); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromBytes(byte x, byte y, byte z, byte w) { Vector4 vector = new Vector4(x, y, z, w); @@ -120,6 +127,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -135,6 +143,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzwBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -151,6 +160,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -166,6 +176,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxwBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -188,12 +199,14 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(NormalizedByte2 other) { return this.PackedValue == other.PackedValue; } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode() { return this.PackedValue.GetHashCode(); @@ -211,6 +224,7 @@ namespace ImageSharp /// The x-component /// The y-component /// The containing the packed values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static ushort Pack(float x, float y) { int byte2 = ((ushort)Math.Round(x.Clamp(-1F, 1F) * 127F) & 0xFF) << 0; diff --git a/src/ImageSharp/Colors/PackedPixel/NormalizedByte4.cs b/src/ImageSharp/Colors/PackedPixel/NormalizedByte4.cs index 22eba51827..c5de795e24 100644 --- a/src/ImageSharp/Colors/PackedPixel/NormalizedByte4.cs +++ b/src/ImageSharp/Colors/PackedPixel/NormalizedByte4.cs @@ -7,6 +7,7 @@ namespace ImageSharp { using System; using System.Numerics; + using System.Runtime.CompilerServices; /// /// Packed pixel type containing four 8-bit signed normalized values, ranging from −1 to 1. @@ -64,6 +65,7 @@ namespace ImageSharp /// /// True if the parameter is equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(NormalizedByte4 left, NormalizedByte4 right) { return left.PackedValue == right.PackedValue; @@ -81,18 +83,21 @@ namespace ImageSharp /// /// True if the parameter is not equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(NormalizedByte4 left, NormalizedByte4 right) { return left.PackedValue != right.PackedValue; } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromVector4(Vector4 vector) { this.PackedValue = Pack(vector.X, vector.Y, vector.Z, vector.W); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ToVector4() { return new Vector4( @@ -103,6 +108,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromBytes(byte x, byte y, byte z, byte w) { Vector4 vector = new Vector4(x, y, z, w); @@ -114,6 +120,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -129,6 +136,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzwBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -145,6 +153,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -160,6 +169,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxwBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -182,12 +192,14 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(NormalizedByte4 other) { return this.PackedValue == other.PackedValue; } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode() { return this.PackedValue.GetHashCode(); @@ -207,6 +219,7 @@ namespace ImageSharp /// The z-component /// The w-component /// The containing the packed values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static uint Pack(float x, float y, float z, float w) { uint byte4 = ((uint)Math.Round(x.Clamp(-1F, 1F) * 127F) & 0xFF) << 0; diff --git a/src/ImageSharp/Colors/PackedPixel/NormalizedShort2.cs b/src/ImageSharp/Colors/PackedPixel/NormalizedShort2.cs index 34b2fc320c..8e35700472 100644 --- a/src/ImageSharp/Colors/PackedPixel/NormalizedShort2.cs +++ b/src/ImageSharp/Colors/PackedPixel/NormalizedShort2.cs @@ -7,6 +7,7 @@ namespace ImageSharp { using System; using System.Numerics; + using System.Runtime.CompilerServices; /// /// Packed pixel type containing two 16-bit signed normalized values, ranging from −1 to 1. @@ -62,6 +63,7 @@ namespace ImageSharp /// /// True if the parameter is equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(NormalizedShort2 left, NormalizedShort2 right) { return left.Equals(right); @@ -79,24 +81,28 @@ namespace ImageSharp /// /// True if the parameter is not equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(NormalizedShort2 left, NormalizedShort2 right) { return !left.Equals(right); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromVector4(Vector4 vector) { this.PackedValue = Pack(vector.X, vector.Y); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ToVector4() { return new Vector4(this.ToVector2(), 0, 1); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromBytes(byte x, byte y, byte z, byte w) { Vector4 vector = new Vector4(x, y, z, w); @@ -108,6 +114,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -123,6 +130,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzwBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -139,6 +147,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -154,6 +163,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxwBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -174,6 +184,7 @@ namespace ImageSharp /// The vector components are typically expanded in least to greatest significance order. /// /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector2 ToVector2() { const float MaxVal = 0x7FFF; @@ -190,12 +201,14 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(NormalizedShort2 other) { return this.PackedValue.Equals(other.PackedValue); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode() { return this.PackedValue.GetHashCode(); @@ -213,6 +226,7 @@ namespace ImageSharp /// The x-component /// The y-component /// The containing the packed values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static uint Pack(float x, float y) { const float MaxPos = 0x7FFF; diff --git a/src/ImageSharp/Colors/PackedPixel/NormalizedShort4.cs b/src/ImageSharp/Colors/PackedPixel/NormalizedShort4.cs index 9742a5f347..81d182cee3 100644 --- a/src/ImageSharp/Colors/PackedPixel/NormalizedShort4.cs +++ b/src/ImageSharp/Colors/PackedPixel/NormalizedShort4.cs @@ -7,6 +7,7 @@ namespace ImageSharp { using System; using System.Numerics; + using System.Runtime.CompilerServices; /// /// Packed pixel type containing four 16-bit signed normalized values, ranging from −1 to 1. @@ -64,6 +65,7 @@ namespace ImageSharp /// /// True if the parameter is equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(NormalizedShort4 left, NormalizedShort4 right) { return left.Equals(right); @@ -81,18 +83,21 @@ namespace ImageSharp /// /// True if the parameter is not equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(NormalizedShort4 left, NormalizedShort4 right) { return !left.Equals(right); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromVector4(Vector4 vector) { this.PackedValue = Pack(vector.X, vector.Y, vector.Z, vector.W); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ToVector4() { const float MaxVal = 0x7FFF; @@ -105,6 +110,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromBytes(byte x, byte y, byte z, byte w) { Vector4 vector = new Vector4(x, y, z, w); @@ -116,6 +122,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -131,6 +138,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzwBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -147,6 +155,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -162,6 +171,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxwBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -184,12 +194,14 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(NormalizedShort4 other) { return this.PackedValue.Equals(other.PackedValue); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode() { return this.PackedValue.GetHashCode(); @@ -209,6 +221,7 @@ namespace ImageSharp /// The z-component /// The w-component /// The containing the packed values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static ulong Pack(float x, float y, float z, float w) { const float MaxPos = 0x7FFF; diff --git a/src/ImageSharp/Colors/PackedPixel/Rg32.cs b/src/ImageSharp/Colors/PackedPixel/Rg32.cs index d885a4470f..f7dab68c3d 100644 --- a/src/ImageSharp/Colors/PackedPixel/Rg32.cs +++ b/src/ImageSharp/Colors/PackedPixel/Rg32.cs @@ -7,6 +7,7 @@ namespace ImageSharp { using System; using System.Numerics; + using System.Runtime.CompilerServices; /// /// Packed pixel type containing two 16-bit unsigned normalized values ranging from 0 to 1. @@ -47,6 +48,7 @@ namespace ImageSharp /// /// True if the parameter is equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(Rg32 left, Rg32 right) { return left.PackedValue == right.PackedValue; @@ -64,6 +66,7 @@ namespace ImageSharp /// /// True if the parameter is not equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(Rg32 left, Rg32 right) { return left.PackedValue != right.PackedValue; @@ -74,6 +77,7 @@ namespace ImageSharp /// The vector components are typically expanded in least to greatest significance order. /// /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector2 ToVector2() { return new Vector2( @@ -82,24 +86,28 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromVector4(Vector4 vector) { this.PackedValue = Pack(vector.X, vector.Y); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ToVector4() { return new Vector4(this.ToVector2(), 0F, 1F); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromBytes(byte x, byte y, byte z, byte w) { this.PackFromVector4(new Vector4(x, y, z, w) / 255F); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4() * 255F; @@ -110,6 +118,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzwBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4() * 255F; @@ -121,6 +130,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4() * 255F; @@ -131,6 +141,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxwBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4() * 255F; @@ -148,6 +159,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(Rg32 other) { return this.PackedValue == other.PackedValue; @@ -160,6 +172,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode() { return this.PackedValue.GetHashCode(); @@ -171,6 +184,7 @@ namespace ImageSharp /// The x-component /// The y-component /// The containing the packed values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static uint Pack(float x, float y) { return (uint)( diff --git a/src/ImageSharp/Colors/PackedPixel/Rgba1010102.cs b/src/ImageSharp/Colors/PackedPixel/Rgba1010102.cs index da7dbe1ee1..8c22610287 100644 --- a/src/ImageSharp/Colors/PackedPixel/Rgba1010102.cs +++ b/src/ImageSharp/Colors/PackedPixel/Rgba1010102.cs @@ -7,6 +7,7 @@ namespace ImageSharp { using System; using System.Numerics; + using System.Runtime.CompilerServices; /// /// Packed vector type containing unsigned normalized values ranging from 0 to 1. @@ -50,6 +51,7 @@ namespace ImageSharp /// /// True if the parameter is equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(Rgba1010102 left, Rgba1010102 right) { return left.PackedValue == right.PackedValue; @@ -67,12 +69,14 @@ namespace ImageSharp /// /// True if the parameter is not equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(Rgba1010102 left, Rgba1010102 right) { return left.PackedValue != right.PackedValue; } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ToVector4() { return new Vector4( @@ -83,18 +87,21 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromVector4(Vector4 vector) { this.PackedValue = Pack(vector.X, vector.Y, vector.Z, vector.W); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromBytes(byte x, byte y, byte z, byte w) { this.PackFromVector4(new Vector4(x, y, z, w) / 255F); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4() * 255F; @@ -105,6 +112,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzwBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4() * 255F; @@ -116,6 +124,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4() * 255F; @@ -126,6 +135,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxwBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4() * 255F; @@ -143,6 +153,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(Rgba1010102 other) { return this.PackedValue == other.PackedValue; @@ -155,6 +166,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode() { return this.PackedValue.GetHashCode(); @@ -168,6 +180,7 @@ namespace ImageSharp /// The z-component /// The w-component /// The containing the packed values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static uint Pack(float x, float y, float z, float w) { return (uint)( diff --git a/src/ImageSharp/Colors/PackedPixel/Rgba64.cs b/src/ImageSharp/Colors/PackedPixel/Rgba64.cs index 64631f1e1a..bb21826498 100644 --- a/src/ImageSharp/Colors/PackedPixel/Rgba64.cs +++ b/src/ImageSharp/Colors/PackedPixel/Rgba64.cs @@ -7,6 +7,7 @@ namespace ImageSharp { using System; using System.Numerics; + using System.Runtime.CompilerServices; /// /// Packed pixel type containing four 16-bit unsigned normalized values ranging from 0 to 1. @@ -49,6 +50,7 @@ namespace ImageSharp /// /// True if the parameter is equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(Rgba64 left, Rgba64 right) { return left.PackedValue == right.PackedValue; @@ -66,12 +68,14 @@ namespace ImageSharp /// /// True if the parameter is not equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(Rgba64 left, Rgba64 right) { return left.PackedValue != right.PackedValue; } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ToVector4() { return new Vector4( @@ -82,18 +86,21 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromVector4(Vector4 vector) { this.PackedValue = Pack(vector.X, vector.Y, vector.Z, vector.W); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromBytes(byte x, byte y, byte z, byte w) { this.PackFromVector4(new Vector4(x, y, z, w) / 255F); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4() * 255F; @@ -104,6 +111,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzwBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4() * 255F; @@ -115,6 +123,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4() * 255F; @@ -125,6 +134,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxwBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4() * 255F; @@ -142,6 +152,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(Rgba64 other) { return this.PackedValue == other.PackedValue; @@ -154,6 +165,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode() { return this.PackedValue.GetHashCode(); @@ -167,6 +179,7 @@ namespace ImageSharp /// The z-component /// The w-component /// The containing the packed values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static ulong Pack(float x, float y, float z, float w) { return (ulong)Math.Round(x.Clamp(0, 1) * 65535F) | diff --git a/src/ImageSharp/Colors/PackedPixel/Short2.cs b/src/ImageSharp/Colors/PackedPixel/Short2.cs index d45a80fcb2..6bfc5d40c2 100644 --- a/src/ImageSharp/Colors/PackedPixel/Short2.cs +++ b/src/ImageSharp/Colors/PackedPixel/Short2.cs @@ -7,6 +7,7 @@ namespace ImageSharp { using System; using System.Numerics; + using System.Runtime.CompilerServices; /// /// Packed pixel type containing two 16-bit signed integer values. @@ -62,6 +63,7 @@ namespace ImageSharp /// /// True if the parameter is equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(Short2 left, Short2 right) { return left.PackedValue == right.PackedValue; @@ -79,24 +81,28 @@ namespace ImageSharp /// /// True if the parameter is not equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(Short2 left, Short2 right) { return left.PackedValue != right.PackedValue; } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromVector4(Vector4 vector) { this.PackedValue = Pack(vector.X, vector.Y); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ToVector4() { return new Vector4((short)(this.PackedValue & 0xFFFF), (short)(this.PackedValue >> 0x10), 0, 1); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromBytes(byte x, byte y, byte z, byte w) { Vector2 vector = new Vector2(x, y) / 255; @@ -106,6 +112,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzBytes(byte[] bytes, int startIndex) { Vector2 vector = this.ToVector2(); @@ -121,6 +128,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzwBytes(byte[] bytes, int startIndex) { Vector2 vector = this.ToVector2(); @@ -137,6 +145,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxBytes(byte[] bytes, int startIndex) { Vector2 vector = this.ToVector2(); @@ -152,6 +161,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxwBytes(byte[] bytes, int startIndex) { Vector2 vector = this.ToVector2(); @@ -172,6 +182,7 @@ namespace ImageSharp /// The vector components are typically expanded in least to greatest significance order. /// /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector2 ToVector2() { return new Vector2((short)(this.PackedValue & 0xFFFF), (short)(this.PackedValue >> 0x10)); @@ -184,12 +195,14 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(Short2 other) { return this == other; } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode() { return this.PackedValue.GetHashCode(); @@ -207,6 +220,7 @@ namespace ImageSharp /// The x-component /// The y-component /// The containing the packed values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static uint Pack(float x, float y) { // Largest two byte positive number 0xFFFF >> 1; diff --git a/src/ImageSharp/Colors/PackedPixel/Short4.cs b/src/ImageSharp/Colors/PackedPixel/Short4.cs index cd112a90f4..4b1f3c2533 100644 --- a/src/ImageSharp/Colors/PackedPixel/Short4.cs +++ b/src/ImageSharp/Colors/PackedPixel/Short4.cs @@ -7,6 +7,7 @@ namespace ImageSharp { using System; using System.Numerics; + using System.Runtime.CompilerServices; /// /// Packed pixel type containing four 16-bit signed integer values. @@ -64,6 +65,7 @@ namespace ImageSharp /// /// True if the parameter is not equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(Short4 left, Short4 right) { return left.PackedValue == right.PackedValue; @@ -81,18 +83,21 @@ namespace ImageSharp /// /// True if the parameter is not equal to the parameter; otherwise, false. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(Short4 left, Short4 right) { return left.PackedValue != right.PackedValue; } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromVector4(Vector4 vector) { this.PackedValue = Pack(vector.X, vector.Y, vector.Z, vector.W); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ToVector4() { return new Vector4( @@ -103,6 +108,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromBytes(byte x, byte y, byte z, byte w) { Vector4 vector = new Vector4(x, y, z, w) / 255; @@ -112,6 +118,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -127,6 +134,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToXyzwBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -143,6 +151,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -158,6 +167,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToZyxwBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4(); @@ -180,6 +190,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(Short4 other) { return this == other; @@ -189,6 +200,7 @@ namespace ImageSharp /// Gets the hash code for the current instance. /// /// Hash code for the instance. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode() { return this.PackedValue.GetHashCode(); @@ -211,6 +223,7 @@ namespace ImageSharp /// The z-component /// The w-component /// The containing the packed values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static ulong Pack(float x, float y, float z, float w) { // Largest two byte positive number 0xFFFF >> 1; From 3b66fe3078500b144750a2cf17a714105abf75d3 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 7 Feb 2017 12:11:22 +1100 Subject: [PATCH 044/142] Inline ToVector4() 10ms faster resize benchmark on my machine. --- src/ImageSharp/Colors/Color.cs | 2 ++ src/ImageSharp/Colors/PackedPixel/Alpha8.cs | 2 ++ src/ImageSharp/Colors/PackedPixel/Argb.cs | 2 ++ src/ImageSharp/Colors/PackedPixel/Bgr565.cs | 2 ++ src/ImageSharp/Colors/PackedPixel/Bgra4444.cs | 2 ++ src/ImageSharp/Colors/PackedPixel/Bgra5551.cs | 2 ++ src/ImageSharp/Colors/PackedPixel/Byte4.cs | 12 ++++-------- src/ImageSharp/Colors/PackedPixel/HalfSingle.cs | 2 ++ src/ImageSharp/Colors/PackedPixel/HalfVector2.cs | 2 ++ src/ImageSharp/Colors/PackedPixel/HalfVector4.cs | 2 ++ src/ImageSharp/Colors/PackedPixel/NormalizedByte2.cs | 2 ++ src/ImageSharp/Colors/PackedPixel/NormalizedByte4.cs | 2 ++ .../Colors/PackedPixel/NormalizedShort2.cs | 2 ++ .../Colors/PackedPixel/NormalizedShort4.cs | 2 ++ 14 files changed, 30 insertions(+), 8 deletions(-) diff --git a/src/ImageSharp/Colors/Color.cs b/src/ImageSharp/Colors/Color.cs index 036ee14c15..139a68292b 100644 --- a/src/ImageSharp/Colors/Color.cs +++ b/src/ImageSharp/Colors/Color.cs @@ -8,6 +8,7 @@ namespace ImageSharp using System; using System.Globalization; using System.Numerics; + using System.Runtime.CompilerServices; /// /// Packed pixel type containing four 8-bit unsigned normalized values ranging from 0 to 255. @@ -313,6 +314,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ToVector4() { return new Vector4(this.R, this.G, this.B, this.A) / MaxBytes; diff --git a/src/ImageSharp/Colors/PackedPixel/Alpha8.cs b/src/ImageSharp/Colors/PackedPixel/Alpha8.cs index 361fe5b9e2..a8e962ac2a 100644 --- a/src/ImageSharp/Colors/PackedPixel/Alpha8.cs +++ b/src/ImageSharp/Colors/PackedPixel/Alpha8.cs @@ -7,6 +7,7 @@ namespace ImageSharp { using System; using System.Numerics; + using System.Runtime.CompilerServices; /// /// Packed pixel type containing a single 8 bit normalized W values that is ranging from 0 to 1. @@ -62,6 +63,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ToVector4() { return new Vector4(0, 0, 0, this.PackedValue / 255F); diff --git a/src/ImageSharp/Colors/PackedPixel/Argb.cs b/src/ImageSharp/Colors/PackedPixel/Argb.cs index 8ab8e3f43f..5478dd9efd 100644 --- a/src/ImageSharp/Colors/PackedPixel/Argb.cs +++ b/src/ImageSharp/Colors/PackedPixel/Argb.cs @@ -7,6 +7,7 @@ namespace ImageSharp { using System; using System.Numerics; + using System.Runtime.CompilerServices; /// /// Packed pixel type containing four 8-bit unsigned normalized values ranging from 0 to 255. @@ -209,6 +210,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ToVector4() { return new Vector4(this.R, this.G, this.B, this.A) / MaxBytes; diff --git a/src/ImageSharp/Colors/PackedPixel/Bgr565.cs b/src/ImageSharp/Colors/PackedPixel/Bgr565.cs index 1f1ce0a17e..0bce703632 100644 --- a/src/ImageSharp/Colors/PackedPixel/Bgr565.cs +++ b/src/ImageSharp/Colors/PackedPixel/Bgr565.cs @@ -7,6 +7,7 @@ namespace ImageSharp { using System; using System.Numerics; + using System.Runtime.CompilerServices; /// /// Packed pixel type containing unsigned normalized values ranging from 0 to 1. The x and z components use 5 bits, and the y component uses 6 bits. @@ -84,6 +85,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ToVector4() { return new Vector4(this.ToVector3(), 1F); diff --git a/src/ImageSharp/Colors/PackedPixel/Bgra4444.cs b/src/ImageSharp/Colors/PackedPixel/Bgra4444.cs index 1f33cb7912..892a3d4b62 100644 --- a/src/ImageSharp/Colors/PackedPixel/Bgra4444.cs +++ b/src/ImageSharp/Colors/PackedPixel/Bgra4444.cs @@ -7,6 +7,7 @@ namespace ImageSharp { using System; using System.Numerics; + using System.Runtime.CompilerServices; /// /// Packed pixel type containing unsigned normalized values, ranging from 0 to 1, using 4 bits each for x, y, z, and w. @@ -64,6 +65,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ToVector4() { const float Max = 1 / 15F; diff --git a/src/ImageSharp/Colors/PackedPixel/Bgra5551.cs b/src/ImageSharp/Colors/PackedPixel/Bgra5551.cs index d0c52068dc..64b506f7b2 100644 --- a/src/ImageSharp/Colors/PackedPixel/Bgra5551.cs +++ b/src/ImageSharp/Colors/PackedPixel/Bgra5551.cs @@ -7,6 +7,7 @@ namespace ImageSharp { using System; using System.Numerics; + using System.Runtime.CompilerServices; /// /// Packed pixel type containing unsigned normalized values ranging from 0 to 1. The x , y and z components use 5 bits, and the w component uses 1 bit. @@ -66,6 +67,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ToVector4() { return new Vector4( diff --git a/src/ImageSharp/Colors/PackedPixel/Byte4.cs b/src/ImageSharp/Colors/PackedPixel/Byte4.cs index 69c69ecaf4..f15bbd7114 100644 --- a/src/ImageSharp/Colors/PackedPixel/Byte4.cs +++ b/src/ImageSharp/Colors/PackedPixel/Byte4.cs @@ -7,6 +7,7 @@ namespace ImageSharp { using System; using System.Numerics; + using System.Runtime.CompilerServices; /// /// Packed pixel type containing four 8-bit unsigned integer values, ranging from 0 to 255. @@ -66,19 +67,14 @@ namespace ImageSharp return left.PackedValue != right.PackedValue; } - /// - /// Sets the packed representation from a Vector4. - /// - /// The vector to create the packed representation from. + /// public void PackFromVector4(Vector4 vector) { this.PackedValue = Pack(ref vector); } - /// - /// Expands the packed representation into a Vector4. - /// - /// The expanded vector. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ToVector4() { return new Vector4( diff --git a/src/ImageSharp/Colors/PackedPixel/HalfSingle.cs b/src/ImageSharp/Colors/PackedPixel/HalfSingle.cs index 22e32aa569..babb0ab71e 100644 --- a/src/ImageSharp/Colors/PackedPixel/HalfSingle.cs +++ b/src/ImageSharp/Colors/PackedPixel/HalfSingle.cs @@ -7,6 +7,7 @@ namespace ImageSharp { using System; using System.Numerics; + using System.Runtime.CompilerServices; /// /// Packed pixel type containing a single 16 bit floating point value. @@ -85,6 +86,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ToVector4() { return new Vector4(this.ToSingle(), 0, 0, 1); diff --git a/src/ImageSharp/Colors/PackedPixel/HalfVector2.cs b/src/ImageSharp/Colors/PackedPixel/HalfVector2.cs index 7c17dcea8f..4277797000 100644 --- a/src/ImageSharp/Colors/PackedPixel/HalfVector2.cs +++ b/src/ImageSharp/Colors/PackedPixel/HalfVector2.cs @@ -7,6 +7,7 @@ namespace ImageSharp { using System; using System.Numerics; + using System.Runtime.CompilerServices; /// /// Packed pixel type containing two 16-bit floating-point values. @@ -98,6 +99,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ToVector4() { Vector2 vector = this.ToVector2(); diff --git a/src/ImageSharp/Colors/PackedPixel/HalfVector4.cs b/src/ImageSharp/Colors/PackedPixel/HalfVector4.cs index a7f10fc71d..2276183311 100644 --- a/src/ImageSharp/Colors/PackedPixel/HalfVector4.cs +++ b/src/ImageSharp/Colors/PackedPixel/HalfVector4.cs @@ -7,6 +7,7 @@ namespace ImageSharp { using System; using System.Numerics; + using System.Runtime.CompilerServices; /// /// Packed pixel type containing four 16-bit floating-point values. @@ -89,6 +90,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ToVector4() { return new Vector4( diff --git a/src/ImageSharp/Colors/PackedPixel/NormalizedByte2.cs b/src/ImageSharp/Colors/PackedPixel/NormalizedByte2.cs index f1dae10667..a6448512bf 100644 --- a/src/ImageSharp/Colors/PackedPixel/NormalizedByte2.cs +++ b/src/ImageSharp/Colors/PackedPixel/NormalizedByte2.cs @@ -7,6 +7,7 @@ namespace ImageSharp { using System; using System.Numerics; + using System.Runtime.CompilerServices; /// /// Packed packed pixel type containing two 8-bit signed normalized values, ranging from −1 to 1. @@ -103,6 +104,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ToVector4() { return new Vector4(this.ToVector2(), 0F, 1F); diff --git a/src/ImageSharp/Colors/PackedPixel/NormalizedByte4.cs b/src/ImageSharp/Colors/PackedPixel/NormalizedByte4.cs index 22eba51827..9a42df1a9e 100644 --- a/src/ImageSharp/Colors/PackedPixel/NormalizedByte4.cs +++ b/src/ImageSharp/Colors/PackedPixel/NormalizedByte4.cs @@ -7,6 +7,7 @@ namespace ImageSharp { using System; using System.Numerics; + using System.Runtime.CompilerServices; /// /// Packed pixel type containing four 8-bit signed normalized values, ranging from −1 to 1. @@ -93,6 +94,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ToVector4() { return new Vector4( diff --git a/src/ImageSharp/Colors/PackedPixel/NormalizedShort2.cs b/src/ImageSharp/Colors/PackedPixel/NormalizedShort2.cs index 34b2fc320c..7422bb598a 100644 --- a/src/ImageSharp/Colors/PackedPixel/NormalizedShort2.cs +++ b/src/ImageSharp/Colors/PackedPixel/NormalizedShort2.cs @@ -7,6 +7,7 @@ namespace ImageSharp { using System; using System.Numerics; + using System.Runtime.CompilerServices; /// /// Packed pixel type containing two 16-bit signed normalized values, ranging from −1 to 1. @@ -91,6 +92,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ToVector4() { return new Vector4(this.ToVector2(), 0, 1); diff --git a/src/ImageSharp/Colors/PackedPixel/NormalizedShort4.cs b/src/ImageSharp/Colors/PackedPixel/NormalizedShort4.cs index 9742a5f347..f3fb0c5064 100644 --- a/src/ImageSharp/Colors/PackedPixel/NormalizedShort4.cs +++ b/src/ImageSharp/Colors/PackedPixel/NormalizedShort4.cs @@ -7,6 +7,7 @@ namespace ImageSharp { using System; using System.Numerics; + using System.Runtime.CompilerServices; /// /// Packed pixel type containing four 16-bit signed normalized values, ranging from −1 to 1. @@ -93,6 +94,7 @@ namespace ImageSharp } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ToVector4() { const float MaxVal = 0x7FFF; From fe4753c69239b47038e970c696a247df1453b821 Mon Sep 17 00:00:00 2001 From: Dirk Lemstra Date: Tue, 7 Feb 2017 07:43:02 +0100 Subject: [PATCH 045/142] Moved synchronization code to the ExifProfile. --- src/ImageSharp/MetaData/ImageMetaData.cs | 23 +------------ .../MetaData/Profiles/Exif/ExifProfile.cs | 20 +++++++++++ .../MetaData/ImageMetaDataTests.cs | 23 +++---------- .../Profiles/Exif/ExifProfileTests.cs | 33 +++++++++++++++++++ 4 files changed, 58 insertions(+), 41 deletions(-) diff --git a/src/ImageSharp/MetaData/ImageMetaData.cs b/src/ImageSharp/MetaData/ImageMetaData.cs index f4aef98632..a40df3110d 100644 --- a/src/ImageSharp/MetaData/ImageMetaData.cs +++ b/src/ImageSharp/MetaData/ImageMetaData.cs @@ -142,28 +142,7 @@ namespace ImageSharp /// internal void SyncProfiles() { - this.SyncExifProfile(); - } - - private void SyncExifProfile() - { - if (this.ExifProfile == null) - { - return; - } - - this.SyncExifResolution(ExifTag.XResolution, this.HorizontalResolution); - this.SyncExifResolution(ExifTag.YResolution, this.VerticalResolution); - } - - private void SyncExifResolution(ExifTag tag, double resolution) - { - ExifValue value = this.ExifProfile.GetValue(tag); - if (value != null) - { - Rational newResolution = new Rational(resolution, false); - this.ExifProfile.SetValue(tag, newResolution); - } + this.ExifProfile?.Sync(this); } } } diff --git a/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs index 5074639855..3bd5ef3cb1 100644 --- a/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs +++ b/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs @@ -224,6 +224,26 @@ namespace ImageSharp return writer.GetData(); } + /// + /// Synchronizes the profiles with the specified meta data. + /// + /// The meta data. + internal void Sync(ImageMetaData metaData) + { + this.SyncResolution(ExifTag.XResolution, metaData.HorizontalResolution); + this.SyncResolution(ExifTag.YResolution, metaData.VerticalResolution); + } + + private void SyncResolution(ExifTag tag, double resolution) + { + ExifValue value = this.GetValue(tag); + if (value != null) + { + Rational newResolution = new Rational(resolution, false); + this.SetValue(tag, newResolution); + } + } + private void InitializeValues() { if (this.values != null) diff --git a/tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs b/tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs index 60e04e18db..3c0057b627 100644 --- a/tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs +++ b/tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs @@ -80,28 +80,13 @@ namespace ImageSharp.Tests Image image = new Image(1, 1); image.MetaData.ExifProfile = exifProfile; - image.MetaData.HorizontalResolution = 200; - image.MetaData.VerticalResolution = 300; - - image.MetaData.HorizontalResolution = 100; - - Assert.Equal(200, ((Rational)image.MetaData.ExifProfile.GetValue(ExifTag.XResolution).Value).ToDouble()); - Assert.Equal(300, ((Rational)image.MetaData.ExifProfile.GetValue(ExifTag.YResolution).Value).ToDouble()); - - image.MetaData.SyncProfiles(); - - Assert.Equal(100, ((Rational)image.MetaData.ExifProfile.GetValue(ExifTag.XResolution).Value).ToDouble()); - Assert.Equal(300, ((Rational)image.MetaData.ExifProfile.GetValue(ExifTag.YResolution).Value).ToDouble()); - - image.MetaData.VerticalResolution = 150; - - Assert.Equal(100, ((Rational)image.MetaData.ExifProfile.GetValue(ExifTag.XResolution).Value).ToDouble()); - Assert.Equal(300, ((Rational)image.MetaData.ExifProfile.GetValue(ExifTag.YResolution).Value).ToDouble()); + image.MetaData.HorizontalResolution = 400; + image.MetaData.VerticalResolution = 500; image.MetaData.SyncProfiles(); - Assert.Equal(100, ((Rational)image.MetaData.ExifProfile.GetValue(ExifTag.XResolution).Value).ToDouble()); - Assert.Equal(150, ((Rational)image.MetaData.ExifProfile.GetValue(ExifTag.YResolution).Value).ToDouble()); + Assert.Equal(400, ((Rational)image.MetaData.ExifProfile.GetValue(ExifTag.XResolution).Value).ToDouble()); + Assert.Equal(500, ((Rational)image.MetaData.ExifProfile.GetValue(ExifTag.YResolution).Value).ToDouble()); } } } diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs index f4a2bcf401..8ec57057f2 100644 --- a/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs +++ b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs @@ -203,6 +203,39 @@ namespace ImageSharp.Tests Assert.Equal(7, image.MetaData.ExifProfile.Values.Count()); } + [Fact] + public void Syncs() + { + ExifProfile exifProfile = new ExifProfile(); + exifProfile.SetValue(ExifTag.XResolution, new Rational(200)); + exifProfile.SetValue(ExifTag.YResolution, new Rational(300)); + + ImageMetaData metaData = new ImageMetaData(); + metaData.ExifProfile = exifProfile; + metaData.HorizontalResolution = 200; + metaData.VerticalResolution = 300; + + metaData.HorizontalResolution = 100; + + Assert.Equal(200, ((Rational)metaData.ExifProfile.GetValue(ExifTag.XResolution).Value).ToDouble()); + Assert.Equal(300, ((Rational)metaData.ExifProfile.GetValue(ExifTag.YResolution).Value).ToDouble()); + + exifProfile.Sync(metaData); + + Assert.Equal(100, ((Rational)metaData.ExifProfile.GetValue(ExifTag.XResolution).Value).ToDouble()); + Assert.Equal(300, ((Rational)metaData.ExifProfile.GetValue(ExifTag.YResolution).Value).ToDouble()); + + metaData.VerticalResolution = 150; + + Assert.Equal(100, ((Rational)metaData.ExifProfile.GetValue(ExifTag.XResolution).Value).ToDouble()); + Assert.Equal(300, ((Rational)metaData.ExifProfile.GetValue(ExifTag.YResolution).Value).ToDouble()); + + exifProfile.Sync(metaData); + + Assert.Equal(100, ((Rational)metaData.ExifProfile.GetValue(ExifTag.XResolution).Value).ToDouble()); + Assert.Equal(150, ((Rational)metaData.ExifProfile.GetValue(ExifTag.YResolution).Value).ToDouble()); + } + [Fact] public void Values() { From 19100abffe2d6d730abb96d22bcc0ec6c9f40e1e Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 8 Feb 2017 00:19:48 +1100 Subject: [PATCH 046/142] Correctly read resolution. Fix #96 --- .../JpegDecoderCore.cs | 24 +++++++++++++++++-- src/ImageSharp/Image/Image{TColor}.cs | 16 ++----------- .../ImageSharp.Sandbox46.csproj | 4 ++-- .../Formats/Jpg/JpegDecoderTests.cs | 20 ++++++++++++++++ 4 files changed, 46 insertions(+), 18 deletions(-) diff --git a/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs b/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs index 9050c20e1c..eb70c4f8c4 100644 --- a/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs @@ -2,6 +2,7 @@ // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // + namespace ImageSharp.Formats { using System; @@ -66,6 +67,11 @@ namespace ImageSharp.Formats /// private bool isJfif; + /// + /// Whether the image has a EXIF header + /// + private bool isExif; + /// /// The vertical resolution. Calculated if the image has a JFIF header. /// @@ -550,6 +556,19 @@ namespace ImageSharp.Formats image.MetaData.HorizontalResolution = this.horizontalResolution; image.MetaData.VerticalResolution = this.verticalResolution; } + else if (this.isExif) + { + ExifValue horizontal = image.MetaData.ExifProfile.GetValue(ExifTag.XResolution); + ExifValue vertical = image.MetaData.ExifProfile.GetValue(ExifTag.YResolution); + double horizontalValue = horizontal != null ? ((Rational)horizontal.Value).ToDouble() : 0; + double verticalValue = vertical != null ? ((Rational)vertical.Value).ToDouble() : 0; + + if (horizontalValue > 0 && verticalValue > 0) + { + image.MetaData.HorizontalResolution = horizontalValue; + image.MetaData.VerticalResolution = verticalValue; + } + } } /// @@ -951,6 +970,7 @@ namespace ImageSharp.Formats if (profile[0] == 'E' && profile[1] == 'x' && profile[2] == 'i' && profile[3] == 'f' && profile[4] == '\0' && profile[5] == '\0') { + this.isExif = true; image.MetaData.ExifProfile = new ExifProfile(profile); } } @@ -976,8 +996,8 @@ namespace ImageSharp.Formats if (this.isJfif) { - this.horizontalResolution = (short)(this.Temp[9] + (this.Temp[10] << 8)); - this.verticalResolution = (short)(this.Temp[11] + (this.Temp[12] << 8)); + this.horizontalResolution = (short)(this.Temp[9] + (this.Temp[8] << 8)); + this.verticalResolution = (short)(this.Temp[11] + (this.Temp[10] << 8)); } if (remaining > 0) diff --git a/src/ImageSharp/Image/Image{TColor}.cs b/src/ImageSharp/Image/Image{TColor}.cs index 7223bc1d8d..c16bba3447 100644 --- a/src/ImageSharp/Image/Image{TColor}.cs +++ b/src/ImageSharp/Image/Image{TColor}.cs @@ -127,13 +127,7 @@ namespace ImageSharp /// the default value is used. /// /// The width of the image in inches. - public double InchWidth - { - get - { - return this.Width / this.MetaData.HorizontalResolution; - } - } + public double InchWidth => this.Width / this.MetaData.HorizontalResolution; /// /// Gets the height of the image in inches. It is calculated as the height of the image @@ -141,13 +135,7 @@ namespace ImageSharp /// the default value is used. /// /// The height of the image in inches. - public double InchHeight - { - get - { - return this.Height / this.MetaData.VerticalResolution; - } - } + public double InchHeight => this.Height / this.MetaData.VerticalResolution; /// /// Gets a value indicating whether this image is animated. diff --git a/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj b/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj index 1bce8003ea..2444db0312 100644 --- a/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj +++ b/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj @@ -238,8 +238,8 @@ Tests\Formats\Jpg\YCbCrImageTests.cs - - Tests\Image\ImagePropertyTests.cs + + Tests\MetaData\ImagePropertyTests.cs Tests\Image\ImageTests.cs diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index 25329d93a9..8bce4c4e8d 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -100,5 +100,25 @@ namespace ImageSharp.Tests } } } + + [Fact] + public void Decoder_Reads_Correct_Resolution_From_Jfif() + { + using (Image image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateImage()) + { + Assert.Equal(300, image.MetaData.HorizontalResolution); + Assert.Equal(300, image.MetaData.VerticalResolution); + } + } + + [Fact] + public void Decoder_Reads_Correct_Resolution_From_Exif() + { + using (Image image = TestFile.Create(TestImages.Jpeg.Baseline.Jpeg420).CreateImage()) + { + Assert.Equal(72, image.MetaData.HorizontalResolution); + Assert.Equal(72, image.MetaData.VerticalResolution); + } + } } } \ No newline at end of file From b432e33ea845e9bb420f30effe57e566cff0117a Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Thu, 9 Feb 2017 12:37:25 +0000 Subject: [PATCH 047/142] Update SixLabors.Shapes --- src/ImageSharp.Drawing.Paths/DrawShape.cs | 112 ------------- src/ImageSharp.Drawing.Paths/FillShape.cs | 78 --------- src/ImageSharp.Drawing.Paths/ShapePath.cs | 63 ++----- src/ImageSharp.Drawing.Paths/ShapeRegion.cs | 17 +- src/ImageSharp.Drawing.Paths/project.json | 2 +- .../Drawing/Paths/DrawBeziersTests.cs | 20 +-- .../Drawing/Paths/DrawLinesTests.cs | 18 +- .../Drawing/Paths/DrawPath.cs | 18 +- .../Drawing/Paths/DrawPolygon.cs | 18 +- .../Drawing/Paths/DrawRectangle.cs | 18 +- .../Drawing/Paths/DrawShape.cs | 156 ------------------ .../Drawing/Paths/FillShape.cs | 107 ------------ .../Drawing/Paths/ShapePathTests.cs | 82 ++------- .../Drawing/Paths/ShapeRegionTests.cs | 43 +++-- 14 files changed, 81 insertions(+), 671 deletions(-) delete mode 100644 src/ImageSharp.Drawing.Paths/DrawShape.cs delete mode 100644 src/ImageSharp.Drawing.Paths/FillShape.cs delete mode 100644 tests/ImageSharp.Tests/Drawing/Paths/DrawShape.cs delete mode 100644 tests/ImageSharp.Tests/Drawing/Paths/FillShape.cs diff --git a/src/ImageSharp.Drawing.Paths/DrawShape.cs b/src/ImageSharp.Drawing.Paths/DrawShape.cs deleted file mode 100644 index 748bb6a11d..0000000000 --- a/src/ImageSharp.Drawing.Paths/DrawShape.cs +++ /dev/null @@ -1,112 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - using System; - - using Drawing; - using Drawing.Brushes; - using Drawing.Pens; - - using SixLabors.Shapes; - - /// - /// Extension methods for the type. - /// - public static partial class ImageExtensions - { - /// - /// Draws the outline of the polygon with the provided pen. - /// - /// The type of the color. - /// The image this method extends. - /// The pen. - /// The shape. - /// The options. - /// The . - public static Image Draw(this Image source, IPen pen, IShape shape, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable - { - return source.Draw(pen, new ShapePath(shape), options); - } - - /// - /// Draws the outline of the polygon with the provided pen. - /// - /// The type of the color. - /// The image this method extends. - /// The pen. - /// The shape. - /// The . - public static Image Draw(this Image source, IPen pen, IShape shape) - where TColor : struct, IPackedPixel, IEquatable - { - return source.Draw(pen, shape, GraphicsOptions.Default); - } - - /// - /// Draws the outline of the polygon with the provided brush at the provided thickness. - /// - /// The type of the color. - /// The image this method extends. - /// The brush. - /// The thickness. - /// The shape. - /// The options. - /// The . - public static Image Draw(this Image source, IBrush brush, float thickness, IShape shape, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable - { - return source.Draw(new Pen(brush, thickness), shape, options); - } - - /// - /// Draws the outline of the polygon with the provided brush at the provided thickness. - /// - /// The type of the color. - /// The image this method extends. - /// The brush. - /// The thickness. - /// The shape. - /// The . - public static Image Draw(this Image source, IBrush brush, float thickness, IShape shape) - where TColor : struct, IPackedPixel, IEquatable - { - return source.Draw(new Pen(brush, thickness), shape); - } - - /// - /// Draws the outline of the polygon with the provided brush at the provided thickness. - /// - /// The type of the color. - /// The image this method extends. - /// The color. - /// The thickness. - /// The shape. - /// The options. - /// The . - public static Image Draw(this Image source, TColor color, float thickness, IShape shape, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable - { - return source.Draw(new SolidBrush(color), thickness, shape, options); - } - - /// - /// Draws the outline of the polygon with the provided brush at the provided thickness. - /// - /// The type of the color. - /// The image this method extends. - /// The color. - /// The thickness. - /// The shape. - /// The . - public static Image Draw(this Image source, TColor color, float thickness, IShape shape) - where TColor : struct, IPackedPixel, IEquatable - { - return source.Draw(new SolidBrush(color), thickness, shape); - } - } -} diff --git a/src/ImageSharp.Drawing.Paths/FillShape.cs b/src/ImageSharp.Drawing.Paths/FillShape.cs deleted file mode 100644 index f143e75b72..0000000000 --- a/src/ImageSharp.Drawing.Paths/FillShape.cs +++ /dev/null @@ -1,78 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - using System; - - using Drawing; - using Drawing.Brushes; - - using SixLabors.Shapes; - - /// - /// Extension methods for the type. - /// - public static partial class ImageExtensions - { - /// - /// Flood fills the image in the shape of the provided polygon with the specified brush. - /// - /// The type of the color. - /// The image this method extends. - /// The brush. - /// The shape. - /// The graphics options. - /// The . - public static Image Fill(this Image source, IBrush brush, IShape shape, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable - { - return source.Fill(brush, new ShapeRegion(shape), options); - } - - /// - /// Flood fills the image in the shape of the provided polygon with the specified brush. - /// - /// The type of the color. - /// The image this method extends. - /// The brush. - /// The shape. - /// The . - public static Image Fill(this Image source, IBrush brush, IShape shape) - where TColor : struct, IPackedPixel, IEquatable - { - return source.Fill(brush, new ShapeRegion(shape), GraphicsOptions.Default); - } - - /// - /// Flood fills the image in the shape of the provided polygon with the specified brush.. - /// - /// The type of the color. - /// The image this method extends. - /// The color. - /// The shape. - /// The options. - /// The . - public static Image Fill(this Image source, TColor color, IShape shape, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable - { - return source.Fill(new SolidBrush(color), shape, options); - } - - /// - /// Flood fills the image in the shape of the provided polygon with the specified brush.. - /// - /// The type of the color. - /// The image this method extends. - /// The color. - /// The shape. - /// The . - public static Image Fill(this Image source, TColor color, IShape shape) - where TColor : struct, IPackedPixel, IEquatable - { - return source.Fill(new SolidBrush(color), shape); - } - } -} diff --git a/src/ImageSharp.Drawing.Paths/ShapePath.cs b/src/ImageSharp.Drawing.Paths/ShapePath.cs index 5ea38a365b..d0376ccc1e 100644 --- a/src/ImageSharp.Drawing.Paths/ShapePath.cs +++ b/src/ImageSharp.Drawing.Paths/ShapePath.cs @@ -14,53 +14,27 @@ namespace ImageSharp.Drawing using Rectangle = ImageSharp.Rectangle; /// - /// A drawable mapping between a / and a drawable/fillable region. + /// A drawable mapping between a and a drawable region. /// internal class ShapePath : Drawable { - /// - /// The fillable shape - /// - private readonly IShape shape; - /// /// Initializes a new instance of the class. /// /// The path. public ShapePath(IPath path) - : this(ImmutableArray.Create(path)) { - this.shape = path.AsShape(); - this.Bounds = RectangleF.Ceiling(path.Bounds.Convert()); + this.Path = path; + this.Bounds = path.Bounds.Convert(); } /// - /// Initializes a new instance of the class. + /// Gets the fillable shape /// - /// The shape. - public ShapePath(IShape shape) - : this(shape.Paths) - { - this.shape = shape; - this.Bounds = RectangleF.Ceiling(shape.Bounds.Convert()); - } - - /// - /// Initializes a new instance of the class. - /// - /// The paths. - private ShapePath(ImmutableArray paths) - { - this.Paths = paths; - } - - /// - /// Gets the drawable paths - /// - public ImmutableArray Paths { get; } + public IPath Path { get; } /// - public override int MaxIntersections => this.shape.MaxIntersections; + public override int MaxIntersections => this.Path.MaxIntersections; /// public override Rectangle Bounds { get; } @@ -73,7 +47,7 @@ namespace ImageSharp.Drawing Vector2[] innerbuffer = ArrayPool.Shared.Rent(length); try { - int count = this.shape.FindIntersections(start, end, innerbuffer, length, 0); + int count = this.Path.FindIntersections(start, end, innerbuffer, length, 0); for (int i = 0; i < count; i++) { @@ -96,7 +70,7 @@ namespace ImageSharp.Drawing Vector2[] innerbuffer = ArrayPool.Shared.Rent(length); try { - int count = this.shape.FindIntersections(start, end, innerbuffer, length, 0); + int count = this.Path.FindIntersections(start, end, innerbuffer, length, 0); for (int i = 0; i < count; i++) { @@ -115,24 +89,15 @@ namespace ImageSharp.Drawing public override PointInfo GetPointInfo(int x, int y) { Vector2 point = new Vector2(x, y); - float distanceFromPath = float.MaxValue; - float distanceAlongPath = 0; - - // ReSharper disable once ForCanBeConvertedToForeach - for (int i = 0; i < this.Paths.Length; i++) - { - SixLabors.Shapes.PointInfo p = this.Paths[i].Distance(point); - if (p.DistanceFromPath < distanceFromPath) - { - distanceFromPath = p.DistanceFromPath; - distanceAlongPath = p.DistanceAlongPath; - } - } + SixLabors.Shapes.PointInfo dist = this.Path.Distance(point); return new PointInfo { - DistanceAlongPath = distanceAlongPath, - DistanceFromPath = distanceFromPath + DistanceAlongPath = dist.DistanceAlongPath, + DistanceFromPath = + dist.DistanceFromPath < 0 + ? -dist.DistanceFromPath + : dist.DistanceFromPath }; } } diff --git a/src/ImageSharp.Drawing.Paths/ShapeRegion.cs b/src/ImageSharp.Drawing.Paths/ShapeRegion.cs index dc035e3b05..b02c5c2e5b 100644 --- a/src/ImageSharp.Drawing.Paths/ShapeRegion.cs +++ b/src/ImageSharp.Drawing.Paths/ShapeRegion.cs @@ -13,33 +13,24 @@ namespace ImageSharp.Drawing using Rectangle = ImageSharp.Rectangle; /// - /// A drawable mapping between a / and a drawable/fillable region. + /// A mapping between a and a region. /// internal class ShapeRegion : Region { - /// - /// Initializes a new instance of the class. - /// - /// The path. - public ShapeRegion(IPath path) - : this(path.AsShape()) - { - } - /// /// Initializes a new instance of the class. /// /// The shape. - public ShapeRegion(IShape shape) + public ShapeRegion(IPath shape) { - this.Shape = shape; + this.Shape = shape.AsClosedPath(); this.Bounds = shape.Bounds.Convert(); } /// /// Gets the fillable shape /// - public IShape Shape { get; } + public IPath Shape { get; } /// public override int MaxIntersections => this.Shape.MaxIntersections; diff --git a/src/ImageSharp.Drawing.Paths/project.json b/src/ImageSharp.Drawing.Paths/project.json index c903f805af..1cc7293801 100644 --- a/src/ImageSharp.Drawing.Paths/project.json +++ b/src/ImageSharp.Drawing.Paths/project.json @@ -44,7 +44,7 @@ "ImageSharp.Drawing": { "target": "project" }, - "SixLabors.Shapes": "0.1.0-alpha0004", + "SixLabors.Shapes": "0.1.0-alpha0005", "StyleCop.Analyzers": { "version": "1.0.0", "type": "build" diff --git a/tests/ImageSharp.Tests/Drawing/Paths/DrawBeziersTests.cs b/tests/ImageSharp.Tests/Drawing/Paths/DrawBeziersTests.cs index 0d3b469812..82e2f72a2f 100644 --- a/tests/ImageSharp.Tests/Drawing/Paths/DrawBeziersTests.cs +++ b/tests/ImageSharp.Tests/Drawing/Paths/DrawBeziersTests.cs @@ -50,9 +50,10 @@ namespace ImageSharp.Tests.Drawing.Paths Assert.Equal(GraphicsOptions.Default, processor.Options); ShapePath path = Assert.IsType(processor.Path); - Assert.NotEmpty(path.Paths); + Assert.NotNull(path.Path); + + SixLabors.Shapes.Path vector = Assert.IsType(path.Path); - SixLabors.Shapes.Path vector = Assert.IsType(path.Paths[0]); BezierLineSegment segment = Assert.IsType(vector.LineSegments[0]); Pen pen = Assert.IsType>(processor.Pen); @@ -71,9 +72,8 @@ namespace ImageSharp.Tests.Drawing.Paths Assert.Equal(noneDefault, processor.Options); ShapePath path = Assert.IsType(processor.Path); - Assert.NotEmpty(path.Paths); - SixLabors.Shapes.Path vector = Assert.IsType(path.Paths[0]); + SixLabors.Shapes.Path vector = Assert.IsType(path.Path); BezierLineSegment segment = Assert.IsType(vector.LineSegments[0]); Pen pen = Assert.IsType>(processor.Pen); @@ -92,9 +92,8 @@ namespace ImageSharp.Tests.Drawing.Paths Assert.Equal(GraphicsOptions.Default, processor.Options); ShapePath path = Assert.IsType(processor.Path); - Assert.NotEmpty(path.Paths); - SixLabors.Shapes.Path vector = Assert.IsType(path.Paths[0]); + SixLabors.Shapes.Path vector = Assert.IsType(path.Path); BezierLineSegment segment = Assert.IsType(vector.LineSegments[0]); Pen pen = Assert.IsType>(processor.Pen); @@ -115,9 +114,8 @@ namespace ImageSharp.Tests.Drawing.Paths Assert.Equal(noneDefault, processor.Options); ShapePath path = Assert.IsType(processor.Path); - Assert.NotEmpty(path.Paths); - SixLabors.Shapes.Path vector = Assert.IsType(path.Paths[0]); + SixLabors.Shapes.Path vector = Assert.IsType(path.Path); BezierLineSegment segment = Assert.IsType(vector.LineSegments[0]); Pen pen = Assert.IsType>(processor.Pen); @@ -138,9 +136,8 @@ namespace ImageSharp.Tests.Drawing.Paths Assert.Equal(GraphicsOptions.Default, processor.Options); ShapePath path = Assert.IsType(processor.Path); - Assert.NotEmpty(path.Paths); - SixLabors.Shapes.Path vector = Assert.IsType(path.Paths[0]); + SixLabors.Shapes.Path vector = Assert.IsType(path.Path); BezierLineSegment segment = Assert.IsType(vector.LineSegments[0]); Assert.Equal(pen, processor.Pen); @@ -157,9 +154,8 @@ namespace ImageSharp.Tests.Drawing.Paths Assert.Equal(noneDefault, processor.Options); ShapePath path = Assert.IsType(processor.Path); - Assert.NotEmpty(path.Paths); - SixLabors.Shapes.Path vector = Assert.IsType(path.Paths[0]); + SixLabors.Shapes.Path vector = Assert.IsType(path.Path); BezierLineSegment segment = Assert.IsType(vector.LineSegments[0]); Assert.Equal(pen, processor.Pen); diff --git a/tests/ImageSharp.Tests/Drawing/Paths/DrawLinesTests.cs b/tests/ImageSharp.Tests/Drawing/Paths/DrawLinesTests.cs index bbed3cae67..cc126614f6 100644 --- a/tests/ImageSharp.Tests/Drawing/Paths/DrawLinesTests.cs +++ b/tests/ImageSharp.Tests/Drawing/Paths/DrawLinesTests.cs @@ -50,9 +50,8 @@ namespace ImageSharp.Tests.Drawing.Paths Assert.Equal(GraphicsOptions.Default, processor.Options); ShapePath path = Assert.IsType(processor.Path); - Assert.NotEmpty(path.Paths); - SixLabors.Shapes.Path vector = Assert.IsType(path.Paths[0]); + SixLabors.Shapes.Path vector = Assert.IsType(path.Path); LinearLineSegment segment = Assert.IsType(vector.LineSegments[0]); Pen pen = Assert.IsType>(processor.Pen); @@ -71,9 +70,8 @@ namespace ImageSharp.Tests.Drawing.Paths Assert.Equal(noneDefault, processor.Options); ShapePath path = Assert.IsType(processor.Path); - Assert.NotEmpty(path.Paths); - SixLabors.Shapes.Path vector = Assert.IsType(path.Paths[0]); + SixLabors.Shapes.Path vector = Assert.IsType(path.Path); LinearLineSegment segment = Assert.IsType(vector.LineSegments[0]); Pen pen = Assert.IsType>(processor.Pen); @@ -92,9 +90,8 @@ namespace ImageSharp.Tests.Drawing.Paths Assert.Equal(GraphicsOptions.Default, processor.Options); ShapePath path = Assert.IsType(processor.Path); - Assert.NotEmpty(path.Paths); - SixLabors.Shapes.Path vector = Assert.IsType(path.Paths[0]); + SixLabors.Shapes.Path vector = Assert.IsType(path.Path); LinearLineSegment segment = Assert.IsType(vector.LineSegments[0]); Pen pen = Assert.IsType>(processor.Pen); @@ -115,9 +112,8 @@ namespace ImageSharp.Tests.Drawing.Paths Assert.Equal(noneDefault, processor.Options); ShapePath path = Assert.IsType(processor.Path); - Assert.NotEmpty(path.Paths); - SixLabors.Shapes.Path vector = Assert.IsType(path.Paths[0]); + SixLabors.Shapes.Path vector = Assert.IsType(path.Path); LinearLineSegment segment = Assert.IsType(vector.LineSegments[0]); Pen pen = Assert.IsType>(processor.Pen); @@ -138,9 +134,8 @@ namespace ImageSharp.Tests.Drawing.Paths Assert.Equal(GraphicsOptions.Default, processor.Options); ShapePath path = Assert.IsType(processor.Path); - Assert.NotEmpty(path.Paths); - SixLabors.Shapes.Path vector = Assert.IsType(path.Paths[0]); + SixLabors.Shapes.Path vector = Assert.IsType(path.Path); LinearLineSegment segment = Assert.IsType(vector.LineSegments[0]); Assert.Equal(pen, processor.Pen); @@ -157,9 +152,8 @@ namespace ImageSharp.Tests.Drawing.Paths Assert.Equal(noneDefault, processor.Options); ShapePath path = Assert.IsType(processor.Path); - Assert.NotEmpty(path.Paths); - SixLabors.Shapes.Path vector = Assert.IsType(path.Paths[0]); + SixLabors.Shapes.Path vector = Assert.IsType(path.Path); LinearLineSegment segment = Assert.IsType(vector.LineSegments[0]); Assert.Equal(pen, processor.Pen); diff --git a/tests/ImageSharp.Tests/Drawing/Paths/DrawPath.cs b/tests/ImageSharp.Tests/Drawing/Paths/DrawPath.cs index 531546370d..6c1c068135 100644 --- a/tests/ImageSharp.Tests/Drawing/Paths/DrawPath.cs +++ b/tests/ImageSharp.Tests/Drawing/Paths/DrawPath.cs @@ -50,8 +50,7 @@ namespace ImageSharp.Tests.Drawing.Paths Assert.Equal(GraphicsOptions.Default, processor.Options); ShapePath shapepath = Assert.IsType(processor.Path); - Assert.NotEmpty(shapepath.Paths); - Assert.Equal(path, shapepath.Paths[0]); + Assert.Equal(path, shapepath.Path); Pen pen = Assert.IsType>(processor.Pen); Assert.Equal(brush, pen.Brush); @@ -69,8 +68,7 @@ namespace ImageSharp.Tests.Drawing.Paths Assert.Equal(noneDefault, processor.Options); ShapePath shapepath = Assert.IsType(processor.Path); - Assert.NotEmpty(shapepath.Paths); - Assert.Equal(path, shapepath.Paths[0]); + Assert.Equal(path, shapepath.Path); Pen pen = Assert.IsType>(processor.Pen); Assert.Equal(brush, pen.Brush); @@ -88,8 +86,7 @@ namespace ImageSharp.Tests.Drawing.Paths Assert.Equal(GraphicsOptions.Default, processor.Options); ShapePath shapepath = Assert.IsType(processor.Path); - Assert.NotEmpty(shapepath.Paths); - Assert.Equal(path, shapepath.Paths[0]); + Assert.Equal(path, shapepath.Path); Pen pen = Assert.IsType>(processor.Pen); Assert.Equal(thickness, pen.Width); @@ -109,8 +106,7 @@ namespace ImageSharp.Tests.Drawing.Paths Assert.Equal(noneDefault, processor.Options); ShapePath shapepath = Assert.IsType(processor.Path); - Assert.NotEmpty(shapepath.Paths); - Assert.Equal(path, shapepath.Paths[0]); + Assert.Equal(path, shapepath.Path); Pen pen = Assert.IsType>(processor.Pen); Assert.Equal(thickness, pen.Width); @@ -130,8 +126,7 @@ namespace ImageSharp.Tests.Drawing.Paths Assert.Equal(GraphicsOptions.Default, processor.Options); ShapePath shapepath = Assert.IsType(processor.Path); - Assert.NotEmpty(shapepath.Paths); - Assert.Equal(path, shapepath.Paths[0]); + Assert.Equal(path, shapepath.Path); Assert.Equal(pen, processor.Pen); } @@ -147,8 +142,7 @@ namespace ImageSharp.Tests.Drawing.Paths Assert.Equal(noneDefault, processor.Options); ShapePath shapepath = Assert.IsType(processor.Path); - Assert.NotEmpty(shapepath.Paths); - Assert.Equal(path, shapepath.Paths[0]); + Assert.Equal(path, shapepath.Path); Assert.Equal(pen, processor.Pen); } diff --git a/tests/ImageSharp.Tests/Drawing/Paths/DrawPolygon.cs b/tests/ImageSharp.Tests/Drawing/Paths/DrawPolygon.cs index 399e79dae1..9de0523313 100644 --- a/tests/ImageSharp.Tests/Drawing/Paths/DrawPolygon.cs +++ b/tests/ImageSharp.Tests/Drawing/Paths/DrawPolygon.cs @@ -50,9 +50,8 @@ namespace ImageSharp.Tests.Drawing.Paths Assert.Equal(GraphicsOptions.Default, processor.Options); ShapePath path = Assert.IsType(processor.Path); - Assert.NotEmpty(path.Paths); - Polygon vector = Assert.IsType(path.Paths[0].AsShape()); + Polygon vector = Assert.IsType(path.Path); LinearLineSegment segment = Assert.IsType(vector.LineSegments[0]); Pen pen = Assert.IsType>(processor.Pen); @@ -71,9 +70,8 @@ namespace ImageSharp.Tests.Drawing.Paths Assert.Equal(noneDefault, processor.Options); ShapePath path = Assert.IsType(processor.Path); - Assert.NotEmpty(path.Paths); - Polygon vector = Assert.IsType(path.Paths[0].AsShape()); + Polygon vector = Assert.IsType(path.Path); LinearLineSegment segment = Assert.IsType(vector.LineSegments[0]); Pen pen = Assert.IsType>(processor.Pen); @@ -92,9 +90,8 @@ namespace ImageSharp.Tests.Drawing.Paths Assert.Equal(GraphicsOptions.Default, processor.Options); ShapePath path = Assert.IsType(processor.Path); - Assert.NotEmpty(path.Paths); - Polygon vector = Assert.IsType(path.Paths[0].AsShape()); + Polygon vector = Assert.IsType(path.Path); LinearLineSegment segment = Assert.IsType(vector.LineSegments[0]); Pen pen = Assert.IsType>(processor.Pen); @@ -115,9 +112,8 @@ namespace ImageSharp.Tests.Drawing.Paths Assert.Equal(noneDefault, processor.Options); ShapePath path = Assert.IsType(processor.Path); - Assert.NotEmpty(path.Paths); - Polygon vector = Assert.IsType(path.Paths[0].AsShape()); + Polygon vector = Assert.IsType(path.Path); LinearLineSegment segment = Assert.IsType(vector.LineSegments[0]); Pen pen = Assert.IsType>(processor.Pen); @@ -138,9 +134,8 @@ namespace ImageSharp.Tests.Drawing.Paths Assert.Equal(GraphicsOptions.Default, processor.Options); ShapePath path = Assert.IsType(processor.Path); - Assert.NotEmpty(path.Paths); - Polygon vector = Assert.IsType(path.Paths[0].AsShape()); + Polygon vector = Assert.IsType(path.Path); LinearLineSegment segment = Assert.IsType(vector.LineSegments[0]); Assert.Equal(pen, processor.Pen); @@ -157,9 +152,8 @@ namespace ImageSharp.Tests.Drawing.Paths Assert.Equal(noneDefault, processor.Options); ShapePath path = Assert.IsType(processor.Path); - Assert.NotEmpty(path.Paths); - Polygon vector = Assert.IsType(path.Paths[0].AsShape()); + Polygon vector = Assert.IsType(path.Path); LinearLineSegment segment = Assert.IsType(vector.LineSegments[0]); Assert.Equal(pen, processor.Pen); diff --git a/tests/ImageSharp.Tests/Drawing/Paths/DrawRectangle.cs b/tests/ImageSharp.Tests/Drawing/Paths/DrawRectangle.cs index 3842063b3e..215d5a7c70 100644 --- a/tests/ImageSharp.Tests/Drawing/Paths/DrawRectangle.cs +++ b/tests/ImageSharp.Tests/Drawing/Paths/DrawRectangle.cs @@ -46,8 +46,7 @@ namespace ImageSharp.Tests.Drawing.Paths Assert.Equal(GraphicsOptions.Default, processor.Options); ShapePath shapepath = Assert.IsType(processor.Path); - Assert.NotEmpty(shapepath.Paths); - SixLabors.Shapes.Rectangle rect = Assert.IsType(shapepath.Paths[0].AsShape()); + SixLabors.Shapes.Rectangle rect = Assert.IsType(shapepath.Path); Assert.Equal(rect.Location.X, rectangle.X); Assert.Equal(rect.Location.Y, rectangle.Y); @@ -70,9 +69,8 @@ namespace ImageSharp.Tests.Drawing.Paths Assert.Equal(noneDefault, processor.Options); ShapePath shapepath = Assert.IsType(processor.Path); - Assert.NotEmpty(shapepath.Paths); - SixLabors.Shapes.Rectangle rect = Assert.IsType(shapepath.Paths[0].AsShape()); + SixLabors.Shapes.Rectangle rect = Assert.IsType(shapepath.Path); Assert.Equal(rect.Location.X, rectangle.X); Assert.Equal(rect.Location.Y, rectangle.Y); @@ -95,9 +93,8 @@ namespace ImageSharp.Tests.Drawing.Paths Assert.Equal(GraphicsOptions.Default, processor.Options); ShapePath shapepath = Assert.IsType(processor.Path); - Assert.NotEmpty(shapepath.Paths); - SixLabors.Shapes.Rectangle rect = Assert.IsType(shapepath.Paths[0].AsShape()); + SixLabors.Shapes.Rectangle rect = Assert.IsType(shapepath.Path); Assert.Equal(rect.Location.X, rectangle.X); Assert.Equal(rect.Location.Y, rectangle.Y); @@ -122,9 +119,8 @@ namespace ImageSharp.Tests.Drawing.Paths Assert.Equal(noneDefault, processor.Options); ShapePath shapepath = Assert.IsType(processor.Path); - Assert.NotEmpty(shapepath.Paths); - SixLabors.Shapes.Rectangle rect = Assert.IsType(shapepath.Paths[0].AsShape()); + SixLabors.Shapes.Rectangle rect = Assert.IsType(shapepath.Path); Assert.Equal(rect.Location.X, rectangle.X); Assert.Equal(rect.Location.Y, rectangle.Y); @@ -149,9 +145,8 @@ namespace ImageSharp.Tests.Drawing.Paths Assert.Equal(GraphicsOptions.Default, processor.Options); ShapePath shapepath = Assert.IsType(processor.Path); - Assert.NotEmpty(shapepath.Paths); - SixLabors.Shapes.Rectangle rect = Assert.IsType(shapepath.Paths[0].AsShape()); + SixLabors.Shapes.Rectangle rect = Assert.IsType(shapepath.Path); Assert.Equal(rect.Location.X, rectangle.X); Assert.Equal(rect.Location.Y, rectangle.Y); @@ -172,9 +167,8 @@ namespace ImageSharp.Tests.Drawing.Paths Assert.Equal(noneDefault, processor.Options); ShapePath shapepath = Assert.IsType(processor.Path); - Assert.NotEmpty(shapepath.Paths); - SixLabors.Shapes.Rectangle rect = Assert.IsType(shapepath.Paths[0].AsShape()); + SixLabors.Shapes.Rectangle rect = Assert.IsType(shapepath.Path); Assert.Equal(rect.Location.X, rectangle.X); Assert.Equal(rect.Location.Y, rectangle.Y); diff --git a/tests/ImageSharp.Tests/Drawing/Paths/DrawShape.cs b/tests/ImageSharp.Tests/Drawing/Paths/DrawShape.cs deleted file mode 100644 index 30a6646f40..0000000000 --- a/tests/ImageSharp.Tests/Drawing/Paths/DrawShape.cs +++ /dev/null @@ -1,156 +0,0 @@ - -namespace ImageSharp.Tests.Drawing.Paths -{ - using System; - using System.IO; - using ImageSharp; - using ImageSharp.Drawing.Brushes; - using Processing; - using System.Collections.Generic; - using Xunit; - using ImageSharp.Drawing; - using System.Numerics; - using SixLabors.Shapes; - using ImageSharp.Drawing.Processors; - using ImageSharp.Drawing.Pens; - - public class DrawShape: IDisposable - { - float thickness = 7.2f; - GraphicsOptions noneDefault = new GraphicsOptions(); - Color color = Color.HotPink; - SolidBrush brush = Brushes.Solid(Color.HotPink); - Pen pen = new Pen(Color.Gray, 99.9f); - IShape shape = new SixLabors.Shapes.Polygon(new LinearLineSegment(new Vector2[] { - new Vector2(10,10), - new Vector2(20,10), - new Vector2(20,10), - new Vector2(30,10), - })); - private ProcessorWatchingImage img; - - public DrawShape() - { - this.img = new Paths.ProcessorWatchingImage(10, 10); - } - - public void Dispose() - { - img.Dispose(); - } - - [Fact] - public void CorrectlySetsBrushThicknessAndShape() - { - img.Draw(brush, thickness, shape); - - Assert.NotEmpty(img.ProcessorApplications); - DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); - - Assert.Equal(GraphicsOptions.Default, processor.Options); - - ShapePath shapepath = Assert.IsType(processor.Path); - Assert.NotEmpty(shapepath.Paths); - Assert.Equal(shape, shapepath.Paths[0].AsShape()); - - Pen pen = Assert.IsType>(processor.Pen); - Assert.Equal(brush, pen.Brush); - Assert.Equal(thickness, pen.Width); - } - - [Fact] - public void CorrectlySetsBrushThicknessShapeAndOptions() - { - img.Draw(brush, thickness, shape, noneDefault); - - Assert.NotEmpty(img.ProcessorApplications); - DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); - - Assert.Equal(noneDefault, processor.Options); - - ShapePath shapepath = Assert.IsType(processor.Path); - Assert.NotEmpty(shapepath.Paths); - Assert.Equal(shape, shapepath.Paths[0].AsShape()); - - Pen pen = Assert.IsType>(processor.Pen); - Assert.Equal(brush, pen.Brush); - Assert.Equal(thickness, pen.Width); - } - - [Fact] - public void CorrectlySetsColorThicknessAndShape() - { - img.Draw(color, thickness, shape); - - Assert.NotEmpty(img.ProcessorApplications); - DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); - - Assert.Equal(GraphicsOptions.Default, processor.Options); - - ShapePath shapepath = Assert.IsType(processor.Path); - Assert.NotEmpty(shapepath.Paths); - Assert.Equal(shape, shapepath.Paths[0].AsShape()); - - Pen pen = Assert.IsType>(processor.Pen); - Assert.Equal(thickness, pen.Width); - - SolidBrush brush = Assert.IsType>(pen.Brush); - Assert.Equal(color, brush.Color); - } - - [Fact] - public void CorrectlySetsColorThicknessShapeAndOptions() - { - img.Draw(color, thickness, shape, noneDefault); - - Assert.NotEmpty(img.ProcessorApplications); - DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); - - Assert.Equal(noneDefault, processor.Options); - - ShapePath shapepath = Assert.IsType(processor.Path); - Assert.NotEmpty(shapepath.Paths); - Assert.Equal(shape, shapepath.Paths[0].AsShape()); - - Pen pen = Assert.IsType>(processor.Pen); - Assert.Equal(thickness, pen.Width); - - SolidBrush brush = Assert.IsType>(pen.Brush); - Assert.Equal(color, brush.Color); - } - - [Fact] - public void CorrectlySetsPenAndShape() - { - img.Draw(pen, shape); - - Assert.NotEmpty(img.ProcessorApplications); - DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); - - Assert.Equal(GraphicsOptions.Default, processor.Options); - - ShapePath shapepath = Assert.IsType(processor.Path); - Assert.NotEmpty(shapepath.Paths); - Assert.Equal(shape, shapepath.Paths[0].AsShape()); - - Assert.Equal(pen, processor.Pen); - } - - [Fact] - public void CorrectlySetsPenShapeAndOptions() - { - img.Draw(pen, shape, noneDefault); - - Assert.NotEmpty(img.ProcessorApplications); - DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); - - Assert.Equal(noneDefault, processor.Options); - - ShapePath shapepath = Assert.IsType(processor.Path); - Assert.NotEmpty(shapepath.Paths); - Assert.Equal(shape, shapepath.Paths[0].AsShape()); - - Assert.Equal(pen, processor.Pen); - } - } -} diff --git a/tests/ImageSharp.Tests/Drawing/Paths/FillShape.cs b/tests/ImageSharp.Tests/Drawing/Paths/FillShape.cs deleted file mode 100644 index 140870a3c2..0000000000 --- a/tests/ImageSharp.Tests/Drawing/Paths/FillShape.cs +++ /dev/null @@ -1,107 +0,0 @@ - -namespace ImageSharp.Tests.Drawing.Paths -{ - using System; - using System.IO; - using ImageSharp; - using ImageSharp.Drawing.Brushes; - using Processing; - using System.Collections.Generic; - using Xunit; - using ImageSharp.Drawing; - using System.Numerics; - using SixLabors.Shapes; - using ImageSharp.Drawing.Processors; - using ImageSharp.Drawing.Pens; - - public class FillShape : IDisposable - { - GraphicsOptions noneDefault = new GraphicsOptions(); - Color color = Color.HotPink; - SolidBrush brush = Brushes.Solid(Color.HotPink); - IShape shape = new Polygon(new LinearLineSegment(new Vector2[] { - new Vector2(10,10), - new Vector2(20,10), - new Vector2(20,10), - new Vector2(30,10), - })); - private ProcessorWatchingImage img; - - public FillShape() - { - this.img = new Paths.ProcessorWatchingImage(10, 10); - } - - public void Dispose() - { - img.Dispose(); - } - - [Fact] - public void CorrectlySetsBrushAndShape() - { - img.Fill(brush, shape); - - Assert.NotEmpty(img.ProcessorApplications); - FillRegionProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); - - Assert.Equal(GraphicsOptions.Default, processor.Options); - - ShapeRegion region = Assert.IsType(processor.Region); - Assert.Equal(shape, region.Shape); - - Assert.Equal(brush, processor.Brush); - } - - [Fact] - public void CorrectlySetsBrushShapeAndOptions() - { - img.Fill(brush, shape, noneDefault); - - Assert.NotEmpty(img.ProcessorApplications); - FillRegionProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); - - Assert.Equal(noneDefault, processor.Options); - - ShapeRegion region = Assert.IsType(processor.Region); - Assert.Equal(shape, region.Shape); - - Assert.Equal(brush, processor.Brush); - } - - [Fact] - public void CorrectlySetsColorAndShape() - { - img.Fill(color, shape); - - Assert.NotEmpty(img.ProcessorApplications); - FillRegionProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); - - Assert.Equal(GraphicsOptions.Default, processor.Options); - - ShapeRegion region = Assert.IsType(processor.Region); - Assert.Equal(shape, region.Shape); - - SolidBrush brush = Assert.IsType>(processor.Brush); - Assert.Equal(color, brush.Color); - } - - [Fact] - public void CorrectlySetsColorShapeAndOptions() - { - img.Fill(color, shape, noneDefault); - - Assert.NotEmpty(img.ProcessorApplications); - FillRegionProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); - - Assert.Equal(noneDefault, processor.Options); - - ShapeRegion region = Assert.IsType(processor.Region); - Assert.Equal(shape, region.Shape); - - SolidBrush brush = Assert.IsType>(processor.Brush); - Assert.Equal(color, brush.Color); - - } - } -} diff --git a/tests/ImageSharp.Tests/Drawing/Paths/ShapePathTests.cs b/tests/ImageSharp.Tests/Drawing/Paths/ShapePathTests.cs index 5fc9678461..494e2a6726 100644 --- a/tests/ImageSharp.Tests/Drawing/Paths/ShapePathTests.cs +++ b/tests/ImageSharp.Tests/Drawing/Paths/ShapePathTests.cs @@ -20,30 +20,18 @@ namespace ImageSharp.Tests.Drawing.Paths { private readonly Mock pathMock1; private readonly Mock pathMock2; - private readonly Mock shapeMock1; private readonly SixLabors.Shapes.Rectangle bounds1; public ShapePathTests() { - this.shapeMock1 = new Mock(); this.pathMock2 = new Mock(); this.pathMock1 = new Mock(); this.bounds1 = new SixLabors.Shapes.Rectangle(10.5f, 10, 10, 10); pathMock1.Setup(x => x.Bounds).Returns(this.bounds1); pathMock2.Setup(x => x.Bounds).Returns(this.bounds1); - shapeMock1.Setup(x => x.Bounds).Returns(this.bounds1); // wire up the 2 mocks to reference eachother - pathMock1.Setup(x => x.AsShape()).Returns(() => shapeMock1.Object); - shapeMock1.Setup(x => x.Paths).Returns(() => ImmutableArray.Create(pathMock1.Object, pathMock2.Object)); - } - - [Fact] - public void ShapePathWithPathCallsAsShape() - { - new ShapePath(pathMock1.Object); - - pathMock1.Verify(x => x.AsShape()); + pathMock1.Setup(x => x.AsClosedPath()).Returns(() => pathMock2.Object); } [Fact] @@ -63,7 +51,7 @@ namespace ImageSharp.Tests.Drawing.Paths ShapePath region = new ShapePath(pathMock1.Object); int i = region.MaxIntersections; - shapeMock1.Verify(x => x.MaxIntersections); + pathMock1.Verify(x => x.MaxIntersections); } [Fact] @@ -72,7 +60,7 @@ namespace ImageSharp.Tests.Drawing.Paths int xToScan = 10; ShapePath region = new ShapePath(pathMock1.Object); - shapeMock1.Setup(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + pathMock1.Setup(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Callback((s, e, b, c, o) => { Assert.Equal(xToScan, s.X); Assert.Equal(xToScan, e.X); @@ -82,7 +70,7 @@ namespace ImageSharp.Tests.Drawing.Paths int i = region.ScanX(xToScan, new float[0], 0, 0); - shapeMock1.Verify(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + pathMock1.Verify(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); } [Fact] @@ -91,7 +79,7 @@ namespace ImageSharp.Tests.Drawing.Paths int yToScan = 10; ShapePath region = new ShapePath(pathMock1.Object); - shapeMock1.Setup(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + pathMock1.Setup(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Callback((s, e, b, c, o) => { Assert.Equal(yToScan, s.Y); Assert.Equal(yToScan, e.Y); @@ -101,7 +89,7 @@ namespace ImageSharp.Tests.Drawing.Paths int i = region.ScanY(yToScan, new float[0], 0, 0); - shapeMock1.Verify(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + pathMock1.Verify(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); } @@ -109,9 +97,9 @@ namespace ImageSharp.Tests.Drawing.Paths public void ShapePathFromShapeScanXProxyToShape() { int xToScan = 10; - ShapePath region = new ShapePath(shapeMock1.Object); + ShapePath region = new ShapePath(pathMock1.Object); - shapeMock1.Setup(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + pathMock1.Setup(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Callback((s, e, b, c, o) => { Assert.Equal(xToScan, s.X); Assert.Equal(xToScan, e.X); @@ -121,59 +109,9 @@ namespace ImageSharp.Tests.Drawing.Paths int i = region.ScanX(xToScan, new float[0], 0, 0); - shapeMock1.Verify(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); - } - - [Fact] - public void ShapePathFromShapeScanYProxyToShape() - { - int yToScan = 10; - ShapePath region = new ShapePath(shapeMock1.Object); - - shapeMock1.Setup(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) - .Callback((s, e, b, c, o) => { - Assert.Equal(yToScan, s.Y); - Assert.Equal(yToScan, e.Y); - Assert.True(s.X < bounds1.Left); - Assert.True(e.X > bounds1.Right); - }).Returns(0); - - int i = region.ScanY(yToScan, new float[0], 0, 0); - - shapeMock1.Verify(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); - } - - [Fact] - public void ShapePathFromShapeConvertsBoundsProxyToShape() - { - ShapePath region = new ShapePath(shapeMock1.Object); - - Assert.Equal(Math.Floor(bounds1.Left), region.Bounds.Left); - Assert.Equal(Math.Ceiling(bounds1.Right), region.Bounds.Right); - - shapeMock1.Verify(x => x.Bounds); - } - - [Fact] - public void ShapePathFromShapeMaxIntersectionsProxyToShape() - { - ShapePath region = new ShapePath(shapeMock1.Object); - - int i = region.MaxIntersections; - shapeMock1.Verify(x => x.MaxIntersections); + pathMock1.Verify(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); } - - [Fact] - public void GetPointInfoCallAllPathsForShape() - { - ShapePath region = new ShapePath(shapeMock1.Object); - - ImageSharp.Drawing.PointInfo info = region.GetPointInfo(10, 1); - - pathMock1.Verify(x => x.Distance(new Vector2(10, 1)), Times.Once); - pathMock2.Verify(x => x.Distance(new Vector2(10, 1)), Times.Once); - } - + [Fact] public void GetPointInfoCallSinglePathForPath() { diff --git a/tests/ImageSharp.Tests/Drawing/Paths/ShapeRegionTests.cs b/tests/ImageSharp.Tests/Drawing/Paths/ShapeRegionTests.cs index 6754949518..aa7c0575c7 100644 --- a/tests/ImageSharp.Tests/Drawing/Paths/ShapeRegionTests.cs +++ b/tests/ImageSharp.Tests/Drawing/Paths/ShapeRegionTests.cs @@ -19,19 +19,16 @@ namespace ImageSharp.Tests.Drawing.Paths public class ShapeRegionTests { private readonly Mock pathMock; - private readonly Mock shapeMock; private readonly SixLabors.Shapes.Rectangle bounds; public ShapeRegionTests() { - this.shapeMock = new Mock(); this.pathMock = new Mock(); this.bounds = new SixLabors.Shapes.Rectangle(10.5f, 10, 10, 10); - shapeMock.Setup(x => x.Bounds).Returns(this.bounds); + pathMock.Setup(x => x.Bounds).Returns(this.bounds); // wire up the 2 mocks to reference eachother - pathMock.Setup(x => x.AsShape()).Returns(() => shapeMock.Object); - shapeMock.Setup(x => x.Paths).Returns(() => ImmutableArray.Create(pathMock.Object)); + pathMock.Setup(x => x.AsClosedPath()).Returns(() => pathMock.Object); } [Fact] @@ -39,7 +36,7 @@ namespace ImageSharp.Tests.Drawing.Paths { new ShapeRegion(pathMock.Object); - pathMock.Verify(x => x.AsShape()); + pathMock.Verify(x => x.AsClosedPath()); } [Fact] @@ -47,7 +44,7 @@ namespace ImageSharp.Tests.Drawing.Paths { ShapeRegion region = new ShapeRegion(pathMock.Object); - Assert.Equal(shapeMock.Object, region.Shape); + Assert.Equal(pathMock.Object, region.Shape); } [Fact] @@ -58,7 +55,7 @@ namespace ImageSharp.Tests.Drawing.Paths Assert.Equal(Math.Floor(bounds.Left), region.Bounds.Left); Assert.Equal(Math.Ceiling(bounds.Right), region.Bounds.Right); - shapeMock.Verify(x => x.Bounds); + pathMock.Verify(x => x.Bounds); } [Fact] @@ -67,7 +64,7 @@ namespace ImageSharp.Tests.Drawing.Paths ShapeRegion region = new ShapeRegion(pathMock.Object); int i = region.MaxIntersections; - shapeMock.Verify(x => x.MaxIntersections); + pathMock.Verify(x => x.MaxIntersections); } [Fact] @@ -76,7 +73,7 @@ namespace ImageSharp.Tests.Drawing.Paths int xToScan = 10; ShapeRegion region = new ShapeRegion(pathMock.Object); - shapeMock.Setup(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + pathMock.Setup(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Callback((s, e, b, c, o) => { Assert.Equal(xToScan, s.X); Assert.Equal(xToScan, e.X); @@ -86,7 +83,7 @@ namespace ImageSharp.Tests.Drawing.Paths int i = region.ScanX(xToScan, new float[0], 0, 0); - shapeMock.Verify(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + pathMock.Verify(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); } [Fact] @@ -95,7 +92,7 @@ namespace ImageSharp.Tests.Drawing.Paths int yToScan = 10; ShapeRegion region = new ShapeRegion(pathMock.Object); - shapeMock.Setup(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + pathMock.Setup(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Callback((s, e, b, c, o) => { Assert.Equal(yToScan, s.Y); Assert.Equal(yToScan, e.Y); @@ -105,7 +102,7 @@ namespace ImageSharp.Tests.Drawing.Paths int i = region.ScanY(yToScan, new float[0], 0, 0); - shapeMock.Verify(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + pathMock.Verify(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); } @@ -113,9 +110,9 @@ namespace ImageSharp.Tests.Drawing.Paths public void ShapeRegionFromShapeScanXProxyToShape() { int xToScan = 10; - ShapeRegion region = new ShapeRegion(shapeMock.Object); + ShapeRegion region = new ShapeRegion(pathMock.Object); - shapeMock.Setup(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + pathMock.Setup(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Callback((s, e, b, c, o) => { Assert.Equal(xToScan, s.X); Assert.Equal(xToScan, e.X); @@ -125,16 +122,16 @@ namespace ImageSharp.Tests.Drawing.Paths int i = region.ScanX(xToScan, new float[0], 0, 0); - shapeMock.Verify(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + pathMock.Verify(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); } [Fact] public void ShapeRegionFromShapeScanYProxyToShape() { int yToScan = 10; - ShapeRegion region = new ShapeRegion(shapeMock.Object); + ShapeRegion region = new ShapeRegion(pathMock.Object); - shapeMock.Setup(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + pathMock.Setup(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Callback((s, e, b, c, o) => { Assert.Equal(yToScan, s.Y); Assert.Equal(yToScan, e.Y); @@ -144,27 +141,27 @@ namespace ImageSharp.Tests.Drawing.Paths int i = region.ScanY(yToScan, new float[0], 0, 0); - shapeMock.Verify(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + pathMock.Verify(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); } [Fact] public void ShapeRegionFromShapeConvertsBoundsProxyToShape() { - ShapeRegion region = new ShapeRegion(shapeMock.Object); + ShapeRegion region = new ShapeRegion(pathMock.Object); Assert.Equal(Math.Floor(bounds.Left), region.Bounds.Left); Assert.Equal(Math.Ceiling(bounds.Right), region.Bounds.Right); - shapeMock.Verify(x => x.Bounds); + pathMock.Verify(x => x.Bounds); } [Fact] public void ShapeRegionFromShapeMaxIntersectionsProxyToShape() { - ShapeRegion region = new ShapeRegion(shapeMock.Object); + ShapeRegion region = new ShapeRegion(pathMock.Object); int i = region.MaxIntersections; - shapeMock.Verify(x => x.MaxIntersections); + pathMock.Verify(x => x.MaxIntersections); } } } From 0610221b0c5cc92d14ba22bacfa2b8be5e43185d Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Thu, 9 Feb 2017 14:24:18 +0000 Subject: [PATCH 048/142] update package version inline with rest of project --- src/ImageSharp.Drawing.Paths/project.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp.Drawing.Paths/project.json b/src/ImageSharp.Drawing.Paths/project.json index 1cc7293801..bf6b1fae8f 100644 --- a/src/ImageSharp.Drawing.Paths/project.json +++ b/src/ImageSharp.Drawing.Paths/project.json @@ -1,5 +1,5 @@ { - "version": "1.0.0-alpha1-*", + "version": "1.0.0-alpha2-*", "title": "ImageSharp.Drawing.Paths", "description": "A cross-platform library for the processing of image files; written in C#", "authors": [ From 8b7923fe6b8c904b32167be1b0e3f1cafd4d557a Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Sun, 12 Feb 2017 16:09:42 +0000 Subject: [PATCH 049/142] file path based apis --- README.md | 10 +++ src/ImageSharp/Image.cs | 17 ++++ src/ImageSharp/Image/Image{TColor}.cs | 80 ++++++++++++++++++- src/ImageSharp/project.json | 24 ++++++ .../Formats/Bmp/BitmapTests.cs | 5 +- tests/ImageSharp.Tests/Image/ImageTests.cs | 31 +++++++ tests/ImageSharp.Tests/TestFile.cs | 5 ++ 7 files changed, 166 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 79f8994629..cfbd18de6e 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,16 @@ Many `Image` methods are also fluent. Here's an example of the code required to resize an image using the default Bicubic resampler then turn the colors into their grayscale equivalent using the BT709 standard matrix. +On platforms supporting netstandard 1.3+ +```csharp +using (Image image = new Image("foo.jpg")) +{ + image.Resize(image.Width / 2, image.Height / 2) + .Grayscale() + .Save("bar.jpg"); // automatic encoder selected based on extension. +} +``` +on netstandard 1.1 - 1.2 ```csharp using (FileStream stream = File.OpenRead("foo.jpg")) using (FileStream output = File.OpenWrite("bar.jpg")) diff --git a/src/ImageSharp/Image.cs b/src/ImageSharp/Image.cs index 7adea78b24..4da9cac36e 100644 --- a/src/ImageSharp/Image.cs +++ b/src/ImageSharp/Image.cs @@ -44,6 +44,23 @@ namespace ImageSharp { } +#if !NO_FILE_IO + /// + /// Initializes a new instance of the class. + /// + /// + /// A file path to read image information. + /// + /// + /// The configuration providing initialization code which allows extending the library. + /// + /// Thrown if the is null. + public Image(string filePath, Configuration configuration = null) + : base(filePath, configuration) + { + } +#endif + /// /// Initializes a new instance of the class. /// diff --git a/src/ImageSharp/Image/Image{TColor}.cs b/src/ImageSharp/Image/Image{TColor}.cs index c16bba3447..ded3e376eb 100644 --- a/src/ImageSharp/Image/Image{TColor}.cs +++ b/src/ImageSharp/Image/Image{TColor}.cs @@ -63,6 +63,28 @@ namespace ImageSharp this.Load(stream); } +#if !NO_FILE_IO + /// + /// Initializes a new instance of the class. + /// + /// + /// The file containing image information. + /// + /// + /// The configuration providing initialization code which allows extending the library. + /// + /// Thrown if the is null. + public Image(string filePath, Configuration configuration = null) + : base(configuration) + { + Guard.NotNull(filePath, nameof(filePath)); + using (var fs = File.OpenRead(filePath)) + { + this.Load(fs); + } + } +#endif + /// /// Initializes a new instance of the class. /// @@ -189,11 +211,12 @@ namespace ImageSharp /// /// The stream to save the image to. /// The format to save the image as. - /// Thrown if the stream is null. + /// Thrown if the stream or format is null. /// The public Image Save(Stream stream, IImageFormat format) { Guard.NotNull(stream, nameof(stream)); + Guard.NotNull(format, nameof(format)); format.Encoder.Encode(this, stream); return this; } @@ -203,13 +226,14 @@ namespace ImageSharp /// /// The stream to save the image to. /// The encoder to save the image with. - /// Thrown if the stream is null. + /// Thrown if the stream or encoder is null. /// /// The . /// public Image Save(Stream stream, IImageEncoder encoder) { Guard.NotNull(stream, nameof(stream)); + Guard.NotNull(encoder, nameof(encoder)); encoder.Encode(this, stream); // Reset to the start of the stream. @@ -221,6 +245,58 @@ namespace ImageSharp return this; } +#if !NO_FILE_IO + /// + /// Saves the image to the given stream using the currently loaded image format. + /// + /// The file path to save the image to. + /// Thrown if the stream is null. + /// The + public Image Save(string filePath) + { + var ext = Path.GetExtension(filePath).Trim('.'); + var format = this.Configuration.ImageFormats.SingleOrDefault(f => f.SupportedExtensions.Contains(ext, StringComparer.OrdinalIgnoreCase)); + if (format == null) + { + throw new InvalidOperationException($"No image formats have been registered for the file extension '{ext}'."); + } + + return this.Save(filePath, format); + } + + /// + /// Saves the image to the given stream using the currently loaded image format. + /// + /// The file path to save the image to. + /// The format to save the image as. + /// Thrown if the format is null. + /// The + public Image Save(string filePath, IImageFormat format) + { + Guard.NotNull(format, nameof(format)); + using (var fs = File.Create(filePath)) + { + return this.Save(fs, format); + } + } + + /// + /// Saves the image to the given stream using the currently loaded image format. + /// + /// The file path to save the image to. + /// The encoder to save the image with. + /// Thrown if the encoder is null. + /// The + public Image Save(string filePath, IImageEncoder encoder) + { + Guard.NotNull(encoder, nameof(encoder)); + using (var fs = File.Create(filePath)) + { + return this.Save(fs, encoder); + } + } +#endif + /// public override string ToString() { diff --git a/src/ImageSharp/project.json b/src/ImageSharp/project.json index 8ad3fd71ab..a5d1bb93f6 100644 --- a/src/ImageSharp/project.json +++ b/src/ImageSharp/project.json @@ -46,7 +46,31 @@ "System.Runtime.CompilerServices.Unsafe": "4.0.0" }, "frameworks": { + "netstandard1.3": { + "dependencies": { + "System.Collections": "4.0.11", + "System.Diagnostics.Debug": "4.0.11", + "System.Diagnostics.Tools": "4.0.1", + "System.IO": "4.1.0", + "System.IO.FileSystem": "4.1.0", + "System.IO.Compression": "4.1.0", + "System.Linq": "4.1.0", + "System.Numerics.Vectors": "4.1.1", + "System.ObjectModel": "4.0.12", + "System.Resources.ResourceManager": "4.0.1", + "System.Runtime.Extensions": "4.1.0", + "System.Runtime.InteropServices": "4.1.0", + "System.Runtime.Numerics": "4.0.1", + "System.Text.Encoding.Extensions": "4.0.11", + "System.Threading": "4.0.11", + "System.Threading.Tasks": "4.0.11", + "System.Threading.Tasks.Parallel": "4.0.1" + } + }, "netstandard1.1": { + "buildOptions": { + "define": [ "NO_FILE_IO" ] + }, "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BitmapTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BitmapTests.cs index 2eb81a6232..c1275335d2 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BitmapTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BitmapTests.cs @@ -31,10 +31,7 @@ namespace ImageSharp.Tests string filename = file.GetFileNameWithoutExtension(bitsPerPixel); using (Image image = file.CreateImage()) { - using (FileStream output = File.OpenWrite($"{path}/{filename}.bmp")) - { - image.Save(output, new BmpEncoder { BitsPerPixel = bitsPerPixel }); - } + image.Save($"{path}/{filename}.bmp", new BmpEncoder { BitsPerPixel = bitsPerPixel }); } } } diff --git a/tests/ImageSharp.Tests/Image/ImageTests.cs b/tests/ImageSharp.Tests/Image/ImageTests.cs index c3c092e8eb..a22456e52f 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.cs @@ -29,5 +29,36 @@ namespace ImageSharp.Tests Assert.Equal(450, image.Height); } } + + [Fact] + public void ConstructorFileSystem() + { + TestFile file = TestFile.Create(TestImages.Bmp.Car); + using (Image image = new Image(file.FilePath)) + { + Assert.Equal(600, image.Width); + Assert.Equal(450, image.Height); + } + } + + [Fact] + public void ConstructorFileSystem_FileNotFound() + { + System.IO.FileNotFoundException ex = Assert.Throws( + () => + { + new Image(Guid.NewGuid().ToString()); + }); + } + + [Fact] + public void ConstructorFileSystem_NullPath() + { + ArgumentNullException ex = Assert.Throws( + () => + { + new Image(null); + }); + } } } diff --git a/tests/ImageSharp.Tests/TestFile.cs b/tests/ImageSharp.Tests/TestFile.cs index 3d66958416..891a45cec9 100644 --- a/tests/ImageSharp.Tests/TestFile.cs +++ b/tests/ImageSharp.Tests/TestFile.cs @@ -54,6 +54,11 @@ namespace ImageSharp.Tests /// public byte[] Bytes { get; } + /// + /// The file name. + /// + public string FilePath => this.file; + /// /// The file name. /// From 0c274cd87870369a760b4895fb041020266c9214 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Sun, 12 Feb 2017 19:09:11 +0000 Subject: [PATCH 050/142] improved test coverage --- tests/ImageSharp.Tests/Image/ImageTests.cs | 67 ++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/tests/ImageSharp.Tests/Image/ImageTests.cs b/tests/ImageSharp.Tests/Image/ImageTests.cs index a22456e52f..aea4330c68 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.cs @@ -7,6 +7,8 @@ namespace ImageSharp.Tests { using System; + using ImageSharp.Formats; + using Xunit; /// @@ -60,5 +62,70 @@ namespace ImageSharp.Tests new Image(null); }); } + + [Fact] + public void Save_DetecedEncoding() + { + string file = TestFile.GetPath("../../TestOutput/Save_DetecedEncoding.png"); + var dir = System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(file)); + using (Image image = new Image(10, 10)) + { + image.Save(file); + } + + var c = TestFile.Create("../../TestOutput/Save_DetecedEncoding.png"); + using (var img = c.CreateImage()) + { + Assert.IsType(img.CurrentImageFormat); + } + } + + [Fact] + public void Save_UnknownExtensionsEncoding() + { + string file = TestFile.GetPath("../../TestOutput/Save_DetecedEncoding.tmp"); + var ex = Assert.Throws( + () => + { + using (Image image = new Image(10, 10)) + { + image.Save(file); + } + }); + } + + [Fact] + public void Save_SetFormat() + { + string file = TestFile.GetPath("../../TestOutput/Save_SetFormat.dat"); + var dir = System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(file)); + using (Image image = new Image(10, 10)) + { + image.Save(file, new PngFormat()); + } + + var c = TestFile.Create("../../TestOutput/Save_SetFormat.dat"); + using (var img = c.CreateImage()) + { + Assert.IsType(img.CurrentImageFormat); + } + } + + [Fact] + public void Save_SetEncoding() + { + string file = TestFile.GetPath("../../TestOutput/Save_SetEncoding.dat"); + var dir = System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(file)); + using (Image image = new Image(10, 10)) + { + image.Save(file, new PngEncoder()); + } + + var c = TestFile.Create("../../TestOutput/Save_SetEncoding.dat"); + using (var img = c.CreateImage()) + { + Assert.IsType(img.CurrentImageFormat); + } + } } } From d8a75dc6a2ddb031875990aa91b71c1679c21800 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 13 Feb 2017 15:11:12 +1100 Subject: [PATCH 051/142] Fix PackVector method in Color and Argb --- src/ImageSharp/Colors/Color.cs | 2 +- src/ImageSharp/Colors/PackedPixel/Argb.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Colors/Color.cs b/src/ImageSharp/Colors/Color.cs index 8d6ce954c2..b2f9437ca7 100644 --- a/src/ImageSharp/Colors/Color.cs +++ b/src/ImageSharp/Colors/Color.cs @@ -372,9 +372,9 @@ namespace ImageSharp [MethodImpl(MethodImplOptions.AggressiveInlining)] private static uint Pack(ref Vector4 vector) { - vector = Vector4.Clamp(vector, Vector4.Zero, Vector4.One); vector *= MaxBytes; vector += Half; + vector = Vector4.Clamp(vector, Vector4.Zero, MaxBytes); return (uint)(((byte)vector.X << RedShift) | ((byte)vector.Y << GreenShift) | ((byte)vector.Z << BlueShift) diff --git a/src/ImageSharp/Colors/PackedPixel/Argb.cs b/src/ImageSharp/Colors/PackedPixel/Argb.cs index 432011702d..783545c680 100644 --- a/src/ImageSharp/Colors/PackedPixel/Argb.cs +++ b/src/ImageSharp/Colors/PackedPixel/Argb.cs @@ -342,9 +342,9 @@ namespace ImageSharp [MethodImpl(MethodImplOptions.AggressiveInlining)] private static uint Pack(ref Vector4 vector) { - vector = Vector4.Clamp(vector, Vector4.Zero, Vector4.One); vector *= MaxBytes; vector += Half; + vector = Vector4.Clamp(vector, Vector4.Zero, MaxBytes); return (uint)(((byte)vector.X << RedShift) | ((byte)vector.Y << GreenShift) | ((byte)vector.Z << BlueShift) From 12654b8b4857d31e3b3650c8c9223a3a3c9f5b81 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 13 Feb 2017 15:15:11 +1100 Subject: [PATCH 052/142] Add dithering to quantizers. FIx #15 --- src/ImageSharp/Dithering/Atkinson.cs | 32 ++++++ src/ImageSharp/Dithering/Burks.cs | 31 +++++ src/ImageSharp/Dithering/ErrorDiffusion.cs | 107 ++++++++++++++++++ src/ImageSharp/Dithering/FloydSteinberg.cs | 31 +++++ src/ImageSharp/Dithering/IErrorDiffusion.cs | 34 ++++++ src/ImageSharp/Dithering/JarvisJudiceNinke.cs | 32 ++++++ src/ImageSharp/Dithering/Sierra2.cs | 31 +++++ src/ImageSharp/Dithering/Sierra3.cs | 32 ++++++ src/ImageSharp/Dithering/SierraLite.cs | 31 +++++ src/ImageSharp/Dithering/Stucki.cs | 32 ++++++ src/ImageSharp/Quantizers/IQuantizer.cs | 20 ++++ src/ImageSharp/Quantizers/Octree/Quantizer.cs | 79 ++++++++++++- .../Quantizers/Palette/PaletteQuantizer.cs | 2 +- 13 files changed, 487 insertions(+), 7 deletions(-) create mode 100644 src/ImageSharp/Dithering/Atkinson.cs create mode 100644 src/ImageSharp/Dithering/Burks.cs create mode 100644 src/ImageSharp/Dithering/ErrorDiffusion.cs create mode 100644 src/ImageSharp/Dithering/FloydSteinberg.cs create mode 100644 src/ImageSharp/Dithering/IErrorDiffusion.cs create mode 100644 src/ImageSharp/Dithering/JarvisJudiceNinke.cs create mode 100644 src/ImageSharp/Dithering/Sierra2.cs create mode 100644 src/ImageSharp/Dithering/Sierra3.cs create mode 100644 src/ImageSharp/Dithering/SierraLite.cs create mode 100644 src/ImageSharp/Dithering/Stucki.cs diff --git a/src/ImageSharp/Dithering/Atkinson.cs b/src/ImageSharp/Dithering/Atkinson.cs new file mode 100644 index 0000000000..6d1580171c --- /dev/null +++ b/src/ImageSharp/Dithering/Atkinson.cs @@ -0,0 +1,32 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Dithering +{ + /// + /// Applies error diffusion based dithering using the Atkinson image dithering algorithm. + /// + /// + public class Atkinson : ErrorDiffusion + { + /// + /// The diffusion matrix + /// + private static readonly byte[,] AtkinsonMatrix = + { + { 0, 0, 1, 1 }, + { 1, 1, 1, 0 }, + { 0, 1, 0, 0 } + }; + + /// + /// Initializes a new instance of the class. + /// + public Atkinson() + : base(AtkinsonMatrix, 8) + { + } + } +} diff --git a/src/ImageSharp/Dithering/Burks.cs b/src/ImageSharp/Dithering/Burks.cs new file mode 100644 index 0000000000..3e2d19e57e --- /dev/null +++ b/src/ImageSharp/Dithering/Burks.cs @@ -0,0 +1,31 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Dithering +{ + /// + /// Applies error diffusion based dithering using the Burks image dithering algorithm. + /// + /// + public class Burks : ErrorDiffusion + { + /// + /// The diffusion matrix + /// + private static readonly byte[,] BurksMatrix = + { + { 0, 0, 0, 8, 4 }, + { 2, 4, 8, 4, 2 } + }; + + /// + /// Initializes a new instance of the class. + /// + public Burks() + : base(BurksMatrix, 32) + { + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Dithering/ErrorDiffusion.cs b/src/ImageSharp/Dithering/ErrorDiffusion.cs new file mode 100644 index 0000000000..481e8b4a69 --- /dev/null +++ b/src/ImageSharp/Dithering/ErrorDiffusion.cs @@ -0,0 +1,107 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Dithering +{ + using System; + using System.Numerics; + using System.Runtime.CompilerServices; + + /// + /// The base class for performing effor diffusion based dithering. + /// + public abstract class ErrorDiffusion : IErrorDiffusion + { + /// + /// The vector to perform division. + /// + private readonly Vector4 divisorVector; + + /// + /// The matrix width + /// + private readonly byte matrixHeight; + + /// + /// The matrix height + /// + private readonly byte matrixWidth; + + /// + /// The offset at which to start the dithering operation. + /// + private readonly int startingOffset; + + /// + /// Initializes a new instance of the class. + /// + /// The dithering matrix. + /// The divisor. + protected ErrorDiffusion(byte[,] matrix, byte divisor) + { + Guard.NotNull(matrix, nameof(matrix)); + Guard.MustBeGreaterThan(divisor, 0, nameof(divisor)); + + this.Matrix = matrix; + this.matrixWidth = (byte)(matrix.GetUpperBound(1) + 1); + this.matrixHeight = (byte)(matrix.GetUpperBound(0) + 1); + this.divisorVector = new Vector4(divisor); + + this.startingOffset = 0; + for (int i = 0; i < this.matrixWidth; i++) + { + if (matrix[0, i] != 0) + { + this.startingOffset = (byte)(i - 1); + break; + } + } + } + + /// + public byte[,] Matrix { get; } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Dither(PixelAccessor pixels, TColor source, TColor transformed, int x, int y, int width, int height) + where TColor : struct, IPackedPixel, IEquatable + { + // Assign the transformed pixel to the array. + pixels[x, y] = transformed; + + // Calculate the error + Vector4 error = source.ToVector4() - transformed.ToVector4(); + + // Loop through and distribute the error amongst neighbouring pixels. + for (int row = 0; row < this.matrixHeight; row++) + { + int matrixY = y + row; + + for (int col = 0; col < this.matrixWidth; col++) + { + int matrixX = x + (col - this.startingOffset); + + if (matrixX > 0 && matrixX < width && matrixY > 0 && matrixY < height) + { + byte coefficient = this.Matrix[row, col]; + if (coefficient == 0) + { + continue; + } + + Vector4 coefficientVector = new Vector4(this.Matrix[row, col]); + Vector4 offsetColor = pixels[matrixX, matrixY].ToVector4(); + Vector4 result = ((error * coefficientVector) / this.divisorVector) + offsetColor; + result.W = offsetColor.W; + + TColor packed = default(TColor); + packed.PackFromVector4(result); + pixels[matrixX, matrixY] = packed; + } + } + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Dithering/FloydSteinberg.cs b/src/ImageSharp/Dithering/FloydSteinberg.cs new file mode 100644 index 0000000000..a87421b959 --- /dev/null +++ b/src/ImageSharp/Dithering/FloydSteinberg.cs @@ -0,0 +1,31 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Dithering +{ + /// + /// Applies error diffusion based dithering using the Floyd–Steinberg image dithering algorithm. + /// + /// + public class FloydSteinberg : ErrorDiffusion + { + /// + /// The diffusion matrix + /// + private static readonly byte[,] FloydSteinbergMatrix = + { + { 0, 0, 7 }, + { 3, 5, 1 } + }; + + /// + /// Initializes a new instance of the class. + /// + public FloydSteinberg() + : base(FloydSteinbergMatrix, 16) + { + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Dithering/IErrorDiffusion.cs b/src/ImageSharp/Dithering/IErrorDiffusion.cs new file mode 100644 index 0000000000..cd38cd1cd3 --- /dev/null +++ b/src/ImageSharp/Dithering/IErrorDiffusion.cs @@ -0,0 +1,34 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Dithering +{ + using System; + + /// + /// Encapsulates properties and methods required to perfom diffused error dithering on an image. + /// + public interface IErrorDiffusion + { + /// + /// Gets the dithering matrix + /// + byte[,] Matrix { get; } + + /// + /// 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. + /// The pixel format. + void Dither(PixelAccessor pixels, TColor source, TColor transformed, int x, int y, int width, int height) + where TColor : struct, IPackedPixel, IEquatable; + } +} diff --git a/src/ImageSharp/Dithering/JarvisJudiceNinke.cs b/src/ImageSharp/Dithering/JarvisJudiceNinke.cs new file mode 100644 index 0000000000..a495f60014 --- /dev/null +++ b/src/ImageSharp/Dithering/JarvisJudiceNinke.cs @@ -0,0 +1,32 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Dithering +{ + /// + /// Applies error diffusion based dithering using the JarvisJudiceNinke image dithering algorithm. + /// + /// + public class JarvisJudiceNinke : ErrorDiffusion + { + /// + /// The diffusion matrix + /// + private static readonly byte[,] JarvisJudiceNinkeMatrix = + { + { 0, 0, 0, 7, 5 }, + { 3, 5, 7, 5, 3 }, + { 1, 3, 5, 3, 1 } + }; + + /// + /// Initializes a new instance of the class. + /// + public JarvisJudiceNinke() + : base(JarvisJudiceNinkeMatrix, 48) + { + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Dithering/Sierra2.cs b/src/ImageSharp/Dithering/Sierra2.cs new file mode 100644 index 0000000000..a2a6db36d6 --- /dev/null +++ b/src/ImageSharp/Dithering/Sierra2.cs @@ -0,0 +1,31 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Dithering +{ + /// + /// Applies error diffusion based dithering using the Sierra2 image dithering algorithm. + /// + /// + public class Sierra2 : ErrorDiffusion + { + /// + /// The diffusion matrix + /// + private static readonly byte[,] Sierra2Matrix = + { + { 0, 0, 0, 4, 3 }, + { 1, 2, 3, 2, 1 } + }; + + /// + /// Initializes a new instance of the class. + /// + public Sierra2() + : base(Sierra2Matrix, 16) + { + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Dithering/Sierra3.cs b/src/ImageSharp/Dithering/Sierra3.cs new file mode 100644 index 0000000000..8ab9279f38 --- /dev/null +++ b/src/ImageSharp/Dithering/Sierra3.cs @@ -0,0 +1,32 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Dithering +{ + /// + /// Applies error diffusion based dithering using the Sierra3 image dithering algorithm. + /// + /// + public class Sierra3 : ErrorDiffusion + { + /// + /// The diffusion matrix + /// + private static readonly byte[,] Sierra3Matrix = + { + { 0, 0, 0, 5, 3 }, + { 2, 4, 5, 4, 2 }, + { 0, 2, 3, 2, 0 } + }; + + /// + /// Initializes a new instance of the class. + /// + public Sierra3() + : base(Sierra3Matrix, 32) + { + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Dithering/SierraLite.cs b/src/ImageSharp/Dithering/SierraLite.cs new file mode 100644 index 0000000000..217b6ac5f8 --- /dev/null +++ b/src/ImageSharp/Dithering/SierraLite.cs @@ -0,0 +1,31 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Dithering +{ + /// + /// Applies error diffusion based dithering using the SierraLite image dithering algorithm. + /// + /// + public class SierraLite : ErrorDiffusion + { + /// + /// The diffusion matrix + /// + private static readonly byte[,] SierraLiteMatrix = + { + { 0, 0, 2 }, + { 1, 1, 0 } + }; + + /// + /// Initializes a new instance of the class. + /// + public SierraLite() + : base(SierraLiteMatrix, 4) + { + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Dithering/Stucki.cs b/src/ImageSharp/Dithering/Stucki.cs new file mode 100644 index 0000000000..0b9b40f735 --- /dev/null +++ b/src/ImageSharp/Dithering/Stucki.cs @@ -0,0 +1,32 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Dithering +{ + /// + /// Applies error diffusion based dithering using the Stucki image dithering algorithm. + /// + /// + public class Stucki : ErrorDiffusion + { + /// + /// The diffusion matrix + /// + private static readonly byte[,] StuckiMatrix = + { + { 0, 0, 0, 8, 4 }, + { 2, 4, 8, 4, 2 }, + { 1, 2, 4, 2, 1 } + }; + + /// + /// Initializes a new instance of the class. + /// + public Stucki() + : base(StuckiMatrix, 4) + { + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Quantizers/IQuantizer.cs b/src/ImageSharp/Quantizers/IQuantizer.cs index 878e9775b4..a027ca94cc 100644 --- a/src/ImageSharp/Quantizers/IQuantizer.cs +++ b/src/ImageSharp/Quantizers/IQuantizer.cs @@ -7,6 +7,8 @@ namespace ImageSharp.Quantizers { using System; + using ImageSharp.Dithering; + /// /// Provides methods for allowing quantization of images pixels. /// @@ -25,6 +27,24 @@ namespace ImageSharp.Quantizers QuantizedImage Quantize(ImageBase image, int maxColors); } + /// + /// Provides methods for allowing dithering of quantized image pixels. + /// + /// The pixel format. + public interface IDitheredQuantizer : IQuantizer + where TColor : struct, IPackedPixel, IEquatable + { + /// + /// Gets or sets a value indicating whether to apply dithering to the output image. + /// + bool Dither { get; set; } + + /// + /// Gets or sets the dithering algorithm to apply to the output image. + /// + IErrorDiffusion DitherType { get; set; } + } + /// /// Provides methods for allowing quantization of images pixels. /// diff --git a/src/ImageSharp/Quantizers/Octree/Quantizer.cs b/src/ImageSharp/Quantizers/Octree/Quantizer.cs index 74aa6aadee..65a9d1ede6 100644 --- a/src/ImageSharp/Quantizers/Octree/Quantizer.cs +++ b/src/ImageSharp/Quantizers/Octree/Quantizer.cs @@ -6,12 +6,16 @@ namespace ImageSharp.Quantizers { using System; + using System.Numerics; + using System.Runtime.CompilerServices; + + using ImageSharp.Dithering; /// /// Encapsulates methods to calculate the color palette of an image. /// /// The pixel format. - public abstract class Quantizer : IQuantizer + public abstract class Quantizer : IDitheredQuantizer where TColor : struct, IPackedPixel, IEquatable { /// @@ -19,6 +23,11 @@ namespace ImageSharp.Quantizers /// private readonly bool singlePass; + /// + /// The reduced image palette + /// + private TColor[] palette; + /// /// Initializes a new instance of the class. /// @@ -35,6 +44,12 @@ namespace ImageSharp.Quantizers this.singlePass = singlePass; } + /// + public bool Dither { get; set; } = true; + + /// + public IErrorDiffusion DitherType { get; set; } = new SierraLite(); + /// public virtual QuantizedImage Quantize(ImageBase image, int maxColors) { @@ -44,7 +59,6 @@ namespace ImageSharp.Quantizers int height = image.Height; int width = image.Width; byte[] quantizedPixels = new byte[width * height]; - TColor[] palette; using (PixelAccessor pixels = image.Lock()) { @@ -57,12 +71,24 @@ namespace ImageSharp.Quantizers } // Get the palette - palette = this.GetPalette(); + this.palette = this.GetPalette(); - this.SecondPass(pixels, quantizedPixels, width, height); + 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); + } } - return new QuantizedImage(width, height, palette, quantizedPixels); + return new QuantizedImage(width, height, this.palette, quantizedPixels); } /// @@ -99,6 +125,14 @@ namespace ImageSharp.Quantizers // And loop through each column for (int x = 0; x < width; x++) { + if (this.Dither) + { + // Apply the dithering matrix + TColor sourcePixel = source[x, y]; + TColor transformedPixel = this.palette[GetClosestColor(sourcePixel, this.palette)]; + this.DitherType.Dither(source, sourcePixel, transformedPixel, x, y, width, height); + } + output[(y * source.Width) + x] = this.QuantizePixel(source[x, y]); } } @@ -129,8 +163,41 @@ namespace ImageSharp.Quantizers /// Retrieve the palette for the quantized image /// /// - /// The new color palette + /// /// protected abstract TColor[] GetPalette(); + + /// + /// Returns the closest color from the palette to the given color by calculating the Euclidean distance. + /// + /// The color. + /// The color palette. + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static byte GetClosestColor(TColor pixel, TColor[] palette) + { + float leastDistance = int.MaxValue; + Vector4 vector = pixel.ToVector4(); + + byte colorIndex = 0; + for (int index = 0; index < palette.Length; index++) + { + float distance = Vector4.Distance(vector, palette[index].ToVector4()); + + if (distance < leastDistance) + { + colorIndex = (byte)index; + leastDistance = distance; + + // And if it's an exact match, exit the loop + if (Math.Abs(distance) < Constants.Epsilon) + { + break; + } + } + } + + return colorIndex; + } } } \ No newline at end of file diff --git a/src/ImageSharp/Quantizers/Palette/PaletteQuantizer.cs b/src/ImageSharp/Quantizers/Palette/PaletteQuantizer.cs index 6edb7801bf..abf1e5dc5c 100644 --- a/src/ImageSharp/Quantizers/Palette/PaletteQuantizer.cs +++ b/src/ImageSharp/Quantizers/Palette/PaletteQuantizer.cs @@ -97,7 +97,7 @@ namespace ImageSharp.Quantizers leastDistance = distance; // And if it's an exact match, exit the loop - if (Math.Abs(distance) < .0001F) + if (Math.Abs(distance) < Constants.Epsilon) { break; } From 0003b9a79d862395e9232fd22f2623426c8e1797 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 13 Feb 2017 17:00:48 +1100 Subject: [PATCH 053/142] Rename base interface, fix Stucki filter --- src/ImageSharp/Dithering/Atkinson.cs | 2 +- src/ImageSharp/Dithering/Burks.cs | 2 +- .../Dithering/{ErrorDiffusion.cs => ErrorDiffuser.cs} | 8 ++++---- src/ImageSharp/Dithering/FloydSteinberg.cs | 2 +- .../Dithering/{IErrorDiffusion.cs => IErrorDiffuser.cs} | 4 ++-- src/ImageSharp/Dithering/JarvisJudiceNinke.cs | 2 +- src/ImageSharp/Dithering/Sierra2.cs | 2 +- src/ImageSharp/Dithering/Sierra3.cs | 2 +- src/ImageSharp/Dithering/SierraLite.cs | 2 +- src/ImageSharp/Dithering/Stucki.cs | 4 ++-- src/ImageSharp/Quantizers/IQuantizer.cs | 2 +- src/ImageSharp/Quantizers/Octree/Quantizer.cs | 2 +- 12 files changed, 17 insertions(+), 17 deletions(-) rename src/ImageSharp/Dithering/{ErrorDiffusion.cs => ErrorDiffuser.cs} (94%) rename src/ImageSharp/Dithering/{IErrorDiffusion.cs => IErrorDiffuser.cs} (91%) diff --git a/src/ImageSharp/Dithering/Atkinson.cs b/src/ImageSharp/Dithering/Atkinson.cs index 6d1580171c..934df7e4a8 100644 --- a/src/ImageSharp/Dithering/Atkinson.cs +++ b/src/ImageSharp/Dithering/Atkinson.cs @@ -9,7 +9,7 @@ namespace ImageSharp.Dithering /// Applies error diffusion based dithering using the Atkinson image dithering algorithm. /// /// - public class Atkinson : ErrorDiffusion + public sealed class Atkinson : ErrorDiffuser { /// /// The diffusion matrix diff --git a/src/ImageSharp/Dithering/Burks.cs b/src/ImageSharp/Dithering/Burks.cs index 3e2d19e57e..311316685b 100644 --- a/src/ImageSharp/Dithering/Burks.cs +++ b/src/ImageSharp/Dithering/Burks.cs @@ -9,7 +9,7 @@ namespace ImageSharp.Dithering /// Applies error diffusion based dithering using the Burks image dithering algorithm. /// /// - public class Burks : ErrorDiffusion + public sealed class Burks : ErrorDiffuser { /// /// The diffusion matrix diff --git a/src/ImageSharp/Dithering/ErrorDiffusion.cs b/src/ImageSharp/Dithering/ErrorDiffuser.cs similarity index 94% rename from src/ImageSharp/Dithering/ErrorDiffusion.cs rename to src/ImageSharp/Dithering/ErrorDiffuser.cs index 481e8b4a69..1de6cd8149 100644 --- a/src/ImageSharp/Dithering/ErrorDiffusion.cs +++ b/src/ImageSharp/Dithering/ErrorDiffuser.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // @@ -12,7 +12,7 @@ namespace ImageSharp.Dithering /// /// The base class for performing effor diffusion based dithering. /// - public abstract class ErrorDiffusion : IErrorDiffusion + public abstract class ErrorDiffuser : IErrorDiffuser { /// /// The vector to perform division. @@ -35,11 +35,11 @@ namespace ImageSharp.Dithering private readonly int startingOffset; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The dithering matrix. /// The divisor. - protected ErrorDiffusion(byte[,] matrix, byte divisor) + protected ErrorDiffuser(byte[,] matrix, byte divisor) { Guard.NotNull(matrix, nameof(matrix)); Guard.MustBeGreaterThan(divisor, 0, nameof(divisor)); diff --git a/src/ImageSharp/Dithering/FloydSteinberg.cs b/src/ImageSharp/Dithering/FloydSteinberg.cs index a87421b959..a392c9dc9c 100644 --- a/src/ImageSharp/Dithering/FloydSteinberg.cs +++ b/src/ImageSharp/Dithering/FloydSteinberg.cs @@ -9,7 +9,7 @@ namespace ImageSharp.Dithering /// Applies error diffusion based dithering using the Floyd–Steinberg image dithering algorithm. /// /// - public class FloydSteinberg : ErrorDiffusion + public sealed class FloydSteinberg : ErrorDiffuser { /// /// The diffusion matrix diff --git a/src/ImageSharp/Dithering/IErrorDiffusion.cs b/src/ImageSharp/Dithering/IErrorDiffuser.cs similarity index 91% rename from src/ImageSharp/Dithering/IErrorDiffusion.cs rename to src/ImageSharp/Dithering/IErrorDiffuser.cs index cd38cd1cd3..22cbad1e6e 100644 --- a/src/ImageSharp/Dithering/IErrorDiffusion.cs +++ b/src/ImageSharp/Dithering/IErrorDiffuser.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // @@ -10,7 +10,7 @@ namespace ImageSharp.Dithering /// /// Encapsulates properties and methods required to perfom diffused error dithering on an image. /// - public interface IErrorDiffusion + public interface IErrorDiffuser { /// /// Gets the dithering matrix diff --git a/src/ImageSharp/Dithering/JarvisJudiceNinke.cs b/src/ImageSharp/Dithering/JarvisJudiceNinke.cs index a495f60014..b5876d7773 100644 --- a/src/ImageSharp/Dithering/JarvisJudiceNinke.cs +++ b/src/ImageSharp/Dithering/JarvisJudiceNinke.cs @@ -9,7 +9,7 @@ namespace ImageSharp.Dithering /// Applies error diffusion based dithering using the JarvisJudiceNinke image dithering algorithm. /// /// - public class JarvisJudiceNinke : ErrorDiffusion + public sealed class JarvisJudiceNinke : ErrorDiffuser { /// /// The diffusion matrix diff --git a/src/ImageSharp/Dithering/Sierra2.cs b/src/ImageSharp/Dithering/Sierra2.cs index a2a6db36d6..d7cc84254e 100644 --- a/src/ImageSharp/Dithering/Sierra2.cs +++ b/src/ImageSharp/Dithering/Sierra2.cs @@ -9,7 +9,7 @@ namespace ImageSharp.Dithering /// Applies error diffusion based dithering using the Sierra2 image dithering algorithm. /// /// - public class Sierra2 : ErrorDiffusion + public sealed class Sierra2 : ErrorDiffuser { /// /// The diffusion matrix diff --git a/src/ImageSharp/Dithering/Sierra3.cs b/src/ImageSharp/Dithering/Sierra3.cs index 8ab9279f38..c3d1fe7565 100644 --- a/src/ImageSharp/Dithering/Sierra3.cs +++ b/src/ImageSharp/Dithering/Sierra3.cs @@ -9,7 +9,7 @@ namespace ImageSharp.Dithering /// Applies error diffusion based dithering using the Sierra3 image dithering algorithm. /// /// - public class Sierra3 : ErrorDiffusion + public sealed class Sierra3 : ErrorDiffuser { /// /// The diffusion matrix diff --git a/src/ImageSharp/Dithering/SierraLite.cs b/src/ImageSharp/Dithering/SierraLite.cs index 217b6ac5f8..7d855b84e3 100644 --- a/src/ImageSharp/Dithering/SierraLite.cs +++ b/src/ImageSharp/Dithering/SierraLite.cs @@ -9,7 +9,7 @@ namespace ImageSharp.Dithering /// Applies error diffusion based dithering using the SierraLite image dithering algorithm. /// /// - public class SierraLite : ErrorDiffusion + public sealed class SierraLite : ErrorDiffuser { /// /// The diffusion matrix diff --git a/src/ImageSharp/Dithering/Stucki.cs b/src/ImageSharp/Dithering/Stucki.cs index 0b9b40f735..3cc01aa798 100644 --- a/src/ImageSharp/Dithering/Stucki.cs +++ b/src/ImageSharp/Dithering/Stucki.cs @@ -9,7 +9,7 @@ namespace ImageSharp.Dithering /// Applies error diffusion based dithering using the Stucki image dithering algorithm. /// /// - public class Stucki : ErrorDiffusion + public sealed class Stucki : ErrorDiffuser { /// /// The diffusion matrix @@ -25,7 +25,7 @@ namespace ImageSharp.Dithering /// Initializes a new instance of the class. /// public Stucki() - : base(StuckiMatrix, 4) + : base(StuckiMatrix, 42) { } } diff --git a/src/ImageSharp/Quantizers/IQuantizer.cs b/src/ImageSharp/Quantizers/IQuantizer.cs index a027ca94cc..ef2f0bb98e 100644 --- a/src/ImageSharp/Quantizers/IQuantizer.cs +++ b/src/ImageSharp/Quantizers/IQuantizer.cs @@ -42,7 +42,7 @@ namespace ImageSharp.Quantizers /// /// Gets or sets the dithering algorithm to apply to the output image. /// - IErrorDiffusion DitherType { get; set; } + IErrorDiffuser DitherType { get; set; } } /// diff --git a/src/ImageSharp/Quantizers/Octree/Quantizer.cs b/src/ImageSharp/Quantizers/Octree/Quantizer.cs index 65a9d1ede6..4d16c5df36 100644 --- a/src/ImageSharp/Quantizers/Octree/Quantizer.cs +++ b/src/ImageSharp/Quantizers/Octree/Quantizer.cs @@ -48,7 +48,7 @@ namespace ImageSharp.Quantizers public bool Dither { get; set; } = true; /// - public IErrorDiffusion DitherType { get; set; } = new SierraLite(); + public IErrorDiffuser DitherType { get; set; } = new SierraLite(); /// public virtual QuantizedImage Quantize(ImageBase image, int maxColors) From 3e164d34b41777ee717e4a8e24dd03252e85b0f8 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 13 Feb 2017 17:02:20 +1100 Subject: [PATCH 054/142] Add error diffusion binerization --- .../Binarization/Dither.cs | 50 ++++++++ .../Binarization/BinaryThresholdProcessor.cs | 16 ++- .../ErrorDiffusionDitherProcessor.cs | 111 ++++++++++++++++++ .../Formats/GeneralFormatTests.cs | 54 +++++---- .../Processors/Filters/DitherTest.cs | 47 ++++++++ 5 files changed, 244 insertions(+), 34 deletions(-) create mode 100644 src/ImageSharp.Processing/Binarization/Dither.cs create mode 100644 src/ImageSharp.Processing/Processors/Binarization/ErrorDiffusionDitherProcessor.cs create mode 100644 tests/ImageSharp.Tests/Processors/Filters/DitherTest.cs diff --git a/src/ImageSharp.Processing/Binarization/Dither.cs b/src/ImageSharp.Processing/Binarization/Dither.cs new file mode 100644 index 0000000000..f481ac4dfb --- /dev/null +++ b/src/ImageSharp.Processing/Binarization/Dither.cs @@ -0,0 +1,50 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + + using ImageSharp.Dithering; + using ImageSharp.Processing.Processors; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Alters the alpha component of the image. + /// + /// The pixel format. + /// The image this method extends. + /// The diffusion algorithm to apply. + /// The threshold to apply binarization of the image. Must be between 0 and 1. + /// The . + public static Image Dither(this Image source, IErrorDiffuser diffuser, float threshold) + where TColor : struct, IPackedPixel, IEquatable + { + return Dither(source, diffuser, threshold, source.Bounds); + } + + /// + /// Alters the alpha component of the image. + /// + /// The pixel format. + /// The image this method extends. + /// The diffusion algorithm to apply. + /// The threshold to apply binarization of the image. Must be between 0 and 1. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static Image Dither(this Image source, IErrorDiffuser diffuser, float threshold, Rectangle rectangle) + where TColor : struct, IPackedPixel, IEquatable + { + source.ApplyProcessor(new ErrorDiffusionDitherProcessor(diffuser, threshold), rectangle); + return source; + } + } +} diff --git a/src/ImageSharp.Processing/Processors/Binarization/BinaryThresholdProcessor.cs b/src/ImageSharp.Processing/Processors/Binarization/BinaryThresholdProcessor.cs index 2eb5225f82..cb37587480 100644 --- a/src/ImageSharp.Processing/Processors/Binarization/BinaryThresholdProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Binarization/BinaryThresholdProcessor.cs @@ -20,28 +20,26 @@ namespace ImageSharp.Processing.Processors /// Initializes a new instance of the class. /// /// The threshold to split the image. Must be between 0 and 1. - /// - /// is less than 0 or is greater than 1. - /// public BinaryThresholdProcessor(float threshold) { - // TODO: Check limit. + // TODO: Check thresholding limit. Colors should probably have Max/Min/Middle properties. Guard.MustBeBetweenOrEqualTo(threshold, 0, 1, nameof(threshold)); - this.Value = threshold; + this.Threshold = threshold; + // Default to white/black for upper/lower. TColor upper = default(TColor); - upper.PackFromVector4(Color.White.ToVector4()); + upper.PackFromBytes(255, 255, 255, 255); this.UpperColor = upper; TColor lower = default(TColor); - lower.PackFromVector4(Color.Black.ToVector4()); + lower.PackFromBytes(0, 0, 0, 255); this.LowerColor = lower; } /// /// Gets the threshold value. /// - public float Value { get; } + public float Threshold { get; } /// /// Gets or sets the color to use for pixels that are above the threshold. @@ -62,7 +60,7 @@ namespace ImageSharp.Processing.Processors /// protected override void OnApply(ImageBase source, Rectangle sourceRectangle) { - float threshold = this.Value; + float threshold = this.Threshold; TColor upper = this.UpperColor; TColor lower = this.LowerColor; diff --git a/src/ImageSharp.Processing/Processors/Binarization/ErrorDiffusionDitherProcessor.cs b/src/ImageSharp.Processing/Processors/Binarization/ErrorDiffusionDitherProcessor.cs new file mode 100644 index 0000000000..c5b78b6390 --- /dev/null +++ b/src/ImageSharp.Processing/Processors/Binarization/ErrorDiffusionDitherProcessor.cs @@ -0,0 +1,111 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Processing.Processors +{ + using System; + + using ImageSharp.Dithering; + + /// + /// An that dithers an image using error diffusion. + /// + /// The pixel format. + public class ErrorDiffusionDitherProcessor : ImageProcessor + where TColor : struct, IPackedPixel, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// The error diffuser + /// The threshold to split the image. Must be between 0 and 1. + public ErrorDiffusionDitherProcessor(IErrorDiffuser diffuser, float threshold) + { + Guard.NotNull(diffuser, nameof(diffuser)); + + // TODO: Check thresholding limit. Colors should probably have Max/Min/Middle properties. + Guard.MustBeBetweenOrEqualTo(threshold, 0, 1, nameof(threshold)); + + this.Diffuser = diffuser; + this.Threshold = threshold; + + // Default to white/black for upper/lower. + TColor upper = default(TColor); + upper.PackFromBytes(255, 255, 255, 255); + this.UpperColor = upper; + + TColor lower = default(TColor); + lower.PackFromBytes(0, 0, 0, 255); + this.LowerColor = lower; + } + + /// + /// Gets the error diffuser. + /// + public IErrorDiffuser Diffuser { get; } + + /// + /// Gets the threshold value. + /// + public float Threshold { get; } + + /// + /// Gets or sets the color to use for pixels that are above the threshold. + /// + public TColor UpperColor { get; set; } + + /// + /// Gets or sets the color to use for pixels that fall below the threshold. + /// + public TColor LowerColor { get; set; } + + /// + protected override void BeforeApply(ImageBase source, Rectangle sourceRectangle) + { + new GrayscaleBt709Processor().Apply(source, sourceRectangle); + } + + /// + protected override void OnApply(ImageBase source, Rectangle sourceRectangle) + { + int startY = sourceRectangle.Y; + int endY = sourceRectangle.Bottom; + int startX = sourceRectangle.X; + int endX = sourceRectangle.Right; + + // Align start/end positions. + int minX = Math.Max(0, startX); + int maxX = Math.Min(source.Width, endX); + int minY = Math.Max(0, startY); + int maxY = Math.Min(source.Height, endY); + + // Reset offset if necessary. + if (minX > 0) + { + startX = 0; + } + + if (minY > 0) + { + startY = 0; + } + + using (PixelAccessor sourcePixels = source.Lock()) + { + for (int y = minY; y < maxY; y++) + { + int offsetY = y - startY; + for (int x = minX; x < maxX; x++) + { + int offsetX = x - startX; + TColor sourceColor = sourcePixels[offsetX, offsetY]; + TColor transformedColor = sourceColor.ToVector4().X >= this.Threshold ? this.UpperColor : this.LowerColor; + this.Diffuser.Dither(sourcePixels, sourceColor, transformedColor, offsetX, offsetY, maxX, maxY); + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs index 97bd34def8..7e47501f3f 100644 --- a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs +++ b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs @@ -111,26 +111,27 @@ namespace ImageSharp.Tests foreach (TestFile file in Files) { - Image image = file.CreateImage(); - - using (FileStream output = File.OpenWrite($"{path}/{file.FileNameWithoutExtension}.gif")) + using (Image image = file.CreateImage()) { - image.SaveAsGif(output); - } + using (FileStream output = File.OpenWrite($"{path}/{file.FileNameWithoutExtension}.bmp")) + { + image.SaveAsBmp(output); + } - using (FileStream output = File.OpenWrite($"{path}/{file.FileNameWithoutExtension}.bmp")) - { - image.SaveAsBmp(output); - } + using (FileStream output = File.OpenWrite($"{path}/{file.FileNameWithoutExtension}.jpg")) + { + image.SaveAsJpeg(output); + } - using (FileStream output = File.OpenWrite($"{path}/{file.FileNameWithoutExtension}.jpg")) - { - image.SaveAsJpeg(output); - } + using (FileStream output = File.OpenWrite($"{path}/{file.FileNameWithoutExtension}.png")) + { + image.SaveAsPng(output); + } - using (FileStream output = File.OpenWrite($"{path}/{file.FileNameWithoutExtension}.png")) - { - image.SaveAsPng(output); + using (FileStream output = File.OpenWrite($"{path}/{file.FileNameWithoutExtension}.gif")) + { + image.SaveAsGif(output); + } } } } @@ -142,22 +143,25 @@ namespace ImageSharp.Tests foreach (TestFile file in Files) { - Image image = file.CreateImage(); - byte[] serialized; - using (MemoryStream memoryStream = new MemoryStream()) + using (Image image = file.CreateImage()) { - image.Save(memoryStream); - memoryStream.Flush(); - serialized = memoryStream.ToArray(); + using (MemoryStream memoryStream = new MemoryStream()) + { + image.Save(memoryStream); + memoryStream.Flush(); + serialized = memoryStream.ToArray(); + } } using (MemoryStream memoryStream = new MemoryStream(serialized)) { - Image image2 = new Image(memoryStream); - using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) + using (Image image2 = new Image(memoryStream)) { - image2.Save(output); + using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) + { + image2.Save(output); + } } } } diff --git a/tests/ImageSharp.Tests/Processors/Filters/DitherTest.cs b/tests/ImageSharp.Tests/Processors/Filters/DitherTest.cs new file mode 100644 index 0000000000..3de2481e5d --- /dev/null +++ b/tests/ImageSharp.Tests/Processors/Filters/DitherTest.cs @@ -0,0 +1,47 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System.IO; + + using ImageSharp.Dithering; + + using Xunit; + + public class DitherTest : FileTestBase + { + [Fact] + public void ImageShouldApplyDitherFilter() + { + string path = this.CreateOutputDirectory("Dither"); + + foreach (TestFile file in Files) + { + using (Image image = file.CreateImage()) + using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) + { + image.Dither(new SierraLite(), .5F).Save(output); + } + } + } + + [Fact] + public void ImageShouldApplyDitherFilterInBox() + { + string path = this.CreateOutputDirectory("Dither"); + + foreach (TestFile file in Files) + { + string filename = file.GetFileName("-InBox"); + using (Image image = file.CreateImage()) + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + image.Dither(new SierraLite(), .5F, new Rectangle(10, 10, image.Width / 2, image.Height / 2)).Save(output); + } + } + } + } +} \ No newline at end of file From 39c23e524c1a0c688f88db4433638291301bb6bc Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 13 Feb 2017 17:05:54 +1100 Subject: [PATCH 055/142] Add information files --- src/ImageSharp/Dithering/DHALF.TXT | 1331 +++++++++++++++++++++++++++ src/ImageSharp/Dithering/DITHER.TXT | 547 +++++++++++ 2 files changed, 1878 insertions(+) create mode 100644 src/ImageSharp/Dithering/DHALF.TXT create mode 100644 src/ImageSharp/Dithering/DITHER.TXT diff --git a/src/ImageSharp/Dithering/DHALF.TXT b/src/ImageSharp/Dithering/DHALF.TXT new file mode 100644 index 0000000000..dce9ef9241 --- /dev/null +++ b/src/ImageSharp/Dithering/DHALF.TXT @@ -0,0 +1,1331 @@ +DHALF.TXT +June 20, 1991 + +Original name: DITHER.TXT +Original date: January 2, 1989 + + +===================================== +ORIGINAL FOREWORD BY LEE CROCKER + +What follows is everything you ever wanted to know (for the time being) +about digital halftoning, or dithering. I'm sure it will be out of date as +soon as it is released, but it does serve to collect data from a wide +variety of sources into a single document, and should save you considerable +searching time. + +Numbers in brackets (e.g. [4] or [12]) are references. A list of these +works appears at the end of this document. + +Because this document describes ideas and algorithms which are constantly +changing, I expect that it may have many editions, additions, and +corrections before it gets to you. I will list my name below as original +author, but I do not wish to deter others from adding their own thoughts and +discoveries. This is not copyrighted in any way, and was created solely +for the purpose of organizing my own knowledge on the subject, and sharing +this with others. Please distribute it to anyone who might be interested. + +If you add anything to this document, please feel free to include your name +below as a contributor or as a reference. I would particularly like to see +additions to the "Other books of interest" section. Please keep the text in +this simple format: no margins, no pagination, no lines longer than 79 +characters, and no non-ASCII or non-printing characters other than a CR/LF +pair at the end of each line. It is intended that this be read on as many +different machines as possible. + + +Original Author: + + Lee Daniel Crocker [73407,2030] + + +Contributors: + + Paul Boulay [72117,446] + + Mike Morra [76703,4051] + + +===================================== +COMMENTS BY MIKE MORRA + +I first entered the world of imaging in the fall of 1990 when my employer, +Epson America Inc., began shipping the ES-300C color flatbed scanner. +Suddenly, here I was, a field systems analyst who had worked almost +exclusively with printers and PCs, thrust into a new and arcane world of +look-up tables and dithering and color reduction and .GIF files! I realized +right away that I had a lot of catching up to do (and it needed to be done +quickly), so I began to frequent the CompuServe Information Service's +Graphics Support Forum on a very regular basis. + +Lee Crocker's excellent paper called DITHER.TXT was one of the first pieces +of information that I came across, and it went a very long way toward +answering a lot of questions that I'd had about the subject of dithering. +It also provided me with the names of other essential reference works upon +which Lee had based his paper, and I immediately began an eager search for +these other references. + +In the course of my self-study, however, I found that DITHER.TXT does +presume the reader's familiarity with some fundamental imaging concepts, +which meant that I needed to do a little "cramming." I get the impression +that Lee was directing his paper more toward graphics programmers than to +complete neophytes like me. I decided that I would rewrite and append to +DITHER.TXT and try to incorporate some of the more elementary information +that I'd absorbed along the way. In doing so, I hope that it will make it +even more comprehensive, and thus even more useful to first-time users. + +I elected to rename the revised file and chose the name DHALF.TXT in homage +to the term "digital halftoning," as used in Robert Ulichney's splendid +reference work. Notwithstanding, this paper is still very much Lee's +original work, and I certainly do not propose that I have created something +new and original here. It is also quite possible that in changing the +presentation of some of the material therein, I may have unwittingly +corrupted Lee's original intent and delivery, and this was also not my +intention. + +Accordingly, I've submitted this paper to the Graphics Support Forum as a +draft work only, at least for the time being. Quite honestly, I don't know +whether it would be appropriate as a replacement to DITHER.TXT, or as a +second, distinct document. Too, I may very well have misconstrued or +misinterpreted some factual information in my revision. As such, I welcome +criticism and comment from all the original authors and contributors, and +any readers, with the hope that their feedback will help me to address these +issues. + +If this revision it is received favorably, I will submit it to the public +domain; if it is met with brickbats (for whatever reason), I will withdraw +it. Whatever the outcome, though, it will at least represent a very +rewarding learning experience on my part! + +With the unselfish help of many of the denizens of the Graphics Support +Forum, I was ultimately able to thrash out (in my own mind) the answers to +my questions that I needed. I'd like to publicly thank the whole Forum +community in general for putting up with my unending barrage of questions +and inquiries over the past few months . In particular, I would thank +John Swenson, Chris Young, and (of course) Lee Crocker for their invaluable +assistance. + +Mike Morra [76703,4051] +June 20, 1991 + + +===================================== +What is Digital Halftoning? + +Throughout much of the course of computer imaging technology, experimenters +and users have been challenged with attempting to acceptably render +digitized images on display devices which were incapable of reproducing the +full spectrum of intensities or colors present in the source image. The +challenge is even more pronounced in today's world of personal computing +because of the technology gap between image generation and image rendering +equipment. + +Today, we now have affordable 24-bit image scanners which can generate +nearly true-to-life scans having as many as 256 shades of gray, or in excess +of 16.7 million colors. Mainstream display technology, however, still lags +behind with 16- and 256-color VGA/SVGA video monitors and printers with +binary (black/white) "marking engines" as the norm. Without specialized +techniques for color reduction -- the process of finding the "best fit" of +the display device's available gray shades and/or colors -- the imaging +experimenter would be plagued with blotchy, noisy, off-color images. + +(As of this writing, "true color" 24-bit video display devices, capable of +reproducing all of the color/intensity information in the source image, are +now beginning to migrate downward into the PC environment, but they exact a +premium in cost and processor power which many users are loathe to pay. So- +called "high-color" video displays -- typically 16-bit, with 32,768-color +capability -- are moving into the mainstream, but color reduction techniques +would still be required with these devices.) + +The science of digital halftoning (more commonly referred to as dithering, +or spatial dithering) is one of the techniques used to achieve satisfactory +image rendering and color reduction. Initially, it was principally +associated with the rendering of continuous-tone (grayscale) images on +"binary" (i.e. 1-bit) video displays which could only display full black or +full white pixels, or on printers which could produce only full black spots +on a printed page. Indeed, Ulichney [3] gives a definition of digital +halftoning as "... any algorithmic process which creates the illusion of +continuous-tone images from the judicious arrangement of binary picture +elements." + +Ulichney's study, as well as the earlier literature on the subject (and this +paper itself), discusses the process mostly in this context. Since we in +the PC world are still saddled primarily with black/white marking engines in +our hardcopy devices, this binary interpretation of digital halftoning is +still very pertinent. However, as we will see later in this discussion, the +concept can also be extended to include display devices (typically video +monitors) which support limited grayscale or color palettes. Accordingly, +we can broaden the traditional definition of digital halftoning to refer to +rendering an image on any display device which is unable to show the entire +range of colors or gray shades that are contained in the source image. + + +===================================== +Intensity/Color Resolution + +The concept of resolution is essential to the understanding of digital +halftoning. Resolution can be defined as "fineness" and is used to +describe the level of detail in a digitally sampled signal. + +Typically, when we hear the term "resolution" applied to images, we think of +what's known as "spatial resolution," which is the basic sampling rate for +the image. It describes the fineness of the "dots" (pixels or ink/toner +spots) which comprise the image, i.e. how many of them are present along +each horizontal and vertical inch. However, we can also speak of "intensity +resolution" or "color resolution," which describes the fineness of detail +available at each spot, i.e. the number of different gray shades or colors +in the image. (I will go back and forth between the two terms depending on +the type of image being discussed, but the reader should be aware that the +concepts are analogous to each other.) + +As you might expect, the higher the resolution of a digital sample, the +better it can reproduce high frequency detail in the particular domain +described by that resolution. A VGA display, for example, has a relatively +good spatial resolution of 640 x 480 and a relatively poor color resolution +of 8 bits (256 colors). By comparison, an NTSC color television receiver +has a spatial resolution of approximately 350 x 525 and an excellent, nearly +infinite color resolution. Thus, images rendered on a VGA screen will be +quite sharp, but rather blotchy in color. The same image displayed on the +television receiver will not be as crisp, but will have much more accurate +color rendition. + +It is often possible to "trade" one kind of resolution for another. If your +display device has a higher spatial resolution than the image you are trying +to reproduce, it can show a very good image even if its color resolution is +less. This is what most of us know as "dithering" and is the subject of +this paper. (The other tradeoff, i.e., trading color resolution for spatial +resolution, is called "anti-aliasing," and is not discussed here.) + + +For the following discussions I will assume that we are given a grayscale +image with 256 shades of gray, which are assigned intensity values from 0 +(black) through 255 (white), and that we are trying to reproduce it on a +black and white output device, e.g. something like an Epson impact dotmatrix +printer, or an HP LaserJet laser printer. Most of these methods can be +extended in obvious ways to deal with displays that have more than two +levels (but still fewer than the source image), or to color images. Where +such extension is not obvious, or where better results can be obtained, I +will go into more detail. + + +===================================== +Fixed Thresholding + +A good place to start is with the example of performing a simple (or fixed) +thresholding operation on our grayscale image in order to display it on our +black and white device. This is accomplished by establishing a demarcation +point, or threshold, at the 50% gray level. Each dot of the source image is +compared against this threshold value: if it is darker than the value, the +device plots it black, and if it's lighter, the device plots it white. + +What happens to the image during this operation? Well, some detail +survives, but our perception of gray levels is completely gone. This means +that a lot of the image content is obliterated. Take an area of the image +which is made up of various gray shades in the range of 60-90%. After fixed +thresholding, all of those shades (being darker than the 50% gray threshold) +will be mapped to solid black. So much for variations of intensity. + +Another portion of the image might show an object with an increasing, +diffused shadow across one of its surfaces, with gray shades in the range of +20-70%. This gradual variation in intensity will be lost in fixed +thresholding, giving way to two separate areas (one white, one black) and a +distinct, visible boundary between them. The situation where a transition +from one intensity or shade to another is very conspicuous is known as +contouring. + + +===================================== +Artifacts + +Phenomena like contouring, which are not present in the source image but +produced by the digital signal processing, are called artifacts. The most +common type of artifact is the Moire' pattern. If you display or print an +image of several lines, closely spaced and radiating from a single point, +you will see what appear to be flower-like patterns. These are not part of +the original image but are an illusion produced by the jaggedness of the +display. We will encounter and discuss other forms of artifacts later in +this paper. + + +===================================== +Error Noise + +Returning to our fixed-thresholded (and badly-rendered) image, how could we +document what has taken place to make this image so inaccurate? Expressing +it in technical terms, a relatively large amount of error "noise" is present +in the fixed-thresholded image. The error value is the difference between +the image's original intensity at a given dot and the intensity of the +displayed dot. Obviously, very dark values like 1 or 2 (which are almost +full black) incur very small errors when they are rendered as a 0 value +(black) dot. On the other hand, a gross error is incurred when a 129 value +dot (a medium gray) is displayed at 255 value (white), for instance. + +Simply put, digital halftoning redistributes this "noise energy" in a way +which makes it less visible. This brings up an important concept: digital +halftoning does not INCREASE the noise energy. In some of the literature, +reference is made to the "addition of dither noise," which might give this +impression. This is not the case, however: effective digital halftoning +acts upon the low-frequency component of the error noise (the component +which contributes to graininess) and scatters it in higher-frequency +components where it is not as obvious. + + +===================================== +Classes of digital halftoning algorithms + +The algorithms we will discuss in this paper can be subdivided into four +categories: + + 1. Random dither + 2. Patterning + 3. Ordered dither + 4. Error-diffusion halftoning + +Each of these methods is generally better than those listed before it, but +other considerations such as processing time, memory constraints, etc. may +weigh in favor of one of the simpler methods. + +To convert any of the first three methods into color, simply apply the +algorithm separately for each primary color and mix the resulting values. +This assumes that you have at least eight output colors: black, red, green, +blue, cyan, magenta, yellow, and white. Though this will work for error +diffusion as well, there are better methods which will be discussed in more +detail later. + + +===================================== +Random dither + +Random dithering could be termed the "bubblesort" of digital halftoning +algorithms. It was the first attempt (documented as far back as 1951) to +correct the contouring produced by fixed thresholding, and it has +traditionally been referenced for comparison in most studies of digital +halftoning. In fact, the name "ordered dither" (which will be discussed +later) was chosen to contrast random dither. + +While it is not really acceptable as a production method, it is very simple +to describe and implement. For each dot in our grayscale image, we generate +a random number in the range 0 - 255: if the random number is greater than +the image value at that dot, the display device plots the dot white; +otherwise, it plots it black. That's it. + +This generates a picture with a lot of "white noise", which looks like TV +picture "snow". Although inaccurate and grainy, the image is free from +artifacts. Interestingly enough, this digital halftoning method is useful +in reproducing very low-frequency images, where the absence of artifacts is +more important than noise. For example, a whole screen containing a +gradient of all levels from black to white would actually look best with a +random dither. With this image, other digital halftoning algorithms would +produce significant artifacts like diagonal patterns (in ordered dithering) +and clustering (in error diffusion halftones). + +I should mention, of course, that unless your computer has a hardware-based +random number generator (and most don't), there may be some artifacts from +the random number generation algorithm itself. For efficiency, you can take +the random number generator "out of the loop" by generating a list of random +numbers beforehand for use in the dither. Make sure that the list is larger +than the number of dots in the image or you may get artifacts from the reuse +of numbers. The worst case would be if the size of your list of random +numbers is a multiple or near-multiple of the horizontal size of the image; +in this case, unwanted vertical or diagonal lines will appear. + +As unattractive as it is, random dithering can actually be related to a +pleasing, centuries-old art know as mezzotinting (the name itself is an +Italianized derivative of the English "halftone"). In a mezzotint, the +skilled craftsman worked a soft metal (usually copper) printing plate, and +roughened or ground the dark regions of the image by hand and in a seemingly +random fashion. Analyzing it in scientific terms (which would surely insult +any mezzotinting artisan who might read this!) the pattern created is not +very regular or periodic at all, but the absence of low frequency noise +leads to a very attractive image without much graininess. A similar process +is still in use today, in the form of modern gravure printing. + + +===================================== +"Classical" halftoning + +Let's take a short departure from the digital domain and look at the +traditional or "classical" printing technique of halftoning. This technique +is over a century old, dating back to the weaving of silk pictures in the +mid 1800's. Modern halftone printing was invented in the late 1800's, and +halftones of that period are even today considered to be attractive +renditions of their subjects. + +Essentially, halftoning involves the printing of dots of different sizes in +an ordered and closely spaced pattern in order to simulate various +intensities. The early halftoning artisans realized that when we view a +very small area at normal viewing distances, our eyes perform a blending or +smoothing function on the fine detail within that area. As a result, we +perceive only the overall intensity of the area. This is known as spatial +integration. + +Although the tools of halftoning (the "screens" and screening process used +to generate the varying dots of the printed image) have undergone +improvements throughout the years, the fundamental principles remain +unchanged. This includes the 45-degree "screen angle" of the lines of dots, +which was known even to the earliest halftone artisans as giving more +pleasing images than dot lines running horizontally and vertically. + + +===================================== +Patterning + +This was the first digital technique to pay homage to the classical +halftone. It takes advantage of the fact that the spatial resolution of +display devices had improved to the point where one could trade some of it +for better intensity resolution. Like random dither, it is also a simple +concept, but is much more effective. + +For each possible value in the image, we create and display a pattern of +pixels (which can be either video pixels or printer "spots") that +approximates that value. Remembering the concept of spatial integration, if +we choose the appropriate patterns we can simulate the appearance of various +intensity levels -- even though our display can only generate a limited set +of intensities. + +For example, consider a 3 x 3 pattern. It can have one of 512 different +arrangements of pixels: however, in terms of intensity, not all of them are +unique. Since the number of black pixels in the pattern determines the +darkness of the pattern, we really have only 10 discrete intensity patterns +(including the all-white pattern), each one having one more black pixel than +the previous one. + +But which 10 patterns? Well, we can eliminate, right off the bat, patterns +like: + + --- X-- --X X-- + XXX or -X- or -X- or X-- + --- --X X-- X-- + + +because if they were repeated over a large area (a common occurrence in many +images [1]) they would create vertical, horizontal, or diagonal lines. +Also, studies [1] have shown that the patterns should form a "growth +sequence:" once a pixel is intensified for a particular value, it should +remain intensified for all subsequent values. In this fashion, each pattern +is a superset of the previous one; this similarity between adjacent +intensity patterns minimizes any contouring artifacts. + +Here is a good pattern for a 3-by-3 matrix which subscribes to the rules set +forth above: + + + --- --- --- -X- -XX -XX -XX -XX XXX XXX + --- -X- -XX -XX -XX -XX XXX XXX XXX XXX + --- --- --- --- --- -X- -X- XX- XX- XXX + + +This pattern matrix effectively simulates a screened halftone with dots of +various sizes. In large areas of constant value, the repetitive pattern +formed will be mostly artifact-free. + +No doubt, the reader will realize that applying this patterning process to +our image will triple its size in each direction. Because of this, +patterning can only be used where the display's spatial resolution is much +greater than that of the image. + +Another limitation of patterning is that the effective spatial resolution is +decreased, since a multiple-pixel "cell" is used to simulate the single, +larger halftone dot. The more intensity resolution we want, the larger the +halftone cell used and, by extension, the lower the spatial resolution. + +In the above example, using 3 x 3 patterning, we are able to simulate 10 +intensity levels (not a very good rendering) but we must reduce the spatial +resolution to 1/3 of the original figure. To get 64 intensity levels (a +very acceptable rendering), we would have to go to an 8 x 8 pattern and an +eight-fold decrease in spatial resolution. And to get the full 256 levels +of intensity in our source image, we would need a 16 x 16 pattern and would +incur a 16-fold reduction in spatial resolution. Because of this size +distortion of the image, and with the development of more effective digital +halftoning methods, patterning is only infrequently used today. + +To extend this method to color images, we would use patterns of colored +pixels to represent shades not directly printable by the hardware. For +example, if your hardware is capable of printing only red, green, blue, and +black (the minimal case for color dithering), other colors can be +represented with 2 x 2 patterns of these four: + + + Yellow = R G Cyan = G B Magenta = R B Gray = R G + G R B G B R B K + + +(B here represents blue, K is black). In this particular example, there are +a total of 31 such distinct patterns which can be used; their enumeration is +left "as an exercise for the reader" (don't you hate books that do that?). + + +===================================== +Clustered vs. dispersed patterns + +The pattern diagrammed above is called a "clustered" pattern, so called +because as new pixels are intensified in each pattern, they are placed +adjacent to the already-intensified pixels. Clustered-dot patterns were +used on many of the early display devices which could not render individual +pixels very distinctly, e.g. printing presses or other printers which smear +the printed spots slightly (a condition known as dot gain), or video +monitors which introduce some blurriness to the pixels. Clustered-dot +groupings tend to hide the effect of dot gain, but also produce a somewhat +grainy image. + +As video and hardcopy display technology improved, newer devices (such as +electrophotographic laser printers and high-res video displays) were better +able to accurately place and size their pixels. Further research showed +that, especially with larger patterns, the dispersed (non-clustered) layout +was more pleasing. Here is one such pattern: + + + --- X-- X-- X-- X-X X-X X-X XXX XXX XXX + --- --- --- --X --X X-X X-X X-X XXX XXX + --- --- -X- -X- -X- -X- XX- XX- XX- XXX + + + +Since clustering is not used, dispersed-dot patterns produce less grainy +images. + + +===================================== +Ordered dither + +While patterning was an important step toward the digital reproduction of +the classic halftone, its main shortcoming was the spatial enlargement (and +corresponding reduction in resolution) of the image. Ordered dither +represents a major improvement in digital halftoning where this spatial +distortion was eliminated and the image could then be rendered in its +original size. + +Obviously, in order to accomplish this, each dot in the source image must be +mapped to a pixel on the display device on a one-to-one basis. Accordingly, +the patterning concept was redefined so that instead of plotting the whole +pattern for each image dot, THE IMAGE DOT IS MAPPED ONLY TO ONE PIXEL IN THE +PATTERN. Returning to our example of a 3 x 3 pattern, this means that we +would be mapping NINE image dots into this pattern. + +The simplest way to do this in programming is to map the X and Y coordinates +of each image dot into the pixel (X mod 3, Y mod 3) in the pattern. + +Returning to our two patterns (clustered and dispersed) as defined earlier, +we can derive an effective mathematical algorithm that can be used to plot +the correct pixel patterns. Because each of the patterns above is a +superset of the previous, we can express the patterns in a compact array +form as the order of pixels added: + + + 8 3 4 1 7 4 + 6 1 2 and 5 8 3 + 7 5 9 6 2 9 + + +Then we can simply use the value in the array as a threshold. If the value +of the original image dot (scaled into the 0-9 range) is less than the +number in the corresponding cell of the matrix, we plot that pixel black; +otherwise, we plot it white. Note that in large areas of constant value, we +will get repetitions of the pattern just as we did with patterning. + +As before, clustered patterns should be used for those display devices which +blur the pixels. In fact, the clustered-dot ordered dither is the process +used by most newspapers, and in the computer imaging world the term +"halftoning" has come to refer to this method if not otherwise qualified. + + +As noted earlier, the dispersed-dot method (where the display hardware +allows) is preferred in order to decrease the graininess of the displayed +images. Bayer [2] has shown that for matrices of orders which are powers of +two there is an optimal pattern of dispersed dots which results in the +pattern noise being as high-frequency as possible. The pattern for a 2x2 +and 4x4 matrices are as follows: + + +1 3 1 9 3 11 These patterns (and their rotations +4 2 13 5 15 7 and reflections) are optimal for a + 4 12 2 10 dispersed-dot ordered dither. + 16 8 14 6 + + +Ulichney [3] shows a recursive technique can be used to generate the larger +patterns. (To fully reproduce our 256-level image, we would need to use an +8x8 pattern.) + +The Bayer ordered dither is in very common use and is easily identified by +the cross-hatch pattern artifacts it produces in the resulting display. +This artifacting is the major drawback of an otherwise powerful and very +fast technique. + + +===================================== +Dithering with "blue noise" + +Up to this point in our discussion, we have (with the exception of dithering +with white noise) discussed digital halftoning schemes which rely on the +application of some fairly regular mathematical processes in order to +redistribute the error noise of the image. Unfortunately, the regularity of +these algorithms leads to different kinds of artifacting which detracts from +the rendered image. In addition, these images all tend to reflect the +display device's row-and-column dot pattern to some extent, and this further +contributes to the "mechanical" character of the output image. + +Dithering with white noise, on the other hand, introduces enough randomness +to suppress the artifacting and the gridlike appearance, but the low- +frequency component of this noise introduces graininess. + +Obviously, what is needed is a method which falls somewhere in the middle of +these two extremes. In theoretical terms, if we could take white noise and +remove its low-frequency content, this would be an ideal way to disperse the +error content of our image. Many of the digital halftoning developers, +making an analogy to the audio world, refer to this concept as dithering +with blue noise. (In audio theory, "pink noise," which is often used as a +diagnostic and testing tool, is white noise from which some level of high- +frequency content has been filtered.) + +Alas, while an audio-frequency analog low-pass filter is a relatively simple +device to construct and operate, implementing a digital high-pass filter in +program code -- and one which operates efficiently enough so as not to +degrade display response time -- is no trivial task. + + +===================================== +Error-diffusion halftoning + +After considerable research, it was found that a set of techniques known as +error diffusion (also termed error dispersion or error distribution) +accomplished this quite effectively. In fact, error diffusion generates the +best results of any of the digital halftoning methods described here. Much +of the low-frequency noise component is suppressed, producing images with +very little grain. Error-diffusion halftones also display a very pleasing +randomness, without the visual sensation of rows and columns of dots; this +effect is known as the "grid defiance illusion." + +As in other areas of life, though, there ain't no such thing as a free +lunch. Error diffusion is, by nature, the slowest method of digital +halftoning. In fact, there are several variants of this technique, and the +better they get, the slower they are. However, one will realize a very +significant improvement in the quality of the processed images which easily +justifies the time and computational power required. + +Error diffusion is very simple to describe. For each point in our image, we +first find the closest intensity (or color) available. We then calculate +the difference between the image value at that point and that nearest +available intensity/color: this difference is our error value. Now we +divide up the error value and distribute it to some of the neighboring image +areas which we have not visited (or processed) yet. When we get to these +later dots, we add in the portions of error values which were distributed +there from the preceding dots, and clip the cumulative value to an allowed +range if needed. This new, modified value now becomes the image value that +we use for processing this point. + +If we are dithering our sample grayscale image for output to a black-and- +white device, the "find closest intensity/color" operation is just a simple +thresholding (the closest intensity is going to be either black or white). +In color imaging -- for instance, color-reducing a 24-bit true color Targa +file to an 8-bit, mapped GIF file -- this involves matching the input color +to the closest available hardware color. Depending on how the display +hardware manages its intensity/color palette, this matching process can be a +difficult task. (This is covered in more detail in the "Color issues" +section later in this paper.) + +Up till now, all other methods of digital halftoning were point operations, +where any adjustments that were made to a given dot had no effect on any of +the surrounding dots. With error diffusion, we are doing a "neighborhood +operation." Dispersing the error value over a larger area is the key to the +success of these methods. + +The different ways of dividing up the error can be expressed as patterns +called filters. In the following sections, I will list a number of the most +commonly-used filters and some info on each. + + +===================================== +The Floyd-Steinberg filter + +This is where it all began, with Floyd and Steinberg's [4] pioneering +research in 1975. The filter can be diagrammed thus: + + + * 7 + 3 5 1 (1/16) + + +In this (and all subsequent) filter diagrams, the "*" represents the pixel +currently being scanning, and the neighboring numbers (called weights) +represent the portion of the error distributed to the pixel in that +position. The expression in parentheses is the divisor used to break up the +error weights. In the Floyd-Steinberg filter, each pixel "communicates" +with 4 "neighbors." The pixel immediately to the right gets 7/16 of the +error value, the pixel directly below gets 5/16 of the error, and the +diagonally adjacent pixels get 3/16 and 1/16. + +The weighting shown is for the traditional left-to-right scanning of the +image. If the line were scanned right-to-left (more about this later), this +pattern would be reversed. In either case, the weights calculated for the +subsequent line must be held by the program, usually in an array of some +sort, until that line is visited later. + +Floyd and Steinberg carefully chose this filter so that it would produce a +checkerboard pattern in areas with intensity of 1/2 (or 128, in our sample +image). It is also fairly easy to execute in programming code, since the +division by 16 is accomplished by simple, fast bit-shifting instructions +(this is the case whenever the divisor is a power of 2). + + +===================================== +The "false" Floyd-Steinberg filter + +Occasionally, you will see the following filter erroneously called the +Floyd-Steinberg filter: + + + * 3 + 3 2 (1/8) + + +The output from this filter is nowhere near as good as that from the real +Floyd-Steinberg filter. There aren't enough weights to the dispersion, +which means that the error value isn't distributed finely enough. With the +entire image scanned left-to-right, the artifacting produced would be +totally unacceptable. + +Much better results would be obtained by using an alternating, or +serpentine, raster scan: processing the first line left-to-right, the next +line right-to-left, and so on (reversing the filter pattern appropriately). +Serpentine scanning -- which can be used with any of the error-diffusion +filters detailed here -- introduces an additional perturbation which +contributes more randomness to the resultant halftone. Even with serpentine +scanning, however, this filter would need additional perturbations (see +below) to give acceptable results. + + +===================================== +The Jarvis, Judice, and Ninke filter + +If the false Floyd-Steinberg filter fails because the error isn't +distributed well enough, then it follows that a filter with a wider +distribution would be better. This is exactly what Jarvis, Judice, and +Ninke [6] did in 1976 with their filter: + + + * 7 5 + 3 5 7 5 3 + 1 3 5 3 1 (1/48) + + +While producing nicer output than Floyd-Steinberg, this filter is much +slower to implement. With the divisor of 48, we can no longer use bit- +shifting to calculate the weights but must invoke actual DIV (divide) +processor instructions. This is further exacerbated by the fact that the +filter must communicate with 12 neighbors; three times as many in the Floyd- +Steinberg filter. Furthermore, with the errors distributed over three +lines, this means that the program must keep two forward error arrays, which +requires extra memory and time for processing. + + +===================================== +The Stucki filter + +P. Stucki [7] offered a rework of the Jarvis, Judice, and Ninke filter in +1981: + + + * 8 4 + 2 4 8 4 2 + 1 2 4 2 1 (1/42) + + +Once again, division by 42 is quite slow to calculate (requiring DIVs). +However, after the initial 8/42 is calculated, some time can be saved by +producing the remaining fractions by shifts. The Stucki filter has been +observed to give very clean, sharp output, which helps to offset the slow +processing time. + + +===================================== +The Burkes filter + +Daniel Burkes [5] of TerraVision undertook to improve upon the Stucki filter +in 1988: + + + * 8 4 The Burkes filter + 2 4 8 4 2 (1/32) + + +Notice that this is just a simplification of the Stucki filter with the +bottom row removed. The main improvement is that the divisor is now 32, +which allows the error values to be calculated using shifts once more, and +the number of neighbors communicated with has been reduced to seven. +Furthermore, the removal of one row reduces the memory requirements of the +filter by eliminating the second forward array which would otherwise be +needed. + + +===================================== +The Sierra filters + +In 1989, Frankie Sierra came out with his three-line filter: + + + * 5 3 The Sierra3 filter + 2 4 5 4 2 + 2 3 2 (1/32) + + +A year later, Sierra followed up with a two-line modification: + + + * 4 3 The Sierra2 filter + 1 2 3 2 1 (1/16) + + +and a very simple "Filter Lite," as he calls it: + + + * 2 The Sierra-2-4A filter + 1 1 (1/4) + + +Even this very simple filter, according to Sierra, produces better results +than the original Floyd-Steinberg filter. + + +===================================== +Miscellaneous filters + +Many image processing software packages offer one or more of the filters +listed above as dithering options. In nearly every case, the Floyd- +Steinberg filter (or a variant thereof) is included. The Bayer ordered +dither is sometimes offered, although the Floyd-Steinberg filter will do a +better job in essentially the same processing time. Higher-quality filters +like Burkes or Stucki are usually also present. + +All of the filters described above are used on display devices which have +"square pixels." This is to say that the display lays out the pixels in +rows and columns, aligned horizontally and vertically and spaced equally in +both directions. This applies to the commonly-used video modes in VGA and +SVGA: 640 x 480, 800 x 600, and 1024 x 768, with a 4:3 "aspect ratio." It +would also include HP-compatible and PostScript desktop laser printers using +300dpi marking engines. + +Some displays may use "rectangular pixels," where the horizontal and +vertical spacings are unequal. This would include various EGA and CGA video +modes and other specialized video displays, and most dot-matrix printers. +In many cases, the filters described earlier will do a decent job on +rectangular pixel grids, but an optimized filter would be preferred. +Slinkman [10] describes one such filter for his 640 x 240 monochrome display +with a 1:2 aspect ratio. + +In other cases, video displays might use a "hexagonal grid" of pixels, where +rows of pixels are offset or staggered, in much the same fashion used on +broadcast television receivers. This is illustrated below: + + + . . . . . . . . . . . . . . . . . . . . . + . . . . . . . . . . . . . . . . . . . . + . . . . . . . . . . . . . . . . . . . . . + . . . . . . . . . . . . . . . . . . . . + . . . . . . . . . . . . . . . . . . . . . + square/rectangular hexagonal + + +Hexagonal grids are given a very thorough treatment by Ulichney, should you +be interested in further information. + +While technically not an error-diffusion filter, a method proposed by Gozum +[11] offers color resolutions in excess of 256 colors by plotting red, +green, and blue pixel "triplets" or triads to simulate an "interlaced" +television display (sacrificing some horizontal resolution in the process). +Again, I would refer interested readers to his document for more +information. + + +===================================== +Special considerations + +The speed disadvantages of the more complex filters can be eliminated +somewhat by performing the divisions beforehand and using lookup tables +instead of doing the math inside the loop. This makes it harder to use +various filters in the same program, but the speed benefits are enormous. + +It is critical with all of these algorithms that when error values are added +to neighboring pixels, the resultant summed values must be truncated to fit +within the limits of hardware. Otherwise, an area of very intense color may +cause streaks into an adjacent area of less intense color. + +This truncation is known as "clipping," and is analogous to the audio +world's concept of the same name. As in the case of an audio amplifier, +clipping adds undesired noise to the data. Unlike the audio world, however, +the visual clipping performed in error-diffusion halftoning is acceptable +since it is not nearly so offensive as the color streaking that would occur +otherwise. It is mainly for this reason that the larger filters work better +-- they split the errors up more finely and produce less clipping noise. + +With all of these filters, it is also important to ensure that the sum of +the distributed error values is equal to the original error value. This is +most easily accomplished by subtracting each fraction, as it is calculated, +from the whole error value, and using the final remainder as the last +fraction. + + +===================================== +Further perturbations + +As alluded to earlier, there are various techniques for the reduction of +digital artifacts, most of which involve using a little randomness to +lightly "perturb" a regular algorithm (particularly the simpler ones). It +could be said that random dither takes this concept to the extreme. + +Serpentine scanning is one of these techniques, as noted earlier. Other +techniques include the addition of small amounts of white noise, or +randomizing the positions of the error weights (essentially, using a +constantly-varying pattern). As you might imagine, any of these methods +incur a penalty in processing time. + +Indeed, some of the above filters (particularly the simpler ones) can be +greatly improved by skewing the weights with a little randomness [3]. + + +===================================== +Nearest available color + +Calculating the nearest available intensity is trivial with a monochrome +image; calculating the nearest available color in a color image requires +more work. + +A table of RGB values of all available colors must be scanned sequentially +for each input pixel to find the closest. The "distance" formula most often +used is a simple pythagorean "least squares". The difference for each color +is squared, and the three squares added to produce the distance value. This +value is equivalent to the square of the distance between the points in RGB- +space. It is not necessary to compute the square root of this value because +we are not interested in the actual distance, only in which is smallest. +The square root function is a monotonic increasing function and does not +affect the order of its operands. If the total number of colors with which +you are dealing is small, this part of the algorithm can be replaced by a +lookup table as well. + +When your hardware allows you to select the available colors, very good +results can be achieved by selecting colors from the image itself. You must +reserve at least 8 colors for the primaries, secondaries, black, and white +for best results. If you do not know the colors in your image ahead of +time, or if you are going to use the same map to dither several different +images, you will have to fill your color map with a good range of colors. +This can be done either by assigning a certain number of bits to each +primary and computing all combinations, or by a smoother distribution as +suggested by Heckbert [8]. + +An alternate method of color selection, based on a tetrahedral color space, +has been proposed by Crawford [12]. His algorithm has been optimized for +either dispersed-dot ordered dither or Floyd-Steinberg error diffusion with +serpentine scan. + + +===================================== +Hardware halftoning + +In some cases, image scanning hardware may be able to digitally halftone and +dither the image "on the fly" as it is being scanned. The data produced by +the "raw" scan is then already in a 1- or 2-bit/pixel format. While this +feature would probably be unsuitable for cases where the image would need +further processing (see the "Loss of image information" section below), it +is very useful where the operator wants to generate a final image, ready for +printing or displaying, with little or no subsequent processing. + +As an example, the Epson ES-300C color scanner (and its European equivalent, +the Epson GT-6000) offers three internal halftone modes. One is a standard +"halftone" algorithm, i.e. a clustered-dot ordered dither. The other two +are error-diffusion filters (one "sharp," the other "soft") which are +proprietary Epson-developed filters. + + +===================================== +Loss of image information incurred by digital halftoning + +It is important to emphasize here that digital halftoning is a ONE-WAY +operation. Once an image has been halftoned or dithered, although it may +look like a good reproduction of the original, INFORMATION IS PERMANENTLY +LOST. Many image processing functions fail on dithered images; in fact, you +would not want to dither an image which had already been dithered to some +extent. + +For these reasons, digital halftoning must be considered primarily as a way +TO PRODUCE AN IMAGE ON HARDWARE THAT WOULD OTHERWISE BE INCAPABLE OF +DISPLAYING IT. This would hold true wherever a grayscale or color image +needs to be rendered on a bilevel display device. In this situation, one +would almost never want to store the dithered image. + +On the other hand, when color images are dithered for display on color +displays with a lower color resolution, the dithered images are more useful. +In fact, the bulk of today's scanned-image GIF files which abound on +electronic BBSs and information services are 8-bit (256 color), colormapped +and dithered files created from 24-bit true-color scans. Only rarely are +the 24-bit files exchanged, because of the huge amount of data contained in +them. + +In some cases, these mapped GIF files may be further processed with special +paint/processing utilities, with very respectable results. However, the +previous warning still applies: one can never obtain the same image fidelity +when operating on the mapped GIF file as they could if they were operating +on the true-color image file. + +Generally speaking, digital halftoning and dithering should be the last +stage in producing a physical display from a digitally stored image. The +data representing an image should always be kept in full detail in case you +should want to reprocess it in any way. As affordable display technology +improves, the day may soon come where you might possess the hardware to +allow you to use all of the original image information without the need for +digital halftoning or color reduction. + + +===================================== +Sample code + +Despite my best efforts in expository writing, nothing explains an algorithm +better than real code. With that in mind, presented here are a few programs +which implement some of the concepts presented in this paper. + + +1) This code (in the C programming language) dithers a 256-level + monochrome image onto a black-and-white display with the Bayer ordered + dither. + +/* Bayer-method ordered dither. The array line[] contains the intensity +** values for the line being processed. As you can see, the ordered +** dither is much simpler than the error dispersion dither. It is also +** many times faster, but it is not as accurate and produces cross-hatch +** patterns on the output. +*/ + +unsigned char line[WIDTH]; + +int pattern[8][8] = { + { 0, 32, 8, 40, 2, 34, 10, 42}, /* 8x8 Bayer ordered dithering */ + {48, 16, 56, 24, 50, 18, 58, 26}, /* pattern. Each input pixel */ + {12, 44, 4, 36, 14, 46, 6, 38}, /* is scaled to the 0..63 range */ + {60, 28, 52, 20, 62, 30, 54, 22}, /* before looking in this table */ + { 3, 35, 11, 43, 1, 33, 9, 41}, /* to determine the action. */ + {51, 19, 59, 27, 49, 17, 57, 25}, + {15, 47, 7, 39, 13, 45, 5, 37}, + {63, 31, 55, 23, 61, 29, 53, 21} }; + +int getline(); /* Function to read line[] from image */ + /* file; must return EOF when done. */ +putdot(int x, int y); /* Plot white dot at given x, y. */ + +dither() +{ + int x, y; + + while (getline() != EOF) { + for (x=0; x> 2; /* Scale value to 0..63 range */ + + if (c > pattern[x & 7][y & 7]) putdot(x, y); + } + ++y; + } +} + + +2) This program (also written in C) dithers a color image onto an 8-color + display by error-diffusion using the Burkes filter. + +/* Burkes filter error diffusion dithering algorithm in color. The array +** line[][] contains the RGB values for the current line being processed; +** line[0][x] = red, line[1][x] = green, line[2][x] = blue. +*/ + +unsigned char line[3][WIDTH]; +unsigned char colormap[3][COLORS] = { + 0, 0, 0, /* Black This color map should be replaced */ + 255, 0, 0, /* Red by one available on your hardware */ + 0, 255, 0, /* Green */ + 0, 0, 255, /* Blue */ + 255, 255, 0, /* Yellow */ + 255, 0, 255, /* Magenta */ + 0, 255, 255, /* Cyan */ + 255, 255, 255 }; /* White */ + +int getline(); /* Function to read line[][] from image */ + /* file; must return EOF when done. */ +putdot(int x, int y, int c); /* Plot dot of given color at given x, y. */ + +dither() +{ + static int ed[3][WIDTH] = {0}; /* Errors distributed down, i.e., */ + /* to the next line. */ + int x, y, h, c, nc, v, /* Working variables */ + e[4], /* Error parts (7/8,1/8,5/8,3/8). */ + ef[3]; /* Error distributed forward. */ + long dist, sdist; /* Used for least-squares match. */ + + for (x=0; x 255) v = 255; /* and clip. */ + line[c][x] = v; + } + + sdist = 255L * 255L * 255L + 1L; /* Compute the color */ + for (c=0; c> 1; /* half of v, e[1..4] */ + e[1] = (7 * h) >> 3; /* will be filled */ + e[2] = h - e[1]; /* with the Floyd and */ + h = v - h; /* Steinberg weights. */ + e[3] = (5 * h) >> 3; + e[4] = h = e[3]; + + ef[c] = e[1]; /* Distribute errors. */ + if (x < WIDTH-1) ed[c][x+1] = e[2]; + if (x == 0) ed[c][x] = e[3]; else ed[c][x] += e[3]; + if (x > 0) ed[c][x-1] += e[4]; + } + } + ++y; + } +} + + +3) This program (in somewhat incomplete, very inefficient pseudo-C) + implements error diffusion dithering with the Floyd and Steinberg + filter. It is not efficiently coded, but its purpose is to show the + method, which I believe it does. + +/* Floyd/Steinberg error diffusion dithering algorithm in color. The array +** line[][] contains the RGB values for the current line being processed; +** line[0][x] = red, line[1][x] = green, line[2][x] = blue. It uses the +** external functions getline() and putdot(), whose purpose should be easy +** to see from the code. +*/ + +unsigned char line[3][WIDTH]; +unsigned char colormap[3][COLORS] = { + 0, 0, 0, /* Black This color map should be replaced */ + 255, 0, 0, /* Red by one available on your hardware. */ + 0, 255, 0, /* Green It may contain any number of colors */ + 0, 0, 255, /* Blue as long as the constant COLORS is */ + 255, 255, 0, /* Yellow set correctly. */ + 255, 0, 255, /* Magenta */ + 0, 255, 255, /* Cyan */ + 255, 255, 255 }; /* White */ + +int getline(); /* Function to read line[] from image file; */ + /* must return EOF when done. */ +putdot(int x, int y, int c); /* Plot dot of color c at location x, y. */ + +dither() +{ + static int ed[3][WIDTH] = {0}; /* Errors distributed down, i.e., */ + /* to the next line. */ + int x, y, h, c, nc, v, /* Working variables */ + e[4], /* Error parts (7/8,1/8,5/8,3/8). */ + ef[3]; /* Error distributed forward. */ + long dist, sdist; /* Used for least-squares match. */ + + for (x=0; x 255) v = 255; /* and clip. */ + line[c][x] = v; + } + + sdist = 255L * 255L * 255L + 1L; /* Compute the color */ + for (c=0; c> 1; /* half of v, e[1..4] */ + e[1] = (7 * h) >> 3; /* will be filled */ + e[2] = h - e[1]; /* with the Floyd and */ + h = v - h; /* Steinberg weights. */ + e[3] = (5 * h) >> 3; + e[4] = h = e[3]; + + ef[c] = e[1]; /* Distribute errors. */ + if (x < WIDTH-1) ed[c][x+1] = e[2]; + if (x == 0) ed[c][x] = e[3]; else ed[c][x] += e[3]; + if (x > 0) ed[c][x-1] += e[4]; + } + } /* next x */ + + ++y; + } /* next y */ +} + + +===================================== +Bibliography + +[1] Foley, J.D. and A. van Dam, Fundamentals of Interactive Computer + Graphics, Addison-Wesley, Reading, MA, 1982. + + This is a standard reference for many graphic techniques which has + not declined with age. Highly recommended. This edition is out + of print but can be found in many university and engineering + libraries. NOTE: This book has been updated and rewritten, and + this new version is currently in print as: + + Foley, J.D., A. van Dam, S.K. Feiner, and J.F. Hughes; Computer + Graphics: Principles and Practice. Addison-Wesley, Reading, MA, 1990. + + This rewrite omits some of the more technical data of the 1982 + edition, but has been updated to include information on error- + diffusion and the Floyd-Steinberg filter. Currently on computer + bookstore shelves and rather expensive (around $75 list price). + +[2] Bayer, B.E., "An Optimum Method for Two-Level Rendition of Continuous + Tone Pictures," IEEE International Conference on Communications, + Conference Records, 1973, pp. 26-11 to 26-15. + + A short article proving the optimality of Bayer's pattern in the + dispersed-dot ordered dither. + +[3] Ulichney, R., Digital Halftoning, The MIT Press, Cambridge, MA, 1987. + + This is the best book I know of for describing the various black + and white dithering methods. It has clear explanations (a little + higher math may come in handy) and wonderful illustrations. It + does not contain any code, but don't let that keep you from + getting this book. Computer Literacy normally carries it but the + title is often sold out. + + [MFM note: I can't describe how much information I got from this + book! Several different writers have praised this reference to + the skies, and I can only concur. Some of it went right over my + head -- it's heavenly for someone who is thrilled by Fourier + analysis -- but the rest of it is a clear and excellent treatment + of the subject. I had to request it on an interlibrary loan, but + it was worth the two weeks' wait and the 25 cents it cost me for + the search. University or engineering libraries would be your + best bet, as would technical bookstores.] + +[4] Floyd, R.W. and L. Steinberg, "An Adaptive Algorithm for Spatial Gray + Scale." SID 1975, International Symposium Digest of Technical Papers, + vol 1975m, pp. 36-37. + + Short article in which Floyd and Steinberg introduce their filter. + +[5] Daniel Burkes is unpublished, but can be reached at this address: + + Daniel Burkes + TerraVision, Inc. + 2351 College Station Road, Suite 563 + Athens, GA 30305 + + or via CIS at UID# 72077,356. The Burkes error filter was submitted to + the public domain on September 15, 1988 in an unpublished document, + "Presentation of the Burkes error filter for use in preparing + continuous-tone images for presentation on bi-level devices." The file + BURKES.ARC, in LIB 15 (Publications) of the CIS Graphics Support Forum, + contains this document as well as sample images. + +[6] Jarvis, J.F., C.N. Judice, and W.H. Ninke, "A Survey of Techniques for + the Display of Continuous Tone Pictures on Bi-Level Displays," Computer + Graphics and Image Processing, vol. 5, pp. 13-40, 1976. + +[7] Stucki, P., "MECCA - a multiple-error correcting computation algorithm + for bilevel image hardcopy reproduction." Research Report RZ1060, IBM + Research Laboratory, Zurich, Switzerland, 1981. + +[8] Heckbert, P. "Color Image Quantization for Frame Buffer Display." + Computer Graphics (SIGGRAPH 82), vol. 16, pp. 297-307, 1982. + +[9] Frankie Sierra is unpublished, but can be reached via CIS at UID# + 76356,2254. Pictorial presentations of his filters can be found in LIB + 17 (Developer's Den) of the CIS Graphics Support Forum as the files + DITER1.GIF, DITER2.GIF, DITER6.GIF, DITER7.GIF, DITER8.GIF, and + DITER9.GIF. + +[10] J.F.R. "Frank" Slinkman is unpublished, but can be reached via CIS at + UID# 72411,650. The file NUDTHR.ARC in LIB 17 (Developer's Den) of the + CIS Graphics Support Forum contains his document "New Dithering Method + for Non-Square Pixels" as well as sample images and encoding program. + +[11] Lawrence Gozum is unpublished, but can be reached via CIS at UID# + 73437,2372. His document "Notes of IDTVGA Dithering Method" can be + found in LIB 17 (Developer's Den) of the CIS Graphics Support Forum as + the file IDTVGA.TXT. + +[12] Robert M. Crawford is unpublished, but can be reached via CIS at UID# + 76356,741. The file DGIF.ZIP in LIB 17 (Developer's Den) of the CIS + Graphics Support Forum contains documentation, sample images, and demo + program. + + +======================================================================== +Other works of interest: + +Knuth, D.E., "Digital Halftones by Dot Diffusion." ACM Transactions on +Graphics, Vol. 6, No. 4, October 1987, pp 245-273. + + Surveys the various methods available for mapping grayscale images to + B&W for high-quality phototypesetting and laser printer reproduction. + Presents an algorithm for smooth dot diffusion. (With 22 references.) + +Newman, W.M. and R.F.S. Sproull, Principles of Interactive Computer +Graphics, 2nd edition, McGraw-Hill, New York, 1979. + + Similar to Foley and van Dam in scope and content. + +Rogers, D.F., Procedural Elements for Computer Graphics, McGraw-Hill, New +York, 1985. + + More of a conceptual treatment of the subject -- for something with + more programming code, see the following work. Alas, the author errs + in his discussion of the Floyd-Steinberg filter and uses the "false" + filter pattern discussed earlier. + +Rogers, D.F. and J. A. Adams, Mathematical Elements for Computer Graphics, +McGraw-Hill, New York, 1976. + + A good detailed discussion of producing graphic images on a computer. + Plenty of sample code. + +Kuto, S., "Continuous Color Presentation Using a Low-Cost Ink Jet Printer," +Proc. Computer Graphics Tokyo 84, 24-27 April, 1984, Tokyo, Japan. + +Mitchell, W.J., R.S. Liggett, and T. Kvan, The Art of Computer Graphics +Programming, Van Nostrand Reinhold Co., New York, 1987. + +Pavlidis, T., Algorithms for Graphics and Image Processing, Computer Science +Press, Rockville, MD, 1982. + diff --git a/src/ImageSharp/Dithering/DITHER.TXT b/src/ImageSharp/Dithering/DITHER.TXT new file mode 100644 index 0000000000..4d29a533e3 --- /dev/null +++ b/src/ImageSharp/Dithering/DITHER.TXT @@ -0,0 +1,547 @@ +DITHER.TXT + +What follows is everything you ever wanted to know (for the time being) about +dithering. I'm sure it will be out of date as soon as it is released, but it +does serve to collect data from a wide variety of sources into a single +document, and should save you considerable searching time. + +Numbers in brackets (like this [0]) are references. A list of these works +appears at the end of this document. + +Because this document describes ideas and algorithms which are constantly +changing, I expect that it may have many editions, additions, and corrections +before it gets to you. I will list my name below as original author, but I +do not wish to deter others from adding their own thoughts and discoveries. +This is not copyrighted in any way, and was created solely for the purpose of +organizing my own knowledge on the subject, and sharing this with others. +Please distribute it to anyonw who might be interested. + +If you add anything to this document, please feel free to include your name +below as a contributor or as a reference. I would particularly like to see +additions to the "Other books of interest" section. Please keep the text in +this simple format: no margins, no pagination, no lines longer that 79 +characters, and no non-ASCII or non-printing characters other than a CR/LF +pair at the end of each line. It is intended that this be read on as many +different machines as possible. + +Original Author: + +Lee Crocker I can be reached in the CompuServe Graphics +1380 Jewett Ave Support Forum (GO PICS) with ID # 73407,2030. +Pittsburg, CA 94565 + +Contributors: + +======================================================================== +What is Dithering? + +Dithering, also called Halftoning or Color Reduction, is the process of +rendering an image on a display device with fewer colors than are in the +image. The number of different colors in an image or on a device I will call +its Color Resolution. The term "resolution" means "fineness" and is used to +describe the level of detail in a digitally sampled signal. It is used most +often in referring to the Spatial Resolution, which is the basic sampling +rate for a digitized image. + +Spatial resolution describes the fineness of the "dots" used in an image. +Color resolution describes the fineness of detail available at each dot. The +higher the resolution of a digital sample, the better it can reproduce high +frequency detail. A compact disc, for example, has a temporal (time) +resolution of 44,000 samples per second, and a dynamic (volume) resolution of +16 bits (0..65535). It can therefore reproduce sounds with a vast dynamic +range (from barely audible to ear-splitting) with great detail, but it has +problems with very high-frequency sounds, like violins and piccolos. + +It is often possible to "trade" one kind of resolution for another. If your +display device has a higher spatial resolution than the image you are trying +to reproduce, it can show a very good image even if its color resolution is +less. This is what we will call "dithering" and is the subject of this +paper. The other tradeoff, i.e., trading color resolution for spatial +resolution, is called "anti-aliasing" and is not discussed here. + +It is important to emphasize here that dithering is a one-way operation. +Once an image has been dithered, although it may look like a good +reproduction of the original, information is permanently lost. Many image +processing functions fail on dithered images. For these reasons, dithering +must be considered only as a way to produce an image on hardware that would +otherwise be incapable of displaying it. The data representing an image +should always be kept in full detail. + + +======================================================================== +Classes of dithering algorithms + +The classes of dithering algorithms we will discuss here are these: + +1. Random +2. Pattern +3. Ordered +4. Error dispersion + +Each of these methods is generally better than those listed before it, but +other considerations such as processing time, memory constraints, etc. may +weigh in favor of one of the simpler methods. + +For the following discussions I will assume that we are given an image with +256 shades of gray (0=black..255=white) that we are trying to reproduce on a +black and white ouput device. Most of these methods can be extended in +obvious ways to deal with displays that have more than two levels but fewer +than the image, or to color images. Where such extension is not obvious, or +where better results can be obtained, I will go into more detail. + +To convert any of the first three methods into color, simply apply the +algorithm separately for each primary and mix the resulting values. This +assumes that you have at least eight output colors: black, red, green, blue, +cyan, magenta, yellow, and white. Though this will work for error dispersion +as well, there are better methods in this case. + + +======================================================================== +Random dither + +This is the bubblesort of dithering algorithms. It is not really acceptable +as a production method, but it is very simple to describe and implement. For +each value in the image, simply generate a random number 1..256; if it is +geater than the image value at that point, plot the point white, otherwise +plot it black. That's it. This generates a picture with a lot of "white +noise", which looks like TV picture "snow". Though the image produced is +very inaccurate and noisy, it is free from "artifacts" which are phenomena +produced by digital signal processing. + +The most common type of artifact is the Moire pattern (Contributors: please +resist the urge to put an accent on the "e", as no portable character set +exists for this). If you draw several lines close together radiating from a +single point on a computer display, you will see what appear to be flower- +like patterns. These patterns are not part of the original idea of lines, +but are an illusion produced by the jaggedness of the display. + +Many techniques exist for the reduction of digital artifacts like these, most +of which involve using a little randomness to "perturb" a regular algorithm a +little. Random dither obviously takes this to extreme. + +I should mention, of course, that unless your computer has a hardware-based +random number generator (and most don't) there may be some artifacts from the +random number generation algorithm itself. + +While random dither adds a lot of high-frequency noise to a picture, it is +useful in reproducing very low-frequency images where the absence of +artifacts is more important than noise. For example, a whole screen +containing a gradient of all levels from black to white would actually look +best with a random dither. In this case, ordered dithering would produce +diagonal patterns, and error dispersion would produce clustering. + +For efficiency, you can take the random number generator "out of the loop" by +generating a list of random numbers beforehand for use in the dither. Make +sure that the list is larger than the number of pixels in the image or you +may get artifacts from the reuse of numbers. The worst case would be if the +size of your list of random numbers is a multiple or near-multiple of the +horizontal size of the image, in which case unwanted vertical or diagonal +lines will appear. + + +======================================================================== +Pattern dither + +This is also a simple concept, but much more effective than random dither. +For each possible value in the image, create a pattern of dots that +approximates that value. For instance, a 3-by-3 block of dots can have one +of 512 patterns, but for our purposes, there are only 10; the number of black +dots in the pattern determines the darkness of the pattern. + +Which 10 patterns do we choose? Obviously, we need the all-white and all- +black patterns. We can eliminate those patterns which would create vertical +or horizontal lines if repeated over a large area because many images have +such regions of similar value [1]. It has been shown [1] that patterns for +adjacent colors should be similar to reduce an artifact called "contouring", +or visible edges between regions of adjacent values. One easy way to assure +this is to make each pattern a superset of the previous. Here are two good +sets of patterns for a 3-by-3 matrix: + + --- --- --- -X- -XX -XX -XX -XX XXX XXX + --- -X- -XX -XX -XX -XX XXX XXX XXX XXX + --- --- --- --- --- -X- -X- XX- XX- XXX +or + --- X-- X-- X-- X-X X-X X-X XXX XXX XXX + --- --- --- --X --X X-X X-X X-X XXX XXX + --- --- -X- -X- -X- -X- XX- XX- XX- XXX + +The first set of patterns above are "clustered" in that as new dots are added +to each pattern, they are added next to dots already there. The second set +is "dispersed" as the dots are spread out more. This distinction is more +important on larger patterns. Dispersed-dot patterns produce less grainy +images, but require that the output device render each dot distinctly. When +this is not the case, as with a printing press which smears the dots a +little, clustered patterns are better. + +For each pixel in the image we now print the pattern which is closest to its +value. This will triple the size of the image in each direction, so this +method can only be used where the display spatial resolution is much greater +than that of the image. + +We can exploit the fact that most images have large areas of similar value to +reduce our need for extra spatial resolution. Instead of plotting a whole +pattern for each pixel, map each pixel in the image to a dot in the pattern +an only plot the corresponding dot for each pixel. + +The simplest way to do this is to map the X and Y coordinates of each pixel +into the dot (X mod 3, Y mod 3) in the pattern. Large areas of constant +value will come out as repetitions of the pattern as before. + +To extend this method to color images, we must use patterns of colored dots +to represent shades not directly printable by the hardware. For example, if +your hardware is capable of printing only red, green, blue, and black (the +minimal case for color dithering), other colors can be represented with +patterns of these four: + + Yellow = R G Cyan = G B Magenta = R B Gray = R G + G R B G B R B K + +(B here represents blue, K is black). There are a total of 31 such distinct +patterns which can be used; I will leave their enumeration "as an exercise +for the reader" (don't you hate books that do that?). + + +======================================================================== +Ordered dither + +Because each of the patterns above is a superset of the previous, we can +express the patterns in compact form as the order of dots added: + + 8 3 4 and 1 7 4 + 6 1 2 5 8 3 + 7 5 9 6 2 9 + +Then we can simply use the value in the array as a threshhold. If the value +of the pixel (scaled into the 0-9 range) is less than the number in the +corresponding cell of the matrix, plot that pixel black, otherwise, plot it +white. This process is called ordered dither. As before, clustered patterns +should be used for devices which blur dots. In fact, the clustered pattern +ordered dither is the process used by most newspapers, and the term +halftoning refers to this method if not otherwise qualified. + +Bayer [2] has shown that for matrices of orders which are powers of two there +is an optimal pattern of dispersed dots which results in the pattern noise +being as high-frequency as possible. The pattern for a 2x2 and 4x4 matrices +are as follows: + + 1 3 1 9 3 11 These patterns (and their rotations + 4 2 13 5 15 7 and reflections) are optimal for a + 4 12 2 10 dispersed-pattern ordered dither. + 16 8 14 6 + +Ulichney [3] shows a recursive technique can be used to generate the larger +patterns. To fully reproduce our 256-level image, we would need to use the +8x8 pattern. + +Bayer's method is in very common use and is easily identified by the cross- +hatch pattern artifacts it produces in the resulting display. This +artifacting is the major drawback of the technique wich is otherwise very +fast and powerful. Ordered dithering also performs very badly on images +which have already been dithered to some extent. As stated earlier, +dithering should be the last stage in producing a physical display from a +digitally stored image. The dithered image should never be stored itself. + + +======================================================================== +Error dispersion + +This technique generates the best results of any method here, and is +naturally the slowest. In fact, there are many variants of this technique as +well, and the better they get, the slower they are. + +Error dispersion is very simple to describe: for each point in the image, +first find the closest color available. Calculate the difference between the +value in the image and the color you have. Now divide up these error values +and distribute them over the neighboring pixels which you have not visited +yet. When you get to these later pixels, just add the errors distributed +from the earlier ones, clip the values to the allowed range if needed, then +continue as above. + +If you are dithering a grayscale image for output to a black-and-white +device, the "find closest color" is just a simle threshholding operation. In +color, it involves matching the input color to the closest available hardware +color, which can be difficult depending on the hardware palette. + +There are many ways to distribute the errors and many ways to scan the +image, but I will deal here with only a few. The two basic ways to scan the +image are with a normal left-to-right, top-to-bottom raster, or with an +alternating left-to-right then right-to-left raster. The latter method +generally produces fewer artifacts and can be used with all the error +diffusion patterns discussed below. + +The different ways of dividing up the error can be expressed as patterns +(called filters, for reasons too boring to go into here). + + X 7 This is the Floyd and Steinberg [4] + 3 5 1 error diffusion filter. + +In this filter, the X represents the pixel you are currently scanning, and +the numbers (called weights, for equally boring reasons) represent the +proportion of the error distributed to the pixel in that position. Here, the +pixel immediately to the right gets 7/16 of the error (the divisor is 16 +because the weights add to 16), the pixel directly below gets 5/16 of the +error, and the diagonally adjacent pixels get 3/16 and 1/16. When scanning a +line right-to-left, this pattern is reversed. This pattern was chosen +carefully so that it would produce a checkerboard pattern in areas with +intensity of 1/2 (or 128 in our image). It is also fairly easy to calculate +when the division by 16 is replaced by shifts. + +Another filter in common use, but not recommended: + + X 3 A simpler filter. + 3 2 + +This is often erroneously called the Floyd-Steinberg filter, but it does not +produce as good results. An alternating raster scan of the image is +necessary with this filter to reduce artifacts. Additional perturbations of +the formula are frequently necessary also. + +Burke [5] suggests the following filter: + + X 8 4 The Burke filter. + 2 4 8 4 2 + +Notice that this is just a simplification of the Stucki filter (below) with +the bottom row removed. The main improvement is that the divisor is now 32, +which makes calculating the errors faster, and the removal of one row +reduces the memory requirements of the method. + +This is also fairly easy to calculate and produces better results than Floyd +and Steinberg. Jarvis, Judice, and Ninke [6] use the following: + + X 7 5 The Jarvis, et al. pattern. + 3 5 7 5 3 + 1 3 5 3 1 + +The divisor here is 48, which is a little more expensive to calculate, and +the errors are distributed over three lines, requiring extra memory and time +for processing. Probably the best filter is from Stucki [7]: + + X 8 4 The Stucki pattern. + 2 4 8 4 2 + 1 2 4 2 1 + +This one takes a division by 42 for each pixel and is therefore slow if math +is done inside the loop. After the initial 8/42 is calculated, some time can +be saved by producing the remaining fractions by shifts. + +The speed advantages of the simpler filters can be eliminated somewhat by +performing the divisions beforehand and using lookup tables instead of per- +forming math inside the loop. This makes it harder to use various filters +in the same program, but the speed benefits are enormous. + +It is critical with all of these algorithms that when error values are added +to neighboring pixels, the values must be truncated to fit within the limits +of hardware, otherwise and area of very intense color may cause streaks into +an adjacent area of less intense color. This truncation adds noise to the +image anagous to clipping in an audio amplifier, but it is not nearly so +offensive as the streaking. It is mainly for this reason that the larger +filters work better--they split the errors up more finely and produce less of +this clipping noise. + +With all of these filters, it is also important to ensure that the errors +you distribute properly add to the original error value. This is easiest to +accomplish by subtracting each fraction from the whole error as it is +calculated, and using the final remainder as the last fraction. + +Some of these methods (particularly the simpler ones) can be greatly improved +by skewing the weights with a little randomness [3]. + +Calculating the "nearest available color" is trivial with a monochrome image; +with color images it requires more work. A table of RGB values of all +available colors must be scanned sequentially for each input pixel to find +the closest. The "distance" formula most often used is a simple pythagorean +"least squares". The difference for each color is squared, and the three +squares added to produce the distance value. This value is equivalent to the +square of the distance between the points in RGB-space. It is not necessary +to compute the square root of this value because we are not interested in the +actual distance, only in which is smallest. The square root function is a +monotonic increasing function and does not affect the order of its operands. +If the total number of colors with which you are dealing is small, this part +of the algorithm can be replaced by a lookup table as well. + +When your hardware allows you to select the available colors, very good +results can be achieved by selecting colors from the image itself. You must +reserve at least 8 colors for the primaries, secondaries, black, and white +for best results. If you do not know the colors in your image ahead of time, +or if you are going to use the same map to dither several different images, +you will have to fill your color map with a good range of colors. This can +be done either by assigning a certain number of bits to each primary and +computing all combinations, or by a smoother distribution as suggested by +Heckbert [8]. + + +======================================================================== +Sample code + +Despite my best efforts in expository writing, nothing explains an algorithm +better than real code. With that in mind, presented here below is an +algorithm (in somewhat incomplete, very inefficient pseudo-C) which +implements error diffusion dithering with the Floyd and Steinberg filter. It +is not efficiently coded, but its purpose is to show the method, which I +believe it does. + +/* Floyd/Steinberg error diffusion dithering algorithm in color. The array +** line[][] contains the RGB values for the current line being processed; +** line[0][x] = red, line[1][x] = green, line[2][x] = blue. It uses the +** external functions getline() and putdot(), whose pupose should be easy +** to see from the code. +*/ + +unsigned char line[3][WIDTH]; +unsigned char colormap[3][COLORS] = { + 0, 0, 0, /* Black This color map should be replaced */ + 255, 0, 0, /* Red by one available on your hardware. */ + 0, 255, 0, /* Green It may contain any number of colors */ + 0, 0, 255, /* Blue as long as the constant COLORS is */ + 255, 255, 0, /* Yellow set correctly. */ + 255, 0, 255, /* Magenta */ + 0, 255, 255, /* Cyan */ + 255, 255, 255 }; /* White */ + +int getline(); /* Function to read line[] from image file; */ + /* must return EOF when done. */ +putdot(int x, int y, int c); /* Plot dot of color c at location x, y. */ + +dither() +{ + static int ed[3][WIDTH] = {0}; /* Errors distributed down, i.e., */ + /* to the next line. */ + int x, y, h, c, nc, v, /* Working variables */ + e[4], /* Error parts (7/8,1/8,5/8,3/8). */ + ef[3]; /* Error distributed forward. */ + long dist, sdist; /* Used for least-squares match. */ + + for (x=0; x 255) v = 255; /* and clip. */ + line[c][x] = v; + } + + sdist = 255L * 255L * 255L + 1L; /* Compute the color */ + for (c=0; c> 1; /* half of v, e[1..4] */ + e[1] = (7 * h) >> 3; /* will be filled */ + e[2] = h - e[1]; /* with the Floyd and */ + h = v - h; /* Steinberg weights. */ + e[3] = (5 * h) >> 3; + e[4] = h = e[3]; + + ef[c] = e[1]; /* Distribute errors. */ + if (x < WIDTH-1) ed[c][x+1] = e[2]; + if (x == 0) ed[c][x] = e[3]; else ed[c][x] += e[3]; + if (x > 0) ed[c][x-1] += e[4]; + } + } /* next x */ + + ++y; + } /* next y */ +} + + +======================================================================== +Bibliography + +[1] Foley, J. D. and Andries Van Dam (1982) + Fundamentals of Interactive Computer Graphics. Reading, MA: Addisson + Wesley. + + This is a standard reference for many graphic techniques which has not + declined with age. Highly recommended. + +[2] Bayer, B. E. (1973) + "An Optimum Method for Two-Level Rendition of Continuous Tone Pictures," + IEEE International Conference on Communications, Conference Records, pp. + 26-11 to 26-15. + + A short article proving the optimality of Bayer's pattern in the + dispersed-dot ordered dither. + +[3] Ulichney, R. (1987) + Digital Halftoning. Cambridge, MA: The MIT Press. + + This is the best book I know of for describing the various black and + white dithering methods. It has clear explanations (a little higher math + may come in handy) and wonderful illustrations. It does not contain any + code, but don't let that keep you from getting this book. Computer + Literacy carries it but is often sold out. + +[4] Floyd, R.W. and L. Steinberg (1975) + "An Adaptive Algorithm for Spatial Gray Scale." SID International + Symposium Digest of Technical Papers, vol 1975m, pp. 36-37. + + Short article in which Floyd and Steinberg introduce their filter. + +[5] Daniel Burkes is unpublished, but can be reached at this address: + + Daniel Burkes + TerraVision Inc. + 2351 College Station Road Suite 563 + Athens, GA 30305 + + or via CompuServe's Graphics Support Forum, ID # 72077,356. + +[6] Jarvis, J. F., C. N. Judice, and W. H. Ninke (1976) + "A Survey of Techniques for the Display of Continuous Tone Pictures on + Bi-Level Displays." Computer Graphics and Image Processing, vol. 5, pp. + 13-40. + +[7] Stucki, P. (1981) + "MECCA - a multiple-error correcting computation algorithm for bilevel + image hardcopy reproduction." Research Report RZ1060, IBM Research + Laboratory, Zurich, Switzerland. + +[8] Heckbert, Paul (9182) + "Color Image Quantization for Frame Buffer Display." Computer Graphics + (SIGGRAPH 82), vol. 16, pp. 297-307. + + +======================================================================== +Other works of interest: + +Newman, William M., and Robert F. S. Sproull (1979) +Principles of Interactive Computer Graphics. 2nd edition. New York: +McGraw-Hill. + +Rogers, David F. (1985) +Procedural Elements for Computer Graphics. New York: McGraw-Hill. + +Rogers, David F., and J. A. Adams (1976) +Mathematical Elements for Computer Graphics. New York: McGraw-Hill. + + +======================================================================== +About CompuServe Graphics Support Forum: + +CompuServe Information Service is a service of the H&R Block companies +providing computer users with electronic mail, teleconferencing, and many +other telecommunications services. Call 800-848-8199 for more information. + +The Graphics Support Forum is dedicated to helping its users get the most out +of their computers' graphics capabilities. It has a small staff and a large +number of "Developers" who create images and software on all types of +machines from Apple IIs to Sun workstations. While on CompuServe, type GO +PICS from any "!" prompt to gain access to the forum. \ No newline at end of file From 593500e11b7764c1a43903e05a5a01cc31ce2e2e Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Mon, 13 Feb 2017 08:12:56 +0000 Subject: [PATCH 056/142] fix vars --- src/ImageSharp/Image/Image{TColor}.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Image/Image{TColor}.cs b/src/ImageSharp/Image/Image{TColor}.cs index ded3e376eb..75d855ad42 100644 --- a/src/ImageSharp/Image/Image{TColor}.cs +++ b/src/ImageSharp/Image/Image{TColor}.cs @@ -254,8 +254,8 @@ namespace ImageSharp /// The public Image Save(string filePath) { - var ext = Path.GetExtension(filePath).Trim('.'); - var format = this.Configuration.ImageFormats.SingleOrDefault(f => f.SupportedExtensions.Contains(ext, StringComparer.OrdinalIgnoreCase)); + string ext = Path.GetExtension(filePath).Trim('.'); + IImageFormat format = this.Configuration.ImageFormats.SingleOrDefault(f => f.SupportedExtensions.Contains(ext, StringComparer.OrdinalIgnoreCase)); if (format == null) { throw new InvalidOperationException($"No image formats have been registered for the file extension '{ext}'."); @@ -274,7 +274,7 @@ namespace ImageSharp public Image Save(string filePath, IImageFormat format) { Guard.NotNull(format, nameof(format)); - using (var fs = File.Create(filePath)) + using (FileStream fs = File.Create(filePath)) { return this.Save(fs, format); } @@ -290,7 +290,7 @@ namespace ImageSharp public Image Save(string filePath, IImageEncoder encoder) { Guard.NotNull(encoder, nameof(encoder)); - using (var fs = File.Create(filePath)) + using (FileStream fs = File.Create(filePath)) { return this.Save(fs, encoder); } From 8e49417bc40c577f1550963223beb6b885c022f9 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 14 Feb 2017 09:27:00 +1100 Subject: [PATCH 057/142] Whitespace --- src/ImageSharp/Dithering/DHALF.TXT | 134 +++---- src/ImageSharp/Dithering/DITHER.TXT | 530 ++++++++++++++-------------- 2 files changed, 332 insertions(+), 332 deletions(-) diff --git a/src/ImageSharp/Dithering/DHALF.TXT b/src/ImageSharp/Dithering/DHALF.TXT index dce9ef9241..ec6ba3d6e4 100644 --- a/src/ImageSharp/Dithering/DHALF.TXT +++ b/src/ImageSharp/Dithering/DHALF.TXT @@ -12,10 +12,10 @@ What follows is everything you ever wanted to know (for the time being) about digital halftoning, or dithering. I'm sure it will be out of date as soon as it is released, but it does serve to collect data from a wide variety of sources into a single document, and should save you considerable -searching time. +searching time. Numbers in brackets (e.g. [4] or [12]) are references. A list of these -works appears at the end of this document. +works appears at the end of this document. Because this document describes ideas and algorithms which are constantly changing, I expect that it may have many editions, additions, and @@ -23,7 +23,7 @@ corrections before it gets to you. I will list my name below as original author, but I do not wish to deter others from adding their own thoughts and discoveries. This is not copyrighted in any way, and was created solely for the purpose of organizing my own knowledge on the subject, and sharing -this with others. Please distribute it to anyone who might be interested. +this with others. Please distribute it to anyone who might be interested. If you add anything to this document, please feel free to include your name below as a contributor or as a reference. I would particularly like to see @@ -31,7 +31,7 @@ additions to the "Other books of interest" section. Please keep the text in this simple format: no margins, no pagination, no lines longer than 79 characters, and no non-ASCII or non-printing characters other than a CR/LF pair at the end of each line. It is intended that this be read on as many -different machines as possible. +different machines as possible. Original Author: @@ -50,7 +50,7 @@ Contributors: COMMENTS BY MIKE MORRA I first entered the world of imaging in the fall of 1990 when my employer, -Epson America Inc., began shipping the ES-300C color flatbed scanner. +Epson America Inc., began shipping the ES-300C color flatbed scanner. Suddenly, here I was, a field systems analyst who had worked almost exclusively with printers and PCs, thrust into a new and arcane world of look-up tables and dithering and color reduction and .GIF files! I realized @@ -60,10 +60,10 @@ Graphics Support Forum on a very regular basis. Lee Crocker's excellent paper called DITHER.TXT was one of the first pieces of information that I came across, and it went a very long way toward -answering a lot of questions that I'd had about the subject of dithering. +answering a lot of questions that I'd had about the subject of dithering. It also provided me with the names of other essential reference works upon which Lee had based his paper, and I immediately began an eager search for -these other references. +these other references. In the course of my self-study, however, I found that DITHER.TXT does presume the reader's familiarity with some fundamental imaging concepts, @@ -90,7 +90,7 @@ second, distinct document. Too, I may very well have misconstrued or misinterpreted some factual information in my revision. As such, I welcome criticism and comment from all the original authors and contributors, and any readers, with the hope that their feedback will help me to address these -issues. +issues. If this revision it is received favorably, I will submit it to the public domain; if it is met with brickbats (for whatever reason), I will withdraw @@ -103,7 +103,7 @@ my questions that I needed. I'd like to publicly thank the whole Forum community in general for putting up with my unending barrage of questions and inquiries over the past few months . In particular, I would thank John Swenson, Chris Young, and (of course) Lee Crocker for their invaluable -assistance. +assistance. Mike Morra [76703,4051] June 20, 1991 @@ -118,7 +118,7 @@ digitized images on display devices which were incapable of reproducing the full spectrum of intensities or colors present in the source image. The challenge is even more pronounced in today's world of personal computing because of the technology gap between image generation and image rendering -equipment. +equipment. Today, we now have affordable 24-bit image scanners which can generate nearly true-to-life scans having as many as 256 shades of gray, or in excess @@ -127,7 +127,7 @@ behind with 16- and 256-color VGA/SVGA video monitors and printers with binary (black/white) "marking engines" as the norm. Without specialized techniques for color reduction -- the process of finding the "best fit" of the display device's available gray shades and/or colors -- the imaging -experimenter would be plagued with blotchy, noisy, off-color images. +experimenter would be plagued with blotchy, noisy, off-color images. (As of this writing, "true color" 24-bit video display devices, capable of reproducing all of the color/intensity information in the source image, are @@ -135,7 +135,7 @@ now beginning to migrate downward into the PC environment, but they exact a premium in cost and processor power which many users are loathe to pay. So- called "high-color" video displays -- typically 16-bit, with 32,768-color capability -- are moving into the mainstream, but color reduction techniques -would still be required with these devices.) +would still be required with these devices.) The science of digital halftoning (more commonly referred to as dithering, or spatial dithering) is one of the techniques used to achieve satisfactory @@ -146,7 +146,7 @@ full white pixels, or on printers which could produce only full black spots on a printed page. Indeed, Ulichney [3] gives a definition of digital halftoning as "... any algorithmic process which creates the illusion of continuous-tone images from the judicious arrangement of binary picture -elements." +elements." Ulichney's study, as well as the earlier literature on the subject (and this paper itself), discusses the process mostly in this context. Since we in @@ -164,8 +164,8 @@ range of colors or gray shades that are contained in the source image. Intensity/Color Resolution The concept of resolution is essential to the understanding of digital -halftoning. Resolution can be defined as "fineness" and is used to -describe the level of detail in a digitally sampled signal. +halftoning. Resolution can be defined as "fineness" and is used to +describe the level of detail in a digitally sampled signal. Typically, when we hear the term "resolution" applied to images, we think of what's known as "spatial resolution," which is the basic sampling rate for @@ -194,7 +194,7 @@ display device has a higher spatial resolution than the image you are trying to reproduce, it can show a very good image even if its color resolution is less. This is what most of us know as "dithering" and is the subject of this paper. (The other tradeoff, i.e., trading color resolution for spatial -resolution, is called "anti-aliasing," and is not discussed here.) +resolution, is called "anti-aliasing," and is not discussed here.) For the following discussions I will assume that we are given a grayscale @@ -205,7 +205,7 @@ printer, or an HP LaserJet laser printer. Most of these methods can be extended in obvious ways to deal with displays that have more than two levels (but still fewer than the source image), or to color images. Where such extension is not obvious, or where better results can be obtained, I -will go into more detail. +will go into more detail. ===================================== @@ -217,7 +217,7 @@ black and white device. This is accomplished by establishing a demarcation point, or threshold, at the 50% gray level. Each dot of the source image is compared against this threshold value: if it is darker than the value, the device plots it black, and if it's lighter, the device plots it white. - + What happens to the image during this operation? Well, some detail survives, but our perception of gray levels is completely gone. This means that a lot of the image content is obliterated. Take an area of the image @@ -281,16 +281,16 @@ categories: 3. Ordered dither 4. Error-diffusion halftoning -Each of these methods is generally better than those listed before it, but -other considerations such as processing time, memory constraints, etc. may -weigh in favor of one of the simpler methods. +Each of these methods is generally better than those listed before it, but +other considerations such as processing time, memory constraints, etc. may +weigh in favor of one of the simpler methods. To convert any of the first three methods into color, simply apply the -algorithm separately for each primary color and mix the resulting values. +algorithm separately for each primary color and mix the resulting values. This assumes that you have at least eight output colors: black, red, green, blue, cyan, magenta, yellow, and white. Though this will work for error diffusion as well, there are better methods which will be discussed in more -detail later. +detail later. ===================================== @@ -307,7 +307,7 @@ While it is not really acceptable as a production method, it is very simple to describe and implement. For each dot in our grayscale image, we generate a random number in the range 0 - 255: if the random number is greater than the image value at that dot, the display device plots the dot white; -otherwise, it plots it black. That's it. +otherwise, it plots it black. That's it. This generates a picture with a lot of "white noise", which looks like TV picture "snow". Although inaccurate and grainy, the image is free from @@ -317,7 +317,7 @@ more important than noise. For example, a whole screen containing a gradient of all levels from black to white would actually look best with a random dither. With this image, other digital halftoning algorithms would produce significant artifacts like diagonal patterns (in ordered dithering) -and clustering (in error diffusion halftones). +and clustering (in error diffusion halftones). I should mention, of course, that unless your computer has a hardware-based random number generator (and most don't), there may be some artifacts from @@ -399,7 +399,7 @@ like: because if they were repeated over a large area (a common occurrence in many -images [1]) they would create vertical, horizontal, or diagonal lines. +images [1]) they would create vertical, horizontal, or diagonal lines. Also, studies [1] have shown that the patterns should form a "growth sequence:" once a pixel is intensified for a particular value, it should remain intensified for all subsequent values. In this fashion, each pattern @@ -407,7 +407,7 @@ is a superset of the previous one; this similarity between adjacent intensity patterns minimizes any contouring artifacts. Here is a good pattern for a 3-by-3 matrix which subscribes to the rules set -forth above: +forth above: --- --- --- -X- -XX -XX -XX -XX XXX XXX @@ -427,7 +427,7 @@ greater than that of the image. Another limitation of patterning is that the effective spatial resolution is decreased, since a multiple-pixel "cell" is used to simulate the single, larger halftone dot. The more intensity resolution we want, the larger the -halftone cell used and, by extension, the lower the spatial resolution. +halftone cell used and, by extension, the lower the spatial resolution. In the above example, using 3 x 3 patterning, we are able to simulate 10 intensity levels (not a very good rendering) but we must reduce the spatial @@ -437,22 +437,22 @@ eight-fold decrease in spatial resolution. And to get the full 256 levels of intensity in our source image, we would need a 16 x 16 pattern and would incur a 16-fold reduction in spatial resolution. Because of this size distortion of the image, and with the development of more effective digital -halftoning methods, patterning is only infrequently used today. +halftoning methods, patterning is only infrequently used today. To extend this method to color images, we would use patterns of colored pixels to represent shades not directly printable by the hardware. For example, if your hardware is capable of printing only red, green, blue, and black (the minimal case for color dithering), other colors can be -represented with 2 x 2 patterns of these four: +represented with 2 x 2 patterns of these four: - Yellow = R G Cyan = G B Magenta = R B Gray = R G + Yellow = R G Cyan = G B Magenta = R B Gray = R G G R B G B R B K (B here represents blue, K is black). In this particular example, there are a total of 31 such distinct patterns which can be used; their enumeration is -left "as an exercise for the reader" (don't you hate books that do that?). +left "as an exercise for the reader" (don't you hate books that do that?). ===================================== @@ -503,13 +503,13 @@ PATTERN. Returning to our example of a 3 x 3 pattern, this means that we would be mapping NINE image dots into this pattern. The simplest way to do this in programming is to map the X and Y coordinates -of each image dot into the pixel (X mod 3, Y mod 3) in the pattern. +of each image dot into the pixel (X mod 3, Y mod 3) in the pattern. Returning to our two patterns (clustered and dispersed) as defined earlier, we can derive an effective mathematical algorithm that can be used to plot the correct pixel patterns. Because each of the patterns above is a superset of the previous, we can express the patterns in a compact array -form as the order of pixels added: +form as the order of pixels added: 8 3 4 1 7 4 @@ -534,7 +534,7 @@ allows) is preferred in order to decrease the graininess of the displayed images. Bayer [2] has shown that for matrices of orders which are powers of two there is an optimal pattern of dispersed dots which results in the pattern noise being as high-frequency as possible. The pattern for a 2x2 -and 4x4 matrices are as follows: +and 4x4 matrices are as follows: 1 3 1 9 3 11 These patterns (and their rotations @@ -548,7 +548,7 @@ patterns. (To fully reproduce our 256-level image, we would need to use an 8x8 pattern.) The Bayer ordered dither is in very common use and is easily identified by -the cross-hatch pattern artifacts it produces in the resulting display. +the cross-hatch pattern artifacts it produces in the resulting display. This artifacting is the major drawback of an otherwise powerful and very fast technique. @@ -616,7 +616,7 @@ we use for processing this point. If we are dithering our sample grayscale image for output to a black-and- white device, the "find closest intensity/color" operation is just a simple -thresholding (the closest intensity is going to be either black or white). +thresholding (the closest intensity is going to be either black or white). In color imaging -- for instance, color-reducing a 24-bit true color Targa file to an 8-bit, mapped GIF file -- this involves matching the input color to the closest available hardware color. Depending on how the display @@ -653,7 +653,7 @@ position. The expression in parentheses is the divisor used to break up the error weights. In the Floyd-Steinberg filter, each pixel "communicates" with 4 "neighbors." The pixel immediately to the right gets 7/16 of the error value, the pixel directly below gets 5/16 of the error, and the -diagonally adjacent pixels get 3/16 and 1/16. +diagonally adjacent pixels get 3/16 and 1/16. The weighting shown is for the traditional left-to-right scanning of the image. If the line were scanned right-to-left (more about this later), this @@ -683,11 +683,11 @@ The output from this filter is nowhere near as good as that from the real Floyd-Steinberg filter. There aren't enough weights to the dispersion, which means that the error value isn't distributed finely enough. With the entire image scanned left-to-right, the artifacting produced would be -totally unacceptable. +totally unacceptable. Much better results would be obtained by using an alternating, or serpentine, raster scan: processing the first line left-to-right, the next -line right-to-left, and so on (reversing the filter pattern appropriately). +line right-to-left, and so on (reversing the filter pattern appropriately). Serpentine scanning -- which can be used with any of the error-diffusion filters detailed here -- introduces an additional perturbation which contributes more randomness to the resultant halftone. Even with serpentine @@ -702,9 +702,9 @@ If the false Floyd-Steinberg filter fails because the error isn't distributed well enough, then it follows that a filter with a wider distribution would be better. This is exactly what Jarvis, Judice, and Ninke [6] did in 1976 with their filter: - - * 7 5 + + * 7 5 3 5 7 5 3 1 3 5 3 1 (1/48) @@ -723,7 +723,7 @@ requires extra memory and time for processing. The Stucki filter P. Stucki [7] offered a rework of the Jarvis, Judice, and Ninke filter in -1981: +1981: * 8 4 @@ -731,12 +731,12 @@ P. Stucki [7] offered a rework of the Jarvis, Judice, and Ninke filter in 1 2 4 2 1 (1/42) -Once again, division by 42 is quite slow to calculate (requiring DIVs). +Once again, division by 42 is quite slow to calculate (requiring DIVs). However, after the initial 8/42 is calculated, some time can be saved by producing the remaining fractions by shifts. The Stucki filter has been observed to give very clean, sharp output, which helps to offset the slow processing time. - + ===================================== The Burkes filter @@ -752,7 +752,7 @@ in 1988: Notice that this is just a simplification of the Stucki filter with the bottom row removed. The main improvement is that the divisor is now 32, which allows the error values to be calculated using shifts once more, and -the number of neighbors communicated with has been reduced to seven. +the number of neighbors communicated with has been reduced to seven. Furthermore, the removal of one row reduces the memory requirements of the filter by eliminating the second forward array which would otherwise be needed. @@ -807,9 +807,9 @@ would also include HP-compatible and PostScript desktop laser printers using Some displays may use "rectangular pixels," where the horizontal and vertical spacings are unequal. This would include various EGA and CGA video -modes and other specialized video displays, and most dot-matrix printers. +modes and other specialized video displays, and most dot-matrix printers. In many cases, the filters described earlier will do a decent job on -rectangular pixel grids, but an optimized filter would be preferred. +rectangular pixel grids, but an optimized filter would be preferred. Slinkman [10] describes one such filter for his 640 x 240 monochrome display with a 1:2 aspect ratio. @@ -832,7 +832,7 @@ be interested in further information. While technically not an error-diffusion filter, a method proposed by Gozum [11] offers color resolutions in excess of 256 colors by plotting red, green, and blue pixel "triplets" or triads to simulate an "interlaced" -television display (sacrificing some horizontal resolution in the process). +television display (sacrificing some horizontal resolution in the process). Again, I would refer interested readers to his document for more information. @@ -848,7 +848,7 @@ various filters in the same program, but the speed benefits are enormous. It is critical with all of these algorithms that when error values are added to neighboring pixels, the resultant summed values must be truncated to fit within the limits of hardware. Otherwise, an area of very intense color may -cause streaks into an adjacent area of less intense color. +cause streaks into an adjacent area of less intense color. This truncation is known as "clipping," and is analogous to the audio world's concept of the same name. As in the case of an audio amplifier, @@ -856,13 +856,13 @@ clipping adds undesired noise to the data. Unlike the audio world, however, the visual clipping performed in error-diffusion halftoning is acceptable since it is not nearly so offensive as the color streaking that would occur otherwise. It is mainly for this reason that the larger filters work better --- they split the errors up more finely and produce less clipping noise. +-- they split the errors up more finely and produce less clipping noise. With all of these filters, it is also important to ensure that the sum of the distributed error values is equal to the original error value. This is most easily accomplished by subtracting each fraction, as it is calculated, from the whole error value, and using the final remainder as the last -fraction. +fraction. ===================================== @@ -880,7 +880,7 @@ constantly-varying pattern). As you might imagine, any of these methods incur a penalty in processing time. Indeed, some of the above filters (particularly the simpler ones) can be -greatly improved by skewing the weights with a little randomness [3]. +greatly improved by skewing the weights with a little randomness [3]. ===================================== @@ -888,7 +888,7 @@ Nearest available color Calculating the nearest available intensity is trivial with a monochrome image; calculating the nearest available color in a color image requires -more work. +more work. A table of RGB values of all available colors must be scanned sequentially for each input pixel to find the closest. The "distance" formula most often @@ -896,7 +896,7 @@ used is a simple pythagorean "least squares". The difference for each color is squared, and the three squares added to produce the distance value. This value is equivalent to the square of the distance between the points in RGB- space. It is not necessary to compute the square root of this value because -we are not interested in the actual distance, only in which is smallest. +we are not interested in the actual distance, only in which is smallest. The square root function is a monotonic increasing function and does not affect the order of its operands. If the total number of colors with which you are dealing is small, this part of the algorithm can be replaced by a @@ -907,7 +907,7 @@ results can be achieved by selecting colors from the image itself. You must reserve at least 8 colors for the primaries, secondaries, black, and white for best results. If you do not know the colors in your image ahead of time, or if you are going to use the same map to dither several different -images, you will have to fill your color map with a good range of colors. +images, you will have to fill your color map with a good range of colors. This can be done either by assigning a certain number of bits to each primary and computing all combinations, or by a smoother distribution as suggested by Heckbert [8]. @@ -927,7 +927,7 @@ the "raw" scan is then already in a 1- or 2-bit/pixel format. While this feature would probably be unsuitable for cases where the image would need further processing (see the "Loss of image information" section below), it is very useful where the operator wants to generate a final image, ready for -printing or displaying, with little or no subsequent processing. +printing or displaying, with little or no subsequent processing. As an example, the Epson ES-300C color scanner (and its European equivalent, the Epson GT-6000) offers three internal halftone modes. One is a standard @@ -953,12 +953,12 @@ needs to be rendered on a bilevel display device. In this situation, one would almost never want to store the dithered image. On the other hand, when color images are dithered for display on color -displays with a lower color resolution, the dithered images are more useful. +displays with a lower color resolution, the dithered images are more useful. In fact, the bulk of today's scanned-image GIF files which abound on electronic BBSs and information services are 8-bit (256 color), colormapped and dithered files created from 24-bit true-color scans. Only rarely are the 24-bit files exchanged, because of the huge amount of data contained in -them. +them. In some cases, these mapped GIF files may be further processed with special paint/processing utilities, with very respectable results. However, the @@ -1214,10 +1214,10 @@ Bibliography [2] Bayer, B.E., "An Optimum Method for Two-Level Rendition of Continuous Tone Pictures," IEEE International Conference on Communications, - Conference Records, 1973, pp. 26-11 to 26-15. + Conference Records, 1973, pp. 26-11 to 26-15. A short article proving the optimality of Bayer's pattern in the - dispersed-dot ordered dither. + dispersed-dot ordered dither. [3] Ulichney, R., Digital Halftoning, The MIT Press, Cambridge, MA, 1987. @@ -1226,7 +1226,7 @@ Bibliography higher math may come in handy) and wonderful illustrations. It does not contain any code, but don't let that keep you from getting this book. Computer Literacy normally carries it but the - title is often sold out. + title is often sold out. [MFM note: I can't describe how much information I got from this book! Several different writers have praised this reference to @@ -1242,8 +1242,8 @@ Bibliography Scale." SID 1975, International Symposium Digest of Technical Papers, vol 1975m, pp. 36-37. - Short article in which Floyd and Steinberg introduce their filter. - + Short article in which Floyd and Steinberg introduce their filter. + [5] Daniel Burkes is unpublished, but can be reached at this address: Daniel Burkes @@ -1266,7 +1266,7 @@ Bibliography for bilevel image hardcopy reproduction." Research Report RZ1060, IBM Research Laboratory, Zurich, Switzerland, 1981. -[8] Heckbert, P. "Color Image Quantization for Frame Buffer Display." +[8] Heckbert, P. "Color Image Quantization for Frame Buffer Display." Computer Graphics (SIGGRAPH 82), vol. 16, pp. 297-307, 1982. [9] Frankie Sierra is unpublished, but can be reached via CIS at UID# @@ -1317,13 +1317,13 @@ York, 1985. Rogers, D.F. and J. A. Adams, Mathematical Elements for Computer Graphics, McGraw-Hill, New York, 1976. - A good detailed discussion of producing graphic images on a computer. + A good detailed discussion of producing graphic images on a computer. Plenty of sample code. Kuto, S., "Continuous Color Presentation Using a Low-Cost Ink Jet Printer," Proc. Computer Graphics Tokyo 84, 24-27 April, 1984, Tokyo, Japan. -Mitchell, W.J., R.S. Liggett, and T. Kvan, The Art of Computer Graphics +Mitchell, W.J., R.S. Liggett, and T. Kvan, The Art of Computer Graphics Programming, Van Nostrand Reinhold Co., New York, 1987. Pavlidis, T., Algorithms for Graphics and Image Processing, Computer Science diff --git a/src/ImageSharp/Dithering/DITHER.TXT b/src/ImageSharp/Dithering/DITHER.TXT index 4d29a533e3..1f49fd6eb1 100644 --- a/src/ImageSharp/Dithering/DITHER.TXT +++ b/src/ImageSharp/Dithering/DITHER.TXT @@ -1,28 +1,28 @@ DITHER.TXT -What follows is everything you ever wanted to know (for the time being) about -dithering. I'm sure it will be out of date as soon as it is released, but it -does serve to collect data from a wide variety of sources into a single -document, and should save you considerable searching time. - -Numbers in brackets (like this [0]) are references. A list of these works -appears at the end of this document. - -Because this document describes ideas and algorithms which are constantly -changing, I expect that it may have many editions, additions, and corrections -before it gets to you. I will list my name below as original author, but I -do not wish to deter others from adding their own thoughts and discoveries. -This is not copyrighted in any way, and was created solely for the purpose of -organizing my own knowledge on the subject, and sharing this with others. -Please distribute it to anyonw who might be interested. - -If you add anything to this document, please feel free to include your name -below as a contributor or as a reference. I would particularly like to see -additions to the "Other books of interest" section. Please keep the text in -this simple format: no margins, no pagination, no lines longer that 79 -characters, and no non-ASCII or non-printing characters other than a CR/LF -pair at the end of each line. It is intended that this be read on as many -different machines as possible. +What follows is everything you ever wanted to know (for the time being) about +dithering. I'm sure it will be out of date as soon as it is released, but it +does serve to collect data from a wide variety of sources into a single +document, and should save you considerable searching time. + +Numbers in brackets (like this [0]) are references. A list of these works +appears at the end of this document. + +Because this document describes ideas and algorithms which are constantly +changing, I expect that it may have many editions, additions, and corrections +before it gets to you. I will list my name below as original author, but I +do not wish to deter others from adding their own thoughts and discoveries. +This is not copyrighted in any way, and was created solely for the purpose of +organizing my own knowledge on the subject, and sharing this with others. +Please distribute it to anyonw who might be interested. + +If you add anything to this document, please feel free to include your name +below as a contributor or as a reference. I would particularly like to see +additions to the "Other books of interest" section. Please keep the text in +this simple format: no margins, no pagination, no lines longer that 79 +characters, and no non-ASCII or non-printing characters other than a CR/LF +pair at the end of each line. It is intended that this be read on as many +different machines as possible. Original Author: @@ -35,37 +35,37 @@ Contributors: ======================================================================== What is Dithering? -Dithering, also called Halftoning or Color Reduction, is the process of -rendering an image on a display device with fewer colors than are in the -image. The number of different colors in an image or on a device I will call -its Color Resolution. The term "resolution" means "fineness" and is used to -describe the level of detail in a digitally sampled signal. It is used most -often in referring to the Spatial Resolution, which is the basic sampling -rate for a digitized image. - -Spatial resolution describes the fineness of the "dots" used in an image. -Color resolution describes the fineness of detail available at each dot. The -higher the resolution of a digital sample, the better it can reproduce high -frequency detail. A compact disc, for example, has a temporal (time) -resolution of 44,000 samples per second, and a dynamic (volume) resolution of -16 bits (0..65535). It can therefore reproduce sounds with a vast dynamic -range (from barely audible to ear-splitting) with great detail, but it has -problems with very high-frequency sounds, like violins and piccolos. - -It is often possible to "trade" one kind of resolution for another. If your -display device has a higher spatial resolution than the image you are trying -to reproduce, it can show a very good image even if its color resolution is -less. This is what we will call "dithering" and is the subject of this -paper. The other tradeoff, i.e., trading color resolution for spatial -resolution, is called "anti-aliasing" and is not discussed here. - -It is important to emphasize here that dithering is a one-way operation. -Once an image has been dithered, although it may look like a good -reproduction of the original, information is permanently lost. Many image -processing functions fail on dithered images. For these reasons, dithering -must be considered only as a way to produce an image on hardware that would -otherwise be incapable of displaying it. The data representing an image -should always be kept in full detail. +Dithering, also called Halftoning or Color Reduction, is the process of +rendering an image on a display device with fewer colors than are in the +image. The number of different colors in an image or on a device I will call +its Color Resolution. The term "resolution" means "fineness" and is used to +describe the level of detail in a digitally sampled signal. It is used most +often in referring to the Spatial Resolution, which is the basic sampling +rate for a digitized image. + +Spatial resolution describes the fineness of the "dots" used in an image. +Color resolution describes the fineness of detail available at each dot. The +higher the resolution of a digital sample, the better it can reproduce high +frequency detail. A compact disc, for example, has a temporal (time) +resolution of 44,000 samples per second, and a dynamic (volume) resolution of +16 bits (0..65535). It can therefore reproduce sounds with a vast dynamic +range (from barely audible to ear-splitting) with great detail, but it has +problems with very high-frequency sounds, like violins and piccolos. + +It is often possible to "trade" one kind of resolution for another. If your +display device has a higher spatial resolution than the image you are trying +to reproduce, it can show a very good image even if its color resolution is +less. This is what we will call "dithering" and is the subject of this +paper. The other tradeoff, i.e., trading color resolution for spatial +resolution, is called "anti-aliasing" and is not discussed here. + +It is important to emphasize here that dithering is a one-way operation. +Once an image has been dithered, although it may look like a good +reproduction of the original, information is permanently lost. Many image +processing functions fail on dithered images. For these reasons, dithering +must be considered only as a way to produce an image on hardware that would +otherwise be incapable of displaying it. The data representing an image +should always be kept in full detail. ======================================================================== @@ -78,84 +78,84 @@ The classes of dithering algorithms we will discuss here are these: 3. Ordered 4. Error dispersion -Each of these methods is generally better than those listed before it, but -other considerations such as processing time, memory constraints, etc. may -weigh in favor of one of the simpler methods. +Each of these methods is generally better than those listed before it, but +other considerations such as processing time, memory constraints, etc. may +weigh in favor of one of the simpler methods. -For the following discussions I will assume that we are given an image with -256 shades of gray (0=black..255=white) that we are trying to reproduce on a -black and white ouput device. Most of these methods can be extended in -obvious ways to deal with displays that have more than two levels but fewer -than the image, or to color images. Where such extension is not obvious, or -where better results can be obtained, I will go into more detail. +For the following discussions I will assume that we are given an image with +256 shades of gray (0=black..255=white) that we are trying to reproduce on a +black and white ouput device. Most of these methods can be extended in +obvious ways to deal with displays that have more than two levels but fewer +than the image, or to color images. Where such extension is not obvious, or +where better results can be obtained, I will go into more detail. -To convert any of the first three methods into color, simply apply the -algorithm separately for each primary and mix the resulting values. This -assumes that you have at least eight output colors: black, red, green, blue, -cyan, magenta, yellow, and white. Though this will work for error dispersion -as well, there are better methods in this case. +To convert any of the first three methods into color, simply apply the +algorithm separately for each primary and mix the resulting values. This +assumes that you have at least eight output colors: black, red, green, blue, +cyan, magenta, yellow, and white. Though this will work for error dispersion +as well, there are better methods in this case. ======================================================================== Random dither -This is the bubblesort of dithering algorithms. It is not really acceptable -as a production method, but it is very simple to describe and implement. For -each value in the image, simply generate a random number 1..256; if it is -geater than the image value at that point, plot the point white, otherwise -plot it black. That's it. This generates a picture with a lot of "white -noise", which looks like TV picture "snow". Though the image produced is -very inaccurate and noisy, it is free from "artifacts" which are phenomena -produced by digital signal processing. +This is the bubblesort of dithering algorithms. It is not really acceptable +as a production method, but it is very simple to describe and implement. For +each value in the image, simply generate a random number 1..256; if it is +geater than the image value at that point, plot the point white, otherwise +plot it black. That's it. This generates a picture with a lot of "white +noise", which looks like TV picture "snow". Though the image produced is +very inaccurate and noisy, it is free from "artifacts" which are phenomena +produced by digital signal processing. The most common type of artifact is the Moire pattern (Contributors: please resist the urge to put an accent on the "e", as no portable character set -exists for this). If you draw several lines close together radiating from a +exists for this). If you draw several lines close together radiating from a single point on a computer display, you will see what appear to be flower- -like patterns. These patterns are not part of the original idea of lines, -but are an illusion produced by the jaggedness of the display. +like patterns. These patterns are not part of the original idea of lines, +but are an illusion produced by the jaggedness of the display. -Many techniques exist for the reduction of digital artifacts like these, most -of which involve using a little randomness to "perturb" a regular algorithm a -little. Random dither obviously takes this to extreme. +Many techniques exist for the reduction of digital artifacts like these, most +of which involve using a little randomness to "perturb" a regular algorithm a +little. Random dither obviously takes this to extreme. -I should mention, of course, that unless your computer has a hardware-based -random number generator (and most don't) there may be some artifacts from the -random number generation algorithm itself. +I should mention, of course, that unless your computer has a hardware-based +random number generator (and most don't) there may be some artifacts from the +random number generation algorithm itself. -While random dither adds a lot of high-frequency noise to a picture, it is -useful in reproducing very low-frequency images where the absence of -artifacts is more important than noise. For example, a whole screen -containing a gradient of all levels from black to white would actually look -best with a random dither. In this case, ordered dithering would produce -diagonal patterns, and error dispersion would produce clustering. +While random dither adds a lot of high-frequency noise to a picture, it is +useful in reproducing very low-frequency images where the absence of +artifacts is more important than noise. For example, a whole screen +containing a gradient of all levels from black to white would actually look +best with a random dither. In this case, ordered dithering would produce +diagonal patterns, and error dispersion would produce clustering. -For efficiency, you can take the random number generator "out of the loop" by -generating a list of random numbers beforehand for use in the dither. Make -sure that the list is larger than the number of pixels in the image or you -may get artifacts from the reuse of numbers. The worst case would be if the -size of your list of random numbers is a multiple or near-multiple of the -horizontal size of the image, in which case unwanted vertical or diagonal -lines will appear. +For efficiency, you can take the random number generator "out of the loop" by +generating a list of random numbers beforehand for use in the dither. Make +sure that the list is larger than the number of pixels in the image or you +may get artifacts from the reuse of numbers. The worst case would be if the +size of your list of random numbers is a multiple or near-multiple of the +horizontal size of the image, in which case unwanted vertical or diagonal +lines will appear. ======================================================================== Pattern dither -This is also a simple concept, but much more effective than random dither. -For each possible value in the image, create a pattern of dots that -approximates that value. For instance, a 3-by-3 block of dots can have one -of 512 patterns, but for our purposes, there are only 10; the number of black -dots in the pattern determines the darkness of the pattern. +This is also a simple concept, but much more effective than random dither. +For each possible value in the image, create a pattern of dots that +approximates that value. For instance, a 3-by-3 block of dots can have one +of 512 patterns, but for our purposes, there are only 10; the number of black +dots in the pattern determines the darkness of the pattern. Which 10 patterns do we choose? Obviously, we need the all-white and all- -black patterns. We can eliminate those patterns which would create vertical -or horizontal lines if repeated over a large area because many images have -such regions of similar value [1]. It has been shown [1] that patterns for -adjacent colors should be similar to reduce an artifact called "contouring", -or visible edges between regions of adjacent values. One easy way to assure -this is to make each pattern a superset of the previous. Here are two good -sets of patterns for a 3-by-3 matrix: +black patterns. We can eliminate those patterns which would create vertical +or horizontal lines if repeated over a large area because many images have +such regions of similar value [1]. It has been shown [1] that patterns for +adjacent colors should be similar to reduce an artifact called "contouring", +or visible edges between regions of adjacent values. One easy way to assure +this is to make each pattern a superset of the previous. Here are two good +sets of patterns for a 3-by-3 matrix: --- --- --- -X- -XX -XX -XX -XX XXX XXX --- -X- -XX -XX -XX -XX XXX XXX XXX XXX @@ -165,102 +165,102 @@ or --- --- --- --X --X X-X X-X X-X XXX XXX --- --- -X- -X- -X- -X- XX- XX- XX- XXX -The first set of patterns above are "clustered" in that as new dots are added -to each pattern, they are added next to dots already there. The second set -is "dispersed" as the dots are spread out more. This distinction is more -important on larger patterns. Dispersed-dot patterns produce less grainy -images, but require that the output device render each dot distinctly. When -this is not the case, as with a printing press which smears the dots a -little, clustered patterns are better. - -For each pixel in the image we now print the pattern which is closest to its -value. This will triple the size of the image in each direction, so this -method can only be used where the display spatial resolution is much greater -than that of the image. - -We can exploit the fact that most images have large areas of similar value to -reduce our need for extra spatial resolution. Instead of plotting a whole -pattern for each pixel, map each pixel in the image to a dot in the pattern -an only plot the corresponding dot for each pixel. - -The simplest way to do this is to map the X and Y coordinates of each pixel -into the dot (X mod 3, Y mod 3) in the pattern. Large areas of constant -value will come out as repetitions of the pattern as before. - -To extend this method to color images, we must use patterns of colored dots -to represent shades not directly printable by the hardware. For example, if -your hardware is capable of printing only red, green, blue, and black (the -minimal case for color dithering), other colors can be represented with -patterns of these four: +The first set of patterns above are "clustered" in that as new dots are added +to each pattern, they are added next to dots already there. The second set +is "dispersed" as the dots are spread out more. This distinction is more +important on larger patterns. Dispersed-dot patterns produce less grainy +images, but require that the output device render each dot distinctly. When +this is not the case, as with a printing press which smears the dots a +little, clustered patterns are better. + +For each pixel in the image we now print the pattern which is closest to its +value. This will triple the size of the image in each direction, so this +method can only be used where the display spatial resolution is much greater +than that of the image. + +We can exploit the fact that most images have large areas of similar value to +reduce our need for extra spatial resolution. Instead of plotting a whole +pattern for each pixel, map each pixel in the image to a dot in the pattern +an only plot the corresponding dot for each pixel. + +The simplest way to do this is to map the X and Y coordinates of each pixel +into the dot (X mod 3, Y mod 3) in the pattern. Large areas of constant +value will come out as repetitions of the pattern as before. + +To extend this method to color images, we must use patterns of colored dots +to represent shades not directly printable by the hardware. For example, if +your hardware is capable of printing only red, green, blue, and black (the +minimal case for color dithering), other colors can be represented with +patterns of these four: Yellow = R G Cyan = G B Magenta = R B Gray = R G G R B G B R B K -(B here represents blue, K is black). There are a total of 31 such distinct -patterns which can be used; I will leave their enumeration "as an exercise -for the reader" (don't you hate books that do that?). +(B here represents blue, K is black). There are a total of 31 such distinct +patterns which can be used; I will leave their enumeration "as an exercise +for the reader" (don't you hate books that do that?). ======================================================================== Ordered dither -Because each of the patterns above is a superset of the previous, we can -express the patterns in compact form as the order of dots added: +Because each of the patterns above is a superset of the previous, we can +express the patterns in compact form as the order of dots added: 8 3 4 and 1 7 4 6 1 2 5 8 3 7 5 9 6 2 9 -Then we can simply use the value in the array as a threshhold. If the value -of the pixel (scaled into the 0-9 range) is less than the number in the -corresponding cell of the matrix, plot that pixel black, otherwise, plot it -white. This process is called ordered dither. As before, clustered patterns -should be used for devices which blur dots. In fact, the clustered pattern -ordered dither is the process used by most newspapers, and the term -halftoning refers to this method if not otherwise qualified. +Then we can simply use the value in the array as a threshhold. If the value +of the pixel (scaled into the 0-9 range) is less than the number in the +corresponding cell of the matrix, plot that pixel black, otherwise, plot it +white. This process is called ordered dither. As before, clustered patterns +should be used for devices which blur dots. In fact, the clustered pattern +ordered dither is the process used by most newspapers, and the term +halftoning refers to this method if not otherwise qualified. -Bayer [2] has shown that for matrices of orders which are powers of two there -is an optimal pattern of dispersed dots which results in the pattern noise -being as high-frequency as possible. The pattern for a 2x2 and 4x4 matrices -are as follows: +Bayer [2] has shown that for matrices of orders which are powers of two there +is an optimal pattern of dispersed dots which results in the pattern noise +being as high-frequency as possible. The pattern for a 2x2 and 4x4 matrices +are as follows: 1 3 1 9 3 11 These patterns (and their rotations 4 2 13 5 15 7 and reflections) are optimal for a 4 12 2 10 dispersed-pattern ordered dither. 16 8 14 6 -Ulichney [3] shows a recursive technique can be used to generate the larger -patterns. To fully reproduce our 256-level image, we would need to use the -8x8 pattern. +Ulichney [3] shows a recursive technique can be used to generate the larger +patterns. To fully reproduce our 256-level image, we would need to use the +8x8 pattern. Bayer's method is in very common use and is easily identified by the cross- -hatch pattern artifacts it produces in the resulting display. This -artifacting is the major drawback of the technique wich is otherwise very -fast and powerful. Ordered dithering also performs very badly on images -which have already been dithered to some extent. As stated earlier, -dithering should be the last stage in producing a physical display from a -digitally stored image. The dithered image should never be stored itself. +hatch pattern artifacts it produces in the resulting display. This +artifacting is the major drawback of the technique wich is otherwise very +fast and powerful. Ordered dithering also performs very badly on images +which have already been dithered to some extent. As stated earlier, +dithering should be the last stage in producing a physical display from a +digitally stored image. The dithered image should never be stored itself. ======================================================================== Error dispersion -This technique generates the best results of any method here, and is -naturally the slowest. In fact, there are many variants of this technique as -well, and the better they get, the slower they are. +This technique generates the best results of any method here, and is +naturally the slowest. In fact, there are many variants of this technique as +well, and the better they get, the slower they are. -Error dispersion is very simple to describe: for each point in the image, -first find the closest color available. Calculate the difference between the -value in the image and the color you have. Now divide up these error values -and distribute them over the neighboring pixels which you have not visited -yet. When you get to these later pixels, just add the errors distributed -from the earlier ones, clip the values to the allowed range if needed, then -continue as above. +Error dispersion is very simple to describe: for each point in the image, +first find the closest color available. Calculate the difference between the +value in the image and the color you have. Now divide up these error values +and distribute them over the neighboring pixels which you have not visited +yet. When you get to these later pixels, just add the errors distributed +from the earlier ones, clip the values to the allowed range if needed, then +continue as above. -If you are dithering a grayscale image for output to a black-and-white -device, the "find closest color" is just a simle threshholding operation. In -color, it involves matching the input color to the closest available hardware -color, which can be difficult depending on the hardware palette. +If you are dithering a grayscale image for output to a black-and-white +device, the "find closest color" is just a simle threshholding operation. In +color, it involves matching the input color to the closest available hardware +color, which can be difficult depending on the hardware palette. There are many ways to distribute the errors and many ways to scan the image, but I will deal here with only a few. The two basic ways to scan the @@ -269,22 +269,22 @@ alternating left-to-right then right-to-left raster. The latter method generally produces fewer artifacts and can be used with all the error diffusion patterns discussed below. -The different ways of dividing up the error can be expressed as patterns +The different ways of dividing up the error can be expressed as patterns (called filters, for reasons too boring to go into here). X 7 This is the Floyd and Steinberg [4] 3 5 1 error diffusion filter. In this filter, the X represents the pixel you are currently scanning, and -the numbers (called weights, for equally boring reasons) represent the -proportion of the error distributed to the pixel in that position. Here, the -pixel immediately to the right gets 7/16 of the error (the divisor is 16 -because the weights add to 16), the pixel directly below gets 5/16 of the -error, and the diagonally adjacent pixels get 3/16 and 1/16. When scanning a -line right-to-left, this pattern is reversed. This pattern was chosen -carefully so that it would produce a checkerboard pattern in areas with -intensity of 1/2 (or 128 in our image). It is also fairly easy to calculate -when the division by 16 is replaced by shifts. +the numbers (called weights, for equally boring reasons) represent the +proportion of the error distributed to the pixel in that position. Here, the +pixel immediately to the right gets 7/16 of the error (the divisor is 16 +because the weights add to 16), the pixel directly below gets 5/16 of the +error, and the diagonally adjacent pixels get 3/16 and 1/16. When scanning a +line right-to-left, this pattern is reversed. This pattern was chosen +carefully so that it would produce a checkerboard pattern in areas with +intensity of 1/2 (or 128 in our image). It is also fairly easy to calculate +when the division by 16 is replaced by shifts. Another filter in common use, but not recommended: @@ -306,24 +306,24 @@ the bottom row removed. The main improvement is that the divisor is now 32, which makes calculating the errors faster, and the removal of one row reduces the memory requirements of the method. -This is also fairly easy to calculate and produces better results than Floyd -and Steinberg. Jarvis, Judice, and Ninke [6] use the following: +This is also fairly easy to calculate and produces better results than Floyd +and Steinberg. Jarvis, Judice, and Ninke [6] use the following: X 7 5 The Jarvis, et al. pattern. 3 5 7 5 3 1 3 5 3 1 -The divisor here is 48, which is a little more expensive to calculate, and -the errors are distributed over three lines, requiring extra memory and time -for processing. Probably the best filter is from Stucki [7]: +The divisor here is 48, which is a little more expensive to calculate, and +the errors are distributed over three lines, requiring extra memory and time +for processing. Probably the best filter is from Stucki [7]: X 8 4 The Stucki pattern. 2 4 8 4 2 1 2 4 2 1 -This one takes a division by 42 for each pixel and is therefore slow if math -is done inside the loop. After the initial 8/42 is calculated, some time can -be saved by producing the remaining fractions by shifts. +This one takes a division by 42 for each pixel and is therefore slow if math +is done inside the loop. After the initial 8/42 is calculated, some time can +be saved by producing the remaining fractions by shifts. The speed advantages of the simpler filters can be eliminated somewhat by performing the divisions beforehand and using lookup tables instead of per- @@ -334,52 +334,52 @@ It is critical with all of these algorithms that when error values are added to neighboring pixels, the values must be truncated to fit within the limits of hardware, otherwise and area of very intense color may cause streaks into an adjacent area of less intense color. This truncation adds noise to the -image anagous to clipping in an audio amplifier, but it is not nearly so -offensive as the streaking. It is mainly for this reason that the larger -filters work better--they split the errors up more finely and produce less of -this clipping noise. +image anagous to clipping in an audio amplifier, but it is not nearly so +offensive as the streaking. It is mainly for this reason that the larger +filters work better--they split the errors up more finely and produce less of +this clipping noise. With all of these filters, it is also important to ensure that the errors -you distribute properly add to the original error value. This is easiest to -accomplish by subtracting each fraction from the whole error as it is -calculated, and using the final remainder as the last fraction. - -Some of these methods (particularly the simpler ones) can be greatly improved -by skewing the weights with a little randomness [3]. - -Calculating the "nearest available color" is trivial with a monochrome image; -with color images it requires more work. A table of RGB values of all -available colors must be scanned sequentially for each input pixel to find -the closest. The "distance" formula most often used is a simple pythagorean -"least squares". The difference for each color is squared, and the three -squares added to produce the distance value. This value is equivalent to the -square of the distance between the points in RGB-space. It is not necessary -to compute the square root of this value because we are not interested in the -actual distance, only in which is smallest. The square root function is a -monotonic increasing function and does not affect the order of its operands. -If the total number of colors with which you are dealing is small, this part +you distribute properly add to the original error value. This is easiest to +accomplish by subtracting each fraction from the whole error as it is +calculated, and using the final remainder as the last fraction. + +Some of these methods (particularly the simpler ones) can be greatly improved +by skewing the weights with a little randomness [3]. + +Calculating the "nearest available color" is trivial with a monochrome image; +with color images it requires more work. A table of RGB values of all +available colors must be scanned sequentially for each input pixel to find +the closest. The "distance" formula most often used is a simple pythagorean +"least squares". The difference for each color is squared, and the three +squares added to produce the distance value. This value is equivalent to the +square of the distance between the points in RGB-space. It is not necessary +to compute the square root of this value because we are not interested in the +actual distance, only in which is smallest. The square root function is a +monotonic increasing function and does not affect the order of its operands. +If the total number of colors with which you are dealing is small, this part of the algorithm can be replaced by a lookup table as well. -When your hardware allows you to select the available colors, very good -results can be achieved by selecting colors from the image itself. You must -reserve at least 8 colors for the primaries, secondaries, black, and white -for best results. If you do not know the colors in your image ahead of time, -or if you are going to use the same map to dither several different images, -you will have to fill your color map with a good range of colors. This can -be done either by assigning a certain number of bits to each primary and -computing all combinations, or by a smoother distribution as suggested by -Heckbert [8]. +When your hardware allows you to select the available colors, very good +results can be achieved by selecting colors from the image itself. You must +reserve at least 8 colors for the primaries, secondaries, black, and white +for best results. If you do not know the colors in your image ahead of time, +or if you are going to use the same map to dither several different images, +you will have to fill your color map with a good range of colors. This can +be done either by assigning a certain number of bits to each primary and +computing all combinations, or by a smoother distribution as suggested by +Heckbert [8]. ======================================================================== Sample code -Despite my best efforts in expository writing, nothing explains an algorithm -better than real code. With that in mind, presented here below is an -algorithm (in somewhat incomplete, very inefficient pseudo-C) which -implements error diffusion dithering with the Floyd and Steinberg filter. It -is not efficiently coded, but its purpose is to show the method, which I -believe it does. +Despite my best efforts in expository writing, nothing explains an algorithm +better than real code. With that in mind, presented here below is an +algorithm (in somewhat incomplete, very inefficient pseudo-C) which +implements error diffusion dithering with the Floyd and Steinberg filter. It +is not efficiently coded, but its purpose is to show the method, which I +believe it does. /* Floyd/Steinberg error diffusion dithering algorithm in color. The array ** line[][] contains the RGB values for the current line being processed; @@ -466,34 +466,34 @@ dither() Bibliography [1] Foley, J. D. and Andries Van Dam (1982) - Fundamentals of Interactive Computer Graphics. Reading, MA: Addisson - Wesley. + Fundamentals of Interactive Computer Graphics. Reading, MA: Addisson + Wesley. - This is a standard reference for many graphic techniques which has not - declined with age. Highly recommended. + This is a standard reference for many graphic techniques which has not + declined with age. Highly recommended. [2] Bayer, B. E. (1973) - "An Optimum Method for Two-Level Rendition of Continuous Tone Pictures," - IEEE International Conference on Communications, Conference Records, pp. - 26-11 to 26-15. + "An Optimum Method for Two-Level Rendition of Continuous Tone Pictures," + IEEE International Conference on Communications, Conference Records, pp. + 26-11 to 26-15. - A short article proving the optimality of Bayer's pattern in the - dispersed-dot ordered dither. + A short article proving the optimality of Bayer's pattern in the + dispersed-dot ordered dither. [3] Ulichney, R. (1987) Digital Halftoning. Cambridge, MA: The MIT Press. - This is the best book I know of for describing the various black and - white dithering methods. It has clear explanations (a little higher math - may come in handy) and wonderful illustrations. It does not contain any - code, but don't let that keep you from getting this book. Computer - Literacy carries it but is often sold out. + This is the best book I know of for describing the various black and + white dithering methods. It has clear explanations (a little higher math + may come in handy) and wonderful illustrations. It does not contain any + code, but don't let that keep you from getting this book. Computer + Literacy carries it but is often sold out. [4] Floyd, R.W. and L. Steinberg (1975) - "An Adaptive Algorithm for Spatial Gray Scale." SID International - Symposium Digest of Technical Papers, vol 1975m, pp. 36-37. + "An Adaptive Algorithm for Spatial Gray Scale." SID International + Symposium Digest of Technical Papers, vol 1975m, pp. 36-37. - Short article in which Floyd and Steinberg introduce their filter. + Short article in which Floyd and Steinberg introduce their filter. [5] Daniel Burkes is unpublished, but can be reached at this address: @@ -502,21 +502,21 @@ Bibliography 2351 College Station Road Suite 563 Athens, GA 30305 - or via CompuServe's Graphics Support Forum, ID # 72077,356. + or via CompuServe's Graphics Support Forum, ID # 72077,356. [6] Jarvis, J. F., C. N. Judice, and W. H. Ninke (1976) - "A Survey of Techniques for the Display of Continuous Tone Pictures on - Bi-Level Displays." Computer Graphics and Image Processing, vol. 5, pp. - 13-40. + "A Survey of Techniques for the Display of Continuous Tone Pictures on + Bi-Level Displays." Computer Graphics and Image Processing, vol. 5, pp. + 13-40. [7] Stucki, P. (1981) - "MECCA - a multiple-error correcting computation algorithm for bilevel - image hardcopy reproduction." Research Report RZ1060, IBM Research - Laboratory, Zurich, Switzerland. + "MECCA - a multiple-error correcting computation algorithm for bilevel + image hardcopy reproduction." Research Report RZ1060, IBM Research + Laboratory, Zurich, Switzerland. [8] Heckbert, Paul (9182) - "Color Image Quantization for Frame Buffer Display." Computer Graphics - (SIGGRAPH 82), vol. 16, pp. 297-307. + "Color Image Quantization for Frame Buffer Display." Computer Graphics + (SIGGRAPH 82), vol. 16, pp. 297-307. ======================================================================== @@ -536,12 +536,12 @@ Mathematical Elements for Computer Graphics. New York: McGraw-Hill. ======================================================================== About CompuServe Graphics Support Forum: -CompuServe Information Service is a service of the H&R Block companies -providing computer users with electronic mail, teleconferencing, and many -other telecommunications services. Call 800-848-8199 for more information. +CompuServe Information Service is a service of the H&R Block companies +providing computer users with electronic mail, teleconferencing, and many +other telecommunications services. Call 800-848-8199 for more information. -The Graphics Support Forum is dedicated to helping its users get the most out -of their computers' graphics capabilities. It has a small staff and a large -number of "Developers" who create images and software on all types of -machines from Apple IIs to Sun workstations. While on CompuServe, type GO -PICS from any "!" prompt to gain access to the forum. \ No newline at end of file +The Graphics Support Forum is dedicated to helping its users get the most out +of their computers' graphics capabilities. It has a small staff and a large +number of "Developers" who create images and software on all types of +machines from Apple IIs to Sun workstations. While on CompuServe, type GO +PICS from any "!" prompt to gain access to the forum. \ No newline at end of file From a1c4882922961d46d40648f592876f6c4fcf0912 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 14 Feb 2017 09:53:12 +1100 Subject: [PATCH 058/142] Ignore chunk length of 1. Fix #103 TODO: Read libpng for matching behaviour --- src/ImageSharp.Formats.Png/PngDecoderCore.cs | 4 ++-- tests/ImageSharp.Tests/FileTestBase.cs | 14 +++++++------- tests/ImageSharp.Tests/TestImages.cs | 3 +++ .../TestImages/Formats/Png/chunklength1.png | Bin 0 -> 149591 bytes 4 files changed, 12 insertions(+), 9 deletions(-) create mode 100644 tests/ImageSharp.Tests/TestImages/Formats/Png/chunklength1.png diff --git a/src/ImageSharp.Formats.Png/PngDecoderCore.cs b/src/ImageSharp.Formats.Png/PngDecoderCore.cs index 3eaa8fde3f..9e871e987b 100644 --- a/src/ImageSharp.Formats.Png/PngDecoderCore.cs +++ b/src/ImageSharp.Formats.Png/PngDecoderCore.cs @@ -927,12 +927,12 @@ namespace ImageSharp.Formats private void ReadChunkLength(PngChunk chunk) { int numBytes = this.currentStream.Read(this.chunkLengthBuffer, 0, 4); - if (numBytes >= 1 && numBytes <= 3) + if (numBytes > 1 && numBytes <= 3) { throw new ImageFormatException("Image stream is not valid!"); } - if (numBytes <= 0) + if (numBytes <= 1) { chunk.Length = -1; return; diff --git a/tests/ImageSharp.Tests/FileTestBase.cs b/tests/ImageSharp.Tests/FileTestBase.cs index f7f4386aad..b147e97e88 100644 --- a/tests/ImageSharp.Tests/FileTestBase.cs +++ b/tests/ImageSharp.Tests/FileTestBase.cs @@ -6,7 +6,6 @@ namespace ImageSharp.Tests { using System.Collections.Generic; - using ImageSharp.Formats; /// /// The test base class for reading and writing to files. @@ -18,8 +17,8 @@ namespace ImageSharp.Tests /// protected static readonly List Files = new List { - TestFile.Create(TestImages.Jpeg.Baseline.Calliphora), - TestFile.Create(TestImages.Jpeg.Baseline.Turtle), + TestFile.Create(TestImages.Jpeg.Baseline.Calliphora), + // TestFile.Create(TestImages.Jpeg.Baseline.Turtle), // Perf: Enable for local testing only // TestFile.Create(TestImages.Jpeg.Baseline.Ycck), // Perf: Enable for local testing only // TestFile.Create(TestImages.Jpeg.Baseline.Cmyk), // Perf: Enable for local testing only // TestFile.Create(TestImages.Jpeg.Baseline.Floorplan), // Perf: Enable for local testing only @@ -28,10 +27,11 @@ namespace ImageSharp.Tests // TestFile.Create(TestImages.Jpeg.Progressive.Progress), // Perf: Enable for local testing only // 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.Car), // TestFile.Create(TestImages.Bmp.Neg_height), // Perf: Enable for local testing only - TestFile.Create(TestImages.Png.Splash), - TestFile.Create(TestImages.Png.Powerpoint), + TestFile.Create(TestImages.Png.Splash), + // TestFile.Create(TestImages.Png.ChunkLength), // Perf: Enable for local testing only + // TestFile.Create(TestImages.Png.Powerpoint), // Perf: Enable for local testing only // TestFile.Create(TestImages.Png.Blur), // Perf: Enable for local testing only // TestFile.Create(TestImages.Png.Indexed), // Perf: Enable for local testing only // TestFile.Create(TestImages.Png.SplashInterlaced), // Perf: Enable for local testing only @@ -44,7 +44,7 @@ namespace ImageSharp.Tests // TestFile.Create(TestImages.Png.FilterVar), // Perf: Enable for local testing only // 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), // Perf: Enable for local testing only + TestFile.Create(TestImages.Gif.Rings), // 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 cd365156a4..4924cc1dee 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -34,6 +34,9 @@ namespace ImageSharp.Tests // filter changing per scanline public const string FilterVar = "Png/filterVar.png"; + + // Chunk length of 1 by end marker + public const string ChunkLength = "Png/chunklength1.png"; } public static class Jpeg diff --git a/tests/ImageSharp.Tests/TestImages/Formats/Png/chunklength1.png b/tests/ImageSharp.Tests/TestImages/Formats/Png/chunklength1.png new file mode 100644 index 0000000000000000000000000000000000000000..40d85e1e8d622ea496e25e8ebd9b26e1b785d5c6 GIT binary patch literal 149591 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%0AIcQ*LxcGhEc@jBxV*o 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

IYRjhHwrlVk{b_Hmmd+JZg<`r~D%48Ff9Bw8 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 Date: Tue, 14 Feb 2017 08:40:04 +0000 Subject: [PATCH 059/142] change NO_FILE_IO to NETSTANDARD11 --- src/ImageSharp/Image.cs | 2 +- src/ImageSharp/Image/Image{TColor}.cs | 4 ++-- src/ImageSharp/project.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Image.cs b/src/ImageSharp/Image.cs index 4da9cac36e..284a7c29b7 100644 --- a/src/ImageSharp/Image.cs +++ b/src/ImageSharp/Image.cs @@ -44,7 +44,7 @@ namespace ImageSharp { } -#if !NO_FILE_IO +#if !NETSTANDARD11 ///

/// Initializes a new instance of the class. /// diff --git a/src/ImageSharp/Image/Image{TColor}.cs b/src/ImageSharp/Image/Image{TColor}.cs index 75d855ad42..0931a12da6 100644 --- a/src/ImageSharp/Image/Image{TColor}.cs +++ b/src/ImageSharp/Image/Image{TColor}.cs @@ -63,7 +63,7 @@ namespace ImageSharp this.Load(stream); } -#if !NO_FILE_IO +#if !NETSTANDARD11 /// /// Initializes a new instance of the class. /// @@ -245,7 +245,7 @@ namespace ImageSharp return this; } -#if !NO_FILE_IO +#if !NETSTANDARD11 /// /// Saves the image to the given stream using the currently loaded image format. /// diff --git a/src/ImageSharp/project.json b/src/ImageSharp/project.json index a5d1bb93f6..7b2dbed08d 100644 --- a/src/ImageSharp/project.json +++ b/src/ImageSharp/project.json @@ -69,7 +69,7 @@ }, "netstandard1.1": { "buildOptions": { - "define": [ "NO_FILE_IO" ] + "define": [ "NETSTANDARD11" ] }, "dependencies": { "System.Collections": "4.0.11", From 8912faacf2387b4064cffb8c7761d311452bf5c9 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Tue, 14 Feb 2017 09:43:56 +0000 Subject: [PATCH 060/142] correct define tag --- src/ImageSharp/Image.cs | 2 +- src/ImageSharp/Image/Image{TColor}.cs | 4 ++-- src/ImageSharp/project.json | 3 --- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/Image.cs b/src/ImageSharp/Image.cs index 284a7c29b7..bdb6b49a92 100644 --- a/src/ImageSharp/Image.cs +++ b/src/ImageSharp/Image.cs @@ -44,7 +44,7 @@ namespace ImageSharp { } -#if !NETSTANDARD11 +#if !NETSTANDARD1_1 /// /// Initializes a new instance of the class. /// diff --git a/src/ImageSharp/Image/Image{TColor}.cs b/src/ImageSharp/Image/Image{TColor}.cs index 0931a12da6..33d6a69655 100644 --- a/src/ImageSharp/Image/Image{TColor}.cs +++ b/src/ImageSharp/Image/Image{TColor}.cs @@ -63,7 +63,7 @@ namespace ImageSharp this.Load(stream); } -#if !NETSTANDARD11 +#if !NETSTANDARD1_1 /// /// Initializes a new instance of the class. /// @@ -245,7 +245,7 @@ namespace ImageSharp return this; } -#if !NETSTANDARD11 +#if !NETSTANDARD1_1 /// /// Saves the image to the given stream using the currently loaded image format. /// diff --git a/src/ImageSharp/project.json b/src/ImageSharp/project.json index 7b2dbed08d..639773377e 100644 --- a/src/ImageSharp/project.json +++ b/src/ImageSharp/project.json @@ -68,9 +68,6 @@ } }, "netstandard1.1": { - "buildOptions": { - "define": [ "NETSTANDARD11" ] - }, "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", From 04b1e6dece17f6613185b56dfac0e3abf56fc86b Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 15 Feb 2017 01:15:34 +1100 Subject: [PATCH 061/142] Add ordered dithering --- .../Binarization/Dither.cs | 36 +++++- .../ErrorDiffusionDitherProcessor.cs | 3 - .../Binarization/OrderedDitherProcessor.cs | 119 ++++++++++++++++++ src/ImageSharp/Colors/PackedPixel/Alpha8.cs | 2 +- .../{ => ErrorDiffusion}/Atkinson.cs | 0 .../Dithering/{ => ErrorDiffusion}/Burks.cs | 0 .../{ => ErrorDiffusion}/ErrorDiffuser.cs | 0 .../{ => ErrorDiffusion}/FloydSteinberg.cs | 0 .../{ => ErrorDiffusion}/IErrorDiffuser.cs | 0 .../{ => ErrorDiffusion}/JarvisJudiceNinke.cs | 0 .../Dithering/{ => ErrorDiffusion}/Sierra2.cs | 0 .../Dithering/{ => ErrorDiffusion}/Sierra3.cs | 0 .../{ => ErrorDiffusion}/SierraLite.cs | 0 .../Dithering/{ => ErrorDiffusion}/Stucki.cs | 0 src/ImageSharp/Dithering/Ordered/Bayer.cs | 38 ++++++ .../Dithering/Ordered/IOrderedDither.cs | 37 ++++++ src/ImageSharp/Dithering/Ordered/Ordered.cs | 38 ++++++ .../Colors/PackedPixelTests.cs | 1 + .../Processors/Filters/DitherTest.cs | 38 +++++- 19 files changed, 303 insertions(+), 9 deletions(-) create mode 100644 src/ImageSharp.Processing/Processors/Binarization/OrderedDitherProcessor.cs rename src/ImageSharp/Dithering/{ => ErrorDiffusion}/Atkinson.cs (100%) rename src/ImageSharp/Dithering/{ => ErrorDiffusion}/Burks.cs (100%) rename src/ImageSharp/Dithering/{ => ErrorDiffusion}/ErrorDiffuser.cs (100%) rename src/ImageSharp/Dithering/{ => ErrorDiffusion}/FloydSteinberg.cs (100%) rename src/ImageSharp/Dithering/{ => ErrorDiffusion}/IErrorDiffuser.cs (100%) rename src/ImageSharp/Dithering/{ => ErrorDiffusion}/JarvisJudiceNinke.cs (100%) rename src/ImageSharp/Dithering/{ => ErrorDiffusion}/Sierra2.cs (100%) rename src/ImageSharp/Dithering/{ => ErrorDiffusion}/Sierra3.cs (100%) rename src/ImageSharp/Dithering/{ => ErrorDiffusion}/SierraLite.cs (100%) rename src/ImageSharp/Dithering/{ => ErrorDiffusion}/Stucki.cs (100%) create mode 100644 src/ImageSharp/Dithering/Ordered/Bayer.cs create mode 100644 src/ImageSharp/Dithering/Ordered/IOrderedDither.cs create mode 100644 src/ImageSharp/Dithering/Ordered/Ordered.cs diff --git a/src/ImageSharp.Processing/Binarization/Dither.cs b/src/ImageSharp.Processing/Binarization/Dither.cs index f481ac4dfb..6a4f7f0057 100644 --- a/src/ImageSharp.Processing/Binarization/Dither.cs +++ b/src/ImageSharp.Processing/Binarization/Dither.cs @@ -16,7 +16,39 @@ namespace ImageSharp public static partial class ImageExtensions { /// - /// Alters the alpha component of the image. + /// Dithers the image reducing it to two colors using ordered dithering. + /// + /// The pixel format. + /// The image this method extends. + /// The ordered ditherer. + /// The component index to test the threshold against. Must range from 0 to 3. + /// The . + public static Image Dither(this Image source, IOrderedDither dither, int index = 0) + where TColor : struct, IPackedPixel, IEquatable + { + return Dither(source, dither, source.Bounds, index); + } + + /// + /// Dithers the image reducing it to two colors using ordered dithering. + /// + /// The pixel format. + /// The image this method extends. + /// The ordered ditherer. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The component index to test the threshold against. Must range from 0 to 3. + /// The . + public static Image Dither(this Image source, IOrderedDither dither, Rectangle rectangle, int index = 0) + where TColor : struct, IPackedPixel, IEquatable + { + source.ApplyProcessor(new OrderedDitherProcessor(dither, index), rectangle); + return source; + } + + /// + /// Dithers the image reducing it to two colors using error diffusion. /// /// The pixel format. /// The image this method extends. @@ -30,7 +62,7 @@ namespace ImageSharp } /// - /// Alters the alpha component of the image. + /// Dithers the image reducing it to two colors using error diffusion. /// /// The pixel format. /// The image this method extends. diff --git a/src/ImageSharp.Processing/Processors/Binarization/ErrorDiffusionDitherProcessor.cs b/src/ImageSharp.Processing/Processors/Binarization/ErrorDiffusionDitherProcessor.cs index c5b78b6390..6429ce6f08 100644 --- a/src/ImageSharp.Processing/Processors/Binarization/ErrorDiffusionDitherProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Binarization/ErrorDiffusionDitherProcessor.cs @@ -25,9 +25,6 @@ namespace ImageSharp.Processing.Processors { Guard.NotNull(diffuser, nameof(diffuser)); - // TODO: Check thresholding limit. Colors should probably have Max/Min/Middle properties. - Guard.MustBeBetweenOrEqualTo(threshold, 0, 1, nameof(threshold)); - this.Diffuser = diffuser; this.Threshold = threshold; diff --git a/src/ImageSharp.Processing/Processors/Binarization/OrderedDitherProcessor.cs b/src/ImageSharp.Processing/Processors/Binarization/OrderedDitherProcessor.cs new file mode 100644 index 0000000000..b2a1e9a228 --- /dev/null +++ b/src/ImageSharp.Processing/Processors/Binarization/OrderedDitherProcessor.cs @@ -0,0 +1,119 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Processing.Processors +{ + using System; + using System.Buffers; + + using ImageSharp.Dithering; + + /// + /// An that dithers an image using error diffusion. + /// + /// The pixel format. + public class OrderedDitherProcessor : ImageProcessor + where TColor : struct, IPackedPixel, IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// The ordered ditherer. + /// The component index to test the threshold against. Must range from 0 to 3. + public OrderedDitherProcessor(IOrderedDither dither, int index) + { + Guard.NotNull(dither, nameof(dither)); + Guard.MustBeBetweenOrEqualTo(index, 0, 3, nameof(index)); + + // Alpha8 only stores the pixel data in the alpha channel. + if (typeof(TColor) == typeof(Alpha8)) + { + index = 3; + } + + this.Dither = dither; + this.Index = index; + + // Default to white/black for upper/lower. + TColor upper = default(TColor); + upper.PackFromBytes(255, 255, 255, 255); + this.UpperColor = upper; + + TColor lower = default(TColor); + lower.PackFromBytes(0, 0, 0, 255); + this.LowerColor = lower; + } + + /// + /// Gets the ditherer. + /// + public IOrderedDither Dither { get; } + + /// + /// Gets the component index to test the threshold against. + /// + public int Index { get; } + + /// + /// Gets or sets the color to use for pixels that are above the threshold. + /// + public TColor UpperColor { get; set; } + + /// + /// Gets or sets the color to use for pixels that fall below the threshold. + /// + public TColor LowerColor { get; set; } + + /// + protected override void BeforeApply(ImageBase source, Rectangle sourceRectangle) + { + new GrayscaleBt709Processor().Apply(source, sourceRectangle); + } + + /// + protected override void OnApply(ImageBase source, Rectangle sourceRectangle) + { + int startY = sourceRectangle.Y; + int endY = sourceRectangle.Bottom; + int startX = sourceRectangle.X; + int endX = sourceRectangle.Right; + + // Align start/end positions. + int minX = Math.Max(0, startX); + int maxX = Math.Min(source.Width, endX); + int minY = Math.Max(0, startY); + int maxY = Math.Min(source.Height, endY); + + // Reset offset if necessary. + if (minX > 0) + { + startX = 0; + } + + if (minY > 0) + { + startY = 0; + } + + using (PixelAccessor sourcePixels = source.Lock()) + { + for (int y = minY; y < maxY; y++) + { + int offsetY = y - startY; + byte[] bytes = ArrayPool.Shared.Rent(4); + + for (int x = minX; x < maxX; x++) + { + int offsetX = x - startX; + TColor sourceColor = sourcePixels[offsetX, offsetY]; + this.Dither.Dither(sourcePixels, sourceColor, this.UpperColor, this.LowerColor, bytes, this.Index, offsetX, offsetY, maxX, maxY); + } + + ArrayPool.Shared.Return(bytes); + } + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Colors/PackedPixel/Alpha8.cs b/src/ImageSharp/Colors/PackedPixel/Alpha8.cs index 95e620df01..5642e62010 100644 --- a/src/ImageSharp/Colors/PackedPixel/Alpha8.cs +++ b/src/ImageSharp/Colors/PackedPixel/Alpha8.cs @@ -10,7 +10,7 @@ namespace ImageSharp using System.Runtime.CompilerServices; /// - /// Packed pixel type containing a single 8 bit normalized W values that is ranging from 0 to 1. + /// Packed pixel type containing a single 8 bit normalized W values ranging from 0 to 1. /// public struct Alpha8 : IPackedPixel, IEquatable { diff --git a/src/ImageSharp/Dithering/Atkinson.cs b/src/ImageSharp/Dithering/ErrorDiffusion/Atkinson.cs similarity index 100% rename from src/ImageSharp/Dithering/Atkinson.cs rename to src/ImageSharp/Dithering/ErrorDiffusion/Atkinson.cs diff --git a/src/ImageSharp/Dithering/Burks.cs b/src/ImageSharp/Dithering/ErrorDiffusion/Burks.cs similarity index 100% rename from src/ImageSharp/Dithering/Burks.cs rename to src/ImageSharp/Dithering/ErrorDiffusion/Burks.cs diff --git a/src/ImageSharp/Dithering/ErrorDiffuser.cs b/src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuser.cs similarity index 100% rename from src/ImageSharp/Dithering/ErrorDiffuser.cs rename to src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuser.cs diff --git a/src/ImageSharp/Dithering/FloydSteinberg.cs b/src/ImageSharp/Dithering/ErrorDiffusion/FloydSteinberg.cs similarity index 100% rename from src/ImageSharp/Dithering/FloydSteinberg.cs rename to src/ImageSharp/Dithering/ErrorDiffusion/FloydSteinberg.cs diff --git a/src/ImageSharp/Dithering/IErrorDiffuser.cs b/src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs similarity index 100% rename from src/ImageSharp/Dithering/IErrorDiffuser.cs rename to src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs diff --git a/src/ImageSharp/Dithering/JarvisJudiceNinke.cs b/src/ImageSharp/Dithering/ErrorDiffusion/JarvisJudiceNinke.cs similarity index 100% rename from src/ImageSharp/Dithering/JarvisJudiceNinke.cs rename to src/ImageSharp/Dithering/ErrorDiffusion/JarvisJudiceNinke.cs diff --git a/src/ImageSharp/Dithering/Sierra2.cs b/src/ImageSharp/Dithering/ErrorDiffusion/Sierra2.cs similarity index 100% rename from src/ImageSharp/Dithering/Sierra2.cs rename to src/ImageSharp/Dithering/ErrorDiffusion/Sierra2.cs diff --git a/src/ImageSharp/Dithering/Sierra3.cs b/src/ImageSharp/Dithering/ErrorDiffusion/Sierra3.cs similarity index 100% rename from src/ImageSharp/Dithering/Sierra3.cs rename to src/ImageSharp/Dithering/ErrorDiffusion/Sierra3.cs diff --git a/src/ImageSharp/Dithering/SierraLite.cs b/src/ImageSharp/Dithering/ErrorDiffusion/SierraLite.cs similarity index 100% rename from src/ImageSharp/Dithering/SierraLite.cs rename to src/ImageSharp/Dithering/ErrorDiffusion/SierraLite.cs diff --git a/src/ImageSharp/Dithering/Stucki.cs b/src/ImageSharp/Dithering/ErrorDiffusion/Stucki.cs similarity index 100% rename from src/ImageSharp/Dithering/Stucki.cs rename to src/ImageSharp/Dithering/ErrorDiffusion/Stucki.cs diff --git a/src/ImageSharp/Dithering/Ordered/Bayer.cs b/src/ImageSharp/Dithering/Ordered/Bayer.cs new file mode 100644 index 0000000000..dc731cf894 --- /dev/null +++ b/src/ImageSharp/Dithering/Ordered/Bayer.cs @@ -0,0 +1,38 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Dithering.Ordered +{ + using System; + + /// + /// Applies error diffusion based dithering using the 4x4 Bayer dithering matrix. + /// + /// + public class Bayer : IOrderedDither + { + /// + /// The threshold matrix. + /// This is calculated by multiplying each value in the original matrix by 16 and subtracting 1 + /// + private static readonly byte[,] ThresholdMatrix = { + { 15, 143, 47, 175 }, + { 207, 79, 239, 111 }, + { 63, 191, 31, 159 }, + { 255, 127, 223, 95 } + }; + + /// + public byte[,] Matrix { get; } = ThresholdMatrix; + + /// + public void Dither(PixelAccessor pixels, TColor source, TColor upper, TColor lower, byte[] bytes, int index, int x, int y, int width, int height) + where TColor : struct, IPackedPixel, IEquatable + { + source.ToXyzwBytes(bytes, 0); + pixels[x, y] = ThresholdMatrix[x % 3, y % 3] >= bytes[index] ? upper : lower; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Dithering/Ordered/IOrderedDither.cs b/src/ImageSharp/Dithering/Ordered/IOrderedDither.cs new file mode 100644 index 0000000000..910b275f94 --- /dev/null +++ b/src/ImageSharp/Dithering/Ordered/IOrderedDither.cs @@ -0,0 +1,37 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Dithering +{ + using System; + + /// + /// Encapsulates properties and methods required to perfom ordered dithering on an image. + /// + public interface IOrderedDither + { + /// + /// Gets the dithering matrix + /// + byte[,] Matrix { get; } + + /// + /// Transforms the image applying the dither matrix. This method alters the input pixels array + /// + /// The pixel accessor + /// The source pixel + /// The color to apply to the pixels above the threshold. + /// The color to apply to the pixels below the threshold. + /// The byte array to pack/unpack to. Must have a length of 4. Bytes are unpacked to Xyzw order. + /// The component index to test the threshold against. Must range from 0 to 3. + /// The column index. + /// The row index. + /// The image width. + /// The image height. + /// The pixel format. + void Dither(PixelAccessor pixels, TColor source, TColor upper, TColor lower, byte[] bytes, int index, int x, int y, int width, int height) + where TColor : struct, IPackedPixel, IEquatable; + } +} diff --git a/src/ImageSharp/Dithering/Ordered/Ordered.cs b/src/ImageSharp/Dithering/Ordered/Ordered.cs new file mode 100644 index 0000000000..0cfc532443 --- /dev/null +++ b/src/ImageSharp/Dithering/Ordered/Ordered.cs @@ -0,0 +1,38 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Dithering.Ordered +{ + using System; + + /// + /// Applies error diffusion based dithering using the 4x4 ordered dithering matrix. + /// + /// + public class Ordered : IOrderedDither + { + /// + /// The threshold matrix. + /// This is calculated by multiplying each value in the original matrix by 16 + /// + private static readonly byte[,] ThresholdMatrix = { + { 0, 128, 32, 160 }, + { 192, 64, 224, 96 }, + { 48, 176, 16, 144 }, + { 240, 112, 208, 80 } + }; + + /// + public byte[,] Matrix { get; } = ThresholdMatrix; + + /// + public void Dither(PixelAccessor pixels, TColor source, TColor upper, TColor lower, byte[] bytes, int index, int x, int y, int width, int height) + where TColor : struct, IPackedPixel, IEquatable + { + source.ToXyzwBytes(bytes, 0); + pixels[x, y] = ThresholdMatrix[x % 3, y % 3] >= bytes[index] ? upper : lower; + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colors/PackedPixelTests.cs b/tests/ImageSharp.Tests/Colors/PackedPixelTests.cs index a79ef620ef..3e2b6fcd5c 100644 --- a/tests/ImageSharp.Tests/Colors/PackedPixelTests.cs +++ b/tests/ImageSharp.Tests/Colors/PackedPixelTests.cs @@ -6,6 +6,7 @@ namespace ImageSharp.Tests.Colors { using System; + using System.Diagnostics; using System.Numerics; using Xunit; diff --git a/tests/ImageSharp.Tests/Processors/Filters/DitherTest.cs b/tests/ImageSharp.Tests/Processors/Filters/DitherTest.cs index 3de2481e5d..db473901d3 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/DitherTest.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/DitherTest.cs @@ -8,6 +8,7 @@ namespace ImageSharp.Tests using System.IO; using ImageSharp.Dithering; + using ImageSharp.Dithering.Ordered; using Xunit; @@ -16,14 +17,14 @@ namespace ImageSharp.Tests [Fact] public void ImageShouldApplyDitherFilter() { - string path = this.CreateOutputDirectory("Dither"); + string path = this.CreateOutputDirectory("Dither", "Dither"); foreach (TestFile file in Files) { using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) { - image.Dither(new SierraLite(), .5F).Save(output); + image.Dither(new Bayer()).Save(output); } } } @@ -31,7 +32,38 @@ namespace ImageSharp.Tests [Fact] public void ImageShouldApplyDitherFilterInBox() { - string path = this.CreateOutputDirectory("Dither"); + string path = this.CreateOutputDirectory("Dither", "Dither"); + + foreach (TestFile file in Files) + { + string filename = file.GetFileName("-InBox"); + using (Image image = file.CreateImage()) + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + image.Dither(new Bayer(), new Rectangle(10, 10, image.Width / 2, image.Height / 2)).Save(output); + } + } + } + + [Fact] + public void ImageShouldApplyDiffusionFilter() + { + string path = this.CreateOutputDirectory("Dither", "Diffusion"); + + foreach (TestFile file in Files) + { + using (Image image = file.CreateImage()) + using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) + { + image.Dither(new SierraLite(), .5F).Save(output); + } + } + } + + [Fact] + public void ImageShouldApplyDiffusionFilterInBox() + { + string path = this.CreateOutputDirectory("Dither", "Diffusion"); foreach (TestFile file in Files) { From c41d95068af05909648ede990035ea3b49d451d6 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 15 Feb 2017 09:34:07 +1100 Subject: [PATCH 062/142] Fix terniary --- src/ImageSharp/Dithering/Ordered/Bayer.cs | 2 +- src/ImageSharp/Dithering/Ordered/Ordered.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Dithering/Ordered/Bayer.cs b/src/ImageSharp/Dithering/Ordered/Bayer.cs index dc731cf894..52e91946ba 100644 --- a/src/ImageSharp/Dithering/Ordered/Bayer.cs +++ b/src/ImageSharp/Dithering/Ordered/Bayer.cs @@ -32,7 +32,7 @@ namespace ImageSharp.Dithering.Ordered where TColor : struct, IPackedPixel, IEquatable { source.ToXyzwBytes(bytes, 0); - pixels[x, y] = ThresholdMatrix[x % 3, y % 3] >= bytes[index] ? upper : lower; + pixels[x, y] = ThresholdMatrix[x % 3, y % 3] >= bytes[index] ? lower : upper; } } } \ No newline at end of file diff --git a/src/ImageSharp/Dithering/Ordered/Ordered.cs b/src/ImageSharp/Dithering/Ordered/Ordered.cs index 0cfc532443..7c8a15386c 100644 --- a/src/ImageSharp/Dithering/Ordered/Ordered.cs +++ b/src/ImageSharp/Dithering/Ordered/Ordered.cs @@ -32,7 +32,7 @@ namespace ImageSharp.Dithering.Ordered where TColor : struct, IPackedPixel, IEquatable { source.ToXyzwBytes(bytes, 0); - pixels[x, y] = ThresholdMatrix[x % 3, y % 3] >= bytes[index] ? upper : lower; + pixels[x, y] = ThresholdMatrix[x % 3, y % 3] >= bytes[index] ? lower : upper; } } } \ No newline at end of file From e3035f68391c62ac99ef58df33af543d6e662144 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 16 Feb 2017 00:00:28 +1100 Subject: [PATCH 063/142] Use Octree for Png for dithering --- src/ImageSharp.Formats.Png/PngEncoderCore.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp.Formats.Png/PngEncoderCore.cs b/src/ImageSharp.Formats.Png/PngEncoderCore.cs index 46aa2187bf..9acaf5b9fa 100644 --- a/src/ImageSharp.Formats.Png/PngEncoderCore.cs +++ b/src/ImageSharp.Formats.Png/PngEncoderCore.cs @@ -495,7 +495,7 @@ namespace ImageSharp.Formats if (this.Quantizer == null) { - this.Quantizer = new WuQuantizer(); + this.Quantizer = new OctreeQuantizer(); } // Quantize the image returning a palette. This boxing is icky. From 6526585850afad620e1e1e1fbc20dc6a0264712b Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 16 Feb 2017 00:01:02 +1100 Subject: [PATCH 064/142] Cleanup --- .../Processors/Binarization/OrderedDitherProcessor.cs | 4 ++-- src/ImageSharp/Dithering/Ordered/Bayer.cs | 11 ++++++----- src/ImageSharp/Dithering/Ordered/Ordered.cs | 11 ++++++----- src/ImageSharp/Quantizers/Octree/OctreeQuantizer.cs | 2 +- src/ImageSharp/Quantizers/Quantize.cs | 6 ++---- tests/ImageSharp.Tests/Formats/Png/PngTests.cs | 2 +- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/ImageSharp.Processing/Processors/Binarization/OrderedDitherProcessor.cs b/src/ImageSharp.Processing/Processors/Binarization/OrderedDitherProcessor.cs index b2a1e9a228..ae1224bad0 100644 --- a/src/ImageSharp.Processing/Processors/Binarization/OrderedDitherProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Binarization/OrderedDitherProcessor.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // @@ -18,7 +18,7 @@ namespace ImageSharp.Processing.Processors where TColor : struct, IPackedPixel, IEquatable { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The ordered ditherer. /// The component index to test the threshold against. Must range from 0 to 3. diff --git a/src/ImageSharp/Dithering/Ordered/Bayer.cs b/src/ImageSharp/Dithering/Ordered/Bayer.cs index 52e91946ba..3674be0aaf 100644 --- a/src/ImageSharp/Dithering/Ordered/Bayer.cs +++ b/src/ImageSharp/Dithering/Ordered/Bayer.cs @@ -17,11 +17,12 @@ namespace ImageSharp.Dithering.Ordered /// The threshold matrix. /// This is calculated by multiplying each value in the original matrix by 16 and subtracting 1 ///
- private static readonly byte[,] ThresholdMatrix = { - { 15, 143, 47, 175 }, - { 207, 79, 239, 111 }, - { 63, 191, 31, 159 }, - { 255, 127, 223, 95 } + private static readonly byte[,] ThresholdMatrix = + { + { 15, 143, 47, 175 }, + { 207, 79, 239, 111 }, + { 63, 191, 31, 159 }, + { 255, 127, 223, 95 } }; /// diff --git a/src/ImageSharp/Dithering/Ordered/Ordered.cs b/src/ImageSharp/Dithering/Ordered/Ordered.cs index 7c8a15386c..2e7766e0bd 100644 --- a/src/ImageSharp/Dithering/Ordered/Ordered.cs +++ b/src/ImageSharp/Dithering/Ordered/Ordered.cs @@ -17,11 +17,12 @@ namespace ImageSharp.Dithering.Ordered /// The threshold matrix. /// This is calculated by multiplying each value in the original matrix by 16 ///
- private static readonly byte[,] ThresholdMatrix = { - { 0, 128, 32, 160 }, - { 192, 64, 224, 96 }, - { 48, 176, 16, 144 }, - { 240, 112, 208, 80 } + private static readonly byte[,] ThresholdMatrix = + { + { 0, 128, 32, 160 }, + { 192, 64, 224, 96 }, + { 48, 176, 16, 144 }, + { 240, 112, 208, 80 } }; /// diff --git a/src/ImageSharp/Quantizers/Octree/OctreeQuantizer.cs b/src/ImageSharp/Quantizers/Octree/OctreeQuantizer.cs index 2cd2de4f59..c0aab00ed6 100644 --- a/src/ImageSharp/Quantizers/Octree/OctreeQuantizer.cs +++ b/src/ImageSharp/Quantizers/Octree/OctreeQuantizer.cs @@ -437,7 +437,7 @@ namespace ImageSharp.Quantizers { if (this.leaf) { - // TODO: Test Vector4 here + // This seems faster than using Vector4 byte r = (this.red / this.pixelCount).ToByte(); byte g = (this.green / this.pixelCount).ToByte(); byte b = (this.blue / this.pixelCount).ToByte(); diff --git a/src/ImageSharp/Quantizers/Quantize.cs b/src/ImageSharp/Quantizers/Quantize.cs index a03833b25c..8a26fb4bad 100644 --- a/src/ImageSharp/Quantizers/Quantize.cs +++ b/src/ImageSharp/Quantizers/Quantize.cs @@ -57,8 +57,6 @@ namespace ImageSharp where TColor : struct, IPackedPixel, IEquatable { QuantizedImage quantized = quantizer.Quantize(source, maxColors); - - int pixelCount = quantized.Pixels.Length; int palleteCount = quantized.Palette.Length - 1; using (PixelAccessor pixels = new PixelAccessor(quantized.Width, quantized.Height)) @@ -69,9 +67,9 @@ namespace ImageSharp source.Configuration.ParallelOptions, y => { - for (var x = 0; x < pixels.Width; x++) + for (int x = 0; x < pixels.Width; x++) { - var i = x + (y * pixels.Width); + int i = x + (y * pixels.Width); TColor color = quantized.Palette[Math.Min(palleteCount, quantized.Pixels[i])]; pixels[x, y] = color; } diff --git a/tests/ImageSharp.Tests/Formats/Png/PngTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngTests.cs index cf485c593f..5ba00eb4d3 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngTests.cs @@ -19,7 +19,7 @@ namespace ImageSharp.Tests [Fact] public void ImageCanSaveIndexedPng() { - string path = CreateOutputDirectory("Png"); + string path = CreateOutputDirectory("Png", "Indexed"); foreach (TestFile file in Files) { From fec92d8c84809300d660962d370511afb3704fb4 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 16 Feb 2017 00:02:01 +1100 Subject: [PATCH 065/142] Use caching for the distance calculation --- src/ImageSharp/Quantizers/Octree/Quantizer.cs | 48 +++++++++++++------ .../Quantizers/Palette/PaletteQuantizer.cs | 42 ++-------------- 2 files changed, 37 insertions(+), 53 deletions(-) diff --git a/src/ImageSharp/Quantizers/Octree/Quantizer.cs b/src/ImageSharp/Quantizers/Octree/Quantizer.cs index 4d16c5df36..87705de94d 100644 --- a/src/ImageSharp/Quantizers/Octree/Quantizer.cs +++ b/src/ImageSharp/Quantizers/Octree/Quantizer.cs @@ -6,6 +6,7 @@ namespace ImageSharp.Quantizers { using System; + using System.Collections.Generic; using System.Numerics; using System.Runtime.CompilerServices; @@ -18,6 +19,11 @@ namespace ImageSharp.Quantizers public abstract class Quantizer : IDitheredQuantizer where TColor : struct, IPackedPixel, IEquatable { + /// + /// A lookup table for colors + /// + private readonly Dictionary colorMap = new Dictionary(); + /// /// Flag used to indicate whether a single pass or two passes are needed for quantization. /// @@ -129,7 +135,7 @@ namespace ImageSharp.Quantizers { // Apply the dithering matrix TColor sourcePixel = source[x, y]; - TColor transformedPixel = this.palette[GetClosestColor(sourcePixel, this.palette)]; + TColor transformedPixel = this.palette[this.GetClosestColor(sourcePixel, this.palette, this.colorMap)]; this.DitherType.Dither(source, sourcePixel, transformedPixel, x, y, width, height); } @@ -171,32 +177,46 @@ namespace ImageSharp.Quantizers /// Returns the closest color from the palette to the given color by calculating the Euclidean distance. ///
/// The color. - /// The color palette. + /// The color palette. + /// The cache to store the result in. /// The [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static byte GetClosestColor(TColor pixel, TColor[] palette) + protected byte GetClosestColor(TColor pixel, TColor[] colorPalette, Dictionary cache) { + // Check if the color is in the lookup table + if (this.colorMap.ContainsKey(pixel)) + { + return this.colorMap[pixel]; + } + + // Not found - loop through the palette and find the nearest match. + byte colorIndex = 0; float leastDistance = int.MaxValue; Vector4 vector = pixel.ToVector4(); - byte colorIndex = 0; - for (int index = 0; index < palette.Length; index++) + for (int index = 0; index < colorPalette.Length; index++) { - float distance = Vector4.Distance(vector, palette[index].ToVector4()); + float distance = Vector4.Distance(vector, colorPalette[index].ToVector4()); - if (distance < leastDistance) + // Greater... Move on. + if (!(distance < leastDistance)) { - colorIndex = (byte)index; - leastDistance = distance; + continue; + } - // And if it's an exact match, exit the loop - if (Math.Abs(distance) < Constants.Epsilon) - { - break; - } + colorIndex = (byte)index; + leastDistance = distance; + + // And if it's an exact match, exit the loop + if (Math.Abs(distance) < Constants.Epsilon) + { + break; } } + // Now I have the index, pop it into the cache for next time + this.colorMap.Add(pixel, colorIndex); + return colorIndex; } } diff --git a/src/ImageSharp/Quantizers/Palette/PaletteQuantizer.cs b/src/ImageSharp/Quantizers/Palette/PaletteQuantizer.cs index abf1e5dc5c..e55d670375 100644 --- a/src/ImageSharp/Quantizers/Palette/PaletteQuantizer.cs +++ b/src/ImageSharp/Quantizers/Palette/PaletteQuantizer.cs @@ -7,15 +7,14 @@ namespace ImageSharp.Quantizers { using System; using System.Collections.Generic; - using System.Numerics; /// /// Encapsulates methods to create a quantized image based upon the given palette. /// /// /// The pixel format. - public class PaletteQuantizer : Quantizer - where TColor : struct, IPackedPixel, IEquatable + public sealed class PaletteQuantizer : Quantizer + where TColor : struct, IPackedPixel, IEquatable { /// /// The pixel buffer, used to reduce allocations. @@ -73,42 +72,7 @@ namespace ImageSharp.Quantizers /// protected override byte QuantizePixel(TColor pixel) { - byte colorIndex = 0; - TColor colorHash = pixel; - - // Check if the color is in the lookup table - if (this.colorMap.ContainsKey(colorHash)) - { - colorIndex = this.colorMap[colorHash]; - } - else - { - // Not found - loop through the palette and find the nearest match. - float leastDistance = int.MaxValue; - Vector4 vector = pixel.ToVector4(); - - for (int index = 0; index < this.colors.Length; index++) - { - float distance = Vector4.Distance(vector, this.colors[index].ToVector4()); - - if (distance < leastDistance) - { - colorIndex = (byte)index; - leastDistance = distance; - - // And if it's an exact match, exit the loop - if (Math.Abs(distance) < Constants.Epsilon) - { - break; - } - } - } - - // Now I have the color, pop it into the cache for next time - this.colorMap.Add(colorHash, colorIndex); - } - - return colorIndex; + return this.GetClosestColor(pixel, this.colors, this.colorMap); } /// From fbf017dd905fa6fc63d28f10f83e90b6a204538d Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 16 Feb 2017 00:26:16 +1100 Subject: [PATCH 066/142] Better integration tests --- .../Processors/Filters/DitherTest.cs | 56 +++++++++++++------ 1 file changed, 40 insertions(+), 16 deletions(-) diff --git a/tests/ImageSharp.Tests/Processors/Filters/DitherTest.cs b/tests/ImageSharp.Tests/Processors/Filters/DitherTest.cs index db473901d3..e89a1b1ec2 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/DitherTest.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/DitherTest.cs @@ -14,64 +14,88 @@ namespace ImageSharp.Tests public class DitherTest : FileTestBase { - [Fact] - public void ImageShouldApplyDitherFilter() + public static readonly TheoryData Ditherers = new TheoryData + { + { "Ordered", new Ordered() }, + { "Bayer", new Bayer() } + }; + + public static readonly TheoryData ErrorDiffusers = new TheoryData + { + { "Atkinson", new Atkinson() }, + { "Burks", new Burks() }, + { "FloydSteinberg", new FloydSteinberg() }, + { "JarvisJudiceNinke", new JarvisJudiceNinke() }, + { "Sierra2", new Sierra2() }, + { "Sierra3", new Sierra3() }, + { "SierraLite", new SierraLite() }, + { "Stucki", new Stucki() }, + }; + + [Theory] + [MemberData(nameof(Ditherers))] + public void ImageShouldApplyDitherFilter(string name, IOrderedDither ditherer) { string path = this.CreateOutputDirectory("Dither", "Dither"); foreach (TestFile file in Files) { + string filename = file.GetFileName(name); using (Image image = file.CreateImage()) - using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) + using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - image.Dither(new Bayer()).Save(output); + image.Dither(ditherer).Save(output); } } } - [Fact] - public void ImageShouldApplyDitherFilterInBox() + [Theory] + [MemberData(nameof(Ditherers))] + public void ImageShouldApplyDitherFilterInBox(string name, IOrderedDither ditherer) { string path = this.CreateOutputDirectory("Dither", "Dither"); foreach (TestFile file in Files) { - string filename = file.GetFileName("-InBox"); + string filename = file.GetFileName($"{name}-InBox"); using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - image.Dither(new Bayer(), new Rectangle(10, 10, image.Width / 2, image.Height / 2)).Save(output); + image.Dither(ditherer, new Rectangle(10, 10, image.Width / 2, image.Height / 2)).Save(output); } } } - [Fact] - public void ImageShouldApplyDiffusionFilter() + [Theory] + [MemberData(nameof(ErrorDiffusers))] + public void ImageShouldApplyDiffusionFilter(string name, IErrorDiffuser diffuser) { string path = this.CreateOutputDirectory("Dither", "Diffusion"); foreach (TestFile file in Files) { + string filename = file.GetFileName(name); using (Image image = file.CreateImage()) - using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) + using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - image.Dither(new SierraLite(), .5F).Save(output); + image.Dither(diffuser, .5F).Save(output); } } } - [Fact] - public void ImageShouldApplyDiffusionFilterInBox() + [Theory] + [MemberData(nameof(ErrorDiffusers))] + public void ImageShouldApplyDiffusionFilterInBox(string name, IErrorDiffuser diffuser) { string path = this.CreateOutputDirectory("Dither", "Diffusion"); foreach (TestFile file in Files) { - string filename = file.GetFileName("-InBox"); + string filename = file.GetFileName($"{name}-InBox"); using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - image.Dither(new SierraLite(), .5F, new Rectangle(10, 10, image.Width / 2, image.Height / 2)).Save(output); + image.Dither(diffuser, .5F, new Rectangle(10, 10, image.Width / 2, image.Height / 2)).Save(output); } } } From b184507fa45cd7a4c05068d42e44175414745f72 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 16 Feb 2017 09:08:15 +1100 Subject: [PATCH 067/142] Remove extra stream --- .../Formats/GeneralFormatTests.cs | 23 ++++++------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs index 7e47501f3f..ae795c2ecc 100644 --- a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs +++ b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs @@ -5,9 +5,7 @@ namespace ImageSharp.Tests { - using System; using System.IO; - using System.Numerics; using Xunit; @@ -145,24 +143,17 @@ namespace ImageSharp.Tests { byte[] serialized; using (Image image = file.CreateImage()) + using (MemoryStream memoryStream = new MemoryStream()) { - using (MemoryStream memoryStream = new MemoryStream()) - { - image.Save(memoryStream); - memoryStream.Flush(); - serialized = memoryStream.ToArray(); - } + image.Save(memoryStream); + memoryStream.Flush(); + serialized = memoryStream.ToArray(); } - using (MemoryStream memoryStream = new MemoryStream(serialized)) + using (Image image2 = new Image(serialized)) + using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) { - using (Image image2 = new Image(memoryStream)) - { - using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) - { - image2.Save(output); - } - } + image2.Save(output); } } } From efbf2659a38f42c36c1d5a20f52c4502e2fcf9a3 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 16 Feb 2017 09:55:16 +1100 Subject: [PATCH 068/142] Use jagged arrays --- .../Dithering/ErrorDiffusion/Atkinson.cs | 8 ++++---- src/ImageSharp/Dithering/ErrorDiffusion/Burks.cs | 6 +++--- .../Dithering/ErrorDiffusion/ErrorDiffuser.cs | 14 +++++++------- .../Dithering/ErrorDiffusion/FloydSteinberg.cs | 6 +++--- .../Dithering/ErrorDiffusion/IErrorDiffuser.cs | 2 +- .../Dithering/ErrorDiffusion/JarvisJudiceNinke.cs | 8 ++++---- src/ImageSharp/Dithering/ErrorDiffusion/Sierra2.cs | 6 +++--- src/ImageSharp/Dithering/ErrorDiffusion/Sierra3.cs | 8 ++++---- .../Dithering/ErrorDiffusion/SierraLite.cs | 6 +++--- src/ImageSharp/Dithering/ErrorDiffusion/Stucki.cs | 8 ++++---- src/ImageSharp/Dithering/Ordered/Bayer.cs | 14 +++++++------- src/ImageSharp/Dithering/Ordered/IOrderedDither.cs | 2 +- src/ImageSharp/Dithering/Ordered/Ordered.cs | 14 +++++++------- 13 files changed, 51 insertions(+), 51 deletions(-) diff --git a/src/ImageSharp/Dithering/ErrorDiffusion/Atkinson.cs b/src/ImageSharp/Dithering/ErrorDiffusion/Atkinson.cs index 934df7e4a8..76a5f96ec9 100644 --- a/src/ImageSharp/Dithering/ErrorDiffusion/Atkinson.cs +++ b/src/ImageSharp/Dithering/ErrorDiffusion/Atkinson.cs @@ -14,11 +14,11 @@ namespace ImageSharp.Dithering /// /// The diffusion matrix /// - private static readonly byte[,] AtkinsonMatrix = + private static readonly byte[][] AtkinsonMatrix = { - { 0, 0, 1, 1 }, - { 1, 1, 1, 0 }, - { 0, 1, 0, 0 } + new byte[] { 0, 0, 1, 1 }, + new byte[] { 1, 1, 1, 0 }, + new byte[] { 0, 1, 0, 0 } }; /// diff --git a/src/ImageSharp/Dithering/ErrorDiffusion/Burks.cs b/src/ImageSharp/Dithering/ErrorDiffusion/Burks.cs index 311316685b..2d71b9cc35 100644 --- a/src/ImageSharp/Dithering/ErrorDiffusion/Burks.cs +++ b/src/ImageSharp/Dithering/ErrorDiffusion/Burks.cs @@ -14,10 +14,10 @@ namespace ImageSharp.Dithering /// /// The diffusion matrix /// - private static readonly byte[,] BurksMatrix = + private static readonly byte[][] BurksMatrix = { - { 0, 0, 0, 8, 4 }, - { 2, 4, 8, 4, 2 } + new byte[] { 0, 0, 0, 8, 4 }, + new byte[] { 2, 4, 8, 4, 2 } }; /// diff --git a/src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuser.cs b/src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuser.cs index 1de6cd8149..0c2a1dc404 100644 --- a/src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuser.cs +++ b/src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuser.cs @@ -39,20 +39,20 @@ namespace ImageSharp.Dithering /// /// The dithering matrix. /// The divisor. - protected ErrorDiffuser(byte[,] matrix, byte divisor) + protected ErrorDiffuser(byte[][] matrix, byte divisor) { Guard.NotNull(matrix, nameof(matrix)); Guard.MustBeGreaterThan(divisor, 0, nameof(divisor)); this.Matrix = matrix; - this.matrixWidth = (byte)(matrix.GetUpperBound(1) + 1); - this.matrixHeight = (byte)(matrix.GetUpperBound(0) + 1); + this.matrixWidth = (byte)matrix[0].Length; + this.matrixHeight = (byte)matrix.Length; this.divisorVector = new Vector4(divisor); this.startingOffset = 0; for (int i = 0; i < this.matrixWidth; i++) { - if (matrix[0, i] != 0) + if (matrix[0][i] != 0) { this.startingOffset = (byte)(i - 1); break; @@ -61,7 +61,7 @@ namespace ImageSharp.Dithering } /// - public byte[,] Matrix { get; } + public byte[][] Matrix { get; } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -85,13 +85,13 @@ namespace ImageSharp.Dithering if (matrixX > 0 && matrixX < width && matrixY > 0 && matrixY < height) { - byte coefficient = this.Matrix[row, col]; + byte coefficient = this.Matrix[row][col]; if (coefficient == 0) { continue; } - Vector4 coefficientVector = new Vector4(this.Matrix[row, col]); + Vector4 coefficientVector = new Vector4(this.Matrix[row][col]); Vector4 offsetColor = pixels[matrixX, matrixY].ToVector4(); Vector4 result = ((error * coefficientVector) / this.divisorVector) + offsetColor; result.W = offsetColor.W; diff --git a/src/ImageSharp/Dithering/ErrorDiffusion/FloydSteinberg.cs b/src/ImageSharp/Dithering/ErrorDiffusion/FloydSteinberg.cs index a392c9dc9c..cf91bd8fab 100644 --- a/src/ImageSharp/Dithering/ErrorDiffusion/FloydSteinberg.cs +++ b/src/ImageSharp/Dithering/ErrorDiffusion/FloydSteinberg.cs @@ -14,10 +14,10 @@ namespace ImageSharp.Dithering /// /// The diffusion matrix /// - private static readonly byte[,] FloydSteinbergMatrix = + private static readonly byte[][] FloydSteinbergMatrix = { - { 0, 0, 7 }, - { 3, 5, 1 } + new byte[] { 0, 0, 7 }, + new byte[] { 3, 5, 1 } }; /// diff --git a/src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs b/src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs index 22cbad1e6e..eb6c27167e 100644 --- a/src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs +++ b/src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs @@ -15,7 +15,7 @@ namespace ImageSharp.Dithering /// /// Gets the dithering matrix /// - byte[,] Matrix { get; } + byte[][] Matrix { get; } /// /// Transforms the image applying the dither matrix. This method alters the input pixels array diff --git a/src/ImageSharp/Dithering/ErrorDiffusion/JarvisJudiceNinke.cs b/src/ImageSharp/Dithering/ErrorDiffusion/JarvisJudiceNinke.cs index b5876d7773..5f26d8b248 100644 --- a/src/ImageSharp/Dithering/ErrorDiffusion/JarvisJudiceNinke.cs +++ b/src/ImageSharp/Dithering/ErrorDiffusion/JarvisJudiceNinke.cs @@ -14,11 +14,11 @@ namespace ImageSharp.Dithering /// /// The diffusion matrix /// - private static readonly byte[,] JarvisJudiceNinkeMatrix = + private static readonly byte[][] JarvisJudiceNinkeMatrix = { - { 0, 0, 0, 7, 5 }, - { 3, 5, 7, 5, 3 }, - { 1, 3, 5, 3, 1 } + new byte[] { 0, 0, 0, 7, 5 }, + new byte[] { 3, 5, 7, 5, 3 }, + new byte[] { 1, 3, 5, 3, 1 } }; /// diff --git a/src/ImageSharp/Dithering/ErrorDiffusion/Sierra2.cs b/src/ImageSharp/Dithering/ErrorDiffusion/Sierra2.cs index d7cc84254e..44132747d9 100644 --- a/src/ImageSharp/Dithering/ErrorDiffusion/Sierra2.cs +++ b/src/ImageSharp/Dithering/ErrorDiffusion/Sierra2.cs @@ -14,10 +14,10 @@ namespace ImageSharp.Dithering /// /// The diffusion matrix /// - private static readonly byte[,] Sierra2Matrix = + private static readonly byte[][] Sierra2Matrix = { - { 0, 0, 0, 4, 3 }, - { 1, 2, 3, 2, 1 } + new byte[] { 0, 0, 0, 4, 3 }, + new byte[] { 1, 2, 3, 2, 1 } }; /// diff --git a/src/ImageSharp/Dithering/ErrorDiffusion/Sierra3.cs b/src/ImageSharp/Dithering/ErrorDiffusion/Sierra3.cs index c3d1fe7565..01f6b5ded7 100644 --- a/src/ImageSharp/Dithering/ErrorDiffusion/Sierra3.cs +++ b/src/ImageSharp/Dithering/ErrorDiffusion/Sierra3.cs @@ -14,11 +14,11 @@ namespace ImageSharp.Dithering /// /// The diffusion matrix /// - private static readonly byte[,] Sierra3Matrix = + private static readonly byte[][] Sierra3Matrix = { - { 0, 0, 0, 5, 3 }, - { 2, 4, 5, 4, 2 }, - { 0, 2, 3, 2, 0 } + new byte[] { 0, 0, 0, 5, 3 }, + new byte[] { 2, 4, 5, 4, 2 }, + new byte[] { 0, 2, 3, 2, 0 } }; /// diff --git a/src/ImageSharp/Dithering/ErrorDiffusion/SierraLite.cs b/src/ImageSharp/Dithering/ErrorDiffusion/SierraLite.cs index 7d855b84e3..42fa8c23d2 100644 --- a/src/ImageSharp/Dithering/ErrorDiffusion/SierraLite.cs +++ b/src/ImageSharp/Dithering/ErrorDiffusion/SierraLite.cs @@ -14,10 +14,10 @@ namespace ImageSharp.Dithering /// /// The diffusion matrix /// - private static readonly byte[,] SierraLiteMatrix = + private static readonly byte[][] SierraLiteMatrix = { - { 0, 0, 2 }, - { 1, 1, 0 } + new byte[] { 0, 0, 2 }, + new byte[] { 1, 1, 0 } }; /// diff --git a/src/ImageSharp/Dithering/ErrorDiffusion/Stucki.cs b/src/ImageSharp/Dithering/ErrorDiffusion/Stucki.cs index 3cc01aa798..0e462630e9 100644 --- a/src/ImageSharp/Dithering/ErrorDiffusion/Stucki.cs +++ b/src/ImageSharp/Dithering/ErrorDiffusion/Stucki.cs @@ -14,11 +14,11 @@ namespace ImageSharp.Dithering /// /// The diffusion matrix /// - private static readonly byte[,] StuckiMatrix = + private static readonly byte[][] StuckiMatrix = { - { 0, 0, 0, 8, 4 }, - { 2, 4, 8, 4, 2 }, - { 1, 2, 4, 2, 1 } + new byte[] { 0, 0, 0, 8, 4 }, + new byte[] { 2, 4, 8, 4, 2 }, + new byte[] { 1, 2, 4, 2, 1 } }; /// diff --git a/src/ImageSharp/Dithering/Ordered/Bayer.cs b/src/ImageSharp/Dithering/Ordered/Bayer.cs index 3674be0aaf..c560889790 100644 --- a/src/ImageSharp/Dithering/Ordered/Bayer.cs +++ b/src/ImageSharp/Dithering/Ordered/Bayer.cs @@ -17,23 +17,23 @@ namespace ImageSharp.Dithering.Ordered /// The threshold matrix. /// This is calculated by multiplying each value in the original matrix by 16 and subtracting 1 /// - private static readonly byte[,] ThresholdMatrix = + private static readonly byte[][] ThresholdMatrix = { - { 15, 143, 47, 175 }, - { 207, 79, 239, 111 }, - { 63, 191, 31, 159 }, - { 255, 127, 223, 95 } + new byte[] { 15, 143, 47, 175 }, + new byte[] { 207, 79, 239, 111 }, + new byte[] { 63, 191, 31, 159 }, + new byte[] { 255, 127, 223, 95 } }; /// - public byte[,] Matrix { get; } = ThresholdMatrix; + public byte[][] Matrix { get; } = ThresholdMatrix; /// public void Dither(PixelAccessor pixels, TColor source, TColor upper, TColor lower, byte[] bytes, int index, int x, int y, int width, int height) where TColor : struct, IPackedPixel, IEquatable { source.ToXyzwBytes(bytes, 0); - pixels[x, y] = ThresholdMatrix[x % 3, y % 3] >= bytes[index] ? lower : upper; + pixels[x, y] = ThresholdMatrix[x % 3][y % 3] >= bytes[index] ? lower : upper; } } } \ No newline at end of file diff --git a/src/ImageSharp/Dithering/Ordered/IOrderedDither.cs b/src/ImageSharp/Dithering/Ordered/IOrderedDither.cs index 910b275f94..464b9d4199 100644 --- a/src/ImageSharp/Dithering/Ordered/IOrderedDither.cs +++ b/src/ImageSharp/Dithering/Ordered/IOrderedDither.cs @@ -15,7 +15,7 @@ namespace ImageSharp.Dithering /// /// Gets the dithering matrix /// - byte[,] Matrix { get; } + byte[][] Matrix { get; } /// /// Transforms the image applying the dither matrix. This method alters the input pixels array diff --git a/src/ImageSharp/Dithering/Ordered/Ordered.cs b/src/ImageSharp/Dithering/Ordered/Ordered.cs index 2e7766e0bd..38f8f21a57 100644 --- a/src/ImageSharp/Dithering/Ordered/Ordered.cs +++ b/src/ImageSharp/Dithering/Ordered/Ordered.cs @@ -17,23 +17,23 @@ namespace ImageSharp.Dithering.Ordered /// The threshold matrix. /// This is calculated by multiplying each value in the original matrix by 16 /// - private static readonly byte[,] ThresholdMatrix = + private static readonly byte[][] ThresholdMatrix = { - { 0, 128, 32, 160 }, - { 192, 64, 224, 96 }, - { 48, 176, 16, 144 }, - { 240, 112, 208, 80 } + new byte[] { 0, 128, 32, 160 }, + new byte[] { 192, 64, 224, 96 }, + new byte[] { 48, 176, 16, 144 }, + new byte[] { 240, 112, 208, 80 } }; /// - public byte[,] Matrix { get; } = ThresholdMatrix; + public byte[][] Matrix { get; } = ThresholdMatrix; /// public void Dither(PixelAccessor pixels, TColor source, TColor upper, TColor lower, byte[] bytes, int index, int x, int y, int width, int height) where TColor : struct, IPackedPixel, IEquatable { source.ToXyzwBytes(bytes, 0); - pixels[x, y] = ThresholdMatrix[x % 3, y % 3] >= bytes[index] ? lower : upper; + pixels[x, y] = ThresholdMatrix[x % 3][y % 3] >= bytes[index] ? lower : upper; } } } \ No newline at end of file From 489c04be7bb544db045a687148a440aa5ae15b50 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 17 Feb 2017 09:40:32 +1100 Subject: [PATCH 069/142] Use Fast2DAarray for ordered dither. --- .../Common/Helpers/Fast2DArray{T}.cs | 102 ++++++++++++++++++ src/ImageSharp/Dithering/Ordered/Bayer.cs | 19 ++-- .../Dithering/Ordered/IOrderedDither.cs | 2 +- src/ImageSharp/Dithering/Ordered/Ordered.cs | 19 ++-- src/ImageSharp/Image/PixelAccessor{TColor}.cs | 12 +-- .../ImageSharp.Benchmarks/General/Array2D.cs | 57 ++++++++++ .../General/ArrayCopy.cs | 5 +- .../Common/Fast2DArrayTests.cs | 71 ++++++++++++ 8 files changed, 260 insertions(+), 27 deletions(-) create mode 100644 src/ImageSharp/Common/Helpers/Fast2DArray{T}.cs create mode 100644 tests/ImageSharp.Benchmarks/General/Array2D.cs create mode 100644 tests/ImageSharp.Tests/Common/Fast2DArrayTests.cs diff --git a/src/ImageSharp/Common/Helpers/Fast2DArray{T}.cs b/src/ImageSharp/Common/Helpers/Fast2DArray{T}.cs new file mode 100644 index 0000000000..26ec816ce3 --- /dev/null +++ b/src/ImageSharp/Common/Helpers/Fast2DArray{T}.cs @@ -0,0 +1,102 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Diagnostics; + using System.Runtime.CompilerServices; + + /// + /// Provides fast access to 2D arrays. + /// + /// The type of elements in the array. + public struct Fast2DArray + { + /// + /// The 1D representation of the 2D array. + /// + public T[] Data; + + /// + /// Gets the width of the 2D array. + /// + public int Width; + + /// + /// Gets the height of the 2D array. + /// + public int Height; + + /// + /// Initializes a new instance of the struct. + /// + /// The 2D array to provide access to. + public Fast2DArray(T[,] data) + { + Guard.NotNull(data, nameof(data)); + this.Height = data.GetLength(0); + this.Width = data.GetLength(1); + + Guard.MustBeGreaterThan(this.Width, 0, nameof(this.Width)); + Guard.MustBeGreaterThan(this.Height, 0, nameof(this.Height)); + + this.Data = new T[this.Width * this.Height]; + + for (int y = 0; y < this.Height; y++) + { + for (int x = 0; x < this.Width; x++) + { + this.Data[(y * this.Width) + x] = data[y, x]; + } + } + } + + /// + /// Gets or sets the item at the specified position. + /// + /// The row-coordinate of the item. Must be greater than or equal to zero and less than the height of the array. + /// The column-coordinate of the item. Must be greater than or equal to zero and less than the width of the array. + /// The at the specified position. + public T this[int row, int column] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + this.CheckCoordinates(row, column); + return this.Data[(row * this.Width) + column]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set + { + this.CheckCoordinates(row, column); + this.Data[(row * this.Width) + column] = value; + } + } + + /// + /// Checks the coordinates to ensure they are within bounds. + /// + /// The row-coordinate of the item. Must be greater than zero and smaller than the height of the array. + /// The column-coordinate of the item. Must be greater than zero and smaller than the width of the array. + /// + /// Thrown if the coordinates are not within the bounds of the array. + /// + [Conditional("DEBUG")] + private void CheckCoordinates(int row, int column) + { + if (row < 0 || row >= this.Height) + { + throw new ArgumentOutOfRangeException(nameof(row), row, $"{row} is outwith the array bounds."); + } + + if (column < 0 || column >= this.Width) + { + throw new ArgumentOutOfRangeException(nameof(column), column, $"{column} is outwith the array bounds."); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Dithering/Ordered/Bayer.cs b/src/ImageSharp/Dithering/Ordered/Bayer.cs index c560889790..3d3d900233 100644 --- a/src/ImageSharp/Dithering/Ordered/Bayer.cs +++ b/src/ImageSharp/Dithering/Ordered/Bayer.cs @@ -17,23 +17,24 @@ namespace ImageSharp.Dithering.Ordered /// The threshold matrix. /// This is calculated by multiplying each value in the original matrix by 16 and subtracting 1 /// - private static readonly byte[][] ThresholdMatrix = - { - new byte[] { 15, 143, 47, 175 }, - new byte[] { 207, 79, 239, 111 }, - new byte[] { 63, 191, 31, 159 }, - new byte[] { 255, 127, 223, 95 } - }; + private static readonly Fast2DArray ThresholdMatrix = + new Fast2DArray(new byte[,] + { + { 15, 143, 47, 175 }, + { 207, 79, 239, 111 }, + { 63, 191, 31, 159 }, + { 255, 127, 223, 95 } + }); /// - public byte[][] Matrix { get; } = ThresholdMatrix; + public Fast2DArray Matrix { get; } = ThresholdMatrix; /// public void Dither(PixelAccessor pixels, TColor source, TColor upper, TColor lower, byte[] bytes, int index, int x, int y, int width, int height) where TColor : struct, IPackedPixel, IEquatable { source.ToXyzwBytes(bytes, 0); - pixels[x, y] = ThresholdMatrix[x % 3][y % 3] >= bytes[index] ? lower : upper; + pixels[x, y] = ThresholdMatrix[y % 3, x % 3] >= bytes[index] ? lower : upper; } } } \ No newline at end of file diff --git a/src/ImageSharp/Dithering/Ordered/IOrderedDither.cs b/src/ImageSharp/Dithering/Ordered/IOrderedDither.cs index 464b9d4199..e74a863a51 100644 --- a/src/ImageSharp/Dithering/Ordered/IOrderedDither.cs +++ b/src/ImageSharp/Dithering/Ordered/IOrderedDither.cs @@ -15,7 +15,7 @@ namespace ImageSharp.Dithering /// /// Gets the dithering matrix /// - byte[][] Matrix { get; } + Fast2DArray Matrix { get; } /// /// Transforms the image applying the dither matrix. This method alters the input pixels array diff --git a/src/ImageSharp/Dithering/Ordered/Ordered.cs b/src/ImageSharp/Dithering/Ordered/Ordered.cs index 38f8f21a57..bb1f21060d 100644 --- a/src/ImageSharp/Dithering/Ordered/Ordered.cs +++ b/src/ImageSharp/Dithering/Ordered/Ordered.cs @@ -17,23 +17,24 @@ namespace ImageSharp.Dithering.Ordered /// The threshold matrix. /// This is calculated by multiplying each value in the original matrix by 16 /// - private static readonly byte[][] ThresholdMatrix = - { - new byte[] { 0, 128, 32, 160 }, - new byte[] { 192, 64, 224, 96 }, - new byte[] { 48, 176, 16, 144 }, - new byte[] { 240, 112, 208, 80 } - }; + private static readonly Fast2DArray ThresholdMatrix = + new Fast2DArray(new byte[,] + { + { 0, 128, 32, 160 }, + { 192, 64, 224, 96 }, + { 48, 176, 16, 144 }, + { 240, 112, 208, 80 } + }); /// - public byte[][] Matrix { get; } = ThresholdMatrix; + public Fast2DArray Matrix { get; } = ThresholdMatrix; /// public void Dither(PixelAccessor pixels, TColor source, TColor upper, TColor lower, byte[] bytes, int index, int x, int y, int width, int height) where TColor : struct, IPackedPixel, IEquatable { source.ToXyzwBytes(bytes, 0); - pixels[x, y] = ThresholdMatrix[x % 3][y % 3] >= bytes[index] ? lower : upper; + pixels[x, y] = ThresholdMatrix[y % 3, x % 3] >= bytes[index] ? lower : upper; } } } \ No newline at end of file diff --git a/src/ImageSharp/Image/PixelAccessor{TColor}.cs b/src/ImageSharp/Image/PixelAccessor{TColor}.cs index f37ba7496f..55b16235a5 100644 --- a/src/ImageSharp/Image/PixelAccessor{TColor}.cs +++ b/src/ImageSharp/Image/PixelAccessor{TColor}.cs @@ -153,8 +153,8 @@ namespace ImageSharp /// /// Gets or sets the pixel at the specified position. /// - /// The x-coordinate of the pixel. Must be greater than zero and smaller than the width of the pixel. - /// The y-coordinate of the pixel. Must be greater than zero and smaller than the width of the pixel. + /// The x-coordinate of the pixel. Must be greater than or equal to zero and less than the width of the image. + /// The y-coordinate of the pixel. Must be greater than or equal to zero and less than the height of the image. /// The at the specified position. public TColor this[int x, int y] { @@ -625,8 +625,8 @@ namespace ImageSharp /// Checks that the given area and offset are within the bounds of the image. /// /// The area. - /// The x-coordinate of the pixel. Must be greater than zero and smaller than the width of the pixel. - /// The y-coordinate of the pixel. Must be greater than zero and smaller than the width of the pixel. + /// The x-coordinate of the pixel. Must be greater than zero and less than the width of the image. + /// The y-coordinate of the pixel. Must be greater than zero and less than the height of the image. /// /// Thrown if the dimensions are not within the bounds of the image. /// @@ -649,8 +649,8 @@ namespace ImageSharp /// /// Checks the coordinates to ensure they are within bounds. /// - /// The x-coordinate of the pixel. Must be greater than zero and smaller than the width of the pixel. - /// The y-coordinate of the pixel. Must be greater than zero and smaller than the width of the pixel. + /// The x-coordinate of the pixel. Must be greater than zero and less than the width of the image. + /// The y-coordinate of the pixel. Must be greater than zero and less than the height of the image. /// /// Thrown if the coordinates are not within the bounds of the image. /// diff --git a/tests/ImageSharp.Benchmarks/General/Array2D.cs b/tests/ImageSharp.Benchmarks/General/Array2D.cs new file mode 100644 index 0000000000..a01ba77adc --- /dev/null +++ b/tests/ImageSharp.Benchmarks/General/Array2D.cs @@ -0,0 +1,57 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Benchmarks.General +{ + using BenchmarkDotNet.Attributes; + + public class Array2D + { + private float[,] data; + + private float[][] jaggedData; + + private Fast2DArray fastData; + + [Params(10, 100, 1000, 10000)] + public int Count { get; set; } + + public int Index { get; set; } + + [Setup] + public void SetUp() + { + this.data = new float[this.Count, this.Count]; + this.jaggedData = new float[this.Count][]; + + for (int i = 0; i < this.Count; i++) + { + this.jaggedData[i] = new float[this.Count]; + } + + this.fastData = new Fast2DArray(this.data); + + this.Index = this.Count / 2; + } + + [Benchmark(Baseline = true, Description = "Array access using 2D array")] + public float ArrayIndex() + { + return this.data[this.Index, this.Index]; + } + + [Benchmark(Description = "Array access using a jagged array")] + public float ArrayJaggedIndex() + { + return this.jaggedData[this.Index][this.Index]; + } + + [Benchmark(Description = "Array access using Fast2DArray")] + public float ArrayFastIndex() + { + return this.fastData[this.Index, this.Index]; + } + } +} diff --git a/tests/ImageSharp.Benchmarks/General/ArrayCopy.cs b/tests/ImageSharp.Benchmarks/General/ArrayCopy.cs index 88d47db519..dddd83e424 100644 --- a/tests/ImageSharp.Benchmarks/General/ArrayCopy.cs +++ b/tests/ImageSharp.Benchmarks/General/ArrayCopy.cs @@ -2,6 +2,7 @@ // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // + namespace ImageSharp.Benchmarks.General { using System; @@ -14,9 +15,9 @@ namespace ImageSharp.Benchmarks.General [Params(100, 1000, 10000)] public int Count { get; set; } - byte[] source; + private byte[] source; - byte[] destination; + private byte[] destination; [Setup] public void SetUp() diff --git a/tests/ImageSharp.Tests/Common/Fast2DArrayTests.cs b/tests/ImageSharp.Tests/Common/Fast2DArrayTests.cs new file mode 100644 index 0000000000..903ea6f8d9 --- /dev/null +++ b/tests/ImageSharp.Tests/Common/Fast2DArrayTests.cs @@ -0,0 +1,71 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Common +{ + using System; + + using Xunit; + + public class Fast2DArrayTests + { + private static readonly float[,] FloydSteinbergMatrix = + { + { 0, 0, 7 }, + { 3, 5, 1 } + }; + + [Fact] + public void Fast2DArrayThrowsOnNullInitializer() + { + Assert.Throws(() => + { + Fast2DArray fast = new Fast2DArray(null); + }); + } + + [Fact] + public void Fast2DArrayThrowsOnEmptyInitializer() + { + Assert.Throws(() => + { + Fast2DArray fast = new Fast2DArray(new float[0, 0]); + }); + } + + [Fact] + public void Fast2DArrayReturnsCorrectDimensions() + { + Fast2DArray fast = new Fast2DArray(FloydSteinbergMatrix); + Assert.True(fast.Width == FloydSteinbergMatrix.GetLength(1)); + Assert.True(fast.Height == FloydSteinbergMatrix.GetLength(0)); + } + + [Fact] + public void Fast2DArrayGetReturnsCorrectResults() + { + Fast2DArray fast = new Fast2DArray(FloydSteinbergMatrix); + + for (int row = 0; row < fast.Height; row++) + { + for (int column = 0; column < fast.Width; column++) + { + Assert.True(Math.Abs(fast[row, column] - FloydSteinbergMatrix[row, column]) < Constants.Epsilon); + } + } + } + + [Fact] + public void Fast2DArrayGetSetReturnsCorrectResults() + { + Fast2DArray fast = new Fast2DArray(new float[4, 4]); + const float Val = 5F; + + fast[3, 3] = Val; + + Assert.True(Math.Abs(Val - fast[3, 3]) < Constants.Epsilon); + } + } +} \ No newline at end of file From 1247c7c721755d3a80034b1bcfad810efda03c24 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 17 Feb 2017 09:59:19 +1100 Subject: [PATCH 070/142] Use Fast2DArray for error diffusion. --- .../Dithering/ErrorDiffusion/Atkinson.cs | 11 +++++---- .../Dithering/ErrorDiffusion/Burks.cs | 9 ++++---- .../Dithering/ErrorDiffusion/ErrorDiffuser.cs | 23 +++++++++++-------- .../ErrorDiffusion/FloydSteinberg.cs | 9 ++++---- .../ErrorDiffusion/IErrorDiffuser.cs | 2 +- .../ErrorDiffusion/JarvisJudiceNinke.cs | 11 +++++---- .../Dithering/ErrorDiffusion/Sierra2.cs | 9 ++++---- .../Dithering/ErrorDiffusion/Sierra3.cs | 11 +++++---- .../Dithering/ErrorDiffusion/SierraLite.cs | 9 ++++---- .../Dithering/ErrorDiffusion/Stucki.cs | 11 +++++---- 10 files changed, 59 insertions(+), 46 deletions(-) diff --git a/src/ImageSharp/Dithering/ErrorDiffusion/Atkinson.cs b/src/ImageSharp/Dithering/ErrorDiffusion/Atkinson.cs index 76a5f96ec9..1fa6852c22 100644 --- a/src/ImageSharp/Dithering/ErrorDiffusion/Atkinson.cs +++ b/src/ImageSharp/Dithering/ErrorDiffusion/Atkinson.cs @@ -14,12 +14,13 @@ namespace ImageSharp.Dithering /// /// The diffusion matrix /// - private static readonly byte[][] AtkinsonMatrix = + private static readonly Fast2DArray AtkinsonMatrix = + new Fast2DArray(new float[,] { - new byte[] { 0, 0, 1, 1 }, - new byte[] { 1, 1, 1, 0 }, - new byte[] { 0, 1, 0, 0 } - }; + { 0, 0, 1, 1 }, + { 1, 1, 1, 0 }, + { 0, 1, 0, 0 } + }); /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp/Dithering/ErrorDiffusion/Burks.cs b/src/ImageSharp/Dithering/ErrorDiffusion/Burks.cs index 2d71b9cc35..a4adcb0a49 100644 --- a/src/ImageSharp/Dithering/ErrorDiffusion/Burks.cs +++ b/src/ImageSharp/Dithering/ErrorDiffusion/Burks.cs @@ -14,11 +14,12 @@ namespace ImageSharp.Dithering /// /// The diffusion matrix /// - private static readonly byte[][] BurksMatrix = + private static readonly Fast2DArray BurksMatrix = + new Fast2DArray(new float[,] { - new byte[] { 0, 0, 0, 8, 4 }, - new byte[] { 2, 4, 8, 4, 2 } - }; + { 0, 0, 0, 8, 4 }, + { 2, 4, 8, 4, 2 } + }); /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuser.cs b/src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuser.cs index 0c2a1dc404..4fd5476f05 100644 --- a/src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuser.cs +++ b/src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuser.cs @@ -22,12 +22,12 @@ namespace ImageSharp.Dithering /// /// The matrix width /// - private readonly byte matrixHeight; + private readonly int matrixHeight; /// /// The matrix height /// - private readonly byte matrixWidth; + private readonly int matrixWidth; /// /// The offset at which to start the dithering operation. @@ -39,20 +39,22 @@ namespace ImageSharp.Dithering /// /// The dithering matrix. /// The divisor. - protected ErrorDiffuser(byte[][] matrix, byte divisor) + protected ErrorDiffuser(Fast2DArray matrix, byte divisor) { Guard.NotNull(matrix, nameof(matrix)); Guard.MustBeGreaterThan(divisor, 0, nameof(divisor)); this.Matrix = matrix; - this.matrixWidth = (byte)matrix[0].Length; - this.matrixHeight = (byte)matrix.Length; + this.matrixWidth = this.Matrix.Width; + this.matrixHeight = this.Matrix.Height; this.divisorVector = new Vector4(divisor); this.startingOffset = 0; for (int i = 0; i < this.matrixWidth; i++) { - if (matrix[0][i] != 0) + // Good to disable here as we are not comparing matematical output. + // ReSharper disable once CompareOfFloatsByEqualityOperator + if (matrix[0, i] != 0) { this.startingOffset = (byte)(i - 1); break; @@ -61,7 +63,7 @@ namespace ImageSharp.Dithering } /// - public byte[][] Matrix { get; } + public Fast2DArray Matrix { get; } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -85,13 +87,16 @@ namespace ImageSharp.Dithering if (matrixX > 0 && matrixX < width && matrixY > 0 && matrixY < height) { - byte coefficient = this.Matrix[row][col]; + float coefficient = this.Matrix[row, col]; + + // Good to disable here as we are not comparing matematical output. + // ReSharper disable once CompareOfFloatsByEqualityOperator if (coefficient == 0) { continue; } - Vector4 coefficientVector = new Vector4(this.Matrix[row][col]); + Vector4 coefficientVector = new Vector4(coefficient); Vector4 offsetColor = pixels[matrixX, matrixY].ToVector4(); Vector4 result = ((error * coefficientVector) / this.divisorVector) + offsetColor; result.W = offsetColor.W; diff --git a/src/ImageSharp/Dithering/ErrorDiffusion/FloydSteinberg.cs b/src/ImageSharp/Dithering/ErrorDiffusion/FloydSteinberg.cs index cf91bd8fab..7b67d2dd12 100644 --- a/src/ImageSharp/Dithering/ErrorDiffusion/FloydSteinberg.cs +++ b/src/ImageSharp/Dithering/ErrorDiffusion/FloydSteinberg.cs @@ -14,11 +14,12 @@ namespace ImageSharp.Dithering /// /// The diffusion matrix /// - private static readonly byte[][] FloydSteinbergMatrix = + private static readonly Fast2DArray FloydSteinbergMatrix = + new Fast2DArray(new float[,] { - new byte[] { 0, 0, 7 }, - new byte[] { 3, 5, 1 } - }; + { 0, 0, 7 }, + { 3, 5, 1 } + }); /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs b/src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs index eb6c27167e..fbee27088e 100644 --- a/src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs +++ b/src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs @@ -15,7 +15,7 @@ namespace ImageSharp.Dithering /// /// Gets the dithering matrix /// - byte[][] Matrix { get; } + Fast2DArray Matrix { get; } /// /// Transforms the image applying the dither matrix. This method alters the input pixels array diff --git a/src/ImageSharp/Dithering/ErrorDiffusion/JarvisJudiceNinke.cs b/src/ImageSharp/Dithering/ErrorDiffusion/JarvisJudiceNinke.cs index 5f26d8b248..32f38fbc85 100644 --- a/src/ImageSharp/Dithering/ErrorDiffusion/JarvisJudiceNinke.cs +++ b/src/ImageSharp/Dithering/ErrorDiffusion/JarvisJudiceNinke.cs @@ -14,12 +14,13 @@ namespace ImageSharp.Dithering /// /// The diffusion matrix /// - private static readonly byte[][] JarvisJudiceNinkeMatrix = + private static readonly Fast2DArray JarvisJudiceNinkeMatrix = + new Fast2DArray(new float[,] { - new byte[] { 0, 0, 0, 7, 5 }, - new byte[] { 3, 5, 7, 5, 3 }, - new byte[] { 1, 3, 5, 3, 1 } - }; + { 0, 0, 0, 7, 5 }, + { 3, 5, 7, 5, 3 }, + { 1, 3, 5, 3, 1 } + }); /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp/Dithering/ErrorDiffusion/Sierra2.cs b/src/ImageSharp/Dithering/ErrorDiffusion/Sierra2.cs index 44132747d9..47b14944e2 100644 --- a/src/ImageSharp/Dithering/ErrorDiffusion/Sierra2.cs +++ b/src/ImageSharp/Dithering/ErrorDiffusion/Sierra2.cs @@ -14,11 +14,12 @@ namespace ImageSharp.Dithering /// /// The diffusion matrix /// - private static readonly byte[][] Sierra2Matrix = + private static readonly Fast2DArray Sierra2Matrix = + new Fast2DArray(new float[,] { - new byte[] { 0, 0, 0, 4, 3 }, - new byte[] { 1, 2, 3, 2, 1 } - }; + { 0, 0, 0, 4, 3 }, + { 1, 2, 3, 2, 1 } + }); /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp/Dithering/ErrorDiffusion/Sierra3.cs b/src/ImageSharp/Dithering/ErrorDiffusion/Sierra3.cs index 01f6b5ded7..ae33954cfd 100644 --- a/src/ImageSharp/Dithering/ErrorDiffusion/Sierra3.cs +++ b/src/ImageSharp/Dithering/ErrorDiffusion/Sierra3.cs @@ -14,12 +14,13 @@ namespace ImageSharp.Dithering /// /// The diffusion matrix /// - private static readonly byte[][] Sierra3Matrix = + private static readonly Fast2DArray Sierra3Matrix = + new Fast2DArray(new float[,] { - new byte[] { 0, 0, 0, 5, 3 }, - new byte[] { 2, 4, 5, 4, 2 }, - new byte[] { 0, 2, 3, 2, 0 } - }; + { 0, 0, 0, 5, 3 }, + { 2, 4, 5, 4, 2 }, + { 0, 2, 3, 2, 0 } + }); /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp/Dithering/ErrorDiffusion/SierraLite.cs b/src/ImageSharp/Dithering/ErrorDiffusion/SierraLite.cs index 42fa8c23d2..8a1e178165 100644 --- a/src/ImageSharp/Dithering/ErrorDiffusion/SierraLite.cs +++ b/src/ImageSharp/Dithering/ErrorDiffusion/SierraLite.cs @@ -14,11 +14,12 @@ namespace ImageSharp.Dithering /// /// The diffusion matrix /// - private static readonly byte[][] SierraLiteMatrix = + private static readonly Fast2DArray SierraLiteMatrix = + new Fast2DArray(new float[,] { - new byte[] { 0, 0, 2 }, - new byte[] { 1, 1, 0 } - }; + { 0, 0, 2 }, + { 1, 1, 0 } + }); /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp/Dithering/ErrorDiffusion/Stucki.cs b/src/ImageSharp/Dithering/ErrorDiffusion/Stucki.cs index 0e462630e9..b5d22b2592 100644 --- a/src/ImageSharp/Dithering/ErrorDiffusion/Stucki.cs +++ b/src/ImageSharp/Dithering/ErrorDiffusion/Stucki.cs @@ -14,12 +14,13 @@ namespace ImageSharp.Dithering /// /// The diffusion matrix /// - private static readonly byte[][] StuckiMatrix = + private static readonly Fast2DArray StuckiMatrix = + new Fast2DArray(new float[,] { - new byte[] { 0, 0, 0, 8, 4 }, - new byte[] { 2, 4, 8, 4, 2 }, - new byte[] { 1, 2, 4, 2, 1 } - }; + { 0, 0, 0, 8, 4 }, + { 2, 4, 8, 4, 2 }, + { 1, 2, 4, 2, 1 } + }); /// /// Initializes a new instance of the class. From 8b529bbd0bed8b3c0109cf9fd46a84ef1a9f6e01 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Wed, 15 Feb 2017 21:02:21 +0000 Subject: [PATCH 071/142] Add namedcolors to work for all TColor types --- src/ImageSharp.Processing/Overlays/Glow.cs | 14 +- .../Overlays/Vignette.cs | 14 +- .../Binarization/BinaryThresholdProcessor.cs | 9 +- .../ErrorDiffusionDitherProcessor.cs | 9 +- .../Binarization/OrderedDitherProcessor.cs | 9 +- .../ColorMatrix/LomographProcessor.cs | 6 +- .../ColorMatrix/PolaroidProcessor.cs | 12 +- .../Processors/Overlays/GlowProcessor.cs | 7 +- .../Processors/Overlays/VignetteProcessor.cs | 7 +- src/ImageSharp/Colors/Color.cs | 60 +- src/ImageSharp/Colors/ColorBuilder{TColor}.cs | 110 +++ src/ImageSharp/Colors/ColorDefinitions.cs | 284 +++---- src/ImageSharp/Colors/NamedColors{TColor}.cs | 727 ++++++++++++++++++ .../Colors/ColorDefinitionTests.cs | 38 + tests/ImageSharp.Tests/Colors/ColorTests.cs | 20 +- .../Image/PixelAccessorTests.cs | 9 +- 16 files changed, 1056 insertions(+), 279 deletions(-) create mode 100644 src/ImageSharp/Colors/ColorBuilder{TColor}.cs create mode 100644 src/ImageSharp/Colors/NamedColors{TColor}.cs create mode 100644 tests/ImageSharp.Tests/Colors/ColorDefinitionTests.cs diff --git a/src/ImageSharp.Processing/Overlays/Glow.cs b/src/ImageSharp.Processing/Overlays/Glow.cs index 91fbfc7f62..9c5fe017f2 100644 --- a/src/ImageSharp.Processing/Overlays/Glow.cs +++ b/src/ImageSharp.Processing/Overlays/Glow.cs @@ -23,7 +23,7 @@ namespace ImageSharp public static Image Glow(this Image source) where TColor : struct, IPackedPixel, IEquatable { - return Glow(source, default(TColor), source.Bounds.Width * .5F, source.Bounds); + return Glow(source, NamedColors.Black, source.Bounds.Width * .5F, source.Bounds); } /// @@ -49,7 +49,7 @@ namespace ImageSharp public static Image Glow(this Image source, float radius) where TColor : struct, IPackedPixel, IEquatable { - return Glow(source, default(TColor), radius, source.Bounds); + return Glow(source, NamedColors.Black, radius, source.Bounds); } /// @@ -64,7 +64,7 @@ namespace ImageSharp public static Image Glow(this Image source, Rectangle rectangle) where TColor : struct, IPackedPixel, IEquatable { - return Glow(source, default(TColor), 0, rectangle); + return Glow(source, NamedColors.Black, 0, rectangle); } /// @@ -81,13 +81,7 @@ namespace ImageSharp public static Image Glow(this Image source, TColor color, float radius, Rectangle rectangle) where TColor : struct, IPackedPixel, IEquatable { - GlowProcessor processor = new GlowProcessor { Radius = radius, }; - - if (!color.Equals(default(TColor))) - { - processor.GlowColor = color; - } - + GlowProcessor processor = new GlowProcessor(color) { Radius = radius, }; source.ApplyProcessor(processor, rectangle); return source; } diff --git a/src/ImageSharp.Processing/Overlays/Vignette.cs b/src/ImageSharp.Processing/Overlays/Vignette.cs index c6cd0ab1e8..4a505ad9bb 100644 --- a/src/ImageSharp.Processing/Overlays/Vignette.cs +++ b/src/ImageSharp.Processing/Overlays/Vignette.cs @@ -23,7 +23,7 @@ namespace ImageSharp public static Image Vignette(this Image source) where TColor : struct, IPackedPixel, IEquatable { - return Vignette(source, default(TColor), source.Bounds.Width * .5F, source.Bounds.Height * .5F, source.Bounds); + return Vignette(source, NamedColors.Black, source.Bounds.Width * .5F, source.Bounds.Height * .5F, source.Bounds); } /// @@ -50,7 +50,7 @@ namespace ImageSharp public static Image Vignette(this Image source, float radiusX, float radiusY) where TColor : struct, IPackedPixel, IEquatable { - return Vignette(source, default(TColor), radiusX, radiusY, source.Bounds); + return Vignette(source, NamedColors.Black, radiusX, radiusY, source.Bounds); } /// @@ -65,7 +65,7 @@ namespace ImageSharp public static Image Vignette(this Image source, Rectangle rectangle) where TColor : struct, IPackedPixel, IEquatable { - return Vignette(source, default(TColor), 0, 0, rectangle); + return Vignette(source, NamedColors.Black, 0, 0, rectangle); } /// @@ -83,13 +83,7 @@ namespace ImageSharp public static Image Vignette(this Image source, TColor color, float radiusX, float radiusY, Rectangle rectangle) where TColor : struct, IPackedPixel, IEquatable { - VignetteProcessor processor = new VignetteProcessor { RadiusX = radiusX, RadiusY = radiusY }; - - if (!color.Equals(default(TColor))) - { - processor.VignetteColor = color; - } - + VignetteProcessor processor = new VignetteProcessor(color) { RadiusX = radiusX, RadiusY = radiusY }; source.ApplyProcessor(processor, rectangle); return source; } diff --git a/src/ImageSharp.Processing/Processors/Binarization/BinaryThresholdProcessor.cs b/src/ImageSharp.Processing/Processors/Binarization/BinaryThresholdProcessor.cs index cb37587480..e4010413fd 100644 --- a/src/ImageSharp.Processing/Processors/Binarization/BinaryThresholdProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Binarization/BinaryThresholdProcessor.cs @@ -27,13 +27,8 @@ namespace ImageSharp.Processing.Processors this.Threshold = threshold; // Default to white/black for upper/lower. - TColor upper = default(TColor); - upper.PackFromBytes(255, 255, 255, 255); - this.UpperColor = upper; - - TColor lower = default(TColor); - lower.PackFromBytes(0, 0, 0, 255); - this.LowerColor = lower; + this.UpperColor = NamedColors.White; + this.LowerColor = NamedColors.Black; } /// diff --git a/src/ImageSharp.Processing/Processors/Binarization/ErrorDiffusionDitherProcessor.cs b/src/ImageSharp.Processing/Processors/Binarization/ErrorDiffusionDitherProcessor.cs index 6429ce6f08..25b464c692 100644 --- a/src/ImageSharp.Processing/Processors/Binarization/ErrorDiffusionDitherProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Binarization/ErrorDiffusionDitherProcessor.cs @@ -29,13 +29,8 @@ namespace ImageSharp.Processing.Processors this.Threshold = threshold; // Default to white/black for upper/lower. - TColor upper = default(TColor); - upper.PackFromBytes(255, 255, 255, 255); - this.UpperColor = upper; - - TColor lower = default(TColor); - lower.PackFromBytes(0, 0, 0, 255); - this.LowerColor = lower; + this.UpperColor = NamedColors.White; + this.LowerColor = NamedColors.Black; } /// diff --git a/src/ImageSharp.Processing/Processors/Binarization/OrderedDitherProcessor.cs b/src/ImageSharp.Processing/Processors/Binarization/OrderedDitherProcessor.cs index ae1224bad0..f201ba49aa 100644 --- a/src/ImageSharp.Processing/Processors/Binarization/OrderedDitherProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Binarization/OrderedDitherProcessor.cs @@ -37,13 +37,8 @@ namespace ImageSharp.Processing.Processors this.Index = index; // Default to white/black for upper/lower. - TColor upper = default(TColor); - upper.PackFromBytes(255, 255, 255, 255); - this.UpperColor = upper; - - TColor lower = default(TColor); - lower.PackFromBytes(0, 0, 0, 255); - this.LowerColor = lower; + this.UpperColor = NamedColors.White; + this.LowerColor = NamedColors.Black; } /// diff --git a/src/ImageSharp.Processing/Processors/ColorMatrix/LomographProcessor.cs b/src/ImageSharp.Processing/Processors/ColorMatrix/LomographProcessor.cs index 731e04bf74..ad166b1347 100644 --- a/src/ImageSharp.Processing/Processors/ColorMatrix/LomographProcessor.cs +++ b/src/ImageSharp.Processing/Processors/ColorMatrix/LomographProcessor.cs @@ -15,6 +15,8 @@ namespace ImageSharp.Processing.Processors public class LomographProcessor : ColorMatrixFilter where TColor : struct, IPackedPixel, IEquatable { + private static readonly TColor VeryDarkGreen = ColorBuilder.FromRGBA(0, 10, 0, 255); + /// public override Matrix4x4 Matrix => new Matrix4x4() { @@ -30,9 +32,7 @@ namespace ImageSharp.Processing.Processors /// protected override void AfterApply(ImageBase source, Rectangle sourceRectangle) { - TColor packed = default(TColor); - packed.PackFromVector4(new Color(0, 10, 0).ToVector4()); // Very dark (mostly black) lime green. - new VignetteProcessor { VignetteColor = packed }.Apply(source, sourceRectangle); + new VignetteProcessor(VeryDarkGreen).Apply(source, sourceRectangle); } } } \ No newline at end of file diff --git a/src/ImageSharp.Processing/Processors/ColorMatrix/PolaroidProcessor.cs b/src/ImageSharp.Processing/Processors/ColorMatrix/PolaroidProcessor.cs index 678edf011a..5df11160fb 100644 --- a/src/ImageSharp.Processing/Processors/ColorMatrix/PolaroidProcessor.cs +++ b/src/ImageSharp.Processing/Processors/ColorMatrix/PolaroidProcessor.cs @@ -15,6 +15,9 @@ namespace ImageSharp.Processing.Processors public class PolaroidProcessor : ColorMatrixFilter where TColor : struct, IPackedPixel, IEquatable { + private static TColor veryDarkOrange = ColorBuilder.FromRGB(102, 34, 0); + private static TColor lightOrange = ColorBuilder.FromRGBA(255, 153, 102, 178); + /// public override Matrix4x4 Matrix => new Matrix4x4() { @@ -36,13 +39,8 @@ namespace ImageSharp.Processing.Processors /// protected override void AfterApply(ImageBase source, Rectangle sourceRectangle) { - TColor packedV = default(TColor); - packedV.PackFromVector4(new Color(102, 34, 0).ToVector4()); // Very dark orange [Brown tone] - new VignetteProcessor { VignetteColor = packedV }.Apply(source, sourceRectangle); - - TColor packedG = default(TColor); - packedG.PackFromVector4(new Color(255, 153, 102, 178).ToVector4()); // Light orange - new GlowProcessor { GlowColor = packedG, Radius = source.Width / 4F }.Apply(source, sourceRectangle); + new VignetteProcessor(veryDarkOrange).Apply(source, sourceRectangle); + new GlowProcessor(lightOrange) { Radius = source.Width / 4F }.Apply(source, sourceRectangle); } } } \ No newline at end of file diff --git a/src/ImageSharp.Processing/Processors/Overlays/GlowProcessor.cs b/src/ImageSharp.Processing/Processors/Overlays/GlowProcessor.cs index f0e32f1fa3..690f57ab73 100644 --- a/src/ImageSharp.Processing/Processors/Overlays/GlowProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Overlays/GlowProcessor.cs @@ -17,12 +17,11 @@ namespace ImageSharp.Processing.Processors where TColor : struct, IPackedPixel, IEquatable { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - public GlowProcessor() + /// The color or the glow. + public GlowProcessor(TColor color) { - TColor color = default(TColor); - color.PackFromVector4(Color.Black.ToVector4()); this.GlowColor = color; } diff --git a/src/ImageSharp.Processing/Processors/Overlays/VignetteProcessor.cs b/src/ImageSharp.Processing/Processors/Overlays/VignetteProcessor.cs index 8449f18332..6e94a4c2ac 100644 --- a/src/ImageSharp.Processing/Processors/Overlays/VignetteProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Overlays/VignetteProcessor.cs @@ -17,12 +17,11 @@ namespace ImageSharp.Processing.Processors where TColor : struct, IPackedPixel, IEquatable { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - public VignetteProcessor() + /// The color of the vignette. + public VignetteProcessor(TColor color) { - TColor color = default(TColor); - color.PackFromVector4(Color.Black.ToVector4()); this.VignetteColor = color; } diff --git a/src/ImageSharp/Colors/Color.cs b/src/ImageSharp/Colors/Color.cs index b2f9437ca7..730fe45a52 100644 --- a/src/ImageSharp/Colors/Color.cs +++ b/src/ImageSharp/Colors/Color.cs @@ -67,28 +67,6 @@ namespace ImageSharp this.packedValue = Pack(r, g, b, a); } - /// - /// Initializes a new instance of the struct. - /// - /// - /// The hexadecimal representation of the combined color components arranged - /// in rgb, rgba, rrggbb, or rrggbbaa format to match web syntax. - /// - public Color(string hex) - { - Guard.NotNullOrEmpty(hex, nameof(hex)); - - hex = ToRgbaHex(hex); - - if (hex == null || !uint.TryParse(hex, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out this.packedValue)) - { - throw new ArgumentException("Hexadecimal string is not in the correct format.", nameof(hex)); - } - - // Order parsed from hex string will be backwards, so reverse it. - this.packedValue = Pack(this.A, this.B, this.G, this.R); - } - /// /// Initializes a new instance of the struct. /// @@ -264,7 +242,7 @@ namespace ImageSharp /// public static Color FromHex(string hex) { - return new Color(hex); + return ColorBuilder.FromHex(hex); } /// @@ -421,39 +399,5 @@ namespace ImageSharp { return (uint)(x << RedShift | y << GreenShift | z << BlueShift | w << AlphaShift); } - - /// - /// Converts the specified hex value to an rrggbbaa hex value. - /// - /// The hex value to convert. - /// - /// A rrggbbaa hex value. - /// - private static string ToRgbaHex(string hex) - { - hex = hex.StartsWith("#") ? hex.Substring(1) : hex; - - if (hex.Length == 8) - { - return hex; - } - - if (hex.Length == 6) - { - return hex + "FF"; - } - - if (hex.Length < 3 || hex.Length > 4) - { - return null; - } - - string red = char.ToString(hex[0]); - string green = char.ToString(hex[1]); - string blue = char.ToString(hex[2]); - string alpha = hex.Length == 3 ? "F" : char.ToString(hex[3]); - - return red + red + green + green + blue + blue + alpha + alpha; - } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Colors/ColorBuilder{TColor}.cs b/src/ImageSharp/Colors/ColorBuilder{TColor}.cs new file mode 100644 index 0000000000..47506af49f --- /dev/null +++ b/src/ImageSharp/Colors/ColorBuilder{TColor}.cs @@ -0,0 +1,110 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Globalization; + + /// + /// A set of named colors mapped to the provided Color space. + /// + /// The type of the color. + public static class ColorBuilder + where TColor : struct, IPackedPixel, IEquatable + { + /// + /// Creates a new representation from the string representing a color. + /// + /// + /// The hexadecimal representation of the combined color components arranged + /// in rgb, rgba, rrggbb, or rrggbbaa format to match web syntax. + /// + /// Returns a that represents the color defined by the provided RGBA heax string. + public static TColor FromHex(string hex) + { + Guard.NotNullOrEmpty(hex, nameof(hex)); + + hex = ToRgbaHex(hex); + uint packedValue; + if (hex == null || !uint.TryParse(hex, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out packedValue)) + { + throw new ArgumentException("Hexadecimal string is not in the correct format.", nameof(hex)); + } + + TColor result = default(TColor); + + result.PackFromBytes( + (byte)(packedValue >> 24), + (byte)(packedValue >> 16), + (byte)(packedValue >> 8), + (byte)(packedValue >> 0)); + return result; + } + + /// + /// Creates a new representation from standard RGB bytes with 100% opacity. + /// + /// The red intensity. + /// The green intensity. + /// The blue intensity. + /// Returns a that represents the color defined by the provided RGB values with 100% opacity. + public static TColor FromRGB(byte red, byte green, byte blue) + { + TColor color = default(TColor); + color.PackFromBytes(red, green, blue, 255); + return color; + } + + /// + /// Creates a new representation from standard RGBA bytes. + /// + /// The red intensity. + /// The green intensity. + /// The blue intensity. + /// The alpha intensity. + /// Returns a that represents the color defined by the provided RGBA values. + public static TColor FromRGBA(byte red, byte green, byte blue, byte alpha) + { + TColor color = default(TColor); + color.PackFromBytes(red, green, blue, alpha); + return color; + } + + /// + /// Converts the specified hex value to an rrggbbaa hex value. + /// + /// The hex value to convert. + /// + /// A rrggbbaa hex value. + /// + private static string ToRgbaHex(string hex) + { + hex = hex.StartsWith("#") ? hex.Substring(1) : hex; + + if (hex.Length == 8) + { + return hex; + } + + if (hex.Length == 6) + { + return hex + "FF"; + } + + if (hex.Length < 3 || hex.Length > 4) + { + return null; + } + + string red = char.ToString(hex[0]); + string green = char.ToString(hex[1]); + string blue = char.ToString(hex[2]); + string alpha = hex.Length == 3 ? "F" : char.ToString(hex[3]); + + return string.Concat(red, red, green, green, blue, blue, alpha, alpha); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Colors/ColorDefinitions.cs b/src/ImageSharp/Colors/ColorDefinitions.cs index 5c1c30fbd5..65165289df 100644 --- a/src/ImageSharp/Colors/ColorDefinitions.cs +++ b/src/ImageSharp/Colors/ColorDefinitions.cs @@ -18,711 +18,711 @@ namespace ImageSharp /// /// Represents a matching the W3C definition that has an hex value of #F0F8FF. /// - public static readonly Color AliceBlue = new Color(240, 248, 255, 255); + public static readonly Color AliceBlue = NamedColors.AliceBlue; /// /// Represents a matching the W3C definition that has an hex value of #FAEBD7. /// - public static readonly Color AntiqueWhite = new Color(250, 235, 215, 255); + public static readonly Color AntiqueWhite = NamedColors.AntiqueWhite; /// /// Represents a matching the W3C definition that has an hex value of #00FFFF. /// - public static readonly Color Aqua = new Color(0, 255, 255, 255); + public static readonly Color Aqua = NamedColors.Aqua; /// /// Represents a matching the W3C definition that has an hex value of #7FFFD4. /// - public static readonly Color Aquamarine = new Color(127, 255, 212, 255); + public static readonly Color Aquamarine = NamedColors.Aquamarine; /// /// Represents a matching the W3C definition that has an hex value of #F0FFFF. /// - public static readonly Color Azure = new Color(240, 255, 255, 255); + public static readonly Color Azure = NamedColors.Azure; /// /// Represents a matching the W3C definition that has an hex value of #F5F5DC. /// - public static readonly Color Beige = new Color(245, 245, 220, 255); + public static readonly Color Beige = NamedColors.Beige; /// /// Represents a matching the W3C definition that has an hex value of #FFE4C4. /// - public static readonly Color Bisque = new Color(255, 228, 196, 255); + public static readonly Color Bisque = NamedColors.Bisque; /// /// Represents a matching the W3C definition that has an hex value of #000000. /// - public static readonly Color Black = new Color(0, 0, 0, 255); + public static readonly Color Black = NamedColors.Black; /// /// Represents a matching the W3C definition that has an hex value of #FFEBCD. /// - public static readonly Color BlanchedAlmond = new Color(255, 235, 205, 255); + public static readonly Color BlanchedAlmond = NamedColors.BlanchedAlmond; /// /// Represents a matching the W3C definition that has an hex value of #0000FF. /// - public static readonly Color Blue = new Color(0, 0, 255, 255); + public static readonly Color Blue = NamedColors.Blue; /// /// Represents a matching the W3C definition that has an hex value of #8A2BE2. /// - public static readonly Color BlueViolet = new Color(138, 43, 226, 255); + public static readonly Color BlueViolet = NamedColors.BlueViolet; /// /// Represents a matching the W3C definition that has an hex value of #A52A2A. /// - public static readonly Color Brown = new Color(165, 42, 42, 255); + public static readonly Color Brown = NamedColors.Brown; /// /// Represents a matching the W3C definition that has an hex value of #DEB887. /// - public static readonly Color BurlyWood = new Color(222, 184, 135, 255); + public static readonly Color BurlyWood = NamedColors.BurlyWood; /// /// Represents a matching the W3C definition that has an hex value of #5F9EA0. /// - public static readonly Color CadetBlue = new Color(95, 158, 160, 255); + public static readonly Color CadetBlue = NamedColors.CadetBlue; /// /// Represents a matching the W3C definition that has an hex value of #7FFF00. /// - public static readonly Color Chartreuse = new Color(127, 255, 0, 255); + public static readonly Color Chartreuse = NamedColors.Chartreuse; /// /// Represents a matching the W3C definition that has an hex value of #D2691E. /// - public static readonly Color Chocolate = new Color(210, 105, 30, 255); + public static readonly Color Chocolate = NamedColors.Chocolate; /// /// Represents a matching the W3C definition that has an hex value of #FF7F50. /// - public static readonly Color Coral = new Color(255, 127, 80, 255); + public static readonly Color Coral = NamedColors.Coral; /// /// Represents a matching the W3C definition that has an hex value of #6495ED. /// - public static readonly Color CornflowerBlue = new Color(100, 149, 237, 255); + public static readonly Color CornflowerBlue = NamedColors.CornflowerBlue; /// /// Represents a matching the W3C definition that has an hex value of #FFF8DC. /// - public static readonly Color Cornsilk = new Color(255, 248, 220, 255); + public static readonly Color Cornsilk = NamedColors.Cornsilk; /// /// Represents a matching the W3C definition that has an hex value of #DC143C. /// - public static readonly Color Crimson = new Color(220, 20, 60, 255); + public static readonly Color Crimson = NamedColors.Crimson; /// /// Represents a matching the W3C definition that has an hex value of #00FFFF. /// - public static readonly Color Cyan = new Color(0, 255, 255, 255); + public static readonly Color Cyan = NamedColors.Cyan; /// /// Represents a matching the W3C definition that has an hex value of #00008B. /// - public static readonly Color DarkBlue = new Color(0, 0, 139, 255); + public static readonly Color DarkBlue = NamedColors.DarkBlue; /// /// Represents a matching the W3C definition that has an hex value of #008B8B. /// - public static readonly Color DarkCyan = new Color(0, 139, 139, 255); + public static readonly Color DarkCyan = NamedColors.DarkCyan; /// /// Represents a matching the W3C definition that has an hex value of #B8860B. /// - public static readonly Color DarkGoldenrod = new Color(184, 134, 11, 255); + public static readonly Color DarkGoldenrod = NamedColors.DarkGoldenrod; /// /// Represents a matching the W3C definition that has an hex value of #A9A9A9. /// - public static readonly Color DarkGray = new Color(169, 169, 169, 255); + public static readonly Color DarkGray = NamedColors.DarkGray; /// /// Represents a matching the W3C definition that has an hex value of #006400. /// - public static readonly Color DarkGreen = new Color(0, 100, 0, 255); + public static readonly Color DarkGreen = NamedColors.DarkGreen; /// /// Represents a matching the W3C definition that has an hex value of #BDB76B. /// - public static readonly Color DarkKhaki = new Color(189, 183, 107, 255); + public static readonly Color DarkKhaki = NamedColors.DarkKhaki; /// /// Represents a matching the W3C definition that has an hex value of #8B008B. /// - public static readonly Color DarkMagenta = new Color(139, 0, 139, 255); + public static readonly Color DarkMagenta = NamedColors.DarkMagenta; /// /// Represents a matching the W3C definition that has an hex value of #556B2F. /// - public static readonly Color DarkOliveGreen = new Color(85, 107, 47, 255); + public static readonly Color DarkOliveGreen = NamedColors.DarkOliveGreen; /// /// Represents a matching the W3C definition that has an hex value of #FF8C00. /// - public static readonly Color DarkOrange = new Color(255, 140, 0, 255); + public static readonly Color DarkOrange = NamedColors.DarkOrange; /// /// Represents a matching the W3C definition that has an hex value of #9932CC. /// - public static readonly Color DarkOrchid = new Color(153, 50, 204, 255); + public static readonly Color DarkOrchid = NamedColors.DarkOrchid; /// /// Represents a matching the W3C definition that has an hex value of #8B0000. /// - public static readonly Color DarkRed = new Color(139, 0, 0, 255); + public static readonly Color DarkRed = NamedColors.DarkRed; /// /// Represents a matching the W3C definition that has an hex value of #E9967A. /// - public static readonly Color DarkSalmon = new Color(233, 150, 122, 255); + public static readonly Color DarkSalmon = NamedColors.DarkSalmon; /// /// Represents a matching the W3C definition that has an hex value of #8FBC8B. /// - public static readonly Color DarkSeaGreen = new Color(143, 188, 139, 255); + public static readonly Color DarkSeaGreen = NamedColors.DarkSeaGreen; /// /// Represents a matching the W3C definition that has an hex value of #483D8B. /// - public static readonly Color DarkSlateBlue = new Color(72, 61, 139, 255); + public static readonly Color DarkSlateBlue = NamedColors.DarkSlateBlue; /// /// Represents a matching the W3C definition that has an hex value of #2F4F4F. /// - public static readonly Color DarkSlateGray = new Color(47, 79, 79, 255); + public static readonly Color DarkSlateGray = NamedColors.DarkSlateGray; /// /// Represents a matching the W3C definition that has an hex value of #00CED1. /// - public static readonly Color DarkTurquoise = new Color(0, 206, 209, 255); + public static readonly Color DarkTurquoise = NamedColors.DarkTurquoise; /// /// Represents a matching the W3C definition that has an hex value of #9400D3. /// - public static readonly Color DarkViolet = new Color(148, 0, 211, 255); + public static readonly Color DarkViolet = NamedColors.DarkViolet; /// /// Represents a matching the W3C definition that has an hex value of #FF1493. /// - public static readonly Color DeepPink = new Color(255, 20, 147, 255); + public static readonly Color DeepPink = NamedColors.DeepPink; /// /// Represents a matching the W3C definition that has an hex value of #00BFFF. /// - public static readonly Color DeepSkyBlue = new Color(0, 191, 255, 255); + public static readonly Color DeepSkyBlue = NamedColors.DeepSkyBlue; /// /// Represents a matching the W3C definition that has an hex value of #696969. /// - public static readonly Color DimGray = new Color(105, 105, 105, 255); + public static readonly Color DimGray = NamedColors.DimGray; /// /// Represents a matching the W3C definition that has an hex value of #1E90FF. /// - public static readonly Color DodgerBlue = new Color(30, 144, 255, 255); + public static readonly Color DodgerBlue = NamedColors.DodgerBlue; /// /// Represents a matching the W3C definition that has an hex value of #B22222. /// - public static readonly Color Firebrick = new Color(178, 34, 34, 255); + public static readonly Color Firebrick = NamedColors.Firebrick; /// /// Represents a matching the W3C definition that has an hex value of #FFFAF0. /// - public static readonly Color FloralWhite = new Color(255, 250, 240, 255); + public static readonly Color FloralWhite = NamedColors.FloralWhite; /// /// Represents a matching the W3C definition that has an hex value of #228B22. /// - public static readonly Color ForestGreen = new Color(34, 139, 34, 255); + public static readonly Color ForestGreen = NamedColors.ForestGreen; /// /// Represents a matching the W3C definition that has an hex value of #FF00FF. /// - public static readonly Color Fuchsia = new Color(255, 0, 255, 255); + public static readonly Color Fuchsia = NamedColors.Fuchsia; /// /// Represents a matching the W3C definition that has an hex value of #DCDCDC. /// - public static readonly Color Gainsboro = new Color(220, 220, 220, 255); + public static readonly Color Gainsboro = NamedColors.Gainsboro; /// /// Represents a matching the W3C definition that has an hex value of #F8F8FF. /// - public static readonly Color GhostWhite = new Color(248, 248, 255, 255); + public static readonly Color GhostWhite = NamedColors.GhostWhite; /// /// Represents a matching the W3C definition that has an hex value of #FFD700. /// - public static readonly Color Gold = new Color(255, 215, 0, 255); + public static readonly Color Gold = NamedColors.Gold; /// /// Represents a matching the W3C definition that has an hex value of #DAA520. /// - public static readonly Color Goldenrod = new Color(218, 165, 32, 255); + public static readonly Color Goldenrod = NamedColors.Goldenrod; /// /// Represents a matching the W3C definition that has an hex value of #808080. /// - public static readonly Color Gray = new Color(128, 128, 128, 255); + public static readonly Color Gray = NamedColors.Gray; /// /// Represents a matching the W3C definition that has an hex value of #008000. /// - public static readonly Color Green = new Color(0, 128, 0, 255); + public static readonly Color Green = NamedColors.Green; /// /// Represents a matching the W3C definition that has an hex value of #ADFF2F. /// - public static readonly Color GreenYellow = new Color(173, 255, 47, 255); + public static readonly Color GreenYellow = NamedColors.GreenYellow; /// /// Represents a matching the W3C definition that has an hex value of #F0FFF0. /// - public static readonly Color Honeydew = new Color(240, 255, 240, 255); + public static readonly Color Honeydew = NamedColors.Honeydew; /// /// Represents a matching the W3C definition that has an hex value of #FF69B4. /// - public static readonly Color HotPink = new Color(255, 105, 180, 255); + public static readonly Color HotPink = NamedColors.HotPink; /// /// Represents a matching the W3C definition that has an hex value of #CD5C5C. /// - public static readonly Color IndianRed = new Color(205, 92, 92, 255); + public static readonly Color IndianRed = NamedColors.IndianRed; /// /// Represents a matching the W3C definition that has an hex value of #4B0082. /// - public static readonly Color Indigo = new Color(75, 0, 130, 255); + public static readonly Color Indigo = NamedColors.Indigo; /// /// Represents a matching the W3C definition that has an hex value of #FFFFF0. /// - public static readonly Color Ivory = new Color(255, 255, 240, 255); + public static readonly Color Ivory = NamedColors.Ivory; /// /// Represents a matching the W3C definition that has an hex value of #F0E68C. /// - public static readonly Color Khaki = new Color(240, 230, 140, 255); + public static readonly Color Khaki = NamedColors.Khaki; /// /// Represents a matching the W3C definition that has an hex value of #E6E6FA. /// - public static readonly Color Lavender = new Color(230, 230, 250, 255); + public static readonly Color Lavender = NamedColors.Lavender; /// /// Represents a matching the W3C definition that has an hex value of #FFF0F5. /// - public static readonly Color LavenderBlush = new Color(255, 240, 245, 255); + public static readonly Color LavenderBlush = NamedColors.LavenderBlush; /// /// Represents a matching the W3C definition that has an hex value of #7CFC00. /// - public static readonly Color LawnGreen = new Color(124, 252, 0, 255); + public static readonly Color LawnGreen = NamedColors.LawnGreen; /// /// Represents a matching the W3C definition that has an hex value of #FFFACD. /// - public static readonly Color LemonChiffon = new Color(255, 250, 205, 255); + public static readonly Color LemonChiffon = NamedColors.LemonChiffon; /// /// Represents a matching the W3C definition that has an hex value of #ADD8E6. /// - public static readonly Color LightBlue = new Color(173, 216, 230, 255); + public static readonly Color LightBlue = NamedColors.LightBlue; /// /// Represents a matching the W3C definition that has an hex value of #F08080. /// - public static readonly Color LightCoral = new Color(240, 128, 128, 255); + public static readonly Color LightCoral = NamedColors.LightCoral; /// /// Represents a matching the W3C definition that has an hex value of #E0FFFF. /// - public static readonly Color LightCyan = new Color(224, 255, 255, 255); + public static readonly Color LightCyan = NamedColors.LightCyan; /// /// Represents a matching the W3C definition that has an hex value of #FAFAD2. /// - public static readonly Color LightGoldenrodYellow = new Color(250, 250, 210, 255); + public static readonly Color LightGoldenrodYellow = NamedColors.LightGoldenrodYellow; /// /// Represents a matching the W3C definition that has an hex value of #D3D3D3. /// - public static readonly Color LightGray = new Color(211, 211, 211, 255); + public static readonly Color LightGray = NamedColors.LightGray; /// /// Represents a matching the W3C definition that has an hex value of #90EE90. /// - public static readonly Color LightGreen = new Color(144, 238, 144, 255); + public static readonly Color LightGreen = NamedColors.LightGreen; /// /// Represents a matching the W3C definition that has an hex value of #FFB6C1. /// - public static readonly Color LightPink = new Color(255, 182, 193, 255); + public static readonly Color LightPink = NamedColors.LightPink; /// /// Represents a matching the W3C definition that has an hex value of #FFA07A. /// - public static readonly Color LightSalmon = new Color(255, 160, 122, 255); + public static readonly Color LightSalmon = NamedColors.LightSalmon; /// /// Represents a matching the W3C definition that has an hex value of #20B2AA. /// - public static readonly Color LightSeaGreen = new Color(32, 178, 170, 255); + public static readonly Color LightSeaGreen = NamedColors.LightSeaGreen; /// /// Represents a matching the W3C definition that has an hex value of #87CEFA. /// - public static readonly Color LightSkyBlue = new Color(135, 206, 250, 255); + public static readonly Color LightSkyBlue = NamedColors.LightSkyBlue; /// /// Represents a matching the W3C definition that has an hex value of #778899. /// - public static readonly Color LightSlateGray = new Color(119, 136, 153, 255); + public static readonly Color LightSlateGray = NamedColors.LightSlateGray; /// /// Represents a matching the W3C definition that has an hex value of #B0C4DE. /// - public static readonly Color LightSteelBlue = new Color(176, 196, 222, 255); + public static readonly Color LightSteelBlue = NamedColors.LightSteelBlue; /// /// Represents a matching the W3C definition that has an hex value of #FFFFE0. /// - public static readonly Color LightYellow = new Color(255, 255, 224, 255); + public static readonly Color LightYellow = NamedColors.LightYellow; /// /// Represents a matching the W3C definition that has an hex value of #00FF00. /// - public static readonly Color Lime = new Color(0, 255, 0, 255); + public static readonly Color Lime = NamedColors.Lime; /// /// Represents a matching the W3C definition that has an hex value of #32CD32. /// - public static readonly Color LimeGreen = new Color(50, 205, 50, 255); + public static readonly Color LimeGreen = NamedColors.LimeGreen; /// /// Represents a matching the W3C definition that has an hex value of #FAF0E6. /// - public static readonly Color Linen = new Color(250, 240, 230, 255); + public static readonly Color Linen = NamedColors.Linen; /// /// Represents a matching the W3C definition that has an hex value of #FF00FF. /// - public static readonly Color Magenta = new Color(255, 0, 255, 255); + public static readonly Color Magenta = NamedColors.Magenta; /// /// Represents a matching the W3C definition that has an hex value of #800000. /// - public static readonly Color Maroon = new Color(128, 0, 0, 255); + public static readonly Color Maroon = NamedColors.Maroon; /// /// Represents a matching the W3C definition that has an hex value of #66CDAA. /// - public static readonly Color MediumAquamarine = new Color(102, 205, 170, 255); + public static readonly Color MediumAquamarine = NamedColors.MediumAquamarine; /// /// Represents a matching the W3C definition that has an hex value of #0000CD. /// - public static readonly Color MediumBlue = new Color(0, 0, 205, 255); + public static readonly Color MediumBlue = NamedColors.MediumBlue; /// /// Represents a matching the W3C definition that has an hex value of #BA55D3. /// - public static readonly Color MediumOrchid = new Color(186, 85, 211, 255); + public static readonly Color MediumOrchid = NamedColors.MediumOrchid; /// /// Represents a matching the W3C definition that has an hex value of #9370DB. /// - public static readonly Color MediumPurple = new Color(147, 112, 219, 255); + public static readonly Color MediumPurple = NamedColors.MediumPurple; /// /// Represents a matching the W3C definition that has an hex value of #3CB371. /// - public static readonly Color MediumSeaGreen = new Color(60, 179, 113, 255); + public static readonly Color MediumSeaGreen = NamedColors.MediumSeaGreen; /// /// Represents a matching the W3C definition that has an hex value of #7B68EE. /// - public static readonly Color MediumSlateBlue = new Color(123, 104, 238, 255); + public static readonly Color MediumSlateBlue = NamedColors.MediumSlateBlue; /// /// Represents a matching the W3C definition that has an hex value of #00FA9A. /// - public static readonly Color MediumSpringGreen = new Color(0, 250, 154, 255); + public static readonly Color MediumSpringGreen = NamedColors.MediumSpringGreen; /// /// Represents a matching the W3C definition that has an hex value of #48D1CC. /// - public static readonly Color MediumTurquoise = new Color(72, 209, 204, 255); + public static readonly Color MediumTurquoise = NamedColors.MediumTurquoise; /// /// Represents a matching the W3C definition that has an hex value of #C71585. /// - public static readonly Color MediumVioletRed = new Color(199, 21, 133, 255); + public static readonly Color MediumVioletRed = NamedColors.MediumVioletRed; /// /// Represents a matching the W3C definition that has an hex value of #191970. /// - public static readonly Color MidnightBlue = new Color(25, 25, 112, 255); + public static readonly Color MidnightBlue = NamedColors.MidnightBlue; /// /// Represents a matching the W3C definition that has an hex value of #F5FFFA. /// - public static readonly Color MintCream = new Color(245, 255, 250, 255); + public static readonly Color MintCream = NamedColors.MintCream; /// /// Represents a matching the W3C definition that has an hex value of #FFE4E1. /// - public static readonly Color MistyRose = new Color(255, 228, 225, 255); + public static readonly Color MistyRose = NamedColors.MistyRose; /// /// Represents a matching the W3C definition that has an hex value of #FFE4B5. /// - public static readonly Color Moccasin = new Color(255, 228, 181, 255); + public static readonly Color Moccasin = NamedColors.Moccasin; /// /// Represents a matching the W3C definition that has an hex value of #FFDEAD. /// - public static readonly Color NavajoWhite = new Color(255, 222, 173, 255); + public static readonly Color NavajoWhite = NamedColors.NavajoWhite; /// /// Represents a matching the W3C definition that has an hex value of #000080. /// - public static readonly Color Navy = new Color(0, 0, 128, 255); + public static readonly Color Navy = NamedColors.Navy; /// /// Represents a matching the W3C definition that has an hex value of #FDF5E6. /// - public static readonly Color OldLace = new Color(253, 245, 230, 255); + public static readonly Color OldLace = NamedColors.OldLace; /// /// Represents a matching the W3C definition that has an hex value of #808000. /// - public static readonly Color Olive = new Color(128, 128, 0, 255); + public static readonly Color Olive = NamedColors.Olive; /// /// Represents a matching the W3C definition that has an hex value of #6B8E23. /// - public static readonly Color OliveDrab = new Color(107, 142, 35, 255); + public static readonly Color OliveDrab = NamedColors.OliveDrab; /// /// Represents a matching the W3C definition that has an hex value of #FFA500. /// - public static readonly Color Orange = new Color(255, 165, 0, 255); + public static readonly Color Orange = NamedColors.Orange; /// /// Represents a matching the W3C definition that has an hex value of #FF4500. /// - public static readonly Color OrangeRed = new Color(255, 69, 0, 255); + public static readonly Color OrangeRed = NamedColors.OrangeRed; /// /// Represents a matching the W3C definition that has an hex value of #DA70D6. /// - public static readonly Color Orchid = new Color(218, 112, 214, 255); + public static readonly Color Orchid = NamedColors.Orchid; /// /// Represents a matching the W3C definition that has an hex value of #EEE8AA. /// - public static readonly Color PaleGoldenrod = new Color(238, 232, 170, 255); + public static readonly Color PaleGoldenrod = NamedColors.PaleGoldenrod; /// /// Represents a matching the W3C definition that has an hex value of #98FB98. /// - public static readonly Color PaleGreen = new Color(152, 251, 152, 255); + public static readonly Color PaleGreen = NamedColors.PaleGreen; /// /// Represents a matching the W3C definition that has an hex value of #AFEEEE. /// - public static readonly Color PaleTurquoise = new Color(175, 238, 238, 255); + public static readonly Color PaleTurquoise = NamedColors.PaleTurquoise; /// /// Represents a matching the W3C definition that has an hex value of #DB7093. /// - public static readonly Color PaleVioletRed = new Color(219, 112, 147, 255); + public static readonly Color PaleVioletRed = NamedColors.PaleVioletRed; /// /// Represents a matching the W3C definition that has an hex value of #FFEFD5. /// - public static readonly Color PapayaWhip = new Color(255, 239, 213, 255); + public static readonly Color PapayaWhip = NamedColors.PapayaWhip; /// /// Represents a matching the W3C definition that has an hex value of #FFDAB9. /// - public static readonly Color PeachPuff = new Color(255, 218, 185, 255); + public static readonly Color PeachPuff = NamedColors.PeachPuff; /// /// Represents a matching the W3C definition that has an hex value of #CD853F. /// - public static readonly Color Peru = new Color(205, 133, 63, 255); + public static readonly Color Peru = NamedColors.Peru; /// /// Represents a matching the W3C definition that has an hex value of #FFC0CB. /// - public static readonly Color Pink = new Color(255, 192, 203, 255); + public static readonly Color Pink = NamedColors.Pink; /// /// Represents a matching the W3C definition that has an hex value of #DDA0DD. /// - public static readonly Color Plum = new Color(221, 160, 221, 255); + public static readonly Color Plum = NamedColors.Plum; /// /// Represents a matching the W3C definition that has an hex value of #B0E0E6. /// - public static readonly Color PowderBlue = new Color(176, 224, 230, 255); + public static readonly Color PowderBlue = NamedColors.PowderBlue; /// /// Represents a matching the W3C definition that has an hex value of #800080. /// - public static readonly Color Purple = new Color(128, 0, 128, 255); + public static readonly Color Purple = NamedColors.Purple; /// /// Represents a matching the W3C definition that has an hex value of #663399. /// - public static readonly Color RebeccaPurple = new Color(102, 51, 153, 255); + public static readonly Color RebeccaPurple = NamedColors.RebeccaPurple; /// /// Represents a matching the W3C definition that has an hex value of #FF0000. /// - public static readonly Color Red = new Color(255, 0, 0, 255); + public static readonly Color Red = NamedColors.Red; /// /// Represents a matching the W3C definition that has an hex value of #BC8F8F. /// - public static readonly Color RosyBrown = new Color(188, 143, 143, 255); + public static readonly Color RosyBrown = NamedColors.RosyBrown; /// /// Represents a matching the W3C definition that has an hex value of #4169E1. /// - public static readonly Color RoyalBlue = new Color(65, 105, 225, 255); + public static readonly Color RoyalBlue = NamedColors.RoyalBlue; /// /// Represents a matching the W3C definition that has an hex value of #8B4513. /// - public static readonly Color SaddleBrown = new Color(139, 69, 19, 255); + public static readonly Color SaddleBrown = NamedColors.SaddleBrown; /// /// Represents a matching the W3C definition that has an hex value of #FA8072. /// - public static readonly Color Salmon = new Color(250, 128, 114, 255); + public static readonly Color Salmon = NamedColors.Salmon; /// /// Represents a matching the W3C definition that has an hex value of #F4A460. /// - public static readonly Color SandyBrown = new Color(244, 164, 96, 255); + public static readonly Color SandyBrown = NamedColors.SandyBrown; /// /// Represents a matching the W3C definition that has an hex value of #2E8B57. /// - public static readonly Color SeaGreen = new Color(46, 139, 87, 255); + public static readonly Color SeaGreen = NamedColors.SeaGreen; /// /// Represents a matching the W3C definition that has an hex value of #FFF5EE. /// - public static readonly Color SeaShell = new Color(255, 245, 238, 255); + public static readonly Color SeaShell = NamedColors.SeaShell; /// /// Represents a matching the W3C definition that has an hex value of #A0522D. /// - public static readonly Color Sienna = new Color(160, 82, 45, 255); + public static readonly Color Sienna = NamedColors.Sienna; /// /// Represents a matching the W3C definition that has an hex value of #C0C0C0. /// - public static readonly Color Silver = new Color(192, 192, 192, 255); + public static readonly Color Silver = NamedColors.Silver; /// /// Represents a matching the W3C definition that has an hex value of #87CEEB. /// - public static readonly Color SkyBlue = new Color(135, 206, 235, 255); + public static readonly Color SkyBlue = NamedColors.SkyBlue; /// /// Represents a matching the W3C definition that has an hex value of #6A5ACD. /// - public static readonly Color SlateBlue = new Color(106, 90, 205, 255); + public static readonly Color SlateBlue = NamedColors.SlateBlue; /// /// Represents a matching the W3C definition that has an hex value of #708090. /// - public static readonly Color SlateGray = new Color(112, 128, 144, 255); + public static readonly Color SlateGray = NamedColors.SlateGray; /// /// Represents a matching the W3C definition that has an hex value of #FFFAFA. /// - public static readonly Color Snow = new Color(255, 250, 250, 255); + public static readonly Color Snow = NamedColors.Snow; /// /// Represents a matching the W3C definition that has an hex value of #00FF7F. /// - public static readonly Color SpringGreen = new Color(0, 255, 127, 255); + public static readonly Color SpringGreen = NamedColors.SpringGreen; /// /// Represents a matching the W3C definition that has an hex value of #4682B4. /// - public static readonly Color SteelBlue = new Color(70, 130, 180, 255); + public static readonly Color SteelBlue = NamedColors.SteelBlue; /// /// Represents a matching the W3C definition that has an hex value of #D2B48C. /// - public static readonly Color Tan = new Color(210, 180, 140, 255); + public static readonly Color Tan = NamedColors.Tan; /// /// Represents a matching the W3C definition that has an hex value of #008080. /// - public static readonly Color Teal = new Color(0, 128, 128, 255); + public static readonly Color Teal = NamedColors.Teal; /// /// Represents a matching the W3C definition that has an hex value of #D8BFD8. /// - public static readonly Color Thistle = new Color(216, 191, 216, 255); + public static readonly Color Thistle = NamedColors.Thistle; /// /// Represents a matching the W3C definition that has an hex value of #FF6347. /// - public static readonly Color Tomato = new Color(255, 99, 71, 255); + public static readonly Color Tomato = NamedColors.Tomato; /// /// Represents a matching the W3C definition that has an hex value of #FFFFFF. /// - public static readonly Color Transparent = new Color(255, 255, 255, 0); + public static readonly Color Transparent = NamedColors.Transparent; /// /// Represents a matching the W3C definition that has an hex value of #40E0D0. /// - public static readonly Color Turquoise = new Color(64, 224, 208, 255); + public static readonly Color Turquoise = NamedColors.Turquoise; /// /// Represents a matching the W3C definition that has an hex value of #EE82EE. /// - public static readonly Color Violet = new Color(238, 130, 238, 255); + public static readonly Color Violet = NamedColors.Violet; /// /// Represents a matching the W3C definition that has an hex value of #F5DEB3. /// - public static readonly Color Wheat = new Color(245, 222, 179, 255); + public static readonly Color Wheat = NamedColors.Wheat; /// /// Represents a matching the W3C definition that has an hex value of #FFFFFF. /// - public static readonly Color White = new Color(255, 255, 255, 255); + public static readonly Color White = NamedColors.White; /// /// Represents a matching the W3C definition that has an hex value of #F5F5F5. /// - public static readonly Color WhiteSmoke = new Color(245, 245, 245, 255); + public static readonly Color WhiteSmoke = NamedColors.WhiteSmoke; /// /// Represents a matching the W3C definition that has an hex value of #FFFF00. /// - public static readonly Color Yellow = new Color(255, 255, 0, 255); + public static readonly Color Yellow = NamedColors.Yellow; /// /// Represents a matching the W3C definition that has an hex value of #9ACD32. /// - public static readonly Color YellowGreen = new Color(154, 205, 50, 255); + public static readonly Color YellowGreen = NamedColors.YellowGreen; } } \ No newline at end of file diff --git a/src/ImageSharp/Colors/NamedColors{TColor}.cs b/src/ImageSharp/Colors/NamedColors{TColor}.cs new file mode 100644 index 0000000000..77a537f0f1 --- /dev/null +++ b/src/ImageSharp/Colors/NamedColors{TColor}.cs @@ -0,0 +1,727 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + + /// + /// A set of named colors mapped to the provided Color space. + /// + /// The type of the color. + public static class NamedColors + where TColor : struct, IPackedPixel, IEquatable + { + /// + /// Represents a matching the W3C definition that has an hex value of #F0F8FF. + /// + public static readonly TColor AliceBlue = ColorBuilder.FromRGBA(240, 248, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FAEBD7. + /// + public static readonly TColor AntiqueWhite = ColorBuilder.FromRGBA(250, 235, 215, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #00FFFF. + /// + public static readonly TColor Aqua = ColorBuilder.FromRGBA(0, 255, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #7FFFD4. + /// + public static readonly TColor Aquamarine = ColorBuilder.FromRGBA(127, 255, 212, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #F0FFFF. + /// + public static readonly TColor Azure = ColorBuilder.FromRGBA(240, 255, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #F5F5DC. + /// + public static readonly TColor Beige = ColorBuilder.FromRGBA(245, 245, 220, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFE4C4. + /// + public static readonly TColor Bisque = ColorBuilder.FromRGBA(255, 228, 196, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #000000. + /// + public static readonly TColor Black = ColorBuilder.FromRGBA(0, 0, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFEBCD. + /// + public static readonly TColor BlanchedAlmond = ColorBuilder.FromRGBA(255, 235, 205, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #0000FF. + /// + public static readonly TColor Blue = ColorBuilder.FromRGBA(0, 0, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #8A2BE2. + /// + public static readonly TColor BlueViolet = ColorBuilder.FromRGBA(138, 43, 226, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #A52A2A. + /// + public static readonly TColor Brown = ColorBuilder.FromRGBA(165, 42, 42, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #DEB887. + /// + public static readonly TColor BurlyWood = ColorBuilder.FromRGBA(222, 184, 135, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #5F9EA0. + /// + public static readonly TColor CadetBlue = ColorBuilder.FromRGBA(95, 158, 160, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #7FFF00. + /// + public static readonly TColor Chartreuse = ColorBuilder.FromRGBA(127, 255, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #D2691E. + /// + public static readonly TColor Chocolate = ColorBuilder.FromRGBA(210, 105, 30, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FF7F50. + /// + public static readonly TColor Coral = ColorBuilder.FromRGBA(255, 127, 80, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #6495ED. + /// + public static readonly TColor CornflowerBlue = ColorBuilder.FromRGBA(100, 149, 237, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFF8DC. + /// + public static readonly TColor Cornsilk = ColorBuilder.FromRGBA(255, 248, 220, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #DC143C. + /// + public static readonly TColor Crimson = ColorBuilder.FromRGBA(220, 20, 60, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #00FFFF. + /// + public static readonly TColor Cyan = ColorBuilder.FromRGBA(0, 255, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #00008B. + /// + public static readonly TColor DarkBlue = ColorBuilder.FromRGBA(0, 0, 139, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #008B8B. + /// + public static readonly TColor DarkCyan = ColorBuilder.FromRGBA(0, 139, 139, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #B8860B. + /// + public static readonly TColor DarkGoldenrod = ColorBuilder.FromRGBA(184, 134, 11, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #A9A9A9. + /// + public static readonly TColor DarkGray = ColorBuilder.FromRGBA(169, 169, 169, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #006400. + /// + public static readonly TColor DarkGreen = ColorBuilder.FromRGBA(0, 100, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #BDB76B. + /// + public static readonly TColor DarkKhaki = ColorBuilder.FromRGBA(189, 183, 107, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #8B008B. + /// + public static readonly TColor DarkMagenta = ColorBuilder.FromRGBA(139, 0, 139, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #556B2F. + /// + public static readonly TColor DarkOliveGreen = ColorBuilder.FromRGBA(85, 107, 47, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FF8C00. + /// + public static readonly TColor DarkOrange = ColorBuilder.FromRGBA(255, 140, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #9932CC. + /// + public static readonly TColor DarkOrchid = ColorBuilder.FromRGBA(153, 50, 204, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #8B0000. + /// + public static readonly TColor DarkRed = ColorBuilder.FromRGBA(139, 0, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #E9967A. + /// + public static readonly TColor DarkSalmon = ColorBuilder.FromRGBA(233, 150, 122, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #8FBC8B. + /// + public static readonly TColor DarkSeaGreen = ColorBuilder.FromRGBA(143, 188, 139, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #483D8B. + /// + public static readonly TColor DarkSlateBlue = ColorBuilder.FromRGBA(72, 61, 139, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #2F4F4F. + /// + public static readonly TColor DarkSlateGray = ColorBuilder.FromRGBA(47, 79, 79, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #00CED1. + /// + public static readonly TColor DarkTurquoise = ColorBuilder.FromRGBA(0, 206, 209, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #9400D3. + /// + public static readonly TColor DarkViolet = ColorBuilder.FromRGBA(148, 0, 211, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FF1493. + /// + public static readonly TColor DeepPink = ColorBuilder.FromRGBA(255, 20, 147, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #00BFFF. + /// + public static readonly TColor DeepSkyBlue = ColorBuilder.FromRGBA(0, 191, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #696969. + /// + public static readonly TColor DimGray = ColorBuilder.FromRGBA(105, 105, 105, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #1E90FF. + /// + public static readonly TColor DodgerBlue = ColorBuilder.FromRGBA(30, 144, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #B22222. + /// + public static readonly TColor Firebrick = ColorBuilder.FromRGBA(178, 34, 34, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFFAF0. + /// + public static readonly TColor FloralWhite = ColorBuilder.FromRGBA(255, 250, 240, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #228B22. + /// + public static readonly TColor ForestGreen = ColorBuilder.FromRGBA(34, 139, 34, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FF00FF. + /// + public static readonly TColor Fuchsia = ColorBuilder.FromRGBA(255, 0, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #DCDCDC. + /// + public static readonly TColor Gainsboro = ColorBuilder.FromRGBA(220, 220, 220, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #F8F8FF. + /// + public static readonly TColor GhostWhite = ColorBuilder.FromRGBA(248, 248, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFD700. + /// + public static readonly TColor Gold = ColorBuilder.FromRGBA(255, 215, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #DAA520. + /// + public static readonly TColor Goldenrod = ColorBuilder.FromRGBA(218, 165, 32, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #808080. + /// + public static readonly TColor Gray = ColorBuilder.FromRGBA(128, 128, 128, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #008000. + /// + public static readonly TColor Green = ColorBuilder.FromRGBA(0, 128, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #ADFF2F. + /// + public static readonly TColor GreenYellow = ColorBuilder.FromRGBA(173, 255, 47, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #F0FFF0. + /// + public static readonly TColor Honeydew = ColorBuilder.FromRGBA(240, 255, 240, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FF69B4. + /// + public static readonly TColor HotPink = ColorBuilder.FromRGBA(255, 105, 180, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #CD5C5C. + /// + public static readonly TColor IndianRed = ColorBuilder.FromRGBA(205, 92, 92, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #4B0082. + /// + public static readonly TColor Indigo = ColorBuilder.FromRGBA(75, 0, 130, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFFFF0. + /// + public static readonly TColor Ivory = ColorBuilder.FromRGBA(255, 255, 240, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #F0E68C. + /// + public static readonly TColor Khaki = ColorBuilder.FromRGBA(240, 230, 140, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #E6E6FA. + /// + public static readonly TColor Lavender = ColorBuilder.FromRGBA(230, 230, 250, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFF0F5. + /// + public static readonly TColor LavenderBlush = ColorBuilder.FromRGBA(255, 240, 245, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #7CFC00. + /// + public static readonly TColor LawnGreen = ColorBuilder.FromRGBA(124, 252, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFFACD. + /// + public static readonly TColor LemonChiffon = ColorBuilder.FromRGBA(255, 250, 205, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #ADD8E6. + /// + public static readonly TColor LightBlue = ColorBuilder.FromRGBA(173, 216, 230, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #F08080. + /// + public static readonly TColor LightCoral = ColorBuilder.FromRGBA(240, 128, 128, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #E0FFFF. + /// + public static readonly TColor LightCyan = ColorBuilder.FromRGBA(224, 255, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FAFAD2. + /// + public static readonly TColor LightGoldenrodYellow = ColorBuilder.FromRGBA(250, 250, 210, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #D3D3D3. + /// + public static readonly TColor LightGray = ColorBuilder.FromRGBA(211, 211, 211, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #90EE90. + /// + public static readonly TColor LightGreen = ColorBuilder.FromRGBA(144, 238, 144, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFB6C1. + /// + public static readonly TColor LightPink = ColorBuilder.FromRGBA(255, 182, 193, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFA07A. + /// + public static readonly TColor LightSalmon = ColorBuilder.FromRGBA(255, 160, 122, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #20B2AA. + /// + public static readonly TColor LightSeaGreen = ColorBuilder.FromRGBA(32, 178, 170, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #87CEFA. + /// + public static readonly TColor LightSkyBlue = ColorBuilder.FromRGBA(135, 206, 250, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #778899. + /// + public static readonly TColor LightSlateGray = ColorBuilder.FromRGBA(119, 136, 153, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #B0C4DE. + /// + public static readonly TColor LightSteelBlue = ColorBuilder.FromRGBA(176, 196, 222, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFFFE0. + /// + public static readonly TColor LightYellow = ColorBuilder.FromRGBA(255, 255, 224, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #00FF00. + /// + public static readonly TColor Lime = ColorBuilder.FromRGBA(0, 255, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #32CD32. + /// + public static readonly TColor LimeGreen = ColorBuilder.FromRGBA(50, 205, 50, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FAF0E6. + /// + public static readonly TColor Linen = ColorBuilder.FromRGBA(250, 240, 230, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FF00FF. + /// + public static readonly TColor Magenta = ColorBuilder.FromRGBA(255, 0, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #800000. + /// + public static readonly TColor Maroon = ColorBuilder.FromRGBA(128, 0, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #66CDAA. + /// + public static readonly TColor MediumAquamarine = ColorBuilder.FromRGBA(102, 205, 170, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #0000CD. + /// + public static readonly TColor MediumBlue = ColorBuilder.FromRGBA(0, 0, 205, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #BA55D3. + /// + public static readonly TColor MediumOrchid = ColorBuilder.FromRGBA(186, 85, 211, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #9370DB. + /// + public static readonly TColor MediumPurple = ColorBuilder.FromRGBA(147, 112, 219, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #3CB371. + /// + public static readonly TColor MediumSeaGreen = ColorBuilder.FromRGBA(60, 179, 113, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #7B68EE. + /// + public static readonly TColor MediumSlateBlue = ColorBuilder.FromRGBA(123, 104, 238, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #00FA9A. + /// + public static readonly TColor MediumSpringGreen = ColorBuilder.FromRGBA(0, 250, 154, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #48D1CC. + /// + public static readonly TColor MediumTurquoise = ColorBuilder.FromRGBA(72, 209, 204, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #C71585. + /// + public static readonly TColor MediumVioletRed = ColorBuilder.FromRGBA(199, 21, 133, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #191970. + /// + public static readonly TColor MidnightBlue = ColorBuilder.FromRGBA(25, 25, 112, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #F5FFFA. + /// + public static readonly TColor MintCream = ColorBuilder.FromRGBA(245, 255, 250, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFE4E1. + /// + public static readonly TColor MistyRose = ColorBuilder.FromRGBA(255, 228, 225, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFE4B5. + /// + public static readonly TColor Moccasin = ColorBuilder.FromRGBA(255, 228, 181, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFDEAD. + /// + public static readonly TColor NavajoWhite = ColorBuilder.FromRGBA(255, 222, 173, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #000080. + /// + public static readonly TColor Navy = ColorBuilder.FromRGBA(0, 0, 128, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FDF5E6. + /// + public static readonly TColor OldLace = ColorBuilder.FromRGBA(253, 245, 230, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #808000. + /// + public static readonly TColor Olive = ColorBuilder.FromRGBA(128, 128, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #6B8E23. + /// + public static readonly TColor OliveDrab = ColorBuilder.FromRGBA(107, 142, 35, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFA500. + /// + public static readonly TColor Orange = ColorBuilder.FromRGBA(255, 165, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FF4500. + /// + public static readonly TColor OrangeRed = ColorBuilder.FromRGBA(255, 69, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #DA70D6. + /// + public static readonly TColor Orchid = ColorBuilder.FromRGBA(218, 112, 214, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #EEE8AA. + /// + public static readonly TColor PaleGoldenrod = ColorBuilder.FromRGBA(238, 232, 170, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #98FB98. + /// + public static readonly TColor PaleGreen = ColorBuilder.FromRGBA(152, 251, 152, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #AFEEEE. + /// + public static readonly TColor PaleTurquoise = ColorBuilder.FromRGBA(175, 238, 238, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #DB7093. + /// + public static readonly TColor PaleVioletRed = ColorBuilder.FromRGBA(219, 112, 147, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFEFD5. + /// + public static readonly TColor PapayaWhip = ColorBuilder.FromRGBA(255, 239, 213, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFDAB9. + /// + public static readonly TColor PeachPuff = ColorBuilder.FromRGBA(255, 218, 185, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #CD853F. + /// + public static readonly TColor Peru = ColorBuilder.FromRGBA(205, 133, 63, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFC0CB. + /// + public static readonly TColor Pink = ColorBuilder.FromRGBA(255, 192, 203, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #DDA0DD. + /// + public static readonly TColor Plum = ColorBuilder.FromRGBA(221, 160, 221, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #B0E0E6. + /// + public static readonly TColor PowderBlue = ColorBuilder.FromRGBA(176, 224, 230, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #800080. + /// + public static readonly TColor Purple = ColorBuilder.FromRGBA(128, 0, 128, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #663399. + /// + public static readonly TColor RebeccaPurple = ColorBuilder.FromRGBA(102, 51, 153, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FF0000. + /// + public static readonly TColor Red = ColorBuilder.FromRGBA(255, 0, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #BC8F8F. + /// + public static readonly TColor RosyBrown = ColorBuilder.FromRGBA(188, 143, 143, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #4169E1. + /// + public static readonly TColor RoyalBlue = ColorBuilder.FromRGBA(65, 105, 225, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #8B4513. + /// + public static readonly TColor SaddleBrown = ColorBuilder.FromRGBA(139, 69, 19, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FA8072. + /// + public static readonly TColor Salmon = ColorBuilder.FromRGBA(250, 128, 114, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #F4A460. + /// + public static readonly TColor SandyBrown = ColorBuilder.FromRGBA(244, 164, 96, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #2E8B57. + /// + public static readonly TColor SeaGreen = ColorBuilder.FromRGBA(46, 139, 87, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFF5EE. + /// + public static readonly TColor SeaShell = ColorBuilder.FromRGBA(255, 245, 238, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #A0522D. + /// + public static readonly TColor Sienna = ColorBuilder.FromRGBA(160, 82, 45, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #C0C0C0. + /// + public static readonly TColor Silver = ColorBuilder.FromRGBA(192, 192, 192, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #87CEEB. + /// + public static readonly TColor SkyBlue = ColorBuilder.FromRGBA(135, 206, 235, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #6A5ACD. + /// + public static readonly TColor SlateBlue = ColorBuilder.FromRGBA(106, 90, 205, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #708090. + /// + public static readonly TColor SlateGray = ColorBuilder.FromRGBA(112, 128, 144, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFFAFA. + /// + public static readonly TColor Snow = ColorBuilder.FromRGBA(255, 250, 250, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #00FF7F. + /// + public static readonly TColor SpringGreen = ColorBuilder.FromRGBA(0, 255, 127, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #4682B4. + /// + public static readonly TColor SteelBlue = ColorBuilder.FromRGBA(70, 130, 180, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #D2B48C. + /// + public static readonly TColor Tan = ColorBuilder.FromRGBA(210, 180, 140, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #008080. + /// + public static readonly TColor Teal = ColorBuilder.FromRGBA(0, 128, 128, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #D8BFD8. + /// + public static readonly TColor Thistle = ColorBuilder.FromRGBA(216, 191, 216, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FF6347. + /// + public static readonly TColor Tomato = ColorBuilder.FromRGBA(255, 99, 71, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFFFFF. + /// + public static readonly TColor Transparent = ColorBuilder.FromRGBA(255, 255, 255, 0); + + /// + /// Represents a matching the W3C definition that has an hex value of #40E0D0. + /// + public static readonly TColor Turquoise = ColorBuilder.FromRGBA(64, 224, 208, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #EE82EE. + /// + public static readonly TColor Violet = ColorBuilder.FromRGBA(238, 130, 238, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #F5DEB3. + /// + public static readonly TColor Wheat = ColorBuilder.FromRGBA(245, 222, 179, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFFFFF. + /// + public static readonly TColor White = ColorBuilder.FromRGBA(255, 255, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #F5F5F5. + /// + public static readonly TColor WhiteSmoke = ColorBuilder.FromRGBA(245, 245, 245, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFFF00. + /// + public static readonly TColor Yellow = ColorBuilder.FromRGBA(255, 255, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #9ACD32. + /// + public static readonly TColor YellowGreen = ColorBuilder.FromRGBA(154, 205, 50, 255); + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colors/ColorDefinitionTests.cs b/tests/ImageSharp.Tests/Colors/ColorDefinitionTests.cs new file mode 100644 index 0000000000..899ce4f77a --- /dev/null +++ b/tests/ImageSharp.Tests/Colors/ColorDefinitionTests.cs @@ -0,0 +1,38 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Linq; + using System.Reflection; + + using ImageSharp.Colors.Spaces; + using Xunit; + public class ColorDefinitionTests + { + public static IEnumerable ColorNames => typeof(NamedColors).GetTypeInfo().GetFields().Select(x => new[] { x.Name }); + + [Theory] + [MemberData(nameof(ColorNames))] + public void AllColorsAreOnGenericAndBaseColor(string name) + { + FieldInfo generic = typeof(NamedColors).GetTypeInfo().GetField(name); + FieldInfo specific = typeof(Color).GetTypeInfo().GetField(name); + + Assert.NotNull(specific); + Assert.NotNull(generic); + Assert.True(specific.Attributes.HasFlag(FieldAttributes.Public), "specific must be public"); + Assert.True(specific.Attributes.HasFlag(FieldAttributes.Static), "specific must be static"); + Assert.True(generic.Attributes.HasFlag(FieldAttributes.Public), "generic must be public"); + Assert.True(generic.Attributes.HasFlag(FieldAttributes.Static), "generic must be static"); + Color expected = (Color)generic.GetValue(null); + Color actual = (Color)specific.GetValue(null); + Assert.Equal(expected, actual); + } + } +} diff --git a/tests/ImageSharp.Tests/Colors/ColorTests.cs b/tests/ImageSharp.Tests/Colors/ColorTests.cs index ec75ae6f76..312e664662 100644 --- a/tests/ImageSharp.Tests/Colors/ColorTests.cs +++ b/tests/ImageSharp.Tests/Colors/ColorTests.cs @@ -23,10 +23,10 @@ namespace ImageSharp.Tests { Color color1 = new Color(0, 0, 0); Color color2 = new Color(0, 0, 0, 1F); - Color color3 = new Color("#000"); - Color color4 = new Color("#000F"); - Color color5 = new Color("#000000"); - Color color6 = new Color("#000000FF"); + Color color3 = Color.FromHex("#000"); + Color color4 = Color.FromHex("#000F"); + Color color5 = Color.FromHex("#000000"); + Color color6 = Color.FromHex("#000000FF"); Assert.Equal(color1, color2); Assert.Equal(color1, color3); @@ -43,9 +43,9 @@ namespace ImageSharp.Tests { Color color1 = new Color(255, 0, 0, 255); Color color2 = new Color(0, 0, 0, 255); - Color color3 = new Color("#000"); - Color color4 = new Color("#000000"); - Color color5 = new Color("#FF000000"); + Color color3 = Color.FromHex("#000"); + Color color4 = Color.FromHex("#000000"); + Color color5 = Color.FromHex("#FF000000"); Assert.NotEqual(color1, color2); Assert.NotEqual(color1, color3); @@ -71,12 +71,6 @@ namespace ImageSharp.Tests Assert.Equal(Math.Round(.133f * 255), color2.B); Assert.Equal(255, color2.A); - Color color3 = new Color("#FF0000"); - Assert.Equal(255, color3.R); - Assert.Equal(0, color3.G); - Assert.Equal(0, color3.B); - Assert.Equal(255, color3.A); - Color color4 = new Color(new Vector3(1, .1f, .133f)); Assert.Equal(255, color4.R); Assert.Equal(Math.Round(.1f * 255), color4.G); diff --git a/tests/ImageSharp.Tests/Image/PixelAccessorTests.cs b/tests/ImageSharp.Tests/Image/PixelAccessorTests.cs index a0bf2cf7a6..46f667e703 100644 --- a/tests/ImageSharp.Tests/Image/PixelAccessorTests.cs +++ b/tests/ImageSharp.Tests/Image/PixelAccessorTests.cs @@ -91,17 +91,12 @@ namespace ImageSharp.Tests [WithBlankImages(16, 16, PixelTypes.All, ComponentOrder.Zyxw)] public void CopyToThenCopyFromWithOffset(TestImageProvider provider, ComponentOrder order) where TColor : struct, IPackedPixel, IEquatable - { using (Image destImage = new Image(8, 8)) { - TColor color; using (Image srcImage = provider.GetImage()) { - color = default(TColor); - color.PackFromBytes(255, 0, 0, 255); - - Fill(srcImage, new Rectangle(4, 4, 8, 8), color); + Fill(srcImage, new Rectangle(4, 4, 8, 8), NamedColors.Red); using (PixelAccessor srcPixels = srcImage.Lock()) { using (PixelArea area = new PixelArea(8, 8, order)) @@ -119,7 +114,7 @@ namespace ImageSharp.Tests provider.Utility.SourceFileOrDescription = order.ToString(); provider.Utility.SaveTestOutputFile(destImage, "bmp"); - using (Image expectedImage = new Image(8, 8).Fill(color)) + using (Image expectedImage = new Image(8, 8).Fill(NamedColors.Red)) { Assert.True(destImage.IsEquivalentTo(expectedImage)); } From 0c7ec053e1a53fb2e28d1dc475a76bf27de4698b Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 18 Feb 2017 12:55:26 +1100 Subject: [PATCH 072/142] Use Fast2DArray for all convolution algorithms. --- .../Convolution/BoxBlur.cs | 3 +- .../Convolution/BoxBlurProcessor.cs | 30 ++--- .../Convolution/Convolution2DProcessor.cs | 31 +++-- .../Convolution/Convolution2PassProcessor.cs | 74 +++++------ .../Convolution/ConvolutionProcessor.cs | 29 ++--- .../EdgeDetection/EdgeDetector2DProcessor.cs | 15 ++- .../EdgeDetectorCompassProcessor.cs | 36 +++--- .../EdgeDetection/EdgeDetectorProcessor.cs | 23 +++- .../EdgeDetection/KayyaliProcessor.cs | 38 +++--- .../EdgeDetection/KirschProcessor.cs | 121 ++++++++++-------- .../EdgeDetection/Laplacian3X3Processor.cs | 22 ++-- .../EdgeDetection/Laplacian5X5Processor.cs | 26 ++-- .../LaplacianOfGaussianProcessor.cs | 26 ++-- .../EdgeDetection/PrewittProcessor.cs | 38 +++--- .../EdgeDetection/RobertsCrossProcessor.cs | 34 ++--- .../EdgeDetection/RobinsonProcessor.cs | 121 ++++++++++-------- .../EdgeDetection/ScharrProcessor.cs | 38 +++--- .../EdgeDetection/SobelProcessor.cs | 38 +++--- .../Convolution/GaussianBlurProcessor.cs | 31 ++--- .../Convolution/GaussianSharpenProcessor.cs | 33 +++-- src/ImageSharp/Common/Helpers/ImageMaths.cs | 6 + 21 files changed, 436 insertions(+), 377 deletions(-) diff --git a/src/ImageSharp.Processing/Convolution/BoxBlur.cs b/src/ImageSharp.Processing/Convolution/BoxBlur.cs index a68c2fa44d..6bdd68b9b6 100644 --- a/src/ImageSharp.Processing/Convolution/BoxBlur.cs +++ b/src/ImageSharp.Processing/Convolution/BoxBlur.cs @@ -7,7 +7,6 @@ namespace ImageSharp { using System; - using Processing; using Processing.Processors; /// @@ -41,7 +40,7 @@ namespace ImageSharp public static Image BoxBlur(this Image source, int radius, Rectangle rectangle) where TColor : struct, IPackedPixel, IEquatable { - source.ApplyProcessor(new BoxBlurProcessor(), rectangle); + source.ApplyProcessor(new BoxBlurProcessor(radius), rectangle); return source; } } diff --git a/src/ImageSharp.Processing/Processors/Convolution/BoxBlurProcessor.cs b/src/ImageSharp.Processing/Processors/Convolution/BoxBlurProcessor.cs index 272b3cc8b4..8ca1ca6669 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/BoxBlurProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/BoxBlurProcessor.cs @@ -35,12 +35,12 @@ namespace ImageSharp.Processing.Processors /// /// Gets the horizontal gradient operator. /// - public float[][] KernelX { get; } + public Fast2DArray KernelX { get; } /// /// Gets the vertical gradient operator. /// - public float[][] KernelY { get; } + public Fast2DArray KernelY { get; } /// protected override void OnApply(ImageBase source, Rectangle sourceRectangle) @@ -52,46 +52,42 @@ namespace ImageSharp.Processing.Processors /// Create a 1 dimensional Box kernel. /// /// Whether to calculate a horizontal kernel. - /// The - private float[][] CreateBoxKernel(bool horizontal) + /// The + private Fast2DArray CreateBoxKernel(bool horizontal) { int size = this.kernelSize; - float[][] kernel = horizontal ? new float[1][] : new float[size][]; - - if (horizontal) - { - kernel[0] = new float[size]; - } - - float sum = 0.0f; + Fast2DArray kernel = horizontal + ? new Fast2DArray(new float[1, size]) + : new Fast2DArray(new float[size, 1]); + float sum = 0F; for (int i = 0; i < size; i++) { float x = 1; sum += x; if (horizontal) { - kernel[0][i] = x; + kernel[0, i] = x; } else { - kernel[i] = new[] { x }; + kernel[i, 0] = x; } } - // Normalise kernel so that the sum of all weights equals 1 + // Normalize kernel so that the sum of all weights equals 1 if (horizontal) { for (int i = 0; i < size; i++) { - kernel[0][i] = kernel[0][i] / sum; + kernel[0, i] = kernel[0, i] / sum; } } else { for (int i = 0; i < size; i++) { - kernel[i][0] = kernel[i][0] / sum; + kernel[i, 0] = kernel[i, 0] / sum; } } diff --git a/src/ImageSharp.Processing/Processors/Convolution/Convolution2DProcessor.cs b/src/ImageSharp.Processing/Processors/Convolution/Convolution2DProcessor.cs index cdea43e854..71b71ba5d2 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/Convolution2DProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/Convolution2DProcessor.cs @@ -21,7 +21,7 @@ namespace ImageSharp.Processing.Processors /// /// The horizontal gradient operator. /// The vertical gradient operator. - public Convolution2DProcessor(float[][] kernelX, float[][] kernelY) + public Convolution2DProcessor(Fast2DArray kernelX, Fast2DArray kernelY) { this.KernelX = kernelX; this.KernelY = kernelY; @@ -30,20 +30,20 @@ namespace ImageSharp.Processing.Processors /// /// Gets the horizontal gradient operator. /// - public float[][] KernelX { get; } + public Fast2DArray KernelX { get; } /// /// Gets the vertical gradient operator. /// - public float[][] KernelY { get; } + public Fast2DArray KernelY { get; } /// protected override void OnApply(ImageBase source, Rectangle sourceRectangle) { - int kernelYHeight = this.KernelY.Length; - int kernelYWidth = this.KernelY[0].Length; - int kernelXHeight = this.KernelX.Length; - int kernelXWidth = this.KernelX[0].Length; + int kernelYHeight = this.KernelY.Height; + int kernelYWidth = this.KernelY.Width; + int kernelXHeight = this.KernelX.Height; + int kernelXWidth = this.KernelX.Width; int radiusY = kernelYHeight >> 1; int radiusX = kernelXWidth >> 1; @@ -89,22 +89,21 @@ namespace ImageSharp.Processing.Processors offsetX = offsetX.Clamp(0, maxX); Vector4 currentColor = sourcePixels[offsetX, offsetY].ToVector4(); - float r = currentColor.X; - float g = currentColor.Y; - float b = currentColor.Z; if (fy < kernelXHeight) { - rX += this.KernelX[fy][fx] * r; - gX += this.KernelX[fy][fx] * g; - bX += this.KernelX[fy][fx] * b; + Vector4 kx = this.KernelX[fy, fx] * currentColor; + rX += kx.X; + gX += kx.Y; + bX += kx.Z; } if (fx < kernelYWidth) { - rY += this.KernelY[fy][fx] * r; - gY += this.KernelY[fy][fx] * g; - bY += this.KernelY[fy][fx] * b; + Vector4 ky = this.KernelY[fy, fx] * currentColor; + rY += ky.X; + gY += ky.Y; + bY += ky.Z; } } } diff --git a/src/ImageSharp.Processing/Processors/Convolution/Convolution2PassProcessor.cs b/src/ImageSharp.Processing/Processors/Convolution/Convolution2PassProcessor.cs index 71b8062617..495bfa5dba 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/Convolution2PassProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/Convolution2PassProcessor.cs @@ -14,14 +14,14 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class Convolution2PassProcessor : ImageProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPackedPixel, IEquatable { /// /// Initializes a new instance of the class. /// /// The horizontal gradient operator. /// The vertical gradient operator. - public Convolution2PassProcessor(float[][] kernelX, float[][] kernelY) + public Convolution2PassProcessor(Fast2DArray kernelX, Fast2DArray kernelY) { this.KernelX = kernelX; this.KernelY = kernelY; @@ -30,18 +30,16 @@ namespace ImageSharp.Processing.Processors /// /// Gets the horizontal gradient operator. /// - public float[][] KernelX { get; } + public Fast2DArray KernelX { get; } /// /// Gets the vertical gradient operator. /// - public float[][] KernelY { get; } + public Fast2DArray KernelY { get; } /// protected override void OnApply(ImageBase source, Rectangle sourceRectangle) { - float[][] kernelX = this.KernelX; - float[][] kernelY = this.KernelY; int width = source.Width; int height = source.Height; @@ -50,8 +48,8 @@ namespace ImageSharp.Processing.Processors using (PixelAccessor firstPassPixels = new PixelAccessor(width, height)) using (PixelAccessor sourcePixels = source.Lock()) { - this.ApplyConvolution(width, height, firstPassPixels, sourcePixels, sourceRectangle, kernelX); - this.ApplyConvolution(width, height, targetPixels, firstPassPixels, sourceRectangle, kernelY); + this.ApplyConvolution(firstPassPixels, sourcePixels, sourceRectangle, this.KernelX); + this.ApplyConvolution(targetPixels, firstPassPixels, sourceRectangle, this.KernelY); } source.SwapPixelsBuffers(targetPixels); @@ -62,18 +60,16 @@ namespace ImageSharp.Processing.Processors /// Applies the process to the specified portion of the specified at the specified location /// and with the specified size. /// - /// The image width. - /// The image height. /// The target pixels to apply the process to. /// The source pixels. Cannot be null. /// /// The structure that specifies the portion of the image object to draw. /// /// The kernel operator. - private void ApplyConvolution(int width, int height, PixelAccessor targetPixels, PixelAccessor sourcePixels, Rectangle sourceRectangle, float[][] kernel) + private void ApplyConvolution(PixelAccessor targetPixels, PixelAccessor sourcePixels, Rectangle sourceRectangle, Fast2DArray kernel) { - int kernelHeight = kernel.Length; - int kernelWidth = kernel[0].Length; + int kernelHeight = kernel.Height; + int kernelWidth = kernel.Width; int radiusY = kernelHeight >> 1; int radiusX = kernelWidth >> 1; @@ -85,40 +81,40 @@ namespace ImageSharp.Processing.Processors int maxX = endX - 1; Parallel.For( - startY, - endY, - this.ParallelOptions, - y => - { - for (int x = startX; x < endX; x++) + startY, + endY, + this.ParallelOptions, + y => { - Vector4 destination = default(Vector4); - - // Apply each matrix multiplier to the color components for each pixel. - for (int fy = 0; fy < kernelHeight; fy++) + for (int x = startX; x < endX; x++) { - int fyr = fy - radiusY; - int offsetY = y + fyr; + Vector4 destination = default(Vector4); - offsetY = offsetY.Clamp(0, maxY); - - for (int fx = 0; fx < kernelWidth; fx++) + // Apply each matrix multiplier to the color components for each pixel. + for (int fy = 0; fy < kernelHeight; fy++) { - int fxr = fx - radiusX; - int offsetX = x + fxr; + int fyr = fy - radiusY; + int offsetY = y + fyr; + + offsetY = offsetY.Clamp(0, maxY); - offsetX = offsetX.Clamp(0, maxX); + for (int fx = 0; fx < kernelWidth; fx++) + { + int fxr = fx - radiusX; + int offsetX = x + fxr; - Vector4 currentColor = sourcePixels[offsetX, offsetY].ToVector4(); - destination += kernel[fy][fx] * currentColor; + offsetX = offsetX.Clamp(0, maxX); + + Vector4 currentColor = sourcePixels[offsetX, offsetY].ToVector4(); + destination += kernel[fy, fx] * currentColor; + } } - } - TColor packed = default(TColor); - packed.PackFromVector4(destination); - targetPixels[x, y] = packed; - } - }); + TColor packed = default(TColor); + packed.PackFromVector4(destination); + targetPixels[x, y] = packed; + } + }); } } } \ No newline at end of file diff --git a/src/ImageSharp.Processing/Processors/Convolution/ConvolutionProcessor.cs b/src/ImageSharp.Processing/Processors/Convolution/ConvolutionProcessor.cs index aa49401926..46df9c6eea 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/ConvolutionProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/ConvolutionProcessor.cs @@ -14,13 +14,13 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class ConvolutionProcessor : ImageProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPackedPixel, IEquatable { /// /// Initializes a new instance of the class. /// /// The 2d gradient operator. - public ConvolutionProcessor(float[][] kernelXY) + public ConvolutionProcessor(Fast2DArray kernelXY) { this.KernelXY = kernelXY; } @@ -28,13 +28,12 @@ namespace ImageSharp.Processing.Processors /// /// Gets the 2d gradient operator. /// - public virtual float[][] KernelXY { get; } + public Fast2DArray KernelXY { get; } /// protected override void OnApply(ImageBase source, Rectangle sourceRectangle) { - float[][] kernelX = this.KernelXY; - int kernelLength = kernelX.GetLength(0); + int kernelLength = this.KernelXY.Height; int radius = kernelLength >> 1; int startY = sourceRectangle.Y; @@ -56,9 +55,9 @@ namespace ImageSharp.Processing.Processors { for (int x = startX; x < endX; x++) { - float rX = 0; - float gX = 0; - float bX = 0; + float red = 0; + float green = 0; + float blue = 0; // Apply each matrix multiplier to the color components for each pixel. for (int fy = 0; fy < kernelLength; fy++) @@ -76,20 +75,14 @@ namespace ImageSharp.Processing.Processors offsetX = offsetX.Clamp(0, maxX); Vector4 currentColor = sourcePixels[offsetX, offsetY].ToVector4(); - float r = currentColor.X; - float g = currentColor.Y; - float b = currentColor.Z; + currentColor *= this.KernelXY[fy, fx]; - rX += kernelX[fy][fx] * r; - gX += kernelX[fy][fx] * g; - bX += kernelX[fy][fx] * b; + red += currentColor.X; + green += currentColor.Y; + blue += currentColor.Z; } } - float red = rX; - float green = gX; - float blue = bX; - TColor packed = default(TColor); packed.PackFromVector4(new Vector4(red, green, blue, sourcePixels[x, y].ToVector4().W)); targetPixels[x, y] = packed; diff --git a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/EdgeDetector2DProcessor.cs b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/EdgeDetector2DProcessor.cs index 6ee5d0f96a..f86940adef 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/EdgeDetector2DProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/EdgeDetector2DProcessor.cs @@ -14,15 +14,26 @@ namespace ImageSharp.Processing.Processors public abstract class EdgeDetector2DProcessor : ImageProcessor, IEdgeDetectorProcessor where TColor : struct, IPackedPixel, IEquatable { + /// + /// Initializes a new instance of the class. + /// + /// The horizontal gradient operator. + /// The vertical gradient operator. + protected EdgeDetector2DProcessor(Fast2DArray kernelX, Fast2DArray kernelY) + { + this.KernelX = kernelX; + this.KernelY = kernelY; + } + /// /// Gets the horizontal gradient operator. /// - public abstract float[][] KernelX { get; } + public Fast2DArray KernelX { get; } /// /// Gets the vertical gradient operator. /// - public abstract float[][] KernelY { get; } + public Fast2DArray KernelY { get; } /// public bool Grayscale { get; set; } diff --git a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/EdgeDetectorCompassProcessor.cs b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/EdgeDetectorCompassProcessor.cs index 1a88dbe345..58967cb5b1 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/EdgeDetectorCompassProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/EdgeDetectorCompassProcessor.cs @@ -19,50 +19,59 @@ namespace ImageSharp.Processing.Processors /// /// Gets the North gradient operator /// - public abstract float[][] North { get; } + public abstract Fast2DArray North { get; } /// /// Gets the NorthWest gradient operator /// - public abstract float[][] NorthWest { get; } + public abstract Fast2DArray NorthWest { get; } /// /// Gets the West gradient operator /// - public abstract float[][] West { get; } + public abstract Fast2DArray West { get; } /// /// Gets the SouthWest gradient operator /// - public abstract float[][] SouthWest { get; } + public abstract Fast2DArray SouthWest { get; } /// /// Gets the South gradient operator /// - public abstract float[][] South { get; } + public abstract Fast2DArray South { get; } /// /// Gets the SouthEast gradient operator /// - public abstract float[][] SouthEast { get; } + public abstract Fast2DArray SouthEast { get; } /// /// Gets the East gradient operator /// - public abstract float[][] East { get; } + public abstract Fast2DArray East { get; } /// /// Gets the NorthEast gradient operator /// - public abstract float[][] NorthEast { get; } + public abstract Fast2DArray NorthEast { get; } /// public bool Grayscale { get; set; } + /// + protected override void BeforeApply(ImageBase source, Rectangle sourceRectangle) + { + if (this.Grayscale) + { + new GrayscaleBt709Processor().Apply(source, sourceRectangle); + } + } + /// protected override void OnApply(ImageBase source, Rectangle sourceRectangle) { - float[][][] kernels = { this.North, this.NorthWest, this.West, this.SouthWest, this.South, this.SouthEast, this.East, this.NorthEast }; + Fast2DArray[] kernels = { this.North, this.NorthWest, this.West, this.SouthWest, this.South, this.SouthEast, this.East, this.NorthEast }; int startY = sourceRectangle.Y; int endY = sourceRectangle.Bottom; @@ -132,14 +141,5 @@ namespace ImageSharp.Processing.Processors } } } - - /// - protected override void BeforeApply(ImageBase source, Rectangle sourceRectangle) - { - if (this.Grayscale) - { - new GrayscaleBt709Processor().Apply(source, sourceRectangle); - } - } } } \ No newline at end of file diff --git a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/EdgeDetectorProcessor.cs b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/EdgeDetectorProcessor.cs index 1033111fcb..cd2b91f167 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/EdgeDetectorProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/EdgeDetectorProcessor.cs @@ -14,19 +14,22 @@ namespace ImageSharp.Processing.Processors public abstract class EdgeDetectorProcessor : ImageProcessor, IEdgeDetectorProcessor where TColor : struct, IPackedPixel, IEquatable { + /// + /// Initializes a new instance of the class. + /// + /// The 2d gradient operator. + protected EdgeDetectorProcessor(Fast2DArray kernelXY) + { + this.KernelXY = kernelXY; + } + /// public bool Grayscale { get; set; } /// /// Gets the 2d gradient operator. /// - public abstract float[][] KernelXY { get; } - - /// - protected override void OnApply(ImageBase source, Rectangle sourceRectangle) - { - new ConvolutionProcessor(this.KernelXY).Apply(source, sourceRectangle); - } + public Fast2DArray KernelXY { get; } /// protected override void BeforeApply(ImageBase source, Rectangle sourceRectangle) @@ -36,5 +39,11 @@ namespace ImageSharp.Processing.Processors new GrayscaleBt709Processor().Apply(source, sourceRectangle); } } + + /// + protected override void OnApply(ImageBase source, Rectangle sourceRectangle) + { + new ConvolutionProcessor(this.KernelXY).Apply(source, sourceRectangle); + } } } \ No newline at end of file diff --git a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/KayyaliProcessor.cs b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/KayyaliProcessor.cs index f628ea1b94..6e1452e17f 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/KayyaliProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/KayyaliProcessor.cs @@ -20,27 +20,31 @@ namespace ImageSharp.Processing.Processors /// /// The horizontal gradient operator. /// - private static readonly float[][] KayyaliX = - { - new float[] { 6, 0, -6 }, - new float[] { 0, 0, 0 }, - new float[] { -6, 0, 6 } - }; + private static readonly Fast2DArray KayyaliX = + new Fast2DArray(new float[,] + { + { 6, 0, -6 }, + { 0, 0, 0 }, + { -6, 0, 6 } + }); /// /// The vertical gradient operator. /// - private static readonly float[][] KayyaliY = - { - new float[] { -6, 0, 6 }, - new float[] { 0, 0, 0 }, - new float[] { 6, 0, -6 } - }; - - /// - public override float[][] KernelX => KayyaliX; + private static readonly Fast2DArray KayyaliY = + new Fast2DArray(new float[,] + { + { -6, 0, 6 }, + { 0, 0, 0 }, + { 6, 0, -6 } + }); - /// - public override float[][] KernelY => KayyaliY; + /// + /// Initializes a new instance of the class. + /// + public KayyaliProcessor() + : base(KayyaliX, KayyaliY) + { + } } } diff --git a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/KirschProcessor.cs b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/KirschProcessor.cs index 3f7e0a00ee..f8cb9aba9d 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/KirschProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/KirschProcessor.cs @@ -2,6 +2,7 @@ // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // + namespace ImageSharp.Processing.Processors { using System; @@ -19,105 +20,113 @@ namespace ImageSharp.Processing.Processors /// /// The North gradient operator /// - private static readonly float[][] KirschNorth = - { - new float[] { 5, 5, 5 }, - new float[] { -3, 0, -3 }, - new float[] { -3, -3, -3 } - }; + private static readonly Fast2DArray KirschNorth = + new Fast2DArray(new float[,] + { + { 5, 5, 5 }, + { -3, 0, -3 }, + { -3, -3, -3 } + }); /// /// The NorthWest gradient operator /// - private static readonly float[][] KirschNorthWest = - { - new float[] { 5, 5, -3 }, - new float[] { 5, 0, -3 }, - new float[] { -3, -3, -3 } - }; + private static readonly Fast2DArray KirschNorthWest = + new Fast2DArray(new float[,] + { + { 5, 5, -3 }, + { 5, 0, -3 }, + { -3, -3, -3 } + }); /// /// The West gradient operator /// - private static readonly float[][] KirschWest = - { - new float[] { 5, -3, -3 }, - new float[] { 5, 0, -3 }, - new float[] { 5, -3, -3 } - }; + private static readonly Fast2DArray KirschWest = + new Fast2DArray(new float[,] + { + { 5, -3, -3 }, + { 5, 0, -3 }, + { 5, -3, -3 } + }); /// /// The SouthWest gradient operator /// - private static readonly float[][] KirschSouthWest = - { - new float[] { -3, -3, -3 }, - new float[] { 5, 0, -3 }, - new float[] { 5, 5, -3 } - }; + private static readonly Fast2DArray KirschSouthWest = + new Fast2DArray(new float[,] + { + { -3, -3, -3 }, + { 5, 0, -3 }, + { 5, 5, -3 } + }); /// /// The South gradient operator /// - private static readonly float[][] KirschSouth = - { - new float[] { -3, -3, -3 }, - new float[] { -3, 0, -3 }, - new float[] { 5, 5, 5 } - }; + private static readonly Fast2DArray KirschSouth = + new Fast2DArray(new float[,] + { + { -3, -3, -3 }, + { -3, 0, -3 }, + { 5, 5, 5 } + }); /// /// The SouthEast gradient operator /// - private static readonly float[][] KirschSouthEast = - { - new float[] { -3, -3, -3 }, - new float[] { -3, 0, 5 }, - new float[] { -3, 5, 5 } - }; + private static readonly Fast2DArray KirschSouthEast = + new Fast2DArray(new float[,] + { + { -3, -3, -3 }, + { -3, 0, 5 }, + { -3, 5, 5 } + }); /// /// The East gradient operator /// - private static readonly float[][] KirschEast = - { - new float[] { -3, -3, 5 }, - new float[] { -3, 0, 5 }, - new float[] { -3, -3, 5 } - }; + private static readonly Fast2DArray KirschEast = + new Fast2DArray(new float[,] + { + { -3, -3, 5 }, + { -3, 0, 5 }, + { -3, -3, 5 } + }); /// /// The NorthEast gradient operator /// - private static readonly float[][] KirschNorthEast = - { - new float[] { -3, 5, 5 }, - new float[] { -3, 0, 5 }, - new float[] { -3, -3, -3 } - }; + private static readonly Fast2DArray KirschNorthEast = + new Fast2DArray(new float[,] + { + { -3, 5, 5 }, + { -3, 0, 5 }, + { -3, -3, -3 } + }); /// - public override float[][] North => KirschNorth; + public override Fast2DArray North => KirschNorth; /// - public override float[][] NorthWest => KirschNorthWest; + public override Fast2DArray NorthWest => KirschNorthWest; /// - public override float[][] West => KirschWest; + public override Fast2DArray West => KirschWest; /// - public override float[][] SouthWest => KirschSouthWest; + public override Fast2DArray SouthWest => KirschSouthWest; /// - public override float[][] South => KirschSouth; + public override Fast2DArray South => KirschSouth; /// - public override float[][] SouthEast => KirschSouthEast; + public override Fast2DArray SouthEast => KirschSouthEast; /// - public override float[][] East => KirschEast; + public override Fast2DArray East => KirschEast; /// - public override float[][] NorthEast => KirschNorthEast; + public override Fast2DArray NorthEast => KirschNorthEast; } } \ No newline at end of file diff --git a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/Laplacian3X3Processor.cs b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/Laplacian3X3Processor.cs index b19c5c7737..4b23dfe470 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/Laplacian3X3Processor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/Laplacian3X3Processor.cs @@ -20,14 +20,20 @@ namespace ImageSharp.Processing.Processors /// /// The 2d gradient operator. /// - private static readonly float[][] Laplacian3X3XY = new float[][] - { - new float[] { -1, -1, -1 }, - new float[] { -1, 8, -1 }, - new float[] { -1, -1, -1 } - }; + private static readonly Fast2DArray Laplacian3X3XY = + new Fast2DArray(new float[,] + { + { -1, -1, -1 }, + { -1, 8, -1 }, + { -1, -1, -1 } + }); - /// - public override float[][] KernelXY => Laplacian3X3XY; + /// + /// Initializes a new instance of the class. + /// + public Laplacian3X3Processor() + : base(Laplacian3X3XY) + { + } } } diff --git a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/Laplacian5X5Processor.cs b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/Laplacian5X5Processor.cs index efa6c28c56..304dafbbd1 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/Laplacian5X5Processor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/Laplacian5X5Processor.cs @@ -20,16 +20,22 @@ namespace ImageSharp.Processing.Processors /// /// The 2d gradient operator. /// - private static readonly float[][] Laplacian5X5XY = - { - new float[] { -1, -1, -1, -1, -1 }, - new float[] { -1, -1, -1, -1, -1 }, - new float[] { -1, -1, 24, -1, -1 }, - new float[] { -1, -1, -1, -1, -1 }, - new float[] { -1, -1, -1, -1, -1 } - }; + private static readonly Fast2DArray Laplacian5X5XY = + new Fast2DArray(new float[,] + { + { -1, -1, -1, -1, -1 }, + { -1, -1, -1, -1, -1 }, + { -1, -1, 24, -1, -1 }, + { -1, -1, -1, -1, -1 }, + { -1, -1, -1, -1, -1 } + }); - /// - public override float[][] KernelXY => Laplacian5X5XY; + /// + /// Initializes a new instance of the class. + /// + public Laplacian5X5Processor() + : base(Laplacian5X5XY) + { + } } } \ No newline at end of file diff --git a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/LaplacianOfGaussianProcessor.cs b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/LaplacianOfGaussianProcessor.cs index 595ca6a4b0..e18957a69a 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/LaplacianOfGaussianProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/LaplacianOfGaussianProcessor.cs @@ -20,16 +20,22 @@ namespace ImageSharp.Processing.Processors /// /// The 2d gradient operator. /// - private static readonly float[][] LaplacianOfGaussianXY = - { - new float[] { 0, 0, -1, 0, 0 }, - new float[] { 0, -1, -2, -1, 0 }, - new float[] { -1, -2, 16, -2, -1 }, - new float[] { 0, -1, -2, -1, 0 }, - new float[] { 0, 0, -1, 0, 0 } - }; + private static readonly Fast2DArray LaplacianOfGaussianXY = + new Fast2DArray(new float[,] + { + { 0, 0, -1, 0, 0 }, + { 0, -1, -2, -1, 0 }, + { -1, -2, 16, -2, -1 }, + { 0, -1, -2, -1, 0 }, + { 0, 0, -1, 0, 0 } + }); - /// - public override float[][] KernelXY => LaplacianOfGaussianXY; + /// + /// Initializes a new instance of the class. + /// + public LaplacianOfGaussianProcessor() + : base(LaplacianOfGaussianXY) + { + } } } \ No newline at end of file diff --git a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/PrewittProcessor.cs b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/PrewittProcessor.cs index 5c48722ef8..1d61b8cd94 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/PrewittProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/PrewittProcessor.cs @@ -20,27 +20,31 @@ namespace ImageSharp.Processing.Processors /// /// The horizontal gradient operator. /// - private static readonly float[][] PrewittX = - { - new float[] { -1, 0, 1 }, - new float[] { -1, 0, 1 }, - new float[] { -1, 0, 1 } - }; + private static readonly Fast2DArray PrewittX = + new Fast2DArray(new float[,] + { + { -1, 0, 1 }, + { -1, 0, 1 }, + { -1, 0, 1 } + }); /// /// The vertical gradient operator. /// - private static readonly float[][] PrewittY = - { - new float[] { 1, 1, 1 }, - new float[] { 0, 0, 0 }, - new float[] { -1, -1, -1 } - }; - - /// - public override float[][] KernelX => PrewittX; + private static readonly Fast2DArray PrewittY = + new Fast2DArray(new float[,] + { + { 1, 1, 1 }, + { 0, 0, 0 }, + { -1, -1, -1 } + }); - /// - public override float[][] KernelY => PrewittY; + /// + /// Initializes a new instance of the class. + /// + public PrewittProcessor() + : base(PrewittX, PrewittY) + { + } } } \ No newline at end of file diff --git a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/RobertsCrossProcessor.cs b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/RobertsCrossProcessor.cs index c64ee8abeb..83f13a3429 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/RobertsCrossProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/RobertsCrossProcessor.cs @@ -20,25 +20,29 @@ namespace ImageSharp.Processing.Processors /// /// The horizontal gradient operator. /// - private static readonly float[][] RobertsCrossX = - { - new float[] { 1, 0 }, - new float[] { 0, -1 } - }; + private static readonly Fast2DArray RobertsCrossX = + new Fast2DArray(new float[,] + { + { 1, 0 }, + { 0, -1 } + }); /// /// The vertical gradient operator. /// - private static readonly float[][] RobertsCrossY = - { - new float[] { 0, 1 }, - new float[] { -1, 0 } - }; - - /// - public override float[][] KernelX => RobertsCrossX; + private static readonly Fast2DArray RobertsCrossY = + new Fast2DArray(new float[,] + { + { 0, 1 }, + { -1, 0 } + }); - /// - public override float[][] KernelY => RobertsCrossY; + /// + /// Initializes a new instance of the class. + /// + public RobertsCrossProcessor() + : base(RobertsCrossX, RobertsCrossY) + { + } } } diff --git a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/RobinsonProcessor.cs b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/RobinsonProcessor.cs index 4e61707c45..a8187caca3 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/RobinsonProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/RobinsonProcessor.cs @@ -2,6 +2,7 @@ // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // + namespace ImageSharp.Processing.Processors { using System; @@ -19,105 +20,113 @@ namespace ImageSharp.Processing.Processors /// /// The North gradient operator /// - private static readonly float[][] RobinsonNorth = - { - new float[] { 1, 2, 1 }, - new float[] { 0, 0, 0 }, - new float[] { -1, -2, -1 } - }; + private static readonly Fast2DArray RobinsonNorth = + new Fast2DArray(new float[,] + { + { 1, 2, 1 }, + { 0, 0, 0 }, + { -1, -2, -1 } + }); /// /// The NorthWest gradient operator /// - private static readonly float[][] RobinsonNorthWest = - { - new float[] { 2, 1, 0 }, - new float[] { 1, 0, -1 }, - new float[] { 0, -1, -2 } - }; + private static readonly Fast2DArray RobinsonNorthWest = + new Fast2DArray(new float[,] + { + { 2, 1, 0 }, + { 1, 0, -1 }, + { 0, -1, -2 } + }); /// /// The West gradient operator /// - private static readonly float[][] RobinsonWest = - { - new float[] { 1, 0, -1 }, - new float[] { 2, 0, -2 }, - new float[] { 1, 0, -1 } - }; + private static readonly Fast2DArray RobinsonWest = + new Fast2DArray(new float[,] + { + { 1, 0, -1 }, + { 2, 0, -2 }, + { 1, 0, -1 } + }); /// /// The SouthWest gradient operator /// - private static readonly float[][] RobinsonSouthWest = - { - new float[] { 0, -1, -2 }, - new float[] { 1, 0, -1 }, - new float[] { 2, 1, 0 } - }; + private static readonly Fast2DArray RobinsonSouthWest = + new Fast2DArray(new float[,] + { + { 0, -1, -2 }, + { 1, 0, -1 }, + { 2, 1, 0 } + }); /// /// The South gradient operator /// - private static readonly float[][] RobinsonSouth = - { - new float[] { -1, -2, -1 }, - new float[] { 0, 0, 0 }, - new float[] { 1, 2, 1 } - }; + private static readonly Fast2DArray RobinsonSouth = + new Fast2DArray(new float[,] + { + { -1, -2, -1 }, + { 0, 0, 0 }, + { 1, 2, 1 } + }); /// /// The SouthEast gradient operator /// - private static readonly float[][] RobinsonSouthEast = - { - new float[] { -2, -1, 0 }, - new float[] { -1, 0, 1 }, - new float[] { 0, 1, 2 } - }; + private static readonly Fast2DArray RobinsonSouthEast = + new Fast2DArray(new float[,] + { + { -2, -1, 0 }, + { -1, 0, 1 }, + { 0, 1, 2 } + }); /// /// The East gradient operator /// - private static readonly float[][] RobinsonEast = - { - new float[] { -1, 0, 1 }, - new float[] { -2, 0, 2 }, - new float[] { -1, 0, 1 } - }; + private static readonly Fast2DArray RobinsonEast = + new Fast2DArray(new float[,] + { + { -1, 0, 1 }, + { -2, 0, 2 }, + { -1, 0, 1 } + }); /// /// The NorthEast gradient operator /// - private static readonly float[][] RobinsonNorthEast = - { - new float[] { 0, 1, 2 }, - new float[] { -1, 0, 1 }, - new float[] { -2, -1, 0 } - }; + private static readonly Fast2DArray RobinsonNorthEast = + new Fast2DArray(new float[,] + { + { 0, 1, 2 }, + { -1, 0, 1 }, + { -2, -1, 0 } + }); /// - public override float[][] North => RobinsonNorth; + public override Fast2DArray North => RobinsonNorth; /// - public override float[][] NorthWest => RobinsonNorthWest; + public override Fast2DArray NorthWest => RobinsonNorthWest; /// - public override float[][] West => RobinsonWest; + public override Fast2DArray West => RobinsonWest; /// - public override float[][] SouthWest => RobinsonSouthWest; + public override Fast2DArray SouthWest => RobinsonSouthWest; /// - public override float[][] South => RobinsonSouth; + public override Fast2DArray South => RobinsonSouth; /// - public override float[][] SouthEast => RobinsonSouthEast; + public override Fast2DArray SouthEast => RobinsonSouthEast; /// - public override float[][] East => RobinsonEast; + public override Fast2DArray East => RobinsonEast; /// - public override float[][] NorthEast => RobinsonNorthEast; + public override Fast2DArray NorthEast => RobinsonNorthEast; } } \ No newline at end of file diff --git a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/ScharrProcessor.cs b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/ScharrProcessor.cs index de2a185f86..6b9e67ce9e 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/ScharrProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/ScharrProcessor.cs @@ -20,27 +20,31 @@ namespace ImageSharp.Processing.Processors /// /// The horizontal gradient operator. /// - private static readonly float[][] ScharrX = new float[3][] - { - new float[] { -3, 0, 3 }, - new float[] { -10, 0, 10 }, - new float[] { -3, 0, 3 } - }; + private static readonly Fast2DArray ScharrX = + new Fast2DArray(new float[,] + { + { -3, 0, 3 }, + { -10, 0, 10 }, + { -3, 0, 3 } + }); /// /// The vertical gradient operator. /// - private static readonly float[][] ScharrY = new float[3][] - { - new float[] { 3, 10, 3 }, - new float[] { 0, 0, 0 }, - new float[] { -3, -10, -3 } - }; - - /// - public override float[][] KernelX => ScharrX; + private static readonly Fast2DArray ScharrY = + new Fast2DArray(new float[,] + { + { 3, 10, 3 }, + { 0, 0, 0 }, + { -3, -10, -3 } + }); - /// - public override float[][] KernelY => ScharrY; + /// + /// Initializes a new instance of the class. + /// + public ScharrProcessor() + : base(ScharrX, ScharrY) + { + } } } \ No newline at end of file diff --git a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/SobelProcessor.cs b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/SobelProcessor.cs index 328c903dc7..1607889677 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/SobelProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/SobelProcessor.cs @@ -20,27 +20,31 @@ namespace ImageSharp.Processing.Processors /// /// The horizontal gradient operator. /// - private static readonly float[][] SobelX = - { - new float[] { -1, 0, 1 }, - new float[] { -2, 0, 2 }, - new float[] { -1, 0, 1 } - }; + private static readonly Fast2DArray SobelX = + new Fast2DArray(new float[,] + { + { -1, 0, 1 }, + { -2, 0, 2 }, + { -1, 0, 1 } + }); /// /// The vertical gradient operator. /// - private static readonly float[][] SobelY = - { - new float[] { -1, -2, -1 }, - new float[] { 0, 0, 0 }, - new float[] { 1, 2, 1 } - }; - - /// - public override float[][] KernelX => SobelX; + private static readonly Fast2DArray SobelY = + new Fast2DArray(new float[,] + { + { -1, -2, -1 }, + { 0, 0, 0 }, + { 1, 2, 1 } + }); - /// - public override float[][] KernelY => SobelY; + /// + /// Initializes a new instance of the class. + /// + public SobelProcessor() + : base(SobelX, SobelY) + { + } } } diff --git a/src/ImageSharp.Processing/Processors/Convolution/GaussianBlurProcessor.cs b/src/ImageSharp.Processing/Processors/Convolution/GaussianBlurProcessor.cs index 7cd3bbe9c5..49733a9bd0 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/GaussianBlurProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/GaussianBlurProcessor.cs @@ -71,12 +71,12 @@ namespace ImageSharp.Processing.Processors /// /// Gets the horizontal gradient operator. /// - public float[][] KernelX { get; } + public Fast2DArray KernelX { get; } /// /// Gets the vertical gradient operator. /// - public float[][] KernelY { get; } + public Fast2DArray KernelY { get; } /// protected override void OnApply(ImageBase source, Rectangle sourceRectangle) @@ -88,21 +88,18 @@ namespace ImageSharp.Processing.Processors /// Create a 1 dimensional Gaussian kernel using the Gaussian G(x) function /// /// Whether to calculate a horizontal kernel. - /// The - private float[][] CreateGaussianKernel(bool horizontal) + /// The + private Fast2DArray CreateGaussianKernel(bool horizontal) { int size = this.kernelSize; float weight = this.sigma; - float[][] kernel = horizontal ? new float[1][] : new float[size][]; + Fast2DArray kernel = horizontal + ? new Fast2DArray(new float[1, size]) + : new Fast2DArray(new float[size, 1]); - if (horizontal) - { - kernel[0] = new float[size]; - } - - float sum = 0.0f; + float sum = 0F; + float midpoint = (size - 1) / 2F; - float midpoint = (size - 1) / 2f; for (int i = 0; i < size; i++) { float x = i - midpoint; @@ -110,27 +107,27 @@ namespace ImageSharp.Processing.Processors sum += gx; if (horizontal) { - kernel[0][i] = gx; + kernel[0, i] = gx; } else { - kernel[i] = new[] { gx }; + kernel[i, 0] = gx; } } - // Normalise kernel so that the sum of all weights equals 1 + // Normalize kernel so that the sum of all weights equals 1 if (horizontal) { for (int i = 0; i < size; i++) { - kernel[0][i] = kernel[0][i] / sum; + kernel[0, i] = kernel[0, i] / sum; } } else { for (int i = 0; i < size; i++) { - kernel[i][0] = kernel[i][0] / sum; + kernel[i, 0] = kernel[i, 0] / sum; } } diff --git a/src/ImageSharp.Processing/Processors/Convolution/GaussianSharpenProcessor.cs b/src/ImageSharp.Processing/Processors/Convolution/GaussianSharpenProcessor.cs index d0654dd77b..2254a61b6e 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/GaussianSharpenProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/GaussianSharpenProcessor.cs @@ -73,12 +73,12 @@ namespace ImageSharp.Processing.Processors /// /// Gets the horizontal gradient operator. /// - public float[][] KernelX { get; } + public Fast2DArray KernelX { get; } /// /// Gets the vertical gradient operator. /// - public float[][] KernelY { get; } + public Fast2DArray KernelY { get; } /// protected override void OnApply(ImageBase source, Rectangle sourceRectangle) @@ -90,17 +90,14 @@ namespace ImageSharp.Processing.Processors /// Create a 1 dimensional Gaussian kernel using the Gaussian G(x) function /// /// Whether to calculate a horizontal kernel. - /// The - private float[][] CreateGaussianKernel(bool horizontal) + /// The + private Fast2DArray CreateGaussianKernel(bool horizontal) { int size = this.kernelSize; float weight = this.sigma; - float[][] kernel = horizontal ? new float[1][] : new float[size][]; - - if (horizontal) - { - kernel[0] = new float[size]; - } + Fast2DArray kernel = horizontal + ? new Fast2DArray(new float[1, size]) + : new Fast2DArray(new float[size, 1]); float sum = 0; @@ -112,11 +109,11 @@ namespace ImageSharp.Processing.Processors sum += gx; if (horizontal) { - kernel[0][i] = gx; + kernel[0, i] = gx; } else { - kernel[i] = new[] { gx }; + kernel[i, 0] = gx; } } @@ -130,12 +127,12 @@ namespace ImageSharp.Processing.Processors if (i == midpointRounded) { // Calculate central value - kernel[0][i] = (2f * sum) - kernel[0][i]; + kernel[0, i] = (2F * sum) - kernel[0, i]; } else { // invert value - kernel[0][i] = -kernel[0][i]; + kernel[0, i] = -kernel[0, i]; } } } @@ -146,12 +143,12 @@ namespace ImageSharp.Processing.Processors if (i == midpointRounded) { // Calculate central value - kernel[i][0] = (2 * sum) - kernel[i][0]; + kernel[i, 0] = (2 * sum) - kernel[i, 0]; } else { // invert value - kernel[i][0] = -kernel[i][0]; + kernel[i, 0] = -kernel[i, 0]; } } } @@ -161,14 +158,14 @@ namespace ImageSharp.Processing.Processors { for (int i = 0; i < size; i++) { - kernel[0][i] = kernel[0][i] / sum; + kernel[0, i] = kernel[0, i] / sum; } } else { for (int i = 0; i < size; i++) { - kernel[i][0] = kernel[i][0] / sum; + kernel[i, 0] = kernel[i, 0] / sum; } } diff --git a/src/ImageSharp/Common/Helpers/ImageMaths.cs b/src/ImageSharp/Common/Helpers/ImageMaths.cs index fc94c689f0..5720d82186 100644 --- a/src/ImageSharp/Common/Helpers/ImageMaths.cs +++ b/src/ImageSharp/Common/Helpers/ImageMaths.cs @@ -37,6 +37,7 @@ namespace ImageSharp /// /// The /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int GetBitsNeededForColorDepth(int colors) { return (int)Math.Ceiling(Math.Log(colors, 2)); @@ -48,6 +49,7 @@ namespace ImageSharp /// The x provided to G(x). /// The spread of the blur. /// The Gaussian G(x) + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float Gaussian(float x, float sigma) { const float Numerator = 1.0f; @@ -72,6 +74,7 @@ namespace ImageSharp /// /// The . /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float GetBcValue(float x, float b, float c) { float temp; @@ -104,6 +107,7 @@ namespace ImageSharp /// /// The . /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float SinC(float x) { if (Math.Abs(x) > Constants.Epsilon) @@ -122,6 +126,7 @@ namespace ImageSharp /// /// The representing the degree as radians. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float DegreesToRadians(float degrees) { return degrees * (float)(Math.PI / 180); @@ -139,6 +144,7 @@ namespace ImageSharp /// /// The bounding . /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Rectangle GetBoundingRectangle(Point topLeft, Point bottomRight) { return new Rectangle(topLeft.X, topLeft.Y, bottomRight.X - topLeft.X, bottomRight.Y - topLeft.Y); From 127a2921ce0719a0ae4b8e819811d4874cdfd61e Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 18 Feb 2017 12:12:04 +0100 Subject: [PATCH 073/142] better Array2D benchmark --- .../ImageSharp.Benchmarks/General/Array2D.cs | 73 ++++++++++++++++--- 1 file changed, 62 insertions(+), 11 deletions(-) diff --git a/tests/ImageSharp.Benchmarks/General/Array2D.cs b/tests/ImageSharp.Benchmarks/General/Array2D.cs index a01ba77adc..fce92e9be5 100644 --- a/tests/ImageSharp.Benchmarks/General/Array2D.cs +++ b/tests/ImageSharp.Benchmarks/General/Array2D.cs @@ -5,25 +5,31 @@ namespace ImageSharp.Benchmarks.General { + using System; + using BenchmarkDotNet.Attributes; public class Array2D { - private float[,] data; + private float[] flatArray; + + private float[,] array2D; private float[][] jaggedData; private Fast2DArray fastData; - - [Params(10, 100, 1000, 10000)] + + [Params(4, 16, 128)] public int Count { get; set; } - public int Index { get; set; } + public int Min { get; private set; } + public int Max { get; private set; } [Setup] public void SetUp() { - this.data = new float[this.Count, this.Count]; + this.flatArray = new float[this.Count * this.Count]; + this.array2D = new float[this.Count, this.Count]; this.jaggedData = new float[this.Count][]; for (int i = 0; i < this.Count; i++) @@ -31,27 +37,72 @@ namespace ImageSharp.Benchmarks.General this.jaggedData[i] = new float[this.Count]; } - this.fastData = new Fast2DArray(this.data); + this.fastData = new Fast2DArray(this.array2D); - this.Index = this.Count / 2; + this.Min = this.Count / 2 - 10; + this.Min = Math.Max(0, this.Min); + this.Max = this.Min + Math.Min(10, this.Count); + } + + [Benchmark(Description = "Emulated 2D array access using flat array")] + public float FlatArrayIndex() + { + float[] a = this.flatArray; + float s = 0; + int count = this.Count; + for (int i = this.Min; i < this.Max; i++) + { + for (int j = this.Min; j < this.Max; j++) + { + s += a[count * i + j]; + } + } + return s; } [Benchmark(Baseline = true, Description = "Array access using 2D array")] - public float ArrayIndex() + public float Array2DIndex() { - return this.data[this.Index, this.Index]; + float s = 0; + float[,] a = this.array2D; + for (int i = this.Min; i < this.Max; i++) + { + for (int j = this.Min; j < this.Max; j++) + { + s += a[i, j]; + } + } + return s; } [Benchmark(Description = "Array access using a jagged array")] public float ArrayJaggedIndex() { - return this.jaggedData[this.Index][this.Index]; + float s = 0; + float[][] a = this.jaggedData; + for (int i = this.Min; i < this.Max; i++) + { + for (int j = this.Min; j < this.Max; j++) + { + s += a[i][j]; + } + } + return s; } [Benchmark(Description = "Array access using Fast2DArray")] public float ArrayFastIndex() { - return this.fastData[this.Index, this.Index]; + float s = 0; + Fast2DArray a = this.fastData; + for (int i = this.Min; i < this.Max; i++) + { + for (int j = this.Min; j < this.Max; j++) + { + s += a[i, j]; + } + } + return s; } } } From 968b424a5cfa47406b919dc4ba0679abac1f8971 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Sat, 18 Feb 2017 11:14:53 +0000 Subject: [PATCH 074/142] Improve Fast2DArray initialisation --- .../Convolution/BoxBlurProcessor.cs | 4 +-- .../EdgeDetection/KayyaliProcessor.cs | 8 ++--- .../EdgeDetection/KirschProcessor.cs | 32 +++++++++---------- .../EdgeDetection/Laplacian3X3Processor.cs | 4 +-- .../EdgeDetection/Laplacian5X5Processor.cs | 4 +-- .../LaplacianOfGaussianProcessor.cs | 4 +-- .../EdgeDetection/PrewittProcessor.cs | 8 ++--- .../EdgeDetection/RobertsCrossProcessor.cs | 8 ++--- .../EdgeDetection/RobinsonProcessor.cs | 32 +++++++++---------- .../EdgeDetection/ScharrProcessor.cs | 8 ++--- .../EdgeDetection/SobelProcessor.cs | 8 ++--- .../Convolution/GaussianBlurProcessor.cs | 4 +-- .../Convolution/GaussianSharpenProcessor.cs | 4 +-- .../Common/Helpers/Fast2DArray{T}.cs | 29 +++++++++++++++++ .../Dithering/ErrorDiffusion/Atkinson.cs | 4 +-- .../Dithering/ErrorDiffusion/Burks.cs | 4 +-- .../ErrorDiffusion/FloydSteinberg.cs | 4 +-- .../ErrorDiffusion/JarvisJudiceNinke.cs | 4 +-- .../Dithering/ErrorDiffusion/Sierra2.cs | 4 +-- .../Dithering/ErrorDiffusion/Sierra3.cs | 4 +-- .../Dithering/ErrorDiffusion/SierraLite.cs | 4 +-- .../Dithering/ErrorDiffusion/Stucki.cs | 4 +-- src/ImageSharp/Dithering/Ordered/Bayer.cs | 4 +-- src/ImageSharp/Dithering/Ordered/Ordered.cs | 4 +-- .../Common/Fast2DArrayTests.cs | 28 +++++++++++++--- 25 files changed, 136 insertions(+), 89 deletions(-) diff --git a/src/ImageSharp.Processing/Processors/Convolution/BoxBlurProcessor.cs b/src/ImageSharp.Processing/Processors/Convolution/BoxBlurProcessor.cs index 8ca1ca6669..a9caea5559 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/BoxBlurProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/BoxBlurProcessor.cs @@ -57,8 +57,8 @@ namespace ImageSharp.Processing.Processors { int size = this.kernelSize; Fast2DArray kernel = horizontal - ? new Fast2DArray(new float[1, size]) - : new Fast2DArray(new float[size, 1]); + ? new Fast2DArray(size, 1) + : new Fast2DArray(1, size); float sum = 0F; for (int i = 0; i < size; i++) diff --git a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/KayyaliProcessor.cs b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/KayyaliProcessor.cs index 6e1452e17f..bda3c9317a 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/KayyaliProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/KayyaliProcessor.cs @@ -21,23 +21,23 @@ namespace ImageSharp.Processing.Processors /// The horizontal gradient operator. /// private static readonly Fast2DArray KayyaliX = - new Fast2DArray(new float[,] + new float[,] { { 6, 0, -6 }, { 0, 0, 0 }, { -6, 0, 6 } - }); + }; /// /// The vertical gradient operator. /// private static readonly Fast2DArray KayyaliY = - new Fast2DArray(new float[,] + new float[,] { { -6, 0, 6 }, { 0, 0, 0 }, { 6, 0, -6 } - }); + }; /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/KirschProcessor.cs b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/KirschProcessor.cs index f8cb9aba9d..4a555137eb 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/KirschProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/KirschProcessor.cs @@ -21,89 +21,89 @@ namespace ImageSharp.Processing.Processors /// The North gradient operator /// private static readonly Fast2DArray KirschNorth = - new Fast2DArray(new float[,] + new float[,] { { 5, 5, 5 }, { -3, 0, -3 }, { -3, -3, -3 } - }); + }; /// /// The NorthWest gradient operator /// private static readonly Fast2DArray KirschNorthWest = - new Fast2DArray(new float[,] + new float[,] { { 5, 5, -3 }, { 5, 0, -3 }, { -3, -3, -3 } - }); + }; /// /// The West gradient operator /// private static readonly Fast2DArray KirschWest = - new Fast2DArray(new float[,] + new float[,] { { 5, -3, -3 }, { 5, 0, -3 }, { 5, -3, -3 } - }); + }; /// /// The SouthWest gradient operator /// private static readonly Fast2DArray KirschSouthWest = - new Fast2DArray(new float[,] + new float[,] { { -3, -3, -3 }, { 5, 0, -3 }, { 5, 5, -3 } - }); + }; /// /// The South gradient operator /// private static readonly Fast2DArray KirschSouth = - new Fast2DArray(new float[,] + new float[,] { { -3, -3, -3 }, { -3, 0, -3 }, { 5, 5, 5 } - }); + }; /// /// The SouthEast gradient operator /// private static readonly Fast2DArray KirschSouthEast = - new Fast2DArray(new float[,] + new float[,] { { -3, -3, -3 }, { -3, 0, 5 }, { -3, 5, 5 } - }); + }; /// /// The East gradient operator /// private static readonly Fast2DArray KirschEast = - new Fast2DArray(new float[,] + new float[,] { { -3, -3, 5 }, { -3, 0, 5 }, { -3, -3, 5 } - }); + }; /// /// The NorthEast gradient operator /// private static readonly Fast2DArray KirschNorthEast = - new Fast2DArray(new float[,] + new float[,] { { -3, 5, 5 }, { -3, 0, 5 }, { -3, -3, -3 } - }); + }; /// public override Fast2DArray North => KirschNorth; diff --git a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/Laplacian3X3Processor.cs b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/Laplacian3X3Processor.cs index 4b23dfe470..74fcfcad55 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/Laplacian3X3Processor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/Laplacian3X3Processor.cs @@ -21,12 +21,12 @@ namespace ImageSharp.Processing.Processors /// The 2d gradient operator. /// private static readonly Fast2DArray Laplacian3X3XY = - new Fast2DArray(new float[,] + new float[,] { { -1, -1, -1 }, { -1, 8, -1 }, { -1, -1, -1 } - }); + }; /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/Laplacian5X5Processor.cs b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/Laplacian5X5Processor.cs index 304dafbbd1..c00831a694 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/Laplacian5X5Processor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/Laplacian5X5Processor.cs @@ -21,14 +21,14 @@ namespace ImageSharp.Processing.Processors /// The 2d gradient operator. /// private static readonly Fast2DArray Laplacian5X5XY = - new Fast2DArray(new float[,] + new float[,] { { -1, -1, -1, -1, -1 }, { -1, -1, -1, -1, -1 }, { -1, -1, 24, -1, -1 }, { -1, -1, -1, -1, -1 }, { -1, -1, -1, -1, -1 } - }); + }; /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/LaplacianOfGaussianProcessor.cs b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/LaplacianOfGaussianProcessor.cs index e18957a69a..8ee01047b2 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/LaplacianOfGaussianProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/LaplacianOfGaussianProcessor.cs @@ -21,14 +21,14 @@ namespace ImageSharp.Processing.Processors /// The 2d gradient operator. /// private static readonly Fast2DArray LaplacianOfGaussianXY = - new Fast2DArray(new float[,] + new float[,] { { 0, 0, -1, 0, 0 }, { 0, -1, -2, -1, 0 }, { -1, -2, 16, -2, -1 }, { 0, -1, -2, -1, 0 }, { 0, 0, -1, 0, 0 } - }); + }; /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/PrewittProcessor.cs b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/PrewittProcessor.cs index 1d61b8cd94..cf213e7240 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/PrewittProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/PrewittProcessor.cs @@ -21,23 +21,23 @@ namespace ImageSharp.Processing.Processors /// The horizontal gradient operator. /// private static readonly Fast2DArray PrewittX = - new Fast2DArray(new float[,] + new float[,] { { -1, 0, 1 }, { -1, 0, 1 }, { -1, 0, 1 } - }); + }; /// /// The vertical gradient operator. /// private static readonly Fast2DArray PrewittY = - new Fast2DArray(new float[,] + new float[,] { { 1, 1, 1 }, { 0, 0, 0 }, { -1, -1, -1 } - }); + }; /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/RobertsCrossProcessor.cs b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/RobertsCrossProcessor.cs index 83f13a3429..de1f279ea1 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/RobertsCrossProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/RobertsCrossProcessor.cs @@ -21,21 +21,21 @@ namespace ImageSharp.Processing.Processors /// The horizontal gradient operator. /// private static readonly Fast2DArray RobertsCrossX = - new Fast2DArray(new float[,] + new float[,] { { 1, 0 }, { 0, -1 } - }); + }; /// /// The vertical gradient operator. /// private static readonly Fast2DArray RobertsCrossY = - new Fast2DArray(new float[,] + new float[,] { { 0, 1 }, { -1, 0 } - }); + }; /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/RobinsonProcessor.cs b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/RobinsonProcessor.cs index a8187caca3..13d7a3f498 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/RobinsonProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/RobinsonProcessor.cs @@ -21,89 +21,89 @@ namespace ImageSharp.Processing.Processors /// The North gradient operator /// private static readonly Fast2DArray RobinsonNorth = - new Fast2DArray(new float[,] + new float[,] { { 1, 2, 1 }, { 0, 0, 0 }, { -1, -2, -1 } - }); + }; /// /// The NorthWest gradient operator /// private static readonly Fast2DArray RobinsonNorthWest = - new Fast2DArray(new float[,] + new float[,] { { 2, 1, 0 }, { 1, 0, -1 }, { 0, -1, -2 } - }); + }; /// /// The West gradient operator /// private static readonly Fast2DArray RobinsonWest = - new Fast2DArray(new float[,] + new float[,] { { 1, 0, -1 }, { 2, 0, -2 }, { 1, 0, -1 } - }); + }; /// /// The SouthWest gradient operator /// private static readonly Fast2DArray RobinsonSouthWest = - new Fast2DArray(new float[,] + new float[,] { { 0, -1, -2 }, { 1, 0, -1 }, { 2, 1, 0 } - }); + }; /// /// The South gradient operator /// private static readonly Fast2DArray RobinsonSouth = - new Fast2DArray(new float[,] + new float[,] { { -1, -2, -1 }, { 0, 0, 0 }, { 1, 2, 1 } - }); + }; /// /// The SouthEast gradient operator /// private static readonly Fast2DArray RobinsonSouthEast = - new Fast2DArray(new float[,] + new float[,] { { -2, -1, 0 }, { -1, 0, 1 }, { 0, 1, 2 } - }); + }; /// /// The East gradient operator /// private static readonly Fast2DArray RobinsonEast = - new Fast2DArray(new float[,] + new float[,] { { -1, 0, 1 }, { -2, 0, 2 }, { -1, 0, 1 } - }); + }; /// /// The NorthEast gradient operator /// private static readonly Fast2DArray RobinsonNorthEast = - new Fast2DArray(new float[,] + new float[,] { { 0, 1, 2 }, { -1, 0, 1 }, { -2, -1, 0 } - }); + }; /// public override Fast2DArray North => RobinsonNorth; diff --git a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/ScharrProcessor.cs b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/ScharrProcessor.cs index 6b9e67ce9e..11cb91b0d9 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/ScharrProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/ScharrProcessor.cs @@ -21,23 +21,23 @@ namespace ImageSharp.Processing.Processors /// The horizontal gradient operator. /// private static readonly Fast2DArray ScharrX = - new Fast2DArray(new float[,] + new float[,] { { -3, 0, 3 }, { -10, 0, 10 }, { -3, 0, 3 } - }); + }; /// /// The vertical gradient operator. /// private static readonly Fast2DArray ScharrY = - new Fast2DArray(new float[,] + new float[,] { { 3, 10, 3 }, { 0, 0, 0 }, { -3, -10, -3 } - }); + }; /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/SobelProcessor.cs b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/SobelProcessor.cs index 1607889677..347a19dab6 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/SobelProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/SobelProcessor.cs @@ -21,23 +21,23 @@ namespace ImageSharp.Processing.Processors /// The horizontal gradient operator. /// private static readonly Fast2DArray SobelX = - new Fast2DArray(new float[,] + new float[,] { { -1, 0, 1 }, { -2, 0, 2 }, { -1, 0, 1 } - }); + }; /// /// The vertical gradient operator. /// private static readonly Fast2DArray SobelY = - new Fast2DArray(new float[,] + new float[,] { { -1, -2, -1 }, { 0, 0, 0 }, { 1, 2, 1 } - }); + }; /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp.Processing/Processors/Convolution/GaussianBlurProcessor.cs b/src/ImageSharp.Processing/Processors/Convolution/GaussianBlurProcessor.cs index 49733a9bd0..87dde5b475 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/GaussianBlurProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/GaussianBlurProcessor.cs @@ -94,8 +94,8 @@ namespace ImageSharp.Processing.Processors int size = this.kernelSize; float weight = this.sigma; Fast2DArray kernel = horizontal - ? new Fast2DArray(new float[1, size]) - : new Fast2DArray(new float[size, 1]); + ? new Fast2DArray(size, 1) + : new Fast2DArray(1, size); float sum = 0F; float midpoint = (size - 1) / 2F; diff --git a/src/ImageSharp.Processing/Processors/Convolution/GaussianSharpenProcessor.cs b/src/ImageSharp.Processing/Processors/Convolution/GaussianSharpenProcessor.cs index 2254a61b6e..5cf8cdf541 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/GaussianSharpenProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/GaussianSharpenProcessor.cs @@ -96,8 +96,8 @@ namespace ImageSharp.Processing.Processors int size = this.kernelSize; float weight = this.sigma; Fast2DArray kernel = horizontal - ? new Fast2DArray(new float[1, size]) - : new Fast2DArray(new float[size, 1]); + ? new Fast2DArray(size, 1) + : new Fast2DArray(1, size); float sum = 0; diff --git a/src/ImageSharp/Common/Helpers/Fast2DArray{T}.cs b/src/ImageSharp/Common/Helpers/Fast2DArray{T}.cs index 26ec816ce3..09cb0fec23 100644 --- a/src/ImageSharp/Common/Helpers/Fast2DArray{T}.cs +++ b/src/ImageSharp/Common/Helpers/Fast2DArray{T}.cs @@ -30,6 +30,22 @@ namespace ImageSharp /// public int Height; + /// + /// Initializes a new instance of the struct. + /// + /// The width. + /// The height. + public Fast2DArray(int width, int height) + { + this.Height = height; + this.Width = width; + + Guard.MustBeGreaterThan(width, 0, nameof(width)); + Guard.MustBeGreaterThan(height, 0, nameof(height)); + + this.Data = new T[this.Width * this.Height]; + } + /// /// Initializes a new instance of the struct. /// @@ -77,6 +93,19 @@ namespace ImageSharp } } + /// + /// Performs an implicit conversion from a 2D array to a . + /// + /// The source array. + /// + /// The represenation on the source data. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Fast2DArray(T[,] data) + { + return new Fast2DArray(data); + } + /// /// Checks the coordinates to ensure they are within bounds. /// diff --git a/src/ImageSharp/Dithering/ErrorDiffusion/Atkinson.cs b/src/ImageSharp/Dithering/ErrorDiffusion/Atkinson.cs index 1fa6852c22..b94b872552 100644 --- a/src/ImageSharp/Dithering/ErrorDiffusion/Atkinson.cs +++ b/src/ImageSharp/Dithering/ErrorDiffusion/Atkinson.cs @@ -15,12 +15,12 @@ namespace ImageSharp.Dithering /// The diffusion matrix /// private static readonly Fast2DArray AtkinsonMatrix = - new Fast2DArray(new float[,] + new float[,] { { 0, 0, 1, 1 }, { 1, 1, 1, 0 }, { 0, 1, 0, 0 } - }); + }; /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp/Dithering/ErrorDiffusion/Burks.cs b/src/ImageSharp/Dithering/ErrorDiffusion/Burks.cs index a4adcb0a49..894b6e236e 100644 --- a/src/ImageSharp/Dithering/ErrorDiffusion/Burks.cs +++ b/src/ImageSharp/Dithering/ErrorDiffusion/Burks.cs @@ -15,11 +15,11 @@ namespace ImageSharp.Dithering /// The diffusion matrix /// private static readonly Fast2DArray BurksMatrix = - new Fast2DArray(new float[,] + new float[,] { { 0, 0, 0, 8, 4 }, { 2, 4, 8, 4, 2 } - }); + }; /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp/Dithering/ErrorDiffusion/FloydSteinberg.cs b/src/ImageSharp/Dithering/ErrorDiffusion/FloydSteinberg.cs index 7b67d2dd12..f7a93667fe 100644 --- a/src/ImageSharp/Dithering/ErrorDiffusion/FloydSteinberg.cs +++ b/src/ImageSharp/Dithering/ErrorDiffusion/FloydSteinberg.cs @@ -15,11 +15,11 @@ namespace ImageSharp.Dithering /// The diffusion matrix /// private static readonly Fast2DArray FloydSteinbergMatrix = - new Fast2DArray(new float[,] + new float[,] { { 0, 0, 7 }, { 3, 5, 1 } - }); + }; /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp/Dithering/ErrorDiffusion/JarvisJudiceNinke.cs b/src/ImageSharp/Dithering/ErrorDiffusion/JarvisJudiceNinke.cs index 32f38fbc85..60fef81216 100644 --- a/src/ImageSharp/Dithering/ErrorDiffusion/JarvisJudiceNinke.cs +++ b/src/ImageSharp/Dithering/ErrorDiffusion/JarvisJudiceNinke.cs @@ -15,12 +15,12 @@ namespace ImageSharp.Dithering /// The diffusion matrix /// private static readonly Fast2DArray JarvisJudiceNinkeMatrix = - new Fast2DArray(new float[,] + new float[,] { { 0, 0, 0, 7, 5 }, { 3, 5, 7, 5, 3 }, { 1, 3, 5, 3, 1 } - }); + }; /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp/Dithering/ErrorDiffusion/Sierra2.cs b/src/ImageSharp/Dithering/ErrorDiffusion/Sierra2.cs index 47b14944e2..4325438e08 100644 --- a/src/ImageSharp/Dithering/ErrorDiffusion/Sierra2.cs +++ b/src/ImageSharp/Dithering/ErrorDiffusion/Sierra2.cs @@ -15,11 +15,11 @@ namespace ImageSharp.Dithering /// The diffusion matrix /// private static readonly Fast2DArray Sierra2Matrix = - new Fast2DArray(new float[,] + new float[,] { { 0, 0, 0, 4, 3 }, { 1, 2, 3, 2, 1 } - }); + }; /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp/Dithering/ErrorDiffusion/Sierra3.cs b/src/ImageSharp/Dithering/ErrorDiffusion/Sierra3.cs index ae33954cfd..25ea70d0a6 100644 --- a/src/ImageSharp/Dithering/ErrorDiffusion/Sierra3.cs +++ b/src/ImageSharp/Dithering/ErrorDiffusion/Sierra3.cs @@ -15,12 +15,12 @@ namespace ImageSharp.Dithering /// The diffusion matrix /// private static readonly Fast2DArray Sierra3Matrix = - new Fast2DArray(new float[,] + new float[,] { { 0, 0, 0, 5, 3 }, { 2, 4, 5, 4, 2 }, { 0, 2, 3, 2, 0 } - }); + }; /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp/Dithering/ErrorDiffusion/SierraLite.cs b/src/ImageSharp/Dithering/ErrorDiffusion/SierraLite.cs index 8a1e178165..c7b1d214f1 100644 --- a/src/ImageSharp/Dithering/ErrorDiffusion/SierraLite.cs +++ b/src/ImageSharp/Dithering/ErrorDiffusion/SierraLite.cs @@ -15,11 +15,11 @@ namespace ImageSharp.Dithering /// The diffusion matrix /// private static readonly Fast2DArray SierraLiteMatrix = - new Fast2DArray(new float[,] + new float[,] { { 0, 0, 2 }, { 1, 1, 0 } - }); + }; /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp/Dithering/ErrorDiffusion/Stucki.cs b/src/ImageSharp/Dithering/ErrorDiffusion/Stucki.cs index b5d22b2592..93258c3508 100644 --- a/src/ImageSharp/Dithering/ErrorDiffusion/Stucki.cs +++ b/src/ImageSharp/Dithering/ErrorDiffusion/Stucki.cs @@ -15,12 +15,12 @@ namespace ImageSharp.Dithering /// The diffusion matrix /// private static readonly Fast2DArray StuckiMatrix = - new Fast2DArray(new float[,] + new float[,] { { 0, 0, 0, 8, 4 }, { 2, 4, 8, 4, 2 }, { 1, 2, 4, 2, 1 } - }); + }; /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp/Dithering/Ordered/Bayer.cs b/src/ImageSharp/Dithering/Ordered/Bayer.cs index 3d3d900233..ff56196c59 100644 --- a/src/ImageSharp/Dithering/Ordered/Bayer.cs +++ b/src/ImageSharp/Dithering/Ordered/Bayer.cs @@ -18,13 +18,13 @@ namespace ImageSharp.Dithering.Ordered /// This is calculated by multiplying each value in the original matrix by 16 and subtracting 1 /// private static readonly Fast2DArray ThresholdMatrix = - new Fast2DArray(new byte[,] + new byte[,] { { 15, 143, 47, 175 }, { 207, 79, 239, 111 }, { 63, 191, 31, 159 }, { 255, 127, 223, 95 } - }); + }; /// public Fast2DArray Matrix { get; } = ThresholdMatrix; diff --git a/src/ImageSharp/Dithering/Ordered/Ordered.cs b/src/ImageSharp/Dithering/Ordered/Ordered.cs index bb1f21060d..86c325cd1f 100644 --- a/src/ImageSharp/Dithering/Ordered/Ordered.cs +++ b/src/ImageSharp/Dithering/Ordered/Ordered.cs @@ -18,13 +18,13 @@ namespace ImageSharp.Dithering.Ordered /// This is calculated by multiplying each value in the original matrix by 16 /// private static readonly Fast2DArray ThresholdMatrix = - new Fast2DArray(new byte[,] + new byte[,] { { 0, 128, 32, 160 }, { 192, 64, 224, 96 }, { 48, 176, 16, 144 }, { 240, 112, 208, 80 } - }); + }; /// public Fast2DArray Matrix { get; } = ThresholdMatrix; diff --git a/tests/ImageSharp.Tests/Common/Fast2DArrayTests.cs b/tests/ImageSharp.Tests/Common/Fast2DArrayTests.cs index 903ea6f8d9..7db7a4820b 100644 --- a/tests/ImageSharp.Tests/Common/Fast2DArrayTests.cs +++ b/tests/ImageSharp.Tests/Common/Fast2DArrayTests.cs @@ -21,9 +21,27 @@ namespace ImageSharp.Tests.Common public void Fast2DArrayThrowsOnNullInitializer() { Assert.Throws(() => - { - Fast2DArray fast = new Fast2DArray(null); - }); + { + Fast2DArray fast = new Fast2DArray(null); + }); + } + + [Fact] + public void Fast2DArrayThrowsOnEmptyZeroWidth() + { + Assert.Throws(() => + { + Fast2DArray fast = new Fast2DArray(0, 10); + }); + } + + [Fact] + public void Fast2DArrayThrowsOnEmptyZeroHeight() + { + Assert.Throws(() => + { + Fast2DArray fast = new Fast2DArray(10, 0); + }); } [Fact] @@ -46,7 +64,7 @@ namespace ImageSharp.Tests.Common [Fact] public void Fast2DArrayGetReturnsCorrectResults() { - Fast2DArray fast = new Fast2DArray(FloydSteinbergMatrix); + Fast2DArray fast = FloydSteinbergMatrix; for (int row = 0; row < fast.Height; row++) { @@ -60,7 +78,7 @@ namespace ImageSharp.Tests.Common [Fact] public void Fast2DArrayGetSetReturnsCorrectResults() { - Fast2DArray fast = new Fast2DArray(new float[4, 4]); + Fast2DArray fast = new Fast2DArray(4, 4); const float Val = 5F; fast[3, 3] = Val; From 9306e531a892043a0afdcd046b1d8caead557c18 Mon Sep 17 00:00:00 2001 From: Dirk Lemstra Date: Sat, 18 Feb 2017 12:30:55 +0100 Subject: [PATCH 075/142] Whitespace. --- src/ImageSharp.Formats.Bmp/BmpDecoder.cs | 4 ++-- src/ImageSharp.Formats.Bmp/BmpEncoder.cs | 2 +- src/ImageSharp.Formats.Gif/GifDecoder.cs | 2 +- src/ImageSharp.Formats.Gif/GifEncoder.cs | 4 ++-- src/ImageSharp.Formats.Jpeg/JpegDecoder.cs | 2 +- src/ImageSharp.Formats.Png/PngEncoder.cs | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/ImageSharp.Formats.Bmp/BmpDecoder.cs b/src/ImageSharp.Formats.Bmp/BmpDecoder.cs index ae7e6c72a2..b91f727c3c 100644 --- a/src/ImageSharp.Formats.Bmp/BmpDecoder.cs +++ b/src/ImageSharp.Formats.Bmp/BmpDecoder.cs @@ -27,8 +27,8 @@ namespace ImageSharp.Formats { /// public void Decode(Image image, Stream stream) - where TColor : struct, IPackedPixel, IEquatable - { + where TColor : struct, IPackedPixel, IEquatable + { Guard.NotNull(image, "image"); Guard.NotNull(stream, "stream"); diff --git a/src/ImageSharp.Formats.Bmp/BmpEncoder.cs b/src/ImageSharp.Formats.Bmp/BmpEncoder.cs index 2ed6b3b986..d43efc5139 100644 --- a/src/ImageSharp.Formats.Bmp/BmpEncoder.cs +++ b/src/ImageSharp.Formats.Bmp/BmpEncoder.cs @@ -22,7 +22,7 @@ namespace ImageSharp.Formats /// public void Encode(Image image, Stream stream) where TColor : struct, IPackedPixel, IEquatable - { + { BmpEncoderCore encoder = new BmpEncoderCore(); encoder.Encode(image, stream, this.BitsPerPixel); } diff --git a/src/ImageSharp.Formats.Gif/GifDecoder.cs b/src/ImageSharp.Formats.Gif/GifDecoder.cs index ce8c0c06e8..f329744abf 100644 --- a/src/ImageSharp.Formats.Gif/GifDecoder.cs +++ b/src/ImageSharp.Formats.Gif/GifDecoder.cs @@ -16,7 +16,7 @@ namespace ImageSharp.Formats /// public void Decode(Image image, Stream stream) where TColor : struct, IPackedPixel, IEquatable - { + { new GifDecoderCore().Decode(image, stream); } } diff --git a/src/ImageSharp.Formats.Gif/GifEncoder.cs b/src/ImageSharp.Formats.Gif/GifEncoder.cs index 2446f0f681..45c2164e14 100644 --- a/src/ImageSharp.Formats.Gif/GifEncoder.cs +++ b/src/ImageSharp.Formats.Gif/GifEncoder.cs @@ -33,8 +33,8 @@ namespace ImageSharp.Formats /// public void Encode(Image image, Stream stream) - where TColor : struct, IPackedPixel, IEquatable - { + where TColor : struct, IPackedPixel, IEquatable + { GifEncoderCore encoder = new GifEncoderCore { Quality = this.Quality, diff --git a/src/ImageSharp.Formats.Jpeg/JpegDecoder.cs b/src/ImageSharp.Formats.Jpeg/JpegDecoder.cs index 9ee216b4d7..41d2a2ad42 100644 --- a/src/ImageSharp.Formats.Jpeg/JpegDecoder.cs +++ b/src/ImageSharp.Formats.Jpeg/JpegDecoder.cs @@ -16,7 +16,7 @@ namespace ImageSharp.Formats /// public void Decode(Image image, Stream stream) where TColor : struct, IPackedPixel, IEquatable - { + { Guard.NotNull(image, "image"); Guard.NotNull(stream, "stream"); diff --git a/src/ImageSharp.Formats.Png/PngEncoder.cs b/src/ImageSharp.Formats.Png/PngEncoder.cs index 13125e30e0..7f71d1f393 100644 --- a/src/ImageSharp.Formats.Png/PngEncoder.cs +++ b/src/ImageSharp.Formats.Png/PngEncoder.cs @@ -58,7 +58,7 @@ namespace ImageSharp.Formats /// public void Encode(Image image, Stream stream) where TColor : struct, IPackedPixel, IEquatable - { + { PngEncoderCore encoder = new PngEncoderCore { CompressionLevel = this.CompressionLevel, From 44f5538c714e15726f9bf33b9ef92ca3f380c7a2 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 18 Feb 2017 15:00:12 +0100 Subject: [PATCH 076/142] ArrayPointer --- src/ImageSharp/Common/Helpers/ThrowHelper.cs | 17 ++++ .../Common/Memory/ArrayPointer{T}.cs | 57 +++++++++++ .../{Helpers => Memory}/Fast2DArray{T}.cs | 2 +- .../Common/ArrayPointerTests.cs | 97 +++++++++++++++++++ 4 files changed, 172 insertions(+), 1 deletion(-) create mode 100644 src/ImageSharp/Common/Helpers/ThrowHelper.cs create mode 100644 src/ImageSharp/Common/Memory/ArrayPointer{T}.cs rename src/ImageSharp/Common/{Helpers => Memory}/Fast2DArray{T}.cs (98%) create mode 100644 tests/ImageSharp.Tests/Common/ArrayPointerTests.cs diff --git a/src/ImageSharp/Common/Helpers/ThrowHelper.cs b/src/ImageSharp/Common/Helpers/ThrowHelper.cs new file mode 100644 index 0000000000..a832645334 --- /dev/null +++ b/src/ImageSharp/Common/Helpers/ThrowHelper.cs @@ -0,0 +1,17 @@ +namespace ImageSharp +{ + using System; + using System.Runtime.CompilerServices; + + /// + /// Helps removing exception throwing code from hot path by providing non-inlined exception thrower methods. + /// + internal static class ThrowHelper + { + [MethodImpl(MethodImplOptions.NoInlining)] + public static void ThrowArgumentNullException(string paramName) + { + throw new ArgumentNullException(nameof(paramName)); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Common/Memory/ArrayPointer{T}.cs b/src/ImageSharp/Common/Memory/ArrayPointer{T}.cs new file mode 100644 index 0000000000..c3fc32234a --- /dev/null +++ b/src/ImageSharp/Common/Memory/ArrayPointer{T}.cs @@ -0,0 +1,57 @@ +namespace ImageSharp +{ + using System; + using System.Runtime.CompilerServices; + + /// + /// Provides access to elements in an array from a given position. + /// This struct shares many similarities with corefx System.Span<T> but there are differences in functionalities and semantics: + /// - It's not possible to use it with stack objects or pointers to unmanaged memory, only with managed arrays + /// - There is no bounds checking for performance reasons. Therefore we don't need to store length. (However this could be added as DEBUG-only feature.) + /// - Currently the arrays provided to ArrayPointer need to be pinned. This behaviour could be changed using C#7 features. + /// + internal unsafe struct ArrayPointer + where T : struct + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ArrayPointer(T[] array, void* pointerToArray, int offset) + { + // TODO: Use Guard.NotNull() here after optimizing it with ThrowHelper! + if (array == null) + { + ThrowHelper.ThrowArgumentNullException(nameof(array)); + } + this.Array = array; + this.Offset = offset; + this.PointerAtOffset = (IntPtr)pointerToArray + Unsafe.SizeOf()*offset; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ArrayPointer(T[] array, void* pointerToArray) + { + // TODO: Use Guard.NotNull() here after optimizing it with ThrowHelper! + if (array == null) + { + ThrowHelper.ThrowArgumentNullException(nameof(array)); + } + this.Array = array; + this.Offset = 0; + this.PointerAtOffset = (IntPtr)pointerToArray; + } + + public T[] Array { get; private set; } + + public int Offset { get; private set; } + + public IntPtr PointerAtOffset { get; private set; } + + public ArrayPointer Slice(int offset) + { + ArrayPointer result = default(ArrayPointer); + result.Array = this.Array; + result.Offset = this.Offset + offset; + result.PointerAtOffset = this.PointerAtOffset + Unsafe.SizeOf() * offset; + return result; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Common/Helpers/Fast2DArray{T}.cs b/src/ImageSharp/Common/Memory/Fast2DArray{T}.cs similarity index 98% rename from src/ImageSharp/Common/Helpers/Fast2DArray{T}.cs rename to src/ImageSharp/Common/Memory/Fast2DArray{T}.cs index 26ec816ce3..88a9797572 100644 --- a/src/ImageSharp/Common/Helpers/Fast2DArray{T}.cs +++ b/src/ImageSharp/Common/Memory/Fast2DArray{T}.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // diff --git a/tests/ImageSharp.Tests/Common/ArrayPointerTests.cs b/tests/ImageSharp.Tests/Common/ArrayPointerTests.cs new file mode 100644 index 0000000000..1d229f86ab --- /dev/null +++ b/tests/ImageSharp.Tests/Common/ArrayPointerTests.cs @@ -0,0 +1,97 @@ +// ReSharper disable ObjectCreationAsStatement +// ReSharper disable InconsistentNaming +namespace ImageSharp.Tests.Common +{ + using System; + + using Xunit; + + public unsafe class ArrayPointerTests + { + public struct Foo + { + private int a; + + private double b; + + internal static Foo[] CreateArray(int size) + { + Foo[] result = new Foo[size]; + for (int i = 0; i < size; i++) + { + result[i] = new Foo() { a = i, b = i }; + } + return result; + } + } + + [Fact] + public void ConstructWithNullArray_Throws() + { + Assert.Throws( + () => + { + new ArrayPointer(null, (void*)0); + }); + + Assert.Throws( + () => + { + new ArrayPointer(null, (void*)0); + }); + } + + [Fact] + public void ConstructWithoutOffset() + { + Foo[] array = Foo.CreateArray(3); + fixed (Foo* p = array) + { + // Act: + ArrayPointer ap = new ArrayPointer(array, p); + + // Assert: + Assert.Equal(array, ap.Array); + Assert.Equal((IntPtr)p, ap.PointerAtOffset); + } + } + + [Fact] + public void ConstructWithOffset() + { + Foo[] array = Foo.CreateArray(3); + int offset = 2; + fixed (Foo* p = array) + { + // Act: + ArrayPointer ap = new ArrayPointer(array, p, offset); + + // Assert: + Assert.Equal(array, ap.Array); + Assert.Equal(offset, ap.Offset); + Assert.Equal((IntPtr)(p+offset), ap.PointerAtOffset); + } + } + + [Fact] + public void Slice() + { + Foo[] array = Foo.CreateArray(5); + int offset0 = 2; + int offset1 = 2; + int totalOffset = offset0 + offset1; + fixed (Foo* p = array) + { + ArrayPointer ap = new ArrayPointer(array, p, offset0); + + // Act: + ap = ap.Slice(offset1); + + // Assert: + Assert.Equal(array, ap.Array); + Assert.Equal(totalOffset, ap.Offset); + Assert.Equal((IntPtr)(p + totalOffset), ap.PointerAtOffset); + } + } + } +} \ No newline at end of file From 7c4d05c06e802aadbb392d3036ae873fbb0e453a Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 18 Feb 2017 15:09:16 +0100 Subject: [PATCH 077/142] IPixel --- src/ImageSharp/Colors/PackedPixel/IPixel.cs | 68 +++++++++++++++++++ .../Common/Memory/ArrayPointer{T}.cs | 4 +- 2 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 src/ImageSharp/Colors/PackedPixel/IPixel.cs diff --git a/src/ImageSharp/Colors/PackedPixel/IPixel.cs b/src/ImageSharp/Colors/PackedPixel/IPixel.cs new file mode 100644 index 0000000000..a222edd9ac --- /dev/null +++ b/src/ImageSharp/Colors/PackedPixel/IPixel.cs @@ -0,0 +1,68 @@ +namespace ImageSharp +{ + using System; + using System.Numerics; + + public interface IPixel + { + /// + /// Sets the packed representation from a . + /// + /// The vector to create the packed representation from. + void PackFromVector4(Vector4 vector); + + /// + /// Expands the packed representation into a . + /// The vector components are typically expanded in least to greatest significance order. + /// + /// The . + Vector4 ToVector4(); + + /// + /// Sets the packed representation from the given byte array. + /// + /// The x-component. + /// The y-component. + /// The z-component. + /// The w-component. + void PackFromBytes(byte x, byte y, byte z, byte w); + + /// + /// Expands the packed representation into a given byte array. + /// Output is expanded to X-> Y-> Z order. Equivalent to R-> G-> B in + /// + /// The bytes to set the color in. + /// The starting index of the . + void ToXyzBytes(byte[] bytes, int startIndex); + + /// + /// Expands the packed representation into a given byte array. + /// Output is expanded to X-> Y-> Z-> W order. Equivalent to R-> G-> B-> A in + /// + /// The bytes to set the color in. + /// The starting index of the . + void ToXyzwBytes(byte[] bytes, int startIndex); + + /// + /// Expands the packed representation into a given byte array. + /// Output is expanded to Z-> Y-> X order. Equivalent to B-> G-> R in + /// + /// The bytes to set the color in. + /// The starting index of the . + void ToZyxBytes(byte[] bytes, int startIndex); + + /// + /// Expands the packed representation into a given byte array. + /// Output is expanded to Z-> Y-> X-> W order. Equivalent to B-> G-> R-> A in + /// + /// The bytes to set the color in. + /// The starting index of the . + void ToZyxwBytes(byte[] bytes, int startIndex); + } + + public interface IPixel : IPixel, IEquatable + where TSelf : struct, IPixel + { + + } +} \ No newline at end of file diff --git a/src/ImageSharp/Common/Memory/ArrayPointer{T}.cs b/src/ImageSharp/Common/Memory/ArrayPointer{T}.cs index c3fc32234a..06de061078 100644 --- a/src/ImageSharp/Common/Memory/ArrayPointer{T}.cs +++ b/src/ImageSharp/Common/Memory/ArrayPointer{T}.cs @@ -16,7 +16,7 @@ namespace ImageSharp [MethodImpl(MethodImplOptions.AggressiveInlining)] public ArrayPointer(T[] array, void* pointerToArray, int offset) { - // TODO: Use Guard.NotNull() here after optimizing it with ThrowHelper! + // TODO: Use Guard.NotNull() here after optimizing it by eliminating the default argument case and applying ThrowHelper! if (array == null) { ThrowHelper.ThrowArgumentNullException(nameof(array)); @@ -29,7 +29,7 @@ namespace ImageSharp [MethodImpl(MethodImplOptions.AggressiveInlining)] public ArrayPointer(T[] array, void* pointerToArray) { - // TODO: Use Guard.NotNull() here after optimizing it with ThrowHelper! + // TODO: Use Guard.NotNull() here after optimizing it by eliminating the default argument case and applying ThrowHelper! if (array == null) { ThrowHelper.ThrowArgumentNullException(nameof(array)); From be4c18953319cbd87e72908b9b3b2c89893a4d46 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 18 Feb 2017 17:27:57 +0100 Subject: [PATCH 078/142] IPixel --- global.json | 2 +- src/ImageSharp.Drawing.Paths/DrawBeziers.cs | 12 ++-- src/ImageSharp.Drawing.Paths/DrawLines.cs | 12 ++-- src/ImageSharp.Drawing.Paths/DrawPath.cs | 12 ++-- src/ImageSharp.Drawing.Paths/DrawPolygon.cs | 12 ++-- src/ImageSharp.Drawing.Paths/DrawRectangle.cs | 12 ++-- src/ImageSharp.Drawing.Paths/FillPaths.cs | 8 +-- src/ImageSharp.Drawing.Paths/FillPolygon.cs | 8 +-- src/ImageSharp.Drawing.Paths/FillRectangle.cs | 8 +-- .../Brushes/Brushes{TColor}.cs | 2 +- src/ImageSharp.Drawing/Brushes/IBrush.cs | 2 +- .../Brushes/ImageBrush{TColor}.cs | 2 +- .../Brushes/PatternBrush{TColor}.cs | 2 +- .../Brushes/Processors/BrushApplicator.cs | 2 +- .../Brushes/RecolorBrush{TColor}.cs | 2 +- .../Brushes/SolidBrush{TColor}.cs | 2 +- src/ImageSharp.Drawing/DrawImage.cs | 4 +- src/ImageSharp.Drawing/DrawPath.cs | 12 ++-- src/ImageSharp.Drawing/FillRegion.cs | 12 ++-- src/ImageSharp.Drawing/Pens/IPen.cs | 2 +- src/ImageSharp.Drawing/Pens/Pens{TColor}.cs | 2 +- src/ImageSharp.Drawing/Pens/Pen{TColor}.cs | 2 +- .../Pens/Processors/ColoredPointInfo.cs | 2 +- .../Pens/Processors/PenApplicator.cs | 2 +- .../Processors/DrawImageProcessor.cs | 2 +- .../Processors/DrawPathProcessor.cs | 2 +- .../Processors/FillProcessor.cs | 2 +- .../Processors/FillRegionProcessor.cs | 2 +- src/ImageSharp.Formats.Bmp/BmpDecoder.cs | 2 +- src/ImageSharp.Formats.Bmp/BmpDecoderCore.cs | 10 ++-- src/ImageSharp.Formats.Bmp/BmpEncoder.cs | 2 +- src/ImageSharp.Formats.Bmp/BmpEncoderCore.cs | 8 +-- src/ImageSharp.Formats.Bmp/ImageExtensions.cs | 2 +- src/ImageSharp.Formats.Gif/GifDecoder.cs | 2 +- src/ImageSharp.Formats.Gif/GifDecoderCore.cs | 2 +- src/ImageSharp.Formats.Gif/GifEncoder.cs | 2 +- src/ImageSharp.Formats.Gif/GifEncoderCore.cs | 18 +++--- src/ImageSharp.Formats.Gif/ImageExtensions.cs | 2 +- .../ImageExtensions.cs | 2 +- src/ImageSharp.Formats.Jpeg/JpegDecoder.cs | 2 +- .../JpegDecoderCore.cs | 28 +++++----- src/ImageSharp.Formats.Jpeg/JpegEncoder.cs | 2 +- .../JpegEncoderCore.cs | 12 ++-- .../Utils/JpegUtils.cs | 6 +- src/ImageSharp.Formats.Png/ImageExtensions.cs | 2 +- src/ImageSharp.Formats.Png/PngDecoder.cs | 2 +- src/ImageSharp.Formats.Png/PngDecoderCore.cs | 16 +++--- src/ImageSharp.Formats.Png/PngEncoder.cs | 2 +- src/ImageSharp.Formats.Png/PngEncoderCore.cs | 16 +++--- .../Binarization/BinaryThreshold.cs | 4 +- .../Binarization/Dither.cs | 8 +-- .../ColorMatrix/BlackWhite.cs | 4 +- .../ColorMatrix/ColorBlindness.cs | 4 +- .../ColorMatrix/Grayscale.cs | 4 +- src/ImageSharp.Processing/ColorMatrix/Hue.cs | 4 +- .../ColorMatrix/Kodachrome.cs | 4 +- .../ColorMatrix/Lomograph.cs | 4 +- .../ColorMatrix/Polaroid.cs | 4 +- .../ColorMatrix/Saturation.cs | 4 +- .../ColorMatrix/Sepia.cs | 4 +- .../Convolution/BoxBlur.cs | 4 +- .../Convolution/DetectEdges.cs | 12 ++-- .../Convolution/GaussianBlur.cs | 4 +- .../Convolution/GaussianSharpen.cs | 4 +- src/ImageSharp.Processing/Effects/Alpha.cs | 4 +- .../Effects/BackgroundColor.cs | 2 +- .../Effects/Brightness.cs | 4 +- src/ImageSharp.Processing/Effects/Contrast.cs | 4 +- src/ImageSharp.Processing/Effects/Invert.cs | 4 +- .../Effects/OilPainting.cs | 4 +- src/ImageSharp.Processing/Effects/Pixelate.cs | 4 +- src/ImageSharp.Processing/Overlays/Glow.cs | 10 ++-- .../Overlays/Vignette.cs | 10 ++-- .../Binarization/BinaryThresholdProcessor.cs | 2 +- .../ErrorDiffusionDitherProcessor.cs | 2 +- .../Binarization/OrderedDitherProcessor.cs | 2 +- .../ColorMatrix/BlackWhiteProcessor.cs | 2 +- .../ColorBlindness/AchromatomalyProcessor.cs | 2 +- .../ColorBlindness/AchromatopsiaProcessor.cs | 2 +- .../ColorBlindness/DeuteranomalyProcessor.cs | 2 +- .../ColorBlindness/DeuteranopiaProcessor.cs | 2 +- .../ColorBlindness/ProtanomalyProcessor.cs | 2 +- .../ColorBlindness/ProtanopiaProcessor.cs | 2 +- .../ColorBlindness/TritanomalyProcessor.cs | 2 +- .../ColorBlindness/TritanopiaProcessor.cs | 2 +- .../ColorMatrix/ColorMatrixFilter.cs | 2 +- .../ColorMatrix/GrayscaleBt601Processor.cs | 2 +- .../ColorMatrix/GrayscaleBt709Processor.cs | 2 +- .../Processors/ColorMatrix/HueProcessor.cs | 2 +- .../ColorMatrix/IColorMatrixFilter.cs | 2 +- .../ColorMatrix/KodachromeProcessor.cs | 2 +- .../ColorMatrix/LomographProcessor.cs | 2 +- .../ColorMatrix/PolaroidProcessor.cs | 2 +- .../ColorMatrix/SaturationProcessor.cs | 2 +- .../Processors/ColorMatrix/SepiaProcessor.cs | 2 +- .../Convolution/BoxBlurProcessor.cs | 2 +- .../Convolution/Convolution2DProcessor.cs | 2 +- .../Convolution/Convolution2PassProcessor.cs | 2 +- .../Convolution/ConvolutionProcessor.cs | 2 +- .../EdgeDetection/EdgeDetector2DProcessor.cs | 2 +- .../EdgeDetectorCompassProcessor.cs | 2 +- .../EdgeDetection/EdgeDetectorProcessor.cs | 2 +- .../EdgeDetection/IEdgeDetectorProcessor.cs | 2 +- .../EdgeDetection/KayyaliProcessor.cs | 2 +- .../EdgeDetection/KirschProcessor.cs | 2 +- .../EdgeDetection/Laplacian3X3Processor.cs | 2 +- .../EdgeDetection/Laplacian5X5Processor.cs | 2 +- .../LaplacianOfGaussianProcessor.cs | 2 +- .../EdgeDetection/PrewittProcessor.cs | 2 +- .../EdgeDetection/RobertsCrossProcessor.cs | 2 +- .../EdgeDetection/RobinsonProcessor.cs | 2 +- .../EdgeDetection/ScharrProcessor.cs | 2 +- .../EdgeDetection/SobelProcessor.cs | 2 +- .../Convolution/GaussianBlurProcessor.cs | 2 +- .../Convolution/GaussianSharpenProcessor.cs | 2 +- .../Processors/Effects/AlphaProcessor.cs | 2 +- .../Effects/BackgroundColorProcessor.cs | 2 +- .../Processors/Effects/BrightnessProcessor.cs | 2 +- .../Processors/Effects/ContrastProcessor.cs | 2 +- .../Processors/Effects/InvertProcessor.cs | 2 +- .../Effects/OilPaintingProcessor.cs | 2 +- .../Processors/Effects/PixelateProcessor.cs | 2 +- .../Processors/Overlays/GlowProcessor.cs | 2 +- .../Processors/Overlays/VignetteProcessor.cs | 2 +- .../Transforms/CompandingResizeProcessor.cs | 2 +- .../Processors/Transforms/CropProcessor.cs | 2 +- .../Transforms/EntropyCropProcessor.cs | 2 +- .../Processors/Transforms/FlipProcessor.cs | 2 +- .../Transforms/Matrix3x2Processor.cs | 2 +- .../Transforms/ResamplingWeightedProcessor.cs | 2 +- .../Processors/Transforms/ResizeProcessor.cs | 2 +- .../Processors/Transforms/RotateProcessor.cs | 2 +- .../Processors/Transforms/SkewProcessor.cs | 2 +- .../Transforms/AutoOrient.cs | 4 +- src/ImageSharp.Processing/Transforms/Crop.cs | 4 +- .../Transforms/EntropyCrop.cs | 2 +- src/ImageSharp.Processing/Transforms/Flip.cs | 2 +- .../Transforms/Options/ResizeHelper.cs | 12 ++-- src/ImageSharp.Processing/Transforms/Pad.cs | 2 +- .../Transforms/Resize.cs | 14 ++--- .../Transforms/Rotate.cs | 6 +- .../Transforms/RotateFlip.cs | 2 +- src/ImageSharp.Processing/Transforms/Skew.cs | 4 +- src/ImageSharp/Colors/Color.cs | 6 +- src/ImageSharp/Colors/ColorBuilder{TColor}.cs | 2 +- src/ImageSharp/Colors/NamedColors{TColor}.cs | 2 +- src/ImageSharp/Colors/PackedPixel/Alpha8.cs | 6 +- src/ImageSharp/Colors/PackedPixel/Argb.cs | 6 +- src/ImageSharp/Colors/PackedPixel/Bgr565.cs | 6 +- src/ImageSharp/Colors/PackedPixel/Bgra4444.cs | 6 +- src/ImageSharp/Colors/PackedPixel/Bgra5551.cs | 6 +- src/ImageSharp/Colors/PackedPixel/Byte4.cs | 6 +- .../Colors/PackedPixel/HalfSingle.cs | 6 +- .../Colors/PackedPixel/HalfVector2.cs | 6 +- .../Colors/PackedPixel/HalfVector4.cs | 6 +- .../Colors/PackedPixel/IPackedBytes.cs | 55 ------------------- .../Colors/PackedPixel/IPackedPixel.cs | 25 --------- .../Colors/PackedPixel/IPackedVector.cs | 43 --------------- src/ImageSharp/Colors/PackedPixel/IPixel.cs | 25 ++++++--- .../Colors/PackedPixel/NormalizedByte2.cs | 6 +- .../Colors/PackedPixel/NormalizedByte4.cs | 6 +- .../Colors/PackedPixel/NormalizedShort2.cs | 6 +- .../Colors/PackedPixel/NormalizedShort4.cs | 6 +- src/ImageSharp/Colors/PackedPixel/Rg32.cs | 6 +- .../Colors/PackedPixel/Rgba1010102.cs | 6 +- src/ImageSharp/Colors/PackedPixel/Rgba64.cs | 6 +- src/ImageSharp/Colors/PackedPixel/Short2.cs | 6 +- src/ImageSharp/Colors/PackedPixel/Short4.cs | 6 +- .../Common/Extensions/ArrayExtensions.cs | 2 +- src/ImageSharp/Common/Helpers/ImageMaths.cs | 2 +- src/ImageSharp/Common/Helpers/ThrowHelper.cs | 9 +++ .../Common/Memory/ArrayPointer{T}.cs | 44 +++++++++++++-- .../Dithering/ErrorDiffusion/ErrorDiffuser.cs | 2 +- .../ErrorDiffusion/IErrorDiffuser.cs | 2 +- src/ImageSharp/Dithering/Ordered/Bayer.cs | 2 +- .../Dithering/Ordered/IOrderedDither.cs | 2 +- src/ImageSharp/Dithering/Ordered/Ordered.cs | 2 +- src/ImageSharp/Formats/IImageDecoder.cs | 2 +- src/ImageSharp/Formats/IImageEncoder.cs | 2 +- src/ImageSharp/Image/IImageBase{TColor}.cs | 2 +- src/ImageSharp/Image/IImageProcessor.cs | 2 +- src/ImageSharp/Image/ImageBase{TColor}.cs | 2 +- src/ImageSharp/Image/ImageFrame{TColor}.cs | 4 +- .../Image/ImageProcessingExtensions.cs | 2 +- src/ImageSharp/Image/Image{TColor}.cs | 4 +- src/ImageSharp/Image/PixelAccessor{TColor}.cs | 2 +- src/ImageSharp/Image/PixelArea{TColor}.cs | 2 +- src/ImageSharp/Image/PixelPool{TColor}.cs | 2 +- src/ImageSharp/ImageProcessor.cs | 2 +- .../MetaData/Profiles/Exif/ExifProfile.cs | 2 +- src/ImageSharp/Quantizers/IQuantizer.cs | 4 +- .../Quantizers/Octree/OctreeQuantizer.cs | 2 +- src/ImageSharp/Quantizers/Octree/Quantizer.cs | 2 +- .../Quantizers/Palette/PaletteQuantizer.cs | 2 +- src/ImageSharp/Quantizers/Quantize.cs | 4 +- src/ImageSharp/Quantizers/QuantizedImage.cs | 2 +- src/ImageSharp/Quantizers/Wu/WuQuantizer.cs | 2 +- .../Colors/ColorConstructorTests.cs | 2 +- .../Colors/ColorPackingTests.cs | 2 +- .../Formats/Jpg/BadEofJpegTests.cs | 4 +- .../Formats/Jpg/JpegDecoderTests.cs | 8 +-- .../Formats/Jpg/JpegEncoderTests.cs | 4 +- .../Formats/Jpg/JpegUtilsTests.cs | 6 +- .../Image/PixelAccessorTests.cs | 16 +++--- .../TestUtilities/Factories/GenericFactory.cs | 2 +- .../ImageProviders/BlankProvider.cs | 2 +- .../ImageProviders/FileProvider.cs | 2 +- .../ImageProviders/LambdaProvider.cs | 2 +- .../ImageProviders/SolidProvider.cs | 2 +- .../ImageProviders/TestImageProvider.cs | 2 +- .../TestUtilities/ImagingTestCaseUtility.cs | 2 +- .../TestUtilities/TestUtilityExtensions.cs | 2 +- .../Tests/TestImageProviderTests.cs | 22 ++++---- .../Tests/TestUtilityExtensionsTests.cs | 6 +- 214 files changed, 507 insertions(+), 536 deletions(-) delete mode 100644 src/ImageSharp/Colors/PackedPixel/IPackedBytes.cs delete mode 100644 src/ImageSharp/Colors/PackedPixel/IPackedPixel.cs delete mode 100644 src/ImageSharp/Colors/PackedPixel/IPackedVector.cs diff --git a/global.json b/global.json index 7346bdc280..6da79b6116 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "projects": [ "src" ], "sdk": { - "version": "1.0.0-preview2-003121" + "version": "1.0.0-preview2-003131" } } \ No newline at end of file diff --git a/src/ImageSharp.Drawing.Paths/DrawBeziers.cs b/src/ImageSharp.Drawing.Paths/DrawBeziers.cs index ef11a3bac5..936d5a9ce5 100644 --- a/src/ImageSharp.Drawing.Paths/DrawBeziers.cs +++ b/src/ImageSharp.Drawing.Paths/DrawBeziers.cs @@ -29,7 +29,7 @@ namespace ImageSharp /// The options. /// The . public static Image DrawBeziers(this Image source, IBrush brush, float thickness, Vector2[] points, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return source.Draw(new Pen(brush, thickness), new Path(new BezierLineSegment(points)), options); } @@ -44,7 +44,7 @@ namespace ImageSharp /// The points. /// The . public static Image DrawBeziers(this Image source, IBrush brush, float thickness, Vector2[] points) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return source.Draw(new Pen(brush, thickness), new Path(new BezierLineSegment(points))); } @@ -59,7 +59,7 @@ namespace ImageSharp /// The points. /// The . public static Image DrawBeziers(this Image source, TColor color, float thickness, Vector2[] points) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return source.DrawBeziers(new SolidBrush(color), thickness, points); } @@ -75,7 +75,7 @@ namespace ImageSharp /// The options. /// The . public static Image DrawBeziers(this Image source, TColor color, float thickness, Vector2[] points, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return source.DrawBeziers(new SolidBrush(color), thickness, points, options); } @@ -90,7 +90,7 @@ namespace ImageSharp /// The options. /// The . public static Image DrawBeziers(this Image source, IPen pen, Vector2[] points, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return source.Draw(pen, new Path(new BezierLineSegment(points)), options); } @@ -104,7 +104,7 @@ namespace ImageSharp /// The points. /// The . public static Image DrawBeziers(this Image source, IPen pen, Vector2[] points) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return source.Draw(pen, new Path(new BezierLineSegment(points))); } diff --git a/src/ImageSharp.Drawing.Paths/DrawLines.cs b/src/ImageSharp.Drawing.Paths/DrawLines.cs index fc046d6276..42f4406e83 100644 --- a/src/ImageSharp.Drawing.Paths/DrawLines.cs +++ b/src/ImageSharp.Drawing.Paths/DrawLines.cs @@ -29,7 +29,7 @@ namespace ImageSharp /// The options. /// The . public static Image DrawLines(this Image source, IBrush brush, float thickness, Vector2[] points, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return source.Draw(new Pen(brush, thickness), new Path(new LinearLineSegment(points)), options); } @@ -44,7 +44,7 @@ namespace ImageSharp /// The points. /// The . public static Image DrawLines(this Image source, IBrush brush, float thickness, Vector2[] points) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return source.Draw(new Pen(brush, thickness), new Path(new LinearLineSegment(points))); } @@ -59,7 +59,7 @@ namespace ImageSharp /// The points. /// The . public static Image DrawLines(this Image source, TColor color, float thickness, Vector2[] points) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return source.DrawLines(new SolidBrush(color), thickness, points); } @@ -75,7 +75,7 @@ namespace ImageSharp /// The options. /// The .> public static Image DrawLines(this Image source, TColor color, float thickness, Vector2[] points, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return source.DrawLines(new SolidBrush(color), thickness, points, options); } @@ -90,7 +90,7 @@ namespace ImageSharp /// The options. /// The . public static Image DrawLines(this Image source, IPen pen, Vector2[] points, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return source.Draw(pen, new Path(new LinearLineSegment(points)), options); } @@ -104,7 +104,7 @@ namespace ImageSharp /// The points. /// The . public static Image DrawLines(this Image source, IPen pen, Vector2[] points) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return source.Draw(pen, new Path(new LinearLineSegment(points))); } diff --git a/src/ImageSharp.Drawing.Paths/DrawPath.cs b/src/ImageSharp.Drawing.Paths/DrawPath.cs index 29b389fa24..e2c1442de8 100644 --- a/src/ImageSharp.Drawing.Paths/DrawPath.cs +++ b/src/ImageSharp.Drawing.Paths/DrawPath.cs @@ -28,7 +28,7 @@ namespace ImageSharp /// The options. /// The . public static Image Draw(this Image source, IPen pen, IPath path, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return source.Draw(pen, new ShapePath(path), options); } @@ -42,7 +42,7 @@ namespace ImageSharp /// The path. /// The . public static Image Draw(this Image source, IPen pen, IPath path) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return source.Draw(pen, path, GraphicsOptions.Default); } @@ -58,7 +58,7 @@ namespace ImageSharp /// The options. /// The . public static Image Draw(this Image source, IBrush brush, float thickness, IPath path, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return source.Draw(new Pen(brush, thickness), path, options); } @@ -73,7 +73,7 @@ namespace ImageSharp /// The path. /// The . public static Image Draw(this Image source, IBrush brush, float thickness, IPath path) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return source.Draw(new Pen(brush, thickness), path); } @@ -89,7 +89,7 @@ namespace ImageSharp /// The options. /// The . public static Image Draw(this Image source, TColor color, float thickness, IPath path, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return source.Draw(new SolidBrush(color), thickness, path, options); } @@ -104,7 +104,7 @@ namespace ImageSharp /// The path. /// The . public static Image Draw(this Image source, TColor color, float thickness, IPath path) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return source.Draw(new SolidBrush(color), thickness, path); } diff --git a/src/ImageSharp.Drawing.Paths/DrawPolygon.cs b/src/ImageSharp.Drawing.Paths/DrawPolygon.cs index 6b3a6f1f36..8043d18e56 100644 --- a/src/ImageSharp.Drawing.Paths/DrawPolygon.cs +++ b/src/ImageSharp.Drawing.Paths/DrawPolygon.cs @@ -29,7 +29,7 @@ namespace ImageSharp /// The options. /// The . public static Image DrawPolygon(this Image source, IBrush brush, float thickness, Vector2[] points, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return source.Draw(new Pen(brush, thickness), new Polygon(new LinearLineSegment(points)), options); } @@ -44,7 +44,7 @@ namespace ImageSharp /// The points. /// The . public static Image DrawPolygon(this Image source, IBrush brush, float thickness, Vector2[] points) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return source.Draw(new Pen(brush, thickness), new Polygon(new LinearLineSegment(points))); } @@ -59,7 +59,7 @@ namespace ImageSharp /// The points. /// The . public static Image DrawPolygon(this Image source, TColor color, float thickness, Vector2[] points) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return source.DrawPolygon(new SolidBrush(color), thickness, points); } @@ -75,7 +75,7 @@ namespace ImageSharp /// The options. /// The . public static Image DrawPolygon(this Image source, TColor color, float thickness, Vector2[] points, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return source.DrawPolygon(new SolidBrush(color), thickness, points, options); } @@ -89,7 +89,7 @@ namespace ImageSharp /// The points. /// The . public static Image DrawPolygon(this Image source, IPen pen, Vector2[] points) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return source.Draw(pen, new Polygon(new LinearLineSegment(points)), GraphicsOptions.Default); } @@ -104,7 +104,7 @@ namespace ImageSharp /// The options. /// The . public static Image DrawPolygon(this Image source, IPen pen, Vector2[] points, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return source.Draw(pen, new Polygon(new LinearLineSegment(points)), options); } diff --git a/src/ImageSharp.Drawing.Paths/DrawRectangle.cs b/src/ImageSharp.Drawing.Paths/DrawRectangle.cs index d42fee5dc1..b356652409 100644 --- a/src/ImageSharp.Drawing.Paths/DrawRectangle.cs +++ b/src/ImageSharp.Drawing.Paths/DrawRectangle.cs @@ -26,7 +26,7 @@ namespace ImageSharp /// The options. /// The . public static Image Draw(this Image source, IPen pen, Rectangle shape, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return source.Draw(pen, new SixLabors.Shapes.Rectangle(shape.X, shape.Y, shape.Width, shape.Height), options); } @@ -40,7 +40,7 @@ namespace ImageSharp /// The shape. /// The . public static Image Draw(this Image source, IPen pen, Rectangle shape) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return source.Draw(pen, shape, GraphicsOptions.Default); } @@ -56,7 +56,7 @@ namespace ImageSharp /// The options. /// The . public static Image Draw(this Image source, IBrush brush, float thickness, Rectangle shape, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return source.Draw(new Pen(brush, thickness), shape, options); } @@ -71,7 +71,7 @@ namespace ImageSharp /// The shape. /// The . public static Image Draw(this Image source, IBrush brush, float thickness, Rectangle shape) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return source.Draw(new Pen(brush, thickness), shape); } @@ -87,7 +87,7 @@ namespace ImageSharp /// The options. /// The . public static Image Draw(this Image source, TColor color, float thickness, Rectangle shape, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return source.Draw(new SolidBrush(color), thickness, shape, options); } @@ -102,7 +102,7 @@ namespace ImageSharp /// The shape. /// The . public static Image Draw(this Image source, TColor color, float thickness, Rectangle shape) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return source.Draw(new SolidBrush(color), thickness, shape); } diff --git a/src/ImageSharp.Drawing.Paths/FillPaths.cs b/src/ImageSharp.Drawing.Paths/FillPaths.cs index 1dd0b0a3a8..92e227ce1f 100644 --- a/src/ImageSharp.Drawing.Paths/FillPaths.cs +++ b/src/ImageSharp.Drawing.Paths/FillPaths.cs @@ -27,7 +27,7 @@ namespace ImageSharp /// The graphics options. /// The . public static Image Fill(this Image source, IBrush brush, IPath path, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return source.Fill(brush, new ShapeRegion(path), options); } @@ -41,7 +41,7 @@ namespace ImageSharp /// The path. /// The . public static Image Fill(this Image source, IBrush brush, IPath path) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return source.Fill(brush, new ShapeRegion(path), GraphicsOptions.Default); } @@ -56,7 +56,7 @@ namespace ImageSharp /// The options. /// The . public static Image Fill(this Image source, TColor color, IPath path, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return source.Fill(new SolidBrush(color), path, options); } @@ -70,7 +70,7 @@ namespace ImageSharp /// The path. /// The . public static Image Fill(this Image source, TColor color, IPath path) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return source.Fill(new SolidBrush(color), path); } diff --git a/src/ImageSharp.Drawing.Paths/FillPolygon.cs b/src/ImageSharp.Drawing.Paths/FillPolygon.cs index b41267b9e2..cd3d154666 100644 --- a/src/ImageSharp.Drawing.Paths/FillPolygon.cs +++ b/src/ImageSharp.Drawing.Paths/FillPolygon.cs @@ -27,7 +27,7 @@ namespace ImageSharp /// The options. /// The . public static Image FillPolygon(this Image source, IBrush brush, Vector2[] points, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return source.Fill(brush, new Polygon(new LinearLineSegment(points)), options); } @@ -41,7 +41,7 @@ namespace ImageSharp /// The points. /// The . public static Image FillPolygon(this Image source, IBrush brush, Vector2[] points) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return source.Fill(brush, new Polygon(new LinearLineSegment(points))); } @@ -56,7 +56,7 @@ namespace ImageSharp /// The options. /// The . public static Image FillPolygon(this Image source, TColor color, Vector2[] points, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return source.Fill(new SolidBrush(color), new Polygon(new LinearLineSegment(points)), options); } @@ -70,7 +70,7 @@ namespace ImageSharp /// The points. /// The . public static Image FillPolygon(this Image source, TColor color, Vector2[] points) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return source.Fill(new SolidBrush(color), new Polygon(new LinearLineSegment(points))); } diff --git a/src/ImageSharp.Drawing.Paths/FillRectangle.cs b/src/ImageSharp.Drawing.Paths/FillRectangle.cs index 3b2cef6131..1928e54d3c 100644 --- a/src/ImageSharp.Drawing.Paths/FillRectangle.cs +++ b/src/ImageSharp.Drawing.Paths/FillRectangle.cs @@ -25,7 +25,7 @@ namespace ImageSharp /// The options. /// The . public static Image Fill(this Image source, IBrush brush, Rectangle shape, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return source.Fill(brush, new SixLabors.Shapes.Rectangle(shape.X, shape.Y, shape.Width, shape.Height), options); } @@ -39,7 +39,7 @@ namespace ImageSharp /// The shape. /// The . public static Image Fill(this Image source, IBrush brush, Rectangle shape) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return source.Fill(brush, new SixLabors.Shapes.Rectangle(shape.X, shape.Y, shape.Width, shape.Height)); } @@ -54,7 +54,7 @@ namespace ImageSharp /// The options. /// The . public static Image Fill(this Image source, TColor color, Rectangle shape, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return source.Fill(new SolidBrush(color), shape, options); } @@ -68,7 +68,7 @@ namespace ImageSharp /// The shape. /// The . public static Image Fill(this Image source, TColor color, Rectangle shape) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return source.Fill(new SolidBrush(color), shape); } diff --git a/src/ImageSharp.Drawing/Brushes/Brushes{TColor}.cs b/src/ImageSharp.Drawing/Brushes/Brushes{TColor}.cs index a5cf02b8a9..d77a6d5941 100644 --- a/src/ImageSharp.Drawing/Brushes/Brushes{TColor}.cs +++ b/src/ImageSharp.Drawing/Brushes/Brushes{TColor}.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Drawing.Brushes /// The pixel format. /// A Brush public class Brushes - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Percent10 Hatch Pattern diff --git a/src/ImageSharp.Drawing/Brushes/IBrush.cs b/src/ImageSharp.Drawing/Brushes/IBrush.cs index b281802048..df05fa23e1 100644 --- a/src/ImageSharp.Drawing/Brushes/IBrush.cs +++ b/src/ImageSharp.Drawing/Brushes/IBrush.cs @@ -18,7 +18,7 @@ namespace ImageSharp.Drawing /// logic for converting a pixel location to a . /// public interface IBrush - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Creates the applicator for this brush. diff --git a/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs index b9bcc2f9a0..2707c00642 100644 --- a/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs +++ b/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs @@ -15,7 +15,7 @@ namespace ImageSharp.Drawing.Brushes /// /// The pixel format. public class ImageBrush : IBrush - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// The image to paint. diff --git a/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs index 3fea53052d..741ab3f005 100644 --- a/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs +++ b/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs @@ -42,7 +42,7 @@ namespace ImageSharp.Drawing.Brushes /// /// The pixel format. public class PatternBrush : IBrush - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// The pattern. diff --git a/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs b/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs index 37af8cd046..b66827e491 100644 --- a/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs +++ b/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs @@ -14,7 +14,7 @@ namespace ImageSharp.Drawing.Processors /// The pixel format. /// public abstract class BrushApplicator : IDisposable // disposable will be required if/when there is an ImageBrush - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Gets the color for a single pixel. diff --git a/src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs index 33403facb2..542c3cfed6 100644 --- a/src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs +++ b/src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs @@ -15,7 +15,7 @@ namespace ImageSharp.Drawing.Brushes /// /// The pixel format. public class RecolorBrush : IBrush - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs index 018c272b7f..30351dbe1b 100644 --- a/src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs +++ b/src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs @@ -15,7 +15,7 @@ namespace ImageSharp.Drawing.Brushes /// /// The pixel format. public class SolidBrush : IBrush - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// The color to paint. diff --git a/src/ImageSharp.Drawing/DrawImage.cs b/src/ImageSharp.Drawing/DrawImage.cs index 2fba227ee6..16582e7ee5 100644 --- a/src/ImageSharp.Drawing/DrawImage.cs +++ b/src/ImageSharp.Drawing/DrawImage.cs @@ -23,7 +23,7 @@ namespace ImageSharp /// The opacity of the image image to blend. Must be between 0 and 100. /// The . public static Image Blend(this Image source, Image image, int percent = 50) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return DrawImage(source, image, percent, default(Size), default(Point)); } @@ -39,7 +39,7 @@ namespace ImageSharp /// The location to draw the blended image. /// The . public static Image DrawImage(this Image source, Image image, int percent, Size size, Point location) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { if (size == default(Size)) { diff --git a/src/ImageSharp.Drawing/DrawPath.cs b/src/ImageSharp.Drawing/DrawPath.cs index 75a0d81575..e91b972033 100644 --- a/src/ImageSharp.Drawing/DrawPath.cs +++ b/src/ImageSharp.Drawing/DrawPath.cs @@ -27,7 +27,7 @@ namespace ImageSharp /// The options. /// The . public static Image Draw(this Image source, IPen pen, Drawable path, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return source.Apply(new DrawPathProcessor(pen, path, options)); } @@ -41,7 +41,7 @@ namespace ImageSharp /// The path. /// The . public static Image Draw(this Image source, IPen pen, Drawable path) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return source.Draw(pen, path, GraphicsOptions.Default); } @@ -57,7 +57,7 @@ namespace ImageSharp /// The options. /// The . public static Image Draw(this Image source, IBrush brush, float thickness, Drawable path, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return source.Draw(new Pen(brush, thickness), path, options); } @@ -72,7 +72,7 @@ namespace ImageSharp /// The path. /// The . public static Image Draw(this Image source, IBrush brush, float thickness, Drawable path) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return source.Draw(new Pen(brush, thickness), path); } @@ -88,7 +88,7 @@ namespace ImageSharp /// The options. /// The . public static Image Draw(this Image source, TColor color, float thickness, Drawable path, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return source.Draw(new SolidBrush(color), thickness, path, options); } @@ -103,7 +103,7 @@ namespace ImageSharp /// The path. /// The . public static Image Draw(this Image source, TColor color, float thickness, Drawable path) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return source.Draw(new SolidBrush(color), thickness, path); } diff --git a/src/ImageSharp.Drawing/FillRegion.cs b/src/ImageSharp.Drawing/FillRegion.cs index 6faf519af1..8aab202519 100644 --- a/src/ImageSharp.Drawing/FillRegion.cs +++ b/src/ImageSharp.Drawing/FillRegion.cs @@ -24,7 +24,7 @@ namespace ImageSharp /// The details how to fill the region of interest. /// The . public static Image Fill(this Image source, IBrush brush) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return source.Apply(new FillProcessor(brush)); } @@ -37,7 +37,7 @@ namespace ImageSharp /// The color. /// The . public static Image Fill(this Image source, TColor color) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return source.Fill(new SolidBrush(color)); } @@ -52,7 +52,7 @@ namespace ImageSharp /// The graphics options. /// The . public static Image Fill(this Image source, IBrush brush, Region region, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return source.Apply(new FillRegionProcessor(brush, region, options)); } @@ -66,7 +66,7 @@ namespace ImageSharp /// The region. /// The . public static Image Fill(this Image source, IBrush brush, Region region) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return source.Fill(brush, region, GraphicsOptions.Default); } @@ -81,7 +81,7 @@ namespace ImageSharp /// The options. /// The . public static Image Fill(this Image source, TColor color, Region region, GraphicsOptions options) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return source.Fill(new SolidBrush(color), region, options); } @@ -95,7 +95,7 @@ namespace ImageSharp /// The region. /// The . public static Image Fill(this Image source, TColor color, Region region) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return source.Fill(new SolidBrush(color), region); } diff --git a/src/ImageSharp.Drawing/Pens/IPen.cs b/src/ImageSharp.Drawing/Pens/IPen.cs index 0cf473427f..72a5ffc367 100644 --- a/src/ImageSharp.Drawing/Pens/IPen.cs +++ b/src/ImageSharp.Drawing/Pens/IPen.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Drawing.Pens /// /// The type of the color. public interface IPen - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Creates the applicator for applying this pen to an Image diff --git a/src/ImageSharp.Drawing/Pens/Pens{TColor}.cs b/src/ImageSharp.Drawing/Pens/Pens{TColor}.cs index 94a2826595..49eed370df 100644 --- a/src/ImageSharp.Drawing/Pens/Pens{TColor}.cs +++ b/src/ImageSharp.Drawing/Pens/Pens{TColor}.cs @@ -12,7 +12,7 @@ namespace ImageSharp.Drawing.Pens /// /// The type of the color. public class Pens - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { private static readonly float[] DashDotPattern = new[] { 3f, 1f, 1f, 1f }; private static readonly float[] DashDotDotPattern = new[] { 3f, 1f, 1f, 1f, 1f, 1f }; diff --git a/src/ImageSharp.Drawing/Pens/Pen{TColor}.cs b/src/ImageSharp.Drawing/Pens/Pen{TColor}.cs index d0ea55c1e7..79a5d6b15a 100644 --- a/src/ImageSharp.Drawing/Pens/Pen{TColor}.cs +++ b/src/ImageSharp.Drawing/Pens/Pen{TColor}.cs @@ -24,7 +24,7 @@ namespace ImageSharp.Drawing.Pens /// the the pattern will imidiatly repeat without gap. /// public class Pen : IPen - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { private static readonly float[] EmptyPattern = new float[0]; private readonly float[] pattern; diff --git a/src/ImageSharp.Drawing/Pens/Processors/ColoredPointInfo.cs b/src/ImageSharp.Drawing/Pens/Processors/ColoredPointInfo.cs index 494f0f4e4e..d042bdccbb 100644 --- a/src/ImageSharp.Drawing/Pens/Processors/ColoredPointInfo.cs +++ b/src/ImageSharp.Drawing/Pens/Processors/ColoredPointInfo.cs @@ -12,7 +12,7 @@ namespace ImageSharp.Drawing.Processors /// /// The type of the color. public struct ColoredPointInfo - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// The color diff --git a/src/ImageSharp.Drawing/Pens/Processors/PenApplicator.cs b/src/ImageSharp.Drawing/Pens/Processors/PenApplicator.cs index 86283b5bb6..8cdb04b455 100644 --- a/src/ImageSharp.Drawing/Pens/Processors/PenApplicator.cs +++ b/src/ImageSharp.Drawing/Pens/Processors/PenApplicator.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Drawing.Processors /// /// The type of the color. public abstract class PenApplicator : IDisposable - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Gets the required region. diff --git a/src/ImageSharp.Drawing/Processors/DrawImageProcessor.cs b/src/ImageSharp.Drawing/Processors/DrawImageProcessor.cs index 81b40e6559..1c1de45cbc 100644 --- a/src/ImageSharp.Drawing/Processors/DrawImageProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/DrawImageProcessor.cs @@ -16,7 +16,7 @@ namespace ImageSharp.Drawing.Processors /// /// The pixel format. public class DrawImageProcessor : ImageProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs b/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs index 83ae9521cb..913293ff34 100644 --- a/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs @@ -18,7 +18,7 @@ namespace ImageSharp.Drawing.Processors /// The type of the color. /// public class DrawPathProcessor : ImageProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { private const float AntialiasFactor = 1f; private const int PaddingFactor = 1; // needs to been the same or greater than AntialiasFactor diff --git a/src/ImageSharp.Drawing/Processors/FillProcessor.cs b/src/ImageSharp.Drawing/Processors/FillProcessor.cs index 37bdac90f9..9fa01075d4 100644 --- a/src/ImageSharp.Drawing/Processors/FillProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/FillProcessor.cs @@ -17,7 +17,7 @@ namespace ImageSharp.Drawing.Processors /// /// The pixel format. public class FillProcessor : ImageProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// The brush. diff --git a/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs b/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs index 67313d84b6..fed97275d6 100644 --- a/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs @@ -18,7 +18,7 @@ namespace ImageSharp.Drawing.Processors /// The type of the color. /// public class FillRegionProcessor : ImageProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { private const float AntialiasFactor = 1f; private const int DrawPadding = 1; diff --git a/src/ImageSharp.Formats.Bmp/BmpDecoder.cs b/src/ImageSharp.Formats.Bmp/BmpDecoder.cs index ae7e6c72a2..7546972dc9 100644 --- a/src/ImageSharp.Formats.Bmp/BmpDecoder.cs +++ b/src/ImageSharp.Formats.Bmp/BmpDecoder.cs @@ -27,7 +27,7 @@ namespace ImageSharp.Formats { /// public void Decode(Image image, Stream stream) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { Guard.NotNull(image, "image"); Guard.NotNull(stream, "stream"); diff --git a/src/ImageSharp.Formats.Bmp/BmpDecoderCore.cs b/src/ImageSharp.Formats.Bmp/BmpDecoderCore.cs index 5624176010..a75031ea19 100644 --- a/src/ImageSharp.Formats.Bmp/BmpDecoderCore.cs +++ b/src/ImageSharp.Formats.Bmp/BmpDecoderCore.cs @@ -58,7 +58,7 @@ namespace ImageSharp.Formats /// is null. /// public void Decode(Image image, Stream stream) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { this.currentStream = stream; @@ -212,7 +212,7 @@ namespace ImageSharp.Formats /// The number of bits per pixel. /// Whether the bitmap is inverted. private void ReadRgbPalette(PixelAccessor pixels, byte[] colors, int width, int height, int bits, bool inverted) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { // Pixels per byte (bits per pixel) int ppb = 8 / bits; @@ -267,7 +267,7 @@ namespace ImageSharp.Formats /// The height of the bitmap. /// Whether the bitmap is inverted. private void ReadRgb16(PixelAccessor pixels, int width, int height, bool inverted) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { // We divide here as we will store the colors in our floating point format. const int ScaleR = 8; // 256/32 @@ -309,7 +309,7 @@ namespace ImageSharp.Formats /// The height of the bitmap. /// Whether the bitmap is inverted. private void ReadRgb24(PixelAccessor pixels, int width, int height, bool inverted) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { int padding = CalculatePadding(width, 3); using (PixelArea row = new PixelArea(width, ComponentOrder.Zyx, padding)) @@ -333,7 +333,7 @@ namespace ImageSharp.Formats /// The height of the bitmap. /// Whether the bitmap is inverted. private void ReadRgb32(PixelAccessor pixels, int width, int height, bool inverted) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { int padding = CalculatePadding(width, 4); using (PixelArea row = new PixelArea(width, ComponentOrder.Zyxw, padding)) diff --git a/src/ImageSharp.Formats.Bmp/BmpEncoder.cs b/src/ImageSharp.Formats.Bmp/BmpEncoder.cs index 2ed6b3b986..36b56b3921 100644 --- a/src/ImageSharp.Formats.Bmp/BmpEncoder.cs +++ b/src/ImageSharp.Formats.Bmp/BmpEncoder.cs @@ -21,7 +21,7 @@ namespace ImageSharp.Formats /// public void Encode(Image image, Stream stream) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { BmpEncoderCore encoder = new BmpEncoderCore(); encoder.Encode(image, stream, this.BitsPerPixel); diff --git a/src/ImageSharp.Formats.Bmp/BmpEncoderCore.cs b/src/ImageSharp.Formats.Bmp/BmpEncoderCore.cs index 8abe7cbfce..02d270a0ad 100644 --- a/src/ImageSharp.Formats.Bmp/BmpEncoderCore.cs +++ b/src/ImageSharp.Formats.Bmp/BmpEncoderCore.cs @@ -33,7 +33,7 @@ namespace ImageSharp.Formats /// The to encode the image data to. /// The public void Encode(ImageBase image, Stream stream, BmpBitsPerPixel bitsPerPixel) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); @@ -124,7 +124,7 @@ namespace ImageSharp.Formats /// The containing pixel data. /// private void WriteImage(EndianBinaryWriter writer, ImageBase image) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { using (PixelAccessor pixels = image.Lock()) { @@ -148,7 +148,7 @@ namespace ImageSharp.Formats /// The containing the stream to write to. /// The containing pixel data. private void Write32Bit(EndianBinaryWriter writer, PixelAccessor pixels) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { using (PixelArea row = new PixelArea(pixels.Width, ComponentOrder.Zyxw, this.padding)) { @@ -167,7 +167,7 @@ namespace ImageSharp.Formats /// The containing the stream to write to. /// The containing pixel data. private void Write24Bit(EndianBinaryWriter writer, PixelAccessor pixels) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { using (PixelArea row = new PixelArea(pixels.Width, ComponentOrder.Zyx, this.padding)) { diff --git a/src/ImageSharp.Formats.Bmp/ImageExtensions.cs b/src/ImageSharp.Formats.Bmp/ImageExtensions.cs index 8bbae8487b..5b92b90d63 100644 --- a/src/ImageSharp.Formats.Bmp/ImageExtensions.cs +++ b/src/ImageSharp.Formats.Bmp/ImageExtensions.cs @@ -26,7 +26,7 @@ namespace ImageSharp /// The . /// public static Image SaveAsBmp(this Image source, Stream stream) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel => source.Save(stream, new BmpEncoder()); } } diff --git a/src/ImageSharp.Formats.Gif/GifDecoder.cs b/src/ImageSharp.Formats.Gif/GifDecoder.cs index ce8c0c06e8..914a9098e0 100644 --- a/src/ImageSharp.Formats.Gif/GifDecoder.cs +++ b/src/ImageSharp.Formats.Gif/GifDecoder.cs @@ -15,7 +15,7 @@ namespace ImageSharp.Formats { /// public void Decode(Image image, Stream stream) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { new GifDecoderCore().Decode(image, stream); } diff --git a/src/ImageSharp.Formats.Gif/GifDecoderCore.cs b/src/ImageSharp.Formats.Gif/GifDecoderCore.cs index e6f7b6136f..5812b9f297 100644 --- a/src/ImageSharp.Formats.Gif/GifDecoderCore.cs +++ b/src/ImageSharp.Formats.Gif/GifDecoderCore.cs @@ -14,7 +14,7 @@ namespace ImageSharp.Formats /// /// The pixel format. internal class GifDecoderCore - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// The temp buffer used to reduce allocations. diff --git a/src/ImageSharp.Formats.Gif/GifEncoder.cs b/src/ImageSharp.Formats.Gif/GifEncoder.cs index 2446f0f681..9000cdb64e 100644 --- a/src/ImageSharp.Formats.Gif/GifEncoder.cs +++ b/src/ImageSharp.Formats.Gif/GifEncoder.cs @@ -33,7 +33,7 @@ namespace ImageSharp.Formats /// public void Encode(Image image, Stream stream) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { GifEncoderCore encoder = new GifEncoderCore { diff --git a/src/ImageSharp.Formats.Gif/GifEncoderCore.cs b/src/ImageSharp.Formats.Gif/GifEncoderCore.cs index 80c9ee36bc..36cf614d94 100644 --- a/src/ImageSharp.Formats.Gif/GifEncoderCore.cs +++ b/src/ImageSharp.Formats.Gif/GifEncoderCore.cs @@ -51,7 +51,7 @@ namespace ImageSharp.Formats /// The to encode from. /// The to encode the image data to. public void Encode(Image image, Stream stream) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); @@ -121,7 +121,7 @@ namespace ImageSharp.Formats /// The . /// private int GetTransparentIndex(QuantizedImage quantized) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { // Find the lowest alpha value and make it the transparent index. int index = 255; @@ -171,7 +171,7 @@ namespace ImageSharp.Formats /// The writer to write to the stream with. /// The transparency index to set the default background index to. private void WriteLogicalScreenDescriptor(Image image, EndianBinaryWriter writer, int tranparencyIndex) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { GifLogicalScreenDescriptor descriptor = new GifLogicalScreenDescriptor { @@ -237,7 +237,7 @@ namespace ImageSharp.Formats /// The stream to write to. /// The index of the color in the color palette to make transparent. private void WriteGraphicalControlExtension(Image image, EndianBinaryWriter writer, int transparencyIndex) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { this.WriteGraphicalControlExtension(image, image.MetaData, writer, transparencyIndex); } @@ -250,7 +250,7 @@ namespace ImageSharp.Formats /// The stream to write to. /// The index of the color in the color palette to make transparent. private void WriteGraphicalControlExtension(ImageFrame imageFrame, EndianBinaryWriter writer, int transparencyIndex) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { this.WriteGraphicalControlExtension(imageFrame, imageFrame.MetaData, writer, transparencyIndex); } @@ -264,7 +264,7 @@ namespace ImageSharp.Formats /// The stream to write to. /// The index of the color in the color palette to make transparent. private void WriteGraphicalControlExtension(ImageBase image, IMetaData metaData, EndianBinaryWriter writer, int transparencyIndex) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { // TODO: Check transparency logic. bool hasTransparent = transparencyIndex < 255; @@ -306,7 +306,7 @@ namespace ImageSharp.Formats /// The to be encoded. /// The stream to write to. private void WriteImageDescriptor(ImageBase image, EndianBinaryWriter writer) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { writer.Write(GifConstants.ImageDescriptorLabel); // 2c @@ -332,7 +332,7 @@ namespace ImageSharp.Formats /// The to encode. /// The writer to write to the stream with. private void WriteColorTable(QuantizedImage image, EndianBinaryWriter writer) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { // Grab the palette and write it to the stream. int pixelCount = image.Palette.Length; @@ -367,7 +367,7 @@ namespace ImageSharp.Formats /// The containing indexed pixels. /// The stream to write to. private void WriteImageData(QuantizedImage image, EndianBinaryWriter writer) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { using (LzwEncoder encoder = new LzwEncoder(image.Pixels, (byte)this.bitDepth)) { diff --git a/src/ImageSharp.Formats.Gif/ImageExtensions.cs b/src/ImageSharp.Formats.Gif/ImageExtensions.cs index 09c836a680..e0ec2e8c84 100644 --- a/src/ImageSharp.Formats.Gif/ImageExtensions.cs +++ b/src/ImageSharp.Formats.Gif/ImageExtensions.cs @@ -27,7 +27,7 @@ namespace ImageSharp /// The . /// public static Image SaveAsGif(this Image source, Stream stream, int quality = 256) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel => source.Save(stream, new GifEncoder { Quality = quality }); } } diff --git a/src/ImageSharp.Formats.Jpeg/ImageExtensions.cs b/src/ImageSharp.Formats.Jpeg/ImageExtensions.cs index c0cda4d46a..2cbba02a90 100644 --- a/src/ImageSharp.Formats.Jpeg/ImageExtensions.cs +++ b/src/ImageSharp.Formats.Jpeg/ImageExtensions.cs @@ -27,7 +27,7 @@ namespace ImageSharp /// The . /// public static Image SaveAsJpeg(this Image source, Stream stream, int quality = 75) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel => source.Save(stream, new JpegEncoder { Quality = quality }); } } diff --git a/src/ImageSharp.Formats.Jpeg/JpegDecoder.cs b/src/ImageSharp.Formats.Jpeg/JpegDecoder.cs index 9ee216b4d7..b4df94d528 100644 --- a/src/ImageSharp.Formats.Jpeg/JpegDecoder.cs +++ b/src/ImageSharp.Formats.Jpeg/JpegDecoder.cs @@ -15,7 +15,7 @@ namespace ImageSharp.Formats { /// public void Decode(Image image, Stream stream) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { Guard.NotNull(image, "image"); Guard.NotNull(stream, "stream"); diff --git a/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs b/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs index eb70c4f8c4..6577876828 100644 --- a/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs @@ -177,7 +177,7 @@ namespace ImageSharp.Formats /// The stream, where the image should be. /// Whether to decode metadata only. public void Decode(Image image, Stream stream, bool metadataOnly) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { this.ProcessStream(image, stream, metadataOnly); if (!metadataOnly) @@ -248,7 +248,7 @@ namespace ImageSharp.Formats /// The cr chroma component. [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void PackYcbCr(ref TColor packed, byte y, byte cb, byte cr) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { int ccb = cb - 128; int ccr = cr - 128; @@ -274,7 +274,7 @@ namespace ImageSharp.Formats /// The stream /// Whether to decode metadata only. private void ProcessStream(Image image, Stream stream, bool metadataOnly) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { this.InputStream = stream; this.InputProcessor = new InputProcessor(stream, this.Temp); @@ -472,7 +472,7 @@ namespace ImageSharp.Formats /// /// The pixel type private void ProcessBlocksIntoJpegImageChannels() - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { Parallel.For( 0, @@ -491,7 +491,7 @@ namespace ImageSharp.Formats /// The pixel type /// The destination image private void ConvertJpegPixelsToImagePixels(Image image) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { if (this.grayImage.IsInitialized) { @@ -549,7 +549,7 @@ namespace ImageSharp.Formats /// The pixel format. /// The image to assign the resolution to. private void AssignResolution(Image image) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { if (this.isJfif && this.horizontalResolution > 0 && this.verticalResolution > 0) { @@ -579,7 +579,7 @@ namespace ImageSharp.Formats /// The image height. /// The image. private void ConvertFromCmyk(int width, int height, Image image) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { int scale = this.ComponentArray[0].HorizontalFactor / this.ComponentArray[1].HorizontalFactor; @@ -620,7 +620,7 @@ namespace ImageSharp.Formats /// The image height. /// The image. private void ConvertFromGrayScale(int width, int height, Image image) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { image.InitPixels(width, height); @@ -655,7 +655,7 @@ namespace ImageSharp.Formats /// The height. /// The image. private void ConvertFromRGB(int width, int height, Image image) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { int scale = this.ComponentArray[0].HorizontalFactor / this.ComponentArray[1].HorizontalFactor; image.InitPixels(width, height); @@ -696,7 +696,7 @@ namespace ImageSharp.Formats /// The image height. /// The image. private void ConvertFromYCbCr(int width, int height, Image image) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { int scale = this.ComponentArray[0].HorizontalFactor / this.ComponentArray[1].HorizontalFactor; image.InitPixels(width, height); @@ -737,7 +737,7 @@ namespace ImageSharp.Formats /// The image height. /// The image. private void ConvertFromYcck(int width, int height, Image image) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { int scale = this.ComponentArray[0].HorizontalFactor / this.ComponentArray[1].HorizontalFactor; @@ -862,7 +862,7 @@ namespace ImageSharp.Formats /// The x-position within the image. /// The y-position within the image. private void PackCmyk(ref TColor packed, byte c, byte m, byte y, int xx, int yy) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { // Get keyline float keyline = (255 - this.blackImage[xx, yy]) / 255F; @@ -887,7 +887,7 @@ namespace ImageSharp.Formats /// The x-position within the image. /// The y-position within the image. private void PackYcck(ref TColor packed, byte y, byte cb, byte cr, int xx, int yy) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { // Convert the YCbCr part of the YCbCrK to RGB, invert the RGB to get // CMY, and patch in the original K. The RGB to CMY inversion cancels @@ -956,7 +956,7 @@ namespace ImageSharp.Formats /// The remaining bytes in the segment block. /// The image. private void ProcessApp1Marker(int remaining, Image image) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { if (remaining < 6) { diff --git a/src/ImageSharp.Formats.Jpeg/JpegEncoder.cs b/src/ImageSharp.Formats.Jpeg/JpegEncoder.cs index e56a9f2e81..07d9b24cd9 100644 --- a/src/ImageSharp.Formats.Jpeg/JpegEncoder.cs +++ b/src/ImageSharp.Formats.Jpeg/JpegEncoder.cs @@ -62,7 +62,7 @@ namespace ImageSharp.Formats /// public void Encode(Image image, Stream stream) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { // Ensure that quality can be set but has a fallback. if (image.MetaData.Quality > 0) diff --git a/src/ImageSharp.Formats.Jpeg/JpegEncoderCore.cs b/src/ImageSharp.Formats.Jpeg/JpegEncoderCore.cs index 657470f5cf..0309af1299 100644 --- a/src/ImageSharp.Formats.Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp.Formats.Jpeg/JpegEncoderCore.cs @@ -157,7 +157,7 @@ namespace ImageSharp.Formats /// The quality. /// The subsampling mode. public void Encode(Image image, Stream stream, int quality, JpegSubsample sample) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); @@ -287,7 +287,7 @@ namespace ImageSharp.Formats Block8x8F* cbBlock, Block8x8F* crBlock, PixelArea rgbBytes) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { float* yBlockRaw = (float*)yBlock; float* cbBlockRaw = (float*)cbBlock; @@ -433,7 +433,7 @@ namespace ImageSharp.Formats /// The pixel format. /// The pixel accessor providing access to the image pixels. private void Encode444(PixelAccessor pixels) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { // TODO: Need a JpegScanEncoder class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.) Block8x8F b = default(Block8x8F); @@ -704,7 +704,7 @@ namespace ImageSharp.Formats /// The image. /// The pixel format. private void WriteProfiles(Image image) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { image.MetaData.SyncProfiles(); this.WriteProfile(image.MetaData.ExifProfile); @@ -772,7 +772,7 @@ namespace ImageSharp.Formats /// The pixel format. /// The pixel accessor providing access to the image pixels. private void WriteStartOfScan(PixelAccessor pixels) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { // TODO: Need a JpegScanEncoder class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.) // TODO: We should allow grayscale writing. @@ -799,7 +799,7 @@ namespace ImageSharp.Formats /// The pixel format. /// The pixel accessor providing access to the image pixels. private void Encode420(PixelAccessor pixels) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { // TODO: Need a JpegScanEncoder class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.) Block8x8F b = default(Block8x8F); diff --git a/src/ImageSharp.Formats.Jpeg/Utils/JpegUtils.cs b/src/ImageSharp.Formats.Jpeg/Utils/JpegUtils.cs index 7f62058b7e..dd96985d9a 100644 --- a/src/ImageSharp.Formats.Jpeg/Utils/JpegUtils.cs +++ b/src/ImageSharp.Formats.Jpeg/Utils/JpegUtils.cs @@ -25,7 +25,7 @@ namespace ImageSharp.Formats.Jpg PixelArea dest, int sourceY, int sourceX) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { pixels.SafeCopyTo(dest, sourceY, sourceX); int stretchFromX = pixels.Width - sourceX; @@ -49,13 +49,13 @@ namespace ImageSharp.Formats.Jpg // Nothing to stretch if (fromX, fromY) is outside the area, or is at (0,0) [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool IsInvalidStretchStartingPosition(PixelArea area, int fromX, int fromY) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return fromX <= 0 || fromY <= 0 || fromX >= area.Width || fromY >= area.Height; } private static void StretchPixels(PixelArea area, int fromX, int fromY) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { if (IsInvalidStretchStartingPosition(area, fromX, fromY)) { diff --git a/src/ImageSharp.Formats.Png/ImageExtensions.cs b/src/ImageSharp.Formats.Png/ImageExtensions.cs index d46c46217e..dcb1c988b7 100644 --- a/src/ImageSharp.Formats.Png/ImageExtensions.cs +++ b/src/ImageSharp.Formats.Png/ImageExtensions.cs @@ -29,7 +29,7 @@ namespace ImageSharp /// The . /// public static Image SaveAsPng(this Image source, Stream stream, int quality = int.MaxValue) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel => source.Save(stream, new PngEncoder { Quality = quality }); } } diff --git a/src/ImageSharp.Formats.Png/PngDecoder.cs b/src/ImageSharp.Formats.Png/PngDecoder.cs index 845f0f2229..088bea5919 100644 --- a/src/ImageSharp.Formats.Png/PngDecoder.cs +++ b/src/ImageSharp.Formats.Png/PngDecoder.cs @@ -37,7 +37,7 @@ namespace ImageSharp.Formats /// The to decode to. /// The containing image data. public void Decode(Image image, Stream stream) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { new PngDecoderCore().Decode(image, stream); } diff --git a/src/ImageSharp.Formats.Png/PngDecoderCore.cs b/src/ImageSharp.Formats.Png/PngDecoderCore.cs index 9e871e987b..47b09d5ffb 100644 --- a/src/ImageSharp.Formats.Png/PngDecoderCore.cs +++ b/src/ImageSharp.Formats.Png/PngDecoderCore.cs @@ -138,7 +138,7 @@ namespace ImageSharp.Formats /// Thrown if the image is larger than the maximum allowable size. /// public void Decode(Image image, Stream stream) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { Image currentImage = image; this.currentStream = stream; @@ -262,7 +262,7 @@ namespace ImageSharp.Formats /// The image to read to. /// The data containing physical data. private void ReadPhysicalChunk(Image image, byte[] data) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { data.ReverseBytes(0, 4); data.ReverseBytes(4, 4); @@ -325,7 +325,7 @@ namespace ImageSharp.Formats /// The containing data. /// The pixel data. private void ReadScanlines(MemoryStream dataStream, PixelAccessor pixels) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { this.bytesPerPixel = this.CalculateBytesPerPixel(); this.bytesPerScanline = this.CalculateScanlineLength(this.header.Width) + 1; @@ -356,7 +356,7 @@ namespace ImageSharp.Formats /// The compressed pixel data stream. /// The image pixel accessor. private void DecodePixelData(Stream compressedStream, PixelAccessor pixels) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { byte[] previousScanline = ArrayPool.Shared.Rent(this.bytesPerScanline); byte[] scanline = ArrayPool.Shared.Rent(this.bytesPerScanline); @@ -429,7 +429,7 @@ namespace ImageSharp.Formats /// The compressed pixel data stream. /// The image pixel accessor. private void DecodeInterlacedPixelData(Stream compressedStream, PixelAccessor pixels) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { byte[] previousScanline = ArrayPool.Shared.Rent(this.bytesPerScanline); byte[] scanline = ArrayPool.Shared.Rent(this.bytesPerScanline); @@ -518,7 +518,7 @@ namespace ImageSharp.Formats /// The current image row. /// The image pixels private void ProcessDefilteredScanline(byte[] defilteredScanline, int row, PixelAccessor pixels) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { TColor color = default(TColor); switch (this.PngColorType) @@ -643,7 +643,7 @@ namespace ImageSharp.Formats /// The column start index. Always 0 for none interlaced images. /// The column increment. Always 1 for none interlaced images. private void ProcessInterlacedDefilteredScanline(byte[] defilteredScanline, int row, PixelAccessor pixels, int pixelOffset = 0, int increment = 1) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { TColor color = default(TColor); @@ -761,7 +761,7 @@ namespace ImageSharp.Formats /// The containing data. /// The maximum length to read. private void ReadTextChunk(Image image, byte[] data, int length) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { int zeroIndex = 0; diff --git a/src/ImageSharp.Formats.Png/PngEncoder.cs b/src/ImageSharp.Formats.Png/PngEncoder.cs index 13125e30e0..a6f5e38a59 100644 --- a/src/ImageSharp.Formats.Png/PngEncoder.cs +++ b/src/ImageSharp.Formats.Png/PngEncoder.cs @@ -57,7 +57,7 @@ namespace ImageSharp.Formats /// public void Encode(Image image, Stream stream) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { PngEncoderCore encoder = new PngEncoderCore { diff --git a/src/ImageSharp.Formats.Png/PngEncoderCore.cs b/src/ImageSharp.Formats.Png/PngEncoderCore.cs index 9acaf5b9fa..2324853cba 100644 --- a/src/ImageSharp.Formats.Png/PngEncoderCore.cs +++ b/src/ImageSharp.Formats.Png/PngEncoderCore.cs @@ -132,7 +132,7 @@ namespace ImageSharp.Formats /// The to encode from. /// The to encode the image data to. public void Encode(Image image, Stream stream) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); @@ -262,7 +262,7 @@ namespace ImageSharp.Formats /// The containing image data. /// The . private void CollectIndexedBytes(ImageBase image, Stream stream, PngHeader header) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { // Quantize the image and get the pixels. QuantizedImage quantized = this.WritePaletteChunk(stream, header, image); @@ -277,7 +277,7 @@ namespace ImageSharp.Formats /// The row index. /// The raw scanline. private void CollectGrayscaleBytes(PixelAccessor pixels, int row, byte[] rawScanline) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { // Copy the pixels across from the image. // Reuse the chunk type buffer. @@ -311,7 +311,7 @@ namespace ImageSharp.Formats /// The row index. /// The raw scanline. private void CollectColorBytes(PixelAccessor pixels, int row, byte[] rawScanline) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { // We can use the optimized PixelAccessor here and copy the bytes in unmanaged memory. using (PixelArea pixelRow = new PixelArea(this.width, rawScanline, this.bytesPerPixel == 4 ? ComponentOrder.Xyzw : ComponentOrder.Xyz)) @@ -332,7 +332,7 @@ namespace ImageSharp.Formats /// The filtered scanline result. /// The private byte[] EncodePixelRow(PixelAccessor pixels, int row, byte[] previousScanline, byte[] rawScanline, byte[] result) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { switch (this.PngColorType) { @@ -486,7 +486,7 @@ namespace ImageSharp.Formats /// The image to encode. /// The private QuantizedImage WritePaletteChunk(Stream stream, PngHeader header, ImageBase image) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { if (this.Quality > 256) { @@ -554,7 +554,7 @@ namespace ImageSharp.Formats /// The containing image data. /// The image base. private void WritePhysicalChunk(Stream stream, ImageBase imageBase) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { Image image = imageBase as Image; if (image != null && image.MetaData.HorizontalResolution > 0 && image.MetaData.VerticalResolution > 0) @@ -600,7 +600,7 @@ namespace ImageSharp.Formats /// The pixel accessor. /// The stream. private void WriteDataChunks(PixelAccessor pixels, Stream stream) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { int bytesPerScanline = this.width * this.bytesPerPixel; byte[] previousScanline = new byte[bytesPerScanline]; diff --git a/src/ImageSharp.Processing/Binarization/BinaryThreshold.cs b/src/ImageSharp.Processing/Binarization/BinaryThreshold.cs index e59369349f..672726d929 100644 --- a/src/ImageSharp.Processing/Binarization/BinaryThreshold.cs +++ b/src/ImageSharp.Processing/Binarization/BinaryThreshold.cs @@ -22,7 +22,7 @@ namespace ImageSharp /// The threshold to apply binarization of the image. Must be between 0 and 1. /// The . public static Image BinaryThreshold(this Image source, float threshold) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return BinaryThreshold(source, threshold, source.Bounds); } @@ -38,7 +38,7 @@ namespace ImageSharp /// /// The . public static Image BinaryThreshold(this Image source, float threshold, Rectangle rectangle) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { source.ApplyProcessor(new BinaryThresholdProcessor(threshold), rectangle); return source; diff --git a/src/ImageSharp.Processing/Binarization/Dither.cs b/src/ImageSharp.Processing/Binarization/Dither.cs index 6a4f7f0057..dd6dfe8a14 100644 --- a/src/ImageSharp.Processing/Binarization/Dither.cs +++ b/src/ImageSharp.Processing/Binarization/Dither.cs @@ -24,7 +24,7 @@ namespace ImageSharp /// The component index to test the threshold against. Must range from 0 to 3. /// The . public static Image Dither(this Image source, IOrderedDither dither, int index = 0) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return Dither(source, dither, source.Bounds, index); } @@ -41,7 +41,7 @@ namespace ImageSharp /// The component index to test the threshold against. Must range from 0 to 3. /// The . public static Image Dither(this Image source, IOrderedDither dither, Rectangle rectangle, int index = 0) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { source.ApplyProcessor(new OrderedDitherProcessor(dither, index), rectangle); return source; @@ -56,7 +56,7 @@ namespace ImageSharp /// The threshold to apply binarization of the image. Must be between 0 and 1. /// The . public static Image Dither(this Image source, IErrorDiffuser diffuser, float threshold) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return Dither(source, diffuser, threshold, source.Bounds); } @@ -73,7 +73,7 @@ namespace ImageSharp /// /// The . public static Image Dither(this Image source, IErrorDiffuser diffuser, float threshold, Rectangle rectangle) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { source.ApplyProcessor(new ErrorDiffusionDitherProcessor(diffuser, threshold), rectangle); return source; diff --git a/src/ImageSharp.Processing/ColorMatrix/BlackWhite.cs b/src/ImageSharp.Processing/ColorMatrix/BlackWhite.cs index 05fb8f19c8..63d6dd33c5 100644 --- a/src/ImageSharp.Processing/ColorMatrix/BlackWhite.cs +++ b/src/ImageSharp.Processing/ColorMatrix/BlackWhite.cs @@ -22,7 +22,7 @@ namespace ImageSharp /// The image this method extends. /// The . public static Image BlackWhite(this Image source) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return BlackWhite(source, source.Bounds); } @@ -37,7 +37,7 @@ namespace ImageSharp /// /// The . public static Image BlackWhite(this Image source, Rectangle rectangle) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { source.ApplyProcessor(new BlackWhiteProcessor(), rectangle); return source; diff --git a/src/ImageSharp.Processing/ColorMatrix/ColorBlindness.cs b/src/ImageSharp.Processing/ColorMatrix/ColorBlindness.cs index 30b28861d0..36a139d0ea 100644 --- a/src/ImageSharp.Processing/ColorMatrix/ColorBlindness.cs +++ b/src/ImageSharp.Processing/ColorMatrix/ColorBlindness.cs @@ -23,7 +23,7 @@ namespace ImageSharp /// The type of color blindness simulator to apply. /// The . public static Image ColorBlindness(this Image source, ColorBlindness colorBlindness) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return ColorBlindness(source, colorBlindness, source.Bounds); } @@ -39,7 +39,7 @@ namespace ImageSharp /// /// The . public static Image ColorBlindness(this Image source, ColorBlindness colorBlindness, Rectangle rectangle) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { IImageProcessor processor; diff --git a/src/ImageSharp.Processing/ColorMatrix/Grayscale.cs b/src/ImageSharp.Processing/ColorMatrix/Grayscale.cs index b2aadc46db..613b999d44 100644 --- a/src/ImageSharp.Processing/ColorMatrix/Grayscale.cs +++ b/src/ImageSharp.Processing/ColorMatrix/Grayscale.cs @@ -23,7 +23,7 @@ namespace ImageSharp /// The formula to apply to perform the operation. /// The . public static Image Grayscale(this Image source, GrayscaleMode mode = GrayscaleMode.Bt709) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return Grayscale(source, source.Bounds, mode); } @@ -39,7 +39,7 @@ namespace ImageSharp /// The formula to apply to perform the operation. /// The . public static Image Grayscale(this Image source, Rectangle rectangle, GrayscaleMode mode = GrayscaleMode.Bt709) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { IImageProcessor processor = mode == GrayscaleMode.Bt709 ? (IImageProcessor)new GrayscaleBt709Processor() diff --git a/src/ImageSharp.Processing/ColorMatrix/Hue.cs b/src/ImageSharp.Processing/ColorMatrix/Hue.cs index 25fcc6c559..8edeb2ff31 100644 --- a/src/ImageSharp.Processing/ColorMatrix/Hue.cs +++ b/src/ImageSharp.Processing/ColorMatrix/Hue.cs @@ -23,7 +23,7 @@ namespace ImageSharp /// The angle in degrees to adjust the image. /// The . public static Image Hue(this Image source, float degrees) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return Hue(source, degrees, source.Bounds); } @@ -39,7 +39,7 @@ namespace ImageSharp /// /// The . public static Image Hue(this Image source, float degrees, Rectangle rectangle) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { source.ApplyProcessor(new HueProcessor(degrees), rectangle); return source; diff --git a/src/ImageSharp.Processing/ColorMatrix/Kodachrome.cs b/src/ImageSharp.Processing/ColorMatrix/Kodachrome.cs index dab0224c3a..5084c96b25 100644 --- a/src/ImageSharp.Processing/ColorMatrix/Kodachrome.cs +++ b/src/ImageSharp.Processing/ColorMatrix/Kodachrome.cs @@ -22,7 +22,7 @@ namespace ImageSharp /// The image this method extends. /// The . public static Image Kodachrome(this Image source) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return Kodachrome(source, source.Bounds); } @@ -37,7 +37,7 @@ namespace ImageSharp /// /// The . public static Image Kodachrome(this Image source, Rectangle rectangle) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { source.ApplyProcessor(new KodachromeProcessor(), rectangle); return source; diff --git a/src/ImageSharp.Processing/ColorMatrix/Lomograph.cs b/src/ImageSharp.Processing/ColorMatrix/Lomograph.cs index df34dc52e9..ef6b23d5da 100644 --- a/src/ImageSharp.Processing/ColorMatrix/Lomograph.cs +++ b/src/ImageSharp.Processing/ColorMatrix/Lomograph.cs @@ -22,7 +22,7 @@ namespace ImageSharp /// The image this method extends. /// The . public static Image Lomograph(this Image source) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return Lomograph(source, source.Bounds); } @@ -37,7 +37,7 @@ namespace ImageSharp /// /// The . public static Image Lomograph(this Image source, Rectangle rectangle) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { source.ApplyProcessor(new LomographProcessor(), rectangle); return source; diff --git a/src/ImageSharp.Processing/ColorMatrix/Polaroid.cs b/src/ImageSharp.Processing/ColorMatrix/Polaroid.cs index 4bb8f82a31..68b10173cc 100644 --- a/src/ImageSharp.Processing/ColorMatrix/Polaroid.cs +++ b/src/ImageSharp.Processing/ColorMatrix/Polaroid.cs @@ -22,7 +22,7 @@ namespace ImageSharp /// The image this method extends. /// The . public static Image Polaroid(this Image source) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return Polaroid(source, source.Bounds); } @@ -37,7 +37,7 @@ namespace ImageSharp /// /// The . public static Image Polaroid(this Image source, Rectangle rectangle) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { source.ApplyProcessor(new PolaroidProcessor(), rectangle); return source; diff --git a/src/ImageSharp.Processing/ColorMatrix/Saturation.cs b/src/ImageSharp.Processing/ColorMatrix/Saturation.cs index a92483c9c3..7a6359744b 100644 --- a/src/ImageSharp.Processing/ColorMatrix/Saturation.cs +++ b/src/ImageSharp.Processing/ColorMatrix/Saturation.cs @@ -23,7 +23,7 @@ namespace ImageSharp /// The new saturation of the image. Must be between -100 and 100. /// The . public static Image Saturation(this Image source, int amount) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return Saturation(source, amount, source.Bounds); } @@ -39,7 +39,7 @@ namespace ImageSharp /// /// The . public static Image Saturation(this Image source, int amount, Rectangle rectangle) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { source.ApplyProcessor(new SaturationProcessor(amount), rectangle); return source; diff --git a/src/ImageSharp.Processing/ColorMatrix/Sepia.cs b/src/ImageSharp.Processing/ColorMatrix/Sepia.cs index 1a8ec4b95e..4943635e06 100644 --- a/src/ImageSharp.Processing/ColorMatrix/Sepia.cs +++ b/src/ImageSharp.Processing/ColorMatrix/Sepia.cs @@ -22,7 +22,7 @@ namespace ImageSharp /// The image this method extends. /// The . public static Image Sepia(this Image source) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return Sepia(source, source.Bounds); } @@ -37,7 +37,7 @@ namespace ImageSharp /// /// The . public static Image Sepia(this Image source, Rectangle rectangle) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { source.ApplyProcessor(new SepiaProcessor(), rectangle); return source; diff --git a/src/ImageSharp.Processing/Convolution/BoxBlur.cs b/src/ImageSharp.Processing/Convolution/BoxBlur.cs index 6bdd68b9b6..428142ffa8 100644 --- a/src/ImageSharp.Processing/Convolution/BoxBlur.cs +++ b/src/ImageSharp.Processing/Convolution/BoxBlur.cs @@ -22,7 +22,7 @@ namespace ImageSharp /// The 'radius' value representing the size of the area to sample. /// The . public static Image BoxBlur(this Image source, int radius = 7) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return BoxBlur(source, radius, source.Bounds); } @@ -38,7 +38,7 @@ namespace ImageSharp /// /// The . public static Image BoxBlur(this Image source, int radius, Rectangle rectangle) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { source.ApplyProcessor(new BoxBlurProcessor(radius), rectangle); return source; diff --git a/src/ImageSharp.Processing/Convolution/DetectEdges.cs b/src/ImageSharp.Processing/Convolution/DetectEdges.cs index a61726e74e..dba062b563 100644 --- a/src/ImageSharp.Processing/Convolution/DetectEdges.cs +++ b/src/ImageSharp.Processing/Convolution/DetectEdges.cs @@ -23,7 +23,7 @@ namespace ImageSharp /// The image this method extends. /// The . public static Image DetectEdges(this Image source) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return DetectEdges(source, source.Bounds, new SobelProcessor { Grayscale = true }); } @@ -39,7 +39,7 @@ namespace ImageSharp /// /// The . public static Image DetectEdges(this Image source, Rectangle rectangle) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return DetectEdges(source, rectangle, new SobelProcessor { Grayscale = true }); } @@ -53,7 +53,7 @@ namespace ImageSharp /// Whether to convert the image to Grayscale first. Defaults to true. /// The . public static Image DetectEdges(this Image source, EdgeDetection filter, bool grayscale = true) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return DetectEdges(source, filter, source.Bounds, grayscale); } @@ -70,7 +70,7 @@ namespace ImageSharp /// Whether to convert the image to Grayscale first. Defaults to true. /// The . public static Image DetectEdges(this Image source, EdgeDetection filter, Rectangle rectangle, bool grayscale = true) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { IEdgeDetectorProcessor processor; @@ -128,7 +128,7 @@ namespace ImageSharp /// The filter for detecting edges. /// The . public static Image DetectEdges(this Image source, IEdgeDetectorProcessor filter) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return DetectEdges(source, source.Bounds, filter); } @@ -144,7 +144,7 @@ namespace ImageSharp /// The filter for detecting edges. /// The . public static Image DetectEdges(this Image source, Rectangle rectangle, IEdgeDetectorProcessor filter) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { source.ApplyProcessor(filter, rectangle); return source; diff --git a/src/ImageSharp.Processing/Convolution/GaussianBlur.cs b/src/ImageSharp.Processing/Convolution/GaussianBlur.cs index 893ebb2646..81f8546380 100644 --- a/src/ImageSharp.Processing/Convolution/GaussianBlur.cs +++ b/src/ImageSharp.Processing/Convolution/GaussianBlur.cs @@ -23,7 +23,7 @@ namespace ImageSharp /// The 'sigma' value representing the weight of the blur. /// The . public static Image GaussianBlur(this Image source, float sigma = 3f) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return GaussianBlur(source, sigma, source.Bounds); } @@ -39,7 +39,7 @@ namespace ImageSharp /// /// The . public static Image GaussianBlur(this Image source, float sigma, Rectangle rectangle) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { source.ApplyProcessor(new GaussianBlurProcessor(sigma), rectangle); return source; diff --git a/src/ImageSharp.Processing/Convolution/GaussianSharpen.cs b/src/ImageSharp.Processing/Convolution/GaussianSharpen.cs index e3f8e995bc..61816198a5 100644 --- a/src/ImageSharp.Processing/Convolution/GaussianSharpen.cs +++ b/src/ImageSharp.Processing/Convolution/GaussianSharpen.cs @@ -23,7 +23,7 @@ namespace ImageSharp /// The 'sigma' value representing the weight of the blur. /// The . public static Image GaussianSharpen(this Image source, float sigma = 3f) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return GaussianSharpen(source, sigma, source.Bounds); } @@ -39,7 +39,7 @@ namespace ImageSharp /// /// The . public static Image GaussianSharpen(this Image source, float sigma, Rectangle rectangle) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { source.ApplyProcessor(new GaussianSharpenProcessor(sigma), rectangle); return source; diff --git a/src/ImageSharp.Processing/Effects/Alpha.cs b/src/ImageSharp.Processing/Effects/Alpha.cs index 21b6dfbcbc..39849d4d4b 100644 --- a/src/ImageSharp.Processing/Effects/Alpha.cs +++ b/src/ImageSharp.Processing/Effects/Alpha.cs @@ -22,7 +22,7 @@ namespace ImageSharp /// The new opacity of the image. Must be between 0 and 100. /// The . public static Image Alpha(this Image source, int percent) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return Alpha(source, percent, source.Bounds); } @@ -38,7 +38,7 @@ namespace ImageSharp /// /// The . public static Image Alpha(this Image source, int percent, Rectangle rectangle) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { source.ApplyProcessor(new AlphaProcessor(percent), rectangle); return source; diff --git a/src/ImageSharp.Processing/Effects/BackgroundColor.cs b/src/ImageSharp.Processing/Effects/BackgroundColor.cs index 0f724c08c0..2e621172e1 100644 --- a/src/ImageSharp.Processing/Effects/BackgroundColor.cs +++ b/src/ImageSharp.Processing/Effects/BackgroundColor.cs @@ -22,7 +22,7 @@ namespace ImageSharp /// The color to set as the background. /// The . public static Image BackgroundColor(this Image source, TColor color) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { source.ApplyProcessor(new BackgroundColorProcessor(color), source.Bounds); return source; diff --git a/src/ImageSharp.Processing/Effects/Brightness.cs b/src/ImageSharp.Processing/Effects/Brightness.cs index bf5500faf5..8ba702c4f5 100644 --- a/src/ImageSharp.Processing/Effects/Brightness.cs +++ b/src/ImageSharp.Processing/Effects/Brightness.cs @@ -22,7 +22,7 @@ namespace ImageSharp /// The new brightness of the image. Must be between -100 and 100. /// The . public static Image Brightness(this Image source, int amount) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return Brightness(source, amount, source.Bounds); } @@ -38,7 +38,7 @@ namespace ImageSharp /// /// The . public static Image Brightness(this Image source, int amount, Rectangle rectangle) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { source.ApplyProcessor(new BrightnessProcessor(amount), rectangle); return source; diff --git a/src/ImageSharp.Processing/Effects/Contrast.cs b/src/ImageSharp.Processing/Effects/Contrast.cs index f05eea6399..0228f4fe37 100644 --- a/src/ImageSharp.Processing/Effects/Contrast.cs +++ b/src/ImageSharp.Processing/Effects/Contrast.cs @@ -22,7 +22,7 @@ namespace ImageSharp /// The new contrast of the image. Must be between -100 and 100. /// The . public static Image Contrast(this Image source, int amount) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return Contrast(source, amount, source.Bounds); } @@ -38,7 +38,7 @@ namespace ImageSharp /// /// The . public static Image Contrast(this Image source, int amount, Rectangle rectangle) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { source.ApplyProcessor(new ContrastProcessor(amount), rectangle); return source; diff --git a/src/ImageSharp.Processing/Effects/Invert.cs b/src/ImageSharp.Processing/Effects/Invert.cs index fff3108df4..6c51ad3eb3 100644 --- a/src/ImageSharp.Processing/Effects/Invert.cs +++ b/src/ImageSharp.Processing/Effects/Invert.cs @@ -21,7 +21,7 @@ namespace ImageSharp /// The image this method extends. /// The . public static Image Invert(this Image source) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return Invert(source, source.Bounds); } @@ -36,7 +36,7 @@ namespace ImageSharp /// /// The . public static Image Invert(this Image source, Rectangle rectangle) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { source.ApplyProcessor(new InvertProcessor(), rectangle); return source; diff --git a/src/ImageSharp.Processing/Effects/OilPainting.cs b/src/ImageSharp.Processing/Effects/OilPainting.cs index fbd7777110..d7d8444c01 100644 --- a/src/ImageSharp.Processing/Effects/OilPainting.cs +++ b/src/ImageSharp.Processing/Effects/OilPainting.cs @@ -23,7 +23,7 @@ namespace ImageSharp /// The number of neighboring pixels used in calculating each individual pixel value. /// The . public static Image OilPaint(this Image source, int levels = 10, int brushSize = 15) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return OilPaint(source, levels, brushSize, source.Bounds); } @@ -40,7 +40,7 @@ namespace ImageSharp /// /// The . public static Image OilPaint(this Image source, int levels, int brushSize, Rectangle rectangle) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { Guard.MustBeGreaterThan(levels, 0, nameof(levels)); diff --git a/src/ImageSharp.Processing/Effects/Pixelate.cs b/src/ImageSharp.Processing/Effects/Pixelate.cs index 77b014e73d..721dd930b0 100644 --- a/src/ImageSharp.Processing/Effects/Pixelate.cs +++ b/src/ImageSharp.Processing/Effects/Pixelate.cs @@ -22,7 +22,7 @@ namespace ImageSharp /// The size of the pixels. /// The . public static Image Pixelate(this Image source, int size = 4) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return Pixelate(source, size, source.Bounds); } @@ -38,7 +38,7 @@ namespace ImageSharp /// /// The . public static Image Pixelate(this Image source, int size, Rectangle rectangle) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { if (size <= 0 || size > source.Height || size > source.Width) { diff --git a/src/ImageSharp.Processing/Overlays/Glow.cs b/src/ImageSharp.Processing/Overlays/Glow.cs index 9c5fe017f2..e8dfbdf0ef 100644 --- a/src/ImageSharp.Processing/Overlays/Glow.cs +++ b/src/ImageSharp.Processing/Overlays/Glow.cs @@ -21,7 +21,7 @@ namespace ImageSharp /// The image this method extends. /// The . public static Image Glow(this Image source) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return Glow(source, NamedColors.Black, source.Bounds.Width * .5F, source.Bounds); } @@ -34,7 +34,7 @@ namespace ImageSharp /// The color to set as the glow. /// The . public static Image Glow(this Image source, TColor color) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return Glow(source, color, source.Bounds.Width * .5F, source.Bounds); } @@ -47,7 +47,7 @@ namespace ImageSharp /// The the radius. /// The . public static Image Glow(this Image source, float radius) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return Glow(source, NamedColors.Black, radius, source.Bounds); } @@ -62,7 +62,7 @@ namespace ImageSharp /// /// The . public static Image Glow(this Image source, Rectangle rectangle) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return Glow(source, NamedColors.Black, 0, rectangle); } @@ -79,7 +79,7 @@ namespace ImageSharp /// /// The . public static Image Glow(this Image source, TColor color, float radius, Rectangle rectangle) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { GlowProcessor processor = new GlowProcessor(color) { Radius = radius, }; source.ApplyProcessor(processor, rectangle); diff --git a/src/ImageSharp.Processing/Overlays/Vignette.cs b/src/ImageSharp.Processing/Overlays/Vignette.cs index 4a505ad9bb..e42ead8d3a 100644 --- a/src/ImageSharp.Processing/Overlays/Vignette.cs +++ b/src/ImageSharp.Processing/Overlays/Vignette.cs @@ -21,7 +21,7 @@ namespace ImageSharp /// The image this method extends. /// The . public static Image Vignette(this Image source) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return Vignette(source, NamedColors.Black, source.Bounds.Width * .5F, source.Bounds.Height * .5F, source.Bounds); } @@ -34,7 +34,7 @@ namespace ImageSharp /// The color to set as the vignette. /// The . public static Image Vignette(this Image source, TColor color) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return Vignette(source, color, source.Bounds.Width * .5F, source.Bounds.Height * .5F, source.Bounds); } @@ -48,7 +48,7 @@ namespace ImageSharp /// The the y-radius. /// The . public static Image Vignette(this Image source, float radiusX, float radiusY) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return Vignette(source, NamedColors.Black, radiusX, radiusY, source.Bounds); } @@ -63,7 +63,7 @@ namespace ImageSharp /// /// The . public static Image Vignette(this Image source, Rectangle rectangle) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return Vignette(source, NamedColors.Black, 0, 0, rectangle); } @@ -81,7 +81,7 @@ namespace ImageSharp /// /// The . public static Image Vignette(this Image source, TColor color, float radiusX, float radiusY, Rectangle rectangle) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { VignetteProcessor processor = new VignetteProcessor(color) { RadiusX = radiusX, RadiusY = radiusY }; source.ApplyProcessor(processor, rectangle); diff --git a/src/ImageSharp.Processing/Processors/Binarization/BinaryThresholdProcessor.cs b/src/ImageSharp.Processing/Processors/Binarization/BinaryThresholdProcessor.cs index e4010413fd..2d57957d3f 100644 --- a/src/ImageSharp.Processing/Processors/Binarization/BinaryThresholdProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Binarization/BinaryThresholdProcessor.cs @@ -14,7 +14,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class BinaryThresholdProcessor : ImageProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp.Processing/Processors/Binarization/ErrorDiffusionDitherProcessor.cs b/src/ImageSharp.Processing/Processors/Binarization/ErrorDiffusionDitherProcessor.cs index 25b464c692..ce03c58a8a 100644 --- a/src/ImageSharp.Processing/Processors/Binarization/ErrorDiffusionDitherProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Binarization/ErrorDiffusionDitherProcessor.cs @@ -14,7 +14,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class ErrorDiffusionDitherProcessor : ImageProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp.Processing/Processors/Binarization/OrderedDitherProcessor.cs b/src/ImageSharp.Processing/Processors/Binarization/OrderedDitherProcessor.cs index f201ba49aa..4126419f25 100644 --- a/src/ImageSharp.Processing/Processors/Binarization/OrderedDitherProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Binarization/OrderedDitherProcessor.cs @@ -15,7 +15,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class OrderedDitherProcessor : ImageProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp.Processing/Processors/ColorMatrix/BlackWhiteProcessor.cs b/src/ImageSharp.Processing/Processors/ColorMatrix/BlackWhiteProcessor.cs index 305375eca1..0ea821bef6 100644 --- a/src/ImageSharp.Processing/Processors/ColorMatrix/BlackWhiteProcessor.cs +++ b/src/ImageSharp.Processing/Processors/ColorMatrix/BlackWhiteProcessor.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class BlackWhiteProcessor : ColorMatrixFilter - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// public override Matrix4x4 Matrix => new Matrix4x4() diff --git a/src/ImageSharp.Processing/Processors/ColorMatrix/ColorBlindness/AchromatomalyProcessor.cs b/src/ImageSharp.Processing/Processors/ColorMatrix/ColorBlindness/AchromatomalyProcessor.cs index 3e34d08382..15e7c78da1 100644 --- a/src/ImageSharp.Processing/Processors/ColorMatrix/ColorBlindness/AchromatomalyProcessor.cs +++ b/src/ImageSharp.Processing/Processors/ColorMatrix/ColorBlindness/AchromatomalyProcessor.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class AchromatomalyProcessor : ColorMatrixFilter - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// public override Matrix4x4 Matrix => new Matrix4x4() diff --git a/src/ImageSharp.Processing/Processors/ColorMatrix/ColorBlindness/AchromatopsiaProcessor.cs b/src/ImageSharp.Processing/Processors/ColorMatrix/ColorBlindness/AchromatopsiaProcessor.cs index 53a7a3556b..adca0fe985 100644 --- a/src/ImageSharp.Processing/Processors/ColorMatrix/ColorBlindness/AchromatopsiaProcessor.cs +++ b/src/ImageSharp.Processing/Processors/ColorMatrix/ColorBlindness/AchromatopsiaProcessor.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class AchromatopsiaProcessor : ColorMatrixFilter - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// public override Matrix4x4 Matrix => new Matrix4x4() diff --git a/src/ImageSharp.Processing/Processors/ColorMatrix/ColorBlindness/DeuteranomalyProcessor.cs b/src/ImageSharp.Processing/Processors/ColorMatrix/ColorBlindness/DeuteranomalyProcessor.cs index 5d252961c5..6de54beeae 100644 --- a/src/ImageSharp.Processing/Processors/ColorMatrix/ColorBlindness/DeuteranomalyProcessor.cs +++ b/src/ImageSharp.Processing/Processors/ColorMatrix/ColorBlindness/DeuteranomalyProcessor.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class DeuteranomalyProcessor : ColorMatrixFilter - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// public override Matrix4x4 Matrix => new Matrix4x4() diff --git a/src/ImageSharp.Processing/Processors/ColorMatrix/ColorBlindness/DeuteranopiaProcessor.cs b/src/ImageSharp.Processing/Processors/ColorMatrix/ColorBlindness/DeuteranopiaProcessor.cs index cd48df4016..4729ccc616 100644 --- a/src/ImageSharp.Processing/Processors/ColorMatrix/ColorBlindness/DeuteranopiaProcessor.cs +++ b/src/ImageSharp.Processing/Processors/ColorMatrix/ColorBlindness/DeuteranopiaProcessor.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class DeuteranopiaProcessor : ColorMatrixFilter - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// public override Matrix4x4 Matrix => new Matrix4x4() diff --git a/src/ImageSharp.Processing/Processors/ColorMatrix/ColorBlindness/ProtanomalyProcessor.cs b/src/ImageSharp.Processing/Processors/ColorMatrix/ColorBlindness/ProtanomalyProcessor.cs index 234c2e13b5..200fff24d8 100644 --- a/src/ImageSharp.Processing/Processors/ColorMatrix/ColorBlindness/ProtanomalyProcessor.cs +++ b/src/ImageSharp.Processing/Processors/ColorMatrix/ColorBlindness/ProtanomalyProcessor.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class ProtanomalyProcessor : ColorMatrixFilter - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// public override Matrix4x4 Matrix => new Matrix4x4() diff --git a/src/ImageSharp.Processing/Processors/ColorMatrix/ColorBlindness/ProtanopiaProcessor.cs b/src/ImageSharp.Processing/Processors/ColorMatrix/ColorBlindness/ProtanopiaProcessor.cs index a88b8812e6..7c0f03543f 100644 --- a/src/ImageSharp.Processing/Processors/ColorMatrix/ColorBlindness/ProtanopiaProcessor.cs +++ b/src/ImageSharp.Processing/Processors/ColorMatrix/ColorBlindness/ProtanopiaProcessor.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class ProtanopiaProcessor : ColorMatrixFilter - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// public override Matrix4x4 Matrix => new Matrix4x4() diff --git a/src/ImageSharp.Processing/Processors/ColorMatrix/ColorBlindness/TritanomalyProcessor.cs b/src/ImageSharp.Processing/Processors/ColorMatrix/ColorBlindness/TritanomalyProcessor.cs index 1f68bddbbb..63f1fd9eb1 100644 --- a/src/ImageSharp.Processing/Processors/ColorMatrix/ColorBlindness/TritanomalyProcessor.cs +++ b/src/ImageSharp.Processing/Processors/ColorMatrix/ColorBlindness/TritanomalyProcessor.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class TritanomalyProcessor : ColorMatrixFilter - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// public override Matrix4x4 Matrix => new Matrix4x4() diff --git a/src/ImageSharp.Processing/Processors/ColorMatrix/ColorBlindness/TritanopiaProcessor.cs b/src/ImageSharp.Processing/Processors/ColorMatrix/ColorBlindness/TritanopiaProcessor.cs index 85332e8106..2200414fee 100644 --- a/src/ImageSharp.Processing/Processors/ColorMatrix/ColorBlindness/TritanopiaProcessor.cs +++ b/src/ImageSharp.Processing/Processors/ColorMatrix/ColorBlindness/TritanopiaProcessor.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class TritanopiaProcessor : ColorMatrixFilter - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// public override Matrix4x4 Matrix => new Matrix4x4() diff --git a/src/ImageSharp.Processing/Processors/ColorMatrix/ColorMatrixFilter.cs b/src/ImageSharp.Processing/Processors/ColorMatrix/ColorMatrixFilter.cs index b11b82b102..a37228a9ba 100644 --- a/src/ImageSharp.Processing/Processors/ColorMatrix/ColorMatrixFilter.cs +++ b/src/ImageSharp.Processing/Processors/ColorMatrix/ColorMatrixFilter.cs @@ -14,7 +14,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public abstract class ColorMatrixFilter : ImageProcessor, IColorMatrixFilter - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// public abstract Matrix4x4 Matrix { get; } diff --git a/src/ImageSharp.Processing/Processors/ColorMatrix/GrayscaleBt601Processor.cs b/src/ImageSharp.Processing/Processors/ColorMatrix/GrayscaleBt601Processor.cs index 364919e74e..bd14da59e5 100644 --- a/src/ImageSharp.Processing/Processors/ColorMatrix/GrayscaleBt601Processor.cs +++ b/src/ImageSharp.Processing/Processors/ColorMatrix/GrayscaleBt601Processor.cs @@ -14,7 +14,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class GrayscaleBt601Processor : ColorMatrixFilter - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// public override Matrix4x4 Matrix => new Matrix4x4() diff --git a/src/ImageSharp.Processing/Processors/ColorMatrix/GrayscaleBt709Processor.cs b/src/ImageSharp.Processing/Processors/ColorMatrix/GrayscaleBt709Processor.cs index 37d41ab74d..925a36c754 100644 --- a/src/ImageSharp.Processing/Processors/ColorMatrix/GrayscaleBt709Processor.cs +++ b/src/ImageSharp.Processing/Processors/ColorMatrix/GrayscaleBt709Processor.cs @@ -14,7 +14,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class GrayscaleBt709Processor : ColorMatrixFilter - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// public override Matrix4x4 Matrix => new Matrix4x4() diff --git a/src/ImageSharp.Processing/Processors/ColorMatrix/HueProcessor.cs b/src/ImageSharp.Processing/Processors/ColorMatrix/HueProcessor.cs index 0de0891fea..fdf5ffdb44 100644 --- a/src/ImageSharp.Processing/Processors/ColorMatrix/HueProcessor.cs +++ b/src/ImageSharp.Processing/Processors/ColorMatrix/HueProcessor.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class HueProcessor : ColorMatrixFilter - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp.Processing/Processors/ColorMatrix/IColorMatrixFilter.cs b/src/ImageSharp.Processing/Processors/ColorMatrix/IColorMatrixFilter.cs index 4230fda12a..faee890eb6 100644 --- a/src/ImageSharp.Processing/Processors/ColorMatrix/IColorMatrixFilter.cs +++ b/src/ImageSharp.Processing/Processors/ColorMatrix/IColorMatrixFilter.cs @@ -14,7 +14,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public interface IColorMatrixFilter : IImageProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Gets the used to alter the image. diff --git a/src/ImageSharp.Processing/Processors/ColorMatrix/KodachromeProcessor.cs b/src/ImageSharp.Processing/Processors/ColorMatrix/KodachromeProcessor.cs index 84a05e579c..fee1684985 100644 --- a/src/ImageSharp.Processing/Processors/ColorMatrix/KodachromeProcessor.cs +++ b/src/ImageSharp.Processing/Processors/ColorMatrix/KodachromeProcessor.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class KodachromeProcessor : ColorMatrixFilter - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// public override Matrix4x4 Matrix => new Matrix4x4() diff --git a/src/ImageSharp.Processing/Processors/ColorMatrix/LomographProcessor.cs b/src/ImageSharp.Processing/Processors/ColorMatrix/LomographProcessor.cs index ad166b1347..0e614afe8a 100644 --- a/src/ImageSharp.Processing/Processors/ColorMatrix/LomographProcessor.cs +++ b/src/ImageSharp.Processing/Processors/ColorMatrix/LomographProcessor.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class LomographProcessor : ColorMatrixFilter - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { private static readonly TColor VeryDarkGreen = ColorBuilder.FromRGBA(0, 10, 0, 255); diff --git a/src/ImageSharp.Processing/Processors/ColorMatrix/PolaroidProcessor.cs b/src/ImageSharp.Processing/Processors/ColorMatrix/PolaroidProcessor.cs index 5df11160fb..666fe5bc0c 100644 --- a/src/ImageSharp.Processing/Processors/ColorMatrix/PolaroidProcessor.cs +++ b/src/ImageSharp.Processing/Processors/ColorMatrix/PolaroidProcessor.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class PolaroidProcessor : ColorMatrixFilter - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { private static TColor veryDarkOrange = ColorBuilder.FromRGB(102, 34, 0); private static TColor lightOrange = ColorBuilder.FromRGBA(255, 153, 102, 178); diff --git a/src/ImageSharp.Processing/Processors/ColorMatrix/SaturationProcessor.cs b/src/ImageSharp.Processing/Processors/ColorMatrix/SaturationProcessor.cs index 430228d53e..d63326385c 100644 --- a/src/ImageSharp.Processing/Processors/ColorMatrix/SaturationProcessor.cs +++ b/src/ImageSharp.Processing/Processors/ColorMatrix/SaturationProcessor.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Processing.Processors ///
/// The pixel format. public class SaturationProcessor : ColorMatrixFilter - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp.Processing/Processors/ColorMatrix/SepiaProcessor.cs b/src/ImageSharp.Processing/Processors/ColorMatrix/SepiaProcessor.cs index 1170fc3a90..d8fdc6cd1a 100644 --- a/src/ImageSharp.Processing/Processors/ColorMatrix/SepiaProcessor.cs +++ b/src/ImageSharp.Processing/Processors/ColorMatrix/SepiaProcessor.cs @@ -14,7 +14,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class SepiaProcessor : ColorMatrixFilter - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// public override Matrix4x4 Matrix => new Matrix4x4 diff --git a/src/ImageSharp.Processing/Processors/Convolution/BoxBlurProcessor.cs b/src/ImageSharp.Processing/Processors/Convolution/BoxBlurProcessor.cs index 8ca1ca6669..367095b2b5 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/BoxBlurProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/BoxBlurProcessor.cs @@ -12,7 +12,7 @@ namespace ImageSharp.Processing.Processors ///
/// The pixel format. public class BoxBlurProcessor : ImageProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// The maximum size of the kernel in either direction. diff --git a/src/ImageSharp.Processing/Processors/Convolution/Convolution2DProcessor.cs b/src/ImageSharp.Processing/Processors/Convolution/Convolution2DProcessor.cs index 71b71ba5d2..d104f5d351 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/Convolution2DProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/Convolution2DProcessor.cs @@ -14,7 +14,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class Convolution2DProcessor : ImageProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp.Processing/Processors/Convolution/Convolution2PassProcessor.cs b/src/ImageSharp.Processing/Processors/Convolution/Convolution2PassProcessor.cs index 495bfa5dba..1d118443c3 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/Convolution2PassProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/Convolution2PassProcessor.cs @@ -14,7 +14,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class Convolution2PassProcessor : ImageProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp.Processing/Processors/Convolution/ConvolutionProcessor.cs b/src/ImageSharp.Processing/Processors/Convolution/ConvolutionProcessor.cs index 46df9c6eea..6b5b6d3fe4 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/ConvolutionProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/ConvolutionProcessor.cs @@ -14,7 +14,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class ConvolutionProcessor : ImageProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/EdgeDetector2DProcessor.cs b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/EdgeDetector2DProcessor.cs index f86940adef..a8c786f712 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/EdgeDetector2DProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/EdgeDetector2DProcessor.cs @@ -12,7 +12,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public abstract class EdgeDetector2DProcessor : ImageProcessor, IEdgeDetectorProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/EdgeDetectorCompassProcessor.cs b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/EdgeDetectorCompassProcessor.cs index 58967cb5b1..eb8491d4cd 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/EdgeDetectorCompassProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/EdgeDetectorCompassProcessor.cs @@ -14,7 +14,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public abstract class EdgeDetectorCompassProcessor : ImageProcessor, IEdgeDetectorProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Gets the North gradient operator diff --git a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/EdgeDetectorProcessor.cs b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/EdgeDetectorProcessor.cs index cd2b91f167..a963bb5780 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/EdgeDetectorProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/EdgeDetectorProcessor.cs @@ -12,7 +12,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public abstract class EdgeDetectorProcessor : ImageProcessor, IEdgeDetectorProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/IEdgeDetectorProcessor.cs b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/IEdgeDetectorProcessor.cs index 68dc7ccdbb..7c0923bbbd 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/IEdgeDetectorProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/IEdgeDetectorProcessor.cs @@ -12,7 +12,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public interface IEdgeDetectorProcessor : IImageProcessor, IEdgeDetectorProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { } diff --git a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/KayyaliProcessor.cs b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/KayyaliProcessor.cs index 6e1452e17f..6cdd5757e1 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/KayyaliProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/KayyaliProcessor.cs @@ -15,7 +15,7 @@ namespace ImageSharp.Processing.Processors /// The pixel format. [SuppressMessage("ReSharper", "StaticMemberInGenericType", Justification = "We want to use only one instance of each array field for each generic type.")] public class KayyaliProcessor : EdgeDetector2DProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// The horizontal gradient operator. diff --git a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/KirschProcessor.cs b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/KirschProcessor.cs index f8cb9aba9d..e4e4e7bfac 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/KirschProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/KirschProcessor.cs @@ -15,7 +15,7 @@ namespace ImageSharp.Processing.Processors /// The pixel format. [SuppressMessage("ReSharper", "StaticMemberInGenericType", Justification = "We want to use only one instance of each array field for each generic type.")] public class KirschProcessor : EdgeDetectorCompassProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// The North gradient operator diff --git a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/Laplacian3X3Processor.cs b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/Laplacian3X3Processor.cs index 4b23dfe470..6dcea8b00e 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/Laplacian3X3Processor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/Laplacian3X3Processor.cs @@ -15,7 +15,7 @@ namespace ImageSharp.Processing.Processors /// The pixel format. [SuppressMessage("ReSharper", "StaticMemberInGenericType", Justification = "We want to use only one instance of each array field for each generic type.")] public class Laplacian3X3Processor : EdgeDetectorProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// The 2d gradient operator. diff --git a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/Laplacian5X5Processor.cs b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/Laplacian5X5Processor.cs index 304dafbbd1..239cbb968e 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/Laplacian5X5Processor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/Laplacian5X5Processor.cs @@ -15,7 +15,7 @@ namespace ImageSharp.Processing.Processors /// The pixel format. [SuppressMessage("ReSharper", "StaticMemberInGenericType", Justification = "We want to use only one instance of each array field for each generic type.")] public class Laplacian5X5Processor : EdgeDetectorProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// The 2d gradient operator. diff --git a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/LaplacianOfGaussianProcessor.cs b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/LaplacianOfGaussianProcessor.cs index e18957a69a..73bc453a32 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/LaplacianOfGaussianProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/LaplacianOfGaussianProcessor.cs @@ -15,7 +15,7 @@ namespace ImageSharp.Processing.Processors /// The pixel format. [SuppressMessage("ReSharper", "StaticMemberInGenericType", Justification = "We want to use only one instance of each array field for each generic type.")] public class LaplacianOfGaussianProcessor : EdgeDetectorProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// The 2d gradient operator. diff --git a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/PrewittProcessor.cs b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/PrewittProcessor.cs index 1d61b8cd94..5d4737e51c 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/PrewittProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/PrewittProcessor.cs @@ -15,7 +15,7 @@ namespace ImageSharp.Processing.Processors /// The pixel format. [SuppressMessage("ReSharper", "StaticMemberInGenericType", Justification = "We want to use only one instance of each array field for each generic type.")] public class PrewittProcessor : EdgeDetector2DProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// The horizontal gradient operator. diff --git a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/RobertsCrossProcessor.cs b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/RobertsCrossProcessor.cs index 83f13a3429..1be583cf3d 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/RobertsCrossProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/RobertsCrossProcessor.cs @@ -15,7 +15,7 @@ namespace ImageSharp.Processing.Processors /// The pixel format. [SuppressMessage("ReSharper", "StaticMemberInGenericType", Justification = "We want to use only one instance of each array field for each generic type.")] public class RobertsCrossProcessor : EdgeDetector2DProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// The horizontal gradient operator. diff --git a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/RobinsonProcessor.cs b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/RobinsonProcessor.cs index a8187caca3..87651c3c63 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/RobinsonProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/RobinsonProcessor.cs @@ -15,7 +15,7 @@ namespace ImageSharp.Processing.Processors /// The pixel format. [SuppressMessage("ReSharper", "StaticMemberInGenericType", Justification = "We want to use only one instance of each array field for each generic type.")] public class RobinsonProcessor : EdgeDetectorCompassProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// The North gradient operator diff --git a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/ScharrProcessor.cs b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/ScharrProcessor.cs index 6b9e67ce9e..4be2e720bd 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/ScharrProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/ScharrProcessor.cs @@ -15,7 +15,7 @@ namespace ImageSharp.Processing.Processors /// The pixel format. [SuppressMessage("ReSharper", "StaticMemberInGenericType", Justification = "We want to use only one instance of each array field for each generic type.")] public class ScharrProcessor : EdgeDetector2DProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// The horizontal gradient operator. diff --git a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/SobelProcessor.cs b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/SobelProcessor.cs index 1607889677..bb64f3e13f 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/SobelProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/EdgeDetection/SobelProcessor.cs @@ -15,7 +15,7 @@ namespace ImageSharp.Processing.Processors /// The pixel format. [SuppressMessage("ReSharper", "StaticMemberInGenericType", Justification = "We want to use only one instance of each array field for each generic type.")] public class SobelProcessor : EdgeDetector2DProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// The horizontal gradient operator. diff --git a/src/ImageSharp.Processing/Processors/Convolution/GaussianBlurProcessor.cs b/src/ImageSharp.Processing/Processors/Convolution/GaussianBlurProcessor.cs index 49733a9bd0..daa660f842 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/GaussianBlurProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/GaussianBlurProcessor.cs @@ -12,7 +12,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class GaussianBlurProcessor : ImageProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// The maximum size of the kernel in either direction. diff --git a/src/ImageSharp.Processing/Processors/Convolution/GaussianSharpenProcessor.cs b/src/ImageSharp.Processing/Processors/Convolution/GaussianSharpenProcessor.cs index 2254a61b6e..a51bb5b9fc 100644 --- a/src/ImageSharp.Processing/Processors/Convolution/GaussianSharpenProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Convolution/GaussianSharpenProcessor.cs @@ -12,7 +12,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class GaussianSharpenProcessor : ImageProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// The maximum size of the kernel in either direction. diff --git a/src/ImageSharp.Processing/Processors/Effects/AlphaProcessor.cs b/src/ImageSharp.Processing/Processors/Effects/AlphaProcessor.cs index ecf47a036c..11af92ea75 100644 --- a/src/ImageSharp.Processing/Processors/Effects/AlphaProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Effects/AlphaProcessor.cs @@ -14,7 +14,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class AlphaProcessor : ImageProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp.Processing/Processors/Effects/BackgroundColorProcessor.cs b/src/ImageSharp.Processing/Processors/Effects/BackgroundColorProcessor.cs index 356b2e925a..d6d209dc7e 100644 --- a/src/ImageSharp.Processing/Processors/Effects/BackgroundColorProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Effects/BackgroundColorProcessor.cs @@ -14,7 +14,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class BackgroundColorProcessor : ImageProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp.Processing/Processors/Effects/BrightnessProcessor.cs b/src/ImageSharp.Processing/Processors/Effects/BrightnessProcessor.cs index eb88b9c41c..566f2c6d74 100644 --- a/src/ImageSharp.Processing/Processors/Effects/BrightnessProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Effects/BrightnessProcessor.cs @@ -14,7 +14,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class BrightnessProcessor : ImageProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp.Processing/Processors/Effects/ContrastProcessor.cs b/src/ImageSharp.Processing/Processors/Effects/ContrastProcessor.cs index 0cc56cc8e4..f4acc42bfc 100644 --- a/src/ImageSharp.Processing/Processors/Effects/ContrastProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Effects/ContrastProcessor.cs @@ -14,7 +14,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class ContrastProcessor : ImageProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp.Processing/Processors/Effects/InvertProcessor.cs b/src/ImageSharp.Processing/Processors/Effects/InvertProcessor.cs index ec1ea7786e..641cd1b47d 100644 --- a/src/ImageSharp.Processing/Processors/Effects/InvertProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Effects/InvertProcessor.cs @@ -14,7 +14,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class InvertProcessor : ImageProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// protected override void OnApply(ImageBase source, Rectangle sourceRectangle) diff --git a/src/ImageSharp.Processing/Processors/Effects/OilPaintingProcessor.cs b/src/ImageSharp.Processing/Processors/Effects/OilPaintingProcessor.cs index 5c16af2f7f..6b9558ad2c 100644 --- a/src/ImageSharp.Processing/Processors/Effects/OilPaintingProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Effects/OilPaintingProcessor.cs @@ -15,7 +15,7 @@ namespace ImageSharp.Processing.Processors /// Adapted from by Dewald Esterhuizen. /// The pixel format. public class OilPaintingProcessor : ImageProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp.Processing/Processors/Effects/PixelateProcessor.cs b/src/ImageSharp.Processing/Processors/Effects/PixelateProcessor.cs index c197ce356f..d9d78dfa8c 100644 --- a/src/ImageSharp.Processing/Processors/Effects/PixelateProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Effects/PixelateProcessor.cs @@ -14,7 +14,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class PixelateProcessor : ImageProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp.Processing/Processors/Overlays/GlowProcessor.cs b/src/ImageSharp.Processing/Processors/Overlays/GlowProcessor.cs index 690f57ab73..9043b66cb8 100644 --- a/src/ImageSharp.Processing/Processors/Overlays/GlowProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Overlays/GlowProcessor.cs @@ -14,7 +14,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class GlowProcessor : ImageProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp.Processing/Processors/Overlays/VignetteProcessor.cs b/src/ImageSharp.Processing/Processors/Overlays/VignetteProcessor.cs index 6e94a4c2ac..f1872d0b81 100644 --- a/src/ImageSharp.Processing/Processors/Overlays/VignetteProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Overlays/VignetteProcessor.cs @@ -14,7 +14,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class VignetteProcessor : ImageProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp.Processing/Processors/Transforms/CompandingResizeProcessor.cs b/src/ImageSharp.Processing/Processors/Transforms/CompandingResizeProcessor.cs index ac8a52321e..53da214831 100644 --- a/src/ImageSharp.Processing/Processors/Transforms/CompandingResizeProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Transforms/CompandingResizeProcessor.cs @@ -15,7 +15,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class CompandingResizeProcessor : ResamplingWeightedProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp.Processing/Processors/Transforms/CropProcessor.cs b/src/ImageSharp.Processing/Processors/Transforms/CropProcessor.cs index bdfbc496c9..88f23a7fd6 100644 --- a/src/ImageSharp.Processing/Processors/Transforms/CropProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Transforms/CropProcessor.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class CropProcessor : ImageProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp.Processing/Processors/Transforms/EntropyCropProcessor.cs b/src/ImageSharp.Processing/Processors/Transforms/EntropyCropProcessor.cs index 98297eed99..d150c59d49 100644 --- a/src/ImageSharp.Processing/Processors/Transforms/EntropyCropProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Transforms/EntropyCropProcessor.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class EntropyCropProcessor : ImageProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp.Processing/Processors/Transforms/FlipProcessor.cs b/src/ImageSharp.Processing/Processors/Transforms/FlipProcessor.cs index ad375ce0fb..da4f2b3a80 100644 --- a/src/ImageSharp.Processing/Processors/Transforms/FlipProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Transforms/FlipProcessor.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class FlipProcessor : ImageProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp.Processing/Processors/Transforms/Matrix3x2Processor.cs b/src/ImageSharp.Processing/Processors/Transforms/Matrix3x2Processor.cs index 209ad3914b..7e87576870 100644 --- a/src/ImageSharp.Processing/Processors/Transforms/Matrix3x2Processor.cs +++ b/src/ImageSharp.Processing/Processors/Transforms/Matrix3x2Processor.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public abstract class Matrix3x2Processor : ImageProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Gets the rectangle designating the target canvas. diff --git a/src/ImageSharp.Processing/Processors/Transforms/ResamplingWeightedProcessor.cs b/src/ImageSharp.Processing/Processors/Transforms/ResamplingWeightedProcessor.cs index cac8871539..25a7a4efd6 100644 --- a/src/ImageSharp.Processing/Processors/Transforms/ResamplingWeightedProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Transforms/ResamplingWeightedProcessor.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public abstract class ResamplingWeightedProcessor : ImageProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp.Processing/Processors/Transforms/ResizeProcessor.cs b/src/ImageSharp.Processing/Processors/Transforms/ResizeProcessor.cs index f5d6308089..f4ec39f785 100644 --- a/src/ImageSharp.Processing/Processors/Transforms/ResizeProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Transforms/ResizeProcessor.cs @@ -17,7 +17,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class ResizeProcessor : ResamplingWeightedProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp.Processing/Processors/Transforms/RotateProcessor.cs b/src/ImageSharp.Processing/Processors/Transforms/RotateProcessor.cs index a5a762b911..22fbb895c1 100644 --- a/src/ImageSharp.Processing/Processors/Transforms/RotateProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Transforms/RotateProcessor.cs @@ -14,7 +14,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class RotateProcessor : Matrix3x2Processor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// The transform matrix to apply. diff --git a/src/ImageSharp.Processing/Processors/Transforms/SkewProcessor.cs b/src/ImageSharp.Processing/Processors/Transforms/SkewProcessor.cs index 4daa46491a..c6afbedcce 100644 --- a/src/ImageSharp.Processing/Processors/Transforms/SkewProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Transforms/SkewProcessor.cs @@ -14,7 +14,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. public class SkewProcessor : Matrix3x2Processor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// The transform matrix to apply. diff --git a/src/ImageSharp.Processing/Transforms/AutoOrient.cs b/src/ImageSharp.Processing/Transforms/AutoOrient.cs index cb4e72a948..8c5e22b995 100644 --- a/src/ImageSharp.Processing/Transforms/AutoOrient.cs +++ b/src/ImageSharp.Processing/Transforms/AutoOrient.cs @@ -21,7 +21,7 @@ namespace ImageSharp /// The image to auto rotate. /// The public static Image AutoOrient(this Image source) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { Orientation orientation = GetExifOrientation(source); @@ -64,7 +64,7 @@ namespace ImageSharp /// The image to auto rotate. /// The private static Orientation GetExifOrientation(Image source) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { if (source.MetaData.ExifProfile == null) { diff --git a/src/ImageSharp.Processing/Transforms/Crop.cs b/src/ImageSharp.Processing/Transforms/Crop.cs index bdcbe51d8a..92773aaeac 100644 --- a/src/ImageSharp.Processing/Transforms/Crop.cs +++ b/src/ImageSharp.Processing/Transforms/Crop.cs @@ -23,7 +23,7 @@ namespace ImageSharp /// The target image height. /// The public static Image Crop(this Image source, int width, int height) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return Crop(source, new Rectangle(0, 0, width, height)); } @@ -38,7 +38,7 @@ namespace ImageSharp /// /// The public static Image Crop(this Image source, Rectangle cropRectangle) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { CropProcessor processor = new CropProcessor(cropRectangle); diff --git a/src/ImageSharp.Processing/Transforms/EntropyCrop.cs b/src/ImageSharp.Processing/Transforms/EntropyCrop.cs index fcfcf3813e..ad2ce89e3d 100644 --- a/src/ImageSharp.Processing/Transforms/EntropyCrop.cs +++ b/src/ImageSharp.Processing/Transforms/EntropyCrop.cs @@ -22,7 +22,7 @@ namespace ImageSharp /// The threshold for entropic density. /// The public static Image EntropyCrop(this Image source, float threshold = .5f) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { EntropyCropProcessor processor = new EntropyCropProcessor(threshold); diff --git a/src/ImageSharp.Processing/Transforms/Flip.cs b/src/ImageSharp.Processing/Transforms/Flip.cs index 82b664eb14..ed096eb750 100644 --- a/src/ImageSharp.Processing/Transforms/Flip.cs +++ b/src/ImageSharp.Processing/Transforms/Flip.cs @@ -23,7 +23,7 @@ namespace ImageSharp /// The to perform the flip. /// The public static Image Flip(this Image source, FlipType flipType) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { FlipProcessor processor = new FlipProcessor(flipType); diff --git a/src/ImageSharp.Processing/Transforms/Options/ResizeHelper.cs b/src/ImageSharp.Processing/Transforms/Options/ResizeHelper.cs index 4ed147aedf..3dc37e52d7 100644 --- a/src/ImageSharp.Processing/Transforms/Options/ResizeHelper.cs +++ b/src/ImageSharp.Processing/Transforms/Options/ResizeHelper.cs @@ -24,7 +24,7 @@ namespace ImageSharp.Processing /// The . /// public static Rectangle CalculateTargetLocationAndBounds(ImageBase source, ResizeOptions options) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { switch (options.Mode) { @@ -55,7 +55,7 @@ namespace ImageSharp.Processing /// The . /// private static Rectangle CalculateCropRectangle(ImageBase source, ResizeOptions options) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { int width = options.Size.Width; int height = options.Size.Height; @@ -174,7 +174,7 @@ namespace ImageSharp.Processing /// The . /// private static Rectangle CalculatePadRectangle(ImageBase source, ResizeOptions options) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { int width = options.Size.Width; int height = options.Size.Height; @@ -255,7 +255,7 @@ namespace ImageSharp.Processing /// The . /// private static Rectangle CalculateBoxPadRectangle(ImageBase source, ResizeOptions options) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { int width = options.Size.Width; int height = options.Size.Height; @@ -342,7 +342,7 @@ namespace ImageSharp.Processing /// The . /// private static Rectangle CalculateMaxRectangle(ImageBase source, ResizeOptions options) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { int width = options.Size.Width; int height = options.Size.Height; @@ -383,7 +383,7 @@ namespace ImageSharp.Processing /// The . /// private static Rectangle CalculateMinRectangle(ImageBase source, ResizeOptions options) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { int width = options.Size.Width; int height = options.Size.Height; diff --git a/src/ImageSharp.Processing/Transforms/Pad.cs b/src/ImageSharp.Processing/Transforms/Pad.cs index df45a94f33..bd530ecd82 100644 --- a/src/ImageSharp.Processing/Transforms/Pad.cs +++ b/src/ImageSharp.Processing/Transforms/Pad.cs @@ -24,7 +24,7 @@ namespace ImageSharp /// The new height. /// The . public static Image Pad(this Image source, int width, int height) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { ResizeOptions options = new ResizeOptions { diff --git a/src/ImageSharp.Processing/Transforms/Resize.cs b/src/ImageSharp.Processing/Transforms/Resize.cs index aa4bf2439f..ab256c4ae2 100644 --- a/src/ImageSharp.Processing/Transforms/Resize.cs +++ b/src/ImageSharp.Processing/Transforms/Resize.cs @@ -24,7 +24,7 @@ namespace ImageSharp /// The /// Passing zero for one of height or width within the resize options will automatically preserve the aspect ratio of the original image public static Image Resize(this Image source, ResizeOptions options) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { // Ensure size is populated across both dimensions. if (options.Size.Width == 0 && options.Size.Height > 0) @@ -51,7 +51,7 @@ namespace ImageSharp /// The /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image public static Image Resize(this Image source, Size size) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return Resize(source, size.Width, size.Height, new BicubicResampler(), false); } @@ -66,7 +66,7 @@ namespace ImageSharp /// The /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image public static Image Resize(this Image source, int width, int height) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return Resize(source, width, height, new BicubicResampler(), false); } @@ -82,7 +82,7 @@ namespace ImageSharp /// The /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image public static Image Resize(this Image source, int width, int height, bool compand) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return Resize(source, width, height, new BicubicResampler(), compand); } @@ -98,7 +98,7 @@ namespace ImageSharp /// The /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image public static Image Resize(this Image source, int width, int height, IResampler sampler) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return Resize(source, width, height, sampler, false); } @@ -115,7 +115,7 @@ namespace ImageSharp /// The /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image public static Image Resize(this Image source, int width, int height, IResampler sampler, bool compand) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return Resize(source, width, height, sampler, source.Bounds, new Rectangle(0, 0, width, height), compand); } @@ -139,7 +139,7 @@ namespace ImageSharp /// The /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image public static Image Resize(this Image source, int width, int height, IResampler sampler, Rectangle sourceRectangle, Rectangle targetRectangle, bool compand = false) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { if (width == 0 && height > 0) { diff --git a/src/ImageSharp.Processing/Transforms/Rotate.cs b/src/ImageSharp.Processing/Transforms/Rotate.cs index e9ed4e97c1..76311ef252 100644 --- a/src/ImageSharp.Processing/Transforms/Rotate.cs +++ b/src/ImageSharp.Processing/Transforms/Rotate.cs @@ -23,7 +23,7 @@ namespace ImageSharp /// The angle in degrees to perform the rotation. /// The public static Image Rotate(this Image source, float degrees) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return Rotate(source, degrees, true); } @@ -36,7 +36,7 @@ namespace ImageSharp /// The to perform the rotation. /// The public static Image Rotate(this Image source, RotateType rotateType) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return Rotate(source, (float)rotateType, false); } @@ -50,7 +50,7 @@ namespace ImageSharp /// Whether to expand the image to fit the rotated result. /// The public static Image Rotate(this Image source, float degrees, bool expand) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { RotateProcessor processor = new RotateProcessor { Angle = degrees, Expand = expand }; diff --git a/src/ImageSharp.Processing/Transforms/RotateFlip.cs b/src/ImageSharp.Processing/Transforms/RotateFlip.cs index e69f183380..d6050db3f3 100644 --- a/src/ImageSharp.Processing/Transforms/RotateFlip.cs +++ b/src/ImageSharp.Processing/Transforms/RotateFlip.cs @@ -22,7 +22,7 @@ namespace ImageSharp /// The to perform the flip. /// The public static Image RotateFlip(this Image source, RotateType rotateType, FlipType flipType) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return source.Rotate(rotateType).Flip(flipType); } diff --git a/src/ImageSharp.Processing/Transforms/Skew.cs b/src/ImageSharp.Processing/Transforms/Skew.cs index 5a662c3004..03fdbcceb4 100644 --- a/src/ImageSharp.Processing/Transforms/Skew.cs +++ b/src/ImageSharp.Processing/Transforms/Skew.cs @@ -23,7 +23,7 @@ namespace ImageSharp /// The angle in degrees to perform the rotation along the y-axis. /// The public static Image Skew(this Image source, float degreesX, float degreesY) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return Skew(source, degreesX, degreesY, true); } @@ -38,7 +38,7 @@ namespace ImageSharp /// Whether to expand the image to fit the skewed result. /// The public static Image Skew(this Image source, float degreesX, float degreesY, bool expand) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { SkewProcessor processor = new SkewProcessor { AngleX = degreesX, AngleY = degreesY, Expand = expand }; diff --git a/src/ImageSharp/Colors/Color.cs b/src/ImageSharp/Colors/Color.cs index 730fe45a52..b82628b3b8 100644 --- a/src/ImageSharp/Colors/Color.cs +++ b/src/ImageSharp/Colors/Color.cs @@ -18,7 +18,7 @@ namespace ImageSharp /// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, /// as it avoids the need to create new values for modification operations. /// - public partial struct Color : IPackedPixel, IEquatable + public partial struct Color : IPixel { /// /// The shift count for the red component @@ -184,7 +184,9 @@ namespace ImageSharp } } - /// + /// + /// Gets or sets the packed representation of the value. + /// public uint PackedValue { get diff --git a/src/ImageSharp/Colors/ColorBuilder{TColor}.cs b/src/ImageSharp/Colors/ColorBuilder{TColor}.cs index 47506af49f..f60b5c8c09 100644 --- a/src/ImageSharp/Colors/ColorBuilder{TColor}.cs +++ b/src/ImageSharp/Colors/ColorBuilder{TColor}.cs @@ -13,7 +13,7 @@ namespace ImageSharp /// /// The type of the color. public static class ColorBuilder - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Creates a new representation from the string representing a color. diff --git a/src/ImageSharp/Colors/NamedColors{TColor}.cs b/src/ImageSharp/Colors/NamedColors{TColor}.cs index 77a537f0f1..bcad4dd40b 100644 --- a/src/ImageSharp/Colors/NamedColors{TColor}.cs +++ b/src/ImageSharp/Colors/NamedColors{TColor}.cs @@ -12,7 +12,7 @@ namespace ImageSharp /// /// The type of the color. public static class NamedColors - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Represents a matching the W3C definition that has an hex value of #F0F8FF. diff --git a/src/ImageSharp/Colors/PackedPixel/Alpha8.cs b/src/ImageSharp/Colors/PackedPixel/Alpha8.cs index 5642e62010..5141cbca43 100644 --- a/src/ImageSharp/Colors/PackedPixel/Alpha8.cs +++ b/src/ImageSharp/Colors/PackedPixel/Alpha8.cs @@ -12,7 +12,7 @@ namespace ImageSharp /// /// Packed pixel type containing a single 8 bit normalized W values ranging from 0 to 1. /// - public struct Alpha8 : IPackedPixel, IEquatable + public struct Alpha8 : IPixel { /// /// Initializes a new instance of the struct. @@ -23,7 +23,9 @@ namespace ImageSharp this.PackedValue = Pack(alpha); } - /// + /// + /// Gets or sets the packed representation of the value. + /// public byte PackedValue { get; set; } /// diff --git a/src/ImageSharp/Colors/PackedPixel/Argb.cs b/src/ImageSharp/Colors/PackedPixel/Argb.cs index 783545c680..57ae0898bb 100644 --- a/src/ImageSharp/Colors/PackedPixel/Argb.cs +++ b/src/ImageSharp/Colors/PackedPixel/Argb.cs @@ -17,7 +17,7 @@ namespace ImageSharp /// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, /// as it avoids the need to create new values for modification operations. /// - public struct Argb : IPackedPixel, IEquatable + public struct Argb : IPixel { /// /// The shift count for the blue component @@ -106,7 +106,9 @@ namespace ImageSharp this.PackedValue = packed; } - /// + /// + /// Gets or sets the packed representation of the value. + /// public uint PackedValue { get; set; } /// diff --git a/src/ImageSharp/Colors/PackedPixel/Bgr565.cs b/src/ImageSharp/Colors/PackedPixel/Bgr565.cs index 19c7f3d14b..042c8c0f5f 100644 --- a/src/ImageSharp/Colors/PackedPixel/Bgr565.cs +++ b/src/ImageSharp/Colors/PackedPixel/Bgr565.cs @@ -12,7 +12,7 @@ namespace ImageSharp /// /// Packed pixel type containing unsigned normalized values ranging from 0 to 1. The x and z components use 5 bits, and the y component uses 6 bits. /// - public struct Bgr565 : IPackedPixel, IEquatable + public struct Bgr565 : IPixel { /// /// Initializes a new instance of the struct. @@ -36,7 +36,9 @@ namespace ImageSharp this.PackedValue = Pack(vector.X, vector.Y, vector.Z); } - /// + /// + /// Gets or sets the packed representation of the value. + /// public ushort PackedValue { get; set; } /// diff --git a/src/ImageSharp/Colors/PackedPixel/Bgra4444.cs b/src/ImageSharp/Colors/PackedPixel/Bgra4444.cs index 41f7cba040..8bae542c2a 100644 --- a/src/ImageSharp/Colors/PackedPixel/Bgra4444.cs +++ b/src/ImageSharp/Colors/PackedPixel/Bgra4444.cs @@ -12,7 +12,7 @@ namespace ImageSharp /// /// Packed pixel type containing unsigned normalized values, ranging from 0 to 1, using 4 bits each for x, y, z, and w. /// - public struct Bgra4444 : IPackedPixel, IEquatable + public struct Bgra4444 : IPixel { /// /// Initializes a new instance of the struct. @@ -35,7 +35,9 @@ namespace ImageSharp this.PackedValue = Pack(vector.X, vector.Y, vector.Z, vector.W); } - /// + /// + /// Gets or sets the packed representation of the value. + /// public ushort PackedValue { get; set; } /// diff --git a/src/ImageSharp/Colors/PackedPixel/Bgra5551.cs b/src/ImageSharp/Colors/PackedPixel/Bgra5551.cs index 800cb0bf16..0ad7dbd0e3 100644 --- a/src/ImageSharp/Colors/PackedPixel/Bgra5551.cs +++ b/src/ImageSharp/Colors/PackedPixel/Bgra5551.cs @@ -12,7 +12,7 @@ namespace ImageSharp /// /// Packed pixel type containing unsigned normalized values ranging from 0 to 1. The x , y and z components use 5 bits, and the w component uses 1 bit. /// - public struct Bgra5551 : IPackedPixel, IEquatable + public struct Bgra5551 : IPixel { /// /// Initializes a new instance of the struct. @@ -37,7 +37,9 @@ namespace ImageSharp this.PackedValue = Pack(vector.X, vector.Y, vector.Z, vector.W); } - /// + /// + /// Gets or sets the packed representation of the value. + /// public ushort PackedValue { get; set; } /// diff --git a/src/ImageSharp/Colors/PackedPixel/Byte4.cs b/src/ImageSharp/Colors/PackedPixel/Byte4.cs index 16e066890e..b1f8d5a6ea 100644 --- a/src/ImageSharp/Colors/PackedPixel/Byte4.cs +++ b/src/ImageSharp/Colors/PackedPixel/Byte4.cs @@ -12,7 +12,7 @@ namespace ImageSharp /// /// Packed pixel type containing four 8-bit unsigned integer values, ranging from 0 to 255. /// - public struct Byte4 : IPackedPixel, IEquatable + public struct Byte4 : IPixel { /// /// Initializes a new instance of the struct. @@ -38,7 +38,9 @@ namespace ImageSharp this.PackedValue = Pack(ref vector); } - /// + /// + /// Gets or sets the packed representation of the value. + /// public uint PackedValue { get; set; } /// diff --git a/src/ImageSharp/Colors/PackedPixel/HalfSingle.cs b/src/ImageSharp/Colors/PackedPixel/HalfSingle.cs index 29a55095b9..bd4ba3840a 100644 --- a/src/ImageSharp/Colors/PackedPixel/HalfSingle.cs +++ b/src/ImageSharp/Colors/PackedPixel/HalfSingle.cs @@ -12,7 +12,7 @@ namespace ImageSharp /// /// Packed pixel type containing a single 16 bit floating point value. /// - public struct HalfSingle : IPackedPixel, IEquatable + public struct HalfSingle : IPixel { /// /// The maximum byte value. @@ -33,7 +33,9 @@ namespace ImageSharp this.PackedValue = HalfTypeHelper.Pack(single); } - /// + /// + /// Gets or sets the packed representation of the value. + /// public ushort PackedValue { get; set; } /// diff --git a/src/ImageSharp/Colors/PackedPixel/HalfVector2.cs b/src/ImageSharp/Colors/PackedPixel/HalfVector2.cs index 02e93b2503..4d1f0a2cb4 100644 --- a/src/ImageSharp/Colors/PackedPixel/HalfVector2.cs +++ b/src/ImageSharp/Colors/PackedPixel/HalfVector2.cs @@ -12,7 +12,7 @@ namespace ImageSharp /// /// Packed pixel type containing two 16-bit floating-point values. /// - public struct HalfVector2 : IPackedPixel, IEquatable + public struct HalfVector2 : IPixel { /// /// The maximum byte value. @@ -43,7 +43,9 @@ namespace ImageSharp this.PackedValue = Pack(vector.X, vector.Y); } - /// + /// + /// Gets or sets the packed representation of the value. + /// public uint PackedValue { get; set; } /// diff --git a/src/ImageSharp/Colors/PackedPixel/HalfVector4.cs b/src/ImageSharp/Colors/PackedPixel/HalfVector4.cs index 38787acea8..e0108404d1 100644 --- a/src/ImageSharp/Colors/PackedPixel/HalfVector4.cs +++ b/src/ImageSharp/Colors/PackedPixel/HalfVector4.cs @@ -12,7 +12,7 @@ namespace ImageSharp /// /// Packed pixel type containing four 16-bit floating-point values. /// - public struct HalfVector4 : IPackedPixel, IEquatable + public struct HalfVector4 : IPixel { /// /// The maximum byte value. @@ -46,7 +46,9 @@ namespace ImageSharp this.PackedValue = Pack(ref vector); } - /// + /// + /// Gets or sets the packed representation of the value. + /// public ulong PackedValue { get; set; } /// diff --git a/src/ImageSharp/Colors/PackedPixel/IPackedBytes.cs b/src/ImageSharp/Colors/PackedPixel/IPackedBytes.cs deleted file mode 100644 index 3343a92c73..0000000000 --- a/src/ImageSharp/Colors/PackedPixel/IPackedBytes.cs +++ /dev/null @@ -1,55 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - /// - /// An interface that converts packed vector types to and from values, - /// allowing multiple encodings to be manipulated in a generic manner. - /// - public interface IPackedBytes - { - /// - /// Sets the packed representation from the given byte array. - /// - /// The x-component. - /// The y-component. - /// The z-component. - /// The w-component. - void PackFromBytes(byte x, byte y, byte z, byte w); - - /// - /// Expands the packed representation into a given byte array. - /// Output is expanded to X-> Y-> Z order. Equivalent to R-> G-> B in - /// - /// The bytes to set the color in. - /// The starting index of the . - void ToXyzBytes(byte[] bytes, int startIndex); - - /// - /// Expands the packed representation into a given byte array. - /// Output is expanded to X-> Y-> Z-> W order. Equivalent to R-> G-> B-> A in - /// - /// The bytes to set the color in. - /// The starting index of the . - void ToXyzwBytes(byte[] bytes, int startIndex); - - /// - /// Expands the packed representation into a given byte array. - /// Output is expanded to Z-> Y-> X order. Equivalent to B-> G-> R in - /// - /// The bytes to set the color in. - /// The starting index of the . - void ToZyxBytes(byte[] bytes, int startIndex); - - /// - /// Expands the packed representation into a given byte array. - /// Output is expanded to Z-> Y-> X-> W order. Equivalent to B-> G-> R-> A in - /// - /// The bytes to set the color in. - /// The starting index of the . - void ToZyxwBytes(byte[] bytes, int startIndex); - } -} diff --git a/src/ImageSharp/Colors/PackedPixel/IPackedPixel.cs b/src/ImageSharp/Colors/PackedPixel/IPackedPixel.cs deleted file mode 100644 index f9b8dc0a23..0000000000 --- a/src/ImageSharp/Colors/PackedPixel/IPackedPixel.cs +++ /dev/null @@ -1,25 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - using System; - - /// - /// An interface that represents a generic packed pixel type. - /// - /// The packed format. uint, long, float. - public interface IPackedPixel : IPackedPixel, IPackedVector - where TPacked : struct, IEquatable - { - } - - /// - /// An interface that represents a packed pixel type. - /// - public interface IPackedPixel : IPackedVector, IPackedBytes - { - } -} \ No newline at end of file diff --git a/src/ImageSharp/Colors/PackedPixel/IPackedVector.cs b/src/ImageSharp/Colors/PackedPixel/IPackedVector.cs deleted file mode 100644 index 4d9a89712b..0000000000 --- a/src/ImageSharp/Colors/PackedPixel/IPackedVector.cs +++ /dev/null @@ -1,43 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - using System; - using System.Numerics; - - /// - /// An interface that converts packed vector types to and from values, - /// allowing multiple encodings to be manipulated in a generic manner. - /// - /// The packed format. uint, long, float. - public interface IPackedVector : IPackedVector - where TPacked : struct, IEquatable - { - /// - /// Gets or sets the packed representation of the value. - /// - TPacked PackedValue { get; set; } - } - - /// - /// An interface that converts packed vector types to and from values. - /// - public interface IPackedVector - { - /// - /// Sets the packed representation from a . - /// - /// The vector to create the packed representation from. - void PackFromVector4(Vector4 vector); - - /// - /// Expands the packed representation into a . - /// The vector components are typically expanded in least to greatest significance order. - /// - /// The . - Vector4 ToVector4(); - } -} \ No newline at end of file diff --git a/src/ImageSharp/Colors/PackedPixel/IPixel.cs b/src/ImageSharp/Colors/PackedPixel/IPixel.cs index a222edd9ac..1c3e20a7e4 100644 --- a/src/ImageSharp/Colors/PackedPixel/IPixel.cs +++ b/src/ImageSharp/Colors/PackedPixel/IPixel.cs @@ -1,8 +1,25 @@ -namespace ImageSharp +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp { using System; using System.Numerics; + /// + /// An interface that represents a generic pixel type. + /// + /// The type implementing this interface + public interface IPixel : IPixel, IEquatable + where TSelf : struct, IPixel + { + } + + /// + /// An interface that represents a pixel type. + /// public interface IPixel { /// @@ -59,10 +76,4 @@ /// The starting index of the . void ToZyxwBytes(byte[] bytes, int startIndex); } - - public interface IPixel : IPixel, IEquatable - where TSelf : struct, IPixel - { - - } } \ No newline at end of file diff --git a/src/ImageSharp/Colors/PackedPixel/NormalizedByte2.cs b/src/ImageSharp/Colors/PackedPixel/NormalizedByte2.cs index 5196cbfaf4..0567b44452 100644 --- a/src/ImageSharp/Colors/PackedPixel/NormalizedByte2.cs +++ b/src/ImageSharp/Colors/PackedPixel/NormalizedByte2.cs @@ -12,7 +12,7 @@ namespace ImageSharp /// /// Packed packed pixel type containing two 8-bit signed normalized values, ranging from −1 to 1. /// - public struct NormalizedByte2 : IPackedPixel, IEquatable + public struct NormalizedByte2 : IPixel { /// /// The maximum byte value. @@ -48,7 +48,9 @@ namespace ImageSharp this.PackedValue = Pack(x, y); } - /// + /// + /// Gets or sets the packed representation of the value. + /// public ushort PackedValue { get; set; } /// diff --git a/src/ImageSharp/Colors/PackedPixel/NormalizedByte4.cs b/src/ImageSharp/Colors/PackedPixel/NormalizedByte4.cs index c5de795e24..a45f601c4c 100644 --- a/src/ImageSharp/Colors/PackedPixel/NormalizedByte4.cs +++ b/src/ImageSharp/Colors/PackedPixel/NormalizedByte4.cs @@ -12,7 +12,7 @@ namespace ImageSharp /// /// Packed pixel type containing four 8-bit signed normalized values, ranging from −1 to 1. /// - public struct NormalizedByte4 : IPackedPixel, IEquatable + public struct NormalizedByte4 : IPixel { /// /// The maximum byte value. @@ -50,7 +50,9 @@ namespace ImageSharp this.PackedValue = Pack(x, y, z, w); } - /// + /// + /// Gets or sets the packed representation of the value. + /// public uint PackedValue { get; set; } /// diff --git a/src/ImageSharp/Colors/PackedPixel/NormalizedShort2.cs b/src/ImageSharp/Colors/PackedPixel/NormalizedShort2.cs index 8e35700472..3642aed5b6 100644 --- a/src/ImageSharp/Colors/PackedPixel/NormalizedShort2.cs +++ b/src/ImageSharp/Colors/PackedPixel/NormalizedShort2.cs @@ -12,7 +12,7 @@ namespace ImageSharp /// /// Packed pixel type containing two 16-bit signed normalized values, ranging from −1 to 1. /// - public struct NormalizedShort2 : IPackedPixel, IEquatable + public struct NormalizedShort2 : IPixel { /// /// The maximum byte value. @@ -48,7 +48,9 @@ namespace ImageSharp this.PackedValue = Pack(x, y); } - /// + /// + /// Gets or sets the packed representation of the value. + /// public uint PackedValue { get; set; } /// diff --git a/src/ImageSharp/Colors/PackedPixel/NormalizedShort4.cs b/src/ImageSharp/Colors/PackedPixel/NormalizedShort4.cs index 81d182cee3..d5a3de79e9 100644 --- a/src/ImageSharp/Colors/PackedPixel/NormalizedShort4.cs +++ b/src/ImageSharp/Colors/PackedPixel/NormalizedShort4.cs @@ -12,7 +12,7 @@ namespace ImageSharp /// /// Packed pixel type containing four 16-bit signed normalized values, ranging from −1 to 1. /// - public struct NormalizedShort4 : IPackedPixel, IEquatable + public struct NormalizedShort4 : IPixel { /// /// The maximum byte value. @@ -50,7 +50,9 @@ namespace ImageSharp this.PackedValue = Pack(x, y, z, w); } - /// + /// + /// Gets or sets the packed representation of the value. + /// public ulong PackedValue { get; set; } /// diff --git a/src/ImageSharp/Colors/PackedPixel/Rg32.cs b/src/ImageSharp/Colors/PackedPixel/Rg32.cs index f7dab68c3d..9bda5f6058 100644 --- a/src/ImageSharp/Colors/PackedPixel/Rg32.cs +++ b/src/ImageSharp/Colors/PackedPixel/Rg32.cs @@ -12,7 +12,7 @@ namespace ImageSharp /// /// Packed pixel type containing two 16-bit unsigned normalized values ranging from 0 to 1. /// - public struct Rg32 : IPackedPixel, IEquatable, IPackedVector + public struct Rg32 : IPixel { /// /// Initializes a new instance of the struct. @@ -33,7 +33,9 @@ namespace ImageSharp this.PackedValue = Pack(vector.X, vector.Y); } - /// + /// + /// Gets or sets the packed representation of the value. + /// public uint PackedValue { get; set; } /// diff --git a/src/ImageSharp/Colors/PackedPixel/Rgba1010102.cs b/src/ImageSharp/Colors/PackedPixel/Rgba1010102.cs index 8c22610287..69c097f964 100644 --- a/src/ImageSharp/Colors/PackedPixel/Rgba1010102.cs +++ b/src/ImageSharp/Colors/PackedPixel/Rgba1010102.cs @@ -13,7 +13,7 @@ namespace ImageSharp /// Packed vector type containing unsigned normalized values ranging from 0 to 1. /// The x, y and z components use 10 bits, and the w component uses 2 bits. /// - public struct Rgba1010102 : IPackedPixel, IEquatable, IPackedVector + public struct Rgba1010102 : IPixel { /// /// Initializes a new instance of the struct. @@ -36,7 +36,9 @@ namespace ImageSharp this.PackedValue = Pack(vector.X, vector.Y, vector.Z, vector.W); } - /// + /// + /// Gets or sets the packed representation of the value. + /// public uint PackedValue { get; set; } /// diff --git a/src/ImageSharp/Colors/PackedPixel/Rgba64.cs b/src/ImageSharp/Colors/PackedPixel/Rgba64.cs index bb21826498..4705564354 100644 --- a/src/ImageSharp/Colors/PackedPixel/Rgba64.cs +++ b/src/ImageSharp/Colors/PackedPixel/Rgba64.cs @@ -12,7 +12,7 @@ namespace ImageSharp /// /// Packed pixel type containing four 16-bit unsigned normalized values ranging from 0 to 1. /// - public struct Rgba64 : IPackedPixel, IEquatable, IPackedVector + public struct Rgba64 : IPixel { /// /// Initializes a new instance of the struct. @@ -35,7 +35,9 @@ namespace ImageSharp this.PackedValue = Pack(vector.X, vector.Y, vector.Z, vector.W); } - /// + /// + /// Gets or sets the packed representation of the value. + /// public ulong PackedValue { get; set; } /// diff --git a/src/ImageSharp/Colors/PackedPixel/Short2.cs b/src/ImageSharp/Colors/PackedPixel/Short2.cs index 6bfc5d40c2..3f83a4cd5d 100644 --- a/src/ImageSharp/Colors/PackedPixel/Short2.cs +++ b/src/ImageSharp/Colors/PackedPixel/Short2.cs @@ -12,7 +12,7 @@ namespace ImageSharp /// /// Packed pixel type containing two 16-bit signed integer values. /// - public struct Short2 : IPackedPixel, IEquatable + public struct Short2 : IPixel { /// /// The maximum byte value. @@ -48,7 +48,9 @@ namespace ImageSharp this.PackedValue = Pack(x, y); } - /// + /// + /// Gets or sets the packed representation of the value. + /// public uint PackedValue { get; set; } /// diff --git a/src/ImageSharp/Colors/PackedPixel/Short4.cs b/src/ImageSharp/Colors/PackedPixel/Short4.cs index 4b1f3c2533..1696cb40a4 100644 --- a/src/ImageSharp/Colors/PackedPixel/Short4.cs +++ b/src/ImageSharp/Colors/PackedPixel/Short4.cs @@ -12,7 +12,7 @@ namespace ImageSharp /// /// Packed pixel type containing four 16-bit signed integer values. /// - public struct Short4 : IPackedPixel, IEquatable + public struct Short4 : IPixel { /// /// The maximum byte value. @@ -50,7 +50,9 @@ namespace ImageSharp this.PackedValue = Pack(x, y, z, w); } - /// + /// + /// Gets or sets the packed representation of the value. + /// public ulong PackedValue { get; set; } /// diff --git a/src/ImageSharp/Common/Extensions/ArrayExtensions.cs b/src/ImageSharp/Common/Extensions/ArrayExtensions.cs index be3d3f1dc7..cce442c522 100644 --- a/src/ImageSharp/Common/Extensions/ArrayExtensions.cs +++ b/src/ImageSharp/Common/Extensions/ArrayExtensions.cs @@ -21,7 +21,7 @@ namespace ImageSharp /// The height of the image represented by the pixel buffer. /// The public static PixelAccessor Lock(this TColor[] pixels, int width, int height) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return new PixelAccessor(width, height, pixels); } diff --git a/src/ImageSharp/Common/Helpers/ImageMaths.cs b/src/ImageSharp/Common/Helpers/ImageMaths.cs index 5720d82186..b59cf54f52 100644 --- a/src/ImageSharp/Common/Helpers/ImageMaths.cs +++ b/src/ImageSharp/Common/Helpers/ImageMaths.cs @@ -183,7 +183,7 @@ namespace ImageSharp /// The . /// public static Rectangle GetFilteredBoundingRectangle(ImageBase bitmap, float componentValue, RgbaComponent channel = RgbaComponent.B) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { int width = bitmap.Width; int height = bitmap.Height; diff --git a/src/ImageSharp/Common/Helpers/ThrowHelper.cs b/src/ImageSharp/Common/Helpers/ThrowHelper.cs index a832645334..97d75717a9 100644 --- a/src/ImageSharp/Common/Helpers/ThrowHelper.cs +++ b/src/ImageSharp/Common/Helpers/ThrowHelper.cs @@ -1,3 +1,8 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + namespace ImageSharp { using System; @@ -8,6 +13,10 @@ namespace ImageSharp /// internal static class ThrowHelper { + /// + /// Throws an + /// + /// The parameter name [MethodImpl(MethodImplOptions.NoInlining)] public static void ThrowArgumentNullException(string paramName) { diff --git a/src/ImageSharp/Common/Memory/ArrayPointer{T}.cs b/src/ImageSharp/Common/Memory/ArrayPointer{T}.cs index 06de061078..95d2e51e66 100644 --- a/src/ImageSharp/Common/Memory/ArrayPointer{T}.cs +++ b/src/ImageSharp/Common/Memory/ArrayPointer{T}.cs @@ -1,18 +1,33 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + namespace ImageSharp { using System; using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; /// - /// Provides access to elements in an array from a given position. - /// This struct shares many similarities with corefx System.Span<T> but there are differences in functionalities and semantics: + /// Provides access to elements in an array from a given position. + /// This type shares many similarities with corefx System.Span<T> but there are significant differences in it's functionalities and semantics: /// - It's not possible to use it with stack objects or pointers to unmanaged memory, only with managed arrays + /// - It's possible to retrieve a reference to the array () so we can pass it to API-s like /// - There is no bounds checking for performance reasons. Therefore we don't need to store length. (However this could be added as DEBUG-only feature.) + /// This makes an unsafe type! /// - Currently the arrays provided to ArrayPointer need to be pinned. This behaviour could be changed using C#7 features. /// + /// The type of elements of the array internal unsafe struct ArrayPointer where T : struct { + /// + /// Initializes a new instance of the struct from a pinned array and an offset. + /// + /// The pinned array + /// Pointer to the beginning of array + /// The offset inside the array [MethodImpl(MethodImplOptions.AggressiveInlining)] public ArrayPointer(T[] array, void* pointerToArray, int offset) { @@ -21,11 +36,17 @@ namespace ImageSharp { ThrowHelper.ThrowArgumentNullException(nameof(array)); } + this.Array = array; this.Offset = offset; - this.PointerAtOffset = (IntPtr)pointerToArray + Unsafe.SizeOf()*offset; + this.PointerAtOffset = (IntPtr)pointerToArray + (Unsafe.SizeOf() * offset); } + /// + /// Initializes a new instance of the struct from a pinned array. + /// + /// The pinned array + /// Pointer to the start of 'array' [MethodImpl(MethodImplOptions.AggressiveInlining)] public ArrayPointer(T[] array, void* pointerToArray) { @@ -34,23 +55,38 @@ namespace ImageSharp { ThrowHelper.ThrowArgumentNullException(nameof(array)); } + this.Array = array; this.Offset = 0; this.PointerAtOffset = (IntPtr)pointerToArray; } + /// + /// Gets the array + /// public T[] Array { get; private set; } + /// + /// Gets the offset inside + /// public int Offset { get; private set; } + /// + /// Gets the pointer to the offseted array position + /// public IntPtr PointerAtOffset { get; private set; } + /// + /// Forms a slice out of the given ArrayPointer, beginning at 'offset'. + /// + /// The offset in number of elements + /// The offseted (sliced) ArrayPointer public ArrayPointer Slice(int offset) { ArrayPointer result = default(ArrayPointer); result.Array = this.Array; result.Offset = this.Offset + offset; - result.PointerAtOffset = this.PointerAtOffset + Unsafe.SizeOf() * offset; + result.PointerAtOffset = this.PointerAtOffset + (Unsafe.SizeOf() * offset); return result; } } diff --git a/src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuser.cs b/src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuser.cs index 4fd5476f05..20a45d4df6 100644 --- a/src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuser.cs +++ b/src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuser.cs @@ -68,7 +68,7 @@ namespace ImageSharp.Dithering /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Dither(PixelAccessor pixels, TColor source, TColor transformed, int x, int y, int width, int height) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { // Assign the transformed pixel to the array. pixels[x, y] = transformed; diff --git a/src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs b/src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs index fbee27088e..4fb31c13e4 100644 --- a/src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs +++ b/src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs @@ -29,6 +29,6 @@ namespace ImageSharp.Dithering /// The image height. /// The pixel format. void Dither(PixelAccessor pixels, TColor source, TColor transformed, int x, int y, int width, int height) - where TColor : struct, IPackedPixel, IEquatable; + where TColor : struct, IPixel; } } diff --git a/src/ImageSharp/Dithering/Ordered/Bayer.cs b/src/ImageSharp/Dithering/Ordered/Bayer.cs index 3d3d900233..95545f46bc 100644 --- a/src/ImageSharp/Dithering/Ordered/Bayer.cs +++ b/src/ImageSharp/Dithering/Ordered/Bayer.cs @@ -31,7 +31,7 @@ namespace ImageSharp.Dithering.Ordered /// public void Dither(PixelAccessor pixels, TColor source, TColor upper, TColor lower, byte[] bytes, int index, int x, int y, int width, int height) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { source.ToXyzwBytes(bytes, 0); pixels[x, y] = ThresholdMatrix[y % 3, x % 3] >= bytes[index] ? lower : upper; diff --git a/src/ImageSharp/Dithering/Ordered/IOrderedDither.cs b/src/ImageSharp/Dithering/Ordered/IOrderedDither.cs index e74a863a51..162cdb6a18 100644 --- a/src/ImageSharp/Dithering/Ordered/IOrderedDither.cs +++ b/src/ImageSharp/Dithering/Ordered/IOrderedDither.cs @@ -32,6 +32,6 @@ namespace ImageSharp.Dithering /// The image height. /// The pixel format. void Dither(PixelAccessor pixels, TColor source, TColor upper, TColor lower, byte[] bytes, int index, int x, int y, int width, int height) - where TColor : struct, IPackedPixel, IEquatable; + where TColor : struct, IPixel; } } diff --git a/src/ImageSharp/Dithering/Ordered/Ordered.cs b/src/ImageSharp/Dithering/Ordered/Ordered.cs index bb1f21060d..df48a31679 100644 --- a/src/ImageSharp/Dithering/Ordered/Ordered.cs +++ b/src/ImageSharp/Dithering/Ordered/Ordered.cs @@ -31,7 +31,7 @@ namespace ImageSharp.Dithering.Ordered /// public void Dither(PixelAccessor pixels, TColor source, TColor upper, TColor lower, byte[] bytes, int index, int x, int y, int width, int height) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { source.ToXyzwBytes(bytes, 0); pixels[x, y] = ThresholdMatrix[y % 3, x % 3] >= bytes[index] ? lower : upper; diff --git a/src/ImageSharp/Formats/IImageDecoder.cs b/src/ImageSharp/Formats/IImageDecoder.cs index d723b82f0a..28bda7837e 100644 --- a/src/ImageSharp/Formats/IImageDecoder.cs +++ b/src/ImageSharp/Formats/IImageDecoder.cs @@ -20,6 +20,6 @@ namespace ImageSharp.Formats /// The to decode to. /// The containing image data. void Decode(Image image, Stream stream) - where TColor : struct, IPackedPixel, IEquatable; + where TColor : struct, IPixel; } } diff --git a/src/ImageSharp/Formats/IImageEncoder.cs b/src/ImageSharp/Formats/IImageEncoder.cs index edde5347e5..0ba56477ac 100644 --- a/src/ImageSharp/Formats/IImageEncoder.cs +++ b/src/ImageSharp/Formats/IImageEncoder.cs @@ -20,6 +20,6 @@ namespace ImageSharp.Formats /// The to encode from. /// The to encode the image data to. void Encode(Image image, Stream stream) - where TColor : struct, IPackedPixel, IEquatable; + where TColor : struct, IPixel; } } diff --git a/src/ImageSharp/Image/IImageBase{TColor}.cs b/src/ImageSharp/Image/IImageBase{TColor}.cs index f01a4b7028..e894fba4a8 100644 --- a/src/ImageSharp/Image/IImageBase{TColor}.cs +++ b/src/ImageSharp/Image/IImageBase{TColor}.cs @@ -12,7 +12,7 @@ namespace ImageSharp /// /// The pixel format. public interface IImageBase : IImageBase, IDisposable - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Gets the pixels as an array of the given packed pixel format. diff --git a/src/ImageSharp/Image/IImageProcessor.cs b/src/ImageSharp/Image/IImageProcessor.cs index fae0c23b8a..0440cdd766 100644 --- a/src/ImageSharp/Image/IImageProcessor.cs +++ b/src/ImageSharp/Image/IImageProcessor.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Processing /// /// The pixel format. public interface IImageProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Gets or sets the parallel options for processing tasks in parallel. diff --git a/src/ImageSharp/Image/ImageBase{TColor}.cs b/src/ImageSharp/Image/ImageBase{TColor}.cs index fd82f77308..f530c0ccf4 100644 --- a/src/ImageSharp/Image/ImageBase{TColor}.cs +++ b/src/ImageSharp/Image/ImageBase{TColor}.cs @@ -16,7 +16,7 @@ namespace ImageSharp /// The pixel format. [DebuggerDisplay("Image: {Width}x{Height}")] public abstract class ImageBase : IImageBase - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// The image pixels diff --git a/src/ImageSharp/Image/ImageFrame{TColor}.cs b/src/ImageSharp/Image/ImageFrame{TColor}.cs index 02e5b71618..2712dc6878 100644 --- a/src/ImageSharp/Image/ImageFrame{TColor}.cs +++ b/src/ImageSharp/Image/ImageFrame{TColor}.cs @@ -14,7 +14,7 @@ namespace ImageSharp /// /// The pixel format. public class ImageFrame : ImageBase, IImageFrame - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Initializes a new instance of the class. @@ -56,7 +56,7 @@ namespace ImageSharp /// The pixel format. /// The public ImageFrame To(Func scaleFunc = null) - where TColor2 : struct, IPackedPixel, IEquatable + where TColor2 : struct, IPixel { scaleFunc = PackedPixelConverterHelper.ComputeScaleFunction(scaleFunc); diff --git a/src/ImageSharp/Image/ImageProcessingExtensions.cs b/src/ImageSharp/Image/ImageProcessingExtensions.cs index db8fb4baa7..ff3ecd5eef 100644 --- a/src/ImageSharp/Image/ImageProcessingExtensions.cs +++ b/src/ImageSharp/Image/ImageProcessingExtensions.cs @@ -22,7 +22,7 @@ namespace ImageSharp /// The processor to apply to the image. /// The . public static Image Apply(this Image source, IImageProcessor processor) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { source.ApplyProcessor(processor, source.Bounds); return source; diff --git a/src/ImageSharp/Image/Image{TColor}.cs b/src/ImageSharp/Image/Image{TColor}.cs index 33d6a69655..bbd839d168 100644 --- a/src/ImageSharp/Image/Image{TColor}.cs +++ b/src/ImageSharp/Image/Image{TColor}.cs @@ -24,7 +24,7 @@ namespace ImageSharp /// The pixel format. [DebuggerDisplay("Image: {Width}x{Height}")] public class Image : ImageBase, IImage - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Initializes a new instance of the class @@ -325,7 +325,7 @@ namespace ImageSharp /// The pixel format. /// The public Image To(Func scaleFunc = null) - where TColor2 : struct, IPackedPixel, IEquatable + where TColor2 : struct, IPixel { scaleFunc = PackedPixelConverterHelper.ComputeScaleFunction(scaleFunc); diff --git a/src/ImageSharp/Image/PixelAccessor{TColor}.cs b/src/ImageSharp/Image/PixelAccessor{TColor}.cs index 55b16235a5..338f491827 100644 --- a/src/ImageSharp/Image/PixelAccessor{TColor}.cs +++ b/src/ImageSharp/Image/PixelAccessor{TColor}.cs @@ -16,7 +16,7 @@ namespace ImageSharp /// /// The pixel format. public unsafe class PixelAccessor : IDisposable - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// The pointer to the pixel buffer. diff --git a/src/ImageSharp/Image/PixelArea{TColor}.cs b/src/ImageSharp/Image/PixelArea{TColor}.cs index 99e9273751..77b648ca56 100644 --- a/src/ImageSharp/Image/PixelArea{TColor}.cs +++ b/src/ImageSharp/Image/PixelArea{TColor}.cs @@ -16,7 +16,7 @@ namespace ImageSharp /// /// The pixel format. public sealed unsafe class PixelArea : IDisposable - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// True if was rented from by the constructor diff --git a/src/ImageSharp/Image/PixelPool{TColor}.cs b/src/ImageSharp/Image/PixelPool{TColor}.cs index 1f33926214..8193600daf 100644 --- a/src/ImageSharp/Image/PixelPool{TColor}.cs +++ b/src/ImageSharp/Image/PixelPool{TColor}.cs @@ -13,7 +13,7 @@ namespace ImageSharp /// /// The pixel format. public static class PixelPool - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// The used to pool data. TODO: Choose sensible default size and count diff --git a/src/ImageSharp/ImageProcessor.cs b/src/ImageSharp/ImageProcessor.cs index 79bc3ee1e6..a0766d1edf 100644 --- a/src/ImageSharp/ImageProcessor.cs +++ b/src/ImageSharp/ImageProcessor.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Processing /// /// The pixel format. public abstract class ImageProcessor : IImageProcessor - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// public virtual ParallelOptions ParallelOptions { get; set; } diff --git a/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs index 3bd5ef3cb1..b363286b00 100644 --- a/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs +++ b/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs @@ -121,7 +121,7 @@ namespace ImageSharp /// The . /// public Image CreateThumbnail() - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { this.InitializeValues(); diff --git a/src/ImageSharp/Quantizers/IQuantizer.cs b/src/ImageSharp/Quantizers/IQuantizer.cs index ef2f0bb98e..9ee3072666 100644 --- a/src/ImageSharp/Quantizers/IQuantizer.cs +++ b/src/ImageSharp/Quantizers/IQuantizer.cs @@ -14,7 +14,7 @@ namespace ImageSharp.Quantizers /// /// The pixel format. public interface IQuantizer : IQuantizer - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Quantize an image and return the resulting output pixels. @@ -32,7 +32,7 @@ namespace ImageSharp.Quantizers /// /// The pixel format. public interface IDitheredQuantizer : IQuantizer - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Gets or sets a value indicating whether to apply dithering to the output image. diff --git a/src/ImageSharp/Quantizers/Octree/OctreeQuantizer.cs b/src/ImageSharp/Quantizers/Octree/OctreeQuantizer.cs index c0aab00ed6..c047e1af48 100644 --- a/src/ImageSharp/Quantizers/Octree/OctreeQuantizer.cs +++ b/src/ImageSharp/Quantizers/Octree/OctreeQuantizer.cs @@ -14,7 +14,7 @@ namespace ImageSharp.Quantizers /// /// The pixel format. public sealed class OctreeQuantizer : Quantizer - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// The pixel buffer, used to reduce allocations. diff --git a/src/ImageSharp/Quantizers/Octree/Quantizer.cs b/src/ImageSharp/Quantizers/Octree/Quantizer.cs index 87705de94d..8af638de35 100644 --- a/src/ImageSharp/Quantizers/Octree/Quantizer.cs +++ b/src/ImageSharp/Quantizers/Octree/Quantizer.cs @@ -17,7 +17,7 @@ namespace ImageSharp.Quantizers /// /// The pixel format. public abstract class Quantizer : IDitheredQuantizer - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// A lookup table for colors diff --git a/src/ImageSharp/Quantizers/Palette/PaletteQuantizer.cs b/src/ImageSharp/Quantizers/Palette/PaletteQuantizer.cs index e55d670375..19f10aabb0 100644 --- a/src/ImageSharp/Quantizers/Palette/PaletteQuantizer.cs +++ b/src/ImageSharp/Quantizers/Palette/PaletteQuantizer.cs @@ -14,7 +14,7 @@ namespace ImageSharp.Quantizers /// /// The pixel format. public sealed class PaletteQuantizer : Quantizer - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// The pixel buffer, used to reduce allocations. diff --git a/src/ImageSharp/Quantizers/Quantize.cs b/src/ImageSharp/Quantizers/Quantize.cs index 8a26fb4bad..f45cd3f799 100644 --- a/src/ImageSharp/Quantizers/Quantize.cs +++ b/src/ImageSharp/Quantizers/Quantize.cs @@ -24,7 +24,7 @@ namespace ImageSharp /// The maximum number of colors to return. Defaults to 256. /// The . public static Image Quantize(this Image source, Quantization mode = Quantization.Octree, int maxColors = 256) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { IQuantizer quantizer; switch (mode) @@ -54,7 +54,7 @@ namespace ImageSharp /// The maximum number of colors to return. /// The . public static Image Quantize(this Image source, IQuantizer quantizer, int maxColors) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { QuantizedImage quantized = quantizer.Quantize(source, maxColors); int palleteCount = quantized.Palette.Length - 1; diff --git a/src/ImageSharp/Quantizers/QuantizedImage.cs b/src/ImageSharp/Quantizers/QuantizedImage.cs index cc740f9be1..528da77172 100644 --- a/src/ImageSharp/Quantizers/QuantizedImage.cs +++ b/src/ImageSharp/Quantizers/QuantizedImage.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Quantizers /// /// The pixel format. public class QuantizedImage - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp/Quantizers/Wu/WuQuantizer.cs b/src/ImageSharp/Quantizers/Wu/WuQuantizer.cs index 55743f81e2..d632cdd73b 100644 --- a/src/ImageSharp/Quantizers/Wu/WuQuantizer.cs +++ b/src/ImageSharp/Quantizers/Wu/WuQuantizer.cs @@ -31,7 +31,7 @@ namespace ImageSharp.Quantizers /// /// The pixel format. public sealed class WuQuantizer : IQuantizer - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { /// /// The index bits. diff --git a/tests/ImageSharp.Tests/Colors/ColorConstructorTests.cs b/tests/ImageSharp.Tests/Colors/ColorConstructorTests.cs index ac1c3f3516..08b9375f82 100644 --- a/tests/ImageSharp.Tests/Colors/ColorConstructorTests.cs +++ b/tests/ImageSharp.Tests/Colors/ColorConstructorTests.cs @@ -132,7 +132,7 @@ namespace ImageSharp.Tests.Colors [MemberData(nameof(Vector3Data))] [MemberData(nameof(Float4Data))] [MemberData(nameof(Float3Data))] - public void ConstructorToVector4(IPackedVector packedVector, float[] expectedVector4Components) + public void ConstructorToVector4(IPixel packedVector, float[] expectedVector4Components) { // Arrange var precision = 2; diff --git a/tests/ImageSharp.Tests/Colors/ColorPackingTests.cs b/tests/ImageSharp.Tests/Colors/ColorPackingTests.cs index a1057a2c26..ac5f8d6b46 100644 --- a/tests/ImageSharp.Tests/Colors/ColorPackingTests.cs +++ b/tests/ImageSharp.Tests/Colors/ColorPackingTests.cs @@ -69,7 +69,7 @@ namespace ImageSharp.Tests.Colors [Theory] [MemberData(nameof(Vector4PackData))] [MemberData(nameof(Vector3PackData))] - public void FromVector4ToVector4(IPackedVector packedVector, float[] vector4ComponentsToPack) + public void FromVector4ToVector4(IPixel packedVector, float[] vector4ComponentsToPack) { // Arrange var precision = 2; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/BadEofJpegTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/BadEofJpegTests.cs index adc568c824..8dbdb998c5 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/BadEofJpegTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/BadEofJpegTests.cs @@ -29,7 +29,7 @@ namespace ImageSharp.Tests [Theory] [WithFile(TestImages.Jpeg.Baseline.Bad.MissingEOF, PixelTypes.Color)] public void LoadBaselineImage(TestImageProvider provider) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { using (Image image = provider.GetImage()) { @@ -41,7 +41,7 @@ namespace ImageSharp.Tests [Theory] // TODO: #18 [WithFile(TestImages.Jpeg.Progressive.Bad.BadEOF, PixelTypes.Color)] public void LoadProgressiveImage(TestImageProvider provider) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { using (Image image = provider.GetImage()) { diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index 8bce4c4e8d..71ce4e1652 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -27,7 +27,7 @@ namespace ImageSharp.Tests [Theory] [WithFileCollection(nameof(BaselineTestJpegs), PixelTypes.Color | PixelTypes.StandardImageClass | PixelTypes.Argb)] public void OpenBaselineJpeg_SaveBmp(TestImageProvider provider) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { using (Image image = provider.GetImage()) { @@ -38,7 +38,7 @@ namespace ImageSharp.Tests [Theory] [WithFileCollection(nameof(ProgressiveTestJpegs), PixelTypes.Color | PixelTypes.StandardImageClass | PixelTypes.Argb)] public void OpenProgressiveJpeg_SaveBmp(TestImageProvider provider) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { using (Image image = provider.GetImage()) { @@ -56,7 +56,7 @@ namespace ImageSharp.Tests TestImageProvider provider, JpegSubsample subsample, int quality) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { byte[] data; using (Image image = provider.GetImage()) @@ -80,7 +80,7 @@ namespace ImageSharp.Tests [WithSolidFilledImages(42, 88, 255, 0, 0, PixelTypes.StandardImageClass)] public void DecodeGenerated_MetadataOnly( TestImageProvider provider) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { using (Image image = provider.GetImage()) { diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs index ef6671931f..6518c3e6b6 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs @@ -32,7 +32,7 @@ namespace ImageSharp.Tests [WithFile(TestImages.Jpeg.Baseline.Snake, PixelTypes.StandardImageClass, 75, JpegSubsample.Ratio444)] [WithFile(TestImages.Jpeg.Baseline.Lake, PixelTypes.StandardImageClass, 75, JpegSubsample.Ratio444)] public void LoadResizeSave(TestImageProvider provider, int quality, JpegSubsample subsample) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { using (Image image = provider.GetImage().Resize(new ResizeOptions { Size = new Size(150, 100), Mode = ResizeMode.Max })) { @@ -50,7 +50,7 @@ namespace ImageSharp.Tests [WithFileCollection(nameof(AllBmpFiles), PixelTypes.Color | PixelTypes.StandardImageClass | PixelTypes.Argb, JpegSubsample.Ratio420, 75)] [WithFileCollection(nameof(AllBmpFiles), PixelTypes.Color | PixelTypes.StandardImageClass | PixelTypes.Argb, JpegSubsample.Ratio444, 75)] public void OpenBmp_SaveJpeg(TestImageProvider provider, JpegSubsample subSample, int quality) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { using (Image image = provider.GetImage()) { diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegUtilsTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegUtilsTests.cs index 0956131b14..d0a7fae333 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegUtilsTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegUtilsTests.cs @@ -16,7 +16,7 @@ namespace ImageSharp.Tests public class JpegUtilsTests : TestBase { public static Image CreateTestImage(GenericFactory factory) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { Image image = factory.CreateImage(10, 10); using (PixelAccessor pixels = image.Lock()) @@ -41,7 +41,7 @@ namespace ImageSharp.Tests [Theory] [WithMemberFactory(nameof(CreateTestImage), PixelTypes.Color | PixelTypes.StandardImageClass | PixelTypes.Argb)] public void CopyStretchedRGBTo_FromOrigo(TestImageProvider provider) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { using (Image src = provider.GetImage()) using (Image dest = provider.Factory.CreateImage(8, 8)) @@ -63,7 +63,7 @@ namespace ImageSharp.Tests [Theory] [WithMemberFactory(nameof(CreateTestImage), PixelTypes.Color | PixelTypes.StandardImageClass | PixelTypes.Argb)] public void CopyStretchedRGBTo_WithOffset(TestImageProvider provider) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { using (Image src = provider.GetImage()) using (PixelArea area = new PixelArea(8, 8, ComponentOrder.Xyz)) diff --git a/tests/ImageSharp.Tests/Image/PixelAccessorTests.cs b/tests/ImageSharp.Tests/Image/PixelAccessorTests.cs index 46f667e703..f3cd20f45c 100644 --- a/tests/ImageSharp.Tests/Image/PixelAccessorTests.cs +++ b/tests/ImageSharp.Tests/Image/PixelAccessorTests.cs @@ -16,7 +16,7 @@ namespace ImageSharp.Tests public class PixelAccessorTests { public static Image CreateTestImage(GenericFactory factory) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { Image image = factory.CreateImage(10, 10); using (PixelAccessor pixels = image.Lock()) @@ -44,7 +44,7 @@ namespace ImageSharp.Tests [WithMemberFactory(nameof(CreateTestImage), PixelTypes.All, ComponentOrder.Xyzw)] [WithMemberFactory(nameof(CreateTestImage), PixelTypes.All, ComponentOrder.Zyxw)] public void CopyTo_Then_CopyFrom_OnFullImageRect(TestImageProvider provider, ComponentOrder order) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { using (Image src = provider.GetImage()) { @@ -70,7 +70,7 @@ namespace ImageSharp.Tests // TODO: Need a processor in the library with this signature private static void Fill(Image image, Rectangle region, TColor color) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { using (PixelAccessor pixels = image.Lock()) { @@ -90,7 +90,7 @@ namespace ImageSharp.Tests [WithBlankImages(16, 16, PixelTypes.All, ComponentOrder.Xyzw)] [WithBlankImages(16, 16, PixelTypes.All, ComponentOrder.Zyxw)] public void CopyToThenCopyFromWithOffset(TestImageProvider provider, ComponentOrder order) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { using (Image destImage = new Image(8, 8)) { @@ -195,7 +195,7 @@ namespace ImageSharp.Tests } private static void CopyFromZYX(Image image) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { using (PixelAccessor pixels = image.Lock()) { @@ -222,7 +222,7 @@ namespace ImageSharp.Tests } private static void CopyFromZYXW(Image image) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { using (PixelAccessor pixels = image.Lock()) { @@ -250,7 +250,7 @@ namespace ImageSharp.Tests } private static void CopyToZYX(Image image) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { using (PixelAccessor pixels = image.Lock()) { @@ -272,7 +272,7 @@ namespace ImageSharp.Tests } private static void CopyToZYXW(Image image) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { using (PixelAccessor pixels = image.Lock()) { diff --git a/tests/ImageSharp.Tests/TestUtilities/Factories/GenericFactory.cs b/tests/ImageSharp.Tests/TestUtilities/Factories/GenericFactory.cs index c2f0aed84e..87b7ace6ae 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Factories/GenericFactory.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Factories/GenericFactory.cs @@ -12,7 +12,7 @@ namespace ImageSharp.Tests /// Used as parameter for -based factory methods /// public class GenericFactory - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { public virtual Image CreateImage(int width, int height) { diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BlankProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BlankProvider.cs index c14f56588a..ad4d2cc986 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BlankProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BlankProvider.cs @@ -8,7 +8,7 @@ namespace ImageSharp.Tests using System; public abstract partial class TestImageProvider - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { private class BlankProvider : TestImageProvider { diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs index 1b8c1498ef..cd403caed3 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs @@ -9,7 +9,7 @@ namespace ImageSharp.Tests using System.Collections.Concurrent; public abstract partial class TestImageProvider - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { private class FileProvider : TestImageProvider { diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/LambdaProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/LambdaProvider.cs index c8bc705f1c..9addc8ca6c 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/LambdaProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/LambdaProvider.cs @@ -12,7 +12,7 @@ namespace ImageSharp.Tests /// /// The pixel format of the image public abstract partial class TestImageProvider - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { private class LambdaProvider : TestImageProvider { diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/SolidProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/SolidProvider.cs index f7d3a0bf8d..855374f552 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/SolidProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/SolidProvider.cs @@ -12,7 +12,7 @@ namespace ImageSharp.Tests /// /// The pixel format of the image public abstract partial class TestImageProvider - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { private class SolidProvider : BlankProvider { diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs index 911719afaf..cdb31ab69a 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Tests /// /// The pixel format of the image public abstract partial class TestImageProvider - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { public PixelTypes PixelType { get; private set; } = typeof(TColor).GetPixelType(); diff --git a/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs b/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs index 8eb658073d..54be62d37d 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs @@ -76,7 +76,7 @@ namespace ImageSharp.Tests /// The requested extension /// Optional encoder public void SaveTestOutputFile(Image image, string extension = null, IImageEncoder encoder = null) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { string path = this.GetTestOutputFileName(extension); diff --git a/tests/ImageSharp.Tests/TestUtilities/TestUtilityExtensions.cs b/tests/ImageSharp.Tests/TestUtilities/TestUtilityExtensions.cs index d2726c16a5..748bd52938 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestUtilityExtensions.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestUtilityExtensions.cs @@ -57,7 +57,7 @@ namespace ImageSharp.Tests public static bool HasFlag(this PixelTypes pixelTypes, PixelTypes flag) => (pixelTypes & flag) == flag; public static bool IsEquivalentTo(this Image a, Image b, bool compareAlpha = true) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { if (a.Width != b.Width || a.Height != b.Height) { diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs index 2f5ec6c28d..09c81b7610 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs @@ -22,7 +22,7 @@ namespace ImageSharp.Tests [Theory] [WithBlankImages(42, 666, PixelTypes.Color | PixelTypes.Argb | PixelTypes.HalfSingle, "hello")] public void Use_WithEmptyImageAttribute(TestImageProvider provider, string message) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { var img = provider.GetImage(); @@ -36,7 +36,7 @@ namespace ImageSharp.Tests public void Use_WithBlankImagesAttribute_WithAllPixelTypes( TestImageProvider provider, string message) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { var img = provider.GetImage(); @@ -50,7 +50,7 @@ namespace ImageSharp.Tests [WithBlankImages(1, 1, PixelTypes.Alpha8, PixelTypes.Alpha8)] [WithBlankImages(1, 1, PixelTypes.StandardImageClass, PixelTypes.StandardImageClass)] public void PixelType_PropertyValueIsCorrect(TestImageProvider provider, PixelTypes expected) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { Assert.Equal(expected, provider.PixelType); } @@ -60,7 +60,7 @@ namespace ImageSharp.Tests [WithFile(TestImages.Bmp.F, PixelTypes.StandardImageClass)] public void PixelTypes_ColorWithDefaultImageClass_TriggersCreatingTheNonGenericDerivedImageClass( TestImageProvider provider) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { var img = provider.GetImage(); @@ -71,7 +71,7 @@ namespace ImageSharp.Tests [WithFile(TestImages.Bmp.Car, PixelTypes.All, 88)] [WithFile(TestImages.Bmp.F, PixelTypes.All, 88)] public void Use_WithFileAttribute(TestImageProvider provider, int yo) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { Assert.NotNull(provider.Utility.SourceFileOrDescription); var img = provider.GetImage(); @@ -88,7 +88,7 @@ namespace ImageSharp.Tests [Theory] [WithFileCollection(nameof(AllBmpFiles), PixelTypes.Color | PixelTypes.Argb)] public void Use_WithFileCollection(TestImageProvider provider) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { Assert.NotNull(provider.Utility.SourceFileOrDescription); var image = provider.GetImage(); @@ -98,7 +98,7 @@ namespace ImageSharp.Tests [Theory] [WithSolidFilledImages(10, 20, 255, 100, 50, 200, PixelTypes.Color | PixelTypes.Argb)] public void Use_WithSolidFilledImagesAttribute(TestImageProvider provider) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { var img = provider.GetImage(); Assert.Equal(img.Width, 10); @@ -130,7 +130,7 @@ namespace ImageSharp.Tests /// /// public static Image CreateTestImage(GenericFactory factory) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { return factory.CreateImage(3, 3); } @@ -138,7 +138,7 @@ namespace ImageSharp.Tests [Theory] [WithMemberFactory(nameof(CreateTestImage), PixelTypes.All)] public void Use_WithMemberFactoryAttribute(TestImageProvider provider) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { var img = provider.GetImage(); Assert.Equal(img.Width, 3); @@ -160,7 +160,7 @@ namespace ImageSharp.Tests [Theory] [MemberData(nameof(BasicData))] public void Blank_MemberData(TestImageProvider provider) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { var img = provider.GetImage(); @@ -178,7 +178,7 @@ namespace ImageSharp.Tests [Theory] [MemberData(nameof(FileData))] public void File_MemberData(TestImageProvider provider) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { this.Output.WriteLine("SRC: " + provider.Utility.SourceFileOrDescription); this.Output.WriteLine("OUT: " + provider.Utility.GetTestOutputFileName()); diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/TestUtilityExtensionsTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/TestUtilityExtensionsTests.cs index 23a42c1a68..ad35cb80f1 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestUtilityExtensionsTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestUtilityExtensionsTests.cs @@ -24,7 +24,7 @@ namespace ImageSharp.Tests private ITestOutputHelper Output { get; } public static Image CreateTestImage(GenericFactory factory) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { Image image = factory.CreateImage(10, 10); @@ -70,7 +70,7 @@ namespace ImageSharp.Tests [WithFile(TestImages.Bmp.Car, PixelTypes.Color, true)] [WithFile(TestImages.Bmp.Car, PixelTypes.Color, false)] public void IsEquivalentTo_WhenFalse(TestImageProvider provider, bool compareAlpha) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { var a = provider.GetImage(); var b = provider.GetImage(); @@ -83,7 +83,7 @@ namespace ImageSharp.Tests [WithMemberFactory(nameof(CreateTestImage), PixelTypes.Color | PixelTypes.Bgr565, true)] [WithMemberFactory(nameof(CreateTestImage), PixelTypes.Color | PixelTypes.Bgr565, false)] public void IsEquivalentTo_WhenTrue(TestImageProvider provider, bool compareAlpha) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { var a = provider.GetImage(); var b = provider.GetImage(); From 4501ca5fb4099f1ddaba19e140aed88c7942924f Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 18 Feb 2017 17:45:21 +0100 Subject: [PATCH 079/142] fix --- global.json | 2 +- tests/ImageSharp.Tests/Common/ArrayPointerTests.cs | 2 ++ .../TestUtilities/TestUtilityExtensions.cs | 9 --------- .../TestUtilities/Tests/TestUtilityExtensionsTests.cs | 10 +--------- 4 files changed, 4 insertions(+), 19 deletions(-) diff --git a/global.json b/global.json index 6da79b6116..7346bdc280 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "projects": [ "src" ], "sdk": { - "version": "1.0.0-preview2-003131" + "version": "1.0.0-preview2-003121" } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Common/ArrayPointerTests.cs b/tests/ImageSharp.Tests/Common/ArrayPointerTests.cs index 1d229f86ab..64cd306f3a 100644 --- a/tests/ImageSharp.Tests/Common/ArrayPointerTests.cs +++ b/tests/ImageSharp.Tests/Common/ArrayPointerTests.cs @@ -10,9 +10,11 @@ namespace ImageSharp.Tests.Common { public struct Foo { +#pragma warning disable CS0414 private int a; private double b; +#pragma warning restore CS0414 internal static Foo[] CreateArray(int size) { diff --git a/tests/ImageSharp.Tests/TestUtilities/TestUtilityExtensions.cs b/tests/ImageSharp.Tests/TestUtilities/TestUtilityExtensions.cs index 748bd52938..ca33402380 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestUtilityExtensions.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestUtilityExtensions.cs @@ -45,15 +45,6 @@ namespace ImageSharp.Tests PixelTypes2ClrTypes[PixelTypes.StandardImageClass] = typeof(Color); } - public static Type GetPackedType(Type pixelType) - { - var intrfcType = - pixelType.GetInterfaces() - .Single(i => i.IsConstructedGenericType && i.GetGenericTypeDefinition() == typeof(IPackedPixel<>)); - - return intrfcType.GetGenericArguments().Single(); - } - public static bool HasFlag(this PixelTypes pixelTypes, PixelTypes flag) => (pixelTypes & flag) == flag; public static bool IsEquivalentTo(this Image a, Image b, bool compareAlpha = true) diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/TestUtilityExtensionsTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/TestUtilityExtensionsTests.cs index ad35cb80f1..1db209dcd3 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestUtilityExtensionsTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestUtilityExtensionsTests.cs @@ -57,15 +57,7 @@ namespace ImageSharp.Tests var fake = typeof(Color).GetTypeInfo().Assembly.GetType("ImageSharp.dsaada_DASqewrr"); Assert.Null(fake); } - - [Fact] - public void GetPackedType() - { - Type shouldBeUIint32 = TestUtilityExtensions.GetPackedType(typeof(Color)); - - Assert.Equal(shouldBeUIint32, typeof(uint)); - } - + [Theory] [WithFile(TestImages.Bmp.Car, PixelTypes.Color, true)] [WithFile(TestImages.Bmp.Car, PixelTypes.Color, false)] From 4301808c65f08b8cefcc66c466dfb5db76e3daf6 Mon Sep 17 00:00:00 2001 From: Dirk Lemstra Date: Sat, 18 Feb 2017 19:15:52 +0100 Subject: [PATCH 080/142] Added a debug version of the Guard class so we get exceptions for debug asserts instead of a dialog. --- src/ImageSharp/Common/Helpers/DebugGuard.cs | 33 +++++++++++++++++++ src/ImageSharp/Common/Helpers/ThrowHelper.cs | 26 --------------- .../Common/Memory/ArrayPointer{T}.cs | 12 ++----- src/ImageSharp/Image/ImageBase{TColor}.cs | 2 +- src/ImageSharp/MetaData/ImageFrameMetaData.cs | 4 +-- src/ImageSharp/MetaData/ImageMetaData.cs | 3 +- src/ImageSharp/MetaData/ImageProperty.cs | 3 +- 7 files changed, 39 insertions(+), 44 deletions(-) create mode 100644 src/ImageSharp/Common/Helpers/DebugGuard.cs delete mode 100644 src/ImageSharp/Common/Helpers/ThrowHelper.cs diff --git a/src/ImageSharp/Common/Helpers/DebugGuard.cs b/src/ImageSharp/Common/Helpers/DebugGuard.cs new file mode 100644 index 0000000000..0ca6f09120 --- /dev/null +++ b/src/ImageSharp/Common/Helpers/DebugGuard.cs @@ -0,0 +1,33 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Diagnostics; + + /// + /// Provides methods to protect against invalid parameters for a DEBUG build. + /// + [DebuggerStepThrough] + internal static class DebugGuard + { + /// + /// Verifies, that the method parameter with specified object value is not null + /// and throws an exception if it is found to be so. + /// + /// The target object, which cannot be null. + /// The name of the parameter that is to be checked. + /// is null + [Conditional("DEBUG")] + public static void NotNull(object target, string parameterName) + { + if (target == null) + { + throw new ArgumentNullException(parameterName); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Common/Helpers/ThrowHelper.cs b/src/ImageSharp/Common/Helpers/ThrowHelper.cs deleted file mode 100644 index 97d75717a9..0000000000 --- a/src/ImageSharp/Common/Helpers/ThrowHelper.cs +++ /dev/null @@ -1,26 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - using System; - using System.Runtime.CompilerServices; - - /// - /// Helps removing exception throwing code from hot path by providing non-inlined exception thrower methods. - /// - internal static class ThrowHelper - { - /// - /// Throws an - /// - /// The parameter name - [MethodImpl(MethodImplOptions.NoInlining)] - public static void ThrowArgumentNullException(string paramName) - { - throw new ArgumentNullException(nameof(paramName)); - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Common/Memory/ArrayPointer{T}.cs b/src/ImageSharp/Common/Memory/ArrayPointer{T}.cs index 95d2e51e66..1ea7706d44 100644 --- a/src/ImageSharp/Common/Memory/ArrayPointer{T}.cs +++ b/src/ImageSharp/Common/Memory/ArrayPointer{T}.cs @@ -31,11 +31,7 @@ namespace ImageSharp [MethodImpl(MethodImplOptions.AggressiveInlining)] public ArrayPointer(T[] array, void* pointerToArray, int offset) { - // TODO: Use Guard.NotNull() here after optimizing it by eliminating the default argument case and applying ThrowHelper! - if (array == null) - { - ThrowHelper.ThrowArgumentNullException(nameof(array)); - } + DebugGuard.NotNull(array, nameof(array)); this.Array = array; this.Offset = offset; @@ -50,11 +46,7 @@ namespace ImageSharp [MethodImpl(MethodImplOptions.AggressiveInlining)] public ArrayPointer(T[] array, void* pointerToArray) { - // TODO: Use Guard.NotNull() here after optimizing it by eliminating the default argument case and applying ThrowHelper! - if (array == null) - { - ThrowHelper.ThrowArgumentNullException(nameof(array)); - } + DebugGuard.NotNull(array, nameof(array)); this.Array = array; this.Offset = 0; diff --git a/src/ImageSharp/Image/ImageBase{TColor}.cs b/src/ImageSharp/Image/ImageBase{TColor}.cs index f530c0ccf4..e4b4485c7f 100644 --- a/src/ImageSharp/Image/ImageBase{TColor}.cs +++ b/src/ImageSharp/Image/ImageBase{TColor}.cs @@ -182,7 +182,7 @@ namespace ImageSharp /// protected void CopyProperties(IImageBase other) { - Debug.Assert(other != null); + DebugGuard.NotNull(other, nameof(other)); this.Configuration = other.Configuration; } diff --git a/src/ImageSharp/MetaData/ImageFrameMetaData.cs b/src/ImageSharp/MetaData/ImageFrameMetaData.cs index c2277686f1..50119096e9 100644 --- a/src/ImageSharp/MetaData/ImageFrameMetaData.cs +++ b/src/ImageSharp/MetaData/ImageFrameMetaData.cs @@ -5,8 +5,6 @@ namespace ImageSharp { - using System.Diagnostics; - /// /// Encapsulates the metadata of an image frame. /// @@ -28,7 +26,7 @@ namespace ImageSharp /// internal ImageFrameMetaData(ImageFrameMetaData other) { - Debug.Assert(other != null); + DebugGuard.NotNull(other, nameof(other)); this.FrameDelay = other.FrameDelay; } diff --git a/src/ImageSharp/MetaData/ImageMetaData.cs b/src/ImageSharp/MetaData/ImageMetaData.cs index a40df3110d..de1e424769 100644 --- a/src/ImageSharp/MetaData/ImageMetaData.cs +++ b/src/ImageSharp/MetaData/ImageMetaData.cs @@ -6,7 +6,6 @@ namespace ImageSharp { using System.Collections.Generic; - using System.Diagnostics; /// /// Encapsulates the metadata of an image. @@ -46,7 +45,7 @@ namespace ImageSharp /// internal ImageMetaData(ImageMetaData other) { - Debug.Assert(other != null); + DebugGuard.NotNull(other, nameof(other)); this.HorizontalResolution = other.HorizontalResolution; this.VerticalResolution = other.VerticalResolution; diff --git a/src/ImageSharp/MetaData/ImageProperty.cs b/src/ImageSharp/MetaData/ImageProperty.cs index c8bd0b23d1..178794283c 100644 --- a/src/ImageSharp/MetaData/ImageProperty.cs +++ b/src/ImageSharp/MetaData/ImageProperty.cs @@ -6,7 +6,6 @@ namespace ImageSharp { using System; - using System.Diagnostics; /// /// Stores meta information about a image, like the name of the author, @@ -37,7 +36,7 @@ namespace ImageSharp /// internal ImageProperty(ImageProperty other) { - Debug.Assert(other != null); + DebugGuard.NotNull(other, nameof(other)); this.Name = other.Name; this.Value = other.Value; From 89529033ee8344fdd5204c0bda92350188bac768 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 19 Feb 2017 14:18:41 +0100 Subject: [PATCH 081/142] The return of IPackedVector --- src/ImageSharp/Colors/Color.cs | 2 +- src/ImageSharp/Colors/PackedPixel/Alpha8.cs | 2 +- src/ImageSharp/Colors/PackedPixel/Argb.cs | 2 +- src/ImageSharp/Colors/PackedPixel/Bgr565.cs | 2 +- src/ImageSharp/Colors/PackedPixel/Bgra4444.cs | 2 +- src/ImageSharp/Colors/PackedPixel/Bgra5551.cs | 2 +- src/ImageSharp/Colors/PackedPixel/Byte4.cs | 2 +- .../Colors/PackedPixel/HalfSingle.cs | 2 +- .../Colors/PackedPixel/HalfVector2.cs | 2 +- .../Colors/PackedPixel/HalfVector4.cs | 2 +- .../PackedPixel/IPackedVector{TPacked}.cs | 24 +++++++++++++++++++ .../Colors/PackedPixel/NormalizedByte2.cs | 2 +- .../Colors/PackedPixel/NormalizedByte4.cs | 2 +- .../Colors/PackedPixel/NormalizedShort2.cs | 2 +- .../Colors/PackedPixel/NormalizedShort4.cs | 2 +- src/ImageSharp/Colors/PackedPixel/Rg32.cs | 2 +- .../Colors/PackedPixel/Rgba1010102.cs | 2 +- src/ImageSharp/Colors/PackedPixel/Rgba64.cs | 2 +- src/ImageSharp/Colors/PackedPixel/Short2.cs | 2 +- src/ImageSharp/Colors/PackedPixel/Short4.cs | 2 +- .../Common/ArrayPointerTests.cs | 18 +------------- 21 files changed, 44 insertions(+), 36 deletions(-) create mode 100644 src/ImageSharp/Colors/PackedPixel/IPackedVector{TPacked}.cs diff --git a/src/ImageSharp/Colors/Color.cs b/src/ImageSharp/Colors/Color.cs index b82628b3b8..2c0d80e52d 100644 --- a/src/ImageSharp/Colors/Color.cs +++ b/src/ImageSharp/Colors/Color.cs @@ -18,7 +18,7 @@ namespace ImageSharp /// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, /// as it avoids the need to create new values for modification operations. /// - public partial struct Color : IPixel + public partial struct Color : IPixel, IPackedVector { /// /// The shift count for the red component diff --git a/src/ImageSharp/Colors/PackedPixel/Alpha8.cs b/src/ImageSharp/Colors/PackedPixel/Alpha8.cs index 5141cbca43..6339b66a88 100644 --- a/src/ImageSharp/Colors/PackedPixel/Alpha8.cs +++ b/src/ImageSharp/Colors/PackedPixel/Alpha8.cs @@ -12,7 +12,7 @@ namespace ImageSharp /// /// Packed pixel type containing a single 8 bit normalized W values ranging from 0 to 1. /// - public struct Alpha8 : IPixel + public struct Alpha8 : IPixel, IPackedVector { /// /// Initializes a new instance of the struct. diff --git a/src/ImageSharp/Colors/PackedPixel/Argb.cs b/src/ImageSharp/Colors/PackedPixel/Argb.cs index 57ae0898bb..da3c50917b 100644 --- a/src/ImageSharp/Colors/PackedPixel/Argb.cs +++ b/src/ImageSharp/Colors/PackedPixel/Argb.cs @@ -17,7 +17,7 @@ namespace ImageSharp /// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, /// as it avoids the need to create new values for modification operations. /// - public struct Argb : IPixel + public struct Argb : IPixel, IPackedVector { /// /// The shift count for the blue component diff --git a/src/ImageSharp/Colors/PackedPixel/Bgr565.cs b/src/ImageSharp/Colors/PackedPixel/Bgr565.cs index 042c8c0f5f..e11aa9c650 100644 --- a/src/ImageSharp/Colors/PackedPixel/Bgr565.cs +++ b/src/ImageSharp/Colors/PackedPixel/Bgr565.cs @@ -12,7 +12,7 @@ namespace ImageSharp /// /// Packed pixel type containing unsigned normalized values ranging from 0 to 1. The x and z components use 5 bits, and the y component uses 6 bits. /// - public struct Bgr565 : IPixel + public struct Bgr565 : IPixel, IPackedVector { /// /// Initializes a new instance of the struct. diff --git a/src/ImageSharp/Colors/PackedPixel/Bgra4444.cs b/src/ImageSharp/Colors/PackedPixel/Bgra4444.cs index 8bae542c2a..9853409eaa 100644 --- a/src/ImageSharp/Colors/PackedPixel/Bgra4444.cs +++ b/src/ImageSharp/Colors/PackedPixel/Bgra4444.cs @@ -12,7 +12,7 @@ namespace ImageSharp /// /// Packed pixel type containing unsigned normalized values, ranging from 0 to 1, using 4 bits each for x, y, z, and w. /// - public struct Bgra4444 : IPixel + public struct Bgra4444 : IPixel, IPackedVector { /// /// Initializes a new instance of the struct. diff --git a/src/ImageSharp/Colors/PackedPixel/Bgra5551.cs b/src/ImageSharp/Colors/PackedPixel/Bgra5551.cs index 0ad7dbd0e3..bed6581339 100644 --- a/src/ImageSharp/Colors/PackedPixel/Bgra5551.cs +++ b/src/ImageSharp/Colors/PackedPixel/Bgra5551.cs @@ -12,7 +12,7 @@ namespace ImageSharp /// /// Packed pixel type containing unsigned normalized values ranging from 0 to 1. The x , y and z components use 5 bits, and the w component uses 1 bit. /// - public struct Bgra5551 : IPixel + public struct Bgra5551 : IPixel, IPackedVector { /// /// Initializes a new instance of the struct. diff --git a/src/ImageSharp/Colors/PackedPixel/Byte4.cs b/src/ImageSharp/Colors/PackedPixel/Byte4.cs index b1f8d5a6ea..79a625c312 100644 --- a/src/ImageSharp/Colors/PackedPixel/Byte4.cs +++ b/src/ImageSharp/Colors/PackedPixel/Byte4.cs @@ -12,7 +12,7 @@ namespace ImageSharp /// /// Packed pixel type containing four 8-bit unsigned integer values, ranging from 0 to 255. /// - public struct Byte4 : IPixel + public struct Byte4 : IPixel, IPackedVector { /// /// Initializes a new instance of the struct. diff --git a/src/ImageSharp/Colors/PackedPixel/HalfSingle.cs b/src/ImageSharp/Colors/PackedPixel/HalfSingle.cs index bd4ba3840a..9737a99ae7 100644 --- a/src/ImageSharp/Colors/PackedPixel/HalfSingle.cs +++ b/src/ImageSharp/Colors/PackedPixel/HalfSingle.cs @@ -12,7 +12,7 @@ namespace ImageSharp /// /// Packed pixel type containing a single 16 bit floating point value. /// - public struct HalfSingle : IPixel + public struct HalfSingle : IPixel, IPackedVector { /// /// The maximum byte value. diff --git a/src/ImageSharp/Colors/PackedPixel/HalfVector2.cs b/src/ImageSharp/Colors/PackedPixel/HalfVector2.cs index 4d1f0a2cb4..57649dbd5e 100644 --- a/src/ImageSharp/Colors/PackedPixel/HalfVector2.cs +++ b/src/ImageSharp/Colors/PackedPixel/HalfVector2.cs @@ -12,7 +12,7 @@ namespace ImageSharp /// /// Packed pixel type containing two 16-bit floating-point values. /// - public struct HalfVector2 : IPixel + public struct HalfVector2 : IPixel, IPackedVector { /// /// The maximum byte value. diff --git a/src/ImageSharp/Colors/PackedPixel/HalfVector4.cs b/src/ImageSharp/Colors/PackedPixel/HalfVector4.cs index e0108404d1..f4f08a25c6 100644 --- a/src/ImageSharp/Colors/PackedPixel/HalfVector4.cs +++ b/src/ImageSharp/Colors/PackedPixel/HalfVector4.cs @@ -12,7 +12,7 @@ namespace ImageSharp /// /// Packed pixel type containing four 16-bit floating-point values. /// - public struct HalfVector4 : IPixel + public struct HalfVector4 : IPixel, IPackedVector { /// /// The maximum byte value. diff --git a/src/ImageSharp/Colors/PackedPixel/IPackedVector{TPacked}.cs b/src/ImageSharp/Colors/PackedPixel/IPackedVector{TPacked}.cs new file mode 100644 index 0000000000..0450fb8fba --- /dev/null +++ b/src/ImageSharp/Colors/PackedPixel/IPackedVector{TPacked}.cs @@ -0,0 +1,24 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Numerics; + + /// + /// This interface exists for ensuring signature compatibility to MonoGame and XNA packed color types. + /// + /// + /// The packed format. uint, long, float. + public interface IPackedVector : IPixel + where TPacked : struct, IEquatable + { + /// + /// Gets or sets the packed representation of the value. + /// + TPacked PackedValue { get; set; } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Colors/PackedPixel/NormalizedByte2.cs b/src/ImageSharp/Colors/PackedPixel/NormalizedByte2.cs index 0567b44452..1fece550fa 100644 --- a/src/ImageSharp/Colors/PackedPixel/NormalizedByte2.cs +++ b/src/ImageSharp/Colors/PackedPixel/NormalizedByte2.cs @@ -12,7 +12,7 @@ namespace ImageSharp /// /// Packed packed pixel type containing two 8-bit signed normalized values, ranging from −1 to 1. /// - public struct NormalizedByte2 : IPixel + public struct NormalizedByte2 : IPixel, IPackedVector { /// /// The maximum byte value. diff --git a/src/ImageSharp/Colors/PackedPixel/NormalizedByte4.cs b/src/ImageSharp/Colors/PackedPixel/NormalizedByte4.cs index a45f601c4c..d49ea8a654 100644 --- a/src/ImageSharp/Colors/PackedPixel/NormalizedByte4.cs +++ b/src/ImageSharp/Colors/PackedPixel/NormalizedByte4.cs @@ -12,7 +12,7 @@ namespace ImageSharp /// /// Packed pixel type containing four 8-bit signed normalized values, ranging from −1 to 1. /// - public struct NormalizedByte4 : IPixel + public struct NormalizedByte4 : IPixel, IPackedVector { /// /// The maximum byte value. diff --git a/src/ImageSharp/Colors/PackedPixel/NormalizedShort2.cs b/src/ImageSharp/Colors/PackedPixel/NormalizedShort2.cs index 3642aed5b6..b9a2dc5332 100644 --- a/src/ImageSharp/Colors/PackedPixel/NormalizedShort2.cs +++ b/src/ImageSharp/Colors/PackedPixel/NormalizedShort2.cs @@ -12,7 +12,7 @@ namespace ImageSharp /// /// Packed pixel type containing two 16-bit signed normalized values, ranging from −1 to 1. /// - public struct NormalizedShort2 : IPixel + public struct NormalizedShort2 : IPixel, IPackedVector { /// /// The maximum byte value. diff --git a/src/ImageSharp/Colors/PackedPixel/NormalizedShort4.cs b/src/ImageSharp/Colors/PackedPixel/NormalizedShort4.cs index d5a3de79e9..2cf90d2081 100644 --- a/src/ImageSharp/Colors/PackedPixel/NormalizedShort4.cs +++ b/src/ImageSharp/Colors/PackedPixel/NormalizedShort4.cs @@ -12,7 +12,7 @@ namespace ImageSharp /// /// Packed pixel type containing four 16-bit signed normalized values, ranging from −1 to 1. /// - public struct NormalizedShort4 : IPixel + public struct NormalizedShort4 : IPixel, IPackedVector { /// /// The maximum byte value. diff --git a/src/ImageSharp/Colors/PackedPixel/Rg32.cs b/src/ImageSharp/Colors/PackedPixel/Rg32.cs index 9bda5f6058..0dbcf483a9 100644 --- a/src/ImageSharp/Colors/PackedPixel/Rg32.cs +++ b/src/ImageSharp/Colors/PackedPixel/Rg32.cs @@ -12,7 +12,7 @@ namespace ImageSharp /// /// Packed pixel type containing two 16-bit unsigned normalized values ranging from 0 to 1. /// - public struct Rg32 : IPixel + public struct Rg32 : IPixel, IPackedVector { /// /// Initializes a new instance of the struct. diff --git a/src/ImageSharp/Colors/PackedPixel/Rgba1010102.cs b/src/ImageSharp/Colors/PackedPixel/Rgba1010102.cs index 69c097f964..f867afb014 100644 --- a/src/ImageSharp/Colors/PackedPixel/Rgba1010102.cs +++ b/src/ImageSharp/Colors/PackedPixel/Rgba1010102.cs @@ -13,7 +13,7 @@ namespace ImageSharp /// Packed vector type containing unsigned normalized values ranging from 0 to 1. /// The x, y and z components use 10 bits, and the w component uses 2 bits. /// - public struct Rgba1010102 : IPixel + public struct Rgba1010102 : IPixel, IPackedVector { /// /// Initializes a new instance of the struct. diff --git a/src/ImageSharp/Colors/PackedPixel/Rgba64.cs b/src/ImageSharp/Colors/PackedPixel/Rgba64.cs index 4705564354..a99e127f35 100644 --- a/src/ImageSharp/Colors/PackedPixel/Rgba64.cs +++ b/src/ImageSharp/Colors/PackedPixel/Rgba64.cs @@ -12,7 +12,7 @@ namespace ImageSharp /// /// Packed pixel type containing four 16-bit unsigned normalized values ranging from 0 to 1. /// - public struct Rgba64 : IPixel + public struct Rgba64 : IPixel, IPackedVector { /// /// Initializes a new instance of the struct. diff --git a/src/ImageSharp/Colors/PackedPixel/Short2.cs b/src/ImageSharp/Colors/PackedPixel/Short2.cs index 3f83a4cd5d..a89005ce54 100644 --- a/src/ImageSharp/Colors/PackedPixel/Short2.cs +++ b/src/ImageSharp/Colors/PackedPixel/Short2.cs @@ -12,7 +12,7 @@ namespace ImageSharp /// /// Packed pixel type containing two 16-bit signed integer values. /// - public struct Short2 : IPixel + public struct Short2 : IPixel, IPackedVector { /// /// The maximum byte value. diff --git a/src/ImageSharp/Colors/PackedPixel/Short4.cs b/src/ImageSharp/Colors/PackedPixel/Short4.cs index 1696cb40a4..4b6a6d00b5 100644 --- a/src/ImageSharp/Colors/PackedPixel/Short4.cs +++ b/src/ImageSharp/Colors/PackedPixel/Short4.cs @@ -12,7 +12,7 @@ namespace ImageSharp /// /// Packed pixel type containing four 16-bit signed integer values. /// - public struct Short4 : IPixel + public struct Short4 : IPixel, IPackedVector { /// /// The maximum byte value. diff --git a/tests/ImageSharp.Tests/Common/ArrayPointerTests.cs b/tests/ImageSharp.Tests/Common/ArrayPointerTests.cs index 64cd306f3a..076e2512c8 100644 --- a/tests/ImageSharp.Tests/Common/ArrayPointerTests.cs +++ b/tests/ImageSharp.Tests/Common/ArrayPointerTests.cs @@ -26,23 +26,7 @@ namespace ImageSharp.Tests.Common return result; } } - - [Fact] - public void ConstructWithNullArray_Throws() - { - Assert.Throws( - () => - { - new ArrayPointer(null, (void*)0); - }); - - Assert.Throws( - () => - { - new ArrayPointer(null, (void*)0); - }); - } - + [Fact] public void ConstructWithoutOffset() { From 571a0cfb45f2fa2a018c92acc6068bdc9601a4bd Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 19 Feb 2017 21:48:50 +0100 Subject: [PATCH 082/142] restored on PackedValue-s --- src/ImageSharp/Colors/Color.cs | 4 +--- src/ImageSharp/Colors/PackedPixel/Alpha8.cs | 4 +--- src/ImageSharp/Colors/PackedPixel/Argb.cs | 4 +--- src/ImageSharp/Colors/PackedPixel/Bgr565.cs | 4 +--- src/ImageSharp/Colors/PackedPixel/Bgra4444.cs | 4 +--- src/ImageSharp/Colors/PackedPixel/Bgra5551.cs | 4 +--- src/ImageSharp/Colors/PackedPixel/Byte4.cs | 4 +--- src/ImageSharp/Colors/PackedPixel/HalfSingle.cs | 4 +--- src/ImageSharp/Colors/PackedPixel/HalfVector2.cs | 4 +--- src/ImageSharp/Colors/PackedPixel/HalfVector4.cs | 4 +--- src/ImageSharp/Colors/PackedPixel/NormalizedByte2.cs | 4 +--- src/ImageSharp/Colors/PackedPixel/NormalizedByte4.cs | 4 +--- src/ImageSharp/Colors/PackedPixel/NormalizedShort2.cs | 4 +--- src/ImageSharp/Colors/PackedPixel/NormalizedShort4.cs | 4 +--- src/ImageSharp/Colors/PackedPixel/Rg32.cs | 4 +--- src/ImageSharp/Colors/PackedPixel/Rgba1010102.cs | 4 +--- src/ImageSharp/Colors/PackedPixel/Rgba64.cs | 4 +--- src/ImageSharp/Colors/PackedPixel/Short2.cs | 4 +--- src/ImageSharp/Colors/PackedPixel/Short4.cs | 4 +--- 19 files changed, 19 insertions(+), 57 deletions(-) diff --git a/src/ImageSharp/Colors/Color.cs b/src/ImageSharp/Colors/Color.cs index 2c0d80e52d..469774b348 100644 --- a/src/ImageSharp/Colors/Color.cs +++ b/src/ImageSharp/Colors/Color.cs @@ -184,9 +184,7 @@ namespace ImageSharp } } - /// - /// Gets or sets the packed representation of the value. - /// + /// public uint PackedValue { get diff --git a/src/ImageSharp/Colors/PackedPixel/Alpha8.cs b/src/ImageSharp/Colors/PackedPixel/Alpha8.cs index 6339b66a88..485725d71a 100644 --- a/src/ImageSharp/Colors/PackedPixel/Alpha8.cs +++ b/src/ImageSharp/Colors/PackedPixel/Alpha8.cs @@ -23,9 +23,7 @@ namespace ImageSharp this.PackedValue = Pack(alpha); } - /// - /// Gets or sets the packed representation of the value. - /// + /// public byte PackedValue { get; set; } /// diff --git a/src/ImageSharp/Colors/PackedPixel/Argb.cs b/src/ImageSharp/Colors/PackedPixel/Argb.cs index da3c50917b..bef986fb9c 100644 --- a/src/ImageSharp/Colors/PackedPixel/Argb.cs +++ b/src/ImageSharp/Colors/PackedPixel/Argb.cs @@ -106,9 +106,7 @@ namespace ImageSharp this.PackedValue = packed; } - /// - /// Gets or sets the packed representation of the value. - /// + /// public uint PackedValue { get; set; } /// diff --git a/src/ImageSharp/Colors/PackedPixel/Bgr565.cs b/src/ImageSharp/Colors/PackedPixel/Bgr565.cs index e11aa9c650..ebe8d25335 100644 --- a/src/ImageSharp/Colors/PackedPixel/Bgr565.cs +++ b/src/ImageSharp/Colors/PackedPixel/Bgr565.cs @@ -36,9 +36,7 @@ namespace ImageSharp this.PackedValue = Pack(vector.X, vector.Y, vector.Z); } - /// - /// Gets or sets the packed representation of the value. - /// + /// public ushort PackedValue { get; set; } /// diff --git a/src/ImageSharp/Colors/PackedPixel/Bgra4444.cs b/src/ImageSharp/Colors/PackedPixel/Bgra4444.cs index 9853409eaa..ccd6ab1f3e 100644 --- a/src/ImageSharp/Colors/PackedPixel/Bgra4444.cs +++ b/src/ImageSharp/Colors/PackedPixel/Bgra4444.cs @@ -35,9 +35,7 @@ namespace ImageSharp this.PackedValue = Pack(vector.X, vector.Y, vector.Z, vector.W); } - /// - /// Gets or sets the packed representation of the value. - /// + /// public ushort PackedValue { get; set; } /// diff --git a/src/ImageSharp/Colors/PackedPixel/Bgra5551.cs b/src/ImageSharp/Colors/PackedPixel/Bgra5551.cs index bed6581339..a7a2e899a5 100644 --- a/src/ImageSharp/Colors/PackedPixel/Bgra5551.cs +++ b/src/ImageSharp/Colors/PackedPixel/Bgra5551.cs @@ -37,9 +37,7 @@ namespace ImageSharp this.PackedValue = Pack(vector.X, vector.Y, vector.Z, vector.W); } - /// - /// Gets or sets the packed representation of the value. - /// + /// public ushort PackedValue { get; set; } /// diff --git a/src/ImageSharp/Colors/PackedPixel/Byte4.cs b/src/ImageSharp/Colors/PackedPixel/Byte4.cs index 79a625c312..9d5eb9be81 100644 --- a/src/ImageSharp/Colors/PackedPixel/Byte4.cs +++ b/src/ImageSharp/Colors/PackedPixel/Byte4.cs @@ -38,9 +38,7 @@ namespace ImageSharp this.PackedValue = Pack(ref vector); } - /// - /// Gets or sets the packed representation of the value. - /// + /// public uint PackedValue { get; set; } /// diff --git a/src/ImageSharp/Colors/PackedPixel/HalfSingle.cs b/src/ImageSharp/Colors/PackedPixel/HalfSingle.cs index 9737a99ae7..acfa639b70 100644 --- a/src/ImageSharp/Colors/PackedPixel/HalfSingle.cs +++ b/src/ImageSharp/Colors/PackedPixel/HalfSingle.cs @@ -33,9 +33,7 @@ namespace ImageSharp this.PackedValue = HalfTypeHelper.Pack(single); } - /// - /// Gets or sets the packed representation of the value. - /// + /// public ushort PackedValue { get; set; } /// diff --git a/src/ImageSharp/Colors/PackedPixel/HalfVector2.cs b/src/ImageSharp/Colors/PackedPixel/HalfVector2.cs index 57649dbd5e..e02c226dd7 100644 --- a/src/ImageSharp/Colors/PackedPixel/HalfVector2.cs +++ b/src/ImageSharp/Colors/PackedPixel/HalfVector2.cs @@ -43,9 +43,7 @@ namespace ImageSharp this.PackedValue = Pack(vector.X, vector.Y); } - /// - /// Gets or sets the packed representation of the value. - /// + /// public uint PackedValue { get; set; } /// diff --git a/src/ImageSharp/Colors/PackedPixel/HalfVector4.cs b/src/ImageSharp/Colors/PackedPixel/HalfVector4.cs index f4f08a25c6..7c7f640e49 100644 --- a/src/ImageSharp/Colors/PackedPixel/HalfVector4.cs +++ b/src/ImageSharp/Colors/PackedPixel/HalfVector4.cs @@ -46,9 +46,7 @@ namespace ImageSharp this.PackedValue = Pack(ref vector); } - /// - /// Gets or sets the packed representation of the value. - /// + /// public ulong PackedValue { get; set; } /// diff --git a/src/ImageSharp/Colors/PackedPixel/NormalizedByte2.cs b/src/ImageSharp/Colors/PackedPixel/NormalizedByte2.cs index 1fece550fa..116a681726 100644 --- a/src/ImageSharp/Colors/PackedPixel/NormalizedByte2.cs +++ b/src/ImageSharp/Colors/PackedPixel/NormalizedByte2.cs @@ -48,9 +48,7 @@ namespace ImageSharp this.PackedValue = Pack(x, y); } - /// - /// Gets or sets the packed representation of the value. - /// + /// public ushort PackedValue { get; set; } /// diff --git a/src/ImageSharp/Colors/PackedPixel/NormalizedByte4.cs b/src/ImageSharp/Colors/PackedPixel/NormalizedByte4.cs index d49ea8a654..7aaa30c520 100644 --- a/src/ImageSharp/Colors/PackedPixel/NormalizedByte4.cs +++ b/src/ImageSharp/Colors/PackedPixel/NormalizedByte4.cs @@ -50,9 +50,7 @@ namespace ImageSharp this.PackedValue = Pack(x, y, z, w); } - /// - /// Gets or sets the packed representation of the value. - /// + /// public uint PackedValue { get; set; } /// diff --git a/src/ImageSharp/Colors/PackedPixel/NormalizedShort2.cs b/src/ImageSharp/Colors/PackedPixel/NormalizedShort2.cs index b9a2dc5332..2f4ef89d65 100644 --- a/src/ImageSharp/Colors/PackedPixel/NormalizedShort2.cs +++ b/src/ImageSharp/Colors/PackedPixel/NormalizedShort2.cs @@ -48,9 +48,7 @@ namespace ImageSharp this.PackedValue = Pack(x, y); } - /// - /// Gets or sets the packed representation of the value. - /// + /// public uint PackedValue { get; set; } /// diff --git a/src/ImageSharp/Colors/PackedPixel/NormalizedShort4.cs b/src/ImageSharp/Colors/PackedPixel/NormalizedShort4.cs index 2cf90d2081..60c5c9805a 100644 --- a/src/ImageSharp/Colors/PackedPixel/NormalizedShort4.cs +++ b/src/ImageSharp/Colors/PackedPixel/NormalizedShort4.cs @@ -50,9 +50,7 @@ namespace ImageSharp this.PackedValue = Pack(x, y, z, w); } - /// - /// Gets or sets the packed representation of the value. - /// + /// public ulong PackedValue { get; set; } /// diff --git a/src/ImageSharp/Colors/PackedPixel/Rg32.cs b/src/ImageSharp/Colors/PackedPixel/Rg32.cs index 0dbcf483a9..9e5e5a711c 100644 --- a/src/ImageSharp/Colors/PackedPixel/Rg32.cs +++ b/src/ImageSharp/Colors/PackedPixel/Rg32.cs @@ -33,9 +33,7 @@ namespace ImageSharp this.PackedValue = Pack(vector.X, vector.Y); } - /// - /// Gets or sets the packed representation of the value. - /// + /// public uint PackedValue { get; set; } /// diff --git a/src/ImageSharp/Colors/PackedPixel/Rgba1010102.cs b/src/ImageSharp/Colors/PackedPixel/Rgba1010102.cs index f867afb014..95a8d3b978 100644 --- a/src/ImageSharp/Colors/PackedPixel/Rgba1010102.cs +++ b/src/ImageSharp/Colors/PackedPixel/Rgba1010102.cs @@ -36,9 +36,7 @@ namespace ImageSharp this.PackedValue = Pack(vector.X, vector.Y, vector.Z, vector.W); } - /// - /// Gets or sets the packed representation of the value. - /// + /// public uint PackedValue { get; set; } /// diff --git a/src/ImageSharp/Colors/PackedPixel/Rgba64.cs b/src/ImageSharp/Colors/PackedPixel/Rgba64.cs index a99e127f35..679a55c4e6 100644 --- a/src/ImageSharp/Colors/PackedPixel/Rgba64.cs +++ b/src/ImageSharp/Colors/PackedPixel/Rgba64.cs @@ -35,9 +35,7 @@ namespace ImageSharp this.PackedValue = Pack(vector.X, vector.Y, vector.Z, vector.W); } - /// - /// Gets or sets the packed representation of the value. - /// + /// public ulong PackedValue { get; set; } /// diff --git a/src/ImageSharp/Colors/PackedPixel/Short2.cs b/src/ImageSharp/Colors/PackedPixel/Short2.cs index a89005ce54..1c1cb28c34 100644 --- a/src/ImageSharp/Colors/PackedPixel/Short2.cs +++ b/src/ImageSharp/Colors/PackedPixel/Short2.cs @@ -48,9 +48,7 @@ namespace ImageSharp this.PackedValue = Pack(x, y); } - /// - /// Gets or sets the packed representation of the value. - /// + /// public uint PackedValue { get; set; } /// diff --git a/src/ImageSharp/Colors/PackedPixel/Short4.cs b/src/ImageSharp/Colors/PackedPixel/Short4.cs index 4b6a6d00b5..2c11a1f8b1 100644 --- a/src/ImageSharp/Colors/PackedPixel/Short4.cs +++ b/src/ImageSharp/Colors/PackedPixel/Short4.cs @@ -50,9 +50,7 @@ namespace ImageSharp this.PackedValue = Pack(x, y, z, w); } - /// - /// Gets or sets the packed representation of the value. - /// + /// public ulong PackedValue { get; set; } /// From c9f2753ed9d31e0940a2c37115744f33b8e65a2d Mon Sep 17 00:00:00 2001 From: Toxantron Date: Sun, 19 Feb 2017 21:57:01 +0100 Subject: [PATCH 083/142] Develop with VSCode --- .vscode/tasks.json | 24 ++++++++++++++++++++++++ README.md | 5 +++++ 2 files changed, 29 insertions(+) create mode 100644 .vscode/tasks.json diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000000..aeae5c6ca9 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,24 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "0.1.0", + "command": "dotnet", + "isShellCommand": true, + "args": [], + "tasks": [ + { + "taskName": "build", + "args": [ "src/*/project.json", "-f", "netstandard1.1" ], + "isBuildCommand": true, + "showOutput": "always", + "problemMatcher": "$msCompile" + }, + { + "taskName": "test", + "args": ["tests/ImageSharp.Tests/project.json", "-f", "netcoreapp1.1"], + "isTestCommand": true, + "showOutput": "always", + "problemMatcher": "$msCompile" + } + ] +} \ No newline at end of file diff --git a/README.md b/README.md index cfbd18de6e..cbe03bb4e1 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,11 @@ If you prefer, you can compile ImageSharp yourself (please do and help!), you'll - [Visual Studio 2015 with Update 3 (or above)](https://www.visualstudio.com/news/releasenotes/vs2015-update3-vs) - The [.NET Core 1.0 SDK Installer](https://www.microsoft.com/net/core#windows) - Non VSCode link. +Alternatively on Linux you can use: + +- [Visual Studio Code](https://code.visualstudio.com/) with [C# Extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode.csharp) +- [.Net Core 1.1](https://www.microsoft.com/net/core#linuxubuntu) + To clone it locally click the "Clone in Windows" button above or run the following git commands. ```bash From e8285ef6660a64dd6d945849761d470a9b932fb5 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Mon, 20 Feb 2017 01:17:31 +0100 Subject: [PATCH 084/142] Benchmarks: PixelAccessorVirtualCopy, vectorization --- src/ImageSharp/Image/PixelAccessor{TColor}.cs | 5 + .../Color/Bulk/PixelAccessorVirtualCopy.cs | 129 ++++++++++++++++++ .../Drawing/DrawBeziers.cs | 2 +- .../Drawing/DrawLines.cs | 2 +- .../Drawing/DrawPolygon.cs | 2 +- .../Drawing/FillWithPattern.cs | 2 +- .../General/Vectorization/BitwiseOrUint32.cs | 54 ++++++++ .../General/Vectorization/DivFloat.cs | 54 ++++++++ .../General/Vectorization/DivUInt32.cs | 54 ++++++++ .../General/Vectorization/MulFloat.cs | 54 ++++++++ .../General/Vectorization/MulUInt32.cs | 54 ++++++++ .../Vectorization/ReinterpretUInt32AsFloat.cs | 62 +++++++++ .../ImageSharp.Sandbox46.csproj | 11 +- tests/ImageSharp.Sandbox46/Program.cs | 19 ++- tests/ImageSharp.Sandbox46/app.config | 4 + tests/ImageSharp.Sandbox46/packages.config | 1 + 16 files changed, 501 insertions(+), 8 deletions(-) create mode 100644 tests/ImageSharp.Benchmarks/Color/Bulk/PixelAccessorVirtualCopy.cs create mode 100644 tests/ImageSharp.Benchmarks/General/Vectorization/BitwiseOrUint32.cs create mode 100644 tests/ImageSharp.Benchmarks/General/Vectorization/DivFloat.cs create mode 100644 tests/ImageSharp.Benchmarks/General/Vectorization/DivUInt32.cs create mode 100644 tests/ImageSharp.Benchmarks/General/Vectorization/MulFloat.cs create mode 100644 tests/ImageSharp.Benchmarks/General/Vectorization/MulUInt32.cs create mode 100644 tests/ImageSharp.Benchmarks/General/Vectorization/ReinterpretUInt32AsFloat.cs diff --git a/src/ImageSharp/Image/PixelAccessor{TColor}.cs b/src/ImageSharp/Image/PixelAccessor{TColor}.cs index 338f491827..b31ada10bb 100644 --- a/src/ImageSharp/Image/PixelAccessor{TColor}.cs +++ b/src/ImageSharp/Image/PixelAccessor{TColor}.cs @@ -120,6 +120,11 @@ namespace ImageSharp /// public bool PooledMemory { get; private set; } + /// + /// Gets the pixel buffer array. + /// + public TColor[] PixelBuffer => this.pixelBuffer; + /// /// Gets the pointer to the pixel buffer. /// diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/PixelAccessorVirtualCopy.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/PixelAccessorVirtualCopy.cs new file mode 100644 index 0000000000..ed649792fa --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/PixelAccessorVirtualCopy.cs @@ -0,0 +1,129 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace ImageSharp.Benchmarks.Color.Bulk +{ + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + + using BenchmarkDotNet.Attributes; + + using Color = ImageSharp.Color; + + /// + /// Benchmark to measure the effect of using virtual bulk-copy calls inside PixelAccessor methods + /// + public unsafe class PixelAccessorVirtualCopy + { + abstract class CopyExecutor + { + internal abstract void VirtualCopy(ArrayPointer destination, ArrayPointer source, int count); + } + + class UnsafeCopyExecutor : CopyExecutor + { + [MethodImpl(MethodImplOptions.NoInlining)] + internal override unsafe void VirtualCopy(ArrayPointer destination, ArrayPointer source, int count) + { + Unsafe.CopyBlock((void*)destination.PointerAtOffset, (void*)source.PointerAtOffset, (uint)count*4); + } + } + + private PixelAccessor pixelAccessor; + + private PixelArea area; + + private CopyExecutor executor; + + [Params(64, 256)] + public int Width { get; set; } + + public int Height { get; set; } = 256; + + + [Setup] + public void Setup() + { + this.pixelAccessor = new PixelAccessor(this.Width, this.Height); + this.area = new PixelArea(this.Width / 2, this.Height, ComponentOrder.Xyzw); + this.executor = new UnsafeCopyExecutor(); + } + + [Cleanup] + public void Cleanup() + { + this.pixelAccessor.Dispose(); + this.area.Dispose(); + } + + [Benchmark(Baseline = true)] + public void CopyRawUnsafeInlined() + { + uint byteCount = (uint)this.area.Width * 4; + + int targetX = this.Width / 4; + int targetY = 0; + + for (int y = 0; y < this.Height; y++) + { + byte* source = this.area.PixelBase + (y * this.area.RowStride); + byte* destination = this.GetRowPointer(targetX, targetY + y); + + Unsafe.CopyBlock(destination, source, byteCount); + } + } + + [Benchmark] + public void CopyArrayPointerUnsafeInlined() + { + uint byteCount = (uint)this.area.Width * 4; + + int targetX = this.Width / 4; + int targetY = 0; + + for (int y = 0; y < this.Height; y++) + { + ArrayPointer source = this.GetAreaRow(y); + ArrayPointer destination = this.GetPixelAccessorRow(targetX, targetY + y); + Unsafe.CopyBlock((void*)destination.PointerAtOffset, (void*)source.PointerAtOffset, byteCount); + } + } + + [Benchmark] + public void CopyArrayPointerUnsafeVirtual() + { + int targetX = this.Width / 4; + int targetY = 0; + + for (int y = 0; y < this.Height; y++) + { + ArrayPointer source = this.GetAreaRow(y); + ArrayPointer destination = this.GetPixelAccessorRow(targetX, targetY + y); + this.executor.VirtualCopy(destination, source, this.area.Width); + } + } + + private byte* GetRowPointer(int x, int y) + { + return (byte*)this.pixelAccessor.DataPointer + (((y * this.pixelAccessor.Width) + x) * Unsafe.SizeOf()); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private ArrayPointer GetPixelAccessorRow(int x, int y) + { + return new ArrayPointer( + this.pixelAccessor.PixelBuffer, + (void*)this.pixelAccessor.DataPointer, + (y * this.pixelAccessor.Width) + x + ); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private ArrayPointer GetAreaRow(int y) + { + return new ArrayPointer(this.area.Bytes, this.area.PixelBase, y * this.area.RowStride); + } + } +} diff --git a/tests/ImageSharp.Benchmarks/Drawing/DrawBeziers.cs b/tests/ImageSharp.Benchmarks/Drawing/DrawBeziers.cs index c066ac18c7..a10417b90b 100644 --- a/tests/ImageSharp.Benchmarks/Drawing/DrawBeziers.cs +++ b/tests/ImageSharp.Benchmarks/Drawing/DrawBeziers.cs @@ -28,7 +28,7 @@ namespace ImageSharp.Benchmarks { graphics.InterpolationMode = InterpolationMode.Default; graphics.SmoothingMode = SmoothingMode.AntiAlias; - var pen = new Pen(Color.HotPink, 10); + var pen = new Pen(System.Drawing.Color.HotPink, 10); graphics.DrawBeziers(pen, new[] { new PointF(10, 500), new PointF(30, 10), diff --git a/tests/ImageSharp.Benchmarks/Drawing/DrawLines.cs b/tests/ImageSharp.Benchmarks/Drawing/DrawLines.cs index 78f71b6606..146def3637 100644 --- a/tests/ImageSharp.Benchmarks/Drawing/DrawLines.cs +++ b/tests/ImageSharp.Benchmarks/Drawing/DrawLines.cs @@ -28,7 +28,7 @@ namespace ImageSharp.Benchmarks { graphics.InterpolationMode = InterpolationMode.Default; graphics.SmoothingMode = SmoothingMode.AntiAlias; - var pen = new Pen(Color.HotPink, 10); + var pen = new Pen(System.Drawing.Color.HotPink, 10); graphics.DrawLines(pen, new[] { new PointF(10, 10), new PointF(550, 50), diff --git a/tests/ImageSharp.Benchmarks/Drawing/DrawPolygon.cs b/tests/ImageSharp.Benchmarks/Drawing/DrawPolygon.cs index 88618b9128..e6c1ac0d65 100644 --- a/tests/ImageSharp.Benchmarks/Drawing/DrawPolygon.cs +++ b/tests/ImageSharp.Benchmarks/Drawing/DrawPolygon.cs @@ -27,7 +27,7 @@ namespace ImageSharp.Benchmarks { graphics.InterpolationMode = InterpolationMode.Default; graphics.SmoothingMode = SmoothingMode.AntiAlias; - var pen = new Pen(Color.HotPink, 10); + var pen = new Pen(System.Drawing.Color.HotPink, 10); graphics.DrawPolygon(pen, new[] { new PointF(10, 10), new PointF(550, 50), diff --git a/tests/ImageSharp.Benchmarks/Drawing/FillWithPattern.cs b/tests/ImageSharp.Benchmarks/Drawing/FillWithPattern.cs index 718474f1f8..589ac0cd43 100644 --- a/tests/ImageSharp.Benchmarks/Drawing/FillWithPattern.cs +++ b/tests/ImageSharp.Benchmarks/Drawing/FillWithPattern.cs @@ -25,7 +25,7 @@ namespace ImageSharp.Benchmarks using (Graphics graphics = Graphics.FromImage(destination)) { graphics.SmoothingMode = SmoothingMode.AntiAlias; - var brush = new HatchBrush(HatchStyle.BackwardDiagonal, Color.HotPink); + var brush = new HatchBrush(HatchStyle.BackwardDiagonal, System.Drawing.Color.HotPink); graphics.FillRectangle(brush, new Rectangle(0,0, 800,800)); // can't find a way to flood fill with a brush } using (MemoryStream ms = new MemoryStream()) diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/BitwiseOrUint32.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/BitwiseOrUint32.cs new file mode 100644 index 0000000000..dd20e85d5b --- /dev/null +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/BitwiseOrUint32.cs @@ -0,0 +1,54 @@ +namespace ImageSharp.Benchmarks.General.Vectorization +{ + using System.Numerics; + + using BenchmarkDotNet.Attributes; + + public class BitwiseOrUInt32 + { + private uint[] input; + + private uint[] result; + + [Params(32)] + public int InputSize { get; set; } + + private uint testValue; + + [Setup] + public void Setup() + { + this.input = new uint[this.InputSize]; + this.result = new uint[this.InputSize]; + this.testValue = 42; + + for (int i = 0; i < this.InputSize; i++) + { + this.input[i] = (uint) i; + } + } + + [Benchmark(Baseline = true)] + public void Standard() + { + uint v = this.testValue; + for (int i = 0; i < this.input.Length; i++) + { + this.result[i] = this.input[i] | v; + } + } + + [Benchmark] + public void Simd() + { + Vector v = new Vector(this.testValue); + + for (int i = 0; i < this.input.Length; i+=Vector.Count) + { + Vector a = new Vector(this.input, i); + a = Vector.BitwiseOr(a, v); + a.CopyTo(this.result, i); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/DivFloat.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/DivFloat.cs new file mode 100644 index 0000000000..61582b7dc5 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/DivFloat.cs @@ -0,0 +1,54 @@ +namespace ImageSharp.Benchmarks.General.Vectorization +{ + using System.Numerics; + + using BenchmarkDotNet.Attributes; + + public class DivFloat + { + private float[] input; + + private float[] result; + + [Params(32)] + public int InputSize { get; set; } + + private float testValue; + + [Setup] + public void Setup() + { + this.input = new float[this.InputSize]; + this.result = new float[this.InputSize]; + this.testValue = 42; + + for (int i = 0; i < this.InputSize; i++) + { + this.input[i] = (uint)i; + } + } + + [Benchmark(Baseline = true)] + public void Standard() + { + float v = this.testValue; + for (int i = 0; i < this.input.Length; i++) + { + this.result[i] = this.input[i] / v; + } + } + + [Benchmark] + public void Simd() + { + Vector v = new Vector(this.testValue); + + for (int i = 0; i < this.input.Length; i += Vector.Count) + { + Vector a = new Vector(this.input, i); + a = a / v; + a.CopyTo(this.result, i); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/DivUInt32.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/DivUInt32.cs new file mode 100644 index 0000000000..75fa03c04b --- /dev/null +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/DivUInt32.cs @@ -0,0 +1,54 @@ +namespace ImageSharp.Benchmarks.General.Vectorization +{ + using System.Numerics; + + using BenchmarkDotNet.Attributes; + + public class DivUInt32 + { + private uint[] input; + + private uint[] result; + + [Params(32)] + public int InputSize { get; set; } + + private uint testValue; + + [Setup] + public void Setup() + { + this.input = new uint[this.InputSize]; + this.result = new uint[this.InputSize]; + this.testValue = 42; + + for (int i = 0; i < this.InputSize; i++) + { + this.input[i] = (uint)i; + } + } + + [Benchmark(Baseline = true)] + public void Standard() + { + uint v = this.testValue; + for (int i = 0; i < this.input.Length; i++) + { + this.result[i] = this.input[i] / v; + } + } + + [Benchmark] + public void Simd() + { + Vector v = new Vector(this.testValue); + + for (int i = 0; i < this.input.Length; i += Vector.Count) + { + Vector a = new Vector(this.input, i); + a = a / v; + a.CopyTo(this.result, i); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/MulFloat.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/MulFloat.cs new file mode 100644 index 0000000000..151145e128 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/MulFloat.cs @@ -0,0 +1,54 @@ +namespace ImageSharp.Benchmarks.General.Vectorization +{ + using System.Numerics; + + using BenchmarkDotNet.Attributes; + + public class MulFloat + { + private float[] input; + + private float[] result; + + [Params(32)] + public int InputSize { get; set; } + + private float testValue; + + [Setup] + public void Setup() + { + this.input = new float[this.InputSize]; + this.result = new float[this.InputSize]; + this.testValue = 42; + + for (int i = 0; i < this.InputSize; i++) + { + this.input[i] = (uint)i; + } + } + + [Benchmark(Baseline = true)] + public void Standard() + { + float v = this.testValue; + for (int i = 0; i < this.input.Length; i++) + { + this.result[i] = this.input[i] * v; + } + } + + [Benchmark] + public void Simd() + { + Vector v = new Vector(this.testValue); + + for (int i = 0; i < this.input.Length; i += Vector.Count) + { + Vector a = new Vector(this.input, i); + a = a * v; + a.CopyTo(this.result, i); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/MulUInt32.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/MulUInt32.cs new file mode 100644 index 0000000000..f7d6cf9b9c --- /dev/null +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/MulUInt32.cs @@ -0,0 +1,54 @@ +namespace ImageSharp.Benchmarks.General.Vectorization +{ + using System.Numerics; + + using BenchmarkDotNet.Attributes; + + public class MulUInt32 + { + private uint[] input; + + private uint[] result; + + [Params(32)] + public int InputSize { get; set; } + + private uint testValue; + + [Setup] + public void Setup() + { + this.input = new uint[this.InputSize]; + this.result = new uint[this.InputSize]; + this.testValue = 42; + + for (int i = 0; i < this.InputSize; i++) + { + this.input[i] = (uint)i; + } + } + + [Benchmark(Baseline = true)] + public void Standard() + { + uint v = this.testValue; + for (int i = 0; i < this.input.Length; i++) + { + this.result[i] = this.input[i] * v; + } + } + + [Benchmark] + public void Simd() + { + Vector v = new Vector(this.testValue); + + for (int i = 0; i < this.input.Length; i += Vector.Count) + { + Vector a = new Vector(this.input, i); + a = a * v; + a.CopyTo(this.result, i); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/ReinterpretUInt32AsFloat.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/ReinterpretUInt32AsFloat.cs new file mode 100644 index 0000000000..b0ca181cd6 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/ReinterpretUInt32AsFloat.cs @@ -0,0 +1,62 @@ +namespace ImageSharp.Benchmarks.General.Vectorization +{ + using System.Numerics; + using System.Runtime.InteropServices; + + using BenchmarkDotNet.Attributes; + + public class ReinterpretUInt32AsFloat + { + private uint[] input; + + private float[] result; + + [Params(32)] + public int InputSize { get; set; } + + [StructLayout(LayoutKind.Explicit)] + struct UIntFloatUnion + { + [FieldOffset(0)] + public float f; + + [FieldOffset(0)] + public uint i; + } + + + [Setup] + public void Setup() + { + this.input = new uint[this.InputSize]; + this.result = new float[this.InputSize]; + + for (int i = 0; i < this.InputSize; i++) + { + this.input[i] = (uint)i; + } + } + + [Benchmark(Baseline = true)] + public void Standard() + { + UIntFloatUnion u = default(UIntFloatUnion); + for (int i = 0; i < this.input.Length; i++) + { + u.i = this.input[i]; + this.result[i] = u.f; + } + } + + [Benchmark] + public void Simd() + { + for (int i = 0; i < this.input.Length; i += Vector.Count) + { + Vector a = new Vector(this.input, i); + Vector b = Vector.AsVectorSingle(a); + b.CopyTo(this.result, i); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj b/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj index 2444db0312..d1b059f44b 100644 --- a/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj +++ b/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj @@ -109,6 +109,10 @@ ..\..\packages\System.Reflection.Metadata.1.3.0\lib\portable-net45+win8\System.Reflection.Metadata.dll True + + ..\..\packages\System.Runtime.CompilerServices.Unsafe.4.3.0\lib\netstandard1.0\System.Runtime.CompilerServices.Unsafe.dll + True + ..\..\packages\System.Security.Cryptography.Algorithms.4.2.0\lib\net461\System.Security.Cryptography.Algorithms.dll True @@ -202,6 +206,9 @@ + + Benchmarks\PixelAccessorVirtualCopy.cs + Tests\Drawing\PolygonTests.cs @@ -327,9 +334,7 @@ - - - + diff --git a/tests/ImageSharp.Sandbox46/Program.cs b/tests/ImageSharp.Sandbox46/Program.cs index 48219902be..f289ac2db2 100644 --- a/tests/ImageSharp.Sandbox46/Program.cs +++ b/tests/ImageSharp.Sandbox46/Program.cs @@ -8,6 +8,7 @@ namespace ImageSharp.Sandbox46 using System; using System.Runtime.DesignerServices; + using ImageSharp.Benchmarks.Color.Bulk; using ImageSharp.Tests; using Xunit.Abstractions; @@ -36,7 +37,23 @@ namespace ImageSharp.Sandbox46 /// public static void Main(string[] args) { - RunDecodeJpegProfilingTests(); + //RunDecodeJpegProfilingTests(); + TestPixelAccessorCopyFromXyzw(); + Console.ReadLine(); + } + + private static void TestPixelAccessorCopyFromXyzw() + { + PixelAccessorVirtualCopy benchmark = new PixelAccessorVirtualCopy(); + benchmark.Width = 64; + benchmark.Setup(); + + benchmark.CopyRawUnsafeInlined(); + benchmark.CopyArrayPointerUnsafe(); + benchmark.CopyArrayPointerVirtualUnsafe(); + benchmark.CopyArrayPointerVirtualMarshal(); + + benchmark.Cleanup(); } private static void RunDecodeJpegProfilingTests() diff --git a/tests/ImageSharp.Sandbox46/app.config b/tests/ImageSharp.Sandbox46/app.config index 5a049c66e3..3328297a54 100644 --- a/tests/ImageSharp.Sandbox46/app.config +++ b/tests/ImageSharp.Sandbox46/app.config @@ -10,6 +10,10 @@ + + + + \ No newline at end of file diff --git a/tests/ImageSharp.Sandbox46/packages.config b/tests/ImageSharp.Sandbox46/packages.config index 65ad74fa67..426f5f1b52 100644 --- a/tests/ImageSharp.Sandbox46/packages.config +++ b/tests/ImageSharp.Sandbox46/packages.config @@ -29,6 +29,7 @@ + From 6475c52ced12a48cdbddb6a979a97cf831e90ff6 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Mon, 20 Feb 2017 01:25:11 +0100 Subject: [PATCH 085/142] additional PixelAccessorVirtualCopy Param --- .../Color/Bulk/PixelAccessorVirtualCopy.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/PixelAccessorVirtualCopy.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/PixelAccessorVirtualCopy.cs index ed649792fa..9222d6bac2 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/PixelAccessorVirtualCopy.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/PixelAccessorVirtualCopy.cs @@ -37,7 +37,7 @@ namespace ImageSharp.Benchmarks.Color.Bulk private CopyExecutor executor; - [Params(64, 256)] + [Params(64, 256, 512)] public int Width { get; set; } public int Height { get; set; } = 256; From 176dbefc105242ef92c51e62fcd8c3d4a53f5e9e Mon Sep 17 00:00:00 2001 From: Dirk Lemstra Date: Sat, 18 Feb 2017 13:16:49 +0100 Subject: [PATCH 086/142] Added options for the decoder. --- src/ImageSharp.Formats.Bmp/BmpDecoder.cs | 2 +- src/ImageSharp.Formats.Gif/GifDecoder.cs | 2 +- src/ImageSharp.Formats.Jpeg/JpegDecoder.cs | 2 +- src/ImageSharp.Formats.Png/PngDecoder.cs | 9 ++---- src/ImageSharp/Formats/DecoderOptions.cs | 18 ++++++++++++ src/ImageSharp/Formats/IDecoderOptions.cs | 18 ++++++++++++ src/ImageSharp/Formats/IImageDecoder.cs | 3 +- src/ImageSharp/Image.cs | 23 +++++++++++---- src/ImageSharp/Image/Image{TColor}.cs | 33 ++++++++++++++-------- 9 files changed, 82 insertions(+), 28 deletions(-) create mode 100644 src/ImageSharp/Formats/DecoderOptions.cs create mode 100644 src/ImageSharp/Formats/IDecoderOptions.cs diff --git a/src/ImageSharp.Formats.Bmp/BmpDecoder.cs b/src/ImageSharp.Formats.Bmp/BmpDecoder.cs index 4b7da38afc..9f490a3a9b 100644 --- a/src/ImageSharp.Formats.Bmp/BmpDecoder.cs +++ b/src/ImageSharp.Formats.Bmp/BmpDecoder.cs @@ -26,7 +26,7 @@ namespace ImageSharp.Formats public class BmpDecoder : IImageDecoder { /// - public void Decode(Image image, Stream stream) + public void Decode(Image image, Stream stream, IDecoderOptions options) where TColor : struct, IPixel { Guard.NotNull(image, "image"); diff --git a/src/ImageSharp.Formats.Gif/GifDecoder.cs b/src/ImageSharp.Formats.Gif/GifDecoder.cs index 76530dc504..aea96bb65f 100644 --- a/src/ImageSharp.Formats.Gif/GifDecoder.cs +++ b/src/ImageSharp.Formats.Gif/GifDecoder.cs @@ -14,7 +14,7 @@ namespace ImageSharp.Formats public class GifDecoder : IImageDecoder { /// - public void Decode(Image image, Stream stream) + public void Decode(Image image, Stream stream, IDecoderOptions options) where TColor : struct, IPixel { new GifDecoderCore().Decode(image, stream); diff --git a/src/ImageSharp.Formats.Jpeg/JpegDecoder.cs b/src/ImageSharp.Formats.Jpeg/JpegDecoder.cs index 435ae51cfd..a399bb6b38 100644 --- a/src/ImageSharp.Formats.Jpeg/JpegDecoder.cs +++ b/src/ImageSharp.Formats.Jpeg/JpegDecoder.cs @@ -14,7 +14,7 @@ namespace ImageSharp.Formats public class JpegDecoder : IImageDecoder { /// - public void Decode(Image image, Stream stream) + public void Decode(Image image, Stream stream, IDecoderOptions options) where TColor : struct, IPixel { Guard.NotNull(image, "image"); diff --git a/src/ImageSharp.Formats.Png/PngDecoder.cs b/src/ImageSharp.Formats.Png/PngDecoder.cs index 088bea5919..9aab6673a9 100644 --- a/src/ImageSharp.Formats.Png/PngDecoder.cs +++ b/src/ImageSharp.Formats.Png/PngDecoder.cs @@ -30,13 +30,8 @@ namespace ImageSharp.Formats /// public class PngDecoder : IImageDecoder { - /// - /// Decodes the image from the specified stream to the . - /// - /// The pixel format. - /// The to decode to. - /// The containing image data. - public void Decode(Image image, Stream stream) + /// + public void Decode(Image image, Stream stream, IDecoderOptions options) where TColor : struct, IPixel { new PngDecoderCore().Decode(image, stream); diff --git a/src/ImageSharp/Formats/DecoderOptions.cs b/src/ImageSharp/Formats/DecoderOptions.cs new file mode 100644 index 0000000000..f25e33d71f --- /dev/null +++ b/src/ImageSharp/Formats/DecoderOptions.cs @@ -0,0 +1,18 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + /// + /// Encapsulates the shared decoder options. + /// + public class DecoderOptions : IDecoderOptions + { + /// + /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. + /// + public bool IgnoreMetadata { get; set; } + } +} diff --git a/src/ImageSharp/Formats/IDecoderOptions.cs b/src/ImageSharp/Formats/IDecoderOptions.cs new file mode 100644 index 0000000000..460de3eb28 --- /dev/null +++ b/src/ImageSharp/Formats/IDecoderOptions.cs @@ -0,0 +1,18 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + /// + /// Encapsulates the shared decoder options. + /// + public interface IDecoderOptions + { + /// + /// Gets a value indicating whether the metadata should be ignored when the image is being decoded. + /// + bool IgnoreMetadata { get; } + } +} diff --git a/src/ImageSharp/Formats/IImageDecoder.cs b/src/ImageSharp/Formats/IImageDecoder.cs index 28bda7837e..df98870ddd 100644 --- a/src/ImageSharp/Formats/IImageDecoder.cs +++ b/src/ImageSharp/Formats/IImageDecoder.cs @@ -19,7 +19,8 @@ namespace ImageSharp.Formats /// The pixel format. /// The to decode to. /// The containing image data. - void Decode(Image image, Stream stream) + /// The options for the decoder. + void Decode(Image image, Stream stream, IDecoderOptions options) where TColor : struct, IPixel; } } diff --git a/src/ImageSharp/Image.cs b/src/ImageSharp/Image.cs index bdb6b49a92..b1c1252ab4 100644 --- a/src/ImageSharp/Image.cs +++ b/src/ImageSharp/Image.cs @@ -8,6 +8,8 @@ namespace ImageSharp using System.Diagnostics; using System.IO; + using Formats; + /// /// Represents an image. Each pixel is a made up four 8-bit components red, green, blue, and alpha /// packed into a single unsigned integer value. @@ -35,12 +37,15 @@ namespace ImageSharp /// /// The stream containing image information. /// + /// + /// The options for the decoder. + /// /// /// The configuration providing initialization code which allows extending the library. /// /// Thrown if the is null. - public Image(Stream stream, Configuration configuration = null) - : base(stream, configuration) + public Image(Stream stream, IDecoderOptions options = null, Configuration configuration = null) + : base(stream, options, configuration) { } @@ -51,12 +56,15 @@ namespace ImageSharp /// /// A file path to read image information. /// + /// + /// The options for the decoder. + /// /// /// The configuration providing initialization code which allows extending the library. /// /// Thrown if the is null. - public Image(string filePath, Configuration configuration = null) - : base(filePath, configuration) + public Image(string filePath, IDecoderOptions options = null, Configuration configuration = null) + : base(filePath, options, configuration) { } #endif @@ -67,12 +75,15 @@ namespace ImageSharp /// /// The byte array containing image information. /// + /// + /// The options for the decoder. + /// /// /// The configuration providing initialization code which allows extending the library. /// /// Thrown if the is null. - public Image(byte[] bytes, Configuration configuration = null) - : base(bytes, configuration) + public Image(byte[] bytes, IDecoderOptions options = null, Configuration configuration = null) + : base(bytes, options, configuration) { } diff --git a/src/ImageSharp/Image/Image{TColor}.cs b/src/ImageSharp/Image/Image{TColor}.cs index bbd839d168..9e4ecba2f5 100644 --- a/src/ImageSharp/Image/Image{TColor}.cs +++ b/src/ImageSharp/Image/Image{TColor}.cs @@ -52,15 +52,18 @@ namespace ImageSharp /// /// The stream containing image information. /// + /// + /// The options for the decoder. + /// /// /// The configuration providing initialization code which allows extending the library. /// /// Thrown if the is null. - public Image(Stream stream, Configuration configuration = null) + public Image(Stream stream, IDecoderOptions options = null, Configuration configuration = null) : base(configuration) { Guard.NotNull(stream, nameof(stream)); - this.Load(stream); + this.Load(stream, options); } #if !NETSTANDARD1_1 @@ -70,17 +73,20 @@ namespace ImageSharp /// /// The file containing image information. /// + /// + /// The options for the decoder. + /// /// /// The configuration providing initialization code which allows extending the library. /// /// Thrown if the is null. - public Image(string filePath, Configuration configuration = null) + public Image(string filePath, IDecoderOptions options = null, Configuration configuration = null) : base(configuration) { Guard.NotNull(filePath, nameof(filePath)); using (var fs = File.OpenRead(filePath)) { - this.Load(fs); + this.Load(fs, options); } } #endif @@ -91,18 +97,21 @@ namespace ImageSharp /// /// The byte array containing image information. /// + /// + /// The options for the decoder. + /// /// /// The configuration providing initialization code which allows extending the library. /// /// Thrown if the is null. - public Image(byte[] bytes, Configuration configuration = null) + public Image(byte[] bytes, IDecoderOptions options = null, Configuration configuration = null) : base(configuration) { Guard.NotNull(bytes, nameof(bytes)); using (MemoryStream stream = new MemoryStream(bytes, false)) { - this.Load(stream); + this.Load(stream, options); } } @@ -397,10 +406,11 @@ namespace ImageSharp /// Loads the image from the given stream. /// /// The stream containing image information. + /// The options for the decoder. /// /// Thrown if the stream is not readable nor seekable. /// - private void Load(Stream stream) + private void Load(Stream stream, IDecoderOptions options) { if (!this.Configuration.ImageFormats.Any()) { @@ -414,7 +424,7 @@ namespace ImageSharp if (stream.CanSeek) { - if (this.Decode(stream)) + if (this.Decode(stream, options)) { return; } @@ -427,7 +437,7 @@ namespace ImageSharp stream.CopyTo(ms); ms.Position = 0; - if (this.Decode(ms)) + if (this.Decode(ms, options)) { return; } @@ -449,10 +459,11 @@ namespace ImageSharp /// Decodes the image stream to the current image. /// /// The stream. + /// The options for the decoder. /// /// The . /// - private bool Decode(Stream stream) + private bool Decode(Stream stream, IDecoderOptions options) { int maxHeaderSize = this.Configuration.MaxHeaderSize; if (maxHeaderSize <= 0) @@ -479,7 +490,7 @@ namespace ImageSharp return false; } - format.Decoder.Decode(this, stream); + format.Decoder.Decode(this, stream, options); this.CurrentImageFormat = format; return true; } From e50825518058f2652e0d79160f001c2dd5c3d26b Mon Sep 17 00:00:00 2001 From: Dirk Lemstra Date: Sat, 18 Feb 2017 18:59:59 +0100 Subject: [PATCH 087/142] Implemented the IDecoderOptions inside the gif decoder. --- src/ImageSharp.Formats.Gif/GifDecoder.cs | 2 +- src/ImageSharp.Formats.Gif/GifDecoderCore.cs | 38 ++++++++++++--- src/ImageSharp/Formats/DecoderOptions.cs | 16 +++++- src/ImageSharp/Formats/IDecoderOptions.cs | 2 +- .../Formats/Gif/GifDecoderCoreTests.cs | 46 ++++++++++++++++++ .../TestImages/Formats/Gif/rings.gif | Bin 53435 -> 53457 bytes 6 files changed, 93 insertions(+), 11 deletions(-) create mode 100644 tests/ImageSharp.Tests/Formats/Gif/GifDecoderCoreTests.cs diff --git a/src/ImageSharp.Formats.Gif/GifDecoder.cs b/src/ImageSharp.Formats.Gif/GifDecoder.cs index aea96bb65f..42dbf4fc7b 100644 --- a/src/ImageSharp.Formats.Gif/GifDecoder.cs +++ b/src/ImageSharp.Formats.Gif/GifDecoder.cs @@ -17,7 +17,7 @@ namespace ImageSharp.Formats public void Decode(Image image, Stream stream, IDecoderOptions options) where TColor : struct, IPixel { - new GifDecoderCore().Decode(image, stream); + new GifDecoderCore(options).Decode(image, stream); } } } diff --git a/src/ImageSharp.Formats.Gif/GifDecoderCore.cs b/src/ImageSharp.Formats.Gif/GifDecoderCore.cs index 5812b9f297..50d4da0ebc 100644 --- a/src/ImageSharp.Formats.Gif/GifDecoderCore.cs +++ b/src/ImageSharp.Formats.Gif/GifDecoderCore.cs @@ -8,6 +8,7 @@ namespace ImageSharp.Formats using System; using System.Buffers; using System.IO; + using System.Text; /// /// Performs the gif decoding operation. @@ -21,6 +22,11 @@ namespace ImageSharp.Formats /// private readonly byte[] buffer = new byte[16]; + /// + /// The decoder options. + /// + private readonly IDecoderOptions options; + /// /// The image to decode the information to. /// @@ -61,6 +67,15 @@ namespace ImageSharp.Formats /// private GifGraphicsControlExtension graphicsControlExtension; + /// + /// Initializes a new instance of the class. + /// + /// The decoder options. + public GifDecoderCore(IDecoderOptions options) + { + this.options = options ?? DecoderOptions.Default; + } + /// /// Decodes the stream to the image. /// @@ -225,25 +240,32 @@ namespace ImageSharp.Formats /// private void ReadComments() { - int flag; + int length; - while ((flag = this.currentStream.ReadByte()) != 0) + while ((length = this.currentStream.ReadByte()) != 0) { - if (flag > GifConstants.MaxCommentLength) + if (length > GifConstants.MaxCommentLength) + { + throw new ImageFormatException($"Gif comment length '{length}' exceeds max '{GifConstants.MaxCommentLength}'"); + } + + if (this.options.IgnoreMetadata) { - throw new ImageFormatException($"Gif comment length '{flag}' exceeds max '{GifConstants.MaxCommentLength}'"); + this.currentStream.Seek(length, SeekOrigin.Current); + continue; } - byte[] flagBuffer = ArrayPool.Shared.Rent(flag); + byte[] commentsBuffer = ArrayPool.Shared.Rent(length); try { - this.currentStream.Read(flagBuffer, 0, flag); - this.decodedImage.MetaData.Properties.Add(new ImageProperty("Comments", BitConverter.ToString(flagBuffer, 0, flag))); + this.currentStream.Read(commentsBuffer, 0, length); + string comments = Encoding.GetEncoding("ASCII").GetString(commentsBuffer, 0, length); + this.decodedImage.MetaData.Properties.Add(new ImageProperty("Comments", comments)); } finally { - ArrayPool.Shared.Return(flagBuffer); + ArrayPool.Shared.Return(commentsBuffer); } } } diff --git a/src/ImageSharp/Formats/DecoderOptions.cs b/src/ImageSharp/Formats/DecoderOptions.cs index f25e33d71f..a9bb152f12 100644 --- a/src/ImageSharp/Formats/DecoderOptions.cs +++ b/src/ImageSharp/Formats/DecoderOptions.cs @@ -3,13 +3,27 @@ // Licensed under the Apache License, Version 2.0. // -namespace ImageSharp.Formats +namespace ImageSharp { /// /// Encapsulates the shared decoder options. /// public class DecoderOptions : IDecoderOptions { + /// + /// Gets the default decoder options + /// + public static DecoderOptions Default + { + get + { + return new DecoderOptions() + { + IgnoreMetadata = false + }; + } + } + /// /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. /// diff --git a/src/ImageSharp/Formats/IDecoderOptions.cs b/src/ImageSharp/Formats/IDecoderOptions.cs index 460de3eb28..cdfd90d5e2 100644 --- a/src/ImageSharp/Formats/IDecoderOptions.cs +++ b/src/ImageSharp/Formats/IDecoderOptions.cs @@ -3,7 +3,7 @@ // Licensed under the Apache License, Version 2.0. // -namespace ImageSharp.Formats +namespace ImageSharp { /// /// Encapsulates the shared decoder options. diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderCoreTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderCoreTests.cs new file mode 100644 index 0000000000..758c724005 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderCoreTests.cs @@ -0,0 +1,46 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using Xunit; + + public class GifDecoderCoreTests + { + [Fact] + public void Decode_IgnoreMetadataIsFalse_CommentsAreRead() + { + var options = new DecoderOptions() + { + IgnoreMetadata = false + }; + + TestFile testFile = TestFile.Create(TestImages.Gif.Rings); + + using (Image image = new Image(testFile.FilePath, options)) + { + Assert.Equal(1, image.MetaData.Properties.Count); + Assert.Equal("Comments", image.MetaData.Properties[0].Name); + Assert.Equal("ImageSharp", image.MetaData.Properties[0].Value); + } + } + + [Fact] + public void Decode_IgnoreMetadataIsTrue_CommentsAreIgnored() + { + var options = new DecoderOptions() + { + IgnoreMetadata = true + }; + + TestFile testFile = TestFile.Create(TestImages.Gif.Rings); + + using (Image image = new Image(testFile.FilePath, options)) + { + Assert.Equal(0, image.MetaData.Properties.Count); + } + } + } +} diff --git a/tests/ImageSharp.Tests/TestImages/Formats/Gif/rings.gif b/tests/ImageSharp.Tests/TestImages/Formats/Gif/rings.gif index 53f2890fa0fcfbf7944e2e6040420cd4432c2a43..76f093a20936f478ae65583c07a6156861a0f896 100644 GIT binary patch delta 37 qcmdnJkon?5<_)~eVv0Xm7(hVrAD3rtVtQ(DMq*I`!)9LQb!P$KnhX&D delta 14 Wcmcb(ka_n)<_)~eo0XZ@odp0cWd-Q~ From 0ae04148639bf2d996c7370df2b36b59af357ad7 Mon Sep 17 00:00:00 2001 From: Dirk Lemstra Date: Sat, 18 Feb 2017 20:42:10 +0100 Subject: [PATCH 088/142] Implemented the IDecoderOptions inside the jpeg decoder. --- src/ImageSharp.Formats.Jpeg/JpegDecoder.cs | 2 +- .../JpegDecoderCore.cs | 11 ++++- .../Formats/Jpg/JpegDecoderCoreTests.cs | 44 +++++++++++++++++++ .../Formats/Jpg/JpegDecoderTests.cs | 2 +- 4 files changed, 55 insertions(+), 4 deletions(-) create mode 100644 tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderCoreTests.cs diff --git a/src/ImageSharp.Formats.Jpeg/JpegDecoder.cs b/src/ImageSharp.Formats.Jpeg/JpegDecoder.cs index a399bb6b38..eeb371d1e7 100644 --- a/src/ImageSharp.Formats.Jpeg/JpegDecoder.cs +++ b/src/ImageSharp.Formats.Jpeg/JpegDecoder.cs @@ -20,7 +20,7 @@ namespace ImageSharp.Formats Guard.NotNull(image, "image"); Guard.NotNull(stream, "stream"); - using (JpegDecoderCore decoder = new JpegDecoderCore()) + using (JpegDecoderCore decoder = new JpegDecoderCore(options)) { decoder.Decode(image, stream, false); } diff --git a/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs b/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs index 6577876828..19b3df5368 100644 --- a/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs @@ -37,6 +37,11 @@ namespace ImageSharp.Formats public InputProcessor InputProcessor; #pragma warning restore SA401 + /// + /// The decoder options. + /// + private readonly IDecoderOptions options; + /// /// The App14 marker color-space /// @@ -85,8 +90,10 @@ namespace ImageSharp.Formats /// /// Initializes a new instance of the class. /// - public JpegDecoderCore() + /// The decoder options. + public JpegDecoderCore(IDecoderOptions options) { + this.options = options ?? DecoderOptions.Default; this.HuffmanTrees = HuffmanTree.CreateHuffmanTrees(); this.QuantizationTables = new Block8x8F[MaxTq + 1]; this.Temp = new byte[2 * Block8x8F.ScalarCount]; @@ -958,7 +965,7 @@ namespace ImageSharp.Formats private void ProcessApp1Marker(int remaining, Image image) where TColor : struct, IPixel { - if (remaining < 6) + if (remaining < 6 || this.options.IgnoreMetadata) { this.InputProcessor.Skip(remaining); return; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderCoreTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderCoreTests.cs new file mode 100644 index 0000000000..18d9e3acfb --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderCoreTests.cs @@ -0,0 +1,44 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using Xunit; + + public class JpegDecoderCoreTests + { + [Fact] + public void Decode_IgnoreMetadataIsFalse_ExifProfileIsRead() + { + var options = new DecoderOptions() + { + IgnoreMetadata = false + }; + + TestFile testFile = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan); + + using (Image image = new Image(testFile.FilePath, options)) + { + Assert.NotNull(image.MetaData.ExifProfile); + } + } + + [Fact] + public void Decode_IgnoreMetadataIsTrue_ExifProfileIgnored() + { + var options = new DecoderOptions() + { + IgnoreMetadata = true + }; + + TestFile testFile = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan); + + using (Image image = new Image(testFile.FilePath, options)) + { + Assert.Null(image.MetaData.ExifProfile); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index 71ce4e1652..b8ec9d49ea 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -90,7 +90,7 @@ namespace ImageSharp.Tests ms.Seek(0, SeekOrigin.Begin); Image mirror = provider.Factory.CreateImage(1, 1); - using (JpegDecoderCore decoder = new JpegDecoderCore()) + using (JpegDecoderCore decoder = new JpegDecoderCore(null)) { decoder.Decode(mirror, ms, true); From e639b2a5f6b522f771e7cad2027aa89828ed7e9c Mon Sep 17 00:00:00 2001 From: Dirk Lemstra Date: Sat, 18 Feb 2017 21:41:47 +0100 Subject: [PATCH 089/142] Use constructor instead of property to get the default decoder options. --- src/ImageSharp.Formats.Gif/GifDecoderCore.cs | 2 +- .../JpegDecoderCore.cs | 2 +- src/ImageSharp/Formats/DecoderOptions.cs | 28 ++++++++++++++----- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/src/ImageSharp.Formats.Gif/GifDecoderCore.cs b/src/ImageSharp.Formats.Gif/GifDecoderCore.cs index 50d4da0ebc..2a0d53bde7 100644 --- a/src/ImageSharp.Formats.Gif/GifDecoderCore.cs +++ b/src/ImageSharp.Formats.Gif/GifDecoderCore.cs @@ -73,7 +73,7 @@ namespace ImageSharp.Formats /// The decoder options. public GifDecoderCore(IDecoderOptions options) { - this.options = options ?? DecoderOptions.Default; + this.options = options ?? new DecoderOptions(); } /// diff --git a/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs b/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs index 19b3df5368..f1b85fa0bf 100644 --- a/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs @@ -93,7 +93,7 @@ namespace ImageSharp.Formats /// The decoder options. public JpegDecoderCore(IDecoderOptions options) { - this.options = options ?? DecoderOptions.Default; + this.options = options ?? new DecoderOptions(); this.HuffmanTrees = HuffmanTree.CreateHuffmanTrees(); this.QuantizationTables = new Block8x8F[MaxTq + 1]; this.Temp = new byte[2 * Block8x8F.ScalarCount]; diff --git a/src/ImageSharp/Formats/DecoderOptions.cs b/src/ImageSharp/Formats/DecoderOptions.cs index a9bb152f12..4755ddcb66 100644 --- a/src/ImageSharp/Formats/DecoderOptions.cs +++ b/src/ImageSharp/Formats/DecoderOptions.cs @@ -11,22 +11,36 @@ namespace ImageSharp public class DecoderOptions : IDecoderOptions { /// - /// Gets the default decoder options + /// Initializes a new instance of the class. /// - public static DecoderOptions Default + public DecoderOptions() { - get + this.InitializeWithDefaults(); + } + + /// + /// Initializes a new instance of the class. + /// + /// The decoder options + protected DecoderOptions(IDecoderOptions options) + { + if (options == null) { - return new DecoderOptions() - { - IgnoreMetadata = false - }; + this.InitializeWithDefaults(); + return; } + + this.IgnoreMetadata = options.IgnoreMetadata; } /// /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. /// public bool IgnoreMetadata { get; set; } + + private void InitializeWithDefaults() + { + this.IgnoreMetadata = false; + } } } From 340ed87176b6d5854950a07236f739ff8deba4f6 Mon Sep 17 00:00:00 2001 From: Dirk Lemstra Date: Sat, 18 Feb 2017 21:42:11 +0100 Subject: [PATCH 090/142] Implemented the IDecoderOptions inside the png decoder. --- .../IPngDecoderOptions.cs | 20 ++++++ src/ImageSharp.Formats.Png/PngDecoder.cs | 4 +- src/ImageSharp.Formats.Png/PngDecoderCore.cs | 23 ++++++- .../PngDecoderOptions.cs | 62 +++++++++++++++++ .../Formats/Png/PngDecoderCoreTests.cs | 66 +++++++++++++++++++ 5 files changed, 172 insertions(+), 3 deletions(-) create mode 100644 src/ImageSharp.Formats.Png/IPngDecoderOptions.cs create mode 100644 src/ImageSharp.Formats.Png/PngDecoderOptions.cs create mode 100644 tests/ImageSharp.Tests/Formats/Png/PngDecoderCoreTests.cs diff --git a/src/ImageSharp.Formats.Png/IPngDecoderOptions.cs b/src/ImageSharp.Formats.Png/IPngDecoderOptions.cs new file mode 100644 index 0000000000..f60f3d1cd3 --- /dev/null +++ b/src/ImageSharp.Formats.Png/IPngDecoderOptions.cs @@ -0,0 +1,20 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System.Text; + + /// + /// Encapsulates the png decoder options. + /// + public interface IPngDecoderOptions : IDecoderOptions + { + /// + /// Gets the encoding that should be used when reading text chunks. + /// + Encoding TextEncoding { get; } + } +} diff --git a/src/ImageSharp.Formats.Png/PngDecoder.cs b/src/ImageSharp.Formats.Png/PngDecoder.cs index 9aab6673a9..c731ae4480 100644 --- a/src/ImageSharp.Formats.Png/PngDecoder.cs +++ b/src/ImageSharp.Formats.Png/PngDecoder.cs @@ -34,7 +34,9 @@ namespace ImageSharp.Formats public void Decode(Image image, Stream stream, IDecoderOptions options) where TColor : struct, IPixel { - new PngDecoderCore().Decode(image, stream); + IPngDecoderOptions pngOptions = PngDecoderOptions.Create(options); + + new PngDecoderCore(pngOptions).Decode(image, stream); } } } diff --git a/src/ImageSharp.Formats.Png/PngDecoderCore.cs b/src/ImageSharp.Formats.Png/PngDecoderCore.cs index 47b09d5ffb..4a5ad36482 100644 --- a/src/ImageSharp.Formats.Png/PngDecoderCore.cs +++ b/src/ImageSharp.Formats.Png/PngDecoderCore.cs @@ -64,6 +64,11 @@ namespace ImageSharp.Formats /// private readonly char[] chars = new char[4]; + /// + /// The decoder options. + /// + private readonly IPngDecoderOptions options; + /// /// Reusable crc for validating chunks. /// @@ -120,6 +125,15 @@ namespace ImageSharp.Formats ColorTypes.Add((int)PngColorType.RgbWithAlpha, new byte[] { 8 }); } + /// + /// Initializes a new instance of the class. + /// + /// The decoder options. + public PngDecoderCore(IPngDecoderOptions options) + { + this.options = options ?? new PngDecoderOptions(); + } + /// /// Gets or sets the png color type /// @@ -763,6 +777,11 @@ namespace ImageSharp.Formats private void ReadTextChunk(Image image, byte[] data, int length) where TColor : struct, IPixel { + if (this.options.IgnoreMetadata) + { + return; + } + int zeroIndex = 0; for (int i = 0; i < length; i++) @@ -774,8 +793,8 @@ namespace ImageSharp.Formats } } - string name = Encoding.Unicode.GetString(data, 0, zeroIndex); - string value = Encoding.Unicode.GetString(data, zeroIndex + 1, length - zeroIndex - 1); + string name = this.options.TextEncoding.GetString(data, 0, zeroIndex); + string value = this.options.TextEncoding.GetString(data, zeroIndex + 1, length - zeroIndex - 1); image.MetaData.Properties.Add(new ImageProperty(name, value)); } diff --git a/src/ImageSharp.Formats.Png/PngDecoderOptions.cs b/src/ImageSharp.Formats.Png/PngDecoderOptions.cs new file mode 100644 index 0000000000..07c0c2739e --- /dev/null +++ b/src/ImageSharp.Formats.Png/PngDecoderOptions.cs @@ -0,0 +1,62 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System.Text; + + /// + /// Encapsulates the png decoder options. + /// + public sealed class PngDecoderOptions : DecoderOptions, IPngDecoderOptions + { + private static readonly Encoding DefaultEncoding = Encoding.GetEncoding("ASCII"); + + /// + /// Initializes a new instance of the class. + /// + public PngDecoderOptions() + { + this.InitializeWithDefaults(); + } + + /// + /// Initializes a new instance of the class. + /// + /// The options for the decoder. + private PngDecoderOptions(IDecoderOptions options) + : base(options) + { + this.InitializeWithDefaults(); + } + + /// + /// Gets or sets the encoding that should be used when reading text chunks. + /// + public Encoding TextEncoding { get; set; } + + /// + /// Converts the options to a instance with a cast + /// or by creating a new instance with the specfied options. + /// + /// The options for the decoder. + /// The options for the png decoder. + internal static IPngDecoderOptions Create(IDecoderOptions options) + { + IPngDecoderOptions pngOptions = options as IPngDecoderOptions; + if (pngOptions != null) + { + return pngOptions; + } + + return new PngDecoderOptions(options); + } + + private void InitializeWithDefaults() + { + this.TextEncoding = DefaultEncoding; + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderCoreTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderCoreTests.cs new file mode 100644 index 0000000000..3ee2ea826b --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderCoreTests.cs @@ -0,0 +1,66 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System.Text; + using Xunit; + + using ImageSharp.Formats; + + public class PngDecoderCoreTests + { + [Fact] + public void Decode_IgnoreMetadataIsFalse_TextChunckIsRead() + { + var options = new PngDecoderOptions() + { + IgnoreMetadata = false + }; + + TestFile testFile = TestFile.Create(TestImages.Png.Blur); + + using (Image image = new Image(testFile.FilePath, options)) + { + Assert.Equal(1, image.MetaData.Properties.Count); + Assert.Equal("Software", image.MetaData.Properties[0].Name); + Assert.Equal("paint.net 4.0.6", image.MetaData.Properties[0].Value); + } + } + + [Fact] + public void Decode_IgnoreMetadataIsTrue_TextChunksAreIgnored() + { + var options = new PngDecoderOptions() + { + IgnoreMetadata = true + }; + + TestFile testFile = TestFile.Create(TestImages.Png.Blur); + + using (Image image = new Image(testFile.FilePath, options)) + { + Assert.Equal(0, image.MetaData.Properties.Count); + } + } + + [Fact] + public void Decode_TextEncodingSetToUnicode_TextIsReadWithCorrectEncoding() + { + var options = new PngDecoderOptions() + { + TextEncoding = Encoding.Unicode + }; + + TestFile testFile = TestFile.Create(TestImages.Png.Blur); + + using (Image image = new Image(testFile.FilePath, options)) + { + Assert.Equal(1, image.MetaData.Properties.Count); + Assert.Equal("潓瑦慷敲", image.MetaData.Properties[0].Name); + } + } + } +} \ No newline at end of file From d35dc80374a63f545e5b46a423146704a04463d8 Mon Sep 17 00:00:00 2001 From: Dirk Lemstra Date: Sat, 18 Feb 2017 21:56:56 +0100 Subject: [PATCH 091/142] Also allow specification of the text encoding inside the gif decoder. --- src/ImageSharp.Formats.Gif/GifDecoder.cs | 4 +- src/ImageSharp.Formats.Gif/GifDecoderCore.cs | 8 +-- .../GifDecoderOptions.cs | 62 +++++++++++++++++++ .../IGifDecoderOptions.cs | 20 ++++++ .../Formats/Gif/GifDecoderCoreTests.cs | 20 ++++++ 5 files changed, 109 insertions(+), 5 deletions(-) create mode 100644 src/ImageSharp.Formats.Gif/GifDecoderOptions.cs create mode 100644 src/ImageSharp.Formats.Gif/IGifDecoderOptions.cs diff --git a/src/ImageSharp.Formats.Gif/GifDecoder.cs b/src/ImageSharp.Formats.Gif/GifDecoder.cs index 42dbf4fc7b..ce97689e99 100644 --- a/src/ImageSharp.Formats.Gif/GifDecoder.cs +++ b/src/ImageSharp.Formats.Gif/GifDecoder.cs @@ -17,7 +17,9 @@ namespace ImageSharp.Formats public void Decode(Image image, Stream stream, IDecoderOptions options) where TColor : struct, IPixel { - new GifDecoderCore(options).Decode(image, stream); + IGifDecoderOptions gifOptions = GifDecoderOptions.Create(options); + + new GifDecoderCore(gifOptions).Decode(image, stream); } } } diff --git a/src/ImageSharp.Formats.Gif/GifDecoderCore.cs b/src/ImageSharp.Formats.Gif/GifDecoderCore.cs index 2a0d53bde7..3ab0e6ca8b 100644 --- a/src/ImageSharp.Formats.Gif/GifDecoderCore.cs +++ b/src/ImageSharp.Formats.Gif/GifDecoderCore.cs @@ -25,7 +25,7 @@ namespace ImageSharp.Formats /// /// The decoder options. /// - private readonly IDecoderOptions options; + private readonly IGifDecoderOptions options; /// /// The image to decode the information to. @@ -71,9 +71,9 @@ namespace ImageSharp.Formats /// Initializes a new instance of the class. /// /// The decoder options. - public GifDecoderCore(IDecoderOptions options) + public GifDecoderCore(IGifDecoderOptions options) { - this.options = options ?? new DecoderOptions(); + this.options = options ?? new GifDecoderOptions(); } /// @@ -260,7 +260,7 @@ namespace ImageSharp.Formats try { this.currentStream.Read(commentsBuffer, 0, length); - string comments = Encoding.GetEncoding("ASCII").GetString(commentsBuffer, 0, length); + string comments = this.options.TextEncoding.GetString(commentsBuffer, 0, length); this.decodedImage.MetaData.Properties.Add(new ImageProperty("Comments", comments)); } finally diff --git a/src/ImageSharp.Formats.Gif/GifDecoderOptions.cs b/src/ImageSharp.Formats.Gif/GifDecoderOptions.cs new file mode 100644 index 0000000000..8bff3e38d6 --- /dev/null +++ b/src/ImageSharp.Formats.Gif/GifDecoderOptions.cs @@ -0,0 +1,62 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System.Text; + + /// + /// Encapsulates the gif decoder options. + /// + public sealed class GifDecoderOptions : DecoderOptions, IGifDecoderOptions + { + private static readonly Encoding DefaultEncoding = Encoding.GetEncoding("ASCII"); + + /// + /// Initializes a new instance of the class. + /// + public GifDecoderOptions() + { + this.InitializeWithDefaults(); + } + + /// + /// Initializes a new instance of the class. + /// + /// The options for the decoder. + private GifDecoderOptions(IDecoderOptions options) + : base(options) + { + this.InitializeWithDefaults(); + } + + /// + /// Gets the encoding that should be used when reading comments. + /// + public Encoding TextEncoding { get; set; } + + /// + /// Converts the options to a instance with a cast + /// or by creating a new instance with the specfied options. + /// + /// The options for the decoder. + /// The options for the png decoder. + internal static IGifDecoderOptions Create(IDecoderOptions options) + { + IGifDecoderOptions gifOptions = options as IGifDecoderOptions; + if (gifOptions != null) + { + return gifOptions; + } + + return new GifDecoderOptions(options); + } + + private void InitializeWithDefaults() + { + this.TextEncoding = DefaultEncoding; + } + } +} diff --git a/src/ImageSharp.Formats.Gif/IGifDecoderOptions.cs b/src/ImageSharp.Formats.Gif/IGifDecoderOptions.cs new file mode 100644 index 0000000000..dd94a616fe --- /dev/null +++ b/src/ImageSharp.Formats.Gif/IGifDecoderOptions.cs @@ -0,0 +1,20 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System.Text; + + /// + /// Encapsulates the gif decoder options. + /// + public interface IGifDecoderOptions : IDecoderOptions + { + /// + /// Gets the encoding that should be used when reading comments. + /// + Encoding TextEncoding { get; } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderCoreTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderCoreTests.cs index 758c724005..3e3010cfc0 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderCoreTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderCoreTests.cs @@ -5,8 +5,11 @@ namespace ImageSharp.Tests { + using System.Text; using Xunit; + using ImageSharp.Formats; + public class GifDecoderCoreTests { [Fact] @@ -42,5 +45,22 @@ namespace ImageSharp.Tests Assert.Equal(0, image.MetaData.Properties.Count); } } + + [Fact] + public void Decode_TextEncodingSetToUnicode_TextIsReadWithCorrectEncoding() + { + var options = new GifDecoderOptions() + { + TextEncoding = Encoding.Unicode + }; + + TestFile testFile = TestFile.Create(TestImages.Gif.Rings); + + using (Image image = new Image(testFile.FilePath, options)) + { + Assert.Equal(1, image.MetaData.Properties.Count); + Assert.Equal("浉条卥慨灲", image.MetaData.Properties[0].Value); + } + } } } From 103b17ebefa7874e30bff21b8eabf51726387561 Mon Sep 17 00:00:00 2001 From: Dirk Lemstra Date: Sun, 19 Feb 2017 09:52:01 +0100 Subject: [PATCH 092/142] StyleCop fix. --- src/ImageSharp.Formats.Gif/GifDecoderOptions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp.Formats.Gif/GifDecoderOptions.cs b/src/ImageSharp.Formats.Gif/GifDecoderOptions.cs index 8bff3e38d6..e2506db708 100644 --- a/src/ImageSharp.Formats.Gif/GifDecoderOptions.cs +++ b/src/ImageSharp.Formats.Gif/GifDecoderOptions.cs @@ -33,7 +33,7 @@ namespace ImageSharp.Formats } /// - /// Gets the encoding that should be used when reading comments. + /// Gets or sets the encoding that should be used when reading comments. /// public Encoding TextEncoding { get; set; } From eb5e8a83aff0ee0c9a426ca2a6c8841f7ca41eb5 Mon Sep 17 00:00:00 2001 From: Dirk Lemstra Date: Sun, 19 Feb 2017 14:51:52 +0100 Subject: [PATCH 093/142] Added options for the encoder. --- src/ImageSharp.Formats.Bmp/BmpEncoder.cs | 2 +- src/ImageSharp.Formats.Gif/GifEncoder.cs | 2 +- src/ImageSharp.Formats.Jpeg/JpegEncoder.cs | 2 +- src/ImageSharp.Formats.Png/PngEncoder.cs | 2 +- src/ImageSharp/Formats/EncoderOptions.cs | 46 ++++++++++++++++++++++ src/ImageSharp/Formats/IEncoderOptions.cs | 18 +++++++++ src/ImageSharp/Formats/IImageEncoder.cs | 3 +- src/ImageSharp/Image/Image{TColor}.cs | 16 ++++---- 8 files changed, 79 insertions(+), 12 deletions(-) create mode 100644 src/ImageSharp/Formats/EncoderOptions.cs create mode 100644 src/ImageSharp/Formats/IEncoderOptions.cs diff --git a/src/ImageSharp.Formats.Bmp/BmpEncoder.cs b/src/ImageSharp.Formats.Bmp/BmpEncoder.cs index 6edaf178bc..ea13422721 100644 --- a/src/ImageSharp.Formats.Bmp/BmpEncoder.cs +++ b/src/ImageSharp.Formats.Bmp/BmpEncoder.cs @@ -20,7 +20,7 @@ namespace ImageSharp.Formats public BmpBitsPerPixel BitsPerPixel { get; set; } = BmpBitsPerPixel.Pixel24; /// - public void Encode(Image image, Stream stream) + public void Encode(Image image, Stream stream, IEncoderOptions options) where TColor : struct, IPixel { BmpEncoderCore encoder = new BmpEncoderCore(); diff --git a/src/ImageSharp.Formats.Gif/GifEncoder.cs b/src/ImageSharp.Formats.Gif/GifEncoder.cs index de7e03322d..402d2d09a1 100644 --- a/src/ImageSharp.Formats.Gif/GifEncoder.cs +++ b/src/ImageSharp.Formats.Gif/GifEncoder.cs @@ -32,7 +32,7 @@ namespace ImageSharp.Formats public IQuantizer Quantizer { get; set; } /// - public void Encode(Image image, Stream stream) + public void Encode(Image image, Stream stream, IEncoderOptions options) where TColor : struct, IPixel { GifEncoderCore encoder = new GifEncoderCore diff --git a/src/ImageSharp.Formats.Jpeg/JpegEncoder.cs b/src/ImageSharp.Formats.Jpeg/JpegEncoder.cs index 07d9b24cd9..46972cd93d 100644 --- a/src/ImageSharp.Formats.Jpeg/JpegEncoder.cs +++ b/src/ImageSharp.Formats.Jpeg/JpegEncoder.cs @@ -61,7 +61,7 @@ namespace ImageSharp.Formats } /// - public void Encode(Image image, Stream stream) + public void Encode(Image image, Stream stream, IEncoderOptions options) where TColor : struct, IPixel { // Ensure that quality can be set but has a fallback. diff --git a/src/ImageSharp.Formats.Png/PngEncoder.cs b/src/ImageSharp.Formats.Png/PngEncoder.cs index ba790f4ae9..8d9c4bc085 100644 --- a/src/ImageSharp.Formats.Png/PngEncoder.cs +++ b/src/ImageSharp.Formats.Png/PngEncoder.cs @@ -56,7 +56,7 @@ namespace ImageSharp.Formats public bool WriteGamma { get; set; } /// - public void Encode(Image image, Stream stream) + public void Encode(Image image, Stream stream, IEncoderOptions options) where TColor : struct, IPixel { PngEncoderCore encoder = new PngEncoderCore diff --git a/src/ImageSharp/Formats/EncoderOptions.cs b/src/ImageSharp/Formats/EncoderOptions.cs new file mode 100644 index 0000000000..1f92927f14 --- /dev/null +++ b/src/ImageSharp/Formats/EncoderOptions.cs @@ -0,0 +1,46 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// Encapsulates the shared encoder options. + /// + public class EncoderOptions : IEncoderOptions + { + /// + /// Initializes a new instance of the class. + /// + public EncoderOptions() + { + this.InitializeWithDefaults(); + } + + /// + /// Initializes a new instance of the class. + /// + /// The encoder options + protected EncoderOptions(IEncoderOptions options) + { + if (options == null) + { + this.InitializeWithDefaults(); + return; + } + + this.IgnoreMetadata = options.IgnoreMetadata; + } + + /// + /// Gets or sets a value indicating whether the metadata should be ignored when the image is being encoded. + /// + public bool IgnoreMetadata { get; set; } + + private void InitializeWithDefaults() + { + this.IgnoreMetadata = false; + } + } +} diff --git a/src/ImageSharp/Formats/IEncoderOptions.cs b/src/ImageSharp/Formats/IEncoderOptions.cs new file mode 100644 index 0000000000..0fd3d1c438 --- /dev/null +++ b/src/ImageSharp/Formats/IEncoderOptions.cs @@ -0,0 +1,18 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// Encapsulates the shared encoder options. + /// + public interface IEncoderOptions + { + /// + /// Gets a value indicating whether the metadata should be ignored when the image is being encoded. + /// + bool IgnoreMetadata { get; } + } +} diff --git a/src/ImageSharp/Formats/IImageEncoder.cs b/src/ImageSharp/Formats/IImageEncoder.cs index 0ba56477ac..918f0d273c 100644 --- a/src/ImageSharp/Formats/IImageEncoder.cs +++ b/src/ImageSharp/Formats/IImageEncoder.cs @@ -19,7 +19,8 @@ namespace ImageSharp.Formats /// The pixel format. /// The to encode from. /// The to encode the image data to. - void Encode(Image image, Stream stream) + /// The options for the encoder. + void Encode(Image image, Stream stream, IEncoderOptions options) where TColor : struct, IPixel; } } diff --git a/src/ImageSharp/Image/Image{TColor}.cs b/src/ImageSharp/Image/Image{TColor}.cs index 9e4ecba2f5..d8890f5faf 100644 --- a/src/ImageSharp/Image/Image{TColor}.cs +++ b/src/ImageSharp/Image/Image{TColor}.cs @@ -206,12 +206,13 @@ namespace ImageSharp /// Saves the image to the given stream using the currently loaded image format. /// /// The stream to save the image to. + /// The options for the encoder. /// Thrown if the stream is null. /// The - public Image Save(Stream stream) + public Image Save(Stream stream, IEncoderOptions options = null) { Guard.NotNull(stream, nameof(stream)); - this.CurrentImageFormat.Encoder.Encode(this, stream); + this.CurrentImageFormat.Encoder.Encode(this, stream, options); return this; } @@ -220,13 +221,13 @@ namespace ImageSharp /// /// The stream to save the image to. /// The format to save the image as. - /// Thrown if the stream or format is null. + /// The options for the encoder. /// The - public Image Save(Stream stream, IImageFormat format) + public Image Save(Stream stream, IImageFormat format, IEncoderOptions options = null) { Guard.NotNull(stream, nameof(stream)); Guard.NotNull(format, nameof(format)); - format.Encoder.Encode(this, stream); + format.Encoder.Encode(this, stream, options); return this; } @@ -235,15 +236,16 @@ namespace ImageSharp /// /// The stream to save the image to. /// The encoder to save the image with. + /// The options for the encoder. /// Thrown if the stream or encoder is null. /// /// The . /// - public Image Save(Stream stream, IImageEncoder encoder) + public Image Save(Stream stream, IImageEncoder encoder, IEncoderOptions options = null) { Guard.NotNull(stream, nameof(stream)); Guard.NotNull(encoder, nameof(encoder)); - encoder.Encode(this, stream); + encoder.Encode(this, stream, options); // Reset to the start of the stream. if (stream.CanSeek) From dec71b0425ac3819c0aeb8f5a3ce167860ae48ec Mon Sep 17 00:00:00 2001 From: Dirk Lemstra Date: Sun, 19 Feb 2017 14:55:42 +0100 Subject: [PATCH 094/142] Added overloads to the decoder that use the coder specific options. --- src/ImageSharp.Formats.Gif/GifDecoder.cs | 15 ++++++++++++++- src/ImageSharp.Formats.Png/PngDecoder.cs | 15 ++++++++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp.Formats.Gif/GifDecoder.cs b/src/ImageSharp.Formats.Gif/GifDecoder.cs index ce97689e99..88a3d5e437 100644 --- a/src/ImageSharp.Formats.Gif/GifDecoder.cs +++ b/src/ImageSharp.Formats.Gif/GifDecoder.cs @@ -19,7 +19,20 @@ namespace ImageSharp.Formats { IGifDecoderOptions gifOptions = GifDecoderOptions.Create(options); - new GifDecoderCore(gifOptions).Decode(image, stream); + this.Decode(image, stream, gifOptions); + } + + /// + /// Decodes the image from the specified stream to the . + /// + /// The pixel format. + /// The to decode to. + /// The containing image data. + /// The options for the decoder. + public void Decode(Image image, Stream stream, IGifDecoderOptions options) + where TColor : struct, IPackedPixel, IEquatable + { + new GifDecoderCore(options).Decode(image, stream); } } } diff --git a/src/ImageSharp.Formats.Png/PngDecoder.cs b/src/ImageSharp.Formats.Png/PngDecoder.cs index c731ae4480..658de9e664 100644 --- a/src/ImageSharp.Formats.Png/PngDecoder.cs +++ b/src/ImageSharp.Formats.Png/PngDecoder.cs @@ -36,7 +36,20 @@ namespace ImageSharp.Formats { IPngDecoderOptions pngOptions = PngDecoderOptions.Create(options); - new PngDecoderCore(pngOptions).Decode(image, stream); + this.Decode(image, stream, pngOptions); + } + + /// + /// Decodes the image from the specified stream to the . + /// + /// The pixel format. + /// The to decode to. + /// The containing image data. + /// The options for the decoder. + public void Decode(Image image, Stream stream, IPngDecoderOptions options) + where TColor : struct, IPackedPixel, IEquatable + { + new PngDecoderCore(options).Decode(image, stream); } } } From 36dd507874867ade736dd8554c6d4106c544b8d7 Mon Sep 17 00:00:00 2001 From: Dirk Lemstra Date: Sun, 19 Feb 2017 15:01:44 +0100 Subject: [PATCH 095/142] Nicer initialization of the options. --- src/ImageSharp.Formats.Gif/GifDecoderOptions.cs | 9 +-------- src/ImageSharp.Formats.Png/PngDecoderOptions.cs | 9 +-------- src/ImageSharp/Formats/DecoderOptions.cs | 15 +++------------ src/ImageSharp/Formats/EncoderOptions.cs | 15 +++------------ 4 files changed, 8 insertions(+), 40 deletions(-) diff --git a/src/ImageSharp.Formats.Gif/GifDecoderOptions.cs b/src/ImageSharp.Formats.Gif/GifDecoderOptions.cs index e2506db708..ff8ad19df8 100644 --- a/src/ImageSharp.Formats.Gif/GifDecoderOptions.cs +++ b/src/ImageSharp.Formats.Gif/GifDecoderOptions.cs @@ -19,7 +19,6 @@ namespace ImageSharp.Formats /// public GifDecoderOptions() { - this.InitializeWithDefaults(); } /// @@ -29,13 +28,12 @@ namespace ImageSharp.Formats private GifDecoderOptions(IDecoderOptions options) : base(options) { - this.InitializeWithDefaults(); } /// /// Gets or sets the encoding that should be used when reading comments. /// - public Encoding TextEncoding { get; set; } + public Encoding TextEncoding { get; set; } = DefaultEncoding; /// /// Converts the options to a instance with a cast @@ -53,10 +51,5 @@ namespace ImageSharp.Formats return new GifDecoderOptions(options); } - - private void InitializeWithDefaults() - { - this.TextEncoding = DefaultEncoding; - } } } diff --git a/src/ImageSharp.Formats.Png/PngDecoderOptions.cs b/src/ImageSharp.Formats.Png/PngDecoderOptions.cs index 07c0c2739e..8630cd1b09 100644 --- a/src/ImageSharp.Formats.Png/PngDecoderOptions.cs +++ b/src/ImageSharp.Formats.Png/PngDecoderOptions.cs @@ -19,7 +19,6 @@ namespace ImageSharp.Formats /// public PngDecoderOptions() { - this.InitializeWithDefaults(); } /// @@ -29,13 +28,12 @@ namespace ImageSharp.Formats private PngDecoderOptions(IDecoderOptions options) : base(options) { - this.InitializeWithDefaults(); } /// /// Gets or sets the encoding that should be used when reading text chunks. /// - public Encoding TextEncoding { get; set; } + public Encoding TextEncoding { get; set; } = DefaultEncoding; /// /// Converts the options to a instance with a cast @@ -53,10 +51,5 @@ namespace ImageSharp.Formats return new PngDecoderOptions(options); } - - private void InitializeWithDefaults() - { - this.TextEncoding = DefaultEncoding; - } } } diff --git a/src/ImageSharp/Formats/DecoderOptions.cs b/src/ImageSharp/Formats/DecoderOptions.cs index 4755ddcb66..5257b07b39 100644 --- a/src/ImageSharp/Formats/DecoderOptions.cs +++ b/src/ImageSharp/Formats/DecoderOptions.cs @@ -15,7 +15,6 @@ namespace ImageSharp /// public DecoderOptions() { - this.InitializeWithDefaults(); } /// @@ -24,23 +23,15 @@ namespace ImageSharp /// The decoder options protected DecoderOptions(IDecoderOptions options) { - if (options == null) + if (options != null) { - this.InitializeWithDefaults(); - return; + this.IgnoreMetadata = options.IgnoreMetadata; } - - this.IgnoreMetadata = options.IgnoreMetadata; } /// /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. /// - public bool IgnoreMetadata { get; set; } - - private void InitializeWithDefaults() - { - this.IgnoreMetadata = false; - } + public bool IgnoreMetadata { get; set; } = false; } } diff --git a/src/ImageSharp/Formats/EncoderOptions.cs b/src/ImageSharp/Formats/EncoderOptions.cs index 1f92927f14..27a7e9781d 100644 --- a/src/ImageSharp/Formats/EncoderOptions.cs +++ b/src/ImageSharp/Formats/EncoderOptions.cs @@ -15,7 +15,6 @@ namespace ImageSharp /// public EncoderOptions() { - this.InitializeWithDefaults(); } /// @@ -24,23 +23,15 @@ namespace ImageSharp /// The encoder options protected EncoderOptions(IEncoderOptions options) { - if (options == null) + if (options != null) { - this.InitializeWithDefaults(); - return; + this.IgnoreMetadata = options.IgnoreMetadata; } - - this.IgnoreMetadata = options.IgnoreMetadata; } /// /// Gets or sets a value indicating whether the metadata should be ignored when the image is being encoded. /// - public bool IgnoreMetadata { get; set; } - - private void InitializeWithDefaults() - { - this.IgnoreMetadata = false; - } + public bool IgnoreMetadata { get; set; } = false; } } From 6fce07c3349c11424d9da125138f46f6b1abd0e7 Mon Sep 17 00:00:00 2001 From: Dirk Lemstra Date: Sun, 19 Feb 2017 15:08:05 +0100 Subject: [PATCH 096/142] Minor comment change. --- src/ImageSharp.Formats.Gif/GifDecoderOptions.cs | 4 ++-- src/ImageSharp.Formats.Gif/IGifDecoderOptions.cs | 2 +- src/ImageSharp.Formats.Png/IPngDecoderOptions.cs | 2 +- src/ImageSharp.Formats.Png/PngDecoderOptions.cs | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp.Formats.Gif/GifDecoderOptions.cs b/src/ImageSharp.Formats.Gif/GifDecoderOptions.cs index ff8ad19df8..8184c19d3f 100644 --- a/src/ImageSharp.Formats.Gif/GifDecoderOptions.cs +++ b/src/ImageSharp.Formats.Gif/GifDecoderOptions.cs @@ -8,7 +8,7 @@ namespace ImageSharp.Formats using System.Text; /// - /// Encapsulates the gif decoder options. + /// Encapsulates the options for the . /// public sealed class GifDecoderOptions : DecoderOptions, IGifDecoderOptions { @@ -40,7 +40,7 @@ namespace ImageSharp.Formats /// or by creating a new instance with the specfied options. /// /// The options for the decoder. - /// The options for the png decoder. + /// The options for the . internal static IGifDecoderOptions Create(IDecoderOptions options) { IGifDecoderOptions gifOptions = options as IGifDecoderOptions; diff --git a/src/ImageSharp.Formats.Gif/IGifDecoderOptions.cs b/src/ImageSharp.Formats.Gif/IGifDecoderOptions.cs index dd94a616fe..729bf1d111 100644 --- a/src/ImageSharp.Formats.Gif/IGifDecoderOptions.cs +++ b/src/ImageSharp.Formats.Gif/IGifDecoderOptions.cs @@ -8,7 +8,7 @@ namespace ImageSharp.Formats using System.Text; /// - /// Encapsulates the gif decoder options. + /// Encapsulates the options for the . /// public interface IGifDecoderOptions : IDecoderOptions { diff --git a/src/ImageSharp.Formats.Png/IPngDecoderOptions.cs b/src/ImageSharp.Formats.Png/IPngDecoderOptions.cs index f60f3d1cd3..cc6d194bfc 100644 --- a/src/ImageSharp.Formats.Png/IPngDecoderOptions.cs +++ b/src/ImageSharp.Formats.Png/IPngDecoderOptions.cs @@ -8,7 +8,7 @@ namespace ImageSharp.Formats using System.Text; /// - /// Encapsulates the png decoder options. + /// Encapsulates the options for the . /// public interface IPngDecoderOptions : IDecoderOptions { diff --git a/src/ImageSharp.Formats.Png/PngDecoderOptions.cs b/src/ImageSharp.Formats.Png/PngDecoderOptions.cs index 8630cd1b09..83716e5d1b 100644 --- a/src/ImageSharp.Formats.Png/PngDecoderOptions.cs +++ b/src/ImageSharp.Formats.Png/PngDecoderOptions.cs @@ -8,7 +8,7 @@ namespace ImageSharp.Formats using System.Text; /// - /// Encapsulates the png decoder options. + /// Encapsulates the options for the . /// public sealed class PngDecoderOptions : DecoderOptions, IPngDecoderOptions { @@ -40,7 +40,7 @@ namespace ImageSharp.Formats /// or by creating a new instance with the specfied options. /// /// The options for the decoder. - /// The options for the png decoder. + /// The options for the . internal static IPngDecoderOptions Create(IDecoderOptions options) { IPngDecoderOptions pngOptions = options as IPngDecoderOptions; From 38e7a2dc1b238088c9e10cdd9e2536bce7d5dfcb Mon Sep 17 00:00:00 2001 From: Dirk Lemstra Date: Sun, 19 Feb 2017 15:32:16 +0100 Subject: [PATCH 097/142] Added extra overload for TestFile.CreateImage and used it in the tests. --- .../Formats/Gif/GifDecoderCoreTests.cs | 6 +++--- .../Formats/Jpg/JpegDecoderCoreTests.cs | 4 ++-- .../Formats/Png/PngDecoderCoreTests.cs | 6 +++--- tests/ImageSharp.Tests/TestFile.cs | 12 ++++++++++++ 4 files changed, 20 insertions(+), 8 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderCoreTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderCoreTests.cs index 3e3010cfc0..e1e3e94b09 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderCoreTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderCoreTests.cs @@ -22,7 +22,7 @@ namespace ImageSharp.Tests TestFile testFile = TestFile.Create(TestImages.Gif.Rings); - using (Image image = new Image(testFile.FilePath, options)) + using (Image image = testFile.CreateImage(options)) { Assert.Equal(1, image.MetaData.Properties.Count); Assert.Equal("Comments", image.MetaData.Properties[0].Name); @@ -40,7 +40,7 @@ namespace ImageSharp.Tests TestFile testFile = TestFile.Create(TestImages.Gif.Rings); - using (Image image = new Image(testFile.FilePath, options)) + using (Image image = testFile.CreateImage(options)) { Assert.Equal(0, image.MetaData.Properties.Count); } @@ -56,7 +56,7 @@ namespace ImageSharp.Tests TestFile testFile = TestFile.Create(TestImages.Gif.Rings); - using (Image image = new Image(testFile.FilePath, options)) + using (Image image = testFile.CreateImage(options)) { Assert.Equal(1, image.MetaData.Properties.Count); Assert.Equal("浉条卥慨灲", image.MetaData.Properties[0].Value); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderCoreTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderCoreTests.cs index 18d9e3acfb..38d2a455e9 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderCoreTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderCoreTests.cs @@ -19,7 +19,7 @@ namespace ImageSharp.Tests TestFile testFile = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan); - using (Image image = new Image(testFile.FilePath, options)) + using (Image image = testFile.CreateImage(options)) { Assert.NotNull(image.MetaData.ExifProfile); } @@ -35,7 +35,7 @@ namespace ImageSharp.Tests TestFile testFile = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan); - using (Image image = new Image(testFile.FilePath, options)) + using (Image image = testFile.CreateImage(options)) { Assert.Null(image.MetaData.ExifProfile); } diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderCoreTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderCoreTests.cs index 3ee2ea826b..766172ec65 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderCoreTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderCoreTests.cs @@ -22,7 +22,7 @@ namespace ImageSharp.Tests TestFile testFile = TestFile.Create(TestImages.Png.Blur); - using (Image image = new Image(testFile.FilePath, options)) + using (Image image = testFile.CreateImage(options)) { Assert.Equal(1, image.MetaData.Properties.Count); Assert.Equal("Software", image.MetaData.Properties[0].Name); @@ -40,7 +40,7 @@ namespace ImageSharp.Tests TestFile testFile = TestFile.Create(TestImages.Png.Blur); - using (Image image = new Image(testFile.FilePath, options)) + using (Image image = testFile.CreateImage(options)) { Assert.Equal(0, image.MetaData.Properties.Count); } @@ -56,7 +56,7 @@ namespace ImageSharp.Tests TestFile testFile = TestFile.Create(TestImages.Png.Blur); - using (Image image = new Image(testFile.FilePath, options)) + using (Image image = testFile.CreateImage(options)) { Assert.Equal(1, image.MetaData.Properties.Count); Assert.Equal("潓瑦慷敲", image.MetaData.Properties[0].Name); diff --git a/tests/ImageSharp.Tests/TestFile.cs b/tests/ImageSharp.Tests/TestFile.cs index 891a45cec9..0c9cc5f47c 100644 --- a/tests/ImageSharp.Tests/TestFile.cs +++ b/tests/ImageSharp.Tests/TestFile.cs @@ -130,6 +130,18 @@ namespace ImageSharp.Tests return new Image(this.image); } + /// + /// Creates a new image. + /// + /// The options for the decoder. + /// + /// The . + /// + public Image CreateImage(IDecoderOptions options) + { + return new Image(this.Bytes, options); + } + /// /// Gets the correct path to the formats directory. /// From 1da9994a1673fe820986fe804c5638506a19c4c1 Mon Sep 17 00:00:00 2001 From: Dirk Lemstra Date: Sun, 19 Feb 2017 19:02:21 +0100 Subject: [PATCH 098/142] Added options argument to the filePath overloads. --- src/ImageSharp/Image/Image{TColor}.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Image/Image{TColor}.cs b/src/ImageSharp/Image/Image{TColor}.cs index d8890f5faf..69b99ce13a 100644 --- a/src/ImageSharp/Image/Image{TColor}.cs +++ b/src/ImageSharp/Image/Image{TColor}.cs @@ -261,9 +261,10 @@ namespace ImageSharp /// Saves the image to the given stream using the currently loaded image format. /// /// The file path to save the image to. + /// The options for the encoder. /// Thrown if the stream is null. /// The - public Image Save(string filePath) + public Image Save(string filePath, IEncoderOptions options = null) { string ext = Path.GetExtension(filePath).Trim('.'); IImageFormat format = this.Configuration.ImageFormats.SingleOrDefault(f => f.SupportedExtensions.Contains(ext, StringComparer.OrdinalIgnoreCase)); @@ -280,9 +281,10 @@ namespace ImageSharp /// /// The file path to save the image to. /// The format to save the image as. + /// The options for the encoder. /// Thrown if the format is null. /// The - public Image Save(string filePath, IImageFormat format) + public Image Save(string filePath, IImageFormat format, IEncoderOptions options = null) { Guard.NotNull(format, nameof(format)); using (FileStream fs = File.Create(filePath)) @@ -296,9 +298,10 @@ namespace ImageSharp /// /// The file path to save the image to. /// The encoder to save the image with. + /// The options for the encoder. /// Thrown if the encoder is null. /// The - public Image Save(string filePath, IImageEncoder encoder) + public Image Save(string filePath, IImageEncoder encoder, IEncoderOptions options = null) { Guard.NotNull(encoder, nameof(encoder)); using (FileStream fs = File.Create(filePath)) From 19c81c5d8ebd61cbedb75ef191dc9f29dd5fe8cc Mon Sep 17 00:00:00 2001 From: Dirk Lemstra Date: Sun, 19 Feb 2017 19:02:55 +0100 Subject: [PATCH 099/142] Implemented the IEncoderOptions inside the bmp encoder. --- src/ImageSharp.Formats.Bmp/BmpEncoder.cs | 24 ++++++--- src/ImageSharp.Formats.Bmp/BmpEncoderCore.cs | 24 +++++---- .../BmpEncoderOptions.cs | 51 +++++++++++++++++++ .../IBmpEncoderOptions.cs | 18 +++++++ .../Formats/Bmp/BitmapTests.cs | 6 +-- 5 files changed, 103 insertions(+), 20 deletions(-) create mode 100644 src/ImageSharp.Formats.Bmp/BmpEncoderOptions.cs create mode 100644 src/ImageSharp.Formats.Bmp/IBmpEncoderOptions.cs diff --git a/src/ImageSharp.Formats.Bmp/BmpEncoder.cs b/src/ImageSharp.Formats.Bmp/BmpEncoder.cs index ea13422721..2c0bb5b5d5 100644 --- a/src/ImageSharp.Formats.Bmp/BmpEncoder.cs +++ b/src/ImageSharp.Formats.Bmp/BmpEncoder.cs @@ -14,17 +14,27 @@ namespace ImageSharp.Formats /// The encoder can currently only write 24-bit rgb images to streams. public class BmpEncoder : IImageEncoder { - /// - /// Gets or sets the number of bits per pixel. - /// - public BmpBitsPerPixel BitsPerPixel { get; set; } = BmpBitsPerPixel.Pixel24; - /// public void Encode(Image image, Stream stream, IEncoderOptions options) where TColor : struct, IPixel { - BmpEncoderCore encoder = new BmpEncoderCore(); - encoder.Encode(image, stream, this.BitsPerPixel); + IBmpEncoderOptions bmpOptions = BmpEncoderOptions.Create(options); + + this.Encode(image, stream, bmpOptions); + } + + /// + /// Encodes the image to the specified stream from the . + /// + /// The pixel format. + /// The to encode from. + /// The to encode the image data to. + /// The options for the encoder. + public void Encode(Image image, Stream stream, IBmpEncoderOptions options) + where TColor : struct, IPackedPixel, IEquatable + { + BmpEncoderCore encoder = new BmpEncoderCore(options); + encoder.Encode(image, stream); } } } diff --git a/src/ImageSharp.Formats.Bmp/BmpEncoderCore.cs b/src/ImageSharp.Formats.Bmp/BmpEncoderCore.cs index 02d270a0ad..9696eb66dd 100644 --- a/src/ImageSharp.Formats.Bmp/BmpEncoderCore.cs +++ b/src/ImageSharp.Formats.Bmp/BmpEncoderCore.cs @@ -16,34 +16,40 @@ namespace ImageSharp.Formats internal sealed class BmpEncoderCore { /// - /// The number of bits per pixel. + /// The options for the encoder. /// - private BmpBitsPerPixel bmpBitsPerPixel; + private IBmpEncoderOptions options; /// /// The amount to pad each row by. /// private int padding; + /// + /// Initializes a new instance of the class. + /// + /// The options for the encoder. + public BmpEncoderCore(IBmpEncoderOptions options) + { + this.options = options ?? new BmpEncoderOptions(); + } + /// /// Encodes the image to the specified stream from the . /// /// The pixel format. /// The to encode from. /// The to encode the image data to. - /// The - public void Encode(ImageBase image, Stream stream, BmpBitsPerPixel bitsPerPixel) + public void Encode(ImageBase image, Stream stream) where TColor : struct, IPixel { Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); - this.bmpBitsPerPixel = bitsPerPixel; - // Cast to int will get the bytes per pixel - short bpp = (short)(8 * (int)bitsPerPixel); + short bpp = (short)(8 * (int)this.options.BitsPerPixel); int bytesPerLine = 4 * (((image.Width * bpp) + 31) / 32); - this.padding = bytesPerLine - (image.Width * (int)bitsPerPixel); + this.padding = bytesPerLine - (image.Width * (int)this.options.BitsPerPixel); // Do not use IDisposable pattern here as we want to preserve the stream. EndianBinaryWriter writer = new EndianBinaryWriter(Endianness.LittleEndian, stream); @@ -128,7 +134,7 @@ namespace ImageSharp.Formats { using (PixelAccessor pixels = image.Lock()) { - switch (this.bmpBitsPerPixel) + switch (this.options.BitsPerPixel) { case BmpBitsPerPixel.Pixel32: this.Write32Bit(writer, pixels); diff --git a/src/ImageSharp.Formats.Bmp/BmpEncoderOptions.cs b/src/ImageSharp.Formats.Bmp/BmpEncoderOptions.cs new file mode 100644 index 0000000000..f0106b4815 --- /dev/null +++ b/src/ImageSharp.Formats.Bmp/BmpEncoderOptions.cs @@ -0,0 +1,51 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + /// + /// Encapsulates the options for the . + /// + public sealed class BmpEncoderOptions : EncoderOptions, IBmpEncoderOptions + { + /// + /// Initializes a new instance of the class. + /// + public BmpEncoderOptions() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The options for the encoder. + private BmpEncoderOptions(IEncoderOptions options) + : base(options) + { + } + + /// + /// Gets or sets the number of bits per pixel. + /// + public BmpBitsPerPixel BitsPerPixel { get; set; } = BmpBitsPerPixel.Pixel24; + + /// + /// Converts the options to a instance with a cast + /// or by creating a new instance with the specfied options. + /// + /// The options for the encoder. + /// The options for the . + internal static IBmpEncoderOptions Create(IEncoderOptions options) + { + IBmpEncoderOptions bmpOptions = options as IBmpEncoderOptions; + if (bmpOptions != null) + { + return bmpOptions; + } + + return new BmpEncoderOptions(options); + } + } +} diff --git a/src/ImageSharp.Formats.Bmp/IBmpEncoderOptions.cs b/src/ImageSharp.Formats.Bmp/IBmpEncoderOptions.cs new file mode 100644 index 0000000000..6cf37cbae3 --- /dev/null +++ b/src/ImageSharp.Formats.Bmp/IBmpEncoderOptions.cs @@ -0,0 +1,18 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + /// + /// Encapsulates the options for the . + /// + public interface IBmpEncoderOptions : IEncoderOptions + { + /// + /// Gets the number of bits per pixel. + /// + BmpBitsPerPixel BitsPerPixel { get; } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BitmapTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BitmapTests.cs index c1275335d2..7579cc86e5 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BitmapTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BitmapTests.cs @@ -7,8 +7,6 @@ using ImageSharp.Formats; namespace ImageSharp.Tests { - using System.IO; - using Xunit; public class BitmapTests : FileTestBase @@ -16,7 +14,7 @@ namespace ImageSharp.Tests public static readonly TheoryData BitsPerPixel = new TheoryData { - BmpBitsPerPixel.Pixel24 , + BmpBitsPerPixel.Pixel24, BmpBitsPerPixel.Pixel32 }; @@ -31,7 +29,7 @@ namespace ImageSharp.Tests string filename = file.GetFileNameWithoutExtension(bitsPerPixel); using (Image image = file.CreateImage()) { - image.Save($"{path}/{filename}.bmp", new BmpEncoder { BitsPerPixel = bitsPerPixel }); + image.Save($"{path}/{filename}.bmp", new BmpEncoderOptions { BitsPerPixel = bitsPerPixel }); } } } From c1aa509caea2cc9f1e0cf5876fd3fb0d6af66f0e Mon Sep 17 00:00:00 2001 From: Dirk Lemstra Date: Sun, 19 Feb 2017 19:11:05 +0100 Subject: [PATCH 100/142] Comment fixes. --- src/ImageSharp.Formats.Bmp/BmpEncoderOptions.cs | 2 +- src/ImageSharp.Formats.Gif/GifDecoderOptions.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp.Formats.Bmp/BmpEncoderOptions.cs b/src/ImageSharp.Formats.Bmp/BmpEncoderOptions.cs index f0106b4815..2c06d8a665 100644 --- a/src/ImageSharp.Formats.Bmp/BmpEncoderOptions.cs +++ b/src/ImageSharp.Formats.Bmp/BmpEncoderOptions.cs @@ -32,7 +32,7 @@ namespace ImageSharp.Formats public BmpBitsPerPixel BitsPerPixel { get; set; } = BmpBitsPerPixel.Pixel24; /// - /// Converts the options to a instance with a cast + /// Converts the options to a instance with a cast /// or by creating a new instance with the specfied options. /// /// The options for the encoder. diff --git a/src/ImageSharp.Formats.Gif/GifDecoderOptions.cs b/src/ImageSharp.Formats.Gif/GifDecoderOptions.cs index 8184c19d3f..5932425e5a 100644 --- a/src/ImageSharp.Formats.Gif/GifDecoderOptions.cs +++ b/src/ImageSharp.Formats.Gif/GifDecoderOptions.cs @@ -36,7 +36,7 @@ namespace ImageSharp.Formats public Encoding TextEncoding { get; set; } = DefaultEncoding; /// - /// Converts the options to a instance with a cast + /// Converts the options to a instance with a cast /// or by creating a new instance with the specfied options. /// /// The options for the decoder. From 7760464803fd5e60df70e39dca8ecd0a064d29f6 Mon Sep 17 00:00:00 2001 From: Dirk Lemstra Date: Sun, 19 Feb 2017 19:16:09 +0100 Subject: [PATCH 101/142] Options should be readonly. --- src/ImageSharp.Formats.Bmp/BmpEncoderCore.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp.Formats.Bmp/BmpEncoderCore.cs b/src/ImageSharp.Formats.Bmp/BmpEncoderCore.cs index 9696eb66dd..df62fb6f40 100644 --- a/src/ImageSharp.Formats.Bmp/BmpEncoderCore.cs +++ b/src/ImageSharp.Formats.Bmp/BmpEncoderCore.cs @@ -18,7 +18,7 @@ namespace ImageSharp.Formats /// /// The options for the encoder. /// - private IBmpEncoderOptions options; + private readonly IBmpEncoderOptions options; /// /// The amount to pad each row by. From 675a97a8776fd945e6fc441224a8ed7bc28f6b1a Mon Sep 17 00:00:00 2001 From: Dirk Lemstra Date: Sun, 19 Feb 2017 21:41:56 +0100 Subject: [PATCH 102/142] Implemented the IEncoderOptions inside the gif encoder. --- src/ImageSharp.Formats.Gif/GifConstants.cs | 14 +++- src/ImageSharp.Formats.Gif/GifDecoderCore.cs | 2 +- .../GifDecoderOptions.cs | 4 +- src/ImageSharp.Formats.Gif/GifEncoder.cs | 37 ++++------ src/ImageSharp.Formats.Gif/GifEncoderCore.cs | 68 +++++++++++++----- .../GifEncoderOptions.cs | 71 +++++++++++++++++++ .../IGifEncoderOptions.cs | 38 ++++++++++ src/ImageSharp.Formats.Gif/ImageExtensions.cs | 11 ++- .../Formats/Gif/GifEncoderCoreTests.cs | 67 +++++++++++++++++ 9 files changed, 264 insertions(+), 48 deletions(-) create mode 100644 src/ImageSharp.Formats.Gif/GifEncoderOptions.cs create mode 100644 src/ImageSharp.Formats.Gif/IGifEncoderOptions.cs create mode 100644 tests/ImageSharp.Tests/Formats/Gif/GifEncoderCoreTests.cs diff --git a/src/ImageSharp.Formats.Gif/GifConstants.cs b/src/ImageSharp.Formats.Gif/GifConstants.cs index 5334bcba37..4af291c2ba 100644 --- a/src/ImageSharp.Formats.Gif/GifConstants.cs +++ b/src/ImageSharp.Formats.Gif/GifConstants.cs @@ -5,10 +5,12 @@ namespace ImageSharp.Formats { + using System.Text; + /// /// Constants that define specific points within a gif. /// - internal sealed class GifConstants + internal static class GifConstants { /// /// The file type. @@ -50,6 +52,11 @@ namespace ImageSharp.Formats /// public const byte CommentLabel = 0xFE; + /// + /// The name of the property inside the image properties for the comments. + /// + public const string Comments = "Comments"; + /// /// The maximum comment length. /// @@ -79,5 +86,10 @@ namespace ImageSharp.Formats /// The end introducer trailer ;. /// public const byte EndIntroducer = 0x3B; + + /// + /// Gets the default encoding to use when reading comments. + /// + public static Encoding DefaultEncoding { get; } = Encoding.GetEncoding("ASCII"); } } diff --git a/src/ImageSharp.Formats.Gif/GifDecoderCore.cs b/src/ImageSharp.Formats.Gif/GifDecoderCore.cs index 3ab0e6ca8b..ab1edc2c76 100644 --- a/src/ImageSharp.Formats.Gif/GifDecoderCore.cs +++ b/src/ImageSharp.Formats.Gif/GifDecoderCore.cs @@ -261,7 +261,7 @@ namespace ImageSharp.Formats { this.currentStream.Read(commentsBuffer, 0, length); string comments = this.options.TextEncoding.GetString(commentsBuffer, 0, length); - this.decodedImage.MetaData.Properties.Add(new ImageProperty("Comments", comments)); + this.decodedImage.MetaData.Properties.Add(new ImageProperty(GifConstants.Comments, comments)); } finally { diff --git a/src/ImageSharp.Formats.Gif/GifDecoderOptions.cs b/src/ImageSharp.Formats.Gif/GifDecoderOptions.cs index 5932425e5a..8722c5fe8f 100644 --- a/src/ImageSharp.Formats.Gif/GifDecoderOptions.cs +++ b/src/ImageSharp.Formats.Gif/GifDecoderOptions.cs @@ -12,8 +12,6 @@ namespace ImageSharp.Formats /// public sealed class GifDecoderOptions : DecoderOptions, IGifDecoderOptions { - private static readonly Encoding DefaultEncoding = Encoding.GetEncoding("ASCII"); - /// /// Initializes a new instance of the class. /// @@ -33,7 +31,7 @@ namespace ImageSharp.Formats /// /// Gets or sets the encoding that should be used when reading comments. /// - public Encoding TextEncoding { get; set; } = DefaultEncoding; + public Encoding TextEncoding { get; set; } = GifConstants.DefaultEncoding; /// /// Converts the options to a instance with a cast diff --git a/src/ImageSharp.Formats.Gif/GifEncoder.cs b/src/ImageSharp.Formats.Gif/GifEncoder.cs index 402d2d09a1..34027ee0cc 100644 --- a/src/ImageSharp.Formats.Gif/GifEncoder.cs +++ b/src/ImageSharp.Formats.Gif/GifEncoder.cs @@ -8,40 +8,31 @@ namespace ImageSharp.Formats using System; using System.IO; - using ImageSharp.Quantizers; - /// /// Image encoder for writing image data to a stream in gif format. /// public class GifEncoder : IImageEncoder { - /// - /// Gets or sets the quality of output for images. - /// - /// For gifs the value ranges from 1 to 256. - public int Quality { get; set; } + /// + public void Encode(Image image, Stream stream, IEncoderOptions options) + where TColor : struct, IPackedPixel, IEquatable + { + IGifEncoderOptions gifOptions = GifEncoderOptions.Create(options); - /// - /// Gets or sets the transparency threshold. - /// - public byte Threshold { get; set; } = 128; + this.Encode(image, stream, gifOptions); + } /// - /// Gets or sets the quantizer for reducing the color count. + /// Encodes the image to the specified stream from the . /// - public IQuantizer Quantizer { get; set; } - - /// - public void Encode(Image image, Stream stream, IEncoderOptions options) + /// The pixel format. + /// The to encode from. + /// The to encode the image data to. + /// The options for the encoder. + public void Encode(Image image, Stream stream, IGifEncoderOptions options) where TColor : struct, IPixel { - GifEncoderCore encoder = new GifEncoderCore - { - Quality = this.Quality, - Quantizer = this.Quantizer, - Threshold = this.Threshold - }; - + GifEncoderCore encoder = new GifEncoderCore(options); encoder.Encode(image, stream); } } diff --git a/src/ImageSharp.Formats.Gif/GifEncoderCore.cs b/src/ImageSharp.Formats.Gif/GifEncoderCore.cs index 36cf614d94..2c9a0c8b0b 100644 --- a/src/ImageSharp.Formats.Gif/GifEncoderCore.cs +++ b/src/ImageSharp.Formats.Gif/GifEncoderCore.cs @@ -24,20 +24,23 @@ namespace ImageSharp.Formats private readonly byte[] buffer = new byte[16]; /// - /// The number of bits requires to store the image palette. + /// The options for the encoder. /// - private int bitDepth; + private readonly IGifEncoderOptions options; /// - /// Gets or sets the quality of output for images. + /// The number of bits requires to store the image palette. /// - /// For gifs the value ranges from 1 to 256. - public int Quality { get; set; } + private int bitDepth; /// - /// Gets or sets the transparency threshold. + /// Initializes a new instance of the class. /// - public byte Threshold { get; set; } = 128; + /// The options for the encoder. + public GifEncoderCore(IGifEncoderOptions options) + { + this.options = options ?? new GifEncoderOptions(); + } /// /// Gets or sets the quantizer for reducing the color count. @@ -56,23 +59,20 @@ namespace ImageSharp.Formats Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); - if (this.Quantizer == null) - { - this.Quantizer = new OctreeQuantizer(); - } + this.Quantizer = this.options.Quantizer ?? new OctreeQuantizer(); // Do not use IDisposable pattern here as we want to preserve the stream. EndianBinaryWriter writer = new EndianBinaryWriter(Endianness.LittleEndian, stream); // Ensure that quality can be set but has a fallback. - int quality = this.Quality > 0 ? this.Quality : image.MetaData.Quality; - this.Quality = quality > 0 ? quality.Clamp(1, 256) : 256; + int quality = this.options.Quality > 0 ? this.options.Quality : image.MetaData.Quality; + quality = quality > 0 ? quality.Clamp(1, 256) : 256; // Get the number of bits. - this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(this.Quality); + this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(quality); // Quantize the image returning a palette. - QuantizedImage quantized = ((IQuantizer)this.Quantizer).Quantize(image, this.Quality); + QuantizedImage quantized = ((IQuantizer)this.Quantizer).Quantize(image, quality); int index = this.GetTransparentIndex(quantized); @@ -84,6 +84,7 @@ namespace ImageSharp.Formats // Write the first frame. this.WriteGraphicalControlExtension(image, writer, index); + this.WriteComments(image, writer); this.WriteImageDescriptor(image, writer); this.WriteColorTable(quantized, writer); this.WriteImageData(quantized, writer); @@ -97,7 +98,7 @@ namespace ImageSharp.Formats for (int i = 0; i < image.Frames.Count; i++) { ImageFrame frame = image.Frames[i]; - QuantizedImage quantizedFrame = ((IQuantizer)this.Quantizer).Quantize(frame, this.Quality); + QuantizedImage quantizedFrame = ((IQuantizer)this.Quantizer).Quantize(frame, quality); this.WriteGraphicalControlExtension(frame, writer, this.GetTransparentIndex(quantizedFrame)); this.WriteImageDescriptor(frame, writer); @@ -106,7 +107,7 @@ namespace ImageSharp.Formats } } - // TODO: Write Comments extension etc + // TODO: Write extension etc writer.Write(GifConstants.EndIntroducer); } @@ -229,6 +230,39 @@ namespace ImageSharp.Formats } } + /// + /// Writes the image comments to the stream. + /// + /// The pixel format. + /// The to be encoded. + /// The stream to write to. + private void WriteComments(Image image, EndianBinaryWriter writer) + where TColor : struct, IPackedPixel, IEquatable + { + if (this.options.IgnoreMetadata == true) + { + return; + } + + ImageProperty property = image.MetaData.Properties.FirstOrDefault(p => p.Name == GifConstants.Comments); + if (property == null || string.IsNullOrEmpty(property.Value)) + { + return; + } + + byte[] comments = this.options.TextEncoding.GetBytes(property.Value); + + int count = Math.Min(comments.Length, 255); + + this.buffer[0] = GifConstants.ExtensionIntroducer; + this.buffer[1] = GifConstants.CommentLabel; + this.buffer[2] = (byte)count; + + writer.Write(this.buffer, 0, 3); + writer.Write(comments, 0, count); + writer.Write(GifConstants.Terminator); + } + /// /// Writes the graphics control extension to the stream. /// diff --git a/src/ImageSharp.Formats.Gif/GifEncoderOptions.cs b/src/ImageSharp.Formats.Gif/GifEncoderOptions.cs new file mode 100644 index 0000000000..94cad9603c --- /dev/null +++ b/src/ImageSharp.Formats.Gif/GifEncoderOptions.cs @@ -0,0 +1,71 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System.Text; + + using Quantizers; + + /// + /// Encapsulates the options for the . + /// + public sealed class GifEncoderOptions : EncoderOptions, IGifEncoderOptions + { + /// + /// Initializes a new instance of the class. + /// + public GifEncoderOptions() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The options for the encoder. + private GifEncoderOptions(IEncoderOptions options) + : base(options) + { + } + + /// + /// Gets or sets the encoding that should be used when writing comments. + /// + public Encoding TextEncoding { get; set; } = GifConstants.DefaultEncoding; + + /// + /// Gets or sets the quality of output for images. + /// + /// For gifs the value ranges from 1 to 256. + public int Quality { get; set; } + + /// + /// Gets or sets the transparency threshold. + /// + public byte Threshold { get; set; } = 128; + + /// + /// Gets or sets the quantizer for reducing the color count. + /// + public IQuantizer Quantizer { get; set; } + + /// + /// Converts the options to a instance with a + /// cast or by creating a new instance with the specfied options. + /// + /// The options for the encoder. + /// The options for the . + internal static IGifEncoderOptions Create(IEncoderOptions options) + { + IGifEncoderOptions gifOptions = options as IGifEncoderOptions; + if (gifOptions != null) + { + return gifOptions; + } + + return new GifEncoderOptions(options); + } + } +} diff --git a/src/ImageSharp.Formats.Gif/IGifEncoderOptions.cs b/src/ImageSharp.Formats.Gif/IGifEncoderOptions.cs new file mode 100644 index 0000000000..c1d6b7ad86 --- /dev/null +++ b/src/ImageSharp.Formats.Gif/IGifEncoderOptions.cs @@ -0,0 +1,38 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using System.Text; + + using Quantizers; + + /// + /// Encapsulates the options for the . + /// + public interface IGifEncoderOptions : IEncoderOptions + { + /// + /// Gets the encoding that should be used when writing comments. + /// + Encoding TextEncoding { get; } + + /// + /// Gets the quality of output for images. + /// + /// For gifs the value ranges from 1 to 256. + int Quality { get; } + + /// + /// Gets the transparency threshold. + /// + byte Threshold { get; } + + /// + /// Gets the quantizer for reducing the color count. + /// + IQuantizer Quantizer { get; } + } +} diff --git a/src/ImageSharp.Formats.Gif/ImageExtensions.cs b/src/ImageSharp.Formats.Gif/ImageExtensions.cs index e0ec2e8c84..dcf4ab29a5 100644 --- a/src/ImageSharp.Formats.Gif/ImageExtensions.cs +++ b/src/ImageSharp.Formats.Gif/ImageExtensions.cs @@ -21,13 +21,18 @@ namespace ImageSharp /// The pixel format. /// The image this method extends. /// The stream to save the image to. - /// The quality to save the image to representing the number of colors. Between 1 and 256. + /// The options for the encoder. /// Thrown if the stream is null. /// /// The . /// - public static Image SaveAsGif(this Image source, Stream stream, int quality = 256) + public static Image SaveAsGif(this Image source, Stream stream, IGifEncoderOptions options = null) where TColor : struct, IPixel - => source.Save(stream, new GifEncoder { Quality = quality }); + { + GifEncoder encoder = new GifEncoder(); + encoder.Encode(source, stream, options); + + return source; + } } } diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderCoreTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderCoreTests.cs new file mode 100644 index 0000000000..6f163bc6e7 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderCoreTests.cs @@ -0,0 +1,67 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System.IO; + using Xunit; + + using ImageSharp.Formats; + + public class GifEncoderCoreTests + { + [Fact] + public void Encode_IgnoreMetadataIsFalse_CommentsAreWritten() + { + var options = new EncoderOptions() + { + IgnoreMetadata = false + }; + + TestFile testFile = TestFile.Create(TestImages.Gif.Rings); + + using (Image input = testFile.CreateImage()) + { + using (MemoryStream memStream = new MemoryStream()) + { + input.Save(memStream, new GifFormat(), options); + + memStream.Position = 0; + using (Image output = new Image(memStream)) + { + Assert.Equal(1, output.MetaData.Properties.Count); + Assert.Equal("Comments", output.MetaData.Properties[0].Name); + Assert.Equal("ImageSharp", output.MetaData.Properties[0].Value); + } + } + } + } + + [Fact] + public void Encode_IgnoreMetadataIsTrue_CommentsAreNotWritten() + { + var options = new GifEncoderOptions() + { + IgnoreMetadata = true + }; + + TestFile testFile = TestFile.Create(TestImages.Gif.Rings); + + using (Image input = testFile.CreateImage()) + { + using (MemoryStream memStream = new MemoryStream()) + { + input.SaveAsGif(memStream, options); + + memStream.Position = 0; + using (Image output = new Image(memStream)) + { + Assert.Equal(0, output.MetaData.Properties.Count); + } + } + } + } + } +} From 56eb309c5f6eedf443bffa0980ce95e07453f094 Mon Sep 17 00:00:00 2001 From: Dirk Lemstra Date: Sun, 19 Feb 2017 21:58:48 +0100 Subject: [PATCH 103/142] Build fixes due to re-base. --- src/ImageSharp.Formats.Bmp/BmpEncoder.cs | 2 +- src/ImageSharp.Formats.Gif/GifDecoder.cs | 2 +- src/ImageSharp.Formats.Gif/GifEncoder.cs | 2 +- src/ImageSharp.Formats.Gif/GifEncoderCore.cs | 2 +- src/ImageSharp.Formats.Png/PngDecoder.cs | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp.Formats.Bmp/BmpEncoder.cs b/src/ImageSharp.Formats.Bmp/BmpEncoder.cs index 2c0bb5b5d5..d0a3550f6f 100644 --- a/src/ImageSharp.Formats.Bmp/BmpEncoder.cs +++ b/src/ImageSharp.Formats.Bmp/BmpEncoder.cs @@ -31,7 +31,7 @@ namespace ImageSharp.Formats /// The to encode the image data to. /// The options for the encoder. public void Encode(Image image, Stream stream, IBmpEncoderOptions options) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { BmpEncoderCore encoder = new BmpEncoderCore(options); encoder.Encode(image, stream); diff --git a/src/ImageSharp.Formats.Gif/GifDecoder.cs b/src/ImageSharp.Formats.Gif/GifDecoder.cs index 88a3d5e437..b1e8ba928d 100644 --- a/src/ImageSharp.Formats.Gif/GifDecoder.cs +++ b/src/ImageSharp.Formats.Gif/GifDecoder.cs @@ -30,7 +30,7 @@ namespace ImageSharp.Formats /// The containing image data. /// The options for the decoder. public void Decode(Image image, Stream stream, IGifDecoderOptions options) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { new GifDecoderCore(options).Decode(image, stream); } diff --git a/src/ImageSharp.Formats.Gif/GifEncoder.cs b/src/ImageSharp.Formats.Gif/GifEncoder.cs index 34027ee0cc..cc8516ed9d 100644 --- a/src/ImageSharp.Formats.Gif/GifEncoder.cs +++ b/src/ImageSharp.Formats.Gif/GifEncoder.cs @@ -15,7 +15,7 @@ namespace ImageSharp.Formats { /// public void Encode(Image image, Stream stream, IEncoderOptions options) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { IGifEncoderOptions gifOptions = GifEncoderOptions.Create(options); diff --git a/src/ImageSharp.Formats.Gif/GifEncoderCore.cs b/src/ImageSharp.Formats.Gif/GifEncoderCore.cs index 2c9a0c8b0b..38cbba8500 100644 --- a/src/ImageSharp.Formats.Gif/GifEncoderCore.cs +++ b/src/ImageSharp.Formats.Gif/GifEncoderCore.cs @@ -237,7 +237,7 @@ namespace ImageSharp.Formats /// The to be encoded. /// The stream to write to. private void WriteComments(Image image, EndianBinaryWriter writer) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { if (this.options.IgnoreMetadata == true) { diff --git a/src/ImageSharp.Formats.Png/PngDecoder.cs b/src/ImageSharp.Formats.Png/PngDecoder.cs index 658de9e664..d527e1654d 100644 --- a/src/ImageSharp.Formats.Png/PngDecoder.cs +++ b/src/ImageSharp.Formats.Png/PngDecoder.cs @@ -47,7 +47,7 @@ namespace ImageSharp.Formats /// The containing image data. /// The options for the decoder. public void Decode(Image image, Stream stream, IPngDecoderOptions options) - where TColor : struct, IPackedPixel, IEquatable + where TColor : struct, IPixel { new PngDecoderCore(options).Decode(image, stream); } From 3a0697b093f61d45ad4e55052b894d0c8afeefba Mon Sep 17 00:00:00 2001 From: Dirk Lemstra Date: Mon, 20 Feb 2017 07:14:23 +0100 Subject: [PATCH 104/142] Added extra test for trimming comments. --- .../Formats/Gif/GifEncoderCoreTests.cs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderCoreTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderCoreTests.cs index 6f163bc6e7..0e8e21780f 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderCoreTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderCoreTests.cs @@ -63,5 +63,28 @@ namespace ImageSharp.Tests } } } + + [Fact] + public void Encode_CommentIsToLong_CommentIsTrimmed() + { + using (Image input = new Image(1, 1)) + { + string comments = new string('c', 256); + input.MetaData.Properties.Add(new ImageProperty("Comments", comments)); + + using (MemoryStream memStream = new MemoryStream()) + { + input.Save(memStream, new GifFormat()); + + memStream.Position = 0; + using (Image output = new Image(memStream)) + { + Assert.Equal(1, output.MetaData.Properties.Count); + Assert.Equal("Comments", output.MetaData.Properties[0].Name); + Assert.Equal(255, output.MetaData.Properties[0].Value.Length); + } + } + } + } } } From 826fb4eb91a1c8fc248f735ccd28efbf51079646 Mon Sep 17 00:00:00 2001 From: Dirk Lemstra Date: Mon, 20 Feb 2017 23:37:08 +0100 Subject: [PATCH 105/142] Implemented the IEncoderOptions inside the jpeg encoder. --- .../IJpegEncoderOptions.cs | 29 +++++++ .../ImageExtensions.cs | 11 ++- src/ImageSharp.Formats.Jpeg/JpegEncoder.cs | 75 ++++--------------- .../JpegEncoderCore.cs | 40 +++++++--- .../JpegEncoderOptions.cs | 62 +++++++++++++++ .../Formats/Jpg/JpegDecoderTests.cs | 5 +- .../Formats/Jpg/JpegEncoderTests.cs | 67 +++++++++++++++-- .../Formats/Jpg/JpegProfilingBenchmarks.cs | 5 +- .../TestUtilities/ImagingTestCaseUtility.cs | 5 +- 9 files changed, 209 insertions(+), 90 deletions(-) create mode 100644 src/ImageSharp.Formats.Jpeg/IJpegEncoderOptions.cs create mode 100644 src/ImageSharp.Formats.Jpeg/JpegEncoderOptions.cs diff --git a/src/ImageSharp.Formats.Jpeg/IJpegEncoderOptions.cs b/src/ImageSharp.Formats.Jpeg/IJpegEncoderOptions.cs new file mode 100644 index 0000000000..7d62dd4ef9 --- /dev/null +++ b/src/ImageSharp.Formats.Jpeg/IJpegEncoderOptions.cs @@ -0,0 +1,29 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + /// + /// Encapsulates the options for the . + /// + public interface IJpegEncoderOptions : IEncoderOptions + { + /// + /// Gets the quality, that will be used to encode the image. Quality + /// index must be between 0 and 100 (compression from max to min). + /// + /// + /// If the quality is less than or equal to 80, the subsampling ratio will switch to + /// + /// The quality of the jpg image from 0 to 100. + int Quality { get; } + + /// + /// Gets the subsample ration, that will be used to encode the image. + /// + /// The subsample ratio of the jpg image. + JpegSubsample? Subsample { get; } + } +} diff --git a/src/ImageSharp.Formats.Jpeg/ImageExtensions.cs b/src/ImageSharp.Formats.Jpeg/ImageExtensions.cs index 2cbba02a90..311bcc6435 100644 --- a/src/ImageSharp.Formats.Jpeg/ImageExtensions.cs +++ b/src/ImageSharp.Formats.Jpeg/ImageExtensions.cs @@ -21,13 +21,18 @@ namespace ImageSharp /// The pixel format. /// The image this method extends. /// The stream to save the image to. - /// The quality to save the image to. Between 1 and 100. + /// The options for the encoder. /// Thrown if the stream is null. /// /// The . /// - public static Image SaveAsJpeg(this Image source, Stream stream, int quality = 75) + public static Image SaveAsJpeg(this Image source, Stream stream, IJpegEncoderOptions options = null) where TColor : struct, IPixel - => source.Save(stream, new JpegEncoder { Quality = quality }); + { + JpegEncoder encoder = new JpegEncoder(); + encoder.Encode(source, stream, options); + + return source; + } } } diff --git a/src/ImageSharp.Formats.Jpeg/JpegEncoder.cs b/src/ImageSharp.Formats.Jpeg/JpegEncoder.cs index 46972cd93d..2f2823fa28 100644 --- a/src/ImageSharp.Formats.Jpeg/JpegEncoder.cs +++ b/src/ImageSharp.Formats.Jpeg/JpegEncoder.cs @@ -5,7 +5,6 @@ namespace ImageSharp.Formats { - using System; using System.IO; /// @@ -13,73 +12,27 @@ namespace ImageSharp.Formats /// public class JpegEncoder : IImageEncoder { - /// - /// The quality used to encode the image. - /// - private int quality = 75; - - /// - /// The subsamples scheme used to encode the image. - /// - private JpegSubsample subsample = JpegSubsample.Ratio420; - - /// - /// Whether subsampling has been specifically set. - /// - private bool subsampleSet; - - /// - /// Gets or sets the quality, that will be used to encode the image. Quality - /// index must be between 0 and 100 (compression from max to min). - /// - /// - /// If the quality is less than or equal to 80, the subsampling ratio will switch to - /// - /// The quality of the jpg image from 0 to 100. - public int Quality + /// + public void Encode(Image image, Stream stream, IEncoderOptions options) + where TColor : struct, IPixel { - get { return this.quality; } - set { this.quality = value.Clamp(1, 100); } + IJpegEncoderOptions gifOptions = JpegEncoderOptions.Create(options); + + this.Encode(image, stream, gifOptions); } /// - /// Gets or sets the subsample ration, that will be used to encode the image. + /// Encodes the image to the specified stream from the . /// - /// The subsample ratio of the jpg image. - public JpegSubsample Subsample - { - get - { - return this.subsample; - } - - set - { - this.subsample = value; - this.subsampleSet = true; - } - } - - /// - public void Encode(Image image, Stream stream, IEncoderOptions options) + /// The pixel format. + /// The to encode from. + /// The to encode the image data to. + /// The options for the encoder. + public void Encode(Image image, Stream stream, IJpegEncoderOptions options) where TColor : struct, IPixel { - // Ensure that quality can be set but has a fallback. - if (image.MetaData.Quality > 0) - { - this.Quality = image.MetaData.Quality; - } - - JpegEncoderCore encode = new JpegEncoderCore(); - if (this.subsampleSet) - { - encode.Encode(image, stream, this.Quality, this.Subsample); - } - else - { - // Use 4:2:0 Subsampling at quality < 91% for reduced filesize. - encode.Encode(image, stream, this.Quality, this.Quality >= 91 ? JpegSubsample.Ratio444 : JpegSubsample.Ratio420); - } + JpegEncoderCore encode = new JpegEncoderCore(options); + encode.Encode(image, stream); } } } diff --git a/src/ImageSharp.Formats.Jpeg/JpegEncoderCore.cs b/src/ImageSharp.Formats.Jpeg/JpegEncoderCore.cs index 0309af1299..66f400c017 100644 --- a/src/ImageSharp.Formats.Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp.Formats.Jpeg/JpegEncoderCore.cs @@ -118,6 +118,11 @@ namespace ImageSharp.Formats /// private readonly byte[] huffmanBuffer = new byte[179]; + /// + /// The options for the encoder. + /// + private readonly IJpegEncoderOptions options; + /// /// The accumulated bits to write to the stream. /// @@ -148,15 +153,22 @@ namespace ImageSharp.Formats /// private JpegSubsample subsample; + /// + /// Initializes a new instance of the class. + /// + /// The options for the encoder. + public JpegEncoderCore(IJpegEncoderOptions options) + { + this.options = options ?? new JpegEncoderOptions(); + } + /// /// Encode writes the image to the jpeg baseline format with the given options. /// /// The pixel format. /// The image to write from. /// The stream to write to. - /// The quality. - /// The subsampling mode. - public void Encode(Image image, Stream stream, int quality, JpegSubsample sample) + public void Encode(Image image, Stream stream) where TColor : struct, IPixel { Guard.NotNull(image, nameof(image)); @@ -168,18 +180,17 @@ namespace ImageSharp.Formats throw new ImageFormatException($"Image is too large to encode at {image.Width}x{image.Height}."); } - this.outputStream = stream; - this.subsample = sample; - - if (quality < 1) + // Ensure that quality can be set but has a fallback. + int quality = this.options.Quality > 0 ? this.options.Quality : image.MetaData.Quality; + if (quality == 0) { - quality = 1; + quality = 75; } - if (quality > 100) - { - quality = 100; - } + quality = quality.Clamp(1, 100); + + this.outputStream = stream; + this.subsample = this.options.Subsample ?? (quality >= 91 ? JpegSubsample.Ratio444 : JpegSubsample.Ratio420); // Convert from a quality rating to a scaling factor. int scale; @@ -706,6 +717,11 @@ namespace ImageSharp.Formats private void WriteProfiles(Image image) where TColor : struct, IPixel { + if (this.options.IgnoreMetadata) + { + return; + } + image.MetaData.SyncProfiles(); this.WriteProfile(image.MetaData.ExifProfile); } diff --git a/src/ImageSharp.Formats.Jpeg/JpegEncoderOptions.cs b/src/ImageSharp.Formats.Jpeg/JpegEncoderOptions.cs new file mode 100644 index 0000000000..6be36627c4 --- /dev/null +++ b/src/ImageSharp.Formats.Jpeg/JpegEncoderOptions.cs @@ -0,0 +1,62 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + /// + /// Encapsulates the options for the . + /// + public sealed class JpegEncoderOptions : EncoderOptions, IJpegEncoderOptions + { + /// + /// Initializes a new instance of the class. + /// + public JpegEncoderOptions() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The options for the encoder. + private JpegEncoderOptions(IEncoderOptions options) + : base(options) + { + } + + /// + /// Gets or sets the quality, that will be used to encode the image. Quality + /// index must be between 0 and 100 (compression from max to min). + /// + /// + /// If the quality is less than or equal to 80, the subsampling ratio will switch to + /// + /// The quality of the jpg image from 0 to 100. + public int Quality { get; set; } + + /// + /// Gets or sets the subsample ration, that will be used to encode the image. + /// + /// The subsample ratio of the jpg image. + public JpegSubsample? Subsample { get; set; } + + /// + /// Converts the options to a instance with a + /// cast or by creating a new instance with the specfied options. + /// + /// The options for the encoder. + /// The options for the . + internal static IJpegEncoderOptions Create(IEncoderOptions options) + { + IJpegEncoderOptions jpegOptions = options as IJpegEncoderOptions; + if (jpegOptions != null) + { + return jpegOptions; + } + + return new JpegEncoderOptions(options); + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index b8ec9d49ea..476de95ff1 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -61,12 +61,13 @@ namespace ImageSharp.Tests byte[] data; using (Image image = provider.GetImage()) { - JpegEncoder encoder = new JpegEncoder() { Subsample = subsample, Quality = quality }; + JpegEncoder encoder = new JpegEncoder(); + JpegEncoderOptions options = new JpegEncoderOptions { Subsample = subsample, Quality = quality }; data = new byte[65536]; using (MemoryStream ms = new MemoryStream(data)) { - image.Save(ms, encoder); + image.Save(ms, encoder, options); } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs index 6518c3e6b6..741e785c0c 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs @@ -38,11 +38,12 @@ namespace ImageSharp.Tests { image.MetaData.Quality = quality; image.MetaData.ExifProfile = null; // Reduce the size of the file - JpegEncoder encoder = new JpegEncoder { Subsample = subsample, Quality = quality }; + JpegEncoder encoder = new JpegEncoder(); + JpegEncoderOptions options = new JpegEncoderOptions { Subsample = subsample, Quality = quality }; provider.Utility.TestName += $"{subsample}_Q{quality}"; provider.Utility.SaveTestOutputFile(image, "png"); - provider.Utility.SaveTestOutputFile(image, "jpg", encoder); + provider.Utility.SaveTestOutputFile(image, "jpg", encoder, options); } } @@ -59,13 +60,63 @@ namespace ImageSharp.Tests using (FileStream outputStream = File.OpenWrite(utility.GetTestOutputFileName("jpg"))) { - JpegEncoder encoder = new JpegEncoder() - { - Subsample = subSample, - Quality = quality - }; + JpegEncoder encoder = new JpegEncoder(); - image.Save(outputStream, encoder); + image.Save(outputStream, encoder, new JpegEncoderOptions() + { + Subsample = subSample, + Quality = quality + }); + } + } + } + + [Fact] + public void Encode_IgnoreMetadataIsFalse_ExifProfileIsWritten() + { + var options = new EncoderOptions() + { + IgnoreMetadata = false + }; + + TestFile testFile = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan); + + using (Image input = testFile.CreateImage()) + { + using (MemoryStream memStream = new MemoryStream()) + { + input.Save(memStream, new JpegFormat(), options); + + memStream.Position = 0; + using (Image output = new Image(memStream)) + { + Assert.NotNull(output.MetaData.ExifProfile); + } + } + } + } + + [Fact] + public void Encode_IgnoreMetadataIsTrue_ExifProfileIgnored() + { + var options = new JpegEncoderOptions() + { + IgnoreMetadata = true + }; + + TestFile testFile = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan); + + using (Image input = testFile.CreateImage()) + { + using (MemoryStream memStream = new MemoryStream()) + { + input.SaveAsJpeg(memStream, options); + + memStream.Position = 0; + using (Image output = new Image(memStream)) + { + Assert.Null(output.MetaData.ExifProfile); + } } } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs index bfe1f1e76c..50e678bf08 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs @@ -81,8 +81,9 @@ namespace ImageSharp.Tests { foreach (Image img in testImages) { - JpegEncoder encoder = new JpegEncoder() { Quality = quality, Subsample = subsample }; - img.Save(ms, encoder); + JpegEncoder encoder = new JpegEncoder(); + JpegEncoderOptions options = new JpegEncoderOptions { Quality = quality, Subsample = subsample }; + img.Save(ms, encoder, options); ms.Seek(0, SeekOrigin.Begin); } }, diff --git a/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs b/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs index 54be62d37d..38429b2786 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs @@ -75,7 +75,8 @@ namespace ImageSharp.Tests /// The image instance /// The requested extension /// Optional encoder - public void SaveTestOutputFile(Image image, string extension = null, IImageEncoder encoder = null) + /// Optional encoder options + public void SaveTestOutputFile(Image image, string extension = null, IImageEncoder encoder = null, IEncoderOptions options = null) where TColor : struct, IPixel { string path = this.GetTestOutputFileName(extension); @@ -86,7 +87,7 @@ namespace ImageSharp.Tests using (var stream = File.OpenWrite(path)) { - image.Save(stream, encoder); + image.Save(stream, encoder, options); } } From a9f4dc07a0eb7a30ff58ef7acbf9420f76385f92 Mon Sep 17 00:00:00 2001 From: Dirk Lemstra Date: Mon, 20 Feb 2017 23:40:34 +0100 Subject: [PATCH 106/142] Renamed test classes. --- .../{BitmapTests.cs => BmpEncoderTests.cs} | 4 +- ...DecoderCoreTests.cs => GifDecoderTests.cs} | 4 +- ...EncoderCoreTests.cs => GifEncoderTests.cs} | 4 +- .../Formats/Jpg/JpegDecoderCoreTests.cs | 44 ------------------- .../Formats/Jpg/JpegDecoderTests.cs | 32 ++++++++++++++ ...DecoderCoreTests.cs => PngDecoderTests.cs} | 4 +- .../Png/{PngTests.cs => PngEncoderTests.cs} | 6 +-- 7 files changed, 42 insertions(+), 56 deletions(-) rename tests/ImageSharp.Tests/Formats/Bmp/{BitmapTests.cs => BmpEncoderTests.cs} (89%) rename tests/ImageSharp.Tests/Formats/Gif/{GifDecoderCoreTests.cs => GifDecoderTests.cs} (94%) rename tests/ImageSharp.Tests/Formats/Gif/{GifEncoderCoreTests.cs => GifEncoderTests.cs} (96%) delete mode 100644 tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderCoreTests.cs rename tests/ImageSharp.Tests/Formats/Png/{PngDecoderCoreTests.cs => PngDecoderTests.cs} (94%) rename tests/ImageSharp.Tests/Formats/Png/{PngTests.cs => PngEncoderTests.cs} (91%) diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BitmapTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs similarity index 89% rename from tests/ImageSharp.Tests/Formats/Bmp/BitmapTests.cs rename to tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs index 7579cc86e5..497abb7d56 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BitmapTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // @@ -9,7 +9,7 @@ namespace ImageSharp.Tests { using Xunit; - public class BitmapTests : FileTestBase + public class BmpEncoderTests : FileTestBase { public static readonly TheoryData BitsPerPixel = new TheoryData diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderCoreTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs similarity index 94% rename from tests/ImageSharp.Tests/Formats/Gif/GifDecoderCoreTests.cs rename to tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs index e1e3e94b09..b874a1585f 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderCoreTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // @@ -10,7 +10,7 @@ namespace ImageSharp.Tests using ImageSharp.Formats; - public class GifDecoderCoreTests + public class GifDecoderTests { [Fact] public void Decode_IgnoreMetadataIsFalse_CommentsAreRead() diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderCoreTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs similarity index 96% rename from tests/ImageSharp.Tests/Formats/Gif/GifEncoderCoreTests.cs rename to tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs index 0e8e21780f..da1323627f 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderCoreTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // @@ -10,7 +10,7 @@ namespace ImageSharp.Tests using ImageSharp.Formats; - public class GifEncoderCoreTests + public class GifEncoderTests { [Fact] public void Encode_IgnoreMetadataIsFalse_CommentsAreWritten() diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderCoreTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderCoreTests.cs deleted file mode 100644 index 38d2a455e9..0000000000 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderCoreTests.cs +++ /dev/null @@ -1,44 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using Xunit; - - public class JpegDecoderCoreTests - { - [Fact] - public void Decode_IgnoreMetadataIsFalse_ExifProfileIsRead() - { - var options = new DecoderOptions() - { - IgnoreMetadata = false - }; - - TestFile testFile = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan); - - using (Image image = testFile.CreateImage(options)) - { - Assert.NotNull(image.MetaData.ExifProfile); - } - } - - [Fact] - public void Decode_IgnoreMetadataIsTrue_ExifProfileIgnored() - { - var options = new DecoderOptions() - { - IgnoreMetadata = true - }; - - TestFile testFile = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan); - - using (Image image = testFile.CreateImage(options)) - { - Assert.Null(image.MetaData.ExifProfile); - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index 476de95ff1..7cb9a7cf23 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -121,5 +121,37 @@ namespace ImageSharp.Tests Assert.Equal(72, image.MetaData.VerticalResolution); } } + + [Fact] + public void Decode_IgnoreMetadataIsFalse_ExifProfileIsRead() + { + var options = new DecoderOptions() + { + IgnoreMetadata = false + }; + + TestFile testFile = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan); + + using (Image image = testFile.CreateImage(options)) + { + Assert.NotNull(image.MetaData.ExifProfile); + } + } + + [Fact] + public void Decode_IgnoreMetadataIsTrue_ExifProfileIgnored() + { + var options = new DecoderOptions() + { + IgnoreMetadata = true + }; + + TestFile testFile = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan); + + using (Image image = testFile.CreateImage(options)) + { + Assert.Null(image.MetaData.ExifProfile); + } + } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderCoreTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs similarity index 94% rename from tests/ImageSharp.Tests/Formats/Png/PngDecoderCoreTests.cs rename to tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs index 766172ec65..921530806c 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderCoreTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // @@ -10,7 +10,7 @@ namespace ImageSharp.Tests using ImageSharp.Formats; - public class PngDecoderCoreTests + public class PngDecoderTests { [Fact] public void Decode_IgnoreMetadataIsFalse_TextChunckIsRead() diff --git a/tests/ImageSharp.Tests/Formats/Png/PngTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs similarity index 91% rename from tests/ImageSharp.Tests/Formats/Png/PngTests.cs rename to tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs index 5ba00eb4d3..49be751391 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // @@ -10,11 +10,9 @@ namespace ImageSharp.Tests using System.IO; using System.Threading.Tasks; - using Formats; - using Xunit; - public class PngTests : FileTestBase + public class PngEncoderTests : FileTestBase { [Fact] public void ImageCanSaveIndexedPng() From 031da70a15e18c3ca0a17ce4a1612f9f31a5d481 Mon Sep 17 00:00:00 2001 From: Dirk Lemstra Date: Tue, 21 Feb 2017 21:59:06 +0100 Subject: [PATCH 107/142] Implemented the IEncoderOptions inside the png encoder. --- .../IPngEncoderOptions.cs | 54 +++++++++++ src/ImageSharp.Formats.Png/ImageExtensions.cs | 14 +-- src/ImageSharp.Formats.Png/PngDecoderCore.cs | 1 - src/ImageSharp.Formats.Png/PngEncoder.cs | 68 +++----------- src/ImageSharp.Formats.Png/PngEncoderCore.cs | 91 +++++++++---------- .../PngEncoderOptions.cs | 88 ++++++++++++++++++ 6 files changed, 206 insertions(+), 110 deletions(-) create mode 100644 src/ImageSharp.Formats.Png/IPngEncoderOptions.cs create mode 100644 src/ImageSharp.Formats.Png/PngEncoderOptions.cs diff --git a/src/ImageSharp.Formats.Png/IPngEncoderOptions.cs b/src/ImageSharp.Formats.Png/IPngEncoderOptions.cs new file mode 100644 index 0000000000..0008080d3f --- /dev/null +++ b/src/ImageSharp.Formats.Png/IPngEncoderOptions.cs @@ -0,0 +1,54 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using Quantizers; + + /// + /// Encapsulates the options for the . + /// + public interface IPngEncoderOptions : IEncoderOptions + { + /// + /// Gets the quality of output for images. + /// + int Quality { get; } + + /// + /// Gets the png color type + /// + PngColorType PngColorType { get; } + + /// + /// Gets the compression level 1-9. + /// + int CompressionLevel { get; } + + /// + /// Gets the gamma value, that will be written + /// the the stream, when the property + /// is set to true. + /// + /// The gamma value of the image. + float Gamma { get; } + + /// + /// Gets quantizer for reducing the color count. + /// + IQuantizer Quantizer { get; } + + /// + /// Gets the transparency threshold. + /// + byte Threshold { get; } + + /// + /// Gets a value indicating whether this instance should write + /// gamma information to the stream. + /// + bool WriteGamma { get; } + } +} diff --git a/src/ImageSharp.Formats.Png/ImageExtensions.cs b/src/ImageSharp.Formats.Png/ImageExtensions.cs index dcb1c988b7..f08ab8ee71 100644 --- a/src/ImageSharp.Formats.Png/ImageExtensions.cs +++ b/src/ImageSharp.Formats.Png/ImageExtensions.cs @@ -5,7 +5,6 @@ namespace ImageSharp { - using System; using System.IO; using Formats; @@ -21,15 +20,18 @@ namespace ImageSharp /// The pixel format. /// The image this method extends. /// The stream to save the image to. - /// The quality to save the image to representing the number of colors. - /// Anything equal to 256 and below will cause the encoder to save the image in an indexed format. - /// + /// The options for the encoder. /// Thrown if the stream is null. /// /// The . /// - public static Image SaveAsPng(this Image source, Stream stream, int quality = int.MaxValue) + public static Image SaveAsPng(this Image source, Stream stream, IPngEncoderOptions options = null) where TColor : struct, IPixel - => source.Save(stream, new PngEncoder { Quality = quality }); + { + PngEncoder encoder = new PngEncoder(); + encoder.Encode(source, stream, options); + + return source; + } } } diff --git a/src/ImageSharp.Formats.Png/PngDecoderCore.cs b/src/ImageSharp.Formats.Png/PngDecoderCore.cs index 4a5ad36482..076770ce51 100644 --- a/src/ImageSharp.Formats.Png/PngDecoderCore.cs +++ b/src/ImageSharp.Formats.Png/PngDecoderCore.cs @@ -10,7 +10,6 @@ namespace ImageSharp.Formats using System.Collections.Generic; using System.IO; using System.Linq; - using System.Text; using static ComparableExtensions; diff --git a/src/ImageSharp.Formats.Png/PngEncoder.cs b/src/ImageSharp.Formats.Png/PngEncoder.cs index 8d9c4bc085..e583f381fb 100644 --- a/src/ImageSharp.Formats.Png/PngEncoder.cs +++ b/src/ImageSharp.Formats.Png/PngEncoder.cs @@ -5,72 +5,34 @@ namespace ImageSharp.Formats { - using System; using System.IO; - using ImageSharp.Quantizers; - /// /// Image encoder for writing image data to a stream in png format. /// public class PngEncoder : IImageEncoder { - /// - /// Gets or sets the quality of output for images. - /// - public int Quality { get; set; } - - /// - /// Gets or sets the png color type - /// - public PngColorType PngColorType { get; set; } = PngColorType.RgbWithAlpha; - - /// - /// Gets or sets the compression level 1-9. - /// Defaults to 6. - /// - public int CompressionLevel { get; set; } = 6; - - /// - /// Gets or sets the gamma value, that will be written - /// the the stream, when the property - /// is set to true. The default value is 2.2F. - /// - /// The gamma value of the image. - public float Gamma { get; set; } = 2.2F; - - /// - /// Gets or sets quantizer for reducing the color count. - /// - public IQuantizer Quantizer { get; set; } + /// + public void Encode(Image image, Stream stream, IEncoderOptions options) + where TColor : struct, IPixel + { + IPngEncoderOptions pngOptions = PngEncoderOptions.Create(options); - /// - /// Gets or sets the transparency threshold. - /// - public byte Threshold { get; set; } = 0; + this.Encode(image, stream, pngOptions); + } /// - /// Gets or sets a value indicating whether this instance should write - /// gamma information to the stream. The default value is false. + /// Encodes the image to the specified stream from the . /// - public bool WriteGamma { get; set; } - - /// - public void Encode(Image image, Stream stream, IEncoderOptions options) + /// The pixel format. + /// The to encode from. + /// The to encode the image data to. + /// The options for the encoder. + public void Encode(Image image, Stream stream, IPngEncoderOptions options) where TColor : struct, IPixel { - PngEncoderCore encoder = new PngEncoderCore - { - CompressionLevel = this.CompressionLevel, - Gamma = this.Gamma, - Quality = this.Quality, - PngColorType = this.PngColorType, - Quantizer = this.Quantizer, - WriteGamma = this.WriteGamma, - Threshold = this.Threshold - }; - - encoder.Encode(image, stream); + PngEncoderCore encode = new PngEncoderCore(options); + encode.Encode(image, stream); } } } diff --git a/src/ImageSharp.Formats.Png/PngEncoderCore.cs b/src/ImageSharp.Formats.Png/PngEncoderCore.cs index 2324853cba..8a00c40b2e 100644 --- a/src/ImageSharp.Formats.Png/PngEncoderCore.cs +++ b/src/ImageSharp.Formats.Png/PngEncoderCore.cs @@ -40,6 +40,11 @@ namespace ImageSharp.Formats /// private readonly Crc32 crc = new Crc32(); + /// + /// The options for the encoder. + /// + private readonly IPngEncoderOptions options; + /// /// Contains the raw pixel data from an indexed image. /// @@ -86,44 +91,28 @@ namespace ImageSharp.Formats private byte[] paeth; /// - /// Gets or sets the quality of output for images. - /// - public int Quality { get; set; } - - /// - /// Gets or sets the png color type + /// The quality of output for images. /// - public PngColorType PngColorType { get; set; } + private int quality; /// - /// Gets or sets the compression level 1-9. - /// Defaults to 6. + /// The png color type. /// - public int CompressionLevel { get; set; } = 6; + private PngColorType pngColorType; /// - /// Gets or sets a value indicating whether this instance should write - /// gamma information to the stream. The default value is false. + /// The quantizer for reducing the color count. /// - public bool WriteGamma { get; set; } + private IQuantizer quantizer; /// - /// Gets or sets the gamma value, that will be written - /// the the stream, when the property - /// is set to true. The default value is 2.2F. + /// Initializes a new instance of the class. /// - /// The gamma value of the image. - public float Gamma { get; set; } = 2.2F; - - /// - /// Gets or sets the quantizer for reducing the color count. - /// - public IQuantizer Quantizer { get; set; } - - /// - /// Gets or sets the transparency threshold. - /// - public byte Threshold { get; set; } + /// The options for the encoder. + public PngEncoderCore(IPngEncoderOptions options) + { + this.options = options ?? new PngEncoderOptions(); + } /// /// Encodes the image to the specified stream from the . @@ -153,23 +142,25 @@ namespace ImageSharp.Formats stream.Write(this.chunkDataBuffer, 0, 8); // Ensure that quality can be set but has a fallback. - int quality = this.Quality > 0 ? this.Quality : image.MetaData.Quality; - this.Quality = quality > 0 ? quality.Clamp(1, int.MaxValue) : int.MaxValue; + this.quality = this.options.Quality > 0 ? this.options.Quality : image.MetaData.Quality; + this.quality = this.quality > 0 ? this.quality.Clamp(1, int.MaxValue) : int.MaxValue; + + this.pngColorType = this.options.PngColorType; // Set correct color type if the color count is 256 or less. - if (this.Quality <= 256) + if (this.quality <= 256) { - this.PngColorType = PngColorType.Palette; + this.pngColorType = PngColorType.Palette; } - if (this.PngColorType == PngColorType.Palette && this.Quality > 256) + if (this.pngColorType == PngColorType.Palette && this.quality > 256) { - this.Quality = 256; + this.quality = 256; } // Set correct bit depth. - this.bitDepth = this.Quality <= 256 - ? (byte)ImageMaths.GetBitsNeededForColorDepth(this.Quality).Clamp(1, 8) + this.bitDepth = this.quality <= 256 + ? (byte)ImageMaths.GetBitsNeededForColorDepth(this.quality).Clamp(1, 8) : (byte)8; // Png only supports in four pixel depths: 1, 2, 4, and 8 bits when using the PLTE chunk @@ -188,7 +179,7 @@ namespace ImageSharp.Formats { Width = image.Width, Height = image.Height, - ColorType = (byte)this.PngColorType, + ColorType = (byte)this.pngColorType, BitDepth = this.bitDepth, FilterMethod = 0, // None CompressionMethod = 0, @@ -198,7 +189,7 @@ namespace ImageSharp.Formats this.WriteHeaderChunk(stream, header); // Collect the indexed pixel data - if (this.PngColorType == PngColorType.Palette) + if (this.pngColorType == PngColorType.Palette) { this.CollectIndexedBytes(image, stream, header); } @@ -334,7 +325,7 @@ namespace ImageSharp.Formats private byte[] EncodePixelRow(PixelAccessor pixels, int row, byte[] previousScanline, byte[] rawScanline, byte[] result) where TColor : struct, IPixel { - switch (this.PngColorType) + switch (this.pngColorType) { case PngColorType.Palette: Buffer.BlockCopy(this.palettePixelData, row * rawScanline.Length, rawScanline, 0, rawScanline.Length); @@ -362,7 +353,7 @@ namespace ImageSharp.Formats private byte[] GetOptimalFilteredScanline(byte[] rawScanline, byte[] previousScanline, byte[] result) { // Palette images don't compress well with adaptive filtering. - if (this.PngColorType == PngColorType.Palette || this.bitDepth < 8) + if (this.pngColorType == PngColorType.Palette || this.bitDepth < 8) { NoneFilter.Encode(rawScanline, result); return result; @@ -436,7 +427,7 @@ namespace ImageSharp.Formats /// The private int CalculateBytesPerPixel() { - switch (this.PngColorType) + switch (this.pngColorType) { case PngColorType.Grayscale: return 1; @@ -488,18 +479,18 @@ namespace ImageSharp.Formats private QuantizedImage WritePaletteChunk(Stream stream, PngHeader header, ImageBase image) where TColor : struct, IPixel { - if (this.Quality > 256) + if (this.quality > 256) { return null; } - if (this.Quantizer == null) + if (this.quantizer == null) { - this.Quantizer = new OctreeQuantizer(); + this.quantizer = new OctreeQuantizer(); } // Quantize the image returning a palette. This boxing is icky. - QuantizedImage quantized = ((IQuantizer)this.Quantizer).Quantize(image, this.Quality); + QuantizedImage quantized = ((IQuantizer)this.quantizer).Quantize(image, this.quality); // Grab the palette and write it to the stream. TColor[] palette = quantized.Palette; @@ -524,7 +515,7 @@ namespace ImageSharp.Formats colorTable[offset + 1] = bytes[1]; colorTable[offset + 2] = bytes[2]; - if (alpha <= this.Threshold) + if (alpha <= this.options.Threshold) { transparentPixels.Add((byte)offset); } @@ -578,9 +569,9 @@ namespace ImageSharp.Formats /// The containing image data. private void WriteGammaChunk(Stream stream) { - if (this.WriteGamma) + if (this.options.WriteGamma) { - int gammaValue = (int)(this.Gamma * 100000F); + int gammaValue = (int)(this.options.Gamma * 100000F); byte[] size = BitConverter.GetBytes(gammaValue); @@ -608,7 +599,7 @@ namespace ImageSharp.Formats int resultLength = bytesPerScanline + 1; byte[] result = new byte[resultLength]; - if (this.PngColorType != PngColorType.Palette) + if (this.pngColorType != PngColorType.Palette) { this.sub = new byte[resultLength]; this.up = new byte[resultLength]; @@ -622,7 +613,7 @@ namespace ImageSharp.Formats try { memoryStream = new MemoryStream(); - using (ZlibDeflateStream deflateStream = new ZlibDeflateStream(memoryStream, this.CompressionLevel)) + using (ZlibDeflateStream deflateStream = new ZlibDeflateStream(memoryStream, this.options.CompressionLevel)) { for (int y = 0; y < this.height; y++) { diff --git a/src/ImageSharp.Formats.Png/PngEncoderOptions.cs b/src/ImageSharp.Formats.Png/PngEncoderOptions.cs new file mode 100644 index 0000000000..9e6e851de1 --- /dev/null +++ b/src/ImageSharp.Formats.Png/PngEncoderOptions.cs @@ -0,0 +1,88 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + using Quantizers; + + /// + /// Encapsulates the options for the . + /// + public sealed class PngEncoderOptions : EncoderOptions, IPngEncoderOptions + { + /// + /// Initializes a new instance of the class. + /// + public PngEncoderOptions() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The options for the encoder. + private PngEncoderOptions(IEncoderOptions options) + : base(options) + { + } + + /// + /// Gets or sets the quality of output for images. + /// + public int Quality { get; set; } + + /// + /// Gets or sets the png color type + /// + public PngColorType PngColorType { get; set; } = PngColorType.RgbWithAlpha; + + /// + /// Gets or sets the compression level 1-9. + /// Defaults to 6. + /// + public int CompressionLevel { get; set; } = 6; + + /// + /// Gets or sets the gamma value, that will be written + /// the the stream, when the property + /// is set to true. The default value is 2.2F. + /// + /// The gamma value of the image. + public float Gamma { get; set; } = 2.2F; + + /// + /// Gets or sets quantizer for reducing the color count. + /// + 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. + /// + public bool WriteGamma { get; set; } + + /// + /// Converts the options to a instance with a + /// cast or by creating a new instance with the specfied options. + /// + /// The options for the encoder. + /// The options for the . + internal static IPngEncoderOptions Create(IEncoderOptions options) + { + IPngEncoderOptions pngOptions = options as IPngEncoderOptions; + if (pngOptions != null) + { + return pngOptions; + } + + return new PngEncoderOptions(options); + } + } +} From 6f11c2cd5da0151920feb8cbd04c29748def2a81 Mon Sep 17 00:00:00 2001 From: Dirk Lemstra Date: Wed, 22 Feb 2017 17:24:01 +0100 Subject: [PATCH 108/142] Refactored the Create method for the encoder/decoder options. --- src/ImageSharp.Formats.Bmp/BmpEncoderOptions.cs | 8 +------- src/ImageSharp.Formats.Gif/GifDecoderOptions.cs | 8 +------- src/ImageSharp.Formats.Gif/GifEncoderOptions.cs | 8 +------- src/ImageSharp.Formats.Jpeg/JpegEncoderOptions.cs | 8 +------- src/ImageSharp.Formats.Png/PngDecoderOptions.cs | 8 +------- src/ImageSharp.Formats.Png/PngEncoderOptions.cs | 8 +------- 6 files changed, 6 insertions(+), 42 deletions(-) diff --git a/src/ImageSharp.Formats.Bmp/BmpEncoderOptions.cs b/src/ImageSharp.Formats.Bmp/BmpEncoderOptions.cs index 2c06d8a665..a0f9ff8e05 100644 --- a/src/ImageSharp.Formats.Bmp/BmpEncoderOptions.cs +++ b/src/ImageSharp.Formats.Bmp/BmpEncoderOptions.cs @@ -39,13 +39,7 @@ namespace ImageSharp.Formats /// The options for the . internal static IBmpEncoderOptions Create(IEncoderOptions options) { - IBmpEncoderOptions bmpOptions = options as IBmpEncoderOptions; - if (bmpOptions != null) - { - return bmpOptions; - } - - return new BmpEncoderOptions(options); + return options as IBmpEncoderOptions ?? new BmpEncoderOptions(options); } } } diff --git a/src/ImageSharp.Formats.Gif/GifDecoderOptions.cs b/src/ImageSharp.Formats.Gif/GifDecoderOptions.cs index 8722c5fe8f..bc7709f759 100644 --- a/src/ImageSharp.Formats.Gif/GifDecoderOptions.cs +++ b/src/ImageSharp.Formats.Gif/GifDecoderOptions.cs @@ -41,13 +41,7 @@ namespace ImageSharp.Formats /// The options for the . internal static IGifDecoderOptions Create(IDecoderOptions options) { - IGifDecoderOptions gifOptions = options as IGifDecoderOptions; - if (gifOptions != null) - { - return gifOptions; - } - - return new GifDecoderOptions(options); + return options as IGifDecoderOptions ?? new GifDecoderOptions(options); } } } diff --git a/src/ImageSharp.Formats.Gif/GifEncoderOptions.cs b/src/ImageSharp.Formats.Gif/GifEncoderOptions.cs index 94cad9603c..5d7c6e40b6 100644 --- a/src/ImageSharp.Formats.Gif/GifEncoderOptions.cs +++ b/src/ImageSharp.Formats.Gif/GifEncoderOptions.cs @@ -59,13 +59,7 @@ namespace ImageSharp.Formats /// The options for the . internal static IGifEncoderOptions Create(IEncoderOptions options) { - IGifEncoderOptions gifOptions = options as IGifEncoderOptions; - if (gifOptions != null) - { - return gifOptions; - } - - return new GifEncoderOptions(options); + return options as IGifEncoderOptions ?? new GifEncoderOptions(options); } } } diff --git a/src/ImageSharp.Formats.Jpeg/JpegEncoderOptions.cs b/src/ImageSharp.Formats.Jpeg/JpegEncoderOptions.cs index 6be36627c4..454afa6ae2 100644 --- a/src/ImageSharp.Formats.Jpeg/JpegEncoderOptions.cs +++ b/src/ImageSharp.Formats.Jpeg/JpegEncoderOptions.cs @@ -50,13 +50,7 @@ namespace ImageSharp.Formats /// The options for the . internal static IJpegEncoderOptions Create(IEncoderOptions options) { - IJpegEncoderOptions jpegOptions = options as IJpegEncoderOptions; - if (jpegOptions != null) - { - return jpegOptions; - } - - return new JpegEncoderOptions(options); + return options as IJpegEncoderOptions ?? new JpegEncoderOptions(options); } } } diff --git a/src/ImageSharp.Formats.Png/PngDecoderOptions.cs b/src/ImageSharp.Formats.Png/PngDecoderOptions.cs index 83716e5d1b..e8990ec456 100644 --- a/src/ImageSharp.Formats.Png/PngDecoderOptions.cs +++ b/src/ImageSharp.Formats.Png/PngDecoderOptions.cs @@ -43,13 +43,7 @@ namespace ImageSharp.Formats /// The options for the . internal static IPngDecoderOptions Create(IDecoderOptions options) { - IPngDecoderOptions pngOptions = options as IPngDecoderOptions; - if (pngOptions != null) - { - return pngOptions; - } - - return new PngDecoderOptions(options); + return options as IPngDecoderOptions ?? new PngDecoderOptions(options); } } } diff --git a/src/ImageSharp.Formats.Png/PngEncoderOptions.cs b/src/ImageSharp.Formats.Png/PngEncoderOptions.cs index 9e6e851de1..2891f1974e 100644 --- a/src/ImageSharp.Formats.Png/PngEncoderOptions.cs +++ b/src/ImageSharp.Formats.Png/PngEncoderOptions.cs @@ -76,13 +76,7 @@ namespace ImageSharp.Formats /// The options for the . internal static IPngEncoderOptions Create(IEncoderOptions options) { - IPngEncoderOptions pngOptions = options as IPngEncoderOptions; - if (pngOptions != null) - { - return pngOptions; - } - - return new PngEncoderOptions(options); + return options as IPngEncoderOptions ?? new PngEncoderOptions(options); } } } From 5a5bbef3a7bc60e412b1bb32b919ae7292c553ca Mon Sep 17 00:00:00 2001 From: Dirk Lemstra Date: Wed, 22 Feb 2017 17:47:26 +0100 Subject: [PATCH 109/142] Fixed text in comments. --- src/ImageSharp.Formats.Jpeg/IJpegEncoderOptions.cs | 3 --- src/ImageSharp.Formats.Jpeg/JpegEncoderOptions.cs | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/ImageSharp.Formats.Jpeg/IJpegEncoderOptions.cs b/src/ImageSharp.Formats.Jpeg/IJpegEncoderOptions.cs index 7d62dd4ef9..a545179653 100644 --- a/src/ImageSharp.Formats.Jpeg/IJpegEncoderOptions.cs +++ b/src/ImageSharp.Formats.Jpeg/IJpegEncoderOptions.cs @@ -14,9 +14,6 @@ namespace ImageSharp.Formats /// Gets the quality, that will be used to encode the image. Quality /// index must be between 0 and 100 (compression from max to min). /// - /// - /// If the quality is less than or equal to 80, the subsampling ratio will switch to - /// /// The quality of the jpg image from 0 to 100. int Quality { get; } diff --git a/src/ImageSharp.Formats.Jpeg/JpegEncoderOptions.cs b/src/ImageSharp.Formats.Jpeg/JpegEncoderOptions.cs index 454afa6ae2..73e483164c 100644 --- a/src/ImageSharp.Formats.Jpeg/JpegEncoderOptions.cs +++ b/src/ImageSharp.Formats.Jpeg/JpegEncoderOptions.cs @@ -31,7 +31,7 @@ namespace ImageSharp.Formats /// index must be between 0 and 100 (compression from max to min). /// /// - /// If the quality is less than or equal to 80, the subsampling ratio will switch to + /// If the quality is less than or equal to 90, the subsampling ratio will switch to /// /// The quality of the jpg image from 0 to 100. public int Quality { get; set; } From c83dec5ed53ec5d2cb925aeb3cdd170633969d47 Mon Sep 17 00:00:00 2001 From: Dirk Lemstra Date: Wed, 22 Feb 2017 17:50:03 +0100 Subject: [PATCH 110/142] Added missing initialization of the quantizer field. --- src/ImageSharp.Formats.Png/PngEncoderCore.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ImageSharp.Formats.Png/PngEncoderCore.cs b/src/ImageSharp.Formats.Png/PngEncoderCore.cs index 8a00c40b2e..7950d260c7 100644 --- a/src/ImageSharp.Formats.Png/PngEncoderCore.cs +++ b/src/ImageSharp.Formats.Png/PngEncoderCore.cs @@ -146,6 +146,7 @@ namespace ImageSharp.Formats this.quality = this.quality > 0 ? this.quality.Clamp(1, int.MaxValue) : int.MaxValue; this.pngColorType = this.options.PngColorType; + this.quantizer = this.options.Quantizer; // Set correct color type if the color count is 256 or less. if (this.quality <= 256) From 0853c66ae33b880733b5a97c69872e73c1bcb835 Mon Sep 17 00:00:00 2001 From: Dirk Lemstra Date: Wed, 22 Feb 2017 18:12:35 +0100 Subject: [PATCH 111/142] Changed optional arguments into overloads. --- src/ImageSharp.Formats.Gif/ImageExtensions.cs | 18 +- .../ImageExtensions.cs | 18 +- src/ImageSharp.Formats.Png/ImageExtensions.cs | 18 +- src/ImageSharp/Image.cs | 132 ++++++++++- src/ImageSharp/Image/Image{TColor}.cs | 215 +++++++++++++++++- tests/ImageSharp.Tests/Image/ImageTests.cs | 2 +- 6 files changed, 387 insertions(+), 16 deletions(-) diff --git a/src/ImageSharp.Formats.Gif/ImageExtensions.cs b/src/ImageSharp.Formats.Gif/ImageExtensions.cs index dcf4ab29a5..1ba03ed351 100644 --- a/src/ImageSharp.Formats.Gif/ImageExtensions.cs +++ b/src/ImageSharp.Formats.Gif/ImageExtensions.cs @@ -15,6 +15,22 @@ namespace ImageSharp /// public static partial class ImageExtensions { + /// + /// Saves the image to the given stream with the gif format. + /// + /// The pixel format. + /// The image this method extends. + /// The stream to save the image to. + /// Thrown if the stream is null. + /// + /// The . + /// + public static Image SaveAsGif(this Image source, Stream stream) + where TColor : struct, IPixel + { + return SaveAsGif(source, stream, null); + } + /// /// Saves the image to the given stream with the gif format. /// @@ -26,7 +42,7 @@ namespace ImageSharp /// /// The . /// - public static Image SaveAsGif(this Image source, Stream stream, IGifEncoderOptions options = null) + public static Image SaveAsGif(this Image source, Stream stream, IGifEncoderOptions options) where TColor : struct, IPixel { GifEncoder encoder = new GifEncoder(); diff --git a/src/ImageSharp.Formats.Jpeg/ImageExtensions.cs b/src/ImageSharp.Formats.Jpeg/ImageExtensions.cs index 311bcc6435..351275ebb7 100644 --- a/src/ImageSharp.Formats.Jpeg/ImageExtensions.cs +++ b/src/ImageSharp.Formats.Jpeg/ImageExtensions.cs @@ -15,6 +15,22 @@ namespace ImageSharp /// public static partial class ImageExtensions { + /// + /// Saves the image to the given stream with the jpeg format. + /// + /// The pixel format. + /// The image this method extends. + /// The stream to save the image to. + /// Thrown if the stream is null. + /// + /// The . + /// + public static Image SaveAsJpeg(this Image source, Stream stream) + where TColor : struct, IPixel + { + return SaveAsJpeg(source, stream, null); + } + /// /// Saves the image to the given stream with the jpeg format. /// @@ -26,7 +42,7 @@ namespace ImageSharp /// /// The . /// - public static Image SaveAsJpeg(this Image source, Stream stream, IJpegEncoderOptions options = null) + public static Image SaveAsJpeg(this Image source, Stream stream, IJpegEncoderOptions options) where TColor : struct, IPixel { JpegEncoder encoder = new JpegEncoder(); diff --git a/src/ImageSharp.Formats.Png/ImageExtensions.cs b/src/ImageSharp.Formats.Png/ImageExtensions.cs index f08ab8ee71..79e96175c1 100644 --- a/src/ImageSharp.Formats.Png/ImageExtensions.cs +++ b/src/ImageSharp.Formats.Png/ImageExtensions.cs @@ -14,6 +14,22 @@ namespace ImageSharp /// public static partial class ImageExtensions { + /// + /// Saves the image to the given stream with the png format. + /// + /// The pixel format. + /// The image this method extends. + /// The stream to save the image to. + /// Thrown if the stream is null. + /// + /// The . + /// + public static Image SaveAsPng(this Image source, Stream stream) + where TColor : struct, IPixel + { + return SaveAsPng(source, stream, null); + } + /// /// Saves the image to the given stream with the png format. /// @@ -25,7 +41,7 @@ namespace ImageSharp /// /// The . /// - public static Image SaveAsPng(this Image source, Stream stream, IPngEncoderOptions options = null) + public static Image SaveAsPng(this Image source, Stream stream, IPngEncoderOptions options) where TColor : struct, IPixel { PngEncoder encoder = new PngEncoder(); diff --git a/src/ImageSharp/Image.cs b/src/ImageSharp/Image.cs index b1c1252ab4..af31eff792 100644 --- a/src/ImageSharp/Image.cs +++ b/src/ImageSharp/Image.cs @@ -31,6 +31,48 @@ namespace ImageSharp { } + /// + /// Initializes a new instance of the class. + /// + /// + /// The stream containing image information. + /// + /// Thrown if the is null. + public Image(Stream stream) + : base(stream, null, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The stream containing image information. + /// + /// + /// The options for the decoder. + /// + /// Thrown if the is null. + public Image(Stream stream, IDecoderOptions options) + : base(stream, options, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The stream containing image information. + /// + /// + /// The configuration providing initialization code which allows extending the library. + /// + /// Thrown if the is null. + public Image(Stream stream, Configuration configuration) + : base(stream, null, configuration) + { + } + /// /// Initializes a new instance of the class. /// @@ -44,12 +86,54 @@ namespace ImageSharp /// The configuration providing initialization code which allows extending the library. /// /// Thrown if the is null. - public Image(Stream stream, IDecoderOptions options = null, Configuration configuration = null) + public Image(Stream stream, IDecoderOptions options, Configuration configuration) : base(stream, options, configuration) { } #if !NETSTANDARD1_1 + /// + /// Initializes a new instance of the class. + /// + /// + /// A file path to read image information. + /// + /// Thrown if the is null. + public Image(string filePath) + : base(filePath, null, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A file path to read image information. + /// + /// + /// The options for the decoder. + /// + /// Thrown if the is null. + public Image(string filePath, IDecoderOptions options) + : base(filePath, options, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// A file path to read image information. + /// + /// + /// The configuration providing initialization code which allows extending the library. + /// + /// Thrown if the is null. + public Image(string filePath, Configuration configuration) + : base(filePath, null, configuration) + { + } + /// /// Initializes a new instance of the class. /// @@ -63,12 +147,54 @@ namespace ImageSharp /// The configuration providing initialization code which allows extending the library. /// /// Thrown if the is null. - public Image(string filePath, IDecoderOptions options = null, Configuration configuration = null) + public Image(string filePath, IDecoderOptions options, Configuration configuration) : base(filePath, options, configuration) { } #endif + /// + /// Initializes a new instance of the class. + /// + /// + /// The byte array containing image information. + /// + /// Thrown if the is null. + public Image(byte[] bytes) + : base(bytes, null, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The byte array containing image information. + /// + /// + /// The options for the decoder. + /// + /// Thrown if the is null. + public Image(byte[] bytes, IDecoderOptions options) + : base(bytes, options, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The byte array containing image information. + /// + /// + /// The configuration providing initialization code which allows extending the library. + /// + /// Thrown if the is null. + public Image(byte[] bytes, Configuration configuration) + : base(bytes, null, configuration) + { + } + /// /// Initializes a new instance of the class. /// @@ -82,7 +208,7 @@ namespace ImageSharp /// The configuration providing initialization code which allows extending the library. /// /// Thrown if the is null. - public Image(byte[] bytes, IDecoderOptions options = null, Configuration configuration = null) + public Image(byte[] bytes, IDecoderOptions options, Configuration configuration) : base(bytes, options, configuration) { } diff --git a/src/ImageSharp/Image/Image{TColor}.cs b/src/ImageSharp/Image/Image{TColor}.cs index 69b99ce13a..27dee54342 100644 --- a/src/ImageSharp/Image/Image{TColor}.cs +++ b/src/ImageSharp/Image/Image{TColor}.cs @@ -46,6 +46,48 @@ namespace ImageSharp this.CurrentImageFormat = this.Configuration.ImageFormats.First(); } + /// + /// Initializes a new instance of the class. + /// + /// + /// The stream containing image information. + /// + /// Thrown if the is null. + public Image(Stream stream) + : this(stream, null, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The stream containing image information. + /// + /// + /// The options for the decoder. + /// + /// Thrown if the is null. + public Image(Stream stream, IDecoderOptions options) + : this(stream, options, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The stream containing image information. + /// + /// + /// The configuration providing initialization code which allows extending the library. + /// + /// Thrown if the is null. + public Image(Stream stream, Configuration configuration) + : this(stream, null, configuration) + { + } + /// /// Initializes a new instance of the class. /// @@ -59,7 +101,7 @@ namespace ImageSharp /// The configuration providing initialization code which allows extending the library. /// /// Thrown if the is null. - public Image(Stream stream, IDecoderOptions options = null, Configuration configuration = null) + public Image(Stream stream, IDecoderOptions options, Configuration configuration) : base(configuration) { Guard.NotNull(stream, nameof(stream)); @@ -67,6 +109,48 @@ namespace ImageSharp } #if !NETSTANDARD1_1 + /// + /// Initializes a new instance of the class. + /// + /// + /// The file containing image information. + /// + /// Thrown if the is null. + public Image(string filePath) + : this(filePath, null, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The file containing image information. + /// + /// + /// The options for the decoder. + /// + /// Thrown if the is null. + public Image(string filePath, IDecoderOptions options) + : this(filePath, options, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The file containing image information. + /// + /// + /// The configuration providing initialization code which allows extending the library. + /// + /// Thrown if the is null. + public Image(string filePath, Configuration configuration) + : this(filePath, null, configuration) + { + } + /// /// Initializes a new instance of the class. /// @@ -80,7 +164,7 @@ namespace ImageSharp /// The configuration providing initialization code which allows extending the library. /// /// Thrown if the is null. - public Image(string filePath, IDecoderOptions options = null, Configuration configuration = null) + public Image(string filePath, IDecoderOptions options, Configuration configuration) : base(configuration) { Guard.NotNull(filePath, nameof(filePath)); @@ -91,6 +175,48 @@ namespace ImageSharp } #endif + /// + /// Initializes a new instance of the class. + /// + /// + /// The byte array containing image information. + /// + /// Thrown if the is null. + public Image(byte[] bytes) + : this(bytes, null, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The byte array containing image information. + /// + /// + /// The options for the decoder. + /// + /// Thrown if the is null. + public Image(byte[] bytes, IDecoderOptions options) + : this(bytes, options, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The byte array containing image information. + /// + /// + /// The configuration providing initialization code which allows extending the library. + /// + /// Thrown if the is null. + public Image(byte[] bytes, Configuration configuration) + : this(bytes, null, configuration) + { + } + /// /// Initializes a new instance of the class. /// @@ -104,7 +230,7 @@ namespace ImageSharp /// The configuration providing initialization code which allows extending the library. /// /// Thrown if the is null. - public Image(byte[] bytes, IDecoderOptions options = null, Configuration configuration = null) + public Image(byte[] bytes, IDecoderOptions options, Configuration configuration) : base(configuration) { Guard.NotNull(bytes, nameof(bytes)); @@ -202,6 +328,17 @@ namespace ImageSharp } } + /// + /// Saves the image to the given stream using the currently loaded image format. + /// + /// The stream to save the image to. + /// Thrown if the stream is null. + /// The + public Image Save(Stream stream) + { + return this.Save(stream, (IEncoderOptions)null); + } + /// /// Saves the image to the given stream using the currently loaded image format. /// @@ -209,13 +346,24 @@ namespace ImageSharp /// The options for the encoder. /// Thrown if the stream is null. /// The - public Image Save(Stream stream, IEncoderOptions options = null) + public Image Save(Stream stream, IEncoderOptions options) { Guard.NotNull(stream, nameof(stream)); this.CurrentImageFormat.Encoder.Encode(this, stream, options); return this; } + /// + /// Saves the image to the given stream using the given image format. + /// + /// The stream to save the image to. + /// The format to save the image as. + /// The + public Image Save(Stream stream, IImageFormat format) + { + return this.Save(stream, format, null); + } + /// /// Saves the image to the given stream using the given image format. /// @@ -223,7 +371,7 @@ namespace ImageSharp /// The format to save the image as. /// The options for the encoder. /// The - public Image Save(Stream stream, IImageFormat format, IEncoderOptions options = null) + public Image Save(Stream stream, IImageFormat format, IEncoderOptions options) { Guard.NotNull(stream, nameof(stream)); Guard.NotNull(format, nameof(format)); @@ -231,6 +379,20 @@ namespace ImageSharp return this; } + /// + /// Saves the image to the given stream using the given image encoder. + /// + /// The stream to save the image to. + /// The encoder to save the image with. + /// Thrown if the stream or encoder is null. + /// + /// The . + /// + public Image Save(Stream stream, IImageEncoder encoder) + { + return this.Save(stream, encoder, null); + } + /// /// Saves the image to the given stream using the given image encoder. /// @@ -241,7 +403,7 @@ namespace ImageSharp /// /// The . /// - public Image Save(Stream stream, IImageEncoder encoder, IEncoderOptions options = null) + public Image Save(Stream stream, IImageEncoder encoder, IEncoderOptions options) { Guard.NotNull(stream, nameof(stream)); Guard.NotNull(encoder, nameof(encoder)); @@ -257,6 +419,17 @@ namespace ImageSharp } #if !NETSTANDARD1_1 + /// + /// Saves the image to the given stream using the currently loaded image format. + /// + /// The file path to save the image to. + /// Thrown if the stream is null. + /// The + public Image Save(string filePath) + { + return this.Save(filePath, (IEncoderOptions)null); + } + /// /// Saves the image to the given stream using the currently loaded image format. /// @@ -264,7 +437,7 @@ namespace ImageSharp /// The options for the encoder. /// Thrown if the stream is null. /// The - public Image Save(string filePath, IEncoderOptions options = null) + public Image Save(string filePath, IEncoderOptions options) { string ext = Path.GetExtension(filePath).Trim('.'); IImageFormat format = this.Configuration.ImageFormats.SingleOrDefault(f => f.SupportedExtensions.Contains(ext, StringComparer.OrdinalIgnoreCase)); @@ -276,6 +449,18 @@ namespace ImageSharp return this.Save(filePath, format); } + /// + /// Saves the image to the given stream using the currently loaded image format. + /// + /// The file path to save the image to. + /// The format to save the image as. + /// Thrown if the format is null. + /// The + public Image Save(string filePath, IImageFormat format) + { + return this.Save(filePath, format, null); + } + /// /// Saves the image to the given stream using the currently loaded image format. /// @@ -284,7 +469,7 @@ namespace ImageSharp /// The options for the encoder. /// Thrown if the format is null. /// The - public Image Save(string filePath, IImageFormat format, IEncoderOptions options = null) + public Image Save(string filePath, IImageFormat format, IEncoderOptions options) { Guard.NotNull(format, nameof(format)); using (FileStream fs = File.Create(filePath)) @@ -293,6 +478,18 @@ namespace ImageSharp } } + /// + /// Saves the image to the given stream using the currently loaded image format. + /// + /// The file path to save the image to. + /// The encoder to save the image with. + /// Thrown if the encoder is null. + /// The + public Image Save(string filePath, IImageEncoder encoder) + { + return this.Save(filePath, encoder, null); + } + /// /// Saves the image to the given stream using the currently loaded image format. /// @@ -301,7 +498,7 @@ namespace ImageSharp /// The options for the encoder. /// Thrown if the encoder is null. /// The - public Image Save(string filePath, IImageEncoder encoder, IEncoderOptions options = null) + public Image Save(string filePath, IImageEncoder encoder, IEncoderOptions options) { Guard.NotNull(encoder, nameof(encoder)); using (FileStream fs = File.Create(filePath)) diff --git a/tests/ImageSharp.Tests/Image/ImageTests.cs b/tests/ImageSharp.Tests/Image/ImageTests.cs index aea4330c68..0ed724fadc 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.cs @@ -59,7 +59,7 @@ namespace ImageSharp.Tests ArgumentNullException ex = Assert.Throws( () => { - new Image(null); + new Image((string) null); }); } From 90de26fbe5e2c488312dc2a2c575c040a12b7c21 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 23 Feb 2017 11:07:21 +1100 Subject: [PATCH 112/142] Fix #115 --- src/ImageSharp.Formats.Png/PngDecoderCore.cs | 9 +++------ tests/ImageSharp.Tests/FileTestBase.cs | 3 ++- tests/ImageSharp.Tests/TestImages.cs | 9 +++++---- .../TestImages/Formats/Png/chunklength2.png | Bin 0 -> 12181 bytes 4 files changed, 10 insertions(+), 11 deletions(-) create mode 100644 tests/ImageSharp.Tests/TestImages/Formats/Png/chunklength2.png diff --git a/src/ImageSharp.Formats.Png/PngDecoderCore.cs b/src/ImageSharp.Formats.Png/PngDecoderCore.cs index 076770ce51..a7765342e9 100644 --- a/src/ImageSharp.Formats.Png/PngDecoderCore.cs +++ b/src/ImageSharp.Formats.Png/PngDecoderCore.cs @@ -10,6 +10,7 @@ namespace ImageSharp.Formats using System.Collections.Generic; using System.IO; using System.Linq; + using System.Runtime.CompilerServices; using static ComparableExtensions; @@ -945,12 +946,7 @@ namespace ImageSharp.Formats private void ReadChunkLength(PngChunk chunk) { int numBytes = this.currentStream.Read(this.chunkLengthBuffer, 0, 4); - if (numBytes > 1 && numBytes <= 3) - { - throw new ImageFormatException("Image stream is not valid!"); - } - - if (numBytes <= 1) + if (numBytes < 4) { chunk.Length = -1; return; @@ -966,6 +962,7 @@ namespace ImageSharp.Formats /// /// Th current pass index /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] private int ComputeColumnsAdam7(int pass) { int width = this.header.Width; diff --git a/tests/ImageSharp.Tests/FileTestBase.cs b/tests/ImageSharp.Tests/FileTestBase.cs index b147e97e88..765ff3a423 100644 --- a/tests/ImageSharp.Tests/FileTestBase.cs +++ b/tests/ImageSharp.Tests/FileTestBase.cs @@ -30,7 +30,8 @@ namespace ImageSharp.Tests TestFile.Create(TestImages.Bmp.Car), // TestFile.Create(TestImages.Bmp.Neg_height), // Perf: Enable for local testing only TestFile.Create(TestImages.Png.Splash), - // TestFile.Create(TestImages.Png.ChunkLength), // Perf: Enable for local testing only + // TestFile.Create(TestImages.Png.ChunkLength1), // Perf: Enable for local testing only + // TestFile.Create(TestImages.Png.ChunkLength2), // Perf: Enable for local testing only // TestFile.Create(TestImages.Png.Powerpoint), // Perf: Enable for local testing only // TestFile.Create(TestImages.Png.Blur), // Perf: Enable for local testing only // TestFile.Create(TestImages.Png.Indexed), // Perf: Enable for local testing only diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 4924cc1dee..f0a0e8dd81 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -25,18 +25,19 @@ namespace ImageSharp.Tests public const string SplashInterlaced = "Png/splash-interlaced.png"; public const string Interlaced = "Png/interlaced.png"; - // filtered test images from http://www.schaik.com/pngsuite/pngsuite_fil_png.html + // Filtered test images from http://www.schaik.com/pngsuite/pngsuite_fil_png.html public const string Filter0 = "Png/filter0.png"; public const string Filter1 = "Png/filter1.png"; public const string Filter2 = "Png/filter2.png"; public const string Filter3 = "Png/filter3.png"; public const string Filter4 = "Png/filter4.png"; - // filter changing per scanline + // Filter changing per scanline public const string FilterVar = "Png/filterVar.png"; - // Chunk length of 1 by end marker - public const string ChunkLength = "Png/chunklength1.png"; + // Odd chunk lengths + public const string ChunkLength1 = "Png/chunklength1.png"; + public const string ChunkLength2 = "Png/chunklength2.png"; } public static class Jpeg diff --git a/tests/ImageSharp.Tests/TestImages/Formats/Png/chunklength2.png b/tests/ImageSharp.Tests/TestImages/Formats/Png/chunklength2.png new file mode 100644 index 0000000000000000000000000000000000000000..0d14abdc4f1fb494914c466021e880bd70a65b73 GIT binary patch literal 12181 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 Wva+?$&GrcLGAhzPB+WjfqyHa>ugB5= literal 0 HcmV?d00001 From a39ae967bc7b1a8c42f0b221143a7a3346c7a985 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 23 Feb 2017 11:07:28 +1100 Subject: [PATCH 113/142] Inline filters --- src/ImageSharp.Formats.Png/Filters/AverageFilter.cs | 5 +++++ src/ImageSharp.Formats.Png/Filters/NoneFilter.cs | 3 +++ src/ImageSharp.Formats.Png/Filters/PaethFilter.cs | 4 ++++ src/ImageSharp.Formats.Png/Filters/SubFilter.cs | 4 ++++ src/ImageSharp.Formats.Png/Filters/UpFilter.cs | 4 ++++ 5 files changed, 20 insertions(+) diff --git a/src/ImageSharp.Formats.Png/Filters/AverageFilter.cs b/src/ImageSharp.Formats.Png/Filters/AverageFilter.cs index d5f8107082..b4ec499469 100644 --- a/src/ImageSharp.Formats.Png/Filters/AverageFilter.cs +++ b/src/ImageSharp.Formats.Png/Filters/AverageFilter.cs @@ -5,6 +5,8 @@ namespace ImageSharp.Formats { + using System.Runtime.CompilerServices; + /// /// The Average filter uses the average of the two neighboring pixels (left and above) to predict /// the value of a pixel. @@ -19,6 +21,7 @@ namespace ImageSharp.Formats /// The previous scanline. /// The number of bytes per scanline /// The bytes per pixel. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Decode(byte[] scanline, byte[] previousScanline, int bytesPerScanline, int bytesPerPixel) { // Average(x) + floor((Raw(x-bpp)+Prior(x))/2) @@ -42,6 +45,7 @@ namespace ImageSharp.Formats /// The previous scanline. /// The filtered scanline result. /// The bytes per pixel. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Encode(byte[] scanline, byte[] previousScanline, byte[] result, int bytesPerPixel) { // Average(x) = Raw(x) - floor((Raw(x-bpp)+Prior(x))/2) @@ -67,6 +71,7 @@ namespace ImageSharp.Formats /// The left byte /// The above byte /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static int Average(byte left, byte above) { return (left + above) >> 1; diff --git a/src/ImageSharp.Formats.Png/Filters/NoneFilter.cs b/src/ImageSharp.Formats.Png/Filters/NoneFilter.cs index e5787a9442..5abd892964 100644 --- a/src/ImageSharp.Formats.Png/Filters/NoneFilter.cs +++ b/src/ImageSharp.Formats.Png/Filters/NoneFilter.cs @@ -6,6 +6,7 @@ namespace ImageSharp.Formats { using System; + using System.Runtime.CompilerServices; /// /// The None filter, the scanline is transmitted unmodified; it is only necessary to @@ -19,6 +20,7 @@ namespace ImageSharp.Formats /// /// The scanline to decode /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static byte[] Decode(byte[] scanline) { // No change required. @@ -30,6 +32,7 @@ namespace ImageSharp.Formats /// /// The scanline to encode /// The filtered scanline result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Encode(byte[] scanline, byte[] result) { // Insert a byte before the data. diff --git a/src/ImageSharp.Formats.Png/Filters/PaethFilter.cs b/src/ImageSharp.Formats.Png/Filters/PaethFilter.cs index ff208f3d77..a43d4f0802 100644 --- a/src/ImageSharp.Formats.Png/Filters/PaethFilter.cs +++ b/src/ImageSharp.Formats.Png/Filters/PaethFilter.cs @@ -6,6 +6,7 @@ namespace ImageSharp.Formats { using System; + using System.Runtime.CompilerServices; /// /// The Paeth filter computes a simple linear function of the three neighboring pixels (left, above, upper left), @@ -22,6 +23,7 @@ namespace ImageSharp.Formats /// The previous scanline. /// The number of bytes per scanline /// The bytes per pixel. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Decode(byte[] scanline, byte[] previousScanline, int bytesPerScanline, int bytesPerPixel) { // Paeth(x) + PaethPredictor(Raw(x-bpp), Prior(x), Prior(x-bpp)) @@ -46,6 +48,7 @@ namespace ImageSharp.Formats /// The previous scanline. /// The filtered scanline result. /// The bytes per pixel. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Encode(byte[] scanline, byte[] previousScanline, byte[] result, int bytesPerPixel) { // Paeth(x) = Raw(x) - PaethPredictor(Raw(x-bpp), Prior(x), Prior(x - bpp)) @@ -76,6 +79,7 @@ namespace ImageSharp.Formats /// /// The . /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static byte PaethPredicator(byte left, byte above, byte upperLeft) { int p = left + above - upperLeft; diff --git a/src/ImageSharp.Formats.Png/Filters/SubFilter.cs b/src/ImageSharp.Formats.Png/Filters/SubFilter.cs index 65e86f4f10..4610ed0ae4 100644 --- a/src/ImageSharp.Formats.Png/Filters/SubFilter.cs +++ b/src/ImageSharp.Formats.Png/Filters/SubFilter.cs @@ -5,6 +5,8 @@ namespace ImageSharp.Formats { + using System.Runtime.CompilerServices; + /// /// The Sub filter transmits the difference between each byte and the value of the corresponding byte /// of the prior pixel. @@ -18,6 +20,7 @@ namespace ImageSharp.Formats /// The scanline to decode /// The number of bytes per scanline /// The bytes per pixel. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Decode(byte[] scanline, int bytesPerScanline, int bytesPerPixel) { // Sub(x) + Raw(x-bpp) @@ -37,6 +40,7 @@ namespace ImageSharp.Formats /// The scanline to encode /// The filtered scanline result. /// The bytes per pixel. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Encode(byte[] scanline, byte[] result, int bytesPerPixel) { // Sub(x) = Raw(x) - Raw(x-bpp) diff --git a/src/ImageSharp.Formats.Png/Filters/UpFilter.cs b/src/ImageSharp.Formats.Png/Filters/UpFilter.cs index 036862ddce..525f50d0ff 100644 --- a/src/ImageSharp.Formats.Png/Filters/UpFilter.cs +++ b/src/ImageSharp.Formats.Png/Filters/UpFilter.cs @@ -5,6 +5,8 @@ namespace ImageSharp.Formats { + using System.Runtime.CompilerServices; + /// /// The Up filter is just like the Sub filter except that the pixel immediately above the current pixel, /// rather than just to its left, is used as the predictor. @@ -18,6 +20,7 @@ namespace ImageSharp.Formats /// The scanline to decode /// The previous scanline. /// The number of bytes per scanline + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Decode(byte[] scanline, byte[] previousScanline, int bytesPerScanline) { // Up(x) + Prior(x) @@ -39,6 +42,7 @@ namespace ImageSharp.Formats /// The scanline to encode /// The previous scanline. /// The filtered scanline result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Encode(byte[] scanline, byte[] previousScanline, byte[] result) { // Up(x) = Raw(x) - Prior(x) From cd573c3bcddf6b10feab398d6bb0d56df21c7cdb Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 24 Feb 2017 00:44:06 +1100 Subject: [PATCH 114/142] Branding.... No PR on this one. I AM THE ABSOLUTE RULER!! :tongue: --- build/icons/imagesharp-logo-128.png | Bin 2490 -> 6622 bytes build/icons/imagesharp-logo-256.png | Bin 4225 -> 14020 bytes build/icons/imagesharp-logo-32.png | Bin 979 -> 1479 bytes build/icons/imagesharp-logo-512.png | Bin 7951 -> 31672 bytes build/icons/imagesharp-logo-64.png | Bin 1698 -> 3143 bytes build/icons/imagesharp-logo.png | Bin 19129 -> 59600 bytes build/icons/imagesharp-logo.svg | 60 +--------------------------- 7 files changed, 1 insertion(+), 59 deletions(-) diff --git a/build/icons/imagesharp-logo-128.png b/build/icons/imagesharp-logo-128.png index 07b040547234acf25965b6e017ca11f063d1816c..267e56263209b5b912996d5d1415fbc3ca6687a5 100644 GIT binary patch literal 6622 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 literal 2490 zcmYjSXHb)g77Y=Sj{qXQr$EY=1|bAOPlBOjVI@cr1q3Nlr7NsrLa1T|lqLcZK@=1b zSH(vQ7K*EYf`W)3HHuhPm_?fV*!}V5&D?v=oH^&t{dMQ&@pm~ZA#@QC2tHjpaZ3~y*7py`c&^5lBm!n%`f+`lf0}U!8semqFtKeIx2Yyb#0e*FVpsD89c(4k= zoGW3aLGRuR#>N*6%Op!c%m}1pazM}szXxEkxrRQU6LB^MBqakdHv%KC4+2!Tlc#Xz z*gYB!^qq8&C}Jb@$z)c+SwaF}8)?DB#~{h92Iu8sRGKrP8)FuF%w*|2YKH+`%fMWN z04|pzs+f(ZhLF2R#a6a5U+ zLou-8%z9t^IFE2x2&SB)y$Hj9+=m~F!ex}x#Ff~=^9EH9=qDf9TA z>_@YH`!N0CE86oh`7+6(0R8`Odnzp;P;jF1K4y8{eJ zG-9;7kw24?0yw6*F$nI&q--SF!6eJVmF#a#(D$_n1(N}<(#t3cI@IbYQ552g)7qcg zjizSs2q97U`yB9aGIqd;5T8fu3BljVKz)kEovt!|bQ-yEMStnKUdv0y_>ke3X~XqN z^!hCN+efStg<@d%izx|IXPNSdAi#=a5~GQBWCl557cMjzNoG^i1W5966fGY`Q48S0 zLt+!{2)?dZOt|$uG!>g*3qKlu$AlE+V|1V4l;wln6Kf1B6M@CM?)u{;{&?nfUS1wH zro_CUfnL}^&uy}psnY*0M&By0p4d7Dc=GODo{;UzD(8l!j9$0^0)a*FxE!z4vCrk< z$?D$PIVIZP!w6>#)@Y=b-ue@*7||fKv~tj6XRa&-4dl4Mw*kKFi(V=p+g((+7xrJk zJ1(}bOuV(~uXyl`*_r}lN)L{+mNt0J!in%ND-$+m($IiLa&-vW;W5{lxLSnC@H(65 zxRaZAM~>3Ey0 z`^$)brEHT3@XR=sr~Z$!l4}yZ#`zYxbRpMhRi0a8H*VKM{u<0(ol_Z2{uS&TVA)+# z@s0G5nSG{Xjw-WBnxCi1E_7!nZqD#1{hzr%8$A@ zKuw1+13P4+Z;n(2rke-F^)=jVtO}Hjp`RzK1>1K*q9v8xkr$natv~glQ#uqYyVi)9 zW-LysZV<@VPTbV)!v!5`O72I>EB1C+3(Z!?p*OnJ7#$vTn#-hSaEAdjg4T&EEwrAY zJipI@C>3jpa2?s$$?Y2>KRF?|TTQ7pS3r< zc;4>4e&F2osW~-vysp7TQFlv=E9?(fO$tbSHQ8Aucd|n*!txxXh)-XE*6s~|9`pio z82_@%fhug2z3_Z(P~H}5d1gM~8!)QptTpMfykA z7_t@c(g=ZQUB+3=bm;H%u&Yr!DX~rCYKLXAQ^YlCs&#xV^^=;lH>n%`lj~KrP{)hj ze?p04uSPyFv(%>vWih!=c+oxt3;bM(wD94;Q{nl>hqn=0+BI{^)xy7wTwkn?JR!H5 zH`W>-w?C80X>bx`D~!uzX?8m$@3|+P7G8K~@zmGmkfQP?3VLPcYsb=HN~R6YD@5+< z7&c!uPMdS;=NEkYnuP{p_n8vP8xrK*dV!|x(Gd(kwIMHj`oim9C*j#~*7ZuI)`%G0 zF7&3l_GYB;{@ZpWq|MIuxAs%_&qw#GW@Q{%eHAL6ecP-=)EQD2Rj|K>hr`pq-9`hs zo*ZeZ9b6@>&hN5@cIq$uQKg_EPR;L|Q*b5G%@geNBkb@{Ps1j;FVtee z`GlcvJi=Fc-jntS zD+ooYp8eM(E%z$!-MFZI4mQ3O{oYl&?AE!bxc%p|Mbm{vooxac3)DyNqWqGQ+!Sti zx~s$*75BvDc9~2kWd2T&<#Eyh#aOA9ByC9^?a590H(u4fgCb)lCy;p-PEOaSG2g2- z_1C$FxXF(a;}wGJ{bcURzJ^UH+=vdU@jjgN^49j+t5$xEX7q<#$-%aM)*UP8*>t1# zfaU*4BHZz(iM{jBhDnct&F{x>C1g@z;NQ1q*R)(FL+a!REh$@X2*PvR#cgy5%J?tt CU diff --git a/build/icons/imagesharp-logo-256.png b/build/icons/imagesharp-logo-256.png index 082859f24c49999e3d1ba026058593cc09ce6137..49fc2e8ed0ce8fec9f39fdf606fd28d42ebb8cd0 100644 GIT binary patch literal 14020 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 QaR2}S07*qoM6N<$f`&t`asU7T delta 958 zcmV;v13~=93)2UXBYy*$NklwksQ@RYVkZ8w^(%d!ew6*}+8U zIt433bSelNQ{n5wljgQ86=Dq37k%-8=Yb#h`8>xR+$|H#zEU`oKf3rqo%s>kJT%edX+ zO+R2&Gyw6ra(@fCIfB;@1`=|MJ3lM;18%!<_al)D6MWe3O2|30+q{Sc(g^T$RU-V} zQ%SU+eGI1`3iy7GSOI^2Cnw1tIC&C7^?D`&sS%%C3E}9tiJ=23_6@1{_^2DdPKYV; zYzA;%WD$MGf++G+KI;wXP#J6uC(&`LSGMFC2T0pCFn`&m1q0OA){M4!8#}*B@p4Bl}PSOi_WVA1d+_CLg*-{gq z5eZ61H-BQ|_AqkY6!|j2aI1ouS2W~^@oRKe5Zv0Vra(C4!ko9TQO#FAz&AhHgEKMY%c7i@11LU5T->j!Yed>VNo z!PP2?Ph$kc!^vX6@wZ^xcL2h=hWQx^p>Y(B;D1&;Dw|c^JaWV-Q*`tJrBNHj!@BG3;#Zkt+Ee zD1T-VUoiQl1$aa9WgNg!s)w;kuC!(yQiPM(i&6y+sZC7g&whaR#9?eG*^6k&9uWO` zI9!#miw37iT8mnx2|H*%`sf~<(As!JmI5q!5{<=s(Pj>CI(-9NY+*%YAwYXPRUTus z#^n~fxHy?HZ(9Zdaw!CLEhk5*K%NvzAvd2<@DL7X2!#tEi$1tT%5jKv>8p4t;QtuF gevua~U^O}aHx$x+^klXYQUCw|07*qoM6N<$f+Eeqh5!Hn diff --git a/build/icons/imagesharp-logo-512.png b/build/icons/imagesharp-logo-512.png index d89ac3fca21e2b98513916c88ce1a30c8b28623d..e869c516db8be75257a12007161bcc9bdf843078 100644 GIT binary patch literal 31672 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 Y%Ek|rf3bdpO*iC;iq7LoC9AOi1K)L9#sB~S literal 7951 zcmc(Ec|25K{P($cR_lmRmc~Bzea|u}A+jcIckY}NM|&$iZZU2E0H2NZ z9%lex)Pw;MgMKwXkZT11103xRSlXQmA&60kVidIHkyJukuI+zqvZN_ma_6l{As5yYew2!<3gNLf|qI{x3~&PT#-*)k*K7_ zzeABrTv1L{K~9%Kl+o9`WyzGgq!?kO zo4i~oAtb3|{m)*ew9%h@Nj?dUZDbQs`&_d}R?CmXCp;9g6nq6_6z3A8M{Fo^a*AB; z_ufirOr4US3X)%#kbNVqtD_t|yhrhyiPD%OMNL|9bxOMAlm1MY+!ad5Y^>~I)r2oP z$|4N^$8CCCd6B=4DR9ZCw8^PvwHT>N+iI8CO%;mAC?TxhQtzbH>$%C~9eYR+}VE)RFRcI^8!VZ$PIe^RgD0ryE;?u)w+H|6n6719x3E^4X77@qg19W8%-mcL;q)SnVGIKnt zM9|ca@s=j4*&R@hOw83J#AOcp%Nwdimy=bGhN+hxRLBgF+C^3>YS1BExN_2fVko`e zNZoifj#$X?o^ zl4d|@^^vbhAUn!>xt~(;kUKdWFSFB7K5DN*PPSZHkyfZSC2ogGYMR__d780=QrH!Z zlak7K#D8o+`hB|Oeo3VXKlz8Rb&g6Zhwe~$acM`|>z!XJ$U7)n9!ga-OZ zetBnbs%=Kw^OK!?-;;iX`>Dn2mNprZlytzsvMOf>U>G%8o9osl)NO&5FWt&(a^XSiY!IeE0eEFw{B_dc#@l=5c>Y zQ6`Dng15Y#{nP)+Z(Qqu<`r9Qefv1*WWcq3RSJkRAOc7VU=bhyVCw%*C&LuF@qZM| zrSI$|)efZo2`6~kb7Eto9sbFJBZ55$F@8~VFZyRb)3K; z*zkg{J}jx;hzAyE+#J4_BN*&_@dy@9{@4KGs?OszLcTdka7b}H1INdlUtE`Ox|GIK z^s__Fg*!}Rx+Ys+tx$Bdu)OWj$(saEZyOhh)h?LJ;~U6TU{Kj zGZgSJ6us}1NV@%rIxOK=xYob>sI`Mp9Fztuow zdn-5c+Euy|BHhu38DsKL#+r8$_QvEo#6M`qWllD;)E)q#ZEi$S}qg2T@362M$fa*#CKg}f*EthS+l*xl zo4# zDi>&{Z$U~Ri}l)NW`u$k922Ln0B202Ne{|rJRtP*Er>#5j#~VCQhZ5e4Wyd7#lOBL ze+#bzu7j>|CVC9j6Gw7PRj=Zvc4Gp{S(cz5PkiTG$F1-y{;3sP283WP+~`FUJ|Il2 zFh|*<2*g{%1DKJ>TX=Zt6RJN5$xy#(TEUL?jt*kWP>_J?4uZd6V)O8*a@7-4 z@}jDbo*hW~%ZR-DPuwwdT>U?B2`Hl+ zXV!|(W=D?WDEUf_htx~@dd*Q^NX!$HioM0}!xIf|NrFajm?%K8L=g`RLg?sXqa}M|{bXnNj zJ3JYwWM#*Xx+oxVniokjC$(8rdZ5MHi|180zMQ^Xn)|nOdmZBUInO)CPF_dD7Pf01 zu~rEzn>lu$Wr<|8Bj+P~xW_K(M^PtbTi^#IT-}*+KtXHQci;vEHK;i%JZaeJo z=-{=ykJIQnpl;4lr^jQiTTtzecdcVK4EqlNclUZ5R$ zGi1&?p7K1^^-6xRPF5cpPbD(L@oA4el{Ina3r%D8c-$A_^U`b#s3 z&kv#6bKq|;AL_OS-Hz_<-iW_ymYf2-xr1NqpLEn;aXNS?hQ1ri5JAl8PfpJGcudZJ z;@g6_12*fr$2szy$Rn7e$>5>d`r7WX{iOH%Hh2Y~etuW>q~7O5JshzYvj=Hip<8$9 znFcTJ%eH-HiIj|!JhV-3x%H?0D*_$*6jo$=tY_)C{^QWfnN*-QR@wF7t)A&Sg)Fo3 zoqLdj+QT~^-&`1Z+=s{)aq<;*qyV*_ZRPc{MB2MyGv~5Jbim{nw3N|*|D(d_VinPYZ-IvK3Y2)*x9$1 z9`9&Brgm>+z^nf9JrEXnbgwnj4G7NiGMAcJ|E}@~=LeDA@t$ul9n9_j<6ZIb#;6U$6K6h337z}G^K9y?N}C)!n<3-!GG| zMcpKxlkIgPHS8_KQBC-IcL1UJh(XXg^s=!3$H(TQKB?O|v&472`vb|Cp5Z7~M)%js zuk{1q%Q^Z^RtXQ2rCWPF(1erz+si1rvA4PRb5_khb$+pdCB4n78Q*>?Wko#U2+>L# z{CB-teyLhN6g`nRcV7+sWbF?&7>Wah2GG#ALbQ{_T2T}%)AW@Uoi zwqreMKUmeR{c?>!Rio1oQV~Lzk{I(cpdHYA9n~Vl_7*}O3+4vKU}~c@u4*!iv1zo0 z@^e0I;|g<#W~>0aKo}fM@V}d@!gv}TQV1O}69$>|sm9#z#>q> zF;x!;qLpiJ^OVSf1UdyYnajY`z#WBxhb`+e6YxXm4f729S%XttTDXA85~&)bIV5t8 ze%K=sEIr|t%*R+rA|M7|WE1YNJB7hNJf(D=snZgX_|WGQd(v#hNSi(s zfL&Ygv(Ovm??TM>;J}&>KWM#=kTyrufxGBwNiavGiVfYK`PJXj6?3KN$`jYGuW}kk z=^N(DZOaA^e-lSIjh#nJ$;-H%1|g@rOCIk$w4tFm8QcUz_k z{^-5A?{s3w5!GZy3Z_7AxdnR*@0scHO>mc{nl*pnuE^cmTo#WbY_R*R7oYoP|5=|m zUHSK}>C5OZ{EHj)F|<6L#oSv)UjuHmzbz|x7U>SUPj@&(E`D}!W*WG0>P}_xxkmRq z5chh#l4iaj!m@iLidg33sM~fp-9`*at^Xj8f|$37AK9EgSyrsf&4ebu<$LY$Tq!>A z<`7%hLKdgUz4|$tyjD0P`o%YeL(!j}es_+|BdH1&a9iFYHCu-sAuBVBR~VaZ<2&Y) zc1d02M2W_VAbIjqX1FsizFR@%=C-R-?e=L6iR~qia`*-gr<(Cln*|YEw>88-8amiF zs9CIa@fNa?LUM1uVzs0QxVAV)RFg8rG2nmW4hr-w=(gnNfearW2{Mn=?CB1<5|)q! z>B#U`x>W0{YN3&IF2t_qsok9G^~&Y7M8?3^+S!MlQs@zf?e=auTc?;T=gv(_(hBbqrym!M|eGm7~{<}JqC_Wo_D684pS2szp z30B-1jmwJTXld`|l;0v)IMh|VIf8L*FV%OB}^?>#0tx#U{6= za}Eh#W>@NnL!q-%Pw&q^X(7&t=JY0=b!B(eAr!e)VajdU)CM>E2%@ zMmwXL+QwiHfb%Z-_G~Vw!SLti>fH7o%KM9%>EuMWCXKdPPRzlgykoPEuS)vbyRE&X zicmw=5xjkpZ$UYM6u!>v$5b~ZJ+!Lytb zy6hg&kNBg|+_a$OeC6f&duIfx{1HEj+clCo(_Xnw#!#QnaG%h&6VTR5Qq8gQ7J*@4 zW_0aYqZNHK*qH4@1yoSBn1zUY6p@e%QMX5ew5}1TyG>;J$r$`et$~>S!m)0+kctg<~HLQQs2r9 z@`?&*UWiSvnelk`)E##J1iK%H{ciKHuLlJZvG50SMC77u*v&s5-hS=*$i=vft2lsV zMDs)}1c}E9TUW)Ls0i^_ECr?Urs*H{Fd}(qP>u)^M;khc-7F9N|wqi}XCP z^A%yjnrdM46zw}}EnD}}bvdbSTw9pgs&KPaeD5}UZp zd6gzVYd-lp`y0-1H|bQfdF=xShXF$shRYj=W;?p9-N){SN(>9XuD+s!h&D7g($Qs3 zaubi`ZnnToKvG{V`O~Vv$8VU-O60v#LHsWea*#<*)>J|~6YNhoPz%Bj`E@w@D)IJP zH0392ZDy7TM?OIEwrA?N3f+8v+@1r|<&?=4)#i_{!x{D4udMnj013G&u+%tx* zpoG@oJwNxH>2N-^sqIF}lpt4t8f5xN&ZJWbv4pi6zn;??Kau=RpmP$IsXjN7_(T<4 zd8P4ot@qRYb6xW6s(CN+Cuw$F1%;R^YD9Bj@}3IdI&pCFuE?F*ZS>Vh=-}S>bN9)+P9Sped%l6OQ+D>ytts#{5jnc za#E!)As%W-OnvUX^HBFvh`0Z@-B-l(NVySA#18Vnm6^(WO8BRPrdR zI1dX|h}*S7iDECYg{OlJh(L5@vLkxe=L?JJ8_d=(0$KzCXYh)`06`A*k6`NC;&(Rt zV8&hgb5fa%KTsol>@QT$5OL(I-wiYM)A37@%uqFXwmX!OD1C^3;+NbfNEaFI>=H%d zI=?=PZ@$1HIV5Zqq8?zD*$bghebrcK=nRFvu2^m{$NH=aZi7R@CBF+jsj+;Byn7Se zp|LJ@C$S(AUj z_=UODgH6K73|Jbtn7SVbUl_VGfjhvjGMuiqsz7xV#BW6*<@pnTOpJ;o!6&EhErW@h zj58rzQZ%q{r)HHFF|D{M{dM@)L|ky!*%QjYz6qEoc`!z+_WQwN!|K9R2YFX$Tbx|D z+>}kw>uG{i{JAZe7f%SO0zpf0?*3D_w2>d8U6RX%p0$E^e(jImotbq<((V}$e9>XohdT-7EXOPNY>N|a7Yly9tfclyHl#^mi17y2Jb+!Y zxnOF&`cIMAx@tec>Xb169*zp!Aj_px9IJQ{1F)R_k~aGkls+1 zdz{CpXDW_%%|(XXpAtYb9{2PBwyngLX6|_wFYPX>2M$d6Efj583btet_>PG10C$#K zeBdD*!wcOjTN~&!pI=!Ug)Jn)!@zc5{Vl$dd>=27LJYVkOR5C>ws5i3)(sC`zUaY-L=$YXOYM&9`3Tra?EP*bFc-N+zC5$-*(pQbkvTqNAwr=& zZ`zi9+j5LUEj_BAMUUkbX3A?wKBtlzB@MZ+&>|?u``T9Avc{hUBP)$;*tzRChjo%^ znOe>#vBnD+G)|RhV*qQKtOAfr8{_SmwxC6^S@t{({Hp13wm;$Y_&+l%YYX`@06DlZ z?nBdA(crB0nLDAUcq{F0jWy%Us70|LwhbCkOBYK%-#9_xl)KZb01%tWH(g0j8e9(l zwmdL6TsnKOGZX^4|C-a~3h6OXF2RcpZ3E`ZgU?5_(cn5d+q%^puaT5Ac(Qsh@ay=$ z>~CknNlDp@OKYR^ABcbv%9(EvdOEw2PyfBKnVg?_M|o%mGPyxttFUVyym09vHU`Id z$M!HN8aZ9+3xL{6yAUlV68n*k2b$+O>ju;v&>{{v*3+NPD(I;y{>?Z4Macru8Ui|qzc6ee0GCk!M(37{%~}G=Z32naW6(p(tc`K5js?jK7?cc{J0S1) zYoNLpkU1ZWa9tBQ4qVvN5g>F>?mMKnb*+3ejVcOqDRuWEIlfY zz5Dh#p?oTn3t-X1We5ROAO!@_!=?YrZRncN)bBP<)63%bvGh->EF{P>dp(5HfRC+p zY4gErH=gmkczzk^96n{D_omCc^n&j66xCMk_kC>)?~{kNqx_m?oKqSsg2#4w+w&oF zyxaQn3MSQyFM2YYo)?w{-TUXF*4+2KDy5e~BOxdwywuZr{4+p5HkS5#{;{AX{4e8{ BlokL0 diff --git a/build/icons/imagesharp-logo-64.png b/build/icons/imagesharp-logo-64.png index cfe391e5cc1f78aaf8b720f83c708712c8a58efc..518866ffde308c8ea9dbc1b19dba7f7223415305 100644 GIT binary patch literal 3143 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 delta 1693 zcmV;O24eZg7@`f38Gi!+005o0f$RVP0{~D=R7D6IB?ufT2pT8|8Yc)FDD8122wSJi zAT2ehNd}#M7CLcdZQzs%E zDM&&kA0R+4l|?2dD9Ru%2plO394iPcNe?eAIUOn}LX#gUCx2A4L<}A%wOl6ik|40p zH3}Xm6dfoDBrQfKX*EkLm?viQks$Mv9|#>S5F02C9WNOmC=D$w7%V9zC0I@^gl#8w zaVmqECS|N7P}w#rAERO=q*^PaQs`AD>R~2&zCM|>GW+cz4l2w$bS zAT0=Jn+R~5O=B(2BrOMelLvm1wLmBbkB2RdLfbnj37&f5Mkq3Gi3-mW?LO9KqFN!Dr+@1CT1Qh z9X41F;N?{;AZyqZ~H77hfB`rcLQzT+oEhtSjCon}TK`nkmFN8@hga}WJ99T9w zO)FY0gMT7NmPkq^U@U`mGbjjEo1h;oXe)zzG$?T?e03;$Xg?=MQ6>mkqX=83MpP(v zDuZl6Cs<4*eJO$^RE`R5T1-|ZB5FH3VKHG*B*7ppY)&L)QzMZ)CkJ!cY7ripl=wVYvoKQLYqS7PA4>^NW@|$>0Kt;Y9;1mCgEr% z>t`nHX(ptfB%`7w&5|A6n;nM2KH{Dn@|qy6$Tak+8~3am_pckb(loi!Gx5P5#@H~< z*MBVe$R5<*EZ5&F*xxJr+8_JhApGhfrRKtD00004bW%=JJU-msYki#A0009`Nkl@T5YSLeQ*xA}p|;C%G&R$v zrlw`fz4vOnwQO6tO*6mG@_Ej=zz@#-$A9DD{SSYAUgvv$=eg(Laf2t^$kW4v_U;LA zvlgHQxTb;ELPJC6cwcRv55)scAF=6Auz*lAbtcQC^q`UY5(qR3h3Rf%0G%?;LSwE1 zr-a>!TnTtU#{_nm#V#rP%SN&92oUy6Am(ogIHv%kCE%au2)JFx28NiWPLI_-0e>|t zkoFA$(e339i4UNCg#S+(<^G_(Q0?GHT!32)O1Ivj0RKwBe@3M)I;9Sc0ID!b0xrmN zx}O2K2xCoI^f8Q7_iR8bjQHWBDNg64g?omp_cH-i7}3uOIsRiJXwL++!q6B{&VAo^ z0+m}s`(^=~#T`B~nY^VVHnD){)PMUl`zqS^n+g18GW`n7SV_WH+Wq~mz>Td3?oUpY z_eUND-pnu$&Adq8@$bWoVIKlfKPuy&kEMCtR1Y9D@IZ=i$mam28ZzihsH+<({C-~r zuvA$8lAdeP$`ZhTw8dQ}bV@M1TSQDl@$^!^Go}PAC7&8EBD}>q`)_(xm*}yK{ zU>?+3dCUg-0QSkBCZ`7Di}Pn!AgVTfRUQ#{HyOAKxQG61Kyb>RfBh7M0kn7r=+R`L zPy*H?xO~~Vq$m;i0LW|31;K@@E6T$0WL&~ah}e>}@s*(FT+j!U{ayW?Ku!)YjVtAW zroO?~mE~o#A=FdfJJta>3V#y0_aM}3ThccSpt7Rxy?VWV@k0Ro^>Zzj*F!?)TT2qT z000roIgabUy?Rgsz}}rSmZRJ8RBuaBah%PTupqu9@p%AGb8`K6R}XRp+UPYcY6I9~ zC-ROEn=Qn_cQ4?9pi)j#yT$;jXE?;V*=*e)#`ofwfVLOx5OU<_JAWYdqF=Lsj+SR5 zp~>;GwX+`+(C#gk;>YZe%vFI2&|YqqO)P=Ns=x$j zuM{T%?^~6+`Y{1@PrW4pZ-1f@Sb%o_hD~mX)kSax!u`d@w^e)CS8l@1YL5qqMYQ~< n+5%i}fG6Cll?MrK?G~>9p@0CpHQ7mz00000NkvXXu0mjfx#Yvb diff --git a/build/icons/imagesharp-logo.png b/build/icons/imagesharp-logo.png index eb9fc6d17368a11fa9fba395035d9e5cdeaceec0..69b2ae89e01710ceea66322fe34226ff4b200576 100644 GIT binary patch literal 59600 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

4rEa)M#F;U>%>5Wqb=?{o=V%C#`;(?E2( zo_E&=#okS-aNLs%9`%uBMDgQ>1(zk$iU?%NJTi)&e>V#< zl|h3>_nr{Mla zanFGZ)UBZjA3EDDPRBClDhIzv{e6(Qoul&WS*~}-6Ng(1wm~EH+F0nvwjR$fk3M$@D=8q9bWkzyhDdn zCDeG=>7~+{Z_IB-ticW3aFeFapinK~hHnWqDo%M{{)+hU@y=3-8CUV)CJIt;?=I%% zs^5iC;*QJWu$Tovug-7orj`})^Q$^QjhXc$aUTyQ7JM50c{rARata6geOoB?aEa zs2meF=ulw{?hDnBem*dbFJVt@BYt$!kRBbFp3%sdDKZ2>7h*A$Adc_L2lPwCBM z50g>So`ba)8|=9NGMXXBUofnH=R%9;T29Jdaalctx@nOxV$XoWf`!%fQdze2`bIR%5d?;as0h@Vd;$HV5g@4 zvup#ZAd;3Gi_%3NG3a^_Iit2D(Q9!0vA#m%>h!xUqhoOxukN-G+kg?nKOdhj;AFj4 z0xKDhQQF*`Z@;qrv!*3(?Qbi_fIZG{q~P|XV>!$JkDSyuHxJJg8or@9)?iAmMWu0i8^v@H_LI702AipE!hf7SGYBGQ6i-htkL!JF z`{~JkR1S>XTZ9)`T4>(++}aBF^rblluRGeuZBFqpsV`z!>;$&fghJgGBSIzWr>%}< zjLjDD>mFaG%ROL{BL?6*zwTxSU2$GU|B<@o=wRL4g02z)()aThS~Y2F+dmF$>=Nn(0`2ziIFhRyvH#z~qC^9OKKlxPn#i zPK<{^i~#W?UpEs^@R4%tOLv5q2W>n32e0*SkiuWzzX`g!|1Jf6XV{4$6vq*k`jHs~ zAERpUtAfm!#*3h1SRp*?U$B4ViT}f3hW1ZFn?FGpIR5(nz3`U&oB6MhzbmcB7hBio z?2n^)djFB>*q2Iq^~QhIns{X3`uO1FzttLa3|BtoeSGmXH0&?l|I3mQvLnLUym+Z& z;cR`|kbaFTOJ$7IQmigvMFI!YnHoCU-MlnYgJRHJunENmM_mxYw;s2Uw<_Ye!g@`1 zSx2P1iyZ8-!7GB==dYBBaRq9!h#u3DZ4Rhwj+==bVvYtH6-zh;;rhtNvPJ>O8W%X> z^v0^U;l`Zs7gA&8Zf4=Ea82JE;81iGJAmH{a0=x?_qRM^z=0vgdxPH*3?r$Oweep6 zYRbT&eQm%m@1T2kaT^$T8N|KV`(M27x|R`I)xjy;8XB!`7e5}cKUmAW;E&~hOp!ZY zM~VJ{dUkjtat>scjuk@bD9?}{jkcTFY!DGF;FhzQ=hBHkwm(;2-&Rk(AOqG);Cit* z0S)wR@WMD#0Itsr6+8*tCP)3xX1q_(7j@!k4C=RffAF40{wWcEAj8o$@t%%Q*rNKj^X&1V=BP& zIR4j%a{MdH|9F1KKfC@thC_p7cUQuMk?&tld-(tBxQTz?wCJ}jWgL`Fyh*Xi3DO^T z))cWWho*goOc&3tVqwv6(xiXqzG`HiItLy5>>pgW42wxW z%)IDcR5;bIPGYN)C64;l>jUAZq=62X?bpitQ^zttTzRvy;zKsuGRxLNvXZ4btVXtF zpHXN=tG?#%y@w#(vNQ0zX3oU+E^qB=1=*<;6F=A2q}MF{7f#UKv&o2aK{S>F)U(S+CMH@WN2d*$Fb+>$scrNb!Ve(Vfs2SPm4-c~PMqBEBaDIqy^%z;p z)a#@!cqVgaPb2rrKI|IvjOv{|ySlGk@YFuFR`E@18~a^S0UthFZ2Sn4c#G(vE+88< H9b^9=&gi>T diff --git a/build/icons/imagesharp-logo.svg b/build/icons/imagesharp-logo.svg index 9638e9785b..2df3cc80ce 100644 --- a/build/icons/imagesharp-logo.svg +++ b/build/icons/imagesharp-logo.svg @@ -1,59 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file From ec79e5b43ff6a38aea9c8ef8798f45fa22d0b9e3 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 24 Feb 2017 07:56:09 +1100 Subject: [PATCH 115/142] Re export images [skip ci] --- build/icons/imagesharp-logo-128.png | Bin 6622 -> 6569 bytes build/icons/imagesharp-logo-256.png | Bin 14020 -> 13949 bytes build/icons/imagesharp-logo-32.png | Bin 1479 -> 1439 bytes build/icons/imagesharp-logo-512.png | Bin 31672 -> 31256 bytes build/icons/imagesharp-logo-64.png | Bin 3143 -> 3132 bytes build/icons/imagesharp-logo.png | Bin 59600 -> 59646 bytes 6 files changed, 0 insertions(+), 0 deletions(-) diff --git a/build/icons/imagesharp-logo-128.png b/build/icons/imagesharp-logo-128.png index 267e56263209b5b912996d5d1415fbc3ca6687a5..6d05c222cd8e42e2990d05fb33b745a4e1b208c2 100644 GIT binary patch delta 6533 zcmV;08G7d4GpRF>B#|)~3c>&Y4#EKyC`y2lQz(BXNkl=FN=y z=DEH3bo9*|!y3aLB`itU6Ov90Aczh`1w>IE2r7t*D9Yjz1Vu%k2nwPg;KCx}f~crb z0vLfL5J>_gL;@t86~yh#|1{M}ch#v%^;X>peZKEU166hJt@}TBzg2Zkb2K9QLqS2o zr6qqQB|Q!vJUFtbsAxKP27h09=+L1}`27OE-@1b1U*i8iz~5JcXYu!k@q06f1D7Mr zKPth8Ez}I+T?s}&wE5t3Pyx}%pCHU?kcoJn!@qASEG%rRL?1S*4W;`6h%g+2ECB~y zg|ZSveFVG-XSn&ukt2Uqk{7l>7&82#!-szlkH#-o)2E#xgbHwkU-8S$$V6^_lw^m^ z7vpvpnVNu1U4`FM&dhEC`~o(jxfp_b_*dn2!e-XW%F6x>(I+4i_q$|jlLYrsjNb=x zb8|0IZZB+lhK#xh%tWT0aAu&lU<5!v{`eyuJ9dmtoH#)h6%|xjSxME^)l^ecL$!ak zwNzJER}cRmAFHaWBD>vA<>lpc{P=M?dh{rveJ3YDwo@DDdR&Cu9eWNneI0rFZg!} zYQhVZY-*l{4C#s+&2znZJvj5Y0~d;=-7K7T?nrvz2s~m)P_nD}7`OHC?)!gngFmZp zT8&fqKy~DT0pE*RM?fC^4(2tRz5#CPBHYj>^^Hn!Ro|3Ld}Hvvanm5(yBg$KCLHDp zB^#Tj;YQk_n$5+ZLSCFss(K-BslGUMSDEZT+ggfEtjwef`3hl!e?Xy3kl z)@?9Bgs(~oVYmkyZu5Wr4?6)DEGf?!X!8Cek316ihgT5MO;Q3F9>a!b_W}nOETbVJ z@_(u#Q(z2j(Uow3YAJGtH&aRp592?0F2!K7=rr_+R^;-p@$3@<-zr4v;A~#hu^OID zl{8?WQ8u*tH$^u883Mmk9hnpEphyV~6F7K1MbbiHrXrhv?AU*?WB-jnD^-y=2zUB| z19u7y`U5{$D zeP2`gJ*|H=4&cQ8!cA0LlCQbWYV=kO(HT5|8r}Rl0mP4y0%C$+`fJfke;i`9(1_>w1{{?paZ$>RBe z)71+#@zrFm5e3(yYSnb~4T85IX0U9$MUmIo&|!bL7$=Lnd_rMI@5$Qz5Gzt6p0E63 z?~U+Pi&>QwR53G76xxW&7f0dR;Ea-ZK~B;R{8ms<&_+e)7v)yZD%Vo>l;oIU)sPAE=Z9Dn(ZauQLU<% zeX@V{uf1Rt-*8J zNEb4}*yxd6x|e#b9YXKrf1o+W6E%{Up3@r5%J-V^E0;|cMfDU8M;&&c;Tc*UPNkh(3M6{d!$n0!g*?lvNZw%Rs<6ukMNBn&78uty9%xu9Zdf5&PT(s;jcDKfoP!?zl^OihBic;0E=HjMZcR z<5eeU;Cuh4aR6r8w%MBVPu3klN$w&F>`G;E1Z~+6LC-uHL3dAxppiFTMQLfNl$x4K z!-o&2@#DwS+&Qyp_1GSiKVBBmSE_&3&(owokm$_`zyT&JvNsq?|39UZt?vCV*zvL` zJ`=ObM&A_g)LipZ^%7BZX8+Czdh)Rd8k`nEskR1EQu=}vH*CGSQF50SWQ%S=Q~I@} zEjPCmh4h-K?Hr3r>huQ+gVDe`MfL{6fgJUTjFmfmiz|*edi|34PHx&fQ8RxmtgEX= zhcL-a_!C7D^wg{fx;DjKcn2w+G~%ZuB~o&9D~AKPGP!RH%D!C?(@UyezgLy^z`-Lo zNmDpLh1=Qys)P%r6B#Qff1cT?U;15nd;j~IYfsgF0pZWA4`h87K_hMugm=&qf+uPm zz}B;iMtmn}SGS;*!~Ikn+>3t>p@j=a2K|6YMSe%=PgI}4SULQwz%Z}>JJ*YXt4Vrq z1f8hXPD}q>HId$56G3UIlJE*CT{YsTB=!@9SMzjgOFAKm>LFEY9?_&dQ0NT@Q42J@ zcL@Iw$N#+FObRp*yt{vsD7GHY?p&-1WBs}nsPbFN!aHaU!IL!(VC#R@P83{X9>SMI z^@NHCqp7aanspU^UPICrn$K6{cMJhvlTNa7@#l^$(?p^5h{dq5;CNB}_1m{^N3T8& zCA^y;yh2K>R{VbPqR>XHxZZassQLC?xBCwidW%D70zOvccQgUw{J&MNbosHejJmH* z6@@lp%IA+duIHL6Jw1P25!D8<+iJzPMY#!IPycvI>C?j_()#wG?8OgK^(QZTP3_*# zH2Dw+cn(L>7BxVzBL6x3$d~w)Evt6@uFKyn3fvexEd2U#7A;t?KqYK1jRT~l^i!SV z8#*nmy=D#}j3g+eI{U`pR+4s-AM=7vC@Mz}KqQHM<7`);Zx;E`vjXB-+c^$wHcIZ@N0$k=jH0qlA9_uB)-EwW z{Egm>APRmzo&$gABOSErsUKeD>-JWPC~l*?kwk6!x1-4T_LOYX_U?JemzvVu?f)Kn z=po9_*N($ElNK&q=x`3#^Ap;4r2Y}DblG@id&;`9gIDCK%n;^DI6f3hk!$+ zgM5(`n7{e1bdM~q3A$wTq1JI%Iq23O9W)o=&R|nG^Y4EbUU;F=cKiMJ-*H;-e@b zSv$k$gsXY8owh}b7P(!U7=4W>J~KX|tthS;cC?9hEI&(W@Fxay0S~bQ+$$Zl>V*$0 zbKe!kHA8D5v@gW%~Ex^ zSWJUI(OVP(PjC~!U&+=Sv}zN`KeLyM;+i3Ha3?nhVEPW!4);cl8YRYU#LSs9-NG2y zqo?Qq13;!AtXV3)({4xeH+pXrf%fx*?f?sSz9N5$YlgNM>Zu)|eQ)hb--8c6D8_BX zE3drb9u7tX36W8v6MQBJYnBsVeIv&2PxN*Ng@gmVuCvEI09;BzEfJCO$U-Ukk#T<@lGGV*LKZunExX1X?u;Si5hdD6SczMnsDa zz+Bxg#!a}qygV^}2`(|bP0aOrC$_{yw_#zLkDb7Z-Fw9}_!GlUpx!Xhs-@shi@p@a zHA9Dq4Z}c*eo6i7h2u`06u-o>Wy{<|S2KUGYfQZZjPqd_$kZGSd_Efv1M$Z&>7Z3J zfqRbTisG7~^AimdK@#nIcXku*jW^y9)8NXsZQC5n%Fb(&le9C8Dapw+pmn6f0Uq~Z zB4}5~o?;sOjp0Pl;lqb}3kNKF_a9FM$Gl||1#XOMRwPlIzEPt1PU2J@!g=%NiOzq` z>*mXsFLx6^#g;Vue#B1o4*heGYkRd~8#*7)Xc=2Ldx^yYcnKOrOxZwuP zdA)k}qYf>*(lzaSYK}4KQL?aRIWT|Cnyq1mi$QwJ!Hg7GsF9PCqglZ52gyk`>eQy2 z!vUB+@eTb6Cu`EZhsz6}492^Q8LVRWJ+_@TYd?mJP?|Si>&TYGq zExBP~Y23JRj@>r-6Q_#eHVPc4vY=4BAAcih3(e1L2w}rNg1RM)c{j6=R(yZ{gI7HF z?za@1a4p65P4t*vLi;{2A&EM+ig9xQhMj_sbB&XgmE}lxRpCL}8P!b|*?>I>gEe^& z2n@D_9d?M%hJT2gLamw&WJWym1^vg!m0oi}>TLR3hjM0l6X3`a%`ii1EX&0k?IJ9@{nPH!rjxA#-q3sB|rTt@I z7EMWdjwTtayIB-m&C&InG}i_SQ;&wR!6WucCtCSlz~lC6`ky=B5k>Ws^M}1i zm-e_(6#wrX<0)aF`wV|yGJ5#j(^re%wRK+_8a3G=yen@%|EVVZR`@W+RAag(i9-6yn_v9kh=b=i zcujzL^7dt-z>R+~s>5u`-nL(p{=?6|tPy`l+pEdGCr5K`pfGw<7<7(OWOp|l6h97O z)#eaarYy@61@wjfkA19(XV>TZL~+eBeaK5xgIdcq%-YH-+TAC`%>mdcT+azK2Ae<% z#Fs>|;owy1BCDGL#$HuJaWgiHLVCmbs8P1>cV7i$;aGn_Nmx(#VAL}R5$*B3k98#0Huaqty z_qHgo5it*ZKs74IEzB#SF`eg%Li)mEBbK6&xTk;HYV@{%gWup7treM#4JV5qj9|}_ zBDIR_f-;J_d%Y;M#JTA!>C@fXG-r%^Lln?wI?SRynP-NX!Ga&_m$XCaH~%3?RQI}{ zj0yHfBe*ReHt#c0a5d=*wGUlnZOM_v^O0Ml=hDY3t$iP`8iS8TFlmas<_vv-&xJsO zG9-VLcy-Huy27AIxMa*4`T?T|*K4XVw7mb?MWTSdFfDaaNVNZYr5{@U;L%5;*idEI zq`;V9*8)z8kJ;(Ymow?l!mQz}HQJ|vH@)_iD4@4+sbBf@M^ss1{SaA=LEFzA_|b~2 z=8b2I;5k`RfZP`dr57aaE<8qK7JWq*j9P!~5I=JAdMdAUU&cQEV>ykF%@Bq3l+m5$ z(DV1Mp@aKF;}d!e*UXYu(6Bf^KmXqqSv@nOTEMDD$2_6rSS3BbDTg-i((Y(}?Xiub zn4WM)kEdwqqZ_DbU+4(W7(DvL4lq`c({od5fo)R2OfY5#y`dnd7{k6fqNt7WkGOw% zDD7+M%MIBfQPr#ASwFo^AJ8s;>(;H_e>|EE)qQsaBtAD_<(tHOA#X>aW8&wj+g3Q{ z{wMaHPj_^G%E9>9d2~)Si1FgLWCItgCQl(bPW|FK9T zLqM;q0Hi{9M1{@7%5hI{;aQ@spo<$~PY+)EUB ztT7BTu0-Tz>cluP?V_|0J^+#Gm^3GsiKyA&CMYe>jMDUnXt39NkENK?l|hmXa7T*>~>u)@N^ zX;MO+@aVwGCykpbJd8FP>M;;Kw65<2;xDjlxaW=W zWCRi^Cf*6pV~0lw4e&ZjBg?u@GD2E>r=cbo4JQzvGmB)(F8GirDd>1?bGxYQdh>NtV;r$EE|e43ISIB=jn5`RFtF;}<*gvw51E8vb^ zW2Ii96t!s&B|Dp@?cKZgV%*3^)s2a8-=H}U7kQ0D@3C!xFTv$XHa2ZT$K{W>nft+M z<>n0G4pDPjP~vgD-Wz|r)u6`w9i{%s9XodXNy)zEW9azB;0E)g8<${5Xv#GLJF!{e z6%HYP`YYMhJPl58E<8K~H)=RaaD|%&?jtmnZD%~X@t~@Amiom71qBx<+0^_EHysVc zCy(^tp(DSxE);78P7|liWarM69EjgX$(FOrxNiU_7>i6ORb_ulqi{xZhI5ZP7&QaO z$#HurI+yx#{7yz5G{3gqQ&T=#F$0L-EieN&6Fjcw>NgOpj~ph4&2eyCQW{KB3!X%6 zcCm8%VRIU)!VBR94=bfZ$~@C>bHPG!N0LS3d3F|%OD$K|42#M*|INqv96pz?;cGbz z4oix|Lu$ZNXy<<~Rc<$IdWMWYA5L%=$X8`zQ{)gFn}@&`DYq9kv&IV_$hbr}!Ycgg z?mP-Xeny(t<2TLchcm-=#*mp8p(KsL?+2X0K+5=F@(V=&4EzH_!2eQ`9kxJNet!O6 zkm19@Vq|y`kOT=hh_?j4%ki6DQc_~=uH3LS7&>ZyhY)T2w)1%qV=I1jcPo1fYM%;( z&F@NhmIX)0u$2;h*h0;??*OOxD}?F^MgrchmH`o0;P313_bvF{<_iArQ~Ywf{U(0r rJYk%1o0)IkeDmfR!VXDH*dqyqgoNAx3L!xjN03bfMGTA285k7-bzDFY z1Oy2^waW;yTkK$c(7S+dG!@Q0m ztB~>N6m7(!gv=fK{4X)uehfu8 z&Q+pbNJLBrFn>6N%F0Tpsi^^r#R4{)4eILZbV29RHPzMCfa5xpmzN7p!sX`<=m@9K z;l|;;{zAO%kWnO$l`;&4qX?d@I5nu{lGr4c)oNu0^%Zuz9cpW91*f2%gDa07@Lo@# zlMbPK7pvjsG;CSju`<5qDoG7$;nceQEw~os5af8&Lw``u!1$DvH@&YZoD;&;1&dHTu4ZOFv@!C7Y>S@|WT_4``1$ia7qO7b;7{|Fv z4_3%YG5U9_SHN>Cuwe`mtEuT3-sD}_>SmIL*>FgLevSpqSlar$18%)3B;DKDn8AJxtk6lg=Rw7nAkdWB6xZNoYu6) z0)M&e_Q|7ZiIlt95Ii(Q(oo6OrxWA^VWJFyF%7y=*x+bomA^eBh%m6hh_kZc` zA$BV+AK;`dO|F0;{0prXs>K7S*%DDc?+hc$?py{0Jps!VE`%}AjEmESFq_TvM3Hx| zrO}HE=wNn_jS?_82<$0=vC>`F{jGXc(6;5xl#5cKFG@A1+&Fqo62sb(lwx!|>?` z7g|F>MMcG6QBL;^J?EF0a3PmyFh>0s>&oGhbt$&wUaX+*aJ42D(~~V|)m#9}#uves z|E2WzL4ew<05!}R(*yZb1QnzX%~sHg$M+fY@1Q|?yQKOapjCkI2#WGcqpS|E4sCv2;BfOOAzsG{@=)V=$8;RPq5Qm6;qP*&c(S2tw0V2@b zycg>%*yYEv;(1L?M@;{$EKfVsEXZI5XUS><)*UNkX&8rAEPy>kw0|hGYs1#R+{MLg zrbctwx|kQxODw#!7BdBXs&IiARVkSTbUd@VQx zB(NfS3QZmB>SVEvr5dVV=*0@G2g?V~Vmih<0rdhXv_d}-Yhhub>yKSx_c4;WNJuac z^ZsgU4SZo%&bxoIZ+~Zn^ps1st%^LX+ZM9Ix`SoI3yO4&fZo60bR^2k$}&Z{m4*&L z-{DhSL~pHd8Tk`Cww~#G=0M`cOen8aK4@~Kb~G!dyIidusYu&iZiecak*uihV%@b$ zk-op7nHr~~$VO3aso~i1Yi#N4M;HS!^;>mh9d!91Rp6y>lz-#%MSDI9*hV3!d$huI zeo&-cyEcmz(<7#}1>5m`iah*{t6&f{{s|lO?V{{A49EZB+y#^nmw`X|uJi|p%^A?( z06p;k$16@M&bw@#%ZRF<%huV7H0!5*t$8s$r24s@P*-W}6H{u-!|X7Cp25D$Z*=!> zv031uZ4(>BcYiQ>7X?+)b7>h^*wtgi)xuVGAC#4z6z=cFE9!1e``n;5Te-&IPt=A1 zY}6}7+3StA4AB9~xs#2a`#X32a&{FeyV6Ux99Cdwu2hD>p}k?S?9DL9dNK?$)5D;D zS}dfcr3nleFaXAk83Qk5O@y4G;c#XWFQQjG-3n|+c7G|#?@v^Qfg(}*dc#(5pNkU> zjsDAO%Axm$bXI&K{o}D& zEueo)3z*e69KIRD2-; z;^N{*MfruzpKR_VBOm&psa~(#!K~=c^x2RBrPx!*ufJ@0MiAa!8V2*9=Y_8)N+Et) zzZ6J~X{m4k2M={?0eeUDg1Sr1+$6(#0eV=3(|?sf&6kVv3jrYeon9lSf2)63&kD{l z@4!OE^%v_(q3UcjOqs-q?jRfmPgXd9>HZj2cxPx>*fN|K)m?1gexfM9zi<@?AV&o8 z=?@#=5A2^=F>3s$MIRd9Ik7pqukR1X%1=nrprz``lZG&&JCW8!A%0qNA}csY8alzY z5r4d>?oj*oqmr`w6MA8X(-s^3C3^0Ip8V;=onYkrpXU9X&}uxs^O=V0$x&y`nk5KI z0Uj&9sTCeim5Ps;x^-m5cEtmoT0q`dURd2^wS~I##?-IcdH@EA5flKjZ~Pefs@EGj zf_D_qFIY4{KabNK6yYI615^QKJ z2t7j%u#`J!)b=k$^j+7RSb_C0p0{dgz>;0&No_pr5IqLZJR>-Rd16EkXQyP9pL&fIJo7p#-PM$mo)2B~wIF~S` zq(D-5Ye)=h$ucyo6&$=b!effwGUo9ydYX&V7#(BpEoQTs-2=F>LE>MY-8YXFR}bw+ zb`l(*b;owICkt9TLuT@81v@MbGkkaQo40##dq-Wy?-59arJQe zf1hK^;cMtoeoo^vXU>#R48R^VY7Jo2_9?8mnygEO)`=Yi2Oy$)E4zAUcJAEC<}ZhW zrt#y)-?-Lff~3~%Sn-L)VeMIQ^^pIU32YgBjmla8&=9~Lt>6@44T zchD=jw?uRYlP6DR^LEAd?SI=P*QdtCv*Hu8!rHRp>fxvN331z-kZL(i865DKuH>XJ4CV$FgdIZE3O{C?~%mj z?`!Cp2To@qpblWO+k~fmIDxf@eyO*@0f^Y7`z68=Uwq;I>szBokAIe=VM zXXo?xC2l(a!6oDmk~clX3akY;1UQIEkZ$_8I|R@on0yEjNKJ*LmTe>s@Pt=AKo2&5 zUlY_0khyg{E3g(^5P#@QLQ0T|TcCh)&p%u_^SZcx=!J zwhX>TYZ_QAKR_7u{h#N*dW{!Y2abgxS2zcRv=DUk=ux%|4u9yL=gyrg5xifDatk@N zcS3^$c(V{xFnJnV4qxMHAqcUM1F(PH%c!S;mK|QpimM05Qm`v1Aavd*pbeaj8#l7c z-1+(WFm2j2iO@-Xk|Cyj524jN|DH&WywB^U;InTmQIx|UxLOKEPSEoJpD+gu`wUEe zMFD(pd=qRqv40gd5+}F9rjy%rk$ZRxBn?Q0xa3|gh=;zZ%16M44I3sb`7U3+92PBF z1XHF=VK;+1b%=$C@GcPFgL#OwZ!)~x!t?H*IKN%r>ZG^08s_o$pSj!)4&JYTNg)B{ z;`oBzafyQXL{zJ;ilKmdlOr={v%>14MHE;|{QuOo+JAO1HvU!Q0Dt2S($s-VFxa5+ zP5wZLx0q5*5Z$(Wg98xVW8x&|tX{nuZ0FCzcU=;Afi?IcmA*y9-;*!Vcp&6CdL}TP zJ80xP0R_dCuyV%=8-H${??2_udlm9Z*a_$7z<}cwVCP{SR4zwJ^W* zx}qNZfq%xk0h#nE*a+?*jT>Bo{@_e$HT>$~58$>D>pbT7_m6}>Mf7xw|A~r&lz!5^ z1M2D{vCHQifMS9{g9b@w6Lvcs9rzF{vKmzf4@v64U#LA4ipQdb4?^M(SLFu@qo#s{ zY2Hp&SiLkGmJNUCI*1k86BI8Y&k8WKMPh!OH3ccD4f)O2NHHc4)j+zUnj(;fkOU$nu2jZ?+k9Sa#58Nx>)NPJoWjPKD0I=AeNqCW^}?Z(204u4}sm$P@vcZ%|t zT0<~T(%6`L2T>Nne z@**R@5<(PJ)Cgj6B6`EBoyreQEt#=_6<04g3zXZ;Re9e+UW=#(@$*_nL-k?h*U0>d z+SfsZ1t7VJpNYG~U10PuKz#OH5!~+O;*c|Ov$vxoOY=DP&1o1F`64T9KZul_4hcBx_4u$j9k5W@hm;NzpLnC?QWj9(s?24Cb1 z&ktY8is~+}PhKZUo3Iv_2ti-L<5|M6AV2O;sKIhN5~Q&xx6)jFE08d9HsDNF_BX7k z?l585SBi9ZZ#=|`=@AigAb)SyaYY{IKi>s*gTJyys73$S{SOu8R+{*=5Uczm!pKD- zXQ-tOzA#Yrds^8FKeu1t16qg`n7N~UrSJ;ugM@!(D_wt~! zM!M!+eW4bfOn8G8(;cRzE`on8-vgDhX=R{Md-V&oI!s!MvbtxOu**LnNo&nqVSLDF=W5E6%Kz{V91-i?xMEoLtsxnNR-okqj3X0h@BA-Mtw=lTRvU; z6)evB5dMxa0FB!dy1tB4!IuOk_V|aetM}Kz%V6H9)$rD|T=?+Kov{CtBT!rz+>gdn zTthJ#rwxI}_J~q?Zs-6m*9C$pAaoa6VU%wUH8#{1eSeVaIGejql+imwq1n|zqA?8x zO$n{89~-Xrs-Ir7SSGL#_WEQ7+#bjyrc^CS+{p&HDsF6O1%1dBY=E6anfz;*(bG%;8VJlBG}O*KK9BhL|Ke8EL1ae0+*|VM92xeLubqX zHvrrw%3`eHQ1Ck};AyU44no6$XV8~Dw8Y2b1%DKni*f|UP~cWrkn>yt9fVd3-Gc%? zE%A{eZ2f-{WeBX5m6dhG0=rx(Bxs5lS|`v(?TuasSl8uPpY6r!4-O+Iz{08a1hiO0 z;~ID4H#^igG&XkxNPNYcy}@E)PoU}q)U}274y4dBdWq|=c{YGueo4@^e7T_$grUek za(_4A48;YZA))>?E@~899XW&N6a>wfuVom)-h~BLogxx=LyhL45?@0*de~UldXa;5 zxi8?+zPpB9T5}Y5HFraDs5c0MLh7rBB#^CYnv8@FK#?|}lFnSYy9K*K^XynNzIXy!xHVD5fhP=LcjH?jJf zh7}eT-iaQ)hPhc5as=us8*`69t9fn;Pxrmr?_0WoH0%N&#NL4YYo#1CG|+N#V|n6; z9F}@8H@bQOyw6f>_5OL1C)f;I#@*=YOU!2Tb*?g8L9K}@HyC>bYVEX;Lk`Q8U4Mn^ zc+FC(6R~=39z#L9;LY!2Dn%8lWST;eGX!e(Ku$dUqc}?a9B9X zRhm0c4?#{ry#xtH!6da->ZZv-NPkS~IULZ>1RbaI$dPDDO6wCemQ#9MKJJ3)oWydC zrh64{J7jXh%J~^q{2=UBcXO547(wB8;r$Gz-cG#Dkm)gOz0njbVkNR4t_o&Q^ja+Y zV*FR%XIVn#+6oH`Z^N#bzGS))EA%IMWd{)#v3$8W^bEaz{rZ23#SfV;4S$2Z+c5}B z$JVtHE4zph!BD6OSeAEiY|ljD)jz=>$2x6RolGA0>xCL5`px?B{NM0i}%`!u*y| zo*1jN5Q`DAU@;7|evUmvXB22CI>mD+*t^JQDBubFw;Ex^CFmGlQ;h%8R`w?RcOecv rGw^rmI5X&gY2PYl(?sl+TNeHwOHbA}tiG5p00000NkvXXu0mjf0U4Kf diff --git a/build/icons/imagesharp-logo-256.png b/build/icons/imagesharp-logo-256.png index 49fc2e8ed0ce8fec9f39fdf606fd28d42ebb8cd0..fc59b03e1a19171ed999e936e6c6ba908682edb2 100644 GIT binary patch literal 13949 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 VW71AW&=&vz002ovPDHLkV1f)frda?0 delta 1401 zcmV-<1%~>c3&#tPBnkm@Qb$4nuFf3kks%uj!vFvd!vV){sAQ2wD1SFeL_t(oN3B;| zY*bYg?WYlc;MX4qv1I_MNEr%DuyhKQnFCM9Zp zeSLF!dwUCddU~n_9e;8k5`d8Buv|OjS|!)~@$vE5YTEsqkS-9?8o?yR`uqDaG&F?K z(NT$NMIySL8H&WD8Pb8%pr#1ssLLYSDCutYEz z#PIMi!~tezTCOGH+$=?#n00q|+hy`)l2AAjiD<>F5Dte8M}JgCE)5I}%vbc*CZY}z z_cLlnqa(i?1OkC*HvC;(T@EFqH`a^;S!YI|BfJ+d5C%kUW>?9XP1XD|2hOB;6m)`_ zU^%FzWN#W59P2d`X}=x(et&dLlqlK%OyRrOR4>|@fU@};Ox(XXMKWBr-vj*;`Rv_0g7sp>PJK!r!Mx>`x z%gIvH0nx5x2HKet-9X}CQ!$p+uQ#ym%nnpmRUpTmf*en(IN*VMwF7SFQUfP;ZbGnr zcMRdSpMSKR1kW1LZes?-0~HQnm!X01#QDr; zw48)0L5L2tSuMivo4WD(myIzv3-;lOl`aEIR=K1c9yn5+c+$QCCr_Nhg-u&y4s`8$ zUw_LhOebf%gK)&(=Xb_Hq7UP)j{-j`R^tNh~zhqp|R;4URk{d`HS|WV9^1* z<~Rgz?+C7J--Rph?ACG-aUZ4pBsP$8V}E%R2ZCWdeeh?K1NFaN#g9iC4dHl{?Kp-| z{{X^cJ}nm^A0moR#+p}oFg`|BeSlw_U?yfsIo2IGgV7;B-f(_m5`SrN5vq$$Y3E15 zOf!0(z=k+Y9u^3u*~953#pC?nBe>W*isNO!;N{eV*p~JY-u0B>>%BkVa(j<8c$!n$nqc zBH9lMw@oO%{ZCoHM2kXU!c`scuGsqK`9D+Y3yNUgn zM2&Tli6w$5MnFdr>6n?d=!b@L$yRaOnkQmvT~rH^@pH=3a4XRv9$p0R`A#h|Vv=)A zo$vjckj@tIJQ1ms`*uN4iLt(ZdEYAc6>^^^*93phlal@e=-F)Ejpr(H00000NkvXX Hu0mjfSHhoq diff --git a/build/icons/imagesharp-logo-512.png b/build/icons/imagesharp-logo-512.png index e869c516db8be75257a12007161bcc9bdf843078..5d5fb854ead971869824ee645d57ee0aa5f912d2 100644 GIT binary patch literal 31256 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 Y%Ek|rf3bdpO*iC;iq7LoC9AOi1K)L9#sB~S diff --git a/build/icons/imagesharp-logo-64.png b/build/icons/imagesharp-logo-64.png index 518866ffde308c8ea9dbc1b19dba7f7223415305..4e97906e517651723ba1060cb131a9a158278408 100644 GIT binary patch delta 3069 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 LNkvXXu0mjf#9zUF delta 3078 zcmV+h4EgiC7{?fpBnkm@Qb$4nuFf3kks%uj!vFvd!vV){sAQ2wD1Wy}L_t(|Ue%j< zP*v9*#~IUP+NLv|E=ead{i6>Q2N4tz#0?b?d=FBCIvFKu8W&6&mk3&06=D;rMy+VA zCOS~NSq+**jz8Z-0j?|0rkm;3HLycGn`%x8G-zO($k zzjMy-oO|visJ~NlbANMaQ&ZEz#>U2-4Gj&ak!!bZ-Lm3eP=eQN^c!-9@?x0^^3`HlRh<8v-BNrF|4OrLPR3`uok(wENskXNEXJ}{+_GGh4fDp_J*vkz1 zEXpIG3G?Q*wzfVkH9PQ0622RSI|K#;_8j0)2BXmkU0q#ZvwzvZ;cy7CTCHF_51Q{bTvcI7b(ca#!bO5KItgP&D$?U#K zI0t`#J^zuiA?kH^0qtQyQ%*e^(AH9%j!#PF2bM@YM)(WL2FUXwV+IQ92VWrYJZ-+kfEojWd0Knf)} zzNOo@Z&S3FD;lglFG4*Md|n;u>gq;eL3ybgY;)cdG##)5Wd~qTyo;kbQj+IB(VW3T z@+OCZi>Q&o4(&owxaU1{1=G4nuz#HX6F6*v-DfDb%(yq`#!;a?Pe9M) z@=SpTK0Eg4T!BzA-&kIj%bNFOF!4&vJ7Ke-4}KUe@pt8>eI&-xdkXwC!kc+BTqX{@FDgre1$^JimB@b9Ea zd6d`{MB3NKlweJneV2BQ=XX4Q-q5Pu1!@s8O2E@IaCOcsA(dVGv*vNjnYrlx1 zgPHJ)XLRuL5*?%&h;T?vO=TD+MuQ-Pm(*|=5$SAPQ? zt^ErX7lZ{p@%m>wWbvHeeX?2pz1z4Pc5dOqiC`3-=yX7G66m8skYL8M(ZO(Ru2{Gd z%lm(DvB4YY9znGlyg>7xp4hMp9UUFQg6`2W}6 zR8Q~6MabegG#ZNMiG_1a8)7hU`hPxMl{ZPqHk$vZ_yvEKzmgjtX~c6Do>>7UAC@qo zX{qBR2ZZ4F6xji(DM>=o4b!3a^HQ+aRrQL)>bt$l^8)Pr6Xbx){DMCL@Xassh2k0i z(nHc5F4lnJhod9n*!P(;XTqvgt64wxz~^-9-`n}0%^+(Dz9 z>qV-9a06uI7if-%3c2IO(v@QINYu8e5EhR^9K}0DCLjjL&dzoTxaH;LkdcuAsRjci z4IcrCx}l62$fv)EfUkPt>YN2Gws-^G;BmdEy`4sYd_h#e6UoQt3dIuPfurgrjHyYJK%tBgit(DJ|b3FmpdrV2^~O)3V32- z(JzJK3GM(QYT8(@9blL~9TJ0wd2~Qo=on#L?&xy|7>ef$#S?rAaDS$zBzT+xoRsJo zk5iy>>{LxXp3qDI3q@ONB4j_TF z46agkIKw7*Kx^NTmy@Sy-y4(%B1i)hPYHYeZTt`wo6))49@2LuXsxy^EyU z`G0lw+WS1Aht}`{__@+%L)TJ%35@z~v?B!8#H|J&$~d8Up)g3S1hW#EQ-< zE2fw*m;xiMRLW=n1a{oVWV<9IutXJMAUUgSfV@2zmPp#MqF5L4$mu>4&l3$Lthhkg z5H&ndae*Q|id2f`HXR6f*t<*E$Sc^0e9u78@C)?b!CPqRk<-<^PD>p|`(=`u{ePRV z@d#{G-Yjv*5p?d}AMt^DTqK;pYh}nt$-IY?=*44fJPYYk8bgqgbg~ra%a$aXMwV!( zOHkWcef~c}z9ck`ZlHG1ZKlF78FB`#&9r>d=pd(1@Fa0GQV9A?*XYoa_V468!ICCJ z!yW6*cl+M&!%k!j8nqD(?2G>w?qiLH`uh3}gG#tMVFVsR=WWPdbV$8QfIrk@kMG0! zrcvEeGmlQfO;a!mn2$nsphHUVZ#AO%cUz<#^HpJ<6DamA{F{N(OD9#+Pbnzq|Lp_A U%CiqL0RR9107*qoM6N<$f|0P+y8r+H diff --git a/build/icons/imagesharp-logo.png b/build/icons/imagesharp-logo.png index 69b2ae89e01710ceea66322fe34226ff4b200576..ed1c36c5ea23eb1bb21eb0be9d5138575029a3b4 100644 GIT binary patch literal 59646 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 F1OOzA^^5=j literal 59600 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 q6!I4(uGr4 Date: Fri, 24 Feb 2017 09:15:09 +1100 Subject: [PATCH 116/142] Update readme [skip ci] --- README.md | 2 +- build/icons/imagesharp-logo-heading.png | Bin 0 -> 8609 bytes features.md | 16 +++++++++++++--- 3 files changed, 14 insertions(+), 4 deletions(-) create mode 100644 build/icons/imagesharp-logo-heading.png diff --git a/README.md b/README.md index cbe03bb4e1..9d5c9788a7 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -# ImageSharp +# ImageSharp **ImageSharp** is a new cross-platform 2D graphics API designed to allow the processing of images without the use of `System.Drawing`. diff --git a/build/icons/imagesharp-logo-heading.png b/build/icons/imagesharp-logo-heading.png new file mode 100644 index 0000000000000000000000000000000000000000..882d8376c075c02a5379255222a9464de01dc086 GIT binary patch literal 8609 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. - [x] Bgra32 @@ -133,5 +143,5 @@ We've achieved a lot so far and hope to do a lot more in the future. We're alway - [x] DrawImage - [ ] Gradient brush (Need help) - **DrawingText** - - [x] DrawString (Single variant support just now, no italic,bold) + - [ ] DrawString (In-progress. Single variant support just now, no italic,bold) - Other stuff I haven't thought of. \ No newline at end of file From c85dc30a367cfdfdace3fbc4aa78b5b04ce2111d Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 24 Feb 2017 09:20:25 +1100 Subject: [PATCH 117/142] Reduce logo height [skip ci] --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 9d5c9788a7..87708cf05d 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ -# ImageSharp **ImageSharp** is a new cross-platform 2D graphics API designed to allow the processing of images without the use of `System.Drawing`. From bc3680cf6c53a093cd5a3081f986dd59c994017b Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 24 Feb 2017 09:20:45 +1100 Subject: [PATCH 118/142] Drop px [skip ci] --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 87708cf05d..d44a15bfd4 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ +# ImageSharp **ImageSharp** is a new cross-platform 2D graphics API designed to allow the processing of images without the use of `System.Drawing`. From 04a46bc0f846254ed5471865936212ec948a49d0 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 25 Feb 2017 13:58:02 +1100 Subject: [PATCH 119/142] Prevent logo squish on mobile [skip ci] --- README.md | 2 +- build/icons/imagesharp-logo-heading.png | Bin 8609 -> 10474 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d44a15bfd4..9d5c9788a7 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -# ImageSharp +# ImageSharp **ImageSharp** is a new cross-platform 2D graphics API designed to allow the processing of images without the use of `System.Drawing`. diff --git a/build/icons/imagesharp-logo-heading.png b/build/icons/imagesharp-logo-heading.png index 882d8376c075c02a5379255222a9464de01dc086..b10d367bfda6135939e5ddea15ffed3be31293df 100644 GIT binary patch literal 10474 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 e=J%%uQ=TEM%|`i-dEi_kilVFK=pu literal 8609 zcmb_?_dlFp(6%1IiWVeG2%?un57AjfT|}>8SuI2ty+-dML??P(tQI{=R*6L26}`9U zy}ft7&-4BT@B72<-e*7OGjrxTGuN4!b4O~bD}sm~6JcRtfs~cxwXm?T*MMhlLLA`R z68W$N_`!D7Qk2E28ll?+26#3yYBE?@weiH)7WlyUzO#~nD;5@M&%Y0Lzf*}7@aH2p z1${SdCu=uP3l|ucvxV(DH{N$JH%39;=e#cj1aXZ%U|~HaSC*I2@iN`ba&&(9CZm6S z+5do;p(3NK(#6KpIJ!)MO`vyx%)r6|8}!yI5|5maFuyY}hA}h1IH}`VKMXR(^q?yuG!Rkg{P!7FxkJiwf8O zl9BQGy&G);HwBF+ad1Lqp$3|k(5%ThXeHxq4f9T%2RC7G0#Bha+R@Pw1Oh#=kt!=M z2V`t*Z+{V2Vxc(h@Yr`$Qv>yS;EMNFC1RtZ7zt&El=Bd&6+g2jR0tYyb=!O|_>D<~ zu9g_?&hOni+S$1XiSh?JXl4WOEUA{IfWGoG+j~(X-(X^iN^-Osanrm0vbzQ|5+1O8 zfUzYjTaP?`8#_7vn6^ewPj765o6*w6Rf{b2oT3(yXE~Ej1{dtyQ8Z78ZD|q1oW(Nw z@c*I4ds-x4w1Y$YbxwgL-&p(#qgaZfR+xi?uAPGM-EXjYaD0@87bw0@zYX90DXW z3fzaIv5bW8pQa!x+rue;#HGLX^OIPe2-OGh)h9DX>zV;eE_TB<(-9OD)QnS$Xuy{l z@WtVr+1k#?c+CHn4*u?4k?$8WdjR@HJgCPXKS$|ik*A)_z%(mguB)wya?EAI=SxEw z*antDa%w;@qZRjU%IxH15l>4*L-j+QBvMUjZxnHx0avK z0R(YiT34_ucT^3h)O1g}rKF$SB)^zd0v2-56K3y8&useaw^r;6_rt^8mJG+9)tL@u zH+ajImEZf3)udJ<$xCKG3UbUbVU)(hr-Z?y=q7d}ADVk^khjsn_K_#1u-PY!o*4c_ zIB$-h?PUC$z$VI^-2y&YW?V-=1vL%qi%o5TuXE8?!!KnX4?v!NQqg`}C5!gVf}hlE zOQWqbjJ^;jJ^D8nPaTcf&S9c^%Ld;26}v-S#cEm{OBvZWFHv$~7`7KaQyiyNNTz=e zbC~oGMr>fZ;SQlqZO4}mq*6MuS^->zWK_!ENTA{6IL*2|zj8jP9u|vaBuo!10798% zvn(ruCaPYC4gB|a+YX%}<$Qb=U)ptXk*z(UljF9u(UWj0yu7jZI!L5*uqt(MBd6!9 z+Pz1Y%!y4IUM5Ef%jY~h_TE0J);2Tyv-;x4B#}r6_@V3@W&jmsQO>LDi=&k?_IIyt zce`zFkTE+KeG&|QexD^@vm~h%xQQD9(8jjL@wiVo*IL^Y@6TR#ID0ZTOz@YGl|7aj zfSm5l-{IPr`klOqVsqm>MNL{OJeKW%u5@`8bx#pm&ZR>bo>5dr7PRE@blFbte zhapKqhhhoShf=xpI5{~{gM;fs=>pCRotc?*N%rC*vu@e|P42&J=i%ZKciTwVHhcs1 z;I7d_B6UOQs8bsH&2-y!7b{Wp#|ljED=#>Pe-Xy6f7roDx=b726m(ne?Ljw z$@viBO~{Hb=9H4dh`TyBEN-ta0L&;GQgb5^%W<)YhDe9{hlsR*3ONVn%rbGgL`+VF zLG#X4R|An{KXp>g(P1CyUq@Zx?g2^9Yp3@?fM<}o+@6^DK0m_Jq6aR~1b-!4bm5W`2^Tc@DU=o=XQ_}k5ctT545$lpKmHjrtypPs? z-D-t+-23hRl`8(9h{c|dPR*;-|0_ZmCEQ`iGSsF>k}sra)RWx7!+Ijy?#k9ZrOkR) z+#yltxo3)j+2yvt*;WQ2^5|gCq#rBYVZ^2HMaK#h<0s1X0LQ&VrE=B7t`3tfFE1_o z<0_3Bd8MT0o18d+HFD@6cMZv1o3_7R`HP*e_k=%SK3Q9hWtf`LpKkQn#ZKrgtlhz$+4*s6N< z1RMP1?QM*8$;-#LhpsFB59pa|0YQsT(fgEW!eu?x=98+wl-9mdptq_PdowmighTlX z(c_I{5US(5U-83>@OU%2+q5UDC7H)6uY09*Xm&%d6CVTnM?aUBTVH>SX|Si7!L6(W zEPB_g6mi>5{x3$L!aqv0QaD?kC-vFEdCo)d)wlS4Qz_qL1w};432EW>sN3^w@{EFp z{oWF$|HRU33gOG9@Oa((RQSn4aivlrg`QZeWasMkm8okzH9^dyij*v=_n|JeO8_^e zgof?TG?t5MsAgK}p2k6!1r_ZV&rL$u@84;fxJmzygJl6sqBU4!Ihrfup2yHH#Ego= z2cTw!+iSM#S}YYj;G)6B%*Dk^-Rk!~dg*&UIuo#Df+kD}4JhFi%e5nt$E5h(T%G{A z3j%@Y?(Uv>*Q)k4CplSLM<;a5)Y6i9w%mbl zF!#jN6oaVqLNAS(yzu48wvE3Eq}sGC;r8gKRJ`2I_S^YSK)w+b74@cEcXfRo+}pAG z$tawZzQuYtqt50hlK_>FU6paO%l<;wSCzC3Pmrc`%45Y9fD@lE40J!ujosY?3qN_oOHkiNACBm%P)=@5L0k-49g9Sr?KZ+)DOpv` zpS%ly&1kl5DYNmh)nj)HUB?ChU_w|RwdXXT#TJN9O#S($7q{^(O;<}BCr%6ASJ*u} z>%V`R8yXrS;)6JkCQ0eI>G)F-TuOEqhpVQOD^Gnks;c5)2L%pfy8}}Nh^InsJx}Gr zM`=8ADRiq%ciXnKW|dx1gC6nn@HBYPxk?UzD(7jPg%*B93NJ(AMaN96CfqY-AsIQe`Boh$q5wzbB> znz{9y0CnZ*E{6BHQPQWT)$aY2tO92;8Tz@bGEy~PKhPXwde`| zRs3#cZEcCWPV(7P;!FrJy1;Uww|Ng_{B)Mf*H1 zcJR{vRUzGyB^L|n-KAeNK1M`pa?NoAKhwuF;Kn_E5;BpuIreET@bGkZhDHm$!F;qf zTmbULd9yp)nnr;Dj!ta7mrP+I^S)!ts>Jc-Q@K;U(KKW8a{~#GCBu3v{>WdvH;!G^ z2rf>}(fSQ$r+uETm{`Pc^mUET;imxOREc*#tm+Wt5H)YjX4(SV*S(>&15R#F6>#IF z&dE}CTL$)4Rvx89X*h@eZ@|1xja7kO_6R)hZry7ijN$VdQbWH!pCfcOxmf(jpcOTO zx4}X*{?c`GtIQ#W>BXnQ=^*nT?oLA=u%d@SX#T`87TBpT1{vYmc&c^2brEwtwMYE!o1v@2AzIvCt+^ZN4 z^`6OY4(N8~p(Va$gc}<(;$rE66ciM;j`~?O7!t~Fujm09ts3AP#d8FiReJ>?RvF5o zJ!Ee)^|w;m%)4;>Cx`CSu8VYBfNmBk3ByXs=$iK1$j#5q%*-?*F+olRAAV&KRPS^a zi-C@vXAD^5HKaJ5PqQTb#4df!-yfO!ECk&P7OGK&v-tiJc9`y*s`G>u5FgcDzY-0; zCc|!GZ!IcXZY0#|`P1yG+%`um?BKPRbG&`v1Y}uWVty%UXH)n;;lar8mbNR~wA;KE zc)vSN8k;-nFI8?NfPwZcx9ADP;j91hYl}b1Hq!_g@iRN@a3W{#MRExG`fEK7GhCED z1v=JI@Mu^I0X1BCQhB>`$9P1L4M1WN(`HHKj}+e8WO8bU8yap2RtTIy>U*!RuMZCo%gQ*nzjHwd zrkR&Y26UZcR|TI%J(g>OzOspNNli&1dGhKrI_(i)1M!#r39jmIZ%sb#W&r9 zH3AAkEu>|DegWF<=T|{N~_m4&9`#77H ziVZXKzvvuEEwXi|>fTH-MY4c1h0PvWP_hM0S^H~+sJ?L znzjFN7ZUjs+NPaPooPp4RHp1dmdZ~=AHT0C>qH=CbNw$o!e zq+)`nv#*`0a5Oq{cfN=?or*4lk93Xdb%JGBk*FknRqhK{^(Au^Ky#=AlI;oPq_0E#oC12XxL_ zr(GfU0?vCFv{;DQ8!COVlPzLy_)Nbk9lTrl{Z%EIO{2)`R;F@kNX_=;@|IA2J|&yF zrUNBAcK}DH;Etdm#R3$au$lBs#aT?Rthd7~;N}FNgn9of$5hh&e23yFoDwcKL4Vr% zce7$qjvr$Y$-izAXq&u$TdR#uI=lWVKU2#n{$Yr386jcY&74t96X*SYG2*2Di+ujU z%Dj+vxpRF#AI{Gxtg8^u3GR=`iT6;$rg<~(dj#r5 znin{j-!_|)2>dV&^0FR#uQsawG~kkAcuFB&vdR}k6dw1&%H+BEQnEK+%w4=z=-K=$ zS)bP|>Nj@nYxbef?G>)@G3Q+&Q591f>_>B7=mHwr||tv8w3 za~L-VN1hV>{uZ8q_0U3fR02+i^ROIW$ajd6|| zOd6M+i))N`VtC(ld#2Ip&6|e`4bFh~8Pr)rb;R?vg!dn)pRT{~+mdN$pm@oGh_ zac9R*YRz|!UQcqqI*nfE)iu+)>V_K{<4)x{Tfh-~<^G z3UxfQ6z)ehu?x`}TQb*2}uJ zktb&S0e@awT2+R{Mtl6ub`ICSyH|&hBdrBWF&*MJnwnb04(%!nbVEZIZ^?$|b;Dk> zm(f2-jg?_C+#1OePvbQ))YspDp+7ycclvHizy=1)km3}4UaepAwj9g)^L1U$&Cbe? zWU8Oo5QgiAwds9*eM0t=3ltxb$EpDSS%2Rhn7T@2h1%v2B#Kbrn+X=LiXrz+rk54i z{_xxWX2JB~NGVuXI{-!5jR6Pu^E;Ckqa%I>vK(aSf!VpVYrT+;|KW{yum1korUdgw zf%L+(=7spa*Awf7cu!i`kwz;4S#o*;3Nn%U>C*=T{sHr0mP>+VZnyh%@=;qP0rC#^ z;SB2GT_@6T<5myH`F6j#d03uv2H#TK+SZ>0lsc3dy073$kNCU@CXp((AhUk*!xX&$ z2x22dnQ)E753S?(`aH?>%>)GcMQ^!%e6pQK&Fi_p7c| z`dhD$P~pAd5v@}oUv|#=t~%r0pP}5&KutsA^Ro9)@jKG`977ng9aOUlq~i}CKD=%P z51W&D?k~t-rt!>jO0_*+gCm)eit-o0F`ifV{%XgeCPNh-6L)p-l}i}RvbxPYvvVCOB2mg6By}|!P!kiiFsJ@2@6SO}a|T5_C=@CP_nubasJ_6DGr5)Y zrP}>}`uWw+L=iJ5StSHWF0S)S)kK8$tP)kC;6sH#0!x)+6ws59tpY>8)fa2k)d@*& z(bG!5xDP_7I(1}Vzd7M#` zld+*4_e;&Kl|E1hbet~Vh{VGo9w5zEj&!6t?5WKm`}PV{*LkSNu%id}MzSSlHv0mr z=LXK?rW9V!R=QI!M4^C58UEJMMKZs2 zE^nMt`GZ&@r-B;)iSo&fSuyT8{i_GR}>wk`|IW6iD`t9~PyXI4_+OQ~ zms)K2W1(0bk`oI5y6kHVrPwHY^26WRuOqJ3l)v2Y?#KmJJUxT&i7mB+M`1{#7!KQ* ztk0LT=uy_$&N5FDD5GqGs_E=mFV;h(4GbPBxaT%Bxc9|8qM9L{$yj9g?B-`aD=xYO zrzkP)xPNG=Z|=kR`~MmYnyfJBVc?shI%i8aNdakzjS4^a1%2b*k)w;LC`fGxMxkXw zNYOs_cWw#1bdx3g_AdChCZvjc=t!taXbV%;Ir4ZUPvOK zF4S|!0+KF1;pc^@t7nP^5nIf_2#|`t0|`&TBJbpXPVt8i%Fsom3)mbwjowS&KeiMD z-&d+b1fBFlULnP%B84Ew8G3^>j|ejeu%$}GY;XMRFkoKyC?_&+EvQ$aZOVpGZw7v7+MSZTFBhFrK^IfxkyW{3}lzdn*B_ejbA#rG|Hs?pEhF?9dw z!tS6QjJm9}D2nlAt>w|mEtw0nd!gD6R9l}jg0L;fro{bU*=h>AMELTz6WHnsD;Y5> ze?h!wm%2ID?oj*HS{)<>3n|yDwoAqwG9T#lV=PsvS^NHfjNf3S-li{?n+P8QkIpBkD@@`r^S z3nsa7f_YF=U>KWvrao{wmHKoxBCz?@!nYaYCXsQ~LGHHDdSDX=Q$XfKw@Wm$4HXqb zC8tVX*dAc=0%&^2|wLD#GS!Kg0uoY|$WE*_#=S=D> zN{U8;;VrvBu8xW*>-$jvRG{uhK8eY<5DMCOlM4l3 zeS9I;zd0l(dKLOIZNoS^Bj0RIH!{8(p*trrO?1*-O39BxPOJ-LeP>}W18!d(@piWR zUFp7g1J#j%vz?*{*h7%v@`*2d99PIrl+gS^4$EAe;iJ8sWIiWb6XO#*`{%8~vT01< z6i}6ETUq6lu2pxrQU#xn7GqDT7mG;*F@yj4)1yDjYVY-^`v{__kU0llD!SPuEx=?Z1Zcs-LnO1szVSCk47i zwKg}Sl_&WhCHa+vwP-pkEB^pGi|5HNFr7Q-y7V_pSo7~@TT+F|@F>wm4hgEP4I&L0 zrKP3HvwSSM!oW>&z2{-NOrMo=mLL45?jMgCN9|&D;QHojA>=_Ey?8jU4is7okQdMa zJ=vSjVX8HEZRX&aP6|Wm=9ie=Fx(zu+X`$?xU4LQQ_4ZnW8eAM8RPW zx=7i`FAVR$$f%|mU~_;=G|uzt36i?2V5}_Lk+1(9Jn9`{?1WJ}dPo@+6=H#YdY1$yOUMQSr00Rc7t$*d}O5TISCM Date: Sat, 25 Feb 2017 17:46:20 +0000 Subject: [PATCH 120/142] Update SixLabors.Shapes version --- src/ImageSharp.Drawing.Paths/project.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp.Drawing.Paths/project.json b/src/ImageSharp.Drawing.Paths/project.json index bf6b1fae8f..b761233c37 100644 --- a/src/ImageSharp.Drawing.Paths/project.json +++ b/src/ImageSharp.Drawing.Paths/project.json @@ -44,7 +44,7 @@ "ImageSharp.Drawing": { "target": "project" }, - "SixLabors.Shapes": "0.1.0-alpha0005", + "SixLabors.Shapes": "0.1.0-alpha0006", "StyleCop.Analyzers": { "version": "1.0.0", "type": "build" From 07b5ff4e2d8553ebc6a54e45c22baa1524bde44e Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Sat, 25 Feb 2017 17:59:11 +0000 Subject: [PATCH 121/142] Fix drawing on transparent background --- src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs | 1 - src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs | 1 - tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs | 3 +-- 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs b/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs index 913293ff34..95f4ab4726 100644 --- a/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs @@ -110,7 +110,6 @@ namespace ImageSharp.Drawing.Processors Vector4 sourceVector = color.Color.ToVector4(); Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity); - finalColor.W = backgroundVector.W; TColor packed = default(TColor); packed.PackFromVector4(finalColor); diff --git a/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs b/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs index fed97275d6..4f468c7070 100644 --- a/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs @@ -202,7 +202,6 @@ namespace ImageSharp.Drawing.Processors Vector4 sourceVector = applicator[x, y].ToVector4(); Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity); - finalColor.W = backgroundVector.W; TColor packed = default(TColor); packed.PackFromVector4(finalColor); diff --git a/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs b/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs index a41afd3334..1d3ead81f2 100644 --- a/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs +++ b/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs @@ -32,7 +32,6 @@ namespace ImageSharp.Tests.Drawing using (FileStream output = File.OpenWrite($"{path}/Simple.png")) { image - .BackgroundColor(Color.Blue) .FillPolygon(Color.HotPink, simplePath, new GraphicsOptions(true)) .Save(output); } @@ -45,7 +44,7 @@ namespace ImageSharp.Tests.Drawing Assert.Equal(Color.HotPink, sourcePixels[50, 50]); - Assert.Equal(Color.Blue, sourcePixels[2, 2]); + Assert.NotEqual(Color.HotPink, sourcePixels[2, 2]); } } } From cceaeb03e6b406dd6ca7017131b98591ba17333d Mon Sep 17 00:00:00 2001 From: Toxantron Date: Sun, 26 Feb 2017 19:02:00 +0100 Subject: [PATCH 122/142] Benchmark running on linux Script for benchmarks Fix from cherry-picking CPU governor not necessary --- .vscode/tasks.json | 7 +++++++ global.json | 4 ++-- tests/ImageSharp.Benchmarks/Program.cs | 5 +++-- tests/ImageSharp.Benchmarks/benchmark.sh | 7 +++++++ tests/ImageSharp.Benchmarks/project.json | 14 +++++++++++++- 5 files changed, 32 insertions(+), 5 deletions(-) create mode 100755 tests/ImageSharp.Benchmarks/benchmark.sh diff --git a/.vscode/tasks.json b/.vscode/tasks.json index aeae5c6ca9..128265ff6f 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -13,6 +13,13 @@ "showOutput": "always", "problemMatcher": "$msCompile" }, + { + "taskName": "build benchmark", + "suppressTaskName": true, + "args": [ "build", "tests/ImageSharp.Benchmarks/project.json", "-f", "netcoreapp1.1", "-c", "Release" ], + "showOutput": "always", + "problemMatcher": "$msCompile" + }, { "taskName": "test", "args": ["tests/ImageSharp.Tests/project.json", "-f", "netcoreapp1.1"], diff --git a/global.json b/global.json index 7346bdc280..c1b6f33634 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { - "projects": [ "src" ], - "sdk": { + "projects": [ "src", "tests" ], + "sdk": { "version": "1.0.0-preview2-003121" } } \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Program.cs b/tests/ImageSharp.Benchmarks/Program.cs index 8c609f015f..789068426b 100644 --- a/tests/ImageSharp.Benchmarks/Program.cs +++ b/tests/ImageSharp.Benchmarks/Program.cs @@ -6,8 +6,9 @@ namespace ImageSharp.Benchmarks { using BenchmarkDotNet.Running; - + using ImageSharp.Formats; + using System.Reflection; public class Program { @@ -19,7 +20,7 @@ namespace ImageSharp.Benchmarks /// public static void Main(string[] args) { - new BenchmarkSwitcher(typeof(Program).Assembly).Run(args); + new BenchmarkSwitcher(typeof(Program).GetTypeInfo().Assembly).Run(args); } } } diff --git a/tests/ImageSharp.Benchmarks/benchmark.sh b/tests/ImageSharp.Benchmarks/benchmark.sh new file mode 100755 index 0000000000..1966475bca --- /dev/null +++ b/tests/ImageSharp.Benchmarks/benchmark.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +# Build in release mode +dotnet build -c Release -f netcoreapp1.1 + +# Run benchmarks +dotnet bin/Release/netcoreapp1.1/ImageSharp.Benchmarks.dll \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/project.json b/tests/ImageSharp.Benchmarks/project.json index 866a36faae..6a8be9f896 100644 --- a/tests/ImageSharp.Benchmarks/project.json +++ b/tests/ImageSharp.Benchmarks/project.json @@ -14,7 +14,6 @@ "allowUnsafe": true }, "dependencies": { - "BenchmarkDotNet.Diagnostics.Windows": "0.10.1", "ImageSharp": { "target": "project" }, @@ -46,10 +45,23 @@ "frameworks": { "net46": { "dependencies": { + "BenchmarkDotNet.Diagnostics.Windows": "0.10.1" }, "frameworkAssemblies": { "System.Drawing": "" } + }, + "netcoreapp1.1": { + "dependencies": { + "Microsoft.NETCore.App": { + "type": "platform", + "version": "1.1.0-*" + }, + "BenchmarkDotNet": "0.10.2", + "CoreCompat.System.Drawing": "1.0.0-beta006", + "runtime.linux-x64.CoreCompat.System.Drawing": "1.0.0-beta009", + "System.Reflection": "4.3.0" + } } } } From f6823888e17f2d36420d3477738786704ba329e1 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Wed, 1 Mar 2017 02:25:33 +0100 Subject: [PATCH 123/142] added "BenchmarkDotNet.Core" dependency --- tests/ImageSharp.Benchmarks/project.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/ImageSharp.Benchmarks/project.json b/tests/ImageSharp.Benchmarks/project.json index 6a8be9f896..810c666bcc 100644 --- a/tests/ImageSharp.Benchmarks/project.json +++ b/tests/ImageSharp.Benchmarks/project.json @@ -60,7 +60,8 @@ "BenchmarkDotNet": "0.10.2", "CoreCompat.System.Drawing": "1.0.0-beta006", "runtime.linux-x64.CoreCompat.System.Drawing": "1.0.0-beta009", - "System.Reflection": "4.3.0" + "System.Reflection": "4.3.0", + "BenchmarkDotNet.Core": "0.10.2" } } } From daa7b65e86668f08b0fb9eb345f3085f865ce28b Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Thu, 2 Mar 2017 00:23:49 +0100 Subject: [PATCH 124/142] revert adding explicit "BenchmarkDotNet.Core" dependency --- tests/ImageSharp.Benchmarks/project.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/ImageSharp.Benchmarks/project.json b/tests/ImageSharp.Benchmarks/project.json index 810c666bcc..6a8be9f896 100644 --- a/tests/ImageSharp.Benchmarks/project.json +++ b/tests/ImageSharp.Benchmarks/project.json @@ -60,8 +60,7 @@ "BenchmarkDotNet": "0.10.2", "CoreCompat.System.Drawing": "1.0.0-beta006", "runtime.linux-x64.CoreCompat.System.Drawing": "1.0.0-beta009", - "System.Reflection": "4.3.0", - "BenchmarkDotNet.Core": "0.10.2" + "System.Reflection": "4.3.0" } } } From a52f6bf1b5065db0d88680c8e6c8a5ace1e6a037 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Thu, 2 Mar 2017 01:50:24 +0100 Subject: [PATCH 125/142] BulkPixelOperations skeleton --- src/ImageSharp/Colors/Color.cs | 3 + src/ImageSharp/Colors/PackedPixel/Alpha8.cs | 3 + src/ImageSharp/Colors/PackedPixel/Argb.cs | 3 + src/ImageSharp/Colors/PackedPixel/Bgr565.cs | 3 + src/ImageSharp/Colors/PackedPixel/Bgra4444.cs | 3 + src/ImageSharp/Colors/PackedPixel/Bgra5551.cs | 3 + .../Colors/PackedPixel/BulkPixelOperations.cs | 56 ++++++++++ src/ImageSharp/Colors/PackedPixel/Byte4.cs | 3 + .../Colors/PackedPixel/HalfSingle.cs | 3 + .../Colors/PackedPixel/HalfVector2.cs | 3 + .../Colors/PackedPixel/HalfVector4.cs | 3 + src/ImageSharp/Colors/PackedPixel/IPixel.cs | 4 + .../Colors/PackedPixel/NormalizedByte2.cs | 3 + .../Colors/PackedPixel/NormalizedByte4.cs | 3 + .../Colors/PackedPixel/NormalizedShort2.cs | 3 + .../Colors/PackedPixel/NormalizedShort4.cs | 3 + src/ImageSharp/Colors/PackedPixel/Rg32.cs | 3 + .../Colors/PackedPixel/Rgba1010102.cs | 3 + src/ImageSharp/Colors/PackedPixel/Rgba64.cs | 3 + src/ImageSharp/Colors/PackedPixel/Short2.cs | 3 + src/ImageSharp/Colors/PackedPixel/Short4.cs | 3 + src/ImageSharp/Common/Memory/ArrayPointer.cs | 50 +++++++++ .../General/ArrayCopy.cs | 25 ++++- .../Colors/BulkPixelOperationsTests.cs | 104 ++++++++++++++++++ .../Common/ArrayPointerTests.cs | 94 +++++++++++++++- 25 files changed, 381 insertions(+), 9 deletions(-) create mode 100644 src/ImageSharp/Colors/PackedPixel/BulkPixelOperations.cs create mode 100644 src/ImageSharp/Common/Memory/ArrayPointer.cs create mode 100644 tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs diff --git a/src/ImageSharp/Colors/Color.cs b/src/ImageSharp/Colors/Color.cs index 469774b348..8a869935c1 100644 --- a/src/ImageSharp/Colors/Color.cs +++ b/src/ImageSharp/Colors/Color.cs @@ -112,6 +112,9 @@ namespace ImageSharp this.packedValue = packed; } + /// + public BulkPixelOperations BulkOperations => new BulkPixelOperations(); + ///

/// Gets or sets the red component. /// diff --git a/src/ImageSharp/Colors/PackedPixel/Alpha8.cs b/src/ImageSharp/Colors/PackedPixel/Alpha8.cs index 485725d71a..1181eb9e47 100644 --- a/src/ImageSharp/Colors/PackedPixel/Alpha8.cs +++ b/src/ImageSharp/Colors/PackedPixel/Alpha8.cs @@ -26,6 +26,9 @@ namespace ImageSharp /// public byte PackedValue { get; set; } + /// + public BulkPixelOperations BulkOperations => new BulkPixelOperations(); + /// /// Compares two objects for equality. /// diff --git a/src/ImageSharp/Colors/PackedPixel/Argb.cs b/src/ImageSharp/Colors/PackedPixel/Argb.cs index bef986fb9c..1b97d2337a 100644 --- a/src/ImageSharp/Colors/PackedPixel/Argb.cs +++ b/src/ImageSharp/Colors/PackedPixel/Argb.cs @@ -109,6 +109,9 @@ namespace ImageSharp /// public uint PackedValue { get; set; } + /// + public BulkPixelOperations BulkOperations => new BulkPixelOperations(); + /// /// Gets or sets the red component. /// diff --git a/src/ImageSharp/Colors/PackedPixel/Bgr565.cs b/src/ImageSharp/Colors/PackedPixel/Bgr565.cs index ebe8d25335..41b2bdc2e0 100644 --- a/src/ImageSharp/Colors/PackedPixel/Bgr565.cs +++ b/src/ImageSharp/Colors/PackedPixel/Bgr565.cs @@ -39,6 +39,9 @@ namespace ImageSharp /// public ushort PackedValue { get; set; } + /// + public BulkPixelOperations BulkOperations => new BulkPixelOperations(); + /// /// Compares two objects for equality. /// diff --git a/src/ImageSharp/Colors/PackedPixel/Bgra4444.cs b/src/ImageSharp/Colors/PackedPixel/Bgra4444.cs index ccd6ab1f3e..99659a36bc 100644 --- a/src/ImageSharp/Colors/PackedPixel/Bgra4444.cs +++ b/src/ImageSharp/Colors/PackedPixel/Bgra4444.cs @@ -38,6 +38,9 @@ namespace ImageSharp /// public ushort PackedValue { get; set; } + /// + public BulkPixelOperations BulkOperations => new BulkPixelOperations(); + /// /// Compares two objects for equality. /// diff --git a/src/ImageSharp/Colors/PackedPixel/Bgra5551.cs b/src/ImageSharp/Colors/PackedPixel/Bgra5551.cs index a7a2e899a5..86864fd485 100644 --- a/src/ImageSharp/Colors/PackedPixel/Bgra5551.cs +++ b/src/ImageSharp/Colors/PackedPixel/Bgra5551.cs @@ -40,6 +40,9 @@ namespace ImageSharp /// public ushort PackedValue { get; set; } + /// + public BulkPixelOperations BulkOperations => new BulkPixelOperations(); + /// /// Compares two objects for equality. /// diff --git a/src/ImageSharp/Colors/PackedPixel/BulkPixelOperations.cs b/src/ImageSharp/Colors/PackedPixel/BulkPixelOperations.cs new file mode 100644 index 0000000000..c914b3921c --- /dev/null +++ b/src/ImageSharp/Colors/PackedPixel/BulkPixelOperations.cs @@ -0,0 +1,56 @@ +namespace ImageSharp +{ + using System.Numerics; + + public unsafe class BulkPixelOperations + where TColor : struct, IPixel + { + public static BulkPixelOperations Instance { get; } = default(TColor).BulkOperations; + + internal virtual void PackFromVector4( + ArrayPointer sourceVectors, + ArrayPointer destColors, + int count) + { + } + + internal virtual void PackToVector4( + ArrayPointer sourceColors, + ArrayPointer destVectors, + int count) + { + } + + internal virtual void PackToXyzBytes(ArrayPointer sourceColors, ArrayPointer destBytes, int count) + { + } + + internal virtual void PackFromXyzBytes(ArrayPointer sourceBytes, ArrayPointer destColors, int count) + { + } + + internal virtual void PackToXyzwBytes(ArrayPointer sourceColors, ArrayPointer destBytes, int count) + { + } + + internal virtual void PackFromXyzwBytes(ArrayPointer sourceBytes, ArrayPointer destColors, int count) + { + } + + internal virtual void PackToZyxBytes(ArrayPointer sourceColors, ArrayPointer destBytes, int count) + { + } + + internal virtual void PackFromZyxBytes(ArrayPointer sourceBytes, ArrayPointer destColors, int count) + { + } + + internal virtual void PackToZyxwBytes(ArrayPointer sourceColors, ArrayPointer destBytes, int count) + { + } + + internal virtual void PackFromZyxwBytes(ArrayPointer sourceBytes, ArrayPointer destColors, int count) + { + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Colors/PackedPixel/Byte4.cs b/src/ImageSharp/Colors/PackedPixel/Byte4.cs index 9d5eb9be81..5712027d96 100644 --- a/src/ImageSharp/Colors/PackedPixel/Byte4.cs +++ b/src/ImageSharp/Colors/PackedPixel/Byte4.cs @@ -41,6 +41,9 @@ namespace ImageSharp /// public uint PackedValue { get; set; } + /// + public BulkPixelOperations BulkOperations => new BulkPixelOperations(); + /// /// Compares two objects for equality. /// diff --git a/src/ImageSharp/Colors/PackedPixel/HalfSingle.cs b/src/ImageSharp/Colors/PackedPixel/HalfSingle.cs index acfa639b70..0bc82c3a61 100644 --- a/src/ImageSharp/Colors/PackedPixel/HalfSingle.cs +++ b/src/ImageSharp/Colors/PackedPixel/HalfSingle.cs @@ -36,6 +36,9 @@ namespace ImageSharp /// public ushort PackedValue { get; set; } + /// + public BulkPixelOperations BulkOperations => new BulkPixelOperations(); + /// /// Compares two objects for equality. /// diff --git a/src/ImageSharp/Colors/PackedPixel/HalfVector2.cs b/src/ImageSharp/Colors/PackedPixel/HalfVector2.cs index e02c226dd7..778f86e0f6 100644 --- a/src/ImageSharp/Colors/PackedPixel/HalfVector2.cs +++ b/src/ImageSharp/Colors/PackedPixel/HalfVector2.cs @@ -45,6 +45,9 @@ namespace ImageSharp /// public uint PackedValue { get; set; } + + /// + public BulkPixelOperations BulkOperations => new BulkPixelOperations(); /// /// Compares two objects for equality. diff --git a/src/ImageSharp/Colors/PackedPixel/HalfVector4.cs b/src/ImageSharp/Colors/PackedPixel/HalfVector4.cs index 7c7f640e49..c24553d3f3 100644 --- a/src/ImageSharp/Colors/PackedPixel/HalfVector4.cs +++ b/src/ImageSharp/Colors/PackedPixel/HalfVector4.cs @@ -49,6 +49,9 @@ namespace ImageSharp /// public ulong PackedValue { get; set; } + /// + public BulkPixelOperations BulkOperations => new BulkPixelOperations(); + /// /// Compares two objects for equality. /// diff --git a/src/ImageSharp/Colors/PackedPixel/IPixel.cs b/src/ImageSharp/Colors/PackedPixel/IPixel.cs index 1c3e20a7e4..c17fe86ccf 100644 --- a/src/ImageSharp/Colors/PackedPixel/IPixel.cs +++ b/src/ImageSharp/Colors/PackedPixel/IPixel.cs @@ -15,6 +15,10 @@ namespace ImageSharp public interface IPixel : IPixel, IEquatable where TSelf : struct, IPixel { + /// + /// Gets the instance for this pixel type. + /// + BulkPixelOperations BulkOperations { get; } } /// diff --git a/src/ImageSharp/Colors/PackedPixel/NormalizedByte2.cs b/src/ImageSharp/Colors/PackedPixel/NormalizedByte2.cs index 116a681726..d425806c27 100644 --- a/src/ImageSharp/Colors/PackedPixel/NormalizedByte2.cs +++ b/src/ImageSharp/Colors/PackedPixel/NormalizedByte2.cs @@ -51,6 +51,9 @@ namespace ImageSharp /// public ushort PackedValue { get; set; } + /// + public BulkPixelOperations BulkOperations => new BulkPixelOperations(); + /// /// Compares two objects for equality. /// diff --git a/src/ImageSharp/Colors/PackedPixel/NormalizedByte4.cs b/src/ImageSharp/Colors/PackedPixel/NormalizedByte4.cs index 7aaa30c520..cba3f0e863 100644 --- a/src/ImageSharp/Colors/PackedPixel/NormalizedByte4.cs +++ b/src/ImageSharp/Colors/PackedPixel/NormalizedByte4.cs @@ -52,6 +52,9 @@ namespace ImageSharp /// public uint PackedValue { get; set; } + + /// + public BulkPixelOperations BulkOperations => new BulkPixelOperations(); /// /// Compares two objects for equality. diff --git a/src/ImageSharp/Colors/PackedPixel/NormalizedShort2.cs b/src/ImageSharp/Colors/PackedPixel/NormalizedShort2.cs index 2f4ef89d65..4bc7f94277 100644 --- a/src/ImageSharp/Colors/PackedPixel/NormalizedShort2.cs +++ b/src/ImageSharp/Colors/PackedPixel/NormalizedShort2.cs @@ -51,6 +51,9 @@ namespace ImageSharp /// public uint PackedValue { get; set; } + /// + public BulkPixelOperations BulkOperations => new BulkPixelOperations(); + /// /// Compares two objects for equality. /// diff --git a/src/ImageSharp/Colors/PackedPixel/NormalizedShort4.cs b/src/ImageSharp/Colors/PackedPixel/NormalizedShort4.cs index 60c5c9805a..c848b72369 100644 --- a/src/ImageSharp/Colors/PackedPixel/NormalizedShort4.cs +++ b/src/ImageSharp/Colors/PackedPixel/NormalizedShort4.cs @@ -53,6 +53,9 @@ namespace ImageSharp /// public ulong PackedValue { get; set; } + /// + public BulkPixelOperations BulkOperations => new BulkPixelOperations(); + /// /// Compares two objects for equality. /// diff --git a/src/ImageSharp/Colors/PackedPixel/Rg32.cs b/src/ImageSharp/Colors/PackedPixel/Rg32.cs index 9e5e5a711c..9eb2247c9b 100644 --- a/src/ImageSharp/Colors/PackedPixel/Rg32.cs +++ b/src/ImageSharp/Colors/PackedPixel/Rg32.cs @@ -36,6 +36,9 @@ namespace ImageSharp /// public uint PackedValue { get; set; } + /// + public BulkPixelOperations BulkOperations => new BulkPixelOperations(); + /// /// Compares two objects for equality. /// diff --git a/src/ImageSharp/Colors/PackedPixel/Rgba1010102.cs b/src/ImageSharp/Colors/PackedPixel/Rgba1010102.cs index 95a8d3b978..4f99feb6e5 100644 --- a/src/ImageSharp/Colors/PackedPixel/Rgba1010102.cs +++ b/src/ImageSharp/Colors/PackedPixel/Rgba1010102.cs @@ -39,6 +39,9 @@ namespace ImageSharp /// public uint PackedValue { get; set; } + /// + public BulkPixelOperations BulkOperations => new BulkPixelOperations(); + /// /// Compares two objects for equality. /// diff --git a/src/ImageSharp/Colors/PackedPixel/Rgba64.cs b/src/ImageSharp/Colors/PackedPixel/Rgba64.cs index 679a55c4e6..a23e57ec3d 100644 --- a/src/ImageSharp/Colors/PackedPixel/Rgba64.cs +++ b/src/ImageSharp/Colors/PackedPixel/Rgba64.cs @@ -38,6 +38,9 @@ namespace ImageSharp /// public ulong PackedValue { get; set; } + /// + public BulkPixelOperations BulkOperations => new BulkPixelOperations(); + /// /// Compares two objects for equality. /// diff --git a/src/ImageSharp/Colors/PackedPixel/Short2.cs b/src/ImageSharp/Colors/PackedPixel/Short2.cs index 1c1cb28c34..f26e825789 100644 --- a/src/ImageSharp/Colors/PackedPixel/Short2.cs +++ b/src/ImageSharp/Colors/PackedPixel/Short2.cs @@ -51,6 +51,9 @@ namespace ImageSharp /// public uint PackedValue { get; set; } + /// + public BulkPixelOperations BulkOperations => new BulkPixelOperations(); + /// /// Compares two objects for equality. /// diff --git a/src/ImageSharp/Colors/PackedPixel/Short4.cs b/src/ImageSharp/Colors/PackedPixel/Short4.cs index 2c11a1f8b1..6dc7545e1b 100644 --- a/src/ImageSharp/Colors/PackedPixel/Short4.cs +++ b/src/ImageSharp/Colors/PackedPixel/Short4.cs @@ -53,6 +53,9 @@ namespace ImageSharp /// public ulong PackedValue { get; set; } + /// + public BulkPixelOperations BulkOperations => new BulkPixelOperations(); + /// /// Compares two objects for equality. /// diff --git a/src/ImageSharp/Common/Memory/ArrayPointer.cs b/src/ImageSharp/Common/Memory/ArrayPointer.cs new file mode 100644 index 0000000000..c864d31fd1 --- /dev/null +++ b/src/ImageSharp/Common/Memory/ArrayPointer.cs @@ -0,0 +1,50 @@ +namespace ImageSharp +{ + using System.Runtime.CompilerServices; + + /// + /// Utility methods to + /// + internal static class ArrayPointer + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe void Copy(ArrayPointer source, ArrayPointer destination, int count) + where T : struct + { + Unsafe.CopyBlock((void*)source.PointerAtOffset, (void*)destination.PointerAtOffset, USizeOf(count)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe void Copy(ArrayPointer source, ArrayPointer destination, int countInSource) + where T : struct + { + Unsafe.CopyBlock((void*)source.PointerAtOffset, (void*)destination.PointerAtOffset, USizeOf(countInSource)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe void Copy(ArrayPointer source, ArrayPointer destination, int countInDest) + where T : struct + { + Unsafe.CopyBlock((void*)source.PointerAtOffset, (void*)destination.PointerAtOffset, USizeOf(countInDest)); + } + + /// + /// Gets the size of `count` elements in bytes. + /// + /// The count of the elements + /// The size in bytes as int + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int SizeOf(int count) + where T : struct => Unsafe.SizeOf() * count; + + /// + /// Gets the size of `count` elements in bytes as UInt32 + /// + /// The count of the elements + /// The size in bytes as UInt32 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint USizeOf(int count) + where T : struct + => (uint)SizeOf(count); + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/General/ArrayCopy.cs b/tests/ImageSharp.Benchmarks/General/ArrayCopy.cs index dddd83e424..72fd6dc248 100644 --- a/tests/ImageSharp.Benchmarks/General/ArrayCopy.cs +++ b/tests/ImageSharp.Benchmarks/General/ArrayCopy.cs @@ -2,22 +2,23 @@ // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // - namespace ImageSharp.Benchmarks.General { using System; using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; using BenchmarkDotNet.Attributes; + [Config(typeof(Config.Short))] public class ArrayCopy { - [Params(100, 1000, 10000)] + [Params(10, 100, 1000, 10000)] public int Count { get; set; } - private byte[] source; + byte[] source; - private byte[] destination; + byte[] destination; [Setup] public void SetUp() @@ -42,6 +43,12 @@ namespace ImageSharp.Benchmarks.General } } + [Benchmark(Description = "Copy using Buffer.BlockCopy()")] + public void CopyUsingBufferBlockCopy() + { + Buffer.BlockCopy(this.source, 0, this.destination, 0, this.Count); + } + [Benchmark(Description = "Copy using Buffer.MemoryCopy")] public unsafe void CopyUsingBufferMemoryCopy() { @@ -51,5 +58,15 @@ namespace ImageSharp.Benchmarks.General Buffer.MemoryCopy(pinnedSource, pinnedDestination, this.Count, this.Count); } } + + + [Benchmark(Description = "Copy using Marshal.Copy")] + public unsafe void CopyUsingMarshalCopy() + { + fixed (byte* pinnedDestination = this.destination) + { + Marshal.Copy(this.source, 0, (IntPtr)pinnedDestination, this.Count); + } + } } } diff --git a/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs b/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs new file mode 100644 index 0000000000..413bd94515 --- /dev/null +++ b/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs @@ -0,0 +1,104 @@ +namespace ImageSharp.Tests.Colors +{ + using System; + + using Xunit; + + public class BulkPixelOperationsTests + { + public class TypeParam + { + } + + + [Theory] + [InlineData(default(TypeParam))] + [InlineData(default(TypeParam))] + public virtual void PackFromVector4(TypeParam dummy) + where TColor : struct, IPixel + { + throw new NotImplementedException(); + } + + [Theory] + [InlineData(default(TypeParam))] + [InlineData(default(TypeParam))] + public virtual void PackToVector4(TypeParam dummy) + where TColor : struct, IPixel + { + throw new NotImplementedException(); + } + + [Theory] + [InlineData(default(TypeParam))] + [InlineData(default(TypeParam))] + public virtual void PackToXyzBytes(TypeParam dummy) + where TColor : struct, IPixel + { + throw new NotImplementedException(); + } + + [Theory] + [InlineData(default(TypeParam))] + [InlineData(default(TypeParam))] + public virtual void PackFromXyzBytes(TypeParam dummy) + where TColor : struct, IPixel + { + throw new NotImplementedException(); + } + + [Theory] + [InlineData(default(TypeParam))] + [InlineData(default(TypeParam))] + public virtual void PackToXyzwBytes(TypeParam dummy) + where TColor : struct, IPixel + { + throw new NotImplementedException(); + } + + [Theory] + [InlineData(default(TypeParam))] + [InlineData(default(TypeParam))] + public virtual void PackFromXyzwBytes(TypeParam dummy) + where TColor : struct, IPixel + { + throw new NotImplementedException(); + } + + [Theory] + [InlineData(default(TypeParam))] + [InlineData(default(TypeParam))] + public virtual void PackToZyxBytes(TypeParam dummy) + where TColor : struct, IPixel + { + throw new NotImplementedException(); + } + + [Theory] + [InlineData(default(TypeParam))] + [InlineData(default(TypeParam))] + public virtual void PackFromZyxBytes(TypeParam dummy) + where TColor : struct, IPixel + { + throw new NotImplementedException(); + } + + [Theory] + [InlineData(default(TypeParam))] + [InlineData(default(TypeParam))] + public virtual void PackToZyxwBytes(TypeParam dummy) + where TColor : struct, IPixel + { + throw new NotImplementedException(); + } + + [Theory] + [InlineData(default(TypeParam))] + [InlineData(default(TypeParam))] + public virtual void PackFromZyxwBytes(TypeParam dummy) + where TColor : struct, IPixel + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Common/ArrayPointerTests.cs b/tests/ImageSharp.Tests/Common/ArrayPointerTests.cs index 076e2512c8..916a109475 100644 --- a/tests/ImageSharp.Tests/Common/ArrayPointerTests.cs +++ b/tests/ImageSharp.Tests/Common/ArrayPointerTests.cs @@ -3,6 +3,7 @@ namespace ImageSharp.Tests.Common { using System; + using System.Runtime.CompilerServices; using Xunit; @@ -10,18 +11,16 @@ namespace ImageSharp.Tests.Common { public struct Foo { -#pragma warning disable CS0414 - private int a; + public int A; - private double b; -#pragma warning restore CS0414 + public double B; internal static Foo[] CreateArray(int size) { Foo[] result = new Foo[size]; for (int i = 0; i < size; i++) { - result[i] = new Foo() { a = i, b = i }; + result[i] = new Foo() { A = i, B = i }; } return result; } @@ -79,5 +78,90 @@ namespace ImageSharp.Tests.Common Assert.Equal((IntPtr)(p + totalOffset), ap.PointerAtOffset); } } + + public class Copy + { + [Theory] + [InlineData(4)] + [InlineData(1500)] + public void GenericToOwnType(int count) + { + Foo[] source = Foo.CreateArray(count + 2); + Foo[] dest = new Foo[count + 5]; + + fixed (Foo* pSource = source) + fixed (Foo* pDest = dest) + { + ArrayPointer apSource = new ArrayPointer(source, pSource); + ArrayPointer apDest = new ArrayPointer(dest, pDest); + + ArrayPointer.Copy(apSource, apDest, count); + } + + Assert.Equal(source[0], dest[0]); + Assert.Equal(source[count-1], dest[count-1]); + Assert.NotEqual(source[count], dest[count]); + } + + [Theory] + [InlineData(4)] + [InlineData(1500)] + public void GenericToBytes(int count) + { + int destCount = count * sizeof(Foo); + Foo[] source = Foo.CreateArray(count + 2); + byte[] dest = new byte[destCount + sizeof(Foo) + 1]; + + fixed (Foo* pSource = source) + fixed (byte* pDest = dest) + { + ArrayPointer apSource = new ArrayPointer(source, pSource); + ArrayPointer apDest = new ArrayPointer(dest, pDest); + + ArrayPointer.Copy(apSource, apDest, count); + } + + Assert.True(ElementsAreEqual(source, dest, 0)); + Assert.True(ElementsAreEqual(source, dest, count - 1)); + Assert.False(ElementsAreEqual(source, dest, count)); + } + + [Theory] + [InlineData(4)] + [InlineData(1500)] + public void BytesToGeneric(int count) + { + int destCount = count * sizeof(Foo); + byte[] source = new byte[destCount + sizeof(Foo) + 1]; + Foo[] dest = Foo.CreateArray(count + 2); + + fixed(byte* pSource = source) + fixed (Foo* pDest = dest) + { + ArrayPointer apSource = new ArrayPointer(source, pSource); + ArrayPointer apDest = new ArrayPointer(dest, pDest); + + ArrayPointer.Copy(apSource, apDest, count); + } + + Assert.True(ElementsAreEqual(dest, source, 0)); + Assert.True(ElementsAreEqual(dest, source, count - 1)); + Assert.False(ElementsAreEqual(dest, source, count)); + } + + private static bool ElementsAreEqual(Foo[] array, byte[] rawArray, int index) + { + fixed (Foo* pArray = array) + fixed (byte* pRaw = rawArray) + { + Foo* pCasted = (Foo*)pRaw; + + Foo val1 = pArray[index]; + Foo val2 = pCasted[index]; + + return val1.Equals(val2); + } + } + } } } \ No newline at end of file From 78f997cd06195dabb27f31f3575b801f209287e6 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Thu, 2 Mar 2017 03:10:00 +0100 Subject: [PATCH 126/142] PinnedBuffer, better tests --- src/ImageSharp/Common/Memory/ArrayPointer.cs | 7 ++ src/ImageSharp/Common/Memory/PinnedBuffer.cs | 109 +++++++++++++++++ src/ImageSharp/Image/PixelPool{TColor}.cs | 1 + .../Colors/BulkPixelOperationsTests.cs | 114 +++++++++++------- .../Common/PinnedBufferTests.cs | 69 +++++++++++ 5 files changed, 257 insertions(+), 43 deletions(-) create mode 100644 src/ImageSharp/Common/Memory/PinnedBuffer.cs create mode 100644 tests/ImageSharp.Tests/Common/PinnedBufferTests.cs diff --git a/src/ImageSharp/Common/Memory/ArrayPointer.cs b/src/ImageSharp/Common/Memory/ArrayPointer.cs index c864d31fd1..56cae35a45 100644 --- a/src/ImageSharp/Common/Memory/ArrayPointer.cs +++ b/src/ImageSharp/Common/Memory/ArrayPointer.cs @@ -7,6 +7,13 @@ namespace ImageSharp /// internal static class ArrayPointer { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ArrayPointer GetArrayPointer(this PinnedBuffer buffer) + where T : struct + { + return new ArrayPointer(buffer.Array, (void*)buffer.Pointer); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void Copy(ArrayPointer source, ArrayPointer destination, int count) where T : struct diff --git a/src/ImageSharp/Common/Memory/PinnedBuffer.cs b/src/ImageSharp/Common/Memory/PinnedBuffer.cs new file mode 100644 index 0000000000..c6e0c7c6f9 --- /dev/null +++ b/src/ImageSharp/Common/Memory/PinnedBuffer.cs @@ -0,0 +1,109 @@ +namespace ImageSharp +{ + using System; + using System.Buffers; + using System.Runtime.InteropServices; + + /// + /// Manages a pinned buffer of 'T' as a Disposable resource. + /// The backing array is either pooled or comes from the outside. + /// TODO: Should replace the pinning/dispose logic in several classes like or ! + /// + /// The value type. + internal class PinnedBuffer : IDisposable + where T : struct + { + private GCHandle handle; + + private bool isBufferRented; + + private bool isDisposed; + + /// + /// TODO: Consider reusing functionality of + /// + private static readonly ArrayPool ArrayPool = ArrayPool.Create(); + + /// + /// Initializes a new instance of the class. + /// + /// The desired count of elements. (Minimum size for ) + public PinnedBuffer(int count) + { + this.Count = count; + this.Array = ArrayPool.Rent(count); + this.isBufferRented = true; + this.Pin(); + } + + /// + /// Initializes a new instance of the class. + /// + /// The array to pin. + public PinnedBuffer(T[] array) + { + this.Count = array.Length; + this.Array = array; + this.Pin(); + } + + /// + /// The count of "relevant" elements. Usually be smaller than 'Array.Length' when is pooled. + /// + public int Count { get; private set; } + + /// + /// The (pinned) array of elements. + /// + public T[] Array { get; private set; } + + /// + /// Pointer to the pinned . + /// + public IntPtr Pointer { get; private set; } + + /// + /// Disposes the instance by unpinning the array, and returning the pooled buffer when necessary. + /// + public void Dispose() + { + if (this.isDisposed) + { + return; + } + this.isDisposed = true; + this.UnPin(); + + if (this.isBufferRented) + { + ArrayPool.Return(this.Array, true); + } + + this.Array = null; + this.Count = 0; + + GC.SuppressFinalize(this); + } + + private void Pin() + { + this.handle = GCHandle.Alloc(this.Array, GCHandleType.Pinned); + this.Pointer = this.handle.AddrOfPinnedObject(); + } + + private void UnPin() + { + if (this.Pointer == IntPtr.Zero || !this.handle.IsAllocated) + { + return; + } + this.handle.Free(); + this.Pointer = IntPtr.Zero; + } + + ~PinnedBuffer() + { + this.UnPin(); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Image/PixelPool{TColor}.cs b/src/ImageSharp/Image/PixelPool{TColor}.cs index 8193600daf..ea6dad6b12 100644 --- a/src/ImageSharp/Image/PixelPool{TColor}.cs +++ b/src/ImageSharp/Image/PixelPool{TColor}.cs @@ -8,6 +8,7 @@ namespace ImageSharp using System; using System.Buffers; + // TODO: Consider refactoring this into a more general ClearPool, so we can use it in PinnedBuffer! /// /// Provides a resource pool that enables reusing instances of type . /// diff --git a/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs b/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs index 413bd94515..f2081b9437 100644 --- a/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs +++ b/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs @@ -1,104 +1,132 @@ namespace ImageSharp.Tests.Colors { using System; + using System.Numerics; using Xunit; - - public class BulkPixelOperationsTests + + public abstract class BulkPixelOperationsTests + where TColor : struct, IPixel { - public class TypeParam + public class ColorPixels : BulkPixelOperationsTests { } + public class ArgbPixels : BulkPixelOperationsTests + { + } + public static TheoryData ArraySizesData = new TheoryData { 7, 16, 1111 }; + [Theory] - [InlineData(default(TypeParam))] - [InlineData(default(TypeParam))] - public virtual void PackFromVector4(TypeParam dummy) - where TColor : struct, IPixel + [MemberData(nameof(ArraySizesData))] + public virtual void PackFromVector4(int count) { throw new NotImplementedException(); } [Theory] - [InlineData(default(TypeParam))] - [InlineData(default(TypeParam))] - public virtual void PackToVector4(TypeParam dummy) - where TColor : struct, IPixel + [MemberData(nameof(ArraySizesData))] + public virtual void PackToVector4(int count) { throw new NotImplementedException(); } [Theory] - [InlineData(default(TypeParam))] - [InlineData(default(TypeParam))] - public virtual void PackToXyzBytes(TypeParam dummy) - where TColor : struct, IPixel + [MemberData(nameof(ArraySizesData))] + public virtual void PackToXyzBytes(int count) { throw new NotImplementedException(); } [Theory] - [InlineData(default(TypeParam))] - [InlineData(default(TypeParam))] - public virtual void PackFromXyzBytes(TypeParam dummy) - where TColor : struct, IPixel + [MemberData(nameof(ArraySizesData))] + public virtual void PackFromXyzBytes(int count) { throw new NotImplementedException(); } [Theory] - [InlineData(default(TypeParam))] - [InlineData(default(TypeParam))] - public virtual void PackToXyzwBytes(TypeParam dummy) - where TColor : struct, IPixel + [MemberData(nameof(ArraySizesData))] + public virtual void PackToXyzwBytes(int count) { throw new NotImplementedException(); } [Theory] - [InlineData(default(TypeParam))] - [InlineData(default(TypeParam))] - public virtual void PackFromXyzwBytes(TypeParam dummy) - where TColor : struct, IPixel + [MemberData(nameof(ArraySizesData))] + public virtual void PackFromXyzwBytes(int count) { throw new NotImplementedException(); } [Theory] - [InlineData(default(TypeParam))] - [InlineData(default(TypeParam))] - public virtual void PackToZyxBytes(TypeParam dummy) - where TColor : struct, IPixel + [MemberData(nameof(ArraySizesData))] + public virtual void PackToZyxBytes(int count) { throw new NotImplementedException(); } [Theory] - [InlineData(default(TypeParam))] - [InlineData(default(TypeParam))] - public virtual void PackFromZyxBytes(TypeParam dummy) - where TColor : struct, IPixel + [MemberData(nameof(ArraySizesData))] + public virtual void PackFromZyxBytes(int count) { throw new NotImplementedException(); } [Theory] - [InlineData(default(TypeParam))] - [InlineData(default(TypeParam))] - public virtual void PackToZyxwBytes(TypeParam dummy) - where TColor : struct, IPixel + [MemberData(nameof(ArraySizesData))] + public virtual void PackToZyxwBytes(int count) { throw new NotImplementedException(); } [Theory] - [InlineData(default(TypeParam))] - [InlineData(default(TypeParam))] - public virtual void PackFromZyxwBytes(TypeParam dummy) - where TColor : struct, IPixel + [MemberData(nameof(ArraySizesData))] + public virtual void PackFromZyxwBytes(int count) { throw new NotImplementedException(); } + + public class TestBuffers + { + internal static PinnedBuffer Vector4(int length) + { + Vector4[] result = new Vector4[length]; + Random rnd = new Random(42); // Deterministic random values + + for (int i = 0; i < result.Length; i++) + { + result[i] = GetVector(rnd); + } + + return new PinnedBuffer(result); + } + + internal static PinnedBuffer Pixel(int length) + { + TColor[] result = new TColor[length]; + + Random rnd = new Random(42); // Deterministic random values + + for (int i = 0; i < result.Length; i++) + { + Vector4 v = GetVector(rnd); + result[i].PackFromVector4(v); + } + + return new PinnedBuffer(result); + } + + private static Vector4 GetVector(Random rnd) + { + return new Vector4( + (float)rnd.NextDouble(), + (float)rnd.NextDouble(), + (float)rnd.NextDouble(), + (float)rnd.NextDouble() + ); + } + } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Common/PinnedBufferTests.cs b/tests/ImageSharp.Tests/Common/PinnedBufferTests.cs new file mode 100644 index 0000000000..e0783f7165 --- /dev/null +++ b/tests/ImageSharp.Tests/Common/PinnedBufferTests.cs @@ -0,0 +1,69 @@ +namespace ImageSharp.Tests.Common +{ + using System; + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + + using Xunit; + + public unsafe class PinnedBufferTests + { + public struct Foo + { + public int A; + + public double B; + } + + [Theory] + [InlineData(42)] + [InlineData(1111)] + public void ConstructWithOwnArray(int count) + { + using (PinnedBuffer buffer = new PinnedBuffer(count)) + { + Assert.NotNull(buffer.Array); + Assert.Equal(count, buffer.Count); + Assert.True(buffer.Array.Length >= count); + + VerifyPointer(buffer); + } + } + + [Theory] + [InlineData(42)] + [InlineData(1111)] + public void ConstructWithExistingArray(int count) + { + Foo[] array = new Foo[count]; + using (PinnedBuffer buffer = new PinnedBuffer(array)) + { + Assert.Equal(array, buffer.Array); + Assert.Equal(count, buffer.Count); + + VerifyPointer(buffer); + } + } + + [Fact] + public void GetArrayPointer() + { + Foo[] a = { new Foo() { A = 1, B = 2 }, new Foo() { A = 3, B = 4 } }; + + using (PinnedBuffer buffer = new PinnedBuffer(a)) + { + var arrayPtr = buffer.GetArrayPointer(); + + Assert.Equal(a, arrayPtr.Array); + Assert.Equal(0, arrayPtr.Offset); + Assert.Equal(buffer.Pointer, arrayPtr.PointerAtOffset); + } + } + + private static void VerifyPointer(PinnedBuffer buffer) + { + IntPtr ptr = (IntPtr)Unsafe.AsPointer(ref buffer.Array[0]); + Assert.Equal(ptr, buffer.Pointer); + } + } +} \ No newline at end of file From 61a2b2520d3c16cfe155a8a8e2211c0b9fa9bf2b Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Fri, 3 Mar 2017 01:58:56 +0100 Subject: [PATCH 127/142] better tests --- .../Colors/BulkPixelOperationsTests.cs | 197 ++++++++++++++---- 1 file changed, 156 insertions(+), 41 deletions(-) diff --git a/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs b/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs index f2081b9437..441b9dacae 100644 --- a/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs +++ b/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs @@ -4,9 +4,8 @@ using System.Numerics; using Xunit; - - public abstract class BulkPixelOperationsTests - where TColor : struct, IPixel + + public abstract class BulkPixelOperationsTests { public class ColorPixels : BulkPixelOperationsTests { @@ -15,118 +14,234 @@ public class ArgbPixels : BulkPixelOperationsTests { } + } + public abstract class BulkPixelOperationsTests + where TColor : struct, IPixel + { public static TheoryData ArraySizesData = new TheoryData { 7, 16, 1111 }; [Theory] [MemberData(nameof(ArraySizesData))] - public virtual void PackFromVector4(int count) + public void PackFromVector4(int count) { - throw new NotImplementedException(); + Vector4[] source = CreateVector4TestData(count); + TColor[] expected = new TColor[count]; + + for (int i = 0; i < count; i++) + { + expected[i].PackFromVector4(source[i]); + } + + TestOperation( + source, + expected, + (ops, s, d) => ops.PackFromVector4(s, d, count) + ); } [Theory] [MemberData(nameof(ArraySizesData))] - public virtual void PackToVector4(int count) + public void PackToVector4(int count) { - throw new NotImplementedException(); + TColor[] source = CreatePixelTestData(count); + Vector4[] expected = new Vector4[count]; + + for (int i = 0; i < count; i++) + { + expected[i] = source[i].ToVector4(); + } + + TestOperation( + source, + expected, + (ops, s, d) => ops.PackToVector4(s, d, count) + ); } + [Theory] [MemberData(nameof(ArraySizesData))] - public virtual void PackToXyzBytes(int count) + public void PackFromXyzBytes(int count) { - throw new NotImplementedException(); + byte[] source = CreateByteTestData(count * 3); + TColor[] expected = new TColor[count]; + + for (int i = 0; i < count; i++) + { + int i3 = i * 3; + + expected[i].PackFromBytes(source[i3 + 0], source[i3 + 1], source[i3 + 2], 255); + } + + TestOperation( + source, + expected, + (ops, s, d) => ops.PackFromXyzBytes(s, d, count) + ); } [Theory] [MemberData(nameof(ArraySizesData))] - public virtual void PackFromXyzBytes(int count) + public void PackToXyzBytes(int count) { - throw new NotImplementedException(); + TColor[] source = CreatePixelTestData(count); + byte[] expected = new byte[count * 3]; + + for (int i = 0; i < count; i++) + { + int i3 = i * 3; + source[i].ToXyzBytes(expected, i3); + } + + TestOperation( + source, + expected, + (ops, s, d) => ops.PackToXyzBytes(s, d, count) + ); } + [Theory] [MemberData(nameof(ArraySizesData))] - public virtual void PackToXyzwBytes(int count) + public void PackToXyzwBytes(int count) { throw new NotImplementedException(); } [Theory] [MemberData(nameof(ArraySizesData))] - public virtual void PackFromXyzwBytes(int count) + public void PackFromXyzwBytes(int count) { throw new NotImplementedException(); } [Theory] [MemberData(nameof(ArraySizesData))] - public virtual void PackToZyxBytes(int count) + public void PackToZyxBytes(int count) { throw new NotImplementedException(); } [Theory] [MemberData(nameof(ArraySizesData))] - public virtual void PackFromZyxBytes(int count) + public void PackFromZyxBytes(int count) { throw new NotImplementedException(); } [Theory] [MemberData(nameof(ArraySizesData))] - public virtual void PackToZyxwBytes(int count) + public void PackToZyxwBytes(int count) { throw new NotImplementedException(); } [Theory] [MemberData(nameof(ArraySizesData))] - public virtual void PackFromZyxwBytes(int count) + public void PackFromZyxwBytes(int count) { throw new NotImplementedException(); } - - public class TestBuffers + + private class TestBuffers : IDisposable + where TSource : struct + where TDest : struct { - internal static PinnedBuffer Vector4(int length) + public PinnedBuffer SourceBuffer { get; } + public PinnedBuffer ActualDestBuffer { get; } + public PinnedBuffer ExpectedDestBuffer { get; } + + public ArrayPointer Source => this.SourceBuffer.GetArrayPointer(); + public ArrayPointer ActualDest => this.ActualDestBuffer.GetArrayPointer(); + + public TestBuffers(TSource[] source, TDest[] expectedDest) + { + this.SourceBuffer = new PinnedBuffer(source); + this.ExpectedDestBuffer = new PinnedBuffer(expectedDest); + this.ActualDestBuffer = new PinnedBuffer(expectedDest.Length); + } + + public void Dispose() { - Vector4[] result = new Vector4[length]; - Random rnd = new Random(42); // Deterministic random values + this.SourceBuffer.Dispose(); + this.ActualDestBuffer.Dispose(); + this.ExpectedDestBuffer.Dispose(); + } - for (int i = 0; i < result.Length; i++) + public void Verify() + { + int count = this.ExpectedDestBuffer.Count; + TDest[] expected = this.ExpectedDestBuffer.Array; + TDest[] actual = this.ActualDestBuffer.Array; + for (int i = 0; i < count; i++) { - result[i] = GetVector(rnd); + Assert.Equal(expected[i], actual[i]); } + } + } - return new PinnedBuffer(result); + private static void TestOperation( + TSource[] source, + TDest[] expected, + Action, ArrayPointer, ArrayPointer> action) + where TSource : struct + where TDest : struct + { + using (var buffers = new TestBuffers(source, expected)) + { + action(BulkPixelOperations.Instance, buffers.Source, buffers.ActualDest); + buffers.Verify(); } + } - internal static PinnedBuffer Pixel(int length) + private static Vector4[] CreateVector4TestData(int length) + { + Vector4[] result = new Vector4[length]; + Random rnd = new Random(42); // Deterministic random values + + for (int i = 0; i < result.Length; i++) { - TColor[] result = new TColor[length]; + result[i] = GetVector(rnd); + } + return result; + } - Random rnd = new Random(42); // Deterministic random values + private static TColor[] CreatePixelTestData(int length) + { + TColor[] result = new TColor[length]; - for (int i = 0; i < result.Length; i++) - { - Vector4 v = GetVector(rnd); - result[i].PackFromVector4(v); - } + Random rnd = new Random(42); // Deterministic random values - return new PinnedBuffer(result); + for (int i = 0; i < result.Length; i++) + { + Vector4 v = GetVector(rnd); + result[i].PackFromVector4(v); } - private static Vector4 GetVector(Random rnd) + return result; + } + + private static byte[] CreateByteTestData(int length) + { + byte[] result = new byte[length]; + Random rnd = new Random(42); // Deterministic random values + + for (int i = 0; i < result.Length; i++) { - return new Vector4( - (float)rnd.NextDouble(), - (float)rnd.NextDouble(), - (float)rnd.NextDouble(), - (float)rnd.NextDouble() - ); + result[i] = (byte)rnd.Next(255); } + return result; + } + + private static Vector4 GetVector(Random rnd) + { + return new Vector4( + (float)rnd.NextDouble(), + (float)rnd.NextDouble(), + (float)rnd.NextDouble(), + (float)rnd.NextDouble() + ); } } } \ No newline at end of file From 93f169372b89f1b516db41680127aff1d41d0898 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Fri, 3 Mar 2017 02:46:26 +0100 Subject: [PATCH 128/142] started implementing operations --- .../Colors/PackedPixel/BulkPixelOperations.cs | 58 ++++++++++++++++++- .../Common/Memory/ArrayPointer{T}.cs | 21 +++++++ .../Colors/BulkPixelOperationsTests.cs | 6 +- .../Formats/Jpg/JpegProfilingBenchmarks.cs | 8 +-- 4 files changed, 84 insertions(+), 9 deletions(-) diff --git a/src/ImageSharp/Colors/PackedPixel/BulkPixelOperations.cs b/src/ImageSharp/Colors/PackedPixel/BulkPixelOperations.cs index c914b3921c..a0dceadedc 100644 --- a/src/ImageSharp/Colors/PackedPixel/BulkPixelOperations.cs +++ b/src/ImageSharp/Colors/PackedPixel/BulkPixelOperations.cs @@ -1,17 +1,34 @@ namespace ImageSharp { + using System; using System.Numerics; + using System.Runtime.CompilerServices; public unsafe class BulkPixelOperations where TColor : struct, IPixel { public static BulkPixelOperations Instance { get; } = default(TColor).BulkOperations; + + private static readonly int ColorSize = Unsafe.SizeOf(); internal virtual void PackFromVector4( ArrayPointer sourceVectors, ArrayPointer destColors, int count) { + Vector4* sp = (Vector4*)sourceVectors.PointerAtOffset; + byte* dp = (byte*)destColors; + + for (int i = 0; i < count; i++) + { + Vector4 v = Unsafe.Read(sp); + TColor c = default(TColor); + c.PackFromVector4(v); + Unsafe.Write(dp, c); + + sp++; + dp += ColorSize; + } } internal virtual void PackToVector4( @@ -19,16 +36,51 @@ ArrayPointer destVectors, int count) { + byte* sp = (byte*)sourceColors; + Vector4* dp = (Vector4*)destVectors.PointerAtOffset; + + for (int i = 0; i < count; i++) + { + TColor c = Unsafe.Read(sp); + *dp = c.ToVector4(); + sp += ColorSize; + dp++; + } } - internal virtual void PackToXyzBytes(ArrayPointer sourceColors, ArrayPointer destBytes, int count) + internal virtual void PackFromXyzBytes( + ArrayPointer sourceBytes, + ArrayPointer destColors, + int count) { + byte* sp = (byte*)sourceBytes; + byte* dp = (byte*)destColors.PointerAtOffset; + + for (int i = 0; i < count; i++) + { + TColor c = default(TColor); + c.PackFromBytes(sp[0], sp[1], sp[2], 255); + Unsafe.Write(dp, c); + sp += 3; + dp += ColorSize; + } } - internal virtual void PackFromXyzBytes(ArrayPointer sourceBytes, ArrayPointer destColors, int count) + internal virtual void PackToXyzBytes( + ArrayPointer sourceColors, + ArrayPointer destBytes, int count) { - } + byte* sp = (byte*)sourceColors; + + byte[] dest = destBytes.Array; + for (int i = destBytes.Offset; i < destBytes.Offset + count*3; i+=3) + { + TColor c = Unsafe.Read(sp); + c.ToXyzBytes(dest, i); + } + } + internal virtual void PackToXyzwBytes(ArrayPointer sourceColors, ArrayPointer destBytes, int count) { } diff --git a/src/ImageSharp/Common/Memory/ArrayPointer{T}.cs b/src/ImageSharp/Common/Memory/ArrayPointer{T}.cs index 1ea7706d44..8f99327ba2 100644 --- a/src/ImageSharp/Common/Memory/ArrayPointer{T}.cs +++ b/src/ImageSharp/Common/Memory/ArrayPointer{T}.cs @@ -73,6 +73,7 @@ namespace ImageSharp /// /// The offset in number of elements /// The offseted (sliced) ArrayPointer + [MethodImpl(MethodImplOptions.AggressiveInlining)] public ArrayPointer Slice(int offset) { ArrayPointer result = default(ArrayPointer); @@ -81,5 +82,25 @@ namespace ImageSharp result.PointerAtOffset = this.PointerAtOffset + (Unsafe.SizeOf() * offset); return result; } + + /// + /// Convertes instance to a raw 'void*' pointer + /// + /// The to convert + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator void*(ArrayPointer arrayPointer) + { + return (void*)arrayPointer.PointerAtOffset; + } + + /// + /// Convertes instance to a raw 'byte*' pointer + /// + /// The to convert + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator byte* (ArrayPointer arrayPointer) + { + return (byte*)arrayPointer.PointerAtOffset; + } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs b/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs index 441b9dacae..4725823107 100644 --- a/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs +++ b/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs @@ -7,12 +7,14 @@ public abstract class BulkPixelOperationsTests { - public class ColorPixels : BulkPixelOperationsTests + public class Color : BulkPixelOperationsTests { + public static TheoryData ArraySizesData = new TheoryData { 7, 16, 1111 }; } - public class ArgbPixels : BulkPixelOperationsTests + public class Argb : BulkPixelOperationsTests { + public static TheoryData ArraySizesData = new TheoryData { 7, 16, 1111 }; } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs index 50e678bf08..12deda577f 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs @@ -38,10 +38,10 @@ namespace ImageSharp.Tests { const int ExecutionCount = 30; - if (!Vector.IsHardwareAccelerated) - { - throw new Exception("Vector.IsHardwareAccelerated == false! ('prefer32 bit' enabled?)"); - } + //if (!Vector.IsHardwareAccelerated) + //{ + // throw new Exception("Vector.IsHardwareAccelerated == false! ('prefer32 bit' enabled?)"); + //} string path = TestFile.GetPath(fileName); byte[] bytes = File.ReadAllBytes(path); From 5ae01f1fa8f3b70a9c5f4fa123cfbd3c72fd7c9a Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Fri, 3 Mar 2017 03:05:06 +0100 Subject: [PATCH 129/142] fixed PackToXyzBytes --- src/ImageSharp/Colors/PackedPixel/BulkPixelOperations.cs | 1 + tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj | 3 +++ tests/ImageSharp.Sandbox46/Program.cs | 3 --- .../Formats/Jpg/JpegProfilingBenchmarks.cs | 8 ++++---- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/ImageSharp/Colors/PackedPixel/BulkPixelOperations.cs b/src/ImageSharp/Colors/PackedPixel/BulkPixelOperations.cs index a0dceadedc..c1f6001af9 100644 --- a/src/ImageSharp/Colors/PackedPixel/BulkPixelOperations.cs +++ b/src/ImageSharp/Colors/PackedPixel/BulkPixelOperations.cs @@ -78,6 +78,7 @@ { TColor c = Unsafe.Read(sp); c.ToXyzBytes(dest, i); + sp += ColorSize; } } diff --git a/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj b/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj index d1b059f44b..ad436793d4 100644 --- a/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj +++ b/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj @@ -209,6 +209,9 @@ Benchmarks\PixelAccessorVirtualCopy.cs + + Tests\Colors\BulkPixelOperationsTests.cs + Tests\Drawing\PolygonTests.cs diff --git a/tests/ImageSharp.Sandbox46/Program.cs b/tests/ImageSharp.Sandbox46/Program.cs index f289ac2db2..3afd180941 100644 --- a/tests/ImageSharp.Sandbox46/Program.cs +++ b/tests/ImageSharp.Sandbox46/Program.cs @@ -49,9 +49,6 @@ namespace ImageSharp.Sandbox46 benchmark.Setup(); benchmark.CopyRawUnsafeInlined(); - benchmark.CopyArrayPointerUnsafe(); - benchmark.CopyArrayPointerVirtualUnsafe(); - benchmark.CopyArrayPointerVirtualMarshal(); benchmark.Cleanup(); } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs index 12deda577f..50e678bf08 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs @@ -38,10 +38,10 @@ namespace ImageSharp.Tests { const int ExecutionCount = 30; - //if (!Vector.IsHardwareAccelerated) - //{ - // throw new Exception("Vector.IsHardwareAccelerated == false! ('prefer32 bit' enabled?)"); - //} + if (!Vector.IsHardwareAccelerated) + { + throw new Exception("Vector.IsHardwareAccelerated == false! ('prefer32 bit' enabled?)"); + } string path = TestFile.GetPath(fileName); byte[] bytes = File.ReadAllBytes(path); From 210cecb51d40d15f4d75818375c1f4ed87fea10a Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Sat, 4 Mar 2017 18:46:52 +0000 Subject: [PATCH 130/142] update SixLabors.Shapes --- src/ImageSharp.Drawing.Paths/project.json | 2 +- .../Drawing/SolidPolygonTests.cs | 27 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/ImageSharp.Drawing.Paths/project.json b/src/ImageSharp.Drawing.Paths/project.json index b761233c37..cca2f9bf9c 100644 --- a/src/ImageSharp.Drawing.Paths/project.json +++ b/src/ImageSharp.Drawing.Paths/project.json @@ -44,7 +44,7 @@ "ImageSharp.Drawing": { "target": "project" }, - "SixLabors.Shapes": "0.1.0-alpha0006", + "SixLabors.Shapes": "0.1.0-alpha0007", "StyleCop.Analyzers": { "version": "1.0.0", "type": "build" diff --git a/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs b/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs index 1d3ead81f2..9c6c6d2342 100644 --- a/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs +++ b/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs @@ -242,5 +242,32 @@ namespace ImageSharp.Tests.Drawing } } } + + [Fact] + public void ImageShouldBeOverlayedBySquareWithCornerClipped() + { + string path = this.CreateOutputDirectory("Drawing", "FilledPolygons"); + + var config = Configuration.CreateDefaultInstance(); + config.ParallelOptions.MaxDegreeOfParallelism = 1; + using (Image image = new Image(200, 200, config)) + { + using (FileStream output = File.OpenWrite($"{path}/clipped-corner.png")) + { + image + .Fill(Color.Blue) + .FillPolygon(Color.HotPink, new[] + { + new Vector2( 8, 8 ), + new Vector2( 64, 8 ), + new Vector2( 64, 64 ), + new Vector2( 120, 64 ), + new Vector2( 120, 120 ), + new Vector2( 8, 120 ) + } ) + .Save(output); + } + } + } } } From 1d6dae9462b4f75a8f36b9beeae7a6542944d299 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 5 Mar 2017 00:47:40 +0100 Subject: [PATCH 131/142] default BulkPixelOperations passing --- .../Colors/PackedPixel/BulkPixelOperations.cs | 75 ++++++++++-- .../Colors/BulkPixelOperationsTests.cs | 109 +++++++++++++++--- 2 files changed, 162 insertions(+), 22 deletions(-) diff --git a/src/ImageSharp/Colors/PackedPixel/BulkPixelOperations.cs b/src/ImageSharp/Colors/PackedPixel/BulkPixelOperations.cs index c1f6001af9..ffa28fc132 100644 --- a/src/ImageSharp/Colors/PackedPixel/BulkPixelOperations.cs +++ b/src/ImageSharp/Colors/PackedPixel/BulkPixelOperations.cs @@ -71,7 +71,6 @@ ArrayPointer destBytes, int count) { byte* sp = (byte*)sourceColors; - byte[] dest = destBytes.Array; for (int i = destBytes.Offset; i < destBytes.Offset + count*3; i+=3) @@ -81,29 +80,89 @@ sp += ColorSize; } } - - internal virtual void PackToXyzwBytes(ArrayPointer sourceColors, ArrayPointer destBytes, int count) - { - } internal virtual void PackFromXyzwBytes(ArrayPointer sourceBytes, ArrayPointer destColors, int count) { + byte* sp = (byte*)sourceBytes; + byte* dp = (byte*)destColors.PointerAtOffset; + + for (int i = 0; i < count; i++) + { + TColor c = default(TColor); + c.PackFromBytes(sp[0], sp[1], sp[2], sp[3]); + Unsafe.Write(dp, c); + sp += 4; + dp += ColorSize; + } } - - internal virtual void PackToZyxBytes(ArrayPointer sourceColors, ArrayPointer destBytes, int count) + + internal virtual void PackToXyzwBytes(ArrayPointer sourceColors, ArrayPointer destBytes, int count) { + byte* sp = (byte*)sourceColors; + byte[] dest = destBytes.Array; + + for (int i = destBytes.Offset; i < destBytes.Offset + count * 4; i += 4) + { + TColor c = Unsafe.Read(sp); + c.ToXyzwBytes(dest, i); + sp += ColorSize; + } } internal virtual void PackFromZyxBytes(ArrayPointer sourceBytes, ArrayPointer destColors, int count) { + byte* sp = (byte*)sourceBytes; + byte* dp = (byte*)destColors.PointerAtOffset; + + for (int i = 0; i < count; i++) + { + TColor c = default(TColor); + c.PackFromBytes(sp[2], sp[1], sp[0], 255); + Unsafe.Write(dp, c); + sp += 3; + dp += ColorSize; + } } - internal virtual void PackToZyxwBytes(ArrayPointer sourceColors, ArrayPointer destBytes, int count) + internal virtual void PackToZyxBytes(ArrayPointer sourceColors, ArrayPointer destBytes, int count) { + byte* sp = (byte*)sourceColors; + byte[] dest = destBytes.Array; + + for (int i = destBytes.Offset; i < destBytes.Offset + count * 3; i += 3) + { + TColor c = Unsafe.Read(sp); + c.ToZyxBytes(dest, i); + sp += ColorSize; + } } internal virtual void PackFromZyxwBytes(ArrayPointer sourceBytes, ArrayPointer destColors, int count) { + byte* sp = (byte*)sourceBytes; + byte* dp = (byte*)destColors.PointerAtOffset; + + for (int i = 0; i < count; i++) + { + TColor c = default(TColor); + c.PackFromBytes(sp[2], sp[1], sp[0], sp[3]); + Unsafe.Write(dp, c); + sp += 4; + dp += ColorSize; + } + } + + internal virtual void PackToZyxwBytes(ArrayPointer sourceColors, ArrayPointer destBytes, int count) + { + byte* sp = (byte*)sourceColors; + byte[] dest = destBytes.Array; + + for (int i = destBytes.Offset; i < destBytes.Offset + count * 4; i += 4) + { + TColor c = Unsafe.Read(sp); + c.ToZyxwBytes(dest, i); + sp += ColorSize; + } } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs b/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs index 4725823107..3682aa78a3 100644 --- a/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs +++ b/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs @@ -21,7 +21,7 @@ public abstract class BulkPixelOperationsTests where TColor : struct, IPixel { - public static TheoryData ArraySizesData = new TheoryData { 7, 16, 1111 }; + protected static TheoryData ArraySizesData = new TheoryData { 7, 16, 1111 }; [Theory] [MemberData(nameof(ArraySizesData))] @@ -103,48 +103,129 @@ ); } - [Theory] [MemberData(nameof(ArraySizesData))] - public void PackToXyzwBytes(int count) + public void PackFromXyzwBytes(int count) { - throw new NotImplementedException(); + byte[] source = CreateByteTestData(count * 4); + TColor[] expected = new TColor[count]; + + for (int i = 0; i < count; i++) + { + int i4 = i * 4; + + expected[i].PackFromBytes(source[i4 + 0], source[i4 + 1], source[i4 + 2], source[i4 + 3]); + } + + TestOperation( + source, + expected, + (ops, s, d) => ops.PackFromXyzwBytes(s, d, count) + ); } [Theory] [MemberData(nameof(ArraySizesData))] - public void PackFromXyzwBytes(int count) + public void PackToXyzwBytes(int count) { - throw new NotImplementedException(); + TColor[] source = CreatePixelTestData(count); + byte[] expected = new byte[count * 4]; + + for (int i = 0; i < count; i++) + { + int i4 = i * 4; + source[i].ToXyzwBytes(expected, i4); + } + + TestOperation( + source, + expected, + (ops, s, d) => ops.PackToXyzwBytes(s, d, count) + ); } [Theory] [MemberData(nameof(ArraySizesData))] - public void PackToZyxBytes(int count) + public void PackFromZyxBytes(int count) { - throw new NotImplementedException(); + byte[] source = CreateByteTestData(count * 3); + TColor[] expected = new TColor[count]; + + for (int i = 0; i < count; i++) + { + int i3 = i * 3; + + expected[i].PackFromBytes(source[i3 + 2], source[i3 + 1], source[i3 + 0], 255); + } + + TestOperation( + source, + expected, + (ops, s, d) => ops.PackFromZyxBytes(s, d, count) + ); } [Theory] [MemberData(nameof(ArraySizesData))] - public void PackFromZyxBytes(int count) + public void PackToZyxBytes(int count) { - throw new NotImplementedException(); + TColor[] source = CreatePixelTestData(count); + byte[] expected = new byte[count * 3]; + + for (int i = 0; i < count; i++) + { + int i3 = i * 3; + source[i].ToZyxBytes(expected, i3); + } + + TestOperation( + source, + expected, + (ops, s, d) => ops.PackToZyxBytes(s, d, count) + ); } [Theory] [MemberData(nameof(ArraySizesData))] - public void PackToZyxwBytes(int count) + public void PackFromZyxwBytes(int count) { - throw new NotImplementedException(); + byte[] source = CreateByteTestData(count * 4); + TColor[] expected = new TColor[count]; + + for (int i = 0; i < count; i++) + { + int i4 = i * 4; + + expected[i].PackFromBytes(source[i4 + 2], source[i4 + 1], source[i4 + 0], source[i4 + 3]); + } + + TestOperation( + source, + expected, + (ops, s, d) => ops.PackFromZyxwBytes(s, d, count) + ); } [Theory] [MemberData(nameof(ArraySizesData))] - public void PackFromZyxwBytes(int count) + public void PackToZyxwBytes(int count) { - throw new NotImplementedException(); + TColor[] source = CreatePixelTestData(count); + byte[] expected = new byte[count * 4]; + + for (int i = 0; i < count; i++) + { + int i4 = i * 4; + source[i].ToZyxwBytes(expected, i4); + } + + TestOperation( + source, + expected, + (ops, s, d) => ops.PackToZyxwBytes(s, d, count) + ); } + private class TestBuffers : IDisposable where TSource : struct From 1b3a94f3706f89f2319916aef7aebc082aa6f55a Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Sat, 4 Mar 2017 18:34:43 +0000 Subject: [PATCH 132/142] render using sixlabors.fonts --- ImageSharp.sln | 7 + NuGet.config | 1 + src/ImageSharp.Drawing.Text/DrawText.cs | 204 ++++++++++++++++++ src/ImageSharp.Drawing.Text/GlyphBuilder.cs | 126 +++++++++++ .../ImageSharp.Drawing.Text.xproj | 25 +++ .../Properties/AssemblyInfo.cs | 6 + .../TextGraphicsOptions.cs | 68 ++++++ src/ImageSharp.Drawing.Text/project.json | 95 ++++++++ .../ImageSharp.Tests/Drawing/Text/DrawText.cs | 194 +++++++++++++++++ .../Drawing/Text/GlyphBuilder.cs | 68 ++++++ .../Drawing/Text/OutputText.cs | 41 ++++ tests/ImageSharp.Tests/TestFont.cs | 90 ++++++++ .../TestFonts/SixLaborsSampleAB.woff | Bin 0 -> 1352 bytes tests/ImageSharp.Tests/project.json | 3 + 14 files changed, 928 insertions(+) create mode 100644 src/ImageSharp.Drawing.Text/DrawText.cs create mode 100644 src/ImageSharp.Drawing.Text/GlyphBuilder.cs create mode 100644 src/ImageSharp.Drawing.Text/ImageSharp.Drawing.Text.xproj create mode 100644 src/ImageSharp.Drawing.Text/Properties/AssemblyInfo.cs create mode 100644 src/ImageSharp.Drawing.Text/TextGraphicsOptions.cs create mode 100644 src/ImageSharp.Drawing.Text/project.json create mode 100644 tests/ImageSharp.Tests/Drawing/Text/DrawText.cs create mode 100644 tests/ImageSharp.Tests/Drawing/Text/GlyphBuilder.cs create mode 100644 tests/ImageSharp.Tests/Drawing/Text/OutputText.cs create mode 100644 tests/ImageSharp.Tests/TestFont.cs create mode 100644 tests/ImageSharp.Tests/TestFonts/SixLaborsSampleAB.woff diff --git a/ImageSharp.sln b/ImageSharp.sln index 503a5b8601..1bcea0b929 100644 --- a/ImageSharp.sln +++ b/ImageSharp.sln @@ -68,6 +68,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageSharp.Sandbox46", "tes EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ImageSharp.Drawing.Paths", "src\ImageSharp.Drawing.Paths\ImageSharp.Drawing.Paths.xproj", "{E5BD4F96-28A8-410C-8B63-1C5731948549}" EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ImageSharp.Drawing.Text", "src\ImageSharp.Drawing.Text\ImageSharp.Drawing.Text.xproj", "{329D7698-65BC-48AD-A16F-428682964493}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -122,6 +124,10 @@ Global {E5BD4F96-28A8-410C-8B63-1C5731948549}.Debug|Any CPU.Build.0 = Debug|Any CPU {E5BD4F96-28A8-410C-8B63-1C5731948549}.Release|Any CPU.ActiveCfg = Release|Any CPU {E5BD4F96-28A8-410C-8B63-1C5731948549}.Release|Any CPU.Build.0 = Release|Any CPU + {329D7698-65BC-48AD-A16F-428682964493}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {329D7698-65BC-48AD-A16F-428682964493}.Debug|Any CPU.Build.0 = Debug|Any CPU + {329D7698-65BC-48AD-A16F-428682964493}.Release|Any CPU.ActiveCfg = Release|Any CPU + {329D7698-65BC-48AD-A16F-428682964493}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -140,5 +146,6 @@ Global {9E574A07-F879-4811-9C41-5CBDC6BAFDB7} = {815C0625-CD3D-440F-9F80-2D83856AB7AE} {96188137-5FA6-4924-AB6E-4EFF79C6E0BB} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC} {E5BD4F96-28A8-410C-8B63-1C5731948549} = {815C0625-CD3D-440F-9F80-2D83856AB7AE} + {329D7698-65BC-48AD-A16F-428682964493} = {815C0625-CD3D-440F-9F80-2D83856AB7AE} EndGlobalSection EndGlobal diff --git a/NuGet.config b/NuGet.config index b2c967cc97..322105d4d4 100644 --- a/NuGet.config +++ b/NuGet.config @@ -1,6 +1,7 @@  + diff --git a/src/ImageSharp.Drawing.Text/DrawText.cs b/src/ImageSharp.Drawing.Text/DrawText.cs new file mode 100644 index 0000000000..486aa6e40f --- /dev/null +++ b/src/ImageSharp.Drawing.Text/DrawText.cs @@ -0,0 +1,204 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System.Numerics; + + using Drawing; + using Drawing.Brushes; + using Drawing.Pens; + + using SixLabors.Fonts; + using System.Linq; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Draws the text onto the the image filled via the brush. + /// + /// The type of the color. + /// The image this method extends. + /// The text. + /// The font. + /// The color. + /// The location. + /// + /// The . + /// + public static Image DrawText(this Image source, string text, Font font, TColor color, Vector2 location) + where TColor : struct, IPixel + { + return source.DrawText(text, font, color, location, TextGraphicsOptions.Default); + } + + /// + /// Draws the text onto the the image filled via the brush. + /// + /// The type of the color. + /// The image this method extends. + /// The text. + /// The font. + /// The color. + /// The location. + /// The options. + /// + /// The . + /// + public static Image DrawText(this Image source, string text, Font font, TColor color, Vector2 location, TextGraphicsOptions options) + where TColor : struct, IPixel + { + return source.DrawText(text, font, Brushes.Solid(color), null, location, options); + } + + /// + /// Draws the text onto the the image filled via the brush. + /// + /// The type of the color. + /// The image this method extends. + /// The text. + /// The font. + /// The brush. + /// The location. + /// + /// The . + /// + public static Image DrawText(this Image source, string text, Font font, IBrush brush, Vector2 location) + where TColor : struct, IPixel + { + return source.DrawText(text, font, brush, location, TextGraphicsOptions.Default); + } + + /// + /// Draws the text onto the the image filled via the brush. + /// + /// The type of the color. + /// The image this method extends. + /// The text. + /// The font. + /// The brush. + /// The location. + /// The options. + /// + /// The . + /// + public static Image DrawText(this Image source, string text, Font font, IBrush brush, Vector2 location, TextGraphicsOptions options) + where TColor : struct, IPixel + { + return source.DrawText(text, font, brush, null, location, options); + } + + /// + /// Draws the text onto the the image outlined via the pen. + /// + /// The type of the color. + /// The image this method extends. + /// The text. + /// The font. + /// The pen. + /// The location. + /// + /// The . + /// + public static Image DrawText(this Image source, string text, Font font, IPen pen, Vector2 location) + where TColor : struct, IPixel + { + return source.DrawText(text, font, pen, location, TextGraphicsOptions.Default); + } + + /// + /// Draws the text onto the the image outlined via the pen. + /// + /// The type of the color. + /// The image this method extends. + /// The text. + /// The font. + /// The pen. + /// The location. + /// The options. + /// + /// The . + /// + public static Image DrawText(this Image source, string text, Font font, IPen pen, Vector2 location, TextGraphicsOptions options) + where TColor : struct, IPixel + { + return source.DrawText(text, font, null, pen, location, options); + } + + /// + /// Draws the text onto the the image filled via the brush then outlined via the pen. + /// + /// The type of the color. + /// The image this method extends. + /// The text. + /// The font. + /// The brush. + /// The pen. + /// The location. + /// + /// The . + /// + public static Image DrawText(this Image source, string text, Font font, IBrush brush, IPen pen, Vector2 location) + where TColor : struct, IPixel + { + return source.DrawText(text, font, brush, pen, location, TextGraphicsOptions.Default); + } + + /// + /// Draws the text onto the the image filled via the brush then outlined via the pen. + /// + /// The type of the color. + /// The image this method extends. + /// The text. + /// The font. + /// The brush. + /// The pen. + /// The location. + /// The options. + /// + /// The . + /// + public static Image DrawText(this Image source, string text, Font font, IBrush brush, IPen pen, Vector2 location, TextGraphicsOptions options) + where TColor : struct, IPixel + { + GlyphBuilder glyphBuilder = new GlyphBuilder(location); + + TextRenderer renderer = new TextRenderer(glyphBuilder); + + Vector2 dpi = new Vector2((float)source.MetaData.HorizontalResolution, (float)source.MetaData.VerticalResolution); + FontSpan style = new FontSpan(font) + { + ApplyKerning = options.ApplyKerning, + TabWidth = options.TabWidth + }; + + renderer.RenderText(text, style, dpi); + + System.Collections.Generic.IEnumerable shapesToDraw = glyphBuilder.Paths; + + GraphicsOptions pathOptions = (GraphicsOptions)options; + if (brush != null) + { + foreach (SixLabors.Shapes.IPath s in shapesToDraw) + { + source.Fill(brush, s, pathOptions); + } + } + + if (pen != null) + { + foreach (SixLabors.Shapes.IPath s in shapesToDraw) + { + source.Draw(pen, s, pathOptions); + } + } + + return source; + } + } +} diff --git a/src/ImageSharp.Drawing.Text/GlyphBuilder.cs b/src/ImageSharp.Drawing.Text/GlyphBuilder.cs new file mode 100644 index 0000000000..ac5d01de72 --- /dev/null +++ b/src/ImageSharp.Drawing.Text/GlyphBuilder.cs @@ -0,0 +1,126 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Drawing +{ + using System.Collections.Generic; + using System.Numerics; + + using SixLabors.Fonts; + using SixLabors.Shapes; + + /// + /// rendering surface that Fonts can use to generate Shapes. + /// + internal class GlyphBuilder : IGlyphRenderer + { + private readonly PathBuilder builder = new PathBuilder(); + private readonly List paths = new List(); + private Vector2 currentPoint = default(Vector2); + + /// + /// Initializes a new instance of the class. + /// + public GlyphBuilder() + : this(Vector2.Zero) + { + // glyphs are renderd realative to bottom left so invert the Y axis to allow it to render on top left origin surface + this.builder = new PathBuilder(); + } + + /// + /// Initializes a new instance of the class. + /// + /// The origin. + public GlyphBuilder(Vector2 origin) + { + this.builder = new PathBuilder(); + this.builder.SetOrigin(origin); + } + + /// + /// Gets the paths that have been rendered by this. + /// + public IEnumerable Paths => this.paths; + + /// + /// Begins the glyph. + /// + void IGlyphRenderer.BeginGlyph() + { + this.builder.Clear(); + } + + /// + /// Begins the figure. + /// + void IGlyphRenderer.BeginFigure() + { + this.builder.StartFigure(); + } + + /// + /// Draws a cubic bezier from the current point to the + /// + /// The second control point. + /// The third control point. + /// The point. + void IGlyphRenderer.CubicBezierTo(Vector2 secondControlPoint, Vector2 thirdControlPoint, Vector2 point) + { + this.builder.AddBezier(this.currentPoint, secondControlPoint, thirdControlPoint, point); + this.currentPoint = point; + } + + /// + /// Ends the glyph. + /// + void IGlyphRenderer.EndGlyph() + { + this.paths.Add(this.builder.Build()); + } + + /// + /// Ends the figure. + /// + void IGlyphRenderer.EndFigure() + { + this.builder.CloseFigure(); + } + + /// + /// Draws a line from the current point to the . + /// + /// The point. + void IGlyphRenderer.LineTo(Vector2 point) + { + this.builder.AddLine(this.currentPoint, point); + this.currentPoint = point; + } + + /// + /// Moves to current point to the supplied vector. + /// + /// The point. + void IGlyphRenderer.MoveTo(Vector2 point) + { + this.builder.StartFigure(); + this.currentPoint = point; + } + + /// + /// Draws a quadratics bezier from the current point to the + /// + /// The second control point. + /// The point. + void IGlyphRenderer.QuadraticBezierTo(Vector2 secondControlPoint, Vector2 point) + { + Vector2 c1 = (((secondControlPoint - this.currentPoint) * 2) / 3) + this.currentPoint; + Vector2 c2 = (((secondControlPoint - point) * 2) / 3) + point; + + this.builder.AddBezier(this.currentPoint, c1, c2, point); + this.currentPoint = point; + } + } +} diff --git a/src/ImageSharp.Drawing.Text/ImageSharp.Drawing.Text.xproj b/src/ImageSharp.Drawing.Text/ImageSharp.Drawing.Text.xproj new file mode 100644 index 0000000000..4dfb394cfb --- /dev/null +++ b/src/ImageSharp.Drawing.Text/ImageSharp.Drawing.Text.xproj @@ -0,0 +1,25 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 329d7698-65bc-48ad-a16f-428682964493 + ImageSharp.Drawing + .\obj + .\bin\ + v4.5.1 + + + 2.0 + + + True + + + + + + \ No newline at end of file diff --git a/src/ImageSharp.Drawing.Text/Properties/AssemblyInfo.cs b/src/ImageSharp.Drawing.Text/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..fba25a9dba --- /dev/null +++ b/src/ImageSharp.Drawing.Text/Properties/AssemblyInfo.cs @@ -0,0 +1,6 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +// Common values read from `AssemblyInfo.Common.cs` diff --git a/src/ImageSharp.Drawing.Text/TextGraphicsOptions.cs b/src/ImageSharp.Drawing.Text/TextGraphicsOptions.cs new file mode 100644 index 0000000000..e707ef5e50 --- /dev/null +++ b/src/ImageSharp.Drawing.Text/TextGraphicsOptions.cs @@ -0,0 +1,68 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Drawing +{ + /// + /// Options for influencing the drawing functions. + /// + public struct TextGraphicsOptions + { + /// + /// Represents the default . + /// + public static readonly TextGraphicsOptions Default = new TextGraphicsOptions(true); + + /// + /// Whether antialiasing should be applied. + /// + public bool Antialias; + + /// + /// Whether the text should be drawing with kerning enabled. + /// + public bool ApplyKerning; + + /// + /// The number of space widths a tab should lock to. + /// + public float TabWidth; + + /// + /// Initializes a new instance of the struct. + /// + /// If set to true [enable antialiasing]. + public TextGraphicsOptions(bool enableAntialiasing) + { + this.Antialias = enableAntialiasing; + this.ApplyKerning = true; + this.TabWidth = 4; + } + + /// + /// Performs an implicit conversion from to . + /// + /// The options. + /// + /// The result of the conversion. + /// + public static implicit operator TextGraphicsOptions(GraphicsOptions options) + { + return new TextGraphicsOptions(options.Antialias); + } + + /// + /// Performs an explicit conversion from to . + /// + /// The options. + /// + /// The result of the conversion. + /// + public static explicit operator GraphicsOptions(TextGraphicsOptions options) + { + return new GraphicsOptions(options.Antialias); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp.Drawing.Text/project.json b/src/ImageSharp.Drawing.Text/project.json new file mode 100644 index 0000000000..f446f91199 --- /dev/null +++ b/src/ImageSharp.Drawing.Text/project.json @@ -0,0 +1,95 @@ +{ + "version": "1.0.0-alpha2-*", + "title": "ImageSharp.Drawing.Text", + "description": "A cross-platform library for the processing of image files; written in C#", + "authors": [ + "James Jackson-South and contributors" + ], + "packOptions": { + "owners": [ + "James Jackson-South and contributors" + ], + "projectUrl": "https://github.com/JimBobSquarePants/ImageSharp", + "licenseUrl": "http://www.apache.org/licenses/LICENSE-2.0", + "iconUrl": "https://raw.githubusercontent.com/JimBobSquarePants/ImageSharp/master/build/icons/imagesharp-logo-128.png", + "requireLicenseAcceptance": false, + "repository": { + "type": "git", + "url": "https://github.com/JimBobSquarePants/ImageSharp" + }, + "tags": [ + "Image Resize Crop Gif Jpg Jpeg Bitmap Png Core" + ] + }, + "buildOptions": { + "allowUnsafe": true, + "xmlDoc": true, + "additionalArguments": [ "/additionalfile:../Shared/stylecop.json", "/ruleset:../../ImageSharp.ruleset" ], + "compile": [ + "../Shared/*.cs" + ] + }, + "configurations": { + "Release": { + "buildOptions": { + "warningsAsErrors": true, + "optimize": true + } + } + }, + "dependencies": { + "ImageSharp": { + "target": "project" + }, + "SixLabors.Fonts": "0.1.0-ci0041", + "ImageSharp.Drawing.Paths": { + "target": "project" + }, + "StyleCop.Analyzers": { + "version": "1.0.0", + "type": "build" + }, + "System.Buffers": "4.0.0", + "System.Runtime.CompilerServices.Unsafe": "4.0.0" + }, + "frameworks": { + "netstandard1.1": { + "dependencies": { + "System.Collections": "4.0.11", + "System.Diagnostics.Debug": "4.0.11", + "System.Diagnostics.Tools": "4.0.1", + "System.IO": "4.1.0", + "System.IO.Compression": "4.1.0", + "System.Linq": "4.1.0", + "System.Numerics.Vectors": "4.1.1", + "System.ObjectModel": "4.0.12", + "System.Resources.ResourceManager": "4.0.1", + "System.Runtime.Extensions": "4.1.0", + "System.Runtime.InteropServices": "4.1.0", + "System.Runtime.Numerics": "4.0.1", + "System.Text.Encoding.Extensions": "4.0.11", + "System.Threading": "4.0.11", + "System.Threading.Tasks": "4.0.11", + "System.Threading.Tasks.Parallel": "4.0.1" + } + }, + "net45": { + "dependencies": { + "System.Numerics.Vectors": "4.1.1", + "System.Threading.Tasks.Parallel": "4.0.0" + }, + "frameworkAssemblies": { + "System.Runtime": { "type": "build" } + } + }, + "net461": { + "dependencies": { + "System.Threading.Tasks.Parallel": "4.0.0" + }, + "frameworkAssemblies": { + "System.Runtime": { "type": "build" }, + "System.Numerics": "4.0.0.0" + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Drawing/Text/DrawText.cs b/tests/ImageSharp.Tests/Drawing/Text/DrawText.cs new file mode 100644 index 0000000000..2a2cb8a075 --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/Text/DrawText.cs @@ -0,0 +1,194 @@ + +namespace ImageSharp.Tests.Drawing.Text +{ + using System; + using System.IO; + using ImageSharp; + using ImageSharp.Drawing.Brushes; + using Processing; + using System.Collections.Generic; + using Xunit; + using ImageSharp.Drawing; + using System.Numerics; + using SixLabors.Shapes; + using ImageSharp.Drawing.Processors; + using ImageSharp.Drawing.Pens; + using SixLabors.Fonts; + using Paths; + + public class DrawText : IDisposable + { + Color color = Color.HotPink; + SolidBrush brush = Brushes.Solid(Color.HotPink); + IPath path = new SixLabors.Shapes.Path(new LinearLineSegment(new Vector2[] { + new Vector2(10,10), + new Vector2(20,10), + new Vector2(20,10), + new Vector2(30,10), + })); + private ProcessorWatchingImage img; + private readonly FontCollection FontCollection; + private readonly Font Font; + + public DrawText() + { + this.FontCollection = new FontCollection(); + this.Font = FontCollection.Install(TestFontUtilities.GetPath("SixLaborsSampleAB.woff")); + this.img = new ProcessorWatchingImage(10, 10); + } + + public void Dispose() + { + img.Dispose(); + } + + [Fact] + public void FillsForEachACharachterWhenBrushSetAndNotPen() + { + img.DrawText("123", this.Font, Brushes.Solid(Color.Red), null, Vector2.Zero, new TextGraphicsOptions(true)); + + Assert.NotEmpty(img.ProcessorApplications); + Assert.Equal(3, img.ProcessorApplications.Count); // 3 fills where applied + Assert.IsType>(img.ProcessorApplications[0].processor); + } + + [Fact] + public void FillsForEachACharachterWhenBrushSetAndNotPenDefaultOptions() + { + img.DrawText("123", this.Font, Brushes.Solid(Color.Red), null, Vector2.Zero); + + Assert.NotEmpty(img.ProcessorApplications); + Assert.Equal(3, img.ProcessorApplications.Count); // 3 fills where applied + Assert.IsType>(img.ProcessorApplications[0].processor); + } + + + [Fact] + public void FillsForEachACharachterWhenBrushSet() + { + img.DrawText("123", this.Font, Brushes.Solid(Color.Red), Vector2.Zero, new TextGraphicsOptions(true)); + + Assert.NotEmpty(img.ProcessorApplications); + Assert.Equal(3, img.ProcessorApplications.Count); // 3 fills where applied + Assert.IsType>(img.ProcessorApplications[0].processor); + } + + [Fact] + public void FillsForEachACharachterWhenBrushSetDefaultOptions() + { + img.DrawText("123", this.Font, Brushes.Solid(Color.Red), Vector2.Zero); + + Assert.NotEmpty(img.ProcessorApplications); + Assert.Equal(3, img.ProcessorApplications.Count); // 3 fills where applied + Assert.IsType>(img.ProcessorApplications[0].processor); + } + + [Fact] + public void FillsForEachACharachterWhenColorSet() + { + img.DrawText("123", this.Font, Color.Red, Vector2.Zero, new TextGraphicsOptions(true)); + + Assert.NotEmpty(img.ProcessorApplications); + Assert.Equal(3, img.ProcessorApplications.Count); + FillRegionProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + SolidBrush brush = Assert.IsType>(processor.Brush); + Assert.Equal(Color.Red, brush.Color); + } + + [Fact] + public void FillsForEachACharachterWhenColorSetDefaultOptions() + { + img.DrawText("123", this.Font, Color.Red, Vector2.Zero); + + Assert.NotEmpty(img.ProcessorApplications); + Assert.Equal(3, img.ProcessorApplications.Count); + Assert.IsType>(img.ProcessorApplications[0].processor); + FillRegionProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); + + SolidBrush brush = Assert.IsType>(processor.Brush); + Assert.Equal(Color.Red, brush.Color); + } + + [Fact] + public void DrawForEachACharachterWhenPenSetAndNotBrush() + { + img.DrawText("123", this.Font, null, Pens.Dash(Color.Red, 1), Vector2.Zero, new TextGraphicsOptions(true)); + + Assert.NotEmpty(img.ProcessorApplications); + Assert.Equal(3, img.ProcessorApplications.Count); // 3 fills where applied + Assert.IsType>(img.ProcessorApplications[0].processor); + } + + [Fact] + public void DrawForEachACharachterWhenPenSetAndNotBrushDefaultOptions() + { + img.DrawText("123", this.Font, null, Pens.Dash(Color.Red, 1), Vector2.Zero); + + Assert.NotEmpty(img.ProcessorApplications); + Assert.Equal(3, img.ProcessorApplications.Count); // 3 fills where applied + Assert.IsType>(img.ProcessorApplications[0].processor); + } + + + [Fact] + public void DrawForEachACharachterWhenPenSet() + { + img.DrawText("123", this.Font, Pens.Dash(Color.Red, 1), Vector2.Zero, new TextGraphicsOptions(true)); + + Assert.NotEmpty(img.ProcessorApplications); + Assert.Equal(3, img.ProcessorApplications.Count); // 3 fills where applied + Assert.IsType>(img.ProcessorApplications[0].processor); + } + + [Fact] + public void DrawForEachACharachterWhenPenSetDefaultOptions() + { + img.DrawText("123", this.Font, Pens.Dash(Color.Red, 1), Vector2.Zero); + + Assert.NotEmpty(img.ProcessorApplications); + Assert.Equal(3, img.ProcessorApplications.Count); // 3 fills where applied + Assert.IsType>(img.ProcessorApplications[0].processor); + } + + [Fact] + public void DrawForEachACharachterWhenPenSetAndFillFroEachWhenBrushSet() + { + img.DrawText("123", this.Font, Brushes.Solid(Color.Red), Pens.Dash(Color.Red, 1), Vector2.Zero, new TextGraphicsOptions(true)); + + Assert.NotEmpty(img.ProcessorApplications); + Assert.Equal(6, img.ProcessorApplications.Count); + } + + [Fact] + public void DrawForEachACharachterWhenPenSetAndFillFroEachWhenBrushSetDefaultOptions() + { + img.DrawText("123", this.Font, Brushes.Solid(Color.Red), Pens.Dash(Color.Red, 1), Vector2.Zero); + + Assert.NotEmpty(img.ProcessorApplications); + Assert.Equal(6, img.ProcessorApplications.Count); + } + + [Fact] + public void BrushAppliesBeforPen() + { + img.DrawText("1", this.Font, Brushes.Solid(Color.Red), Pens.Dash(Color.Red, 1), Vector2.Zero, new TextGraphicsOptions(true)); + + Assert.NotEmpty(img.ProcessorApplications); + Assert.Equal(2, img.ProcessorApplications.Count); + Assert.IsType>(img.ProcessorApplications[0].processor); + Assert.IsType>(img.ProcessorApplications[1].processor); + } + + [Fact] + public void BrushAppliesBeforPenDefaultOptions() + { + img.DrawText("1", this.Font, Brushes.Solid(Color.Red), Pens.Dash(Color.Red, 1), Vector2.Zero); + + Assert.NotEmpty(img.ProcessorApplications); + Assert.Equal(2, img.ProcessorApplications.Count); + Assert.IsType>(img.ProcessorApplications[0].processor); + Assert.IsType>(img.ProcessorApplications[1].processor); + } + } +} diff --git a/tests/ImageSharp.Tests/Drawing/Text/GlyphBuilder.cs b/tests/ImageSharp.Tests/Drawing/Text/GlyphBuilder.cs new file mode 100644 index 0000000000..1faa5edd37 --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/Text/GlyphBuilder.cs @@ -0,0 +1,68 @@ + +namespace ImageSharp.Tests.Drawing.Text +{ + using ImageSharp.Drawing; + using SixLabors.Fonts; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Numerics; + using System.Threading.Tasks; + using Xunit; + + public class GlyphBuilderTests + { + [Fact] + public void OriginUsed() + { + // Y axis is inverted as it expects to be drawing for bottom left + var fullBuilder = new GlyphBuilder(new System.Numerics.Vector2(10, 99)); + IGlyphRenderer builder = fullBuilder; + + builder.BeginGlyph(); + builder.BeginFigure(); + builder.MoveTo(new Vector2(0, 0)); + builder.LineTo(new Vector2(0, 10)); // becomes 0, -10 + + builder.CubicBezierTo( + new Vector2(15, 15), // control point - will not be in the final point collection + new Vector2(15, 10), // control point - will not be in the final point collection + new Vector2(10, 10));// becomes 10, -10 + + builder.QuadraticBezierTo( + new Vector2(10, 5), // control point - will not be in the final point collection + new Vector2(10, 0)); + + builder.EndFigure(); + builder.EndGlyph(); + + var points = fullBuilder.Paths.Single().Flatten().Single().Points; + + Assert.Contains(new Vector2(10, 99), points); + Assert.Contains(new Vector2(10, 109), points); + Assert.Contains(new Vector2(20, 99), points); + Assert.Contains(new Vector2(20, 109), points); + } + + [Fact] + public void EachGlypeCausesNewPath() + { + // Y axis is inverted as it expects to be drawing for bottom left + GlyphBuilder fullBuilder = new GlyphBuilder(); + IGlyphRenderer builder = fullBuilder; + for (var i = 0; i < 10; i++) + { + builder.BeginGlyph(); + builder.BeginFigure(); + builder.MoveTo(new Vector2(0, 0)); + builder.LineTo(new Vector2(0, 10)); // becomes 0, -10 + builder.LineTo(new Vector2(10, 10));// becomes 10, -10 + builder.LineTo(new Vector2(10, 0)); + builder.EndFigure(); + builder.EndGlyph(); + } + + Assert.Equal(10, fullBuilder.Paths.Count()); + } + } +} diff --git a/tests/ImageSharp.Tests/Drawing/Text/OutputText.cs b/tests/ImageSharp.Tests/Drawing/Text/OutputText.cs new file mode 100644 index 0000000000..ae007727a5 --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/Text/OutputText.cs @@ -0,0 +1,41 @@ + +namespace ImageSharp.Tests.Drawing.Text +{ + using System; + using System.IO; + using ImageSharp; + using ImageSharp.Drawing.Brushes; + using Processing; + using System.Collections.Generic; + using Xunit; + using ImageSharp.Drawing; + using System.Numerics; + using SixLabors.Shapes; + using ImageSharp.Drawing.Processors; + using ImageSharp.Drawing.Pens; + using SixLabors.Fonts; + + public class OutputText : FileTestBase + { + private readonly FontCollection FontCollection; + private readonly Font Font; + + public OutputText() + { + this.FontCollection = new FontCollection(); + this.Font = FontCollection.Install(TestFontUtilities.GetPath("SixLaborsSampleAB.woff")); + } + + [Fact] + public void DrawAB() + { + //draws 2 overlapping triangle glyphs twice 1 set on each line + using (var img = new Image(100, 200)) + { + img.Fill(Color.DarkBlue) + .DrawText("AB\nAB", new Font(this.Font, 50), Color.Red, new Vector2(0, 0)); + img.Save($"{this.CreateOutputDirectory("Drawing", "Text")}/AB.png"); + } + } + } +} diff --git a/tests/ImageSharp.Tests/TestFont.cs b/tests/ImageSharp.Tests/TestFont.cs new file mode 100644 index 0000000000..3a5bb2b2c1 --- /dev/null +++ b/tests/ImageSharp.Tests/TestFont.cs @@ -0,0 +1,90 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System; + using System.Collections.Concurrent; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Reflection; + + /// + /// A test image file. + /// + public static class TestFontUtilities + { + /// + /// The formats directory. + /// + private static readonly string FormatsDirectory = GetFontsDirectory(); + + /// + /// Gets the full qualified path to the file. + /// + /// + /// The file path. + /// + /// + /// The . + /// + public static string GetPath(string file) + { + return Path.Combine(FormatsDirectory, file); + } + + /// + /// Gets the correct path to the formats directory. + /// + /// + /// The . + /// + private static string GetFontsDirectory() + { + List directories = new List< string > { + "TestFonts/", // Here for code coverage tests. + "tests/ImageSharp.Tests/TestFonts/", // from travis/build script + "../../../ImageSharp.Tests/TestFonts/", // from Sandbox46 + "../../../../TestFonts/" + }; + + directories = directories.SelectMany(x => new[] + { + Path.GetFullPath(x) + }).ToList(); + + AddFormatsDirectoryFromTestAssebmlyPath(directories); + + var directory = directories.FirstOrDefault(x => Directory.Exists(x)); + + if(directory != null) + { + return directory; + } + + throw new System.Exception($"Unable to find Fonts directory at any of these locations [{string.Join(", ", directories)}]"); + } + + /// + /// The path returned by Path.GetFullPath(x) can be relative to dotnet framework directory + /// in certain scenarios like dotTrace test profiling. + /// This method calculates and adds the format directory based on the ImageSharp.Tests assembly location. + /// + /// The directories list + private static void AddFormatsDirectoryFromTestAssebmlyPath(List directories) + { + string assemblyLocation = typeof(TestFile).GetTypeInfo().Assembly.Location; + assemblyLocation = Path.GetDirectoryName(assemblyLocation); + + if (assemblyLocation != null) + { + string dirFromAssemblyLocation = Path.Combine(assemblyLocation, "../../../TestFonts/"); + dirFromAssemblyLocation = Path.GetFullPath(dirFromAssemblyLocation); + directories.Add(dirFromAssemblyLocation); + } + } + } +} diff --git a/tests/ImageSharp.Tests/TestFonts/SixLaborsSampleAB.woff b/tests/ImageSharp.Tests/TestFonts/SixLaborsSampleAB.woff new file mode 100644 index 0000000000000000000000000000000000000000..277749dfb19f13eaa020c9e823b76e5a91cf6d49 GIT binary patch literal 1352 zcmZuweM}Qc6rXLoYxyV?DB22##Z=Vus~{3g#k2&9U;{;jLy5ZELR%H23MwKhs}&F| zQ9!LxQ4uKuQ9vtP@~DA#V{0MMKoM$zko!Og&|SzTOz;g$YmIAeAn8$W#H-(0Xy0qyTnb05Z0dpY& zz#gzY3sK_9IBrp#Q1%me=FV{k!h+*OE|9X#(M!7Ng_3>Zm43or4{yU(D}eW5AGY|4oW5X^JVlZcI=vhhqsJe48+$4^ z*bEHK6TCGXO6RUKD7oSIHbd%JWo{Xb?g7k$4zEEL(%aR*IxOdi2gd)J6Nm-X^P}fJ zS&Fo*Ja#4c$i&UdeE)%`r7wmmnA!x!&e-z>r$(3}y=$jy(?tBJ2bad zjW3f;9Ba3F_2;Fd6BbN^u(`KqA5TI(RO=Tf4zwZW5jOu@x(LjhX3rVNg{t$Nw~EP9 ziGd{DJZNutk9lSx=%6-GdXiJH`}F0~Sa#3zsrE^#obnW_W+!g*PzEGUex}|~wY(4a zQZF1j=`7Tun#%e2XZmff;$jH~z1S@XL-M?(MrumtPZ_5~jD&M&nPc*Z%AE)Ztxj>b zQh&oQj{2ZnfRCsiDQ=;^`m+|yGqJm~&WyR1+$mvZ@F>t5BKz?U7}+Ul`qn={7>64w z#wLTd@R~Pp#g^{a6p7%uAyT*XyAvukPP>MI(V-b8SmVF_F1CKnBS}*+>`751W5Hp^ zU=Y(vdtzTNQRmX7ZW!h#!eaO?3`HcH1!j8EY`=rO+Z;F}0JC2h1EAAKVa(iI_rO+A z&?AUidPy zW%He9ANBjS=`Te?ljSR7)-A3jkDb|ZJm*hc`Hk|%`b+B3#qU?H75#=@*)-ukdijTs zP5Xy^UL<`j-g>aF<5j!4YjgZbuJirAtCdgu2A)P2A-+OO!2$2pU1C|NyX239`3)O& zA^A&6-%agnAJoKOoL957v#M^+yoA`=yElLDXs>IjS~uhCBgvQiQdys++j`;6Td8%Z zCWkR=;V;JO+u7q+tr4%=np&4v6?eW%*gYXJ2At*jYadjMn3!(i5py5Q6ITs?PC99A zt1#YgR2fW8MZ-}QJvsNr_#W&p-od`IRSv<9>Q~7+uVOdr(JislQO*1!bLr_*NrC4s zW=xOY73rm_{EnW?N{cD*`|J&F{cDGH*5+x@_&jf+y$1?#grc}yUOnQ%S21c7Dmfuf O(Gpq|B>YSp1mGVP7wVk= literal 0 HcmV?d00001 diff --git a/tests/ImageSharp.Tests/project.json b/tests/ImageSharp.Tests/project.json index 3761bb3858..7c67a5c706 100644 --- a/tests/ImageSharp.Tests/project.json +++ b/tests/ImageSharp.Tests/project.json @@ -31,6 +31,9 @@ "ImageSharp.Drawing.Paths": { "target": "project" }, + "ImageSharp.Drawing.Text": { + "target": "project" + }, "ImageSharp.Formats.Png": { "target": "project" }, From 2bbf85ae91cec7c4045ef18ecc8925f60363befc Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Sun, 5 Mar 2017 15:24:03 +0000 Subject: [PATCH 133/142] move to alpha release --- NuGet.config | 1 - src/ImageSharp.Drawing.Text/DrawText.cs | 1 - src/ImageSharp.Drawing.Text/project.json | 2 +- 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/NuGet.config b/NuGet.config index 322105d4d4..b2c967cc97 100644 --- a/NuGet.config +++ b/NuGet.config @@ -1,7 +1,6 @@  - diff --git a/src/ImageSharp.Drawing.Text/DrawText.cs b/src/ImageSharp.Drawing.Text/DrawText.cs index 486aa6e40f..28781fab22 100644 --- a/src/ImageSharp.Drawing.Text/DrawText.cs +++ b/src/ImageSharp.Drawing.Text/DrawText.cs @@ -12,7 +12,6 @@ namespace ImageSharp using Drawing.Pens; using SixLabors.Fonts; - using System.Linq; /// /// Extension methods for the type. diff --git a/src/ImageSharp.Drawing.Text/project.json b/src/ImageSharp.Drawing.Text/project.json index f446f91199..66d0e7d269 100644 --- a/src/ImageSharp.Drawing.Text/project.json +++ b/src/ImageSharp.Drawing.Text/project.json @@ -41,7 +41,7 @@ "ImageSharp": { "target": "project" }, - "SixLabors.Fonts": "0.1.0-ci0041", + "SixLabors.Fonts": "0.1.0-alpha0001", "ImageSharp.Drawing.Paths": { "target": "project" }, From 26b1715bf8854c5d15a3cce98116980075039684 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 5 Mar 2017 17:56:30 +0100 Subject: [PATCH 134/142] PixelAccessor and PixelArea using PinnedBuffer --- .../Colors/PackedPixel/BulkPixelOperations.cs | 168 ------------ .../BulkPixelOperations{TColor}.cs | 256 ++++++++++++++++++ .../Common/Memory/ArrayPointer{T}.cs | 32 +-- src/ImageSharp/Common/Memory/PinnedBuffer.cs | 89 ++++-- .../Common/Memory/PixelDataPool{T}.cs | 60 ++++ src/ImageSharp/Image/ImageBase{TColor}.cs | 10 +- src/ImageSharp/Image/PixelAccessor{TColor}.cs | 139 ++++------ src/ImageSharp/Image/PixelArea{TColor}.cs | 108 ++------ src/ImageSharp/Image/PixelPool{TColor}.cs | 43 --- .../ImageSharp.Sandbox46.csproj | 6 + .../Common/PinnedBufferTests.cs | 25 ++ .../ImageSharp.Tests/Image/PixelPoolTests.cs | 49 +++- 12 files changed, 552 insertions(+), 433 deletions(-) delete mode 100644 src/ImageSharp/Colors/PackedPixel/BulkPixelOperations.cs create mode 100644 src/ImageSharp/Colors/PackedPixel/BulkPixelOperations{TColor}.cs create mode 100644 src/ImageSharp/Common/Memory/PixelDataPool{T}.cs delete mode 100644 src/ImageSharp/Image/PixelPool{TColor}.cs diff --git a/src/ImageSharp/Colors/PackedPixel/BulkPixelOperations.cs b/src/ImageSharp/Colors/PackedPixel/BulkPixelOperations.cs deleted file mode 100644 index ffa28fc132..0000000000 --- a/src/ImageSharp/Colors/PackedPixel/BulkPixelOperations.cs +++ /dev/null @@ -1,168 +0,0 @@ -namespace ImageSharp -{ - using System; - using System.Numerics; - using System.Runtime.CompilerServices; - - public unsafe class BulkPixelOperations - where TColor : struct, IPixel - { - public static BulkPixelOperations Instance { get; } = default(TColor).BulkOperations; - - private static readonly int ColorSize = Unsafe.SizeOf(); - - internal virtual void PackFromVector4( - ArrayPointer sourceVectors, - ArrayPointer destColors, - int count) - { - Vector4* sp = (Vector4*)sourceVectors.PointerAtOffset; - byte* dp = (byte*)destColors; - - for (int i = 0; i < count; i++) - { - Vector4 v = Unsafe.Read(sp); - TColor c = default(TColor); - c.PackFromVector4(v); - Unsafe.Write(dp, c); - - sp++; - dp += ColorSize; - } - } - - internal virtual void PackToVector4( - ArrayPointer sourceColors, - ArrayPointer destVectors, - int count) - { - byte* sp = (byte*)sourceColors; - Vector4* dp = (Vector4*)destVectors.PointerAtOffset; - - for (int i = 0; i < count; i++) - { - TColor c = Unsafe.Read(sp); - *dp = c.ToVector4(); - sp += ColorSize; - dp++; - } - } - - internal virtual void PackFromXyzBytes( - ArrayPointer sourceBytes, - ArrayPointer destColors, - int count) - { - byte* sp = (byte*)sourceBytes; - byte* dp = (byte*)destColors.PointerAtOffset; - - for (int i = 0; i < count; i++) - { - TColor c = default(TColor); - c.PackFromBytes(sp[0], sp[1], sp[2], 255); - Unsafe.Write(dp, c); - sp += 3; - dp += ColorSize; - } - } - - internal virtual void PackToXyzBytes( - ArrayPointer sourceColors, - ArrayPointer destBytes, int count) - { - byte* sp = (byte*)sourceColors; - byte[] dest = destBytes.Array; - - for (int i = destBytes.Offset; i < destBytes.Offset + count*3; i+=3) - { - TColor c = Unsafe.Read(sp); - c.ToXyzBytes(dest, i); - sp += ColorSize; - } - } - - internal virtual void PackFromXyzwBytes(ArrayPointer sourceBytes, ArrayPointer destColors, int count) - { - byte* sp = (byte*)sourceBytes; - byte* dp = (byte*)destColors.PointerAtOffset; - - for (int i = 0; i < count; i++) - { - TColor c = default(TColor); - c.PackFromBytes(sp[0], sp[1], sp[2], sp[3]); - Unsafe.Write(dp, c); - sp += 4; - dp += ColorSize; - } - } - - internal virtual void PackToXyzwBytes(ArrayPointer sourceColors, ArrayPointer destBytes, int count) - { - byte* sp = (byte*)sourceColors; - byte[] dest = destBytes.Array; - - for (int i = destBytes.Offset; i < destBytes.Offset + count * 4; i += 4) - { - TColor c = Unsafe.Read(sp); - c.ToXyzwBytes(dest, i); - sp += ColorSize; - } - } - - internal virtual void PackFromZyxBytes(ArrayPointer sourceBytes, ArrayPointer destColors, int count) - { - byte* sp = (byte*)sourceBytes; - byte* dp = (byte*)destColors.PointerAtOffset; - - for (int i = 0; i < count; i++) - { - TColor c = default(TColor); - c.PackFromBytes(sp[2], sp[1], sp[0], 255); - Unsafe.Write(dp, c); - sp += 3; - dp += ColorSize; - } - } - - internal virtual void PackToZyxBytes(ArrayPointer sourceColors, ArrayPointer destBytes, int count) - { - byte* sp = (byte*)sourceColors; - byte[] dest = destBytes.Array; - - for (int i = destBytes.Offset; i < destBytes.Offset + count * 3; i += 3) - { - TColor c = Unsafe.Read(sp); - c.ToZyxBytes(dest, i); - sp += ColorSize; - } - } - - internal virtual void PackFromZyxwBytes(ArrayPointer sourceBytes, ArrayPointer destColors, int count) - { - byte* sp = (byte*)sourceBytes; - byte* dp = (byte*)destColors.PointerAtOffset; - - for (int i = 0; i < count; i++) - { - TColor c = default(TColor); - c.PackFromBytes(sp[2], sp[1], sp[0], sp[3]); - Unsafe.Write(dp, c); - sp += 4; - dp += ColorSize; - } - } - - internal virtual void PackToZyxwBytes(ArrayPointer sourceColors, ArrayPointer destBytes, int count) - { - byte* sp = (byte*)sourceColors; - byte[] dest = destBytes.Array; - - for (int i = destBytes.Offset; i < destBytes.Offset + count * 4; i += 4) - { - TColor c = Unsafe.Read(sp); - c.ToZyxwBytes(dest, i); - sp += ColorSize; - } - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Colors/PackedPixel/BulkPixelOperations{TColor}.cs b/src/ImageSharp/Colors/PackedPixel/BulkPixelOperations{TColor}.cs new file mode 100644 index 0000000000..557d59a16c --- /dev/null +++ b/src/ImageSharp/Colors/PackedPixel/BulkPixelOperations{TColor}.cs @@ -0,0 +1,256 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System.Numerics; + using System.Runtime.CompilerServices; + + /// + /// A stateless class implementing Strategy Pattern for batched pixel-data conversion operations + /// for pixel buffers of type . + /// + /// The pixel format. + public unsafe class BulkPixelOperations + where TColor : struct, IPixel + { + /// + /// The size of in bytes + /// + private static readonly int ColorSize = Unsafe.SizeOf(); + + /// + /// Gets the global instance for the pixel type + /// + public static BulkPixelOperations Instance { get; } = default(TColor).BulkOperations; + + /// + /// Bulk version of + /// + /// The to the source vectors. + /// The to the destination colors. + /// The number of pixels to convert. + internal virtual void PackFromVector4( + ArrayPointer sourceVectors, + ArrayPointer destColors, + int count) + { + Vector4* sp = (Vector4*)sourceVectors.PointerAtOffset; + byte* dp = (byte*)destColors; + + for (int i = 0; i < count; i++) + { + Vector4 v = Unsafe.Read(sp); + TColor c = default(TColor); + c.PackFromVector4(v); + Unsafe.Write(dp, c); + + sp++; + dp += ColorSize; + } + } + + /// + /// Bulk version of . + /// + /// The to the source colors. + /// The to the destination vectors. + /// The number of pixels to convert. + internal virtual void PackToVector4( + ArrayPointer sourceColors, + ArrayPointer destVectors, + int count) + { + byte* sp = (byte*)sourceColors; + Vector4* dp = (Vector4*)destVectors.PointerAtOffset; + + for (int i = 0; i < count; i++) + { + TColor c = Unsafe.Read(sp); + *dp = c.ToVector4(); + sp += ColorSize; + dp++; + } + } + + /// + /// Bulk version of that converts data in . + /// + /// + /// + /// + internal virtual void PackFromXyzBytes( + ArrayPointer sourceBytes, + ArrayPointer destColors, + int count) + { + byte* sp = (byte*)sourceBytes; + byte* dp = (byte*)destColors.PointerAtOffset; + + for (int i = 0; i < count; i++) + { + TColor c = default(TColor); + c.PackFromBytes(sp[0], sp[1], sp[2], 255); + Unsafe.Write(dp, c); + sp += 3; + dp += ColorSize; + } + } + + /// + /// Bulk version of . + /// + /// + /// + /// + internal virtual void PackToXyzBytes(ArrayPointer sourceColors, ArrayPointer destBytes, int count) + { + byte* sp = (byte*)sourceColors; + byte[] dest = destBytes.Array; + + for (int i = destBytes.Offset; i < destBytes.Offset + count * 3; i += 3) + { + TColor c = Unsafe.Read(sp); + c.ToXyzBytes(dest, i); + sp += ColorSize; + } + } + + /// + /// Bulk version of that converts data in . + /// + /// + /// + /// + internal virtual void PackFromXyzwBytes( + ArrayPointer sourceBytes, + ArrayPointer destColors, + int count) + { + byte* sp = (byte*)sourceBytes; + byte* dp = (byte*)destColors.PointerAtOffset; + + for (int i = 0; i < count; i++) + { + TColor c = default(TColor); + c.PackFromBytes(sp[0], sp[1], sp[2], sp[3]); + Unsafe.Write(dp, c); + sp += 4; + dp += ColorSize; + } + } + + /// + /// Bulk version of . + /// + /// + /// + /// + internal virtual void PackToXyzwBytes( + ArrayPointer sourceColors, + ArrayPointer destBytes, + int count) + { + byte* sp = (byte*)sourceColors; + byte[] dest = destBytes.Array; + + for (int i = destBytes.Offset; i < destBytes.Offset + count * 4; i += 4) + { + TColor c = Unsafe.Read(sp); + c.ToXyzwBytes(dest, i); + sp += ColorSize; + } + } + + /// + /// Bulk version of that converts data in . + /// + /// + /// + /// + internal virtual void PackFromZyxBytes( + ArrayPointer sourceBytes, + ArrayPointer destColors, + int count) + { + byte* sp = (byte*)sourceBytes; + byte* dp = (byte*)destColors.PointerAtOffset; + + for (int i = 0; i < count; i++) + { + TColor c = default(TColor); + c.PackFromBytes(sp[2], sp[1], sp[0], 255); + Unsafe.Write(dp, c); + sp += 3; + dp += ColorSize; + } + } + + /// + /// Bulk version of . + /// + /// + /// + /// + internal virtual void PackToZyxBytes(ArrayPointer sourceColors, ArrayPointer destBytes, int count) + { + byte* sp = (byte*)sourceColors; + byte[] dest = destBytes.Array; + + for (int i = destBytes.Offset; i < destBytes.Offset + count * 3; i += 3) + { + TColor c = Unsafe.Read(sp); + c.ToZyxBytes(dest, i); + sp += ColorSize; + } + } + + /// + /// Bulk version of that converts data in . + /// + /// + /// + /// + internal virtual void PackFromZyxwBytes( + ArrayPointer sourceBytes, + ArrayPointer destColors, + int count) + { + byte* sp = (byte*)sourceBytes; + byte* dp = (byte*)destColors.PointerAtOffset; + + for (int i = 0; i < count; i++) + { + TColor c = default(TColor); + c.PackFromBytes(sp[2], sp[1], sp[0], sp[3]); + Unsafe.Write(dp, c); + sp += 4; + dp += ColorSize; + } + } + + /// + /// Bulk version of . + /// + /// + /// + /// + internal virtual void PackToZyxwBytes( + ArrayPointer sourceColors, + ArrayPointer destBytes, + int count) + { + byte* sp = (byte*)sourceColors; + byte[] dest = destBytes.Array; + + for (int i = destBytes.Offset; i < destBytes.Offset + count * 4; i += 4) + { + TColor c = Unsafe.Read(sp); + c.ToZyxwBytes(dest, i); + sp += ColorSize; + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Common/Memory/ArrayPointer{T}.cs b/src/ImageSharp/Common/Memory/ArrayPointer{T}.cs index 8f99327ba2..e0b728095e 100644 --- a/src/ImageSharp/Common/Memory/ArrayPointer{T}.cs +++ b/src/ImageSharp/Common/Memory/ArrayPointer{T}.cs @@ -68,27 +68,12 @@ namespace ImageSharp /// public IntPtr PointerAtOffset { get; private set; } - /// - /// Forms a slice out of the given ArrayPointer, beginning at 'offset'. - /// - /// The offset in number of elements - /// The offseted (sliced) ArrayPointer - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ArrayPointer Slice(int offset) - { - ArrayPointer result = default(ArrayPointer); - result.Array = this.Array; - result.Offset = this.Offset + offset; - result.PointerAtOffset = this.PointerAtOffset + (Unsafe.SizeOf() * offset); - return result; - } - /// /// Convertes instance to a raw 'void*' pointer /// /// The to convert [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static explicit operator void*(ArrayPointer arrayPointer) + public static explicit operator void* (ArrayPointer arrayPointer) { return (void*)arrayPointer.PointerAtOffset; } @@ -102,5 +87,20 @@ namespace ImageSharp { return (byte*)arrayPointer.PointerAtOffset; } + + /// + /// Forms a slice out of the given ArrayPointer, beginning at 'offset'. + /// + /// The offset in number of elements + /// The offseted (sliced) ArrayPointer + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ArrayPointer Slice(int offset) + { + ArrayPointer result = default(ArrayPointer); + result.Array = this.Array; + result.Offset = this.Offset + offset; + result.PointerAtOffset = this.PointerAtOffset + (Unsafe.SizeOf() * offset); + return result; + } } } \ No newline at end of file diff --git a/src/ImageSharp/Common/Memory/PinnedBuffer.cs b/src/ImageSharp/Common/Memory/PinnedBuffer.cs index c6e0c7c6f9..201d93b56a 100644 --- a/src/ImageSharp/Common/Memory/PinnedBuffer.cs +++ b/src/ImageSharp/Common/Memory/PinnedBuffer.cs @@ -5,25 +5,23 @@ namespace ImageSharp using System.Runtime.InteropServices; /// - /// Manages a pinned buffer of 'T' as a Disposable resource. + /// Manages a pinned buffer of value type data 'T' as a Disposable resource. /// The backing array is either pooled or comes from the outside. - /// TODO: Should replace the pinning/dispose logic in several classes like or ! /// /// The value type. internal class PinnedBuffer : IDisposable where T : struct { + /// + /// A handle that allows to access the managed as an unmanaged memory by pinning. + /// private GCHandle handle; - private bool isBufferRented; - - private bool isDisposed; - /// - /// TODO: Consider reusing functionality of + /// A value indicating wether this instance should return the array to the pool. /// - private static readonly ArrayPool ArrayPool = ArrayPool.Create(); - + private bool isPoolingOwner; + /// /// Initializes a new instance of the class. /// @@ -31,8 +29,8 @@ namespace ImageSharp public PinnedBuffer(int count) { this.Count = count; - this.Array = ArrayPool.Rent(count); - this.isBufferRented = true; + this.Array = PixelDataPool.Rent(count); + this.isPoolingOwner = true; this.Pin(); } @@ -48,7 +46,34 @@ namespace ImageSharp } /// - /// The count of "relevant" elements. Usually be smaller than 'Array.Length' when is pooled. + /// Initializes a new instance of the class. + /// + /// The count of "relevant" elements in 'array'. + /// The array to pin. + public PinnedBuffer(int count, T[] array) + { + if (array.Length < count) + { + throw new ArgumentException("Can't initialize a PinnedBuffer with array.Length < count", nameof(array)); + } + this.Count = count; + this.Array = array; + this.Pin(); + } + + ~PinnedBuffer() + { + this.UnPin(); + } + + /// + /// Gets a value indicating whether this instance is disposed, or has lost ownership of . + /// + public bool IsDisposedOrLostArrayOwnership { get; private set; } + + + /// + /// Gets the count of "relevant" elements. Usually be smaller than 'Array.Length' when is pooled. /// public int Count { get; private set; } @@ -58,7 +83,7 @@ namespace ImageSharp public T[] Array { get; private set; } /// - /// Pointer to the pinned . + /// Gets a pointer to the pinned . /// public IntPtr Pointer { get; private set; } @@ -67,16 +92,16 @@ namespace ImageSharp /// public void Dispose() { - if (this.isDisposed) + if (this.IsDisposedOrLostArrayOwnership) { return; } - this.isDisposed = true; + this.IsDisposedOrLostArrayOwnership = true; this.UnPin(); - if (this.isBufferRented) + if (this.isPoolingOwner) { - ArrayPool.Return(this.Array, true); + PixelDataPool.Return(this.Array); } this.Array = null; @@ -85,12 +110,37 @@ namespace ImageSharp GC.SuppressFinalize(this); } + /// + /// Unpins and makes the object "quasi-disposed" so the array is no longer owned by this object. + /// If is rented, it's the callers responsibility to return it to it's pool. (Most likely ) + /// + /// The unpinned + public T[] UnPinAndTakeArrayOwnership() + { + if (this.IsDisposedOrLostArrayOwnership) + { + throw new InvalidOperationException("UnPinAndTakeArrayOwnership() is invalid: either PinnedBuffer is disposed or UnPinAndTakeArrayOwnership() has been called multiple times!"); + } + + this.IsDisposedOrLostArrayOwnership = true; + this.UnPin(); + T[] array = this.Array; + this.Array = null; + return array; + } + + /// + /// Pins . + /// private void Pin() { this.handle = GCHandle.Alloc(this.Array, GCHandleType.Pinned); this.Pointer = this.handle.AddrOfPinnedObject(); } + /// + /// Unpins . + /// private void UnPin() { if (this.Pointer == IntPtr.Zero || !this.handle.IsAllocated) @@ -100,10 +150,5 @@ namespace ImageSharp this.handle.Free(); this.Pointer = IntPtr.Zero; } - - ~PinnedBuffer() - { - this.UnPin(); - } } } \ No newline at end of file diff --git a/src/ImageSharp/Common/Memory/PixelDataPool{T}.cs b/src/ImageSharp/Common/Memory/PixelDataPool{T}.cs new file mode 100644 index 0000000000..f6f6a10425 --- /dev/null +++ b/src/ImageSharp/Common/Memory/PixelDataPool{T}.cs @@ -0,0 +1,60 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Buffers; + + /// + /// Provides a resource pool that enables reusing instances of value type arrays . + /// will always return arrays initialized with 'default(T)' + /// + /// The value type. + public static class PixelDataPool + where T : struct + { + /// + /// The used to pool data. + /// + private static readonly ArrayPool ArrayPool = ArrayPool.Create(CalculateMaxArrayLength(), 50); + + /// + /// Rents the pixel array from the pool. + /// + /// The minimum length of the array to return. + /// The + public static T[] Rent(int minimumLength) + { + return ArrayPool.Rent(minimumLength); + } + + /// + /// Returns the rented pixel array back to the pool. + /// + /// The array to return to the buffer pool. + public static void Return(T[] array) + { + ArrayPool.Return(array, true); + } + + /// + /// Heuristically calculates a reasonable maxArrayLength value for the backing . + /// + /// The maxArrayLength value + internal static int CalculateMaxArrayLength() + { + if (typeof(IPixel).IsAssignableFrom(typeof(T))) + { + const int MaximumExpectedImageSize = 16384; + return MaximumExpectedImageSize * MaximumExpectedImageSize; + } + else + { + return int.MaxValue; + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Image/ImageBase{TColor}.cs b/src/ImageSharp/Image/ImageBase{TColor}.cs index e4b4485c7f..9bd760805a 100644 --- a/src/ImageSharp/Image/ImageBase{TColor}.cs +++ b/src/ImageSharp/Image/ImageBase{TColor}.cs @@ -162,13 +162,15 @@ namespace ImageSharp internal void SwapPixelsBuffers(PixelAccessor pixelSource) { Guard.NotNull(pixelSource, nameof(pixelSource)); - Guard.IsTrue(pixelSource.PooledMemory, nameof(pixelSource.PooledMemory), "pixelSource must be using pooled memory"); + + // TODO: This check was useful. We can introduce a bool PixelAccessor.IsBoundToImage to re-introduce it. + // Guard.IsTrue(pixelSource.PooledMemory, nameof(pixelSource.PooledMemory), "pixelSource must be using pooled memory"); int newWidth = pixelSource.Width; int newHeight = pixelSource.Height; // Push my memory into the accessor (which in turn unpins the old puffer ready for the images use) - TColor[] newPixels = pixelSource.ReturnCurrentPixelsAndReplaceThemInternally(this.Width, this.Height, this.pixelBuffer, true); + TColor[] newPixels = pixelSource.ReturnCurrentPixelsAndReplaceThemInternally(this.Width, this.Height, this.pixelBuffer); this.Width = newWidth; this.Height = newHeight; this.pixelBuffer = newPixels; @@ -222,7 +224,7 @@ namespace ImageSharp ///
private void RentPixels() { - this.pixelBuffer = PixelPool.RentPixels(this.Width * this.Height); + this.pixelBuffer = PixelDataPool.Rent(this.Width * this.Height); } /// @@ -230,7 +232,7 @@ namespace ImageSharp /// private void ReturnPixels() { - PixelPool.ReturnPixels(this.pixelBuffer); + PixelDataPool.Return(this.pixelBuffer); this.pixelBuffer = null; } } diff --git a/src/ImageSharp/Image/PixelAccessor{TColor}.cs b/src/ImageSharp/Image/PixelAccessor{TColor}.cs index b31ada10bb..3a3f0f5c7e 100644 --- a/src/ImageSharp/Image/PixelAccessor{TColor}.cs +++ b/src/ImageSharp/Image/PixelAccessor{TColor}.cs @@ -18,21 +18,11 @@ namespace ImageSharp public unsafe class PixelAccessor : IDisposable where TColor : struct, IPixel { - /// - /// The pointer to the pixel buffer. - /// - private IntPtr dataPointer; - /// /// The position of the first pixel in the image. /// private byte* pixelsBase; - - /// - /// Provides a way to access the pixels from unmanaged memory. - /// - private GCHandle pixelsHandle; - + /// /// A value indicating whether this instance of the given entity has been disposed. /// @@ -45,9 +35,9 @@ namespace ImageSharp private bool isDisposed; /// - /// The pixel buffer + /// The containing the pixel data. /// - private TColor[] pixelBuffer; + private PinnedBuffer pixelBuffer; /// /// Initializes a new instance of the class. @@ -59,7 +49,7 @@ namespace ImageSharp Guard.MustBeGreaterThan(image.Width, 0, "image width"); Guard.MustBeGreaterThan(image.Height, 0, "image height"); - this.SetPixelBufferUnsafe(image.Width, image.Height, image.Pixels, false); + this.SetPixelBufferUnsafe(image.Width, image.Height, image.Pixels); this.ParallelOptions = image.Configuration.ParallelOptions; } @@ -70,7 +60,7 @@ namespace ImageSharp /// The height of the image represented by the pixel buffer. /// The pixel buffer. public PixelAccessor(int width, int height, TColor[] pixels) - : this(width, height, pixels, false) + : this(width, height, new PinnedBuffer(width * height, pixels)) { } @@ -80,7 +70,7 @@ namespace ImageSharp /// The width of the image represented by the pixel buffer. /// The height of the image represented by the pixel buffer. public PixelAccessor(int width, int height) - : this(width, height, PixelPool.RentPixels(width * height), true) + : this(width, height, new PinnedBuffer(width * height)) { } @@ -90,19 +80,18 @@ namespace ImageSharp /// The width of the image represented by the pixel buffer. /// The height of the image represented by the pixel buffer. /// The pixel buffer. - /// if set to true then the is from the thus should be returned once disposed. - private PixelAccessor(int width, int height, TColor[] pixels, bool pooledMemory) + private PixelAccessor(int width, int height, PinnedBuffer pixels) { Guard.NotNull(pixels, nameof(pixels)); Guard.MustBeGreaterThan(width, 0, nameof(width)); Guard.MustBeGreaterThan(height, 0, nameof(height)); - if (!(pixels.Length >= width * height)) - { - throw new ArgumentException($"Pixel array must have the length of at least {width * height}."); - } + //if (!(pixels.Length >= width * height)) + //{ + // throw new ArgumentException($"Pixel array must have the length of at least {width * height}."); + //} - this.SetPixelBufferUnsafe(width, height, pixels, pooledMemory); + this.SetPixelBufferUnsafe(width, height, pixels); this.ParallelOptions = Configuration.Default.ParallelOptions; } @@ -114,21 +103,16 @@ namespace ImageSharp { this.Dispose(); } - - /// - /// Gets a value indicating whether the current pixel buffer is from a pooled source. - /// - public bool PooledMemory { get; private set; } - + /// /// Gets the pixel buffer array. /// - public TColor[] PixelBuffer => this.pixelBuffer; + public TColor[] PixelBuffer => this.pixelBuffer.Array; /// /// Gets the pointer to the pixel buffer. /// - public IntPtr DataPointer => this.dataPointer; + public IntPtr DataPointer => this.pixelBuffer.Pointer; /// /// Gets the size of a single pixel in the number of bytes. @@ -246,24 +230,18 @@ namespace ImageSharp { return; } - - this.UnPinPixels(); - + // Note disposing is done. this.isDisposed = true; + this.pixelBuffer.Dispose(); + // This object will be cleaned up by the Dispose method. // Therefore, you should call GC.SuppressFinalize to // take this object off the finalization queue // and prevent finalization code for this object // from executing a second time. GC.SuppressFinalize(this); - - if (this.PooledMemory) - { - PixelPool.ReturnPixels(this.pixelBuffer); - this.pixelBuffer = null; - } } /// @@ -280,13 +258,12 @@ namespace ImageSharp /// The width. /// The height. /// The pixels. - /// If set to true this indicates that the pixel buffer is from a pooled source. /// Returns the old pixel data thats has gust been replaced. - /// If is true then caller is responsible for ensuring is called. - internal TColor[] ReturnCurrentPixelsAndReplaceThemInternally(int width, int height, TColor[] pixels, bool pooledMemory) + /// If is true then caller is responsible for ensuring is called. + internal TColor[] ReturnCurrentPixelsAndReplaceThemInternally(int width, int height, TColor[] pixels) { - TColor[] oldPixels = this.pixelBuffer; - this.SetPixelBufferUnsafe(width, height, pixels, pooledMemory); + TColor[] oldPixels = this.pixelBuffer.UnPinAndTakeArrayOwnership(); + this.SetPixelBufferUnsafe(width, height, pixels); return oldPixels; } @@ -514,53 +491,57 @@ namespace ImageSharp return this.pixelsBase + (((y * this.Width) + x) * Unsafe.SizeOf()); } + private void SetPixelBufferUnsafe(int width, int height, TColor[] pixels) + { + this.SetPixelBufferUnsafe(width, height, new PinnedBuffer(width * height, pixels)); + } + /// /// Sets the pixel buffer in an unsafe manor this should not be used unless you know what its doing!!! /// /// The width. /// The height. - /// The pixels. - /// If set to true this indicates that the pixel buffer is from a pooled source. - private void SetPixelBufferUnsafe(int width, int height, TColor[] pixels, bool pooledMemory) + /// The pixel buffer + private void SetPixelBufferUnsafe(int width, int height, PinnedBuffer pixels) { this.pixelBuffer = pixels; - this.PooledMemory = pooledMemory; + this.pixelsBase = (byte*)pixels.Pointer; + this.Width = width; this.Height = height; - this.PinPixels(); this.PixelSize = Unsafe.SizeOf(); this.RowStride = this.Width * this.PixelSize; } - /// - /// Pins the pixels data. - /// - private void PinPixels() - { - // unpin any old pixels just incase - this.UnPinPixels(); - - this.pixelsHandle = GCHandle.Alloc(this.pixelBuffer, GCHandleType.Pinned); - this.dataPointer = this.pixelsHandle.AddrOfPinnedObject(); - this.pixelsBase = (byte*)this.dataPointer.ToPointer(); - } - - /// - /// Unpins pixels data. - /// - private void UnPinPixels() - { - if (this.pixelsBase != null) - { - if (this.pixelsHandle.IsAllocated) - { - this.pixelsHandle.Free(); - } - - this.dataPointer = IntPtr.Zero; - this.pixelsBase = null; - } - } + ///// + ///// Pins the pixels data. + ///// + //private void PinPixels() + //{ + // // unpin any old pixels just incase + // this.UnPinPixels(); + + // this.pixelsHandle = GCHandle.Alloc(this.pixelBuffer, GCHandleType.Pinned); + // this.dataPointer = this.pixelsHandle.AddrOfPinnedObject(); + // this.pixelsBase = (byte*)this.dataPointer.ToPointer(); + //} + + ///// + ///// Unpins pixels data. + ///// + //private void UnPinPixels() + //{ + // if (this.pixelsBase != null) + // { + // if (this.pixelsHandle.IsAllocated) + // { + // this.pixelsHandle.Free(); + // } + + // this.dataPointer = IntPtr.Zero; + // this.pixelsBase = null; + // } + //} /// /// Copy an area of pixels to the image. diff --git a/src/ImageSharp/Image/PixelArea{TColor}.cs b/src/ImageSharp/Image/PixelArea{TColor}.cs index 77b648ca56..25840167eb 100644 --- a/src/ImageSharp/Image/PixelArea{TColor}.cs +++ b/src/ImageSharp/Image/PixelArea{TColor}.cs @@ -18,21 +18,6 @@ namespace ImageSharp public sealed unsafe class PixelArea : IDisposable where TColor : struct, IPixel { - /// - /// True if was rented from by the constructor - /// - private readonly bool isBufferRented; - - /// - /// Provides a way to access the pixels from unmanaged memory. - /// - private readonly GCHandle pixelsHandle; - - /// - /// The pointer to the pixel buffer. - /// - private IntPtr dataPointer; - /// /// A value indicating whether this instance of the given entity has been disposed. /// @@ -44,6 +29,11 @@ namespace ImageSharp /// private bool isDisposed; + /// + /// The underlying buffer containing the raw pixel data. + /// + private PinnedBuffer byteBuffer; + /// /// Initializes a new instance of the class. /// @@ -76,14 +66,11 @@ namespace ImageSharp this.Height = height; this.ComponentOrder = componentOrder; this.RowStride = width * GetComponentCount(componentOrder); - this.Bytes = bytes; - this.Length = bytes.Length; - this.isBufferRented = false; - this.pixelsHandle = GCHandle.Alloc(this.Bytes, GCHandleType.Pinned); + this.Length = bytes.Length; // TODO: Is this the right value for Length? - // TODO: Why is Resharper warning us about an impure method call? - this.dataPointer = this.pixelsHandle.AddrOfPinnedObject(); - this.PixelBase = (byte*)this.dataPointer.ToPointer(); + this.byteBuffer = new PinnedBuffer(bytes); + + this.PixelBase = (byte*)this.byteBuffer.Pointer; } /// @@ -132,27 +119,15 @@ namespace ImageSharp this.ComponentOrder = componentOrder; this.RowStride = (width * GetComponentCount(componentOrder)) + padding; this.Length = this.RowStride * height; - this.Bytes = BytesPool.Rent(this.Length); - this.isBufferRented = true; - this.pixelsHandle = GCHandle.Alloc(this.Bytes, GCHandleType.Pinned); - // TODO: Why is Resharper warning us about an impure method call? - this.dataPointer = this.pixelsHandle.AddrOfPinnedObject(); - this.PixelBase = (byte*)this.dataPointer.ToPointer(); + this.byteBuffer = new PinnedBuffer(this.Length); + this.PixelBase = (byte*)this.byteBuffer.Pointer; } - - /// - /// Finalizes an instance of the class. - /// - ~PixelArea() - { - this.Dispose(false); - } - + /// /// Gets the data in bytes. /// - public byte[] Bytes { get; } + public byte[] Bytes => this.byteBuffer.Array; /// /// Gets the length of the buffer. @@ -167,7 +142,7 @@ namespace ImageSharp /// /// Gets the pointer to the pixel buffer. /// - public IntPtr DataPointer => this.dataPointer; + public IntPtr DataPointer => this.byteBuffer.Pointer; /// /// Gets the height. @@ -188,26 +163,19 @@ namespace ImageSharp /// Gets the width. /// public int Width { get; } - - /// - /// Gets the pool used to rent bytes, when it's not coming from an external source. - /// - // TODO: Use own pool? - private static ArrayPool BytesPool => ArrayPool.Shared; - + /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// public void Dispose() { - this.Dispose(true); + if (this.isDisposed) + { + return; + } - // This object will be cleaned up by the Dispose method. - // Therefore, you should call GC.SuppressFinalize to - // take this object off the finalization queue - // and prevent finalization code for this object - // from executing a second time. - GC.SuppressFinalize(this); + this.byteBuffer.Dispose(); + this.isDisposed = true; } /// @@ -281,38 +249,6 @@ namespace ImageSharp nameof(bytes), $"Invalid byte array length. Length {bytes.Length}; Should be {requiredLength}."); } - } - - /// - /// Disposes the object and frees resources for the Garbage Collector. - /// - /// If true, the object gets disposed. - private void Dispose(bool disposing) - { - if (this.isDisposed) - { - return; - } - - if (this.PixelBase == null) - { - return; - } - - if (this.pixelsHandle.IsAllocated) - { - this.pixelsHandle.Free(); - } - - if (disposing && this.isBufferRented) - { - BytesPool.Return(this.Bytes); - } - - this.dataPointer = IntPtr.Zero; - this.PixelBase = null; - - this.isDisposed = true; - } + } } } \ No newline at end of file diff --git a/src/ImageSharp/Image/PixelPool{TColor}.cs b/src/ImageSharp/Image/PixelPool{TColor}.cs deleted file mode 100644 index ea6dad6b12..0000000000 --- a/src/ImageSharp/Image/PixelPool{TColor}.cs +++ /dev/null @@ -1,43 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - using System; - using System.Buffers; - - // TODO: Consider refactoring this into a more general ClearPool, so we can use it in PinnedBuffer! - /// - /// Provides a resource pool that enables reusing instances of type . - /// - /// The pixel format. - public static class PixelPool - where TColor : struct, IPixel - { - /// - /// The used to pool data. TODO: Choose sensible default size and count - /// - private static readonly ArrayPool ArrayPool = ArrayPool.Create(int.MaxValue, 50); - - /// - /// Rents the pixel array from the pool. - /// - /// The minimum length of the array to return. - /// The - public static TColor[] RentPixels(int minimumLength) - { - return ArrayPool.Rent(minimumLength); - } - - /// - /// Returns the rented pixel array back to the pool. - /// - /// The array to return to the buffer pool. - public static void ReturnPixels(TColor[] array) - { - ArrayPool.Return(array, true); - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj b/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj index ad436793d4..094eedb180 100644 --- a/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj +++ b/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj @@ -212,6 +212,9 @@ Tests\Colors\BulkPixelOperationsTests.cs + + Tests\Common\PinnedBufferTests.cs + Tests\Drawing\PolygonTests.cs @@ -248,6 +251,9 @@ Tests\Formats\Jpg\YCbCrImageTests.cs + + Tests\Image\PixelPoolTests.cs + Tests\MetaData\ImagePropertyTests.cs diff --git a/tests/ImageSharp.Tests/Common/PinnedBufferTests.cs b/tests/ImageSharp.Tests/Common/PinnedBufferTests.cs index e0783f7165..c5eb2a5103 100644 --- a/tests/ImageSharp.Tests/Common/PinnedBufferTests.cs +++ b/tests/ImageSharp.Tests/Common/PinnedBufferTests.cs @@ -22,6 +22,7 @@ { using (PinnedBuffer buffer = new PinnedBuffer(count)) { + Assert.False(buffer.IsDisposedOrLostArrayOwnership); Assert.NotNull(buffer.Array); Assert.Equal(count, buffer.Count); Assert.True(buffer.Array.Length >= count); @@ -38,6 +39,7 @@ Foo[] array = new Foo[count]; using (PinnedBuffer buffer = new PinnedBuffer(array)) { + Assert.False(buffer.IsDisposedOrLostArrayOwnership); Assert.Equal(array, buffer.Array); Assert.Equal(count, buffer.Count); @@ -45,6 +47,15 @@ } } + [Fact] + public void Dispose() + { + PinnedBuffer buffer = new PinnedBuffer(42); + buffer.Dispose(); + + Assert.True(buffer.IsDisposedOrLostArrayOwnership); + } + [Fact] public void GetArrayPointer() { @@ -60,6 +71,20 @@ } } + [Fact] + public void UnPinAndTakeArrayOwnership() + { + Foo[] data = null; + using (PinnedBuffer buffer = new PinnedBuffer(42)) + { + data = buffer.UnPinAndTakeArrayOwnership(); + Assert.True(buffer.IsDisposedOrLostArrayOwnership); + } + + Assert.NotNull(data); + Assert.True(data.Length >= 42); + } + private static void VerifyPointer(PinnedBuffer buffer) { IntPtr ptr = (IntPtr)Unsafe.AsPointer(ref buffer.Array[0]); diff --git a/tests/ImageSharp.Tests/Image/PixelPoolTests.cs b/tests/ImageSharp.Tests/Image/PixelPoolTests.cs index 0b762cf7c3..001785d60c 100644 --- a/tests/ImageSharp.Tests/Image/PixelPoolTests.cs +++ b/tests/ImageSharp.Tests/Image/PixelPoolTests.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // @@ -10,54 +10,54 @@ namespace ImageSharp.Tests using Xunit; /// - /// Tests the class. + /// Tests the class. /// - public class PixelPoolTests + public class PixelDataPoolTests { [Fact] - public void PixelPoolRentsMinimumSize() + public void PixelDataPoolRentsMinimumSize() { - Color[] pixels = PixelPool.RentPixels(1024); + Color[] pixels = PixelDataPool.Rent(1024); Assert.True(pixels.Length >= 1024); } [Fact] - public void PixelPoolRentsEmptyArray() + public void PixelDataPoolRentsEmptyArray() { for (int i = 16; i < 1024; i += 16) { - Color[] pixels = PixelPool.RentPixels(i); + Color[] pixels = PixelDataPool.Rent(i); Assert.True(pixels.All(p => p == default(Color))); - PixelPool.ReturnPixels(pixels); + PixelDataPool.Return(pixels); } for (int i = 16; i < 1024; i += 16) { - Color[] pixels = PixelPool.RentPixels(i); + Color[] pixels = PixelDataPool.Rent(i); Assert.True(pixels.All(p => p == default(Color))); - PixelPool.ReturnPixels(pixels); + PixelDataPool.Return(pixels); } } [Fact] - public void PixelPoolDoesNotThrowWhenReturningNonPooled() + public void PixelDataPoolDoesNotThrowWhenReturningNonPooled() { Color[] pixels = new Color[1024]; - PixelPool.ReturnPixels(pixels); + PixelDataPool.Return(pixels); Assert.True(pixels.Length >= 1024); } [Fact] - public void PixelPoolCleansRentedArray() + public void PixelDataPoolCleansRentedArray() { - Color[] pixels = PixelPool.RentPixels(256); + Color[] pixels = PixelDataPool.Rent(256); for (int i = 0; i < pixels.Length; i++) { @@ -66,9 +66,28 @@ namespace ImageSharp.Tests Assert.True(pixels.All(p => p == Color.Azure)); - PixelPool.ReturnPixels(pixels); + PixelDataPool.Return(pixels); Assert.True(pixels.All(p => p == default(Color))); } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void CalculateMaxArrayLength(bool isRawData) + { + int max = isRawData ? PixelDataPool.CalculateMaxArrayLength() + : PixelDataPool.CalculateMaxArrayLength(); + + Assert.Equal(max < int.MaxValue, !isRawData); + } + + [Fact] + public void RentNonIPixelData() + { + byte[] data = PixelDataPool.Rent(16384); + + Assert.True(data.Length >= 16384); + } } } \ No newline at end of file From 6132d8fa4cfe879f1679bfe544547863e746bde8 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 5 Mar 2017 18:18:09 +0100 Subject: [PATCH 135/142] cleanup & stylecop --- .../BulkPixelOperations{TColor}.cs | 56 +++++++++---------- .../Colors/PackedPixel/HalfVector2.cs | 2 +- .../Colors/PackedPixel/NormalizedByte4.cs | 2 +- src/ImageSharp/Common/Memory/ArrayPointer.cs | 34 +++++++++++ .../Common/Memory/ArrayPointer{T}.cs | 4 +- .../{PinnedBuffer.cs => PinnedBuffer{T}.cs} | 16 +++++- .../Common/Memory/PixelDataPool{T}.cs | 2 +- src/ImageSharp/Image/ImageBase{TColor}.cs | 4 +- src/ImageSharp/Image/PixelAccessor{TColor}.cs | 41 +------------- src/ImageSharp/Image/PixelArea{TColor}.cs | 7 +-- .../Colors/BulkPixelOperationsTests.cs | 5 +- 11 files changed, 92 insertions(+), 81 deletions(-) rename src/ImageSharp/Common/Memory/{PinnedBuffer.cs => PinnedBuffer{T}.cs} (93%) diff --git a/src/ImageSharp/Colors/PackedPixel/BulkPixelOperations{TColor}.cs b/src/ImageSharp/Colors/PackedPixel/BulkPixelOperations{TColor}.cs index 557d59a16c..31f872e42a 100644 --- a/src/ImageSharp/Colors/PackedPixel/BulkPixelOperations{TColor}.cs +++ b/src/ImageSharp/Colors/PackedPixel/BulkPixelOperations{TColor}.cs @@ -78,9 +78,9 @@ namespace ImageSharp /// /// Bulk version of that converts data in . /// - /// - /// - /// + /// The to the source bytes. + /// The to the destination colors. + /// The number of pixels to convert. internal virtual void PackFromXyzBytes( ArrayPointer sourceBytes, ArrayPointer destColors, @@ -102,15 +102,15 @@ namespace ImageSharp /// /// Bulk version of . /// - /// - /// - /// + /// The to the source colors. + /// The to the destination bytes. + /// The number of pixels to convert. internal virtual void PackToXyzBytes(ArrayPointer sourceColors, ArrayPointer destBytes, int count) { byte* sp = (byte*)sourceColors; byte[] dest = destBytes.Array; - for (int i = destBytes.Offset; i < destBytes.Offset + count * 3; i += 3) + for (int i = destBytes.Offset; i < destBytes.Offset + (count * 3); i += 3) { TColor c = Unsafe.Read(sp); c.ToXyzBytes(dest, i); @@ -121,9 +121,9 @@ namespace ImageSharp /// /// Bulk version of that converts data in . /// - /// - /// - /// + /// The to the source bytes. + /// The to the destination colors. + /// The number of pixels to convert. internal virtual void PackFromXyzwBytes( ArrayPointer sourceBytes, ArrayPointer destColors, @@ -145,9 +145,9 @@ namespace ImageSharp /// /// Bulk version of . /// - /// - /// - /// + /// The to the source colors. + /// The to the destination bytes. + /// The number of pixels to convert. internal virtual void PackToXyzwBytes( ArrayPointer sourceColors, ArrayPointer destBytes, @@ -156,7 +156,7 @@ namespace ImageSharp byte* sp = (byte*)sourceColors; byte[] dest = destBytes.Array; - for (int i = destBytes.Offset; i < destBytes.Offset + count * 4; i += 4) + for (int i = destBytes.Offset; i < destBytes.Offset + (count * 4); i += 4) { TColor c = Unsafe.Read(sp); c.ToXyzwBytes(dest, i); @@ -167,9 +167,9 @@ namespace ImageSharp /// /// Bulk version of that converts data in . /// - /// - /// - /// + /// The to the source bytes. + /// The to the destination colors. + /// The number of pixels to convert. internal virtual void PackFromZyxBytes( ArrayPointer sourceBytes, ArrayPointer destColors, @@ -191,15 +191,15 @@ namespace ImageSharp /// /// Bulk version of . /// - /// - /// - /// + /// The to the source colors. + /// The to the destination bytes. + /// The number of pixels to convert. internal virtual void PackToZyxBytes(ArrayPointer sourceColors, ArrayPointer destBytes, int count) { byte* sp = (byte*)sourceColors; byte[] dest = destBytes.Array; - for (int i = destBytes.Offset; i < destBytes.Offset + count * 3; i += 3) + for (int i = destBytes.Offset; i < destBytes.Offset + (count * 3); i += 3) { TColor c = Unsafe.Read(sp); c.ToZyxBytes(dest, i); @@ -210,9 +210,9 @@ namespace ImageSharp /// /// Bulk version of that converts data in . /// - /// - /// - /// + /// The to the source bytes. + /// The to the destination colors. + /// The number of pixels to convert. internal virtual void PackFromZyxwBytes( ArrayPointer sourceBytes, ArrayPointer destColors, @@ -234,9 +234,9 @@ namespace ImageSharp /// /// Bulk version of . /// - /// - /// - /// + /// The to the source colors. + /// The to the destination bytes. + /// The number of pixels to convert. internal virtual void PackToZyxwBytes( ArrayPointer sourceColors, ArrayPointer destBytes, @@ -245,7 +245,7 @@ namespace ImageSharp byte* sp = (byte*)sourceColors; byte[] dest = destBytes.Array; - for (int i = destBytes.Offset; i < destBytes.Offset + count * 4; i += 4) + for (int i = destBytes.Offset; i < destBytes.Offset + (count * 4); i += 4) { TColor c = Unsafe.Read(sp); c.ToZyxwBytes(dest, i); diff --git a/src/ImageSharp/Colors/PackedPixel/HalfVector2.cs b/src/ImageSharp/Colors/PackedPixel/HalfVector2.cs index 778f86e0f6..68cb427356 100644 --- a/src/ImageSharp/Colors/PackedPixel/HalfVector2.cs +++ b/src/ImageSharp/Colors/PackedPixel/HalfVector2.cs @@ -45,7 +45,7 @@ namespace ImageSharp /// public uint PackedValue { get; set; } - + /// public BulkPixelOperations BulkOperations => new BulkPixelOperations(); diff --git a/src/ImageSharp/Colors/PackedPixel/NormalizedByte4.cs b/src/ImageSharp/Colors/PackedPixel/NormalizedByte4.cs index cba3f0e863..4511060e48 100644 --- a/src/ImageSharp/Colors/PackedPixel/NormalizedByte4.cs +++ b/src/ImageSharp/Colors/PackedPixel/NormalizedByte4.cs @@ -52,7 +52,7 @@ namespace ImageSharp /// public uint PackedValue { get; set; } - + /// public BulkPixelOperations BulkOperations => new BulkPixelOperations(); diff --git a/src/ImageSharp/Common/Memory/ArrayPointer.cs b/src/ImageSharp/Common/Memory/ArrayPointer.cs index 56cae35a45..342eaa6c87 100644 --- a/src/ImageSharp/Common/Memory/ArrayPointer.cs +++ b/src/ImageSharp/Common/Memory/ArrayPointer.cs @@ -1,3 +1,8 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + namespace ImageSharp { using System.Runtime.CompilerServices; @@ -7,6 +12,12 @@ namespace ImageSharp /// internal static class ArrayPointer { + /// + /// Gets an to the beginning of the raw data in 'buffer'. + /// + /// The element type + /// The input + /// The [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe ArrayPointer GetArrayPointer(this PinnedBuffer buffer) where T : struct @@ -14,6 +25,13 @@ namespace ImageSharp return new ArrayPointer(buffer.Array, (void*)buffer.Pointer); } + /// + /// Copy 'count' number of elements of the same type from 'source' to 'dest' + /// + /// The element type. + /// The input + /// The destination . + /// The number of elements to copy [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void Copy(ArrayPointer source, ArrayPointer destination, int count) where T : struct @@ -21,6 +39,13 @@ namespace ImageSharp Unsafe.CopyBlock((void*)source.PointerAtOffset, (void*)destination.PointerAtOffset, USizeOf(count)); } + /// + /// Copy 'countInSource' elements of from 'source' into the raw byte buffer 'destination'. + /// + /// The element type. + /// The source buffer of elements to copy from. + /// The destination buffer. + /// The number of elements to copy from 'source' [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void Copy(ArrayPointer source, ArrayPointer destination, int countInSource) where T : struct @@ -28,6 +53,13 @@ namespace ImageSharp Unsafe.CopyBlock((void*)source.PointerAtOffset, (void*)destination.PointerAtOffset, USizeOf(countInSource)); } + /// + /// Copy 'countInDest' number of elements into 'dest' from a raw byte buffer defined by 'source'. + /// + /// The element type. + /// The raw source buffer to copy from"/> + /// The destination buffer"/> + /// The number of elements to copy. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void Copy(ArrayPointer source, ArrayPointer destination, int countInDest) where T : struct @@ -38,6 +70,7 @@ namespace ImageSharp /// /// Gets the size of `count` elements in bytes. /// + /// The element type. /// The count of the elements /// The size in bytes as int [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -47,6 +80,7 @@ namespace ImageSharp /// /// Gets the size of `count` elements in bytes as UInt32 /// + /// The element type. /// The count of the elements /// The size in bytes as UInt32 [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/ImageSharp/Common/Memory/ArrayPointer{T}.cs b/src/ImageSharp/Common/Memory/ArrayPointer{T}.cs index e0b728095e..9cbbaf0946 100644 --- a/src/ImageSharp/Common/Memory/ArrayPointer{T}.cs +++ b/src/ImageSharp/Common/Memory/ArrayPointer{T}.cs @@ -73,7 +73,7 @@ namespace ImageSharp /// /// The to convert [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static explicit operator void* (ArrayPointer arrayPointer) + public static explicit operator void*(ArrayPointer arrayPointer) { return (void*)arrayPointer.PointerAtOffset; } @@ -83,7 +83,7 @@ namespace ImageSharp /// /// The to convert [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static explicit operator byte* (ArrayPointer arrayPointer) + public static explicit operator byte*(ArrayPointer arrayPointer) { return (byte*)arrayPointer.PointerAtOffset; } diff --git a/src/ImageSharp/Common/Memory/PinnedBuffer.cs b/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs similarity index 93% rename from src/ImageSharp/Common/Memory/PinnedBuffer.cs rename to src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs index 201d93b56a..d95d675571 100644 --- a/src/ImageSharp/Common/Memory/PinnedBuffer.cs +++ b/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs @@ -1,3 +1,8 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + namespace ImageSharp { using System; @@ -21,7 +26,7 @@ namespace ImageSharp /// A value indicating wether this instance should return the array to the pool. /// private bool isPoolingOwner; - + /// /// Initializes a new instance of the class. /// @@ -56,11 +61,15 @@ namespace ImageSharp { throw new ArgumentException("Can't initialize a PinnedBuffer with array.Length < count", nameof(array)); } + this.Count = count; this.Array = array; this.Pin(); } + /// + /// Finalizes an instance of the class. + /// ~PinnedBuffer() { this.UnPin(); @@ -71,14 +80,13 @@ namespace ImageSharp /// public bool IsDisposedOrLostArrayOwnership { get; private set; } - /// /// Gets the count of "relevant" elements. Usually be smaller than 'Array.Length' when is pooled. /// public int Count { get; private set; } /// - /// The (pinned) array of elements. + /// Gets the backing pinned array. /// public T[] Array { get; private set; } @@ -96,6 +104,7 @@ namespace ImageSharp { return; } + this.IsDisposedOrLostArrayOwnership = true; this.UnPin(); @@ -147,6 +156,7 @@ namespace ImageSharp { return; } + this.handle.Free(); this.Pointer = IntPtr.Zero; } diff --git a/src/ImageSharp/Common/Memory/PixelDataPool{T}.cs b/src/ImageSharp/Common/Memory/PixelDataPool{T}.cs index f6f6a10425..74f0c1e8e0 100644 --- a/src/ImageSharp/Common/Memory/PixelDataPool{T}.cs +++ b/src/ImageSharp/Common/Memory/PixelDataPool{T}.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // diff --git a/src/ImageSharp/Image/ImageBase{TColor}.cs b/src/ImageSharp/Image/ImageBase{TColor}.cs index 9bd760805a..c2110eca4f 100644 --- a/src/ImageSharp/Image/ImageBase{TColor}.cs +++ b/src/ImageSharp/Image/ImageBase{TColor}.cs @@ -163,9 +163,9 @@ namespace ImageSharp { Guard.NotNull(pixelSource, nameof(pixelSource)); - // TODO: This check was useful. We can introduce a bool PixelAccessor.IsBoundToImage to re-introduce it. - // Guard.IsTrue(pixelSource.PooledMemory, nameof(pixelSource.PooledMemory), "pixelSource must be using pooled memory"); + // TODO: Was this check really useful? If yes, we can define a bool PixelAccessor.IsBoundToImage to re-introduce it: + // Guard.IsTrue(pixelSource.PooledMemory, nameof(pixelSource.PooledMemory), "pixelSource must be using pooled memory"); int newWidth = pixelSource.Width; int newHeight = pixelSource.Height; diff --git a/src/ImageSharp/Image/PixelAccessor{TColor}.cs b/src/ImageSharp/Image/PixelAccessor{TColor}.cs index 3a3f0f5c7e..9201270d9d 100644 --- a/src/ImageSharp/Image/PixelAccessor{TColor}.cs +++ b/src/ImageSharp/Image/PixelAccessor{TColor}.cs @@ -22,7 +22,7 @@ namespace ImageSharp /// The position of the first pixel in the image. /// private byte* pixelsBase; - + /// /// A value indicating whether this instance of the given entity has been disposed. /// @@ -86,11 +86,6 @@ namespace ImageSharp Guard.MustBeGreaterThan(width, 0, nameof(width)); Guard.MustBeGreaterThan(height, 0, nameof(height)); - //if (!(pixels.Length >= width * height)) - //{ - // throw new ArgumentException($"Pixel array must have the length of at least {width * height}."); - //} - this.SetPixelBufferUnsafe(width, height, pixels); this.ParallelOptions = Configuration.Default.ParallelOptions; @@ -103,7 +98,7 @@ namespace ImageSharp { this.Dispose(); } - + /// /// Gets the pixel buffer array. /// @@ -230,7 +225,7 @@ namespace ImageSharp { return; } - + // Note disposing is done. this.isDisposed = true; @@ -513,36 +508,6 @@ namespace ImageSharp this.RowStride = this.Width * this.PixelSize; } - ///// - ///// Pins the pixels data. - ///// - //private void PinPixels() - //{ - // // unpin any old pixels just incase - // this.UnPinPixels(); - - // this.pixelsHandle = GCHandle.Alloc(this.pixelBuffer, GCHandleType.Pinned); - // this.dataPointer = this.pixelsHandle.AddrOfPinnedObject(); - // this.pixelsBase = (byte*)this.dataPointer.ToPointer(); - //} - - ///// - ///// Unpins pixels data. - ///// - //private void UnPinPixels() - //{ - // if (this.pixelsBase != null) - // { - // if (this.pixelsHandle.IsAllocated) - // { - // this.pixelsHandle.Free(); - // } - - // this.dataPointer = IntPtr.Zero; - // this.pixelsBase = null; - // } - //} - /// /// Copy an area of pixels to the image. /// diff --git a/src/ImageSharp/Image/PixelArea{TColor}.cs b/src/ImageSharp/Image/PixelArea{TColor}.cs index 25840167eb..c54de12d6c 100644 --- a/src/ImageSharp/Image/PixelArea{TColor}.cs +++ b/src/ImageSharp/Image/PixelArea{TColor}.cs @@ -69,7 +69,6 @@ namespace ImageSharp this.Length = bytes.Length; // TODO: Is this the right value for Length? this.byteBuffer = new PinnedBuffer(bytes); - this.PixelBase = (byte*)this.byteBuffer.Pointer; } @@ -123,7 +122,7 @@ namespace ImageSharp this.byteBuffer = new PinnedBuffer(this.Length); this.PixelBase = (byte*)this.byteBuffer.Pointer; } - + /// /// Gets the data in bytes. /// @@ -163,7 +162,7 @@ namespace ImageSharp /// Gets the width. /// public int Width { get; } - + /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// @@ -249,6 +248,6 @@ namespace ImageSharp nameof(bytes), $"Invalid byte array length. Length {bytes.Length}; Should be {requiredLength}."); } - } + } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs b/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs index 3682aa78a3..4e1083033e 100644 --- a/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs +++ b/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs @@ -9,11 +9,13 @@ { public class Color : BulkPixelOperationsTests { + // MemberData does not work without redeclaring the public field in the derived test class: public static TheoryData ArraySizesData = new TheoryData { 7, 16, 1111 }; } public class Argb : BulkPixelOperationsTests { + // MemberData does not work without redeclaring the public field in the derived test class: public static TheoryData ArraySizesData = new TheoryData { 7, 16, 1111 }; } } @@ -21,7 +23,8 @@ public abstract class BulkPixelOperationsTests where TColor : struct, IPixel { - protected static TheoryData ArraySizesData = new TheoryData { 7, 16, 1111 }; + // The field is private so it can be safely redeclared in inherited non-abstract test classes. + private static TheoryData ArraySizesData = new TheoryData { 7, 16, 1111 }; [Theory] [MemberData(nameof(ArraySizesData))] From 8a08319ce0d8b146410a85eff45d7c64aecbef3e Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 5 Mar 2017 18:32:06 +0100 Subject: [PATCH 136/142] build and testrunning fix --- src/ImageSharp/Common/Memory/PixelDataPool{T}.cs | 3 ++- .../Colors/BulkPixelOperationsTests.cs | 11 +++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/ImageSharp/Common/Memory/PixelDataPool{T}.cs b/src/ImageSharp/Common/Memory/PixelDataPool{T}.cs index 74f0c1e8e0..a97d17fdbb 100644 --- a/src/ImageSharp/Common/Memory/PixelDataPool{T}.cs +++ b/src/ImageSharp/Common/Memory/PixelDataPool{T}.cs @@ -46,7 +46,8 @@ namespace ImageSharp /// The maxArrayLength value internal static int CalculateMaxArrayLength() { - if (typeof(IPixel).IsAssignableFrom(typeof(T))) + // ReSharper disable once SuspiciousTypeConversion.Global + if (default(T) is IPixel) { const int MaximumExpectedImageSize = 16384; return MaximumExpectedImageSize * MaximumExpectedImageSize; diff --git a/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs b/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs index 4e1083033e..0e69a54f54 100644 --- a/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs +++ b/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs @@ -9,22 +9,21 @@ { public class Color : BulkPixelOperationsTests { - // MemberData does not work without redeclaring the public field in the derived test class: - public static TheoryData ArraySizesData = new TheoryData { 7, 16, 1111 }; + // For 4.6 test runner MemberData does not work without redeclaring the public field in the derived test class: + public static new TheoryData ArraySizesData => new TheoryData { 7, 16, 1111 }; } public class Argb : BulkPixelOperationsTests { - // MemberData does not work without redeclaring the public field in the derived test class: - public static TheoryData ArraySizesData = new TheoryData { 7, 16, 1111 }; + // For 4.6 test runner MemberData does not work without redeclaring the public field in the derived test class: + public static new TheoryData ArraySizesData => new TheoryData { 7, 16, 1111 }; } } public abstract class BulkPixelOperationsTests where TColor : struct, IPixel { - // The field is private so it can be safely redeclared in inherited non-abstract test classes. - private static TheoryData ArraySizesData = new TheoryData { 7, 16, 1111 }; + public static TheoryData ArraySizesData => new TheoryData { 7, 16, 1111 }; [Theory] [MemberData(nameof(ArraySizesData))] From 4e54632d631417fd49b395401b8d96f95640f694 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 5 Mar 2017 19:25:31 +0100 Subject: [PATCH 137/142] BulkPixelOperationsTests.GetGlobalInstance() to make CodeCov happy --- .../Colors/BulkPixelOperationsTests.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs b/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs index 0e69a54f54..c179b01a10 100644 --- a/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs +++ b/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs @@ -5,7 +5,7 @@ using Xunit; - public abstract class BulkPixelOperationsTests + public class BulkPixelOperationsTests { public class Color : BulkPixelOperationsTests { @@ -18,6 +18,14 @@ // For 4.6 test runner MemberData does not work without redeclaring the public field in the derived test class: public static new TheoryData ArraySizesData => new TheoryData { 7, 16, 1111 }; } + + [Theory] + [WithBlankImages(1, 1, PixelTypes.All)] + public void GetGlobalInstance(TestImageProvider dummy) + where TColor:struct, IPixel + { + Assert.NotNull(BulkPixelOperations.Instance); + } } public abstract class BulkPixelOperationsTests From 4b11becfff2d487d122824ccb89a9a1acf44bccd Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 5 Mar 2017 21:18:31 +0100 Subject: [PATCH 138/142] IPixel.BulkOperations ---> IPixel.CreateBulkOperations() --- src/ImageSharp/Colors/Color.cs | 6 +++--- src/ImageSharp/Colors/PackedPixel/Alpha8.cs | 6 +++--- src/ImageSharp/Colors/PackedPixel/Argb.cs | 6 +++--- src/ImageSharp/Colors/PackedPixel/Bgr565.cs | 6 +++--- src/ImageSharp/Colors/PackedPixel/Bgra4444.cs | 6 +++--- src/ImageSharp/Colors/PackedPixel/Bgra5551.cs | 6 +++--- .../Colors/PackedPixel/BulkPixelOperations{TColor}.cs | 2 +- src/ImageSharp/Colors/PackedPixel/Byte4.cs | 6 +++--- src/ImageSharp/Colors/PackedPixel/HalfSingle.cs | 6 +++--- src/ImageSharp/Colors/PackedPixel/HalfVector2.cs | 6 +++--- src/ImageSharp/Colors/PackedPixel/HalfVector4.cs | 6 +++--- src/ImageSharp/Colors/PackedPixel/IPixel.cs | 6 ++++-- src/ImageSharp/Colors/PackedPixel/NormalizedByte2.cs | 6 +++--- src/ImageSharp/Colors/PackedPixel/NormalizedByte4.cs | 6 +++--- src/ImageSharp/Colors/PackedPixel/NormalizedShort2.cs | 6 +++--- src/ImageSharp/Colors/PackedPixel/NormalizedShort4.cs | 6 +++--- src/ImageSharp/Colors/PackedPixel/Rg32.cs | 6 +++--- src/ImageSharp/Colors/PackedPixel/Rgba1010102.cs | 6 +++--- src/ImageSharp/Colors/PackedPixel/Rgba64.cs | 6 +++--- src/ImageSharp/Colors/PackedPixel/Short2.cs | 6 +++--- src/ImageSharp/Colors/PackedPixel/Short4.cs | 6 +++--- 21 files changed, 62 insertions(+), 60 deletions(-) diff --git a/src/ImageSharp/Colors/Color.cs b/src/ImageSharp/Colors/Color.cs index 8a869935c1..1ad6f9a643 100644 --- a/src/ImageSharp/Colors/Color.cs +++ b/src/ImageSharp/Colors/Color.cs @@ -112,9 +112,6 @@ namespace ImageSharp this.packedValue = packed; } - /// - public BulkPixelOperations BulkOperations => new BulkPixelOperations(); - /// /// Gets or sets the red component. /// @@ -248,6 +245,9 @@ namespace ImageSharp return ColorBuilder.FromHex(hex); } + /// + public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromBytes(byte x, byte y, byte z, byte w) diff --git a/src/ImageSharp/Colors/PackedPixel/Alpha8.cs b/src/ImageSharp/Colors/PackedPixel/Alpha8.cs index 1181eb9e47..9a340544cf 100644 --- a/src/ImageSharp/Colors/PackedPixel/Alpha8.cs +++ b/src/ImageSharp/Colors/PackedPixel/Alpha8.cs @@ -26,9 +26,6 @@ namespace ImageSharp /// public byte PackedValue { get; set; } - /// - public BulkPixelOperations BulkOperations => new BulkPixelOperations(); - /// /// Compares two objects for equality. /// @@ -61,6 +58,9 @@ namespace ImageSharp return left.PackedValue != right.PackedValue; } + /// + public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromVector4(Vector4 vector) diff --git a/src/ImageSharp/Colors/PackedPixel/Argb.cs b/src/ImageSharp/Colors/PackedPixel/Argb.cs index 1b97d2337a..70fd7de8a7 100644 --- a/src/ImageSharp/Colors/PackedPixel/Argb.cs +++ b/src/ImageSharp/Colors/PackedPixel/Argb.cs @@ -109,9 +109,6 @@ namespace ImageSharp /// public uint PackedValue { get; set; } - /// - public BulkPixelOperations BulkOperations => new BulkPixelOperations(); - /// /// Gets or sets the red component. /// @@ -223,6 +220,9 @@ namespace ImageSharp this.PackedValue = Pack(ref vector); } + /// + public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ToVector4() diff --git a/src/ImageSharp/Colors/PackedPixel/Bgr565.cs b/src/ImageSharp/Colors/PackedPixel/Bgr565.cs index 41b2bdc2e0..77d9434785 100644 --- a/src/ImageSharp/Colors/PackedPixel/Bgr565.cs +++ b/src/ImageSharp/Colors/PackedPixel/Bgr565.cs @@ -39,9 +39,6 @@ namespace ImageSharp /// public ushort PackedValue { get; set; } - /// - public BulkPixelOperations BulkOperations => new BulkPixelOperations(); - /// /// Compares two objects for equality. /// @@ -70,6 +67,9 @@ namespace ImageSharp return left.PackedValue != right.PackedValue; } + /// + public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + /// /// Expands the packed representation into a . /// The vector components are typically expanded in least to greatest significance order. diff --git a/src/ImageSharp/Colors/PackedPixel/Bgra4444.cs b/src/ImageSharp/Colors/PackedPixel/Bgra4444.cs index 99659a36bc..1346a54efc 100644 --- a/src/ImageSharp/Colors/PackedPixel/Bgra4444.cs +++ b/src/ImageSharp/Colors/PackedPixel/Bgra4444.cs @@ -38,9 +38,6 @@ namespace ImageSharp /// public ushort PackedValue { get; set; } - /// - public BulkPixelOperations BulkOperations => new BulkPixelOperations(); - /// /// Compares two objects for equality. /// @@ -69,6 +66,9 @@ namespace ImageSharp return left.PackedValue != right.PackedValue; } + /// + public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ToVector4() diff --git a/src/ImageSharp/Colors/PackedPixel/Bgra5551.cs b/src/ImageSharp/Colors/PackedPixel/Bgra5551.cs index 86864fd485..7989804cfb 100644 --- a/src/ImageSharp/Colors/PackedPixel/Bgra5551.cs +++ b/src/ImageSharp/Colors/PackedPixel/Bgra5551.cs @@ -40,9 +40,6 @@ namespace ImageSharp /// public ushort PackedValue { get; set; } - /// - public BulkPixelOperations BulkOperations => new BulkPixelOperations(); - /// /// Compares two objects for equality. /// @@ -71,6 +68,9 @@ namespace ImageSharp return left.PackedValue != right.PackedValue; } + /// + public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ToVector4() diff --git a/src/ImageSharp/Colors/PackedPixel/BulkPixelOperations{TColor}.cs b/src/ImageSharp/Colors/PackedPixel/BulkPixelOperations{TColor}.cs index 31f872e42a..986994af7b 100644 --- a/src/ImageSharp/Colors/PackedPixel/BulkPixelOperations{TColor}.cs +++ b/src/ImageSharp/Colors/PackedPixel/BulkPixelOperations{TColor}.cs @@ -24,7 +24,7 @@ namespace ImageSharp /// /// Gets the global instance for the pixel type /// - public static BulkPixelOperations Instance { get; } = default(TColor).BulkOperations; + public static BulkPixelOperations Instance { get; } = default(TColor).CreateBulkOperations(); /// /// Bulk version of diff --git a/src/ImageSharp/Colors/PackedPixel/Byte4.cs b/src/ImageSharp/Colors/PackedPixel/Byte4.cs index 5712027d96..11ec5eaf4b 100644 --- a/src/ImageSharp/Colors/PackedPixel/Byte4.cs +++ b/src/ImageSharp/Colors/PackedPixel/Byte4.cs @@ -41,9 +41,6 @@ namespace ImageSharp /// public uint PackedValue { get; set; } - /// - public BulkPixelOperations BulkOperations => new BulkPixelOperations(); - /// /// Compares two objects for equality. /// @@ -72,6 +69,9 @@ namespace ImageSharp return left.PackedValue != right.PackedValue; } + /// + public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromVector4(Vector4 vector) diff --git a/src/ImageSharp/Colors/PackedPixel/HalfSingle.cs b/src/ImageSharp/Colors/PackedPixel/HalfSingle.cs index 0bc82c3a61..4c785a8636 100644 --- a/src/ImageSharp/Colors/PackedPixel/HalfSingle.cs +++ b/src/ImageSharp/Colors/PackedPixel/HalfSingle.cs @@ -36,9 +36,6 @@ namespace ImageSharp /// public ushort PackedValue { get; set; } - /// - public BulkPixelOperations BulkOperations => new BulkPixelOperations(); - /// /// Compares two objects for equality. /// @@ -75,6 +72,9 @@ namespace ImageSharp return left.PackedValue != right.PackedValue; } + /// + public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + /// /// Expands the packed representation into a . /// diff --git a/src/ImageSharp/Colors/PackedPixel/HalfVector2.cs b/src/ImageSharp/Colors/PackedPixel/HalfVector2.cs index 68cb427356..d06ab6ba07 100644 --- a/src/ImageSharp/Colors/PackedPixel/HalfVector2.cs +++ b/src/ImageSharp/Colors/PackedPixel/HalfVector2.cs @@ -46,9 +46,6 @@ namespace ImageSharp /// public uint PackedValue { get; set; } - /// - public BulkPixelOperations BulkOperations => new BulkPixelOperations(); - /// /// Compares two objects for equality. /// @@ -85,6 +82,9 @@ namespace ImageSharp return !left.Equals(right); } + /// + public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + /// /// Expands the packed representation into a . /// diff --git a/src/ImageSharp/Colors/PackedPixel/HalfVector4.cs b/src/ImageSharp/Colors/PackedPixel/HalfVector4.cs index c24553d3f3..a5fa796e1d 100644 --- a/src/ImageSharp/Colors/PackedPixel/HalfVector4.cs +++ b/src/ImageSharp/Colors/PackedPixel/HalfVector4.cs @@ -49,9 +49,6 @@ namespace ImageSharp /// public ulong PackedValue { get; set; } - /// - public BulkPixelOperations BulkOperations => new BulkPixelOperations(); - /// /// Compares two objects for equality. /// @@ -88,6 +85,9 @@ namespace ImageSharp return !left.Equals(right); } + /// + public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromVector4(Vector4 vector) diff --git a/src/ImageSharp/Colors/PackedPixel/IPixel.cs b/src/ImageSharp/Colors/PackedPixel/IPixel.cs index c17fe86ccf..67e013a422 100644 --- a/src/ImageSharp/Colors/PackedPixel/IPixel.cs +++ b/src/ImageSharp/Colors/PackedPixel/IPixel.cs @@ -16,9 +16,11 @@ namespace ImageSharp where TSelf : struct, IPixel { /// - /// Gets the instance for this pixel type. + /// Creates a instance for this pixel type. + /// This method is not intended to be consumed directly. Use instead. /// - BulkPixelOperations BulkOperations { get; } + /// The instance. + BulkPixelOperations CreateBulkOperations(); } /// diff --git a/src/ImageSharp/Colors/PackedPixel/NormalizedByte2.cs b/src/ImageSharp/Colors/PackedPixel/NormalizedByte2.cs index d425806c27..56be64a86c 100644 --- a/src/ImageSharp/Colors/PackedPixel/NormalizedByte2.cs +++ b/src/ImageSharp/Colors/PackedPixel/NormalizedByte2.cs @@ -51,9 +51,6 @@ namespace ImageSharp /// public ushort PackedValue { get; set; } - /// - public BulkPixelOperations BulkOperations => new BulkPixelOperations(); - /// /// Compares two objects for equality. /// @@ -90,6 +87,9 @@ namespace ImageSharp return left.PackedValue != right.PackedValue; } + /// + public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + /// /// Expands the packed representation into a . /// The vector components are typically expanded in least to greatest significance order. diff --git a/src/ImageSharp/Colors/PackedPixel/NormalizedByte4.cs b/src/ImageSharp/Colors/PackedPixel/NormalizedByte4.cs index 4511060e48..a1f9b8d847 100644 --- a/src/ImageSharp/Colors/PackedPixel/NormalizedByte4.cs +++ b/src/ImageSharp/Colors/PackedPixel/NormalizedByte4.cs @@ -53,9 +53,6 @@ namespace ImageSharp /// public uint PackedValue { get; set; } - /// - public BulkPixelOperations BulkOperations => new BulkPixelOperations(); - /// /// Compares two objects for equality. /// @@ -92,6 +89,9 @@ namespace ImageSharp return left.PackedValue != right.PackedValue; } + /// + public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromVector4(Vector4 vector) diff --git a/src/ImageSharp/Colors/PackedPixel/NormalizedShort2.cs b/src/ImageSharp/Colors/PackedPixel/NormalizedShort2.cs index 4bc7f94277..b34c1e88b7 100644 --- a/src/ImageSharp/Colors/PackedPixel/NormalizedShort2.cs +++ b/src/ImageSharp/Colors/PackedPixel/NormalizedShort2.cs @@ -51,9 +51,6 @@ namespace ImageSharp /// public uint PackedValue { get; set; } - /// - public BulkPixelOperations BulkOperations => new BulkPixelOperations(); - /// /// Compares two objects for equality. /// @@ -90,6 +87,9 @@ namespace ImageSharp return !left.Equals(right); } + /// + public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromVector4(Vector4 vector) diff --git a/src/ImageSharp/Colors/PackedPixel/NormalizedShort4.cs b/src/ImageSharp/Colors/PackedPixel/NormalizedShort4.cs index c848b72369..f33ac25a64 100644 --- a/src/ImageSharp/Colors/PackedPixel/NormalizedShort4.cs +++ b/src/ImageSharp/Colors/PackedPixel/NormalizedShort4.cs @@ -53,9 +53,6 @@ namespace ImageSharp /// public ulong PackedValue { get; set; } - /// - public BulkPixelOperations BulkOperations => new BulkPixelOperations(); - /// /// Compares two objects for equality. /// @@ -92,6 +89,9 @@ namespace ImageSharp return !left.Equals(right); } + /// + public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromVector4(Vector4 vector) diff --git a/src/ImageSharp/Colors/PackedPixel/Rg32.cs b/src/ImageSharp/Colors/PackedPixel/Rg32.cs index 9eb2247c9b..f8486f7f29 100644 --- a/src/ImageSharp/Colors/PackedPixel/Rg32.cs +++ b/src/ImageSharp/Colors/PackedPixel/Rg32.cs @@ -36,9 +36,6 @@ namespace ImageSharp /// public uint PackedValue { get; set; } - /// - public BulkPixelOperations BulkOperations => new BulkPixelOperations(); - /// /// Compares two objects for equality. /// @@ -75,6 +72,9 @@ namespace ImageSharp return left.PackedValue != right.PackedValue; } + /// + public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + /// /// Expands the packed representation into a . /// The vector components are typically expanded in least to greatest significance order. diff --git a/src/ImageSharp/Colors/PackedPixel/Rgba1010102.cs b/src/ImageSharp/Colors/PackedPixel/Rgba1010102.cs index 4f99feb6e5..56f3040703 100644 --- a/src/ImageSharp/Colors/PackedPixel/Rgba1010102.cs +++ b/src/ImageSharp/Colors/PackedPixel/Rgba1010102.cs @@ -39,9 +39,6 @@ namespace ImageSharp /// public uint PackedValue { get; set; } - /// - public BulkPixelOperations BulkOperations => new BulkPixelOperations(); - /// /// Compares two objects for equality. /// @@ -78,6 +75,9 @@ namespace ImageSharp return left.PackedValue != right.PackedValue; } + /// + public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ToVector4() diff --git a/src/ImageSharp/Colors/PackedPixel/Rgba64.cs b/src/ImageSharp/Colors/PackedPixel/Rgba64.cs index a23e57ec3d..816401d4e0 100644 --- a/src/ImageSharp/Colors/PackedPixel/Rgba64.cs +++ b/src/ImageSharp/Colors/PackedPixel/Rgba64.cs @@ -38,9 +38,6 @@ namespace ImageSharp /// public ulong PackedValue { get; set; } - /// - public BulkPixelOperations BulkOperations => new BulkPixelOperations(); - /// /// Compares two objects for equality. /// @@ -77,6 +74,9 @@ namespace ImageSharp return left.PackedValue != right.PackedValue; } + /// + public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ToVector4() diff --git a/src/ImageSharp/Colors/PackedPixel/Short2.cs b/src/ImageSharp/Colors/PackedPixel/Short2.cs index f26e825789..802df7c1d4 100644 --- a/src/ImageSharp/Colors/PackedPixel/Short2.cs +++ b/src/ImageSharp/Colors/PackedPixel/Short2.cs @@ -51,9 +51,6 @@ namespace ImageSharp /// public uint PackedValue { get; set; } - /// - public BulkPixelOperations BulkOperations => new BulkPixelOperations(); - /// /// Compares two objects for equality. /// @@ -90,6 +87,9 @@ namespace ImageSharp return left.PackedValue != right.PackedValue; } + /// + public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromVector4(Vector4 vector) diff --git a/src/ImageSharp/Colors/PackedPixel/Short4.cs b/src/ImageSharp/Colors/PackedPixel/Short4.cs index 6dc7545e1b..2517ef7a84 100644 --- a/src/ImageSharp/Colors/PackedPixel/Short4.cs +++ b/src/ImageSharp/Colors/PackedPixel/Short4.cs @@ -53,9 +53,6 @@ namespace ImageSharp /// public ulong PackedValue { get; set; } - /// - public BulkPixelOperations BulkOperations => new BulkPixelOperations(); - /// /// Compares two objects for equality. /// @@ -92,6 +89,9 @@ namespace ImageSharp return left.PackedValue != right.PackedValue; } + /// + public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromVector4(Vector4 vector) From a8ab5d4b89591bf865b9aac3136006c6083ca8e2 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Mon, 6 Mar 2017 01:15:07 +0100 Subject: [PATCH 139/142] Renamed BulkPixelOperation methods, removed ArrayExtensions and the unused dangerous PixelAccessor ctr. --- .../BulkPixelOperations{TColor}.cs | 10 +++---- .../Common/Extensions/ArrayExtensions.cs | 29 ------------------- .../Common/Memory/PinnedBuffer{T}.cs | 2 +- src/ImageSharp/Image/ImageBase{TColor}.cs | 3 -- src/ImageSharp/Image/PixelAccessor{TColor}.cs | 11 ------- .../ImageSharp.Sandbox46.csproj | 3 -- tests/ImageSharp.Sandbox46/Program.cs | 2 +- .../Colors/BulkPixelOperationsTests.cs | 10 +++---- .../PixelDataPoolTests.cs} | 0 9 files changed, 12 insertions(+), 58 deletions(-) delete mode 100644 src/ImageSharp/Common/Extensions/ArrayExtensions.cs rename tests/ImageSharp.Tests/{Image/PixelPoolTests.cs => Common/PixelDataPoolTests.cs} (100%) diff --git a/src/ImageSharp/Colors/PackedPixel/BulkPixelOperations{TColor}.cs b/src/ImageSharp/Colors/PackedPixel/BulkPixelOperations{TColor}.cs index 986994af7b..a84c3edf3e 100644 --- a/src/ImageSharp/Colors/PackedPixel/BulkPixelOperations{TColor}.cs +++ b/src/ImageSharp/Colors/PackedPixel/BulkPixelOperations{TColor}.cs @@ -58,7 +58,7 @@ namespace ImageSharp /// The to the source colors. /// The to the destination vectors. /// The number of pixels to convert. - internal virtual void PackToVector4( + internal virtual void ToVector4( ArrayPointer sourceColors, ArrayPointer destVectors, int count) @@ -105,7 +105,7 @@ namespace ImageSharp /// The to the source colors. /// The to the destination bytes. /// The number of pixels to convert. - internal virtual void PackToXyzBytes(ArrayPointer sourceColors, ArrayPointer destBytes, int count) + internal virtual void ToXyzBytes(ArrayPointer sourceColors, ArrayPointer destBytes, int count) { byte* sp = (byte*)sourceColors; byte[] dest = destBytes.Array; @@ -148,7 +148,7 @@ namespace ImageSharp /// The to the source colors. /// The to the destination bytes. /// The number of pixels to convert. - internal virtual void PackToXyzwBytes( + internal virtual void ToXyzwBytes( ArrayPointer sourceColors, ArrayPointer destBytes, int count) @@ -194,7 +194,7 @@ namespace ImageSharp /// The to the source colors. /// The to the destination bytes. /// The number of pixels to convert. - internal virtual void PackToZyxBytes(ArrayPointer sourceColors, ArrayPointer destBytes, int count) + internal virtual void ToZyxBytes(ArrayPointer sourceColors, ArrayPointer destBytes, int count) { byte* sp = (byte*)sourceColors; byte[] dest = destBytes.Array; @@ -237,7 +237,7 @@ namespace ImageSharp /// The to the source colors. /// The to the destination bytes. /// The number of pixels to convert. - internal virtual void PackToZyxwBytes( + internal virtual void ToZyxwBytes( ArrayPointer sourceColors, ArrayPointer destBytes, int count) diff --git a/src/ImageSharp/Common/Extensions/ArrayExtensions.cs b/src/ImageSharp/Common/Extensions/ArrayExtensions.cs deleted file mode 100644 index cce442c522..0000000000 --- a/src/ImageSharp/Common/Extensions/ArrayExtensions.cs +++ /dev/null @@ -1,29 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - using System; - - /// - /// Extension methods for arrays. - /// - public static class ArrayExtensions - { - /// - /// Locks the pixel buffer providing access to the pixels. - /// - /// The pixel format. - /// The pixel buffer. - /// Gets the width of the image represented by the pixel buffer. - /// The height of the image represented by the pixel buffer. - /// The - public static PixelAccessor Lock(this TColor[] pixels, int width, int height) - where TColor : struct, IPixel - { - return new PixelAccessor(width, height, pixels); - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs b/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs index d95d675571..04217a0123 100644 --- a/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs +++ b/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs @@ -23,7 +23,7 @@ namespace ImageSharp private GCHandle handle; /// - /// A value indicating wether this instance should return the array to the pool. + /// A value indicating whether this instance should return the array to the pool. /// private bool isPoolingOwner; diff --git a/src/ImageSharp/Image/ImageBase{TColor}.cs b/src/ImageSharp/Image/ImageBase{TColor}.cs index c2110eca4f..2badc008a2 100644 --- a/src/ImageSharp/Image/ImageBase{TColor}.cs +++ b/src/ImageSharp/Image/ImageBase{TColor}.cs @@ -163,9 +163,6 @@ namespace ImageSharp { Guard.NotNull(pixelSource, nameof(pixelSource)); - // TODO: Was this check really useful? If yes, we can define a bool PixelAccessor.IsBoundToImage to re-introduce it: - - // Guard.IsTrue(pixelSource.PooledMemory, nameof(pixelSource.PooledMemory), "pixelSource must be using pooled memory"); int newWidth = pixelSource.Width; int newHeight = pixelSource.Height; diff --git a/src/ImageSharp/Image/PixelAccessor{TColor}.cs b/src/ImageSharp/Image/PixelAccessor{TColor}.cs index 9201270d9d..e104b8ae77 100644 --- a/src/ImageSharp/Image/PixelAccessor{TColor}.cs +++ b/src/ImageSharp/Image/PixelAccessor{TColor}.cs @@ -53,17 +53,6 @@ namespace ImageSharp this.ParallelOptions = image.Configuration.ParallelOptions; } - /// - /// Initializes a new instance of the class. - /// - /// The width of the image represented by the pixel buffer. - /// The height of the image represented by the pixel buffer. - /// The pixel buffer. - public PixelAccessor(int width, int height, TColor[] pixels) - : this(width, height, new PinnedBuffer(width * height, pixels)) - { - } - /// /// Initializes a new instance of the class. /// diff --git a/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj b/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj index 094eedb180..1d78994860 100644 --- a/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj +++ b/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj @@ -251,9 +251,6 @@ Tests\Formats\Jpg\YCbCrImageTests.cs - - Tests\Image\PixelPoolTests.cs - Tests\MetaData\ImagePropertyTests.cs diff --git a/tests/ImageSharp.Sandbox46/Program.cs b/tests/ImageSharp.Sandbox46/Program.cs index 3afd180941..4d6d159255 100644 --- a/tests/ImageSharp.Sandbox46/Program.cs +++ b/tests/ImageSharp.Sandbox46/Program.cs @@ -37,7 +37,7 @@ namespace ImageSharp.Sandbox46 /// public static void Main(string[] args) { - //RunDecodeJpegProfilingTests(); + // RunDecodeJpegProfilingTests(); TestPixelAccessorCopyFromXyzw(); Console.ReadLine(); } diff --git a/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs b/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs index c179b01a10..1c22411d09 100644 --- a/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs +++ b/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs @@ -67,7 +67,7 @@ TestOperation( source, expected, - (ops, s, d) => ops.PackToVector4(s, d, count) + (ops, s, d) => ops.ToVector4(s, d, count) ); } @@ -109,7 +109,7 @@ TestOperation( source, expected, - (ops, s, d) => ops.PackToXyzBytes(s, d, count) + (ops, s, d) => ops.ToXyzBytes(s, d, count) ); } @@ -150,7 +150,7 @@ TestOperation( source, expected, - (ops, s, d) => ops.PackToXyzwBytes(s, d, count) + (ops, s, d) => ops.ToXyzwBytes(s, d, count) ); } @@ -191,7 +191,7 @@ TestOperation( source, expected, - (ops, s, d) => ops.PackToZyxBytes(s, d, count) + (ops, s, d) => ops.ToZyxBytes(s, d, count) ); } @@ -232,7 +232,7 @@ TestOperation( source, expected, - (ops, s, d) => ops.PackToZyxwBytes(s, d, count) + (ops, s, d) => ops.ToZyxwBytes(s, d, count) ); } diff --git a/tests/ImageSharp.Tests/Image/PixelPoolTests.cs b/tests/ImageSharp.Tests/Common/PixelDataPoolTests.cs similarity index 100% rename from tests/ImageSharp.Tests/Image/PixelPoolTests.cs rename to tests/ImageSharp.Tests/Common/PixelDataPoolTests.cs From 099697cf0a31e4ddc81666a579ef7c32634edf62 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Mon, 6 Mar 2017 21:47:45 +0100 Subject: [PATCH 140/142] renamed ArrayPointer to BufferPointer --- .../BulkPixelOperations{TColor}.cs | 76 +++++++++---------- .../{ArrayPointer.cs => BufferPointer.cs} | 24 +++--- ...ArrayPointer{T}.cs => BufferPointer{T}.cs} | 40 +++++----- .../Color/Bulk/PixelAccessorVirtualCopy.cs | 24 +++--- .../Colors/BulkPixelOperationsTests.cs | 6 +- ...yPointerTests.cs => BufferPointerTests.cs} | 26 +++---- .../Common/PinnedBufferTests.cs | 4 +- 7 files changed, 100 insertions(+), 100 deletions(-) rename src/ImageSharp/Common/Memory/{ArrayPointer.cs => BufferPointer.cs} (79%) rename src/ImageSharp/Common/Memory/{ArrayPointer{T}.cs => BufferPointer{T}.cs} (66%) rename tests/ImageSharp.Tests/Common/{ArrayPointerTests.cs => BufferPointerTests.cs} (81%) diff --git a/src/ImageSharp/Colors/PackedPixel/BulkPixelOperations{TColor}.cs b/src/ImageSharp/Colors/PackedPixel/BulkPixelOperations{TColor}.cs index a84c3edf3e..259b1c9b47 100644 --- a/src/ImageSharp/Colors/PackedPixel/BulkPixelOperations{TColor}.cs +++ b/src/ImageSharp/Colors/PackedPixel/BulkPixelOperations{TColor}.cs @@ -29,12 +29,12 @@ namespace ImageSharp /// /// Bulk version of /// - /// The to the source vectors. - /// The to the destination colors. + /// The to the source vectors. + /// The to the destination colors. /// The number of pixels to convert. internal virtual void PackFromVector4( - ArrayPointer sourceVectors, - ArrayPointer destColors, + BufferPointer sourceVectors, + BufferPointer destColors, int count) { Vector4* sp = (Vector4*)sourceVectors.PointerAtOffset; @@ -55,12 +55,12 @@ namespace ImageSharp /// /// Bulk version of . /// - /// The to the source colors. - /// The to the destination vectors. + /// The to the source colors. + /// The to the destination vectors. /// The number of pixels to convert. internal virtual void ToVector4( - ArrayPointer sourceColors, - ArrayPointer destVectors, + BufferPointer sourceColors, + BufferPointer destVectors, int count) { byte* sp = (byte*)sourceColors; @@ -78,12 +78,12 @@ namespace ImageSharp /// /// Bulk version of that converts data in . /// - /// The to the source bytes. - /// The to the destination colors. + /// The to the source bytes. + /// The to the destination colors. /// The number of pixels to convert. internal virtual void PackFromXyzBytes( - ArrayPointer sourceBytes, - ArrayPointer destColors, + BufferPointer sourceBytes, + BufferPointer destColors, int count) { byte* sp = (byte*)sourceBytes; @@ -102,10 +102,10 @@ namespace ImageSharp /// /// Bulk version of . /// - /// The to the source colors. - /// The to the destination bytes. + /// The to the source colors. + /// The to the destination bytes. /// The number of pixels to convert. - internal virtual void ToXyzBytes(ArrayPointer sourceColors, ArrayPointer destBytes, int count) + internal virtual void ToXyzBytes(BufferPointer sourceColors, BufferPointer destBytes, int count) { byte* sp = (byte*)sourceColors; byte[] dest = destBytes.Array; @@ -121,12 +121,12 @@ namespace ImageSharp /// /// Bulk version of that converts data in . /// - /// The to the source bytes. - /// The to the destination colors. + /// The to the source bytes. + /// The to the destination colors. /// The number of pixels to convert. internal virtual void PackFromXyzwBytes( - ArrayPointer sourceBytes, - ArrayPointer destColors, + BufferPointer sourceBytes, + BufferPointer destColors, int count) { byte* sp = (byte*)sourceBytes; @@ -145,12 +145,12 @@ namespace ImageSharp /// /// Bulk version of . /// - /// The to the source colors. - /// The to the destination bytes. + /// The to the source colors. + /// The to the destination bytes. /// The number of pixels to convert. internal virtual void ToXyzwBytes( - ArrayPointer sourceColors, - ArrayPointer destBytes, + BufferPointer sourceColors, + BufferPointer destBytes, int count) { byte* sp = (byte*)sourceColors; @@ -167,12 +167,12 @@ namespace ImageSharp /// /// Bulk version of that converts data in . /// - /// The to the source bytes. - /// The to the destination colors. + /// The to the source bytes. + /// The to the destination colors. /// The number of pixels to convert. internal virtual void PackFromZyxBytes( - ArrayPointer sourceBytes, - ArrayPointer destColors, + BufferPointer sourceBytes, + BufferPointer destColors, int count) { byte* sp = (byte*)sourceBytes; @@ -191,10 +191,10 @@ namespace ImageSharp /// /// Bulk version of . /// - /// The to the source colors. - /// The to the destination bytes. + /// The to the source colors. + /// The to the destination bytes. /// The number of pixels to convert. - internal virtual void ToZyxBytes(ArrayPointer sourceColors, ArrayPointer destBytes, int count) + internal virtual void ToZyxBytes(BufferPointer sourceColors, BufferPointer destBytes, int count) { byte* sp = (byte*)sourceColors; byte[] dest = destBytes.Array; @@ -210,12 +210,12 @@ namespace ImageSharp /// /// Bulk version of that converts data in . /// - /// The to the source bytes. - /// The to the destination colors. + /// The to the source bytes. + /// The to the destination colors. /// The number of pixels to convert. internal virtual void PackFromZyxwBytes( - ArrayPointer sourceBytes, - ArrayPointer destColors, + BufferPointer sourceBytes, + BufferPointer destColors, int count) { byte* sp = (byte*)sourceBytes; @@ -234,12 +234,12 @@ namespace ImageSharp /// /// Bulk version of . /// - /// The to the source colors. - /// The to the destination bytes. + /// The to the source colors. + /// The to the destination bytes. /// The number of pixels to convert. internal virtual void ToZyxwBytes( - ArrayPointer sourceColors, - ArrayPointer destBytes, + BufferPointer sourceColors, + BufferPointer destBytes, int count) { byte* sp = (byte*)sourceColors; diff --git a/src/ImageSharp/Common/Memory/ArrayPointer.cs b/src/ImageSharp/Common/Memory/BufferPointer.cs similarity index 79% rename from src/ImageSharp/Common/Memory/ArrayPointer.cs rename to src/ImageSharp/Common/Memory/BufferPointer.cs index 342eaa6c87..c80e22e21d 100644 --- a/src/ImageSharp/Common/Memory/ArrayPointer.cs +++ b/src/ImageSharp/Common/Memory/BufferPointer.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // @@ -8,32 +8,32 @@ namespace ImageSharp using System.Runtime.CompilerServices; /// - /// Utility methods to + /// Utility methods for /// - internal static class ArrayPointer + internal static class BufferPointer { /// - /// Gets an to the beginning of the raw data in 'buffer'. + /// Gets a to the beginning of the raw data in 'buffer'. /// /// The element type /// The input - /// The + /// The [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe ArrayPointer GetArrayPointer(this PinnedBuffer buffer) + public static unsafe BufferPointer Slice(this PinnedBuffer buffer) where T : struct { - return new ArrayPointer(buffer.Array, (void*)buffer.Pointer); + return new BufferPointer(buffer.Array, (void*)buffer.Pointer); } /// /// Copy 'count' number of elements of the same type from 'source' to 'dest' /// /// The element type. - /// The input - /// The destination . + /// The input + /// The destination . /// The number of elements to copy [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe void Copy(ArrayPointer source, ArrayPointer destination, int count) + public static unsafe void Copy(BufferPointer source, BufferPointer destination, int count) where T : struct { Unsafe.CopyBlock((void*)source.PointerAtOffset, (void*)destination.PointerAtOffset, USizeOf(count)); @@ -47,7 +47,7 @@ namespace ImageSharp /// The destination buffer. /// The number of elements to copy from 'source' [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe void Copy(ArrayPointer source, ArrayPointer destination, int countInSource) + public static unsafe void Copy(BufferPointer source, BufferPointer destination, int countInSource) where T : struct { Unsafe.CopyBlock((void*)source.PointerAtOffset, (void*)destination.PointerAtOffset, USizeOf(countInSource)); @@ -61,7 +61,7 @@ namespace ImageSharp /// The destination buffer"/> /// The number of elements to copy. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe void Copy(ArrayPointer source, ArrayPointer destination, int countInDest) + public static unsafe void Copy(BufferPointer source, BufferPointer destination, int countInDest) where T : struct { Unsafe.CopyBlock((void*)source.PointerAtOffset, (void*)destination.PointerAtOffset, USizeOf(countInDest)); diff --git a/src/ImageSharp/Common/Memory/ArrayPointer{T}.cs b/src/ImageSharp/Common/Memory/BufferPointer{T}.cs similarity index 66% rename from src/ImageSharp/Common/Memory/ArrayPointer{T}.cs rename to src/ImageSharp/Common/Memory/BufferPointer{T}.cs index 9cbbaf0946..fff4e513e1 100644 --- a/src/ImageSharp/Common/Memory/ArrayPointer{T}.cs +++ b/src/ImageSharp/Common/Memory/BufferPointer{T}.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // @@ -15,21 +15,21 @@ namespace ImageSharp /// - It's not possible to use it with stack objects or pointers to unmanaged memory, only with managed arrays /// - It's possible to retrieve a reference to the array () so we can pass it to API-s like /// - There is no bounds checking for performance reasons. Therefore we don't need to store length. (However this could be added as DEBUG-only feature.) - /// This makes an unsafe type! - /// - Currently the arrays provided to ArrayPointer need to be pinned. This behaviour could be changed using C#7 features. + /// This makes an unsafe type! + /// - Currently the arrays provided to BufferPointer need to be pinned. This behaviour could be changed using C#7 features. /// /// The type of elements of the array - internal unsafe struct ArrayPointer + internal unsafe struct BufferPointer where T : struct { /// - /// Initializes a new instance of the struct from a pinned array and an offset. + /// Initializes a new instance of the struct from a pinned array and an offset. /// /// The pinned array /// Pointer to the beginning of array /// The offset inside the array [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ArrayPointer(T[] array, void* pointerToArray, int offset) + public BufferPointer(T[] array, void* pointerToArray, int offset) { DebugGuard.NotNull(array, nameof(array)); @@ -39,12 +39,12 @@ namespace ImageSharp } /// - /// Initializes a new instance of the struct from a pinned array. + /// Initializes a new instance of the struct from a pinned array. /// /// The pinned array /// Pointer to the start of 'array' [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ArrayPointer(T[] array, void* pointerToArray) + public BufferPointer(T[] array, void* pointerToArray) { DebugGuard.NotNull(array, nameof(array)); @@ -69,34 +69,34 @@ namespace ImageSharp public IntPtr PointerAtOffset { get; private set; } /// - /// Convertes instance to a raw 'void*' pointer + /// Convertes instance to a raw 'void*' pointer /// - /// The to convert + /// The to convert [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static explicit operator void*(ArrayPointer arrayPointer) + public static explicit operator void*(BufferPointer bufferPointer) { - return (void*)arrayPointer.PointerAtOffset; + return (void*)bufferPointer.PointerAtOffset; } /// - /// Convertes instance to a raw 'byte*' pointer + /// Convertes instance to a raw 'byte*' pointer /// - /// The to convert + /// The to convert [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static explicit operator byte*(ArrayPointer arrayPointer) + public static explicit operator byte*(BufferPointer bufferPointer) { - return (byte*)arrayPointer.PointerAtOffset; + return (byte*)bufferPointer.PointerAtOffset; } /// - /// Forms a slice out of the given ArrayPointer, beginning at 'offset'. + /// Forms a slice out of the given BufferPointer, beginning at 'offset'. /// /// The offset in number of elements - /// The offseted (sliced) ArrayPointer + /// The offseted (sliced) BufferPointer [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ArrayPointer Slice(int offset) + public BufferPointer Slice(int offset) { - ArrayPointer result = default(ArrayPointer); + BufferPointer result = default(BufferPointer); result.Array = this.Array; result.Offset = this.Offset + offset; result.PointerAtOffset = this.PointerAtOffset + (Unsafe.SizeOf() * offset); diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/PixelAccessorVirtualCopy.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/PixelAccessorVirtualCopy.cs index 9222d6bac2..694a26f3dd 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/PixelAccessorVirtualCopy.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/PixelAccessorVirtualCopy.cs @@ -19,13 +19,13 @@ namespace ImageSharp.Benchmarks.Color.Bulk { abstract class CopyExecutor { - internal abstract void VirtualCopy(ArrayPointer destination, ArrayPointer source, int count); + internal abstract void VirtualCopy(BufferPointer destination, BufferPointer source, int count); } class UnsafeCopyExecutor : CopyExecutor { [MethodImpl(MethodImplOptions.NoInlining)] - internal override unsafe void VirtualCopy(ArrayPointer destination, ArrayPointer source, int count) + internal override unsafe void VirtualCopy(BufferPointer destination, BufferPointer source, int count) { Unsafe.CopyBlock((void*)destination.PointerAtOffset, (void*)source.PointerAtOffset, (uint)count*4); } @@ -76,7 +76,7 @@ namespace ImageSharp.Benchmarks.Color.Bulk } [Benchmark] - public void CopyArrayPointerUnsafeInlined() + public void CopyBufferPointerUnsafeInlined() { uint byteCount = (uint)this.area.Width * 4; @@ -85,22 +85,22 @@ namespace ImageSharp.Benchmarks.Color.Bulk for (int y = 0; y < this.Height; y++) { - ArrayPointer source = this.GetAreaRow(y); - ArrayPointer destination = this.GetPixelAccessorRow(targetX, targetY + y); + BufferPointer source = this.GetAreaRow(y); + BufferPointer destination = this.GetPixelAccessorRow(targetX, targetY + y); Unsafe.CopyBlock((void*)destination.PointerAtOffset, (void*)source.PointerAtOffset, byteCount); } } [Benchmark] - public void CopyArrayPointerUnsafeVirtual() + public void CopyBufferPointerUnsafeVirtual() { int targetX = this.Width / 4; int targetY = 0; for (int y = 0; y < this.Height; y++) { - ArrayPointer source = this.GetAreaRow(y); - ArrayPointer destination = this.GetPixelAccessorRow(targetX, targetY + y); + BufferPointer source = this.GetAreaRow(y); + BufferPointer destination = this.GetPixelAccessorRow(targetX, targetY + y); this.executor.VirtualCopy(destination, source, this.area.Width); } } @@ -111,9 +111,9 @@ namespace ImageSharp.Benchmarks.Color.Bulk } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private ArrayPointer GetPixelAccessorRow(int x, int y) + private BufferPointer GetPixelAccessorRow(int x, int y) { - return new ArrayPointer( + return new BufferPointer( this.pixelAccessor.PixelBuffer, (void*)this.pixelAccessor.DataPointer, (y * this.pixelAccessor.Width) + x @@ -121,9 +121,9 @@ namespace ImageSharp.Benchmarks.Color.Bulk } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private ArrayPointer GetAreaRow(int y) + private BufferPointer GetAreaRow(int y) { - return new ArrayPointer(this.area.Bytes, this.area.PixelBase, y * this.area.RowStride); + return new BufferPointer(this.area.Bytes, this.area.PixelBase, y * this.area.RowStride); } } } diff --git a/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs b/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs index 1c22411d09..80d5952a1e 100644 --- a/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs +++ b/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs @@ -245,8 +245,8 @@ public PinnedBuffer ActualDestBuffer { get; } public PinnedBuffer ExpectedDestBuffer { get; } - public ArrayPointer Source => this.SourceBuffer.GetArrayPointer(); - public ArrayPointer ActualDest => this.ActualDestBuffer.GetArrayPointer(); + public BufferPointer Source => this.SourceBuffer.Slice(); + public BufferPointer ActualDest => this.ActualDestBuffer.Slice(); public TestBuffers(TSource[] source, TDest[] expectedDest) { @@ -277,7 +277,7 @@ private static void TestOperation( TSource[] source, TDest[] expected, - Action, ArrayPointer, ArrayPointer> action) + Action, BufferPointer, BufferPointer> action) where TSource : struct where TDest : struct { diff --git a/tests/ImageSharp.Tests/Common/ArrayPointerTests.cs b/tests/ImageSharp.Tests/Common/BufferPointerTests.cs similarity index 81% rename from tests/ImageSharp.Tests/Common/ArrayPointerTests.cs rename to tests/ImageSharp.Tests/Common/BufferPointerTests.cs index 916a109475..4b5847d53f 100644 --- a/tests/ImageSharp.Tests/Common/ArrayPointerTests.cs +++ b/tests/ImageSharp.Tests/Common/BufferPointerTests.cs @@ -7,7 +7,7 @@ namespace ImageSharp.Tests.Common using Xunit; - public unsafe class ArrayPointerTests + public unsafe class BufferPointerTests { public struct Foo { @@ -33,7 +33,7 @@ namespace ImageSharp.Tests.Common fixed (Foo* p = array) { // Act: - ArrayPointer ap = new ArrayPointer(array, p); + BufferPointer ap = new BufferPointer(array, p); // Assert: Assert.Equal(array, ap.Array); @@ -49,7 +49,7 @@ namespace ImageSharp.Tests.Common fixed (Foo* p = array) { // Act: - ArrayPointer ap = new ArrayPointer(array, p, offset); + BufferPointer ap = new BufferPointer(array, p, offset); // Assert: Assert.Equal(array, ap.Array); @@ -67,7 +67,7 @@ namespace ImageSharp.Tests.Common int totalOffset = offset0 + offset1; fixed (Foo* p = array) { - ArrayPointer ap = new ArrayPointer(array, p, offset0); + BufferPointer ap = new BufferPointer(array, p, offset0); // Act: ap = ap.Slice(offset1); @@ -92,10 +92,10 @@ namespace ImageSharp.Tests.Common fixed (Foo* pSource = source) fixed (Foo* pDest = dest) { - ArrayPointer apSource = new ArrayPointer(source, pSource); - ArrayPointer apDest = new ArrayPointer(dest, pDest); + BufferPointer apSource = new BufferPointer(source, pSource); + BufferPointer apDest = new BufferPointer(dest, pDest); - ArrayPointer.Copy(apSource, apDest, count); + BufferPointer.Copy(apSource, apDest, count); } Assert.Equal(source[0], dest[0]); @@ -115,10 +115,10 @@ namespace ImageSharp.Tests.Common fixed (Foo* pSource = source) fixed (byte* pDest = dest) { - ArrayPointer apSource = new ArrayPointer(source, pSource); - ArrayPointer apDest = new ArrayPointer(dest, pDest); + BufferPointer apSource = new BufferPointer(source, pSource); + BufferPointer apDest = new BufferPointer(dest, pDest); - ArrayPointer.Copy(apSource, apDest, count); + BufferPointer.Copy(apSource, apDest, count); } Assert.True(ElementsAreEqual(source, dest, 0)); @@ -138,10 +138,10 @@ namespace ImageSharp.Tests.Common fixed(byte* pSource = source) fixed (Foo* pDest = dest) { - ArrayPointer apSource = new ArrayPointer(source, pSource); - ArrayPointer apDest = new ArrayPointer(dest, pDest); + BufferPointer apSource = new BufferPointer(source, pSource); + BufferPointer apDest = new BufferPointer(dest, pDest); - ArrayPointer.Copy(apSource, apDest, count); + BufferPointer.Copy(apSource, apDest, count); } Assert.True(ElementsAreEqual(dest, source, 0)); diff --git a/tests/ImageSharp.Tests/Common/PinnedBufferTests.cs b/tests/ImageSharp.Tests/Common/PinnedBufferTests.cs index c5eb2a5103..65077ae7f3 100644 --- a/tests/ImageSharp.Tests/Common/PinnedBufferTests.cs +++ b/tests/ImageSharp.Tests/Common/PinnedBufferTests.cs @@ -57,13 +57,13 @@ } [Fact] - public void GetArrayPointer() + public void Slice() { Foo[] a = { new Foo() { A = 1, B = 2 }, new Foo() { A = 3, B = 4 } }; using (PinnedBuffer buffer = new PinnedBuffer(a)) { - var arrayPtr = buffer.GetArrayPointer(); + var arrayPtr = buffer.Slice(); Assert.Equal(a, arrayPtr.Array); Assert.Equal(0, arrayPtr.Offset); From e04920416016a710fee81bf9fafe34423f4ceeaa Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 8 Mar 2017 22:07:23 +1100 Subject: [PATCH 141/142] Fix #127 Stop processing once we hit the end marker. --- src/ImageSharp.Formats.Png/PngDecoderCore.cs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/ImageSharp.Formats.Png/PngDecoderCore.cs b/src/ImageSharp.Formats.Png/PngDecoderCore.cs index a7765342e9..fd03ed39b8 100644 --- a/src/ImageSharp.Formats.Png/PngDecoderCore.cs +++ b/src/ImageSharp.Formats.Png/PngDecoderCore.cs @@ -109,6 +109,11 @@ namespace ImageSharp.Formats /// private byte[] paletteAlpha; + /// + /// A value indicating whether the end chunk has been reached. + /// + private bool isEndChunkReached; + /// /// Initializes static members of the class. /// @@ -158,18 +163,11 @@ namespace ImageSharp.Formats this.currentStream = stream; this.currentStream.Skip(8); - bool isEndChunkReached = false; - using (MemoryStream dataStream = new MemoryStream()) { PngChunk currentChunk; - while ((currentChunk = this.ReadChunk()) != null) + while (!this.isEndChunkReached && (currentChunk = this.ReadChunk()) != null) { - if (isEndChunkReached) - { - throw new ImageFormatException("Image does not end with end chunk."); - } - try { switch (currentChunk.Type) @@ -199,7 +197,7 @@ namespace ImageSharp.Formats this.ReadTextChunk(currentImage, currentChunk.Data, currentChunk.Length); break; case PngChunkTypes.End: - isEndChunkReached = true; + this.isEndChunkReached = true; break; } } From 98dd13b935d980177cdf22cb98a065aec6057153 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 9 Mar 2017 21:37:41 +1100 Subject: [PATCH 142/142] Resize should respect source rectangle Fix #118 --- .../Transforms/CompandingResizeProcessor.cs | 12 ++++++----- .../Processors/Transforms/ResizeProcessor.cs | 12 ++++++----- .../Processors/Filters/ResizeTests.cs | 21 +++++++++++++++++++ 3 files changed, 35 insertions(+), 10 deletions(-) diff --git a/src/ImageSharp.Processing/Processors/Transforms/CompandingResizeProcessor.cs b/src/ImageSharp.Processing/Processors/Transforms/CompandingResizeProcessor.cs index 53da214831..2190254f0d 100644 --- a/src/ImageSharp.Processing/Processors/Transforms/CompandingResizeProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Transforms/CompandingResizeProcessor.cs @@ -56,6 +56,8 @@ namespace ImageSharp.Processing.Processors int width = this.Width; int height = this.Height; + int sourceX = sourceRectangle.X; + int sourceY = sourceRectangle.Y; int startY = this.ResizeRectangle.Y; int endY = this.ResizeRectangle.Bottom; int startX = this.ResizeRectangle.X; @@ -83,12 +85,12 @@ namespace ImageSharp.Processing.Processors y => { // Y coordinates of source points - int originY = (int)((y - startY) * heightFactor); + int originY = (int)(((y - startY) * heightFactor) + sourceY); for (int x = minX; x < maxX; x++) { // X coordinates of source points - targetPixels[x, y] = sourcePixels[(int)((x - startX) * widthFactor), originY]; + targetPixels[x, y] = sourcePixels[(int)(((x - startX) * widthFactor) + sourceX), originY]; } }); } @@ -110,7 +112,7 @@ namespace ImageSharp.Processing.Processors { Parallel.For( 0, - sourceRectangle.Height, + sourceRectangle.Bottom, this.ParallelOptions, y => { @@ -125,7 +127,7 @@ namespace ImageSharp.Processing.Processors for (int i = 0; i < horizontalValues.Length; i++) { Weight xw = horizontalValues[i]; - destination += sourcePixels[xw.Index, y].ToVector4().Expand() * xw.Value; + destination += sourcePixels[xw.Index + sourceX, y].ToVector4().Expand() * xw.Value; } TColor d = default(TColor); @@ -152,7 +154,7 @@ namespace ImageSharp.Processing.Processors for (int i = 0; i < verticalValues.Length; i++) { Weight yw = verticalValues[i]; - destination += firstPassPixels[x, yw.Index].ToVector4().Expand() * yw.Value; + destination += firstPassPixels[x, yw.Index + sourceY].ToVector4().Expand() * yw.Value; } TColor d = default(TColor); diff --git a/src/ImageSharp.Processing/Processors/Transforms/ResizeProcessor.cs b/src/ImageSharp.Processing/Processors/Transforms/ResizeProcessor.cs index f4ec39f785..9ec804aa4f 100644 --- a/src/ImageSharp.Processing/Processors/Transforms/ResizeProcessor.cs +++ b/src/ImageSharp.Processing/Processors/Transforms/ResizeProcessor.cs @@ -55,6 +55,8 @@ namespace ImageSharp.Processing.Processors int width = this.Width; int height = this.Height; + int sourceX = sourceRectangle.X; + int sourceY = sourceRectangle.Y; int startY = this.ResizeRectangle.Y; int endY = this.ResizeRectangle.Bottom; int startX = this.ResizeRectangle.X; @@ -82,12 +84,12 @@ namespace ImageSharp.Processing.Processors y => { // Y coordinates of source points - int originY = (int)((y - startY) * heightFactor); + int originY = (int)(((y - startY) * heightFactor) + sourceY); for (int x = minX; x < maxX; x++) { // X coordinates of source points - targetPixels[x, y] = sourcePixels[(int)((x - startX) * widthFactor), originY]; + targetPixels[x, y] = sourcePixels[(int)(((x - startX) * widthFactor) + sourceX), originY]; } }); } @@ -109,7 +111,7 @@ namespace ImageSharp.Processing.Processors { Parallel.For( 0, - sourceRectangle.Height, + sourceRectangle.Bottom, this.ParallelOptions, y => { @@ -124,7 +126,7 @@ namespace ImageSharp.Processing.Processors for (int i = 0; i < horizontalValues.Length; i++) { Weight xw = horizontalValues[i]; - destination += sourcePixels[xw.Index, y].ToVector4() * xw.Value; + destination += sourcePixels[xw.Index + sourceX, y].ToVector4() * xw.Value; } TColor d = default(TColor); @@ -151,7 +153,7 @@ namespace ImageSharp.Processing.Processors for (int i = 0; i < verticalValues.Length; i++) { Weight yw = verticalValues[i]; - destination += firstPassPixels[x, yw.Index].ToVector4() * yw.Value; + destination += firstPassPixels[x, yw.Index + sourceY].ToVector4() * yw.Value; } TColor d = default(TColor); diff --git a/tests/ImageSharp.Tests/Processors/Filters/ResizeTests.cs b/tests/ImageSharp.Tests/Processors/Filters/ResizeTests.cs index 5acbe0f3eb..06ab245c90 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/ResizeTests.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/ResizeTests.cs @@ -50,6 +50,27 @@ namespace ImageSharp.Tests } } + [Theory] + [MemberData(nameof(ReSamplers))] + public void ImageShouldResizeFromSourceRectangle(string name, IResampler sampler) + { + name = $"{name}-SourceRect"; + + string path = this.CreateOutputDirectory("Resize"); + + foreach (TestFile file in Files) + { + string filename = file.GetFileName(name); + using (Image image = file.CreateImage()) + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + var sourceRectangle = new Rectangle(image.Width / 8, image.Height / 8, image.Width / 4, image.Height / 4); + var destRectangle = new Rectangle(image.Width / 4, image.Height / 4, image.Width / 2, image.Height / 2); + image.Resize(image.Width, image.Height, sampler, sourceRectangle, destRectangle, false).Save(output); + } + } + } + [Theory] [MemberData(nameof(ReSamplers))] public void ImageShouldResizeWidthAndKeepAspect(string name, IResampler sampler)