diff --git a/README.md b/README.md
index 86ce5ddd4..6d37dd5e7 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,13 @@
-#
+#
ImageSharp
-**ImageSharp** is a new cross-platform 2D graphics API designed to allow the processing of images without the use of `System.Drawing`.
+**ImageSharp** is a new, fully featured, fully managed, cross-platform, 2D graphics API designed to allow the processing of images without the use of `System.Drawing`.
-> **ImageSharp is still in early stages (alpha) but progress has been pretty quick. As such, please do not use on production environments until the library reaches release candidate status. Pre-release downloads are available from the [MyGet package repository](https://www.myget.org/gallery/imagesharp).**
+Built against .Net Standard 1.1 ImageSharp can be used in device, cloud, and embedded/IoT scenarios.
+
+> **ImageSharp** has made excellent progress and contains many great features but is still considered by us to be still in early stages (alpha). As such, we cannot support its use on production environments until the library reaches release candidate status.
+>
+> Pre-release downloads are available from the [MyGet package repository](https://www.myget.org/gallery/imagesharp).
[](https://raw.githubusercontent.com/JimBobSquarePants/ImageSharp/master/APACHE-2.0-LICENSE.txt)
[](https://github.com/JimBobSquarePants/ImageSharp/issues)
@@ -11,6 +15,9 @@
[](https://github.com/JimBobSquarePants/ImageSharp/network)
[](https://gitter.im/ImageSharp/General?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[](https://twitter.com/intent/tweet?hashtags=imagesharp,dotnet,oss&text=ImageSharp.+A+new+cross-platform+2D+graphics+API+in+C%23&url=https%3a%2f%2fgithub.com%2fJimBobSquarePants%2fImageSharp&via=james_m_south)
+[](#backers)
+[](#sponsors)
+
| |Build Status|Code Coverage|
@@ -66,7 +73,9 @@ There's plenty there and more coming. Check out the [current features](features.
### API
-Without the constraints of `System.Drawing` We have been able to develop something much more flexible, easier to code against, and much, much less prone to memory leaks. Gone are system-wide process-locks. Images and processors are thread safe usable in parallel processing utilizing all the availables cores.
+Without the constraints of `System.Drawing` We have been able to develop something much more flexible, easier to code against, and much, much less prone to memory leaks.
+
+Gone are system-wide process-locks; ImageSharp images are thread-safe and fully supported in web environments.
Many `Image` methods are also fluent.
@@ -126,3 +135,73 @@ Core Team
- [Anton Firsov](https://github.com/antonfirsov)
- [Olivia Ifrim](https://github.com/olivif)
- [Scott Williams](https://github.com/tocsoft)
+
+### Backers
+
+Support us with a monthly donation and help us continue our activities. [[Become a backer](https://opencollective.com/imagesharp#backer)]
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+### Sponsors
+
+Become a sponsor and get your logo on our README on Github with a link to your site. [[Become a sponsor](https://opencollective.com/imagesharp#sponsor)]
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/build/icons/imagesharp-logo-128.png b/build/icons/imagesharp-logo-128.png
index 6d05c222c..3fd4963dd 100644
Binary files a/build/icons/imagesharp-logo-128.png and b/build/icons/imagesharp-logo-128.png differ
diff --git a/build/icons/imagesharp-logo-256.png b/build/icons/imagesharp-logo-256.png
index fc59b03e1..d40880a63 100644
Binary files a/build/icons/imagesharp-logo-256.png and b/build/icons/imagesharp-logo-256.png differ
diff --git a/build/icons/imagesharp-logo-32.png b/build/icons/imagesharp-logo-32.png
index 0fd4411ce..8fcae249d 100644
Binary files a/build/icons/imagesharp-logo-32.png and b/build/icons/imagesharp-logo-32.png differ
diff --git a/build/icons/imagesharp-logo-512.png b/build/icons/imagesharp-logo-512.png
index 5d5fb854e..fcde03ec4 100644
Binary files a/build/icons/imagesharp-logo-512.png and b/build/icons/imagesharp-logo-512.png differ
diff --git a/build/icons/imagesharp-logo-64.png b/build/icons/imagesharp-logo-64.png
index 4e97906e5..b43538a6f 100644
Binary files a/build/icons/imagesharp-logo-64.png and b/build/icons/imagesharp-logo-64.png differ
diff --git a/build/icons/imagesharp-logo-heading.png b/build/icons/imagesharp-logo-heading.png
index b10d367bf..35a945930 100644
Binary files a/build/icons/imagesharp-logo-heading.png and b/build/icons/imagesharp-logo-heading.png differ
diff --git a/build/icons/imagesharp-logo.png b/build/icons/imagesharp-logo.png
index ed1c36c5e..fcde03ec4 100644
Binary files a/build/icons/imagesharp-logo.png and b/build/icons/imagesharp-logo.png differ
diff --git a/build/icons/imagesharp-logo.svg b/build/icons/imagesharp-logo.svg
index 2df3cc80c..cd4dfa117 100644
--- a/build/icons/imagesharp-logo.svg
+++ b/build/icons/imagesharp-logo.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj b/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj
index 351764bb9..8ba0cbdd0 100644
--- a/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj
+++ b/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj
@@ -39,8 +39,8 @@
All
-
-
+
+
..\..\ImageSharp.ruleset
diff --git a/src/ImageSharp.Drawing/Text/DrawText.cs b/src/ImageSharp.Drawing/Text/DrawText.cs
index 1f5f4cdb1..eba11b5c5 100644
--- a/src/ImageSharp.Drawing/Text/DrawText.cs
+++ b/src/ImageSharp.Drawing/Text/DrawText.cs
@@ -177,13 +177,14 @@ namespace ImageSharp
dpi = new Vector2((float)source.MetaData.HorizontalResolution, (float)source.MetaData.VerticalResolution);
}
- FontSpan style = new FontSpan(font)
+ FontSpan style = new FontSpan(font, dpi)
{
ApplyKerning = options.ApplyKerning,
- TabWidth = options.TabWidth
+ TabWidth = options.TabWidth,
+ WrappingWidth = options.WrapTextWidth
};
- renderer.RenderText(text, style, dpi);
+ renderer.RenderText(text, style);
System.Collections.Generic.IEnumerable shapesToDraw = glyphBuilder.Paths;
diff --git a/src/ImageSharp.Drawing/Text/TextGraphicsOptions.cs b/src/ImageSharp.Drawing/Text/TextGraphicsOptions.cs
index b58a40b34..6b09f2395 100644
--- a/src/ImageSharp.Drawing/Text/TextGraphicsOptions.cs
+++ b/src/ImageSharp.Drawing/Text/TextGraphicsOptions.cs
@@ -41,6 +41,11 @@ namespace ImageSharp.Drawing
///
public bool UseImageResolution;
+ ///
+ /// If greater than zero determine the width at which text should wrap.
+ ///
+ public float WrapTextWidth;
+
///
/// Initializes a new instance of the struct.
///
@@ -52,6 +57,7 @@ namespace ImageSharp.Drawing
this.TabWidth = 4;
this.AntialiasSubpixelDepth = 16;
this.UseImageResolution = false;
+ this.WrapTextWidth = 0;
}
///
diff --git a/src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuser.cs b/src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuser.cs
index cde146f1e..d6ab8eb64 100644
--- a/src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuser.cs
+++ b/src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuser.cs
@@ -71,8 +71,19 @@ namespace ImageSharp.Dithering
public void Dither(PixelAccessor pixels, TColor source, TColor transformed, int x, int y, int width, int height)
where TColor : struct, IPixel
{
- // Assign the transformed pixel to the array.
- pixels[x, y] = transformed;
+ this.Dither(pixels, source, transformed, x, y, width, height, true);
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Dither(PixelAccessor pixels, TColor source, TColor transformed, int x, int y, int width, int height, bool replacePixel)
+ where TColor : struct, IPixel
+ {
+ if (replacePixel)
+ {
+ // Assign the transformed pixel to the array.
+ pixels[x, y] = transformed;
+ }
// Calculate the error
Vector4 error = source.ToVector4() - transformed.ToVector4();
diff --git a/src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs b/src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs
index 18079b1fb..66ec3d515 100644
--- a/src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs
+++ b/src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs
@@ -25,5 +25,23 @@ namespace ImageSharp.Dithering
/// The pixel format.
void Dither(PixelAccessor pixels, TColor source, TColor transformed, int x, int y, int width, int height)
where TColor : struct, IPixel;
+
+ ///
+ /// Transforms the image applying the dither matrix. This method alters the input pixels array
+ ///
+ /// The pixel accessor
+ /// The source pixel
+ /// The transformed pixel
+ /// The column index.
+ /// The row index.
+ /// The image width.
+ /// The image height.
+ ///
+ /// Whether to replace the pixel at the given coordinates with the transformed value.
+ /// Generally this would be true for standard two-color dithering but when used in conjunction with color quantization this should be false.
+ ///
+ /// The pixel format.
+ void Dither(PixelAccessor pixels, TColor source, TColor transformed, int x, int y, int width, int height, bool replacePixel)
+ where TColor : struct, IPixel;
}
}
diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs
index d6529940e..5ce9b5eb0 100644
--- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs
+++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs
@@ -579,7 +579,7 @@ namespace ImageSharp.Formats
// channel and we should try to read it.
for (int x = 0; x < this.header.Width; x++)
{
- int index = newScanline[x];
+ int index = newScanline[x + 1];
int pixelOffset = index * 3;
byte a = this.paletteAlpha.Length > index ? this.paletteAlpha[index] : (byte)255;
@@ -603,7 +603,7 @@ namespace ImageSharp.Formats
{
for (int x = 0; x < this.header.Width; x++)
{
- int index = newScanline[x];
+ int index = newScanline[x + 1];
int pixelOffset = index * 3;
byte r = this.palette[pixelOffset];
@@ -982,4 +982,4 @@ namespace ImageSharp.Formats
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs
index 498ae578c..c11fc94df 100644
--- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs
+++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs
@@ -7,7 +7,6 @@ namespace ImageSharp.Formats
{
using System;
using System.Buffers;
- using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -487,7 +486,7 @@ namespace ImageSharp.Formats
if (this.quantizer == null)
{
- this.quantizer = new OctreeQuantizer();
+ this.quantizer = new WuQuantizer();
}
// Quantize the image returning a palette. This boxing is icky.
@@ -495,47 +494,54 @@ namespace ImageSharp.Formats
// Grab the palette and write it to the stream.
TColor[] palette = quantized.Palette;
- int pixelCount = palette.Length;
- List transparentPixels = new List();
+ byte pixelCount = palette.Length.ToByte();
// Get max colors for bit depth.
int colorTableLength = (int)Math.Pow(2, header.BitDepth) * 3;
byte[] colorTable = ArrayPool.Shared.Rent(colorTableLength);
+ byte[] alphaTable = ArrayPool.Shared.Rent(pixelCount);
byte[] bytes = ArrayPool.Shared.Rent(4);
-
+ bool anyAlpha = false;
try
{
- for (int i = 0; i < pixelCount; i++)
+ for (byte i = 0; i < pixelCount; i++)
{
- int offset = i * 3;
- palette[i].ToXyzwBytes(bytes, 0);
+ if (quantized.Pixels.Contains(i))
+ {
+ int offset = i * 3;
+ palette[i].ToXyzwBytes(bytes, 0);
- int alpha = bytes[3];
+ byte alpha = bytes[3];
- colorTable[offset] = bytes[0];
- colorTable[offset + 1] = bytes[1];
- colorTable[offset + 2] = bytes[2];
+ colorTable[offset] = bytes[0];
+ colorTable[offset + 1] = bytes[1];
+ colorTable[offset + 2] = bytes[2];
- if (alpha <= this.options.Threshold)
- {
- transparentPixels.Add((byte)offset);
+ if (alpha > this.options.Threshold)
+ {
+ alpha = 255;
+ }
+
+ anyAlpha = anyAlpha || alpha < 255;
+ alphaTable[i] = alpha;
}
}
this.WriteChunk(stream, PngChunkTypes.Palette, colorTable, 0, colorTableLength);
+
+ // Write the transparency data
+ if (anyAlpha)
+ {
+ this.WriteChunk(stream, PngChunkTypes.PaletteAlpha, alphaTable, 0, pixelCount);
+ }
}
finally
{
ArrayPool.Shared.Return(colorTable);
+ ArrayPool.Shared.Return(alphaTable);
ArrayPool.Shared.Return(bytes);
}
- // Write the transparency data
- if (transparentPixels.Any())
- {
- this.WriteChunk(stream, PngChunkTypes.PaletteAlpha, transparentPixels.ToArray());
- }
-
return quantized;
}
diff --git a/src/ImageSharp/Formats/Png/PngEncoderOptions.cs b/src/ImageSharp/Formats/Png/PngEncoderOptions.cs
index 2891f1974..90175c6d6 100644
--- a/src/ImageSharp/Formats/Png/PngEncoderOptions.cs
+++ b/src/ImageSharp/Formats/Png/PngEncoderOptions.cs
@@ -60,7 +60,7 @@ namespace ImageSharp.Formats
///
/// Gets or sets the transparency threshold.
///
- public byte Threshold { get; set; } = 0;
+ public byte Threshold { get; set; } = 255;
///
/// Gets or sets a value indicating whether this instance should write
diff --git a/src/ImageSharp/Image.FromStream.cs b/src/ImageSharp/Image.FromStream.cs
index 41ac7757e..112b8a109 100644
--- a/src/ImageSharp/Image.FromStream.cs
+++ b/src/ImageSharp/Image.FromStream.cs
@@ -201,7 +201,7 @@ namespace ImageSharp
{
config = config ?? Configuration.Default;
- Image img = WithSeekableStream(stream, s => Decode(stream, options, config));
+ Image img = WithSeekableStream(stream, s => Decode(s, options, config));
if (img != null)
{
@@ -238,7 +238,7 @@ namespace ImageSharp
stream.CopyTo(ms);
ms.Position = 0;
- return action(stream);
+ return action(ms);
}
}
}
diff --git a/src/ImageSharp/MetaData/Profiles/Exif/ExifReader.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifReader.cs
index 0b845c491..58f32bdd8 100644
--- a/src/ImageSharp/MetaData/Profiles/Exif/ExifReader.cs
+++ b/src/ImageSharp/MetaData/Profiles/Exif/ExifReader.cs
@@ -319,6 +319,13 @@ namespace ImageSharp
uint numberOfComponents = this.GetLong();
+ // Issue #132: ExifDataType == Undefined is treated like a byte array.
+ // If numberOfComponents == 0 this value can only be handled as an inline value and must fallback to 4 (bytes)
+ if (dataType == ExifDataType.Undefined && numberOfComponents == 0)
+ {
+ numberOfComponents = 4;
+ }
+
uint size = numberOfComponents * ExifValue.GetSize(dataType);
byte[] data = this.GetBytes(4);
diff --git a/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs
index 24d898fee..99b143de6 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs
@@ -168,7 +168,7 @@ namespace ImageSharp.Processing.Processors
/// The weights
public WeightsWindow GetWeightsWindow(int destIdx, int leftIdx, int rightIdx)
{
- BufferSpan span = this.dataBuffer.GetRowSpan(destIdx).Slice(leftIdx, rightIdx - leftIdx);
+ BufferSpan span = this.dataBuffer.GetRowSpan(destIdx).Slice(leftIdx, rightIdx - leftIdx + 1);
return new WeightsWindow(leftIdx, span);
}
}
diff --git a/src/ImageSharp/Quantizers/Wu/Box.cs b/src/ImageSharp/Quantizers/Box.cs
similarity index 96%
rename from src/ImageSharp/Quantizers/Wu/Box.cs
rename to src/ImageSharp/Quantizers/Box.cs
index e715c7839..7f2a32087 100644
--- a/src/ImageSharp/Quantizers/Wu/Box.cs
+++ b/src/ImageSharp/Quantizers/Box.cs
@@ -7,6 +7,7 @@ namespace ImageSharp.Quantizers
{
///
/// Represents a box color cube.
+ /// TODO: This should be a struct for performance
///
internal sealed class Box
{
diff --git a/src/ImageSharp/Quantizers/Octree/OctreeQuantizer.cs b/src/ImageSharp/Quantizers/OctreeQuantizer.cs
similarity index 89%
rename from src/ImageSharp/Quantizers/Octree/OctreeQuantizer.cs
rename to src/ImageSharp/Quantizers/OctreeQuantizer.cs
index 2590f297e..df52ee7f9 100644
--- a/src/ImageSharp/Quantizers/Octree/OctreeQuantizer.cs
+++ b/src/ImageSharp/Quantizers/OctreeQuantizer.cs
@@ -58,18 +58,12 @@ namespace ImageSharp.Quantizers
public override QuantizedImage Quantize(ImageBase image, int maxColors)
{
this.colors = maxColors.Clamp(1, 255);
- this.octree = new Octree(this.GetBitsNeededForColorDepth(maxColors));
+ this.octree = new Octree(this.GetBitsNeededForColorDepth(this.colors));
- return base.Quantize(image, maxColors);
+ return base.Quantize(image, this.colors);
}
- ///
- /// Execute a second pass through the bitmap
- ///
- /// The source image.
- /// The output pixel array
- /// The width in pixels of the image
- /// The height in pixels of the image
+ ///
protected override void SecondPass(PixelAccessor source, byte[] output, int width, int height)
{
// Load up the values for the first pixel. We can use these to speed up the second
@@ -107,7 +101,7 @@ namespace ImageSharp.Quantizers
if (this.Dither)
{
// Apply the dithering matrix. We have to reapply the value now as the original has changed.
- this.DitherType.Dither(source, sourcePixel, transformedPixel, x, y, width, height);
+ this.DitherType.Dither(source, sourcePixel, transformedPixel, x, y, width, height, false);
}
output[(y * source.Width) + x] = pixelValue;
@@ -115,36 +109,17 @@ namespace ImageSharp.Quantizers
}
}
- ///
- /// Process the pixel in the first pass of the algorithm
- ///
- ///
- /// The pixel to quantize
- ///
- ///
- /// This function need only be overridden if your quantize algorithm needs two passes,
- /// such as an Octree quantizer.
- ///
+ ///
protected override void InitialQuantizePixel(TColor pixel)
{
// Add the color to the Octree
this.octree.AddColor(pixel, this.pixelBuffer);
}
- ///
- /// Retrieve the palette for the quantized image.
- ///
- ///
- /// The new color palette
- ///
+ ///
protected override TColor[] GetPalette()
{
- if (this.palette == null)
- {
- this.palette = this.octree.Palletize(Math.Max(this.colors, 1));
- }
-
- return this.palette;
+ return this.palette ?? (this.palette = this.octree.Palletize(Math.Max(this.colors, 1)));
}
///
@@ -175,6 +150,7 @@ namespace ImageSharp.Quantizers
///
/// The
///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
private int GetBitsNeededForColorDepth(int colorCount)
{
return (int)Math.Ceiling(Math.Log(colorCount, 2));
@@ -189,7 +165,7 @@ namespace ImageSharp.Quantizers
/// Mask used when getting the appropriate pixels for a given node
///
// ReSharper disable once StaticMemberInGenericType
- private static readonly int[] Mask = { 0x100, 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 };
+ private static readonly int[] Mask = { 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 };
///
/// The root of the Octree
@@ -379,11 +355,6 @@ namespace ImageSharp.Quantizers
///
private int blue;
- ///
- /// Alpha component
- ///
- private int alpha;
-
///
/// The index of this node in the palette
///
@@ -406,7 +377,7 @@ namespace ImageSharp.Quantizers
// Construct the new node
this.leaf = level == colorBits;
- this.red = this.green = this.blue = this.alpha = 0;
+ this.red = this.green = this.blue = 0;
this.pixelCount = 0;
// If a leaf, increment the leaf count
@@ -454,10 +425,9 @@ namespace ImageSharp.Quantizers
int shift = 7 - level;
pixel.ToXyzwBytes(buffer, 0);
- int index = ((buffer[3] & Mask[0]) >> (shift - 3)) |
- ((buffer[2] & Mask[level + 1]) >> (shift - 2)) |
- ((buffer[1] & Mask[level + 1]) >> (shift - 1)) |
- ((buffer[0] & Mask[level + 1]) >> shift);
+ int index = ((buffer[2] & Mask[level]) >> (shift - 2)) |
+ ((buffer[1] & Mask[level]) >> (shift - 1)) |
+ ((buffer[0] & Mask[level]) >> shift);
OctreeNode child = this.children[index];
@@ -479,7 +449,7 @@ namespace ImageSharp.Quantizers
/// The number of leaves removed
public int Reduce()
{
- this.red = this.green = this.blue = this.alpha = 0;
+ this.red = this.green = this.blue = 0;
int childNodes = 0;
// Loop through all children and add their information to this node
@@ -490,7 +460,6 @@ namespace ImageSharp.Quantizers
this.red += this.children[index].red;
this.green += this.children[index].green;
this.blue += this.children[index].blue;
- this.alpha += this.children[index].alpha;
this.pixelCount += this.children[index].pixelCount;
++childNodes;
this.children[index] = null;
@@ -517,11 +486,10 @@ namespace ImageSharp.Quantizers
byte r = (this.red / this.pixelCount).ToByte();
byte g = (this.green / this.pixelCount).ToByte();
byte b = (this.blue / this.pixelCount).ToByte();
- byte a = (this.alpha / this.pixelCount).ToByte();
// And set the color of the palette entry
TColor pixel = default(TColor);
- pixel.PackFromBytes(r, g, b, a);
+ pixel.PackFromBytes(r, g, b, 255);
palette[index] = pixel;
// Consume the next palette index
@@ -558,10 +526,9 @@ namespace ImageSharp.Quantizers
int shift = 7 - level;
pixel.ToXyzwBytes(buffer, 0);
- int pixelIndex = ((buffer[3] & Mask[0]) >> (shift - 3)) |
- ((buffer[2] & Mask[level + 1]) >> (shift - 2)) |
- ((buffer[1] & Mask[level + 1]) >> (shift - 1)) |
- ((buffer[0] & Mask[level + 1]) >> shift);
+ int pixelIndex = ((buffer[2] & Mask[level]) >> (shift - 2)) |
+ ((buffer[1] & Mask[level]) >> (shift - 1)) |
+ ((buffer[0] & Mask[level]) >> shift);
if (this.children[pixelIndex] != null)
{
@@ -588,9 +555,8 @@ namespace ImageSharp.Quantizers
this.red += buffer[0];
this.green += buffer[1];
this.blue += buffer[2];
- this.alpha += buffer[3];
}
}
}
}
-}
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Quantizers/Palette/PaletteQuantizer.cs b/src/ImageSharp/Quantizers/PaletteQuantizer.cs
similarity index 91%
rename from src/ImageSharp/Quantizers/Palette/PaletteQuantizer.cs
rename to src/ImageSharp/Quantizers/PaletteQuantizer.cs
index dedb5ca23..f039fe0c5 100644
--- a/src/ImageSharp/Quantizers/Palette/PaletteQuantizer.cs
+++ b/src/ImageSharp/Quantizers/PaletteQuantizer.cs
@@ -7,7 +7,6 @@ namespace ImageSharp.Quantizers
{
using System;
using System.Collections.Generic;
- using System.Numerics;
using System.Runtime.CompilerServices;
///
@@ -71,13 +70,7 @@ namespace ImageSharp.Quantizers
return base.Quantize(image, maxColors);
}
- ///
- /// Execute a second pass through the bitmap
- ///
- /// The source image.
- /// The output pixel array
- /// The width in pixels of the image
- /// The height in pixels of the image
+ ///
protected override void SecondPass(PixelAccessor source, byte[] output, int width, int height)
{
// Load up the values for the first pixel. We can use these to speed up the second
@@ -115,7 +108,7 @@ namespace ImageSharp.Quantizers
if (this.Dither)
{
// Apply the dithering matrix. We have to reapply the value now as the original has changed.
- this.DitherType.Dither(source, sourcePixel, transformedPixel, x, y, width, height);
+ this.DitherType.Dither(source, sourcePixel, transformedPixel, x, y, width, height, false);
}
output[(y * source.Width) + x] = pixelValue;
diff --git a/src/ImageSharp/Quantizers/Options/Quantization.cs b/src/ImageSharp/Quantizers/Quantization.cs
similarity index 81%
rename from src/ImageSharp/Quantizers/Options/Quantization.cs
rename to src/ImageSharp/Quantizers/Quantization.cs
index 428c8e801..039404384 100644
--- a/src/ImageSharp/Quantizers/Options/Quantization.cs
+++ b/src/ImageSharp/Quantizers/Quantization.cs
@@ -12,17 +12,20 @@ namespace ImageSharp
{
///
/// An adaptive Octree quantizer. Fast with good quality.
+ /// The quantizer only supports a single alpha value.
///
Octree,
///
/// Xiaolin Wu's Color Quantizer which generates high quality output.
+ /// The quantizer supports multiple alpha values.
///
Wu,
///
/// Palette based, Uses the collection of web-safe colors by default.
+ /// The quantizer supports multiple alpha values.
///
Palette
}
-}
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Quantizers/QuantizedImage.cs b/src/ImageSharp/Quantizers/QuantizedImage.cs
index 528da7717..471abbae7 100644
--- a/src/ImageSharp/Quantizers/QuantizedImage.cs
+++ b/src/ImageSharp/Quantizers/QuantizedImage.cs
@@ -6,7 +6,6 @@
namespace ImageSharp.Quantizers
{
using System;
- using System.Threading.Tasks;
///
/// Represents a quantized image where the pixels indexed by a color palette.
diff --git a/src/ImageSharp/Quantizers/Octree/Quantizer.cs b/src/ImageSharp/Quantizers/Quantizer.cs
similarity index 90%
rename from src/ImageSharp/Quantizers/Octree/Quantizer.cs
rename to src/ImageSharp/Quantizers/Quantizer.cs
index ccf8da3df..bb856ccc5 100644
--- a/src/ImageSharp/Quantizers/Octree/Quantizer.cs
+++ b/src/ImageSharp/Quantizers/Quantizer.cs
@@ -66,22 +66,9 @@ namespace ImageSharp.Quantizers
this.FirstPass(pixels, width, height);
}
- // Collect the palette. Octree requires this to be done before the second pass runs.
+ // Collect the palette. Required before the second pass runs.
colorPalette = this.GetPalette();
-
- if (this.Dither)
- {
- // We clone the image as we don't want to alter the original.
- using (Image clone = new Image(image))
- using (PixelAccessor clonedPixels = clone.Lock())
- {
- this.SecondPass(clonedPixels, quantizedPixels, width, height);
- }
- }
- else
- {
- this.SecondPass(pixels, quantizedPixels, width, height);
- }
+ this.SecondPass(pixels, quantizedPixels, width, height);
}
return new QuantizedImage(width, height, colorPalette, quantizedPixels);
diff --git a/src/ImageSharp/Quantizers/WuArrayPool.cs b/src/ImageSharp/Quantizers/WuArrayPool.cs
new file mode 100644
index 000000000..5e4956f01
--- /dev/null
+++ b/src/ImageSharp/Quantizers/WuArrayPool.cs
@@ -0,0 +1,36 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Quantizers
+{
+ using System.Buffers;
+
+ ///
+ /// Provides array pooling for the .
+ /// This is a separate class so that the pools can be shared accross multiple generic quantizer instaces.
+ ///
+ internal static class WuArrayPool
+ {
+ ///
+ /// The long array pool.
+ ///
+ public static readonly ArrayPool LongPool = ArrayPool.Create(TableLength, 25);
+
+ ///
+ /// The float array pool.
+ ///
+ public static readonly ArrayPool FloatPool = ArrayPool.Create(TableLength, 5);
+
+ ///
+ /// The byte array pool.
+ ///
+ public static readonly ArrayPool BytePool = ArrayPool.Create(TableLength, 5);
+
+ ///
+ /// The table length. Matches the calculated value in
+ ///
+ private const int TableLength = 2471625;
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Quantizers/Wu/WuQuantizer.cs b/src/ImageSharp/Quantizers/WuQuantizer.cs
similarity index 70%
rename from src/ImageSharp/Quantizers/Wu/WuQuantizer.cs
rename to src/ImageSharp/Quantizers/WuQuantizer.cs
index d632cdd73..c1c81d0ac 100644
--- a/src/ImageSharp/Quantizers/Wu/WuQuantizer.cs
+++ b/src/ImageSharp/Quantizers/WuQuantizer.cs
@@ -7,8 +7,9 @@ namespace ImageSharp.Quantizers
{
using System;
using System.Buffers;
+ using System.Collections.Generic;
using System.Numerics;
- using System.Threading.Tasks;
+ using System.Runtime.CompilerServices;
///
/// An implementation of Wu's color quantizer with alpha channel.
@@ -30,7 +31,7 @@ namespace ImageSharp.Quantizers
///
///
/// The pixel format.
- public sealed class WuQuantizer : IQuantizer
+ public class WuQuantizer : Quantizer
where TColor : struct, IPixel
{
///
@@ -59,103 +60,238 @@ namespace ImageSharp.Quantizers
private const int TableLength = IndexCount * IndexCount * IndexCount * IndexAlphaCount;
///
- /// The long array pool.
- ///
- private static readonly ArrayPool LongPool = ArrayPool.Create(TableLength, 25);
-
- ///
- /// The double array pool.
+ /// A buffer for storing pixels
///
- private static readonly ArrayPool DoublePool = ArrayPool.Create(TableLength, 5);
+ private readonly byte[] rgbaBuffer = new byte[4];
///
- /// The byte array pool.
+ /// A lookup table for colors
///
- private static readonly ArrayPool BytePool = ArrayPool.Create(TableLength, 5);
+ private readonly Dictionary colorMap = new Dictionary();
///
/// Moment of P(c).
///
- private readonly long[] vwt;
+ private long[] vwt;
///
/// Moment of r*P(c).
///
- private readonly long[] vmr;
+ private long[] vmr;
///
/// Moment of g*P(c).
///
- private readonly long[] vmg;
+ private long[] vmg;
///
/// Moment of b*P(c).
///
- private readonly long[] vmb;
+ private long[] vmb;
///
/// Moment of a*P(c).
///
- private readonly long[] vma;
+ private long[] vma;
///
/// Moment of c^2*P(c).
///
- private readonly double[] m2;
+ private float[] m2;
///
/// Color space tag.
///
- private readonly byte[] tag;
+ private byte[] tag;
///
- /// A buffer for storing pixels
+ /// Maximum allowed color depth
///
- private readonly byte[] rgbaBuffer = new byte[4];
+ private int colors;
+
+ ///
+ /// The reduced image palette
+ ///
+ private TColor[] palette;
+
+ ///
+ /// The color cube representing the image palette
+ ///
+ private Box[] colorCube;
///
/// Initializes a new instance of the class.
///
+ ///
+ /// The Wu quantizer is a two pass algorithm. The initial pass sets up the 3-D color histogram,
+ /// the second pass quantizes a color based on the position in the histogram.
+ ///
public WuQuantizer()
+ : base(false)
{
- this.vwt = LongPool.Rent(TableLength);
- this.vmr = LongPool.Rent(TableLength);
- this.vmg = LongPool.Rent(TableLength);
- this.vmb = LongPool.Rent(TableLength);
- this.vma = LongPool.Rent(TableLength);
- this.m2 = DoublePool.Rent(TableLength);
- this.tag = BytePool.Rent(TableLength);
}
///
- public QuantizedImage Quantize(ImageBase image, int maxColors)
+ public override QuantizedImage Quantize(ImageBase image, int maxColors)
{
Guard.NotNull(image, nameof(image));
- int colorCount = maxColors.Clamp(1, 256);
+ this.colors = maxColors.Clamp(1, 256);
+
+ try
+ {
+ this.vwt = WuArrayPool.LongPool.Rent(TableLength);
+ this.vmr = WuArrayPool.LongPool.Rent(TableLength);
+ this.vmg = WuArrayPool.LongPool.Rent(TableLength);
+ this.vmb = WuArrayPool.LongPool.Rent(TableLength);
+ this.vma = WuArrayPool.LongPool.Rent(TableLength);
+ this.m2 = WuArrayPool.FloatPool.Rent(TableLength);
+ this.tag = WuArrayPool.BytePool.Rent(TableLength);
+
+ return base.Quantize(image, this.colors);
+ }
+ finally
+ {
+ WuArrayPool.LongPool.Return(this.vwt, true);
+ WuArrayPool.LongPool.Return(this.vmr, true);
+ WuArrayPool.LongPool.Return(this.vmg, true);
+ WuArrayPool.LongPool.Return(this.vmb, true);
+ WuArrayPool.LongPool.Return(this.vma, true);
+ WuArrayPool.FloatPool.Return(this.m2, true);
+ WuArrayPool.BytePool.Return(this.tag, true);
+ }
+ }
+
+ ///
+ protected override TColor[] GetPalette()
+ {
+ if (this.palette == null)
+ {
+ this.palette = new TColor[this.colors];
+ for (int k = 0; k < this.colors; k++)
+ {
+ this.Mark(this.colorCube[k], (byte)k);
+
+ float weight = Volume(this.colorCube[k], this.vwt);
+
+ if (MathF.Abs(weight) > Constants.Epsilon)
+ {
+ float r = Volume(this.colorCube[k], this.vmr) / weight;
+ float g = Volume(this.colorCube[k], this.vmg) / weight;
+ float b = Volume(this.colorCube[k], this.vmb) / weight;
+ float a = Volume(this.colorCube[k], this.vma) / weight;
+
+ TColor color = default(TColor);
+ color.PackFromVector4(new Vector4(r, g, b, a) / 255F);
+ this.palette[k] = color;
+ }
+ }
+ }
+
+ return this.palette;
+ }
+
+ ///
+ protected override void InitialQuantizePixel(TColor pixel)
+ {
+ // Add the color to a 3-D color histogram.
+ // Colors are expected in r->g->b->a format
+ pixel.ToXyzwBytes(this.rgbaBuffer, 0);
+
+ byte r = this.rgbaBuffer[0];
+ byte g = this.rgbaBuffer[1];
+ byte b = this.rgbaBuffer[2];
+ byte a = this.rgbaBuffer[3];
+
+ int inr = r >> (8 - IndexBits);
+ int ing = g >> (8 - IndexBits);
+ int inb = b >> (8 - IndexBits);
+ int ina = a >> (8 - IndexAlphaBits);
+
+ int ind = GetPaletteIndex(inr + 1, ing + 1, inb + 1, ina + 1);
+
+ this.vwt[ind]++;
+ this.vmr[ind] += r;
+ this.vmg[ind] += g;
+ this.vmb[ind] += b;
+ this.vma[ind] += a;
+ this.m2[ind] += (r * r) + (g * g) + (b * b) + (a * a);
+ }
+
+ ///
+ protected override void FirstPass(PixelAccessor source, int width, int height)
+ {
+ // Build up the 3-D color histogram
+ // Loop through each row
+ for (int y = 0; y < height; y++)
+ {
+ // And loop through each column
+ for (int x = 0; x < width; x++)
+ {
+ // Now I have the pixel, call the FirstPassQuantize function...
+ this.InitialQuantizePixel(source[x, y]);
+ }
+ }
- this.Clear();
+ this.Get3DMoments();
+ this.BuildCube();
+ }
- using (PixelAccessor imagePixels = image.Lock())
+ ///
+ protected override void SecondPass(PixelAccessor source, byte[] output, int width, int height)
+ {
+ // Load up the values for the first pixel. We can use these to speed up the second
+ // pass of the algorithm by avoiding transforming rows of identical color.
+ TColor sourcePixel = source[0, 0];
+ TColor previousPixel = sourcePixel;
+ byte pixelValue = this.QuantizePixel(sourcePixel);
+ TColor[] colorPalette = this.GetPalette();
+ TColor transformedPixel = colorPalette[pixelValue];
+
+ for (int y = 0; y < height; y++)
{
- this.Build3DHistogram(imagePixels);
- this.Get3DMoments();
+ // And loop through each column
+ for (int x = 0; x < width; x++)
+ {
+ // Get the pixel.
+ sourcePixel = source[x, y];
+
+ // Check if this is the same as the last pixel. If so use that value
+ // rather than calculating it again. This is an inexpensive optimization.
+ if (!previousPixel.Equals(sourcePixel))
+ {
+ // Quantize the pixel
+ pixelValue = this.QuantizePixel(sourcePixel);
+
+ // And setup the previous pointer
+ previousPixel = sourcePixel;
+
+ if (this.Dither)
+ {
+ transformedPixel = colorPalette[pixelValue];
+ }
+ }
- Box[] cube;
- this.BuildCube(out cube, ref colorCount);
+ if (this.Dither)
+ {
+ // Apply the dithering matrix. We have to reapply the value now as the original has changed.
+ this.DitherType.Dither(source, sourcePixel, transformedPixel, x, y, width, height, false);
+ }
- return this.GenerateResult(imagePixels, colorCount, cube);
+ output[(y * source.Width) + x] = pixelValue;
+ }
}
}
///
- /// Gets an index.
+ /// Gets the index index of the given color in the palette.
///
/// The red value.
/// The green value.
/// The blue value.
/// The alpha value.
/// The index.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int GetPaletteIndex(int r, int g, int b, int a)
{
return (r << ((IndexBits * 2) + IndexAlphaBits)) + (r << (IndexBits + IndexAlphaBits + 1))
@@ -169,7 +305,7 @@ namespace ImageSharp.Quantizers
/// The cube.
/// The moment.
/// The result.
- private static double Volume(Box cube, long[] moment)
+ private static float Volume(Box cube, long[] moment)
{
return moment[GetPaletteIndex(cube.R1, cube.G1, cube.B1, cube.A1)]
- moment[GetPaletteIndex(cube.R1, cube.G1, cube.B1, cube.A0)]
@@ -310,55 +446,6 @@ namespace ImageSharp.Quantizers
}
}
- ///
- /// Clears the tables.
- ///
- private void Clear()
- {
- Array.Clear(this.vwt, 0, TableLength);
- Array.Clear(this.vmr, 0, TableLength);
- Array.Clear(this.vmg, 0, TableLength);
- Array.Clear(this.vmb, 0, TableLength);
- Array.Clear(this.vma, 0, TableLength);
- Array.Clear(this.m2, 0, TableLength);
- Array.Clear(this.tag, 0, TableLength);
- }
-
- ///
- /// Builds a 3-D color histogram of counts, r/g/b, c^2.
- ///
- /// The pixel accessor.
- private void Build3DHistogram(PixelAccessor pixels)
- {
- for (int y = 0; y < pixels.Height; y++)
- {
- for (int x = 0; x < pixels.Width; x++)
- {
- // Colors are expected in r->g->b->a format
- pixels[x, y].ToXyzwBytes(this.rgbaBuffer, 0);
-
- byte r = this.rgbaBuffer[0];
- byte g = this.rgbaBuffer[1];
- byte b = this.rgbaBuffer[2];
- byte a = this.rgbaBuffer[3];
-
- int inr = r >> (8 - IndexBits);
- int ing = g >> (8 - IndexBits);
- int inb = b >> (8 - IndexBits);
- int ina = a >> (8 - IndexAlphaBits);
-
- int ind = GetPaletteIndex(inr + 1, ing + 1, inb + 1, ina + 1);
-
- this.vwt[ind]++;
- this.vmr[ind] += r;
- this.vmg[ind] += g;
- this.vmb[ind] += b;
- this.vma[ind] += a;
- this.m2[ind] += (r * r) + (g * g) + (b * b) + (a * a);
- }
- }
- }
-
///
/// Converts the histogram into moments so that we can rapidly calculate the sums of the above quantities over any desired box.
///
@@ -369,14 +456,14 @@ namespace ImageSharp.Quantizers
long[] volumeG = ArrayPool.Shared.Rent(IndexCount * IndexAlphaCount);
long[] volumeB = ArrayPool.Shared.Rent(IndexCount * IndexAlphaCount);
long[] volumeA = ArrayPool.Shared.Rent(IndexCount * IndexAlphaCount);
- double[] volume2 = ArrayPool.Shared.Rent(IndexCount * IndexAlphaCount);
+ float[] volume2 = ArrayPool.Shared.Rent(IndexCount * IndexAlphaCount);
long[] area = ArrayPool.Shared.Rent(IndexAlphaCount);
long[] areaR = ArrayPool.Shared.Rent(IndexAlphaCount);
long[] areaG = ArrayPool.Shared.Rent(IndexAlphaCount);
long[] areaB = ArrayPool.Shared.Rent(IndexAlphaCount);
long[] areaA = ArrayPool.Shared.Rent(IndexAlphaCount);
- double[] area2 = ArrayPool.Shared.Rent(IndexAlphaCount);
+ float[] area2 = ArrayPool.Shared.Rent(IndexAlphaCount);
try
{
@@ -405,7 +492,7 @@ namespace ImageSharp.Quantizers
long lineG = 0;
long lineB = 0;
long lineA = 0;
- double line2 = 0;
+ float line2 = 0;
for (int a = 1; a < IndexAlphaCount; a++)
{
@@ -454,14 +541,14 @@ namespace ImageSharp.Quantizers
ArrayPool.Shared.Return(volumeG);
ArrayPool.Shared.Return(volumeB);
ArrayPool.Shared.Return(volumeA);
- ArrayPool.Shared.Return(volume2);
+ ArrayPool.Shared.Return(volume2);
ArrayPool.Shared.Return(area);
ArrayPool.Shared.Return(areaR);
ArrayPool.Shared.Return(areaG);
ArrayPool.Shared.Return(areaB);
ArrayPool.Shared.Return(areaA);
- ArrayPool.Shared.Return(area2);
+ ArrayPool.Shared.Return(area2);
}
}
@@ -469,15 +556,15 @@ namespace ImageSharp.Quantizers
/// Computes the weighted variance of a box cube.
///
/// The cube.
- /// The .
- private double Variance(Box cube)
+ /// The .
+ private float Variance(Box cube)
{
- double dr = Volume(cube, this.vmr);
- double dg = Volume(cube, this.vmg);
- double db = Volume(cube, this.vmb);
- double da = Volume(cube, this.vma);
+ float dr = Volume(cube, this.vmr);
+ float dg = Volume(cube, this.vmg);
+ float db = Volume(cube, this.vmb);
+ float da = Volume(cube, this.vma);
- double xx =
+ float xx =
this.m2[GetPaletteIndex(cube.R1, cube.G1, cube.B1, cube.A1)]
- this.m2[GetPaletteIndex(cube.R1, cube.G1, cube.B1, cube.A0)]
- this.m2[GetPaletteIndex(cube.R1, cube.G1, cube.B0, cube.A1)]
@@ -515,8 +602,8 @@ namespace ImageSharp.Quantizers
/// The whole blue.
/// The whole alpha.
/// The whole weight.
- /// The .
- private double Maximize(Box cube, int direction, int first, int last, out int cut, double wholeR, double wholeG, double wholeB, double wholeA, double wholeW)
+ /// The .
+ private float Maximize(Box cube, int direction, int first, int last, out int cut, float wholeR, float wholeG, float wholeB, float wholeA, float wholeW)
{
long baseR = Bottom(cube, direction, this.vmr);
long baseG = Bottom(cube, direction, this.vmg);
@@ -524,20 +611,20 @@ namespace ImageSharp.Quantizers
long baseA = Bottom(cube, direction, this.vma);
long baseW = Bottom(cube, direction, this.vwt);
- double max = 0.0;
+ float max = 0F;
cut = -1;
for (int i = first; i < last; i++)
{
- double halfR = baseR + Top(cube, direction, i, this.vmr);
- double halfG = baseG + Top(cube, direction, i, this.vmg);
- double halfB = baseB + Top(cube, direction, i, this.vmb);
- double halfA = baseA + Top(cube, direction, i, this.vma);
- double halfW = baseW + Top(cube, direction, i, this.vwt);
+ float halfR = baseR + Top(cube, direction, i, this.vmr);
+ float halfG = baseG + Top(cube, direction, i, this.vmg);
+ float halfB = baseB + Top(cube, direction, i, this.vmb);
+ float halfA = baseA + Top(cube, direction, i, this.vma);
+ float halfW = baseW + Top(cube, direction, i, this.vwt);
- double temp;
+ float temp;
- if (Math.Abs(halfW) < Constants.Epsilon)
+ if (MathF.Abs(halfW) < Constants.Epsilon)
{
continue;
}
@@ -550,7 +637,7 @@ namespace ImageSharp.Quantizers
halfA = wholeA - halfA;
halfW = wholeW - halfW;
- if (Math.Abs(halfW) < Constants.Epsilon)
+ if (MathF.Abs(halfW) < Constants.Epsilon)
{
continue;
}
@@ -575,21 +662,16 @@ namespace ImageSharp.Quantizers
/// Returns a value indicating whether the box has been split.
private bool Cut(Box set1, Box set2)
{
- double wholeR = Volume(set1, this.vmr);
- double wholeG = Volume(set1, this.vmg);
- double wholeB = Volume(set1, this.vmb);
- double wholeA = Volume(set1, this.vma);
- double wholeW = Volume(set1, this.vwt);
-
- int cutr;
- int cutg;
- int cutb;
- int cuta;
-
- double maxr = this.Maximize(set1, 0, set1.R0 + 1, set1.R1, out cutr, wholeR, wholeG, wholeB, wholeA, wholeW);
- double maxg = this.Maximize(set1, 1, set1.G0 + 1, set1.G1, out cutg, wholeR, wholeG, wholeB, wholeA, wholeW);
- double maxb = this.Maximize(set1, 2, set1.B0 + 1, set1.B1, out cutb, wholeR, wholeG, wholeB, wholeA, wholeW);
- double maxa = this.Maximize(set1, 3, set1.A0 + 1, set1.A1, out cuta, wholeR, wholeG, wholeB, wholeA, wholeW);
+ float wholeR = Volume(set1, this.vmr);
+ float wholeG = Volume(set1, this.vmg);
+ float wholeB = Volume(set1, this.vmb);
+ float wholeA = Volume(set1, this.vma);
+ float wholeW = Volume(set1, this.vwt);
+
+ float maxr = this.Maximize(set1, 0, set1.R0 + 1, set1.R1, out int cutr, wholeR, wholeG, wholeB, wholeA, wholeW);
+ float maxg = this.Maximize(set1, 1, set1.G0 + 1, set1.G1, out int cutg, wholeR, wholeG, wholeB, wholeA, wholeW);
+ float maxb = this.Maximize(set1, 2, set1.B0 + 1, set1.B1, out int cutb, wholeR, wholeG, wholeB, wholeA, wholeW);
+ float maxa = this.Maximize(set1, 3, set1.A0 + 1, set1.A1, out int cuta, wholeR, wholeG, wholeB, wholeA, wholeW);
int dir;
@@ -686,40 +768,38 @@ namespace ImageSharp.Quantizers
///
/// Builds the cube.
///
- /// The cube.
- /// The color count.
- private void BuildCube(out Box[] cube, ref int colorCount)
+ private void BuildCube()
{
- cube = new Box[colorCount];
- double[] vv = new double[colorCount];
+ this.colorCube = new Box[this.colors];
+ float[] vv = new float[this.colors];
- for (int i = 0; i < colorCount; i++)
+ for (int i = 0; i < this.colors; i++)
{
- cube[i] = new Box();
+ this.colorCube[i] = new Box();
}
- cube[0].R0 = cube[0].G0 = cube[0].B0 = cube[0].A0 = 0;
- cube[0].R1 = cube[0].G1 = cube[0].B1 = IndexCount - 1;
- cube[0].A1 = IndexAlphaCount - 1;
+ this.colorCube[0].R0 = this.colorCube[0].G0 = this.colorCube[0].B0 = this.colorCube[0].A0 = 0;
+ this.colorCube[0].R1 = this.colorCube[0].G1 = this.colorCube[0].B1 = IndexCount - 1;
+ this.colorCube[0].A1 = IndexAlphaCount - 1;
int next = 0;
- for (int i = 1; i < colorCount; i++)
+ for (int i = 1; i < this.colors; i++)
{
- if (this.Cut(cube[next], cube[i]))
+ if (this.Cut(this.colorCube[next], this.colorCube[i]))
{
- vv[next] = cube[next].Volume > 1 ? this.Variance(cube[next]) : 0.0;
- vv[i] = cube[i].Volume > 1 ? this.Variance(cube[i]) : 0.0;
+ vv[next] = this.colorCube[next].Volume > 1 ? this.Variance(this.colorCube[next]) : 0F;
+ vv[i] = this.colorCube[i].Volume > 1 ? this.Variance(this.colorCube[i]) : 0F;
}
else
{
- vv[next] = 0.0;
+ vv[next] = 0F;
i--;
}
next = 0;
- double temp = vv[0];
+ float temp = vv[0];
for (int k = 1; k <= i; k++)
{
if (vv[k] > temp)
@@ -731,79 +811,38 @@ namespace ImageSharp.Quantizers
if (temp <= 0.0)
{
- colorCount = i + 1;
+ this.colors = i + 1;
break;
}
}
}
///
- /// Generates the quantized result.
+ /// Process the pixel in the second pass of the algorithm
///
- /// The image pixels.
- /// The color count.
- /// The cube.
- /// The result.
- private QuantizedImage GenerateResult(PixelAccessor imagePixels, int colorCount, Box[] cube)
+ /// The pixel to quantize
+ ///
+ /// The quantized value
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private byte QuantizePixel(TColor pixel)
{
- TColor[] pallette = new TColor[colorCount];
- byte[] pixels = new byte[imagePixels.Width * imagePixels.Height];
- int width = imagePixels.Width;
- int height = imagePixels.Height;
-
- for (int k = 0; k < colorCount; k++)
+ if (this.Dither)
{
- this.Mark(cube[k], (byte)k);
-
- double weight = Volume(cube[k], this.vwt);
-
- if (Math.Abs(weight) > Constants.Epsilon)
- {
- float r = (float)(Volume(cube[k], this.vmr) / weight);
- float g = (float)(Volume(cube[k], this.vmg) / weight);
- float b = (float)(Volume(cube[k], this.vmb) / weight);
- float a = (float)(Volume(cube[k], this.vma) / weight);
-
- TColor color = default(TColor);
- color.PackFromVector4(new Vector4(r, g, b, a) / 255F);
- pallette[k] = color;
- }
+ // The colors have changed so we need to use Euclidean distance caclulation to find the closest value.
+ // This palette can never be null here.
+ return this.GetClosestColor(pixel, this.palette, this.colorMap);
}
- Parallel.For(
- 0,
- height,
- imagePixels.ParallelOptions,
- y =>
- {
- byte[] rgba = ArrayPool.Shared.Rent(4);
- for (int x = 0; x < width; x++)
- {
- // Expected order r->g->b->a
- imagePixels[x, y].ToXyzwBytes(rgba, 0);
-
- int r = rgba[0] >> (8 - IndexBits);
- int g = rgba[1] >> (8 - IndexBits);
- int b = rgba[2] >> (8 - IndexBits);
- int a = rgba[3] >> (8 - IndexAlphaBits);
-
- int ind = GetPaletteIndex(r + 1, g + 1, b + 1, a + 1);
- pixels[(y * width) + x] = this.tag[ind];
- }
-
- ArrayPool.Shared.Return(rgba);
- });
+ // Expected order r->g->b->a
+ pixel.ToXyzwBytes(this.rgbaBuffer, 0);
- // Cleanup
- LongPool.Return(this.vwt);
- LongPool.Return(this.vmr);
- LongPool.Return(this.vmg);
- LongPool.Return(this.vmb);
- LongPool.Return(this.vma);
- DoublePool.Return(this.m2);
- BytePool.Return(this.tag);
+ int r = this.rgbaBuffer[0] >> (8 - IndexBits);
+ int g = this.rgbaBuffer[1] >> (8 - IndexBits);
+ int b = this.rgbaBuffer[2] >> (8 - IndexBits);
+ int a = this.rgbaBuffer[3] >> (8 - IndexAlphaBits);
- return new QuantizedImage(width, height, pallette, pixels);
+ return this.tag[GetPaletteIndex(r + 1, g + 1, b + 1, a + 1)];
}
}
}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/FileTestBase.cs b/tests/ImageSharp.Tests/FileTestBase.cs
index 765ff3a42..1fa26063d 100644
--- a/tests/ImageSharp.Tests/FileTestBase.cs
+++ b/tests/ImageSharp.Tests/FileTestBase.cs
@@ -28,7 +28,7 @@ namespace ImageSharp.Tests
// TestFile.Create(TestImages.Jpeg.Baseline.GammaDalaiLamaGray), // Perf: Enable for local testing only
// TestFile.Create(TestImages.Jpeg.Progressive.Bad.BadEOF), // Perf: Enable for local testing only
TestFile.Create(TestImages.Bmp.Car),
- // TestFile.Create(TestImages.Bmp.Neg_height), // Perf: Enable for local testing only
+ // TestFile.Create(TestImages.Bmp.NegHeight), // Perf: Enable for local testing only
TestFile.Create(TestImages.Png.Splash),
// TestFile.Create(TestImages.Png.ChunkLength1), // Perf: Enable for local testing only
// TestFile.Create(TestImages.Png.ChunkLength2), // Perf: Enable for local testing only
@@ -46,6 +46,7 @@ namespace ImageSharp.Tests
// TestFile.Create(TestImages.Png.P1), // Perf: Enable for local testing only
// TestFile.Create(TestImages.Png.Pd), // Perf: Enable for local testing only
TestFile.Create(TestImages.Gif.Rings),
+ // TestFile.Create(TestImages.Gif.Trans), // Perf: Enable for local testing only
// TestFile.Create(TestImages.Gif.Cheers), // Perf: Enable for local testing only
// TestFile.Create(TestImages.Gif.Giphy) // Perf: Enable for local testing only
};
diff --git a/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs
index 882f903d6..a7453f77c 100644
--- a/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs
@@ -13,6 +13,7 @@ namespace ImageSharp.Tests.Formats.Png
using ImageSharp.Formats;
using System.Linq;
using ImageSharp.IO;
+ using System.Numerics;
public class PngSmokeTests
{
@@ -58,6 +59,49 @@ namespace ImageSharp.Tests.Formats.Png
}
}
+ // JJS: Commented out for now since the test does not take into lossy nature of indexing.
+ //[Theory]
+ //[WithTestPatternImages(100, 100, PixelTypes.Color)]
+ //public void CanSaveIndexedPngTwice(TestImageProvider provider)
+ // where TColor : struct, IPixel
+ //{
+ // // does saving a file then repoening mean both files are identical???
+ // using (Image source = provider.GetImage())
+ // using (MemoryStream ms = new MemoryStream())
+ // {
+ // source.MetaData.Quality = 256;
+ // source.Save(ms, new PngEncoder(), new PngEncoderOptions {
+ // Threshold = 200
+ // });
+ // ms.Position = 0;
+ // using (Image img1 = Image.Load(ms, new PngDecoder()))
+ // {
+ // using (MemoryStream ms2 = new MemoryStream())
+ // {
+ // img1.Save(ms2, new PngEncoder(), new PngEncoderOptions
+ // {
+ // Threshold = 200
+ // });
+ // ms2.Position = 0;
+ // using (Image img2 = Image.Load(ms2, new PngDecoder()))
+ // {
+ // using (PixelAccessor pixels1 = img1.Lock())
+ // using (PixelAccessor pixels2 = img2.Lock())
+ // {
+ // for (int y = 0; y < img1.Height; y++)
+ // {
+ // for (int x = 0; x < img1.Width; x++)
+ // {
+ // Assert.Equal(pixels1[x, y], pixels2[x, y]);
+ // }
+ // }
+ // }
+ // }
+ // }
+ // }
+ // }
+ //}
+
[Theory]
[WithTestPatternImages(300, 300, PixelTypes.All)]
public void Resize(TestImageProvider provider)
diff --git a/tests/ImageSharp.Tests/Image/ImageLoadTests.cs b/tests/ImageSharp.Tests/Image/ImageLoadTests.cs
index ddb9414cc..10b0cbb94 100644
--- a/tests/ImageSharp.Tests/Image/ImageLoadTests.cs
+++ b/tests/ImageSharp.Tests/Image/ImageLoadTests.cs
@@ -86,6 +86,20 @@ namespace ImageSharp.Tests
}
+ [Fact]
+ public void LoadFromNoneSeekableStream()
+ {
+ NoneSeekableStream stream = new NoneSeekableStream(this.DataStream);
+ Image img = Image.Load(stream);
+
+ Assert.NotNull(img);
+ Assert.Equal(TestFormat.GlobalTestFormat, img.CurrentImageFormat);
+
+
+ TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, null, Configuration.Default);
+
+ }
+
[Fact]
public void LoadFromStreamWithType()
{
diff --git a/tests/ImageSharp.Tests/Image/NoneSeekableStream.cs b/tests/ImageSharp.Tests/Image/NoneSeekableStream.cs
new file mode 100644
index 000000000..bc36b60eb
--- /dev/null
+++ b/tests/ImageSharp.Tests/Image/NoneSeekableStream.cs
@@ -0,0 +1,50 @@
+using System;
+using System.IO;
+
+namespace ImageSharp.Tests
+{
+ internal class NoneSeekableStream : Stream
+ {
+ private Stream dataStream;
+
+ public NoneSeekableStream(Stream dataStream)
+ {
+ this.dataStream = dataStream;
+ }
+
+ public override bool CanRead => this.dataStream.CanRead;
+
+ public override bool CanSeek => false;
+
+ public override bool CanWrite => false;
+
+ public override long Length => this.dataStream.Length;
+
+ public override long Position { get => this.dataStream.Position; set => throw new NotImplementedException(); }
+
+ public override void Flush()
+ {
+ this.dataStream.Flush();
+ }
+
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ return this.dataStream.Read(buffer, offset, count);
+ }
+
+ public override long Seek(long offset, SeekOrigin origin)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override void SetLength(long value)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override void Write(byte[] buffer, int offset, int count)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs
index 1bc31286d..f380724df 100644
--- a/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs
+++ b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs
@@ -268,6 +268,24 @@ namespace ImageSharp.Tests
}
}
+ [Fact]
+ public void ExifTypeUndefined()
+ {
+ Image image = TestFile.Create(TestImages.Jpeg.Baseline.Bad.ExifUndefType).CreateImage();
+ Assert.NotNull(image);
+
+ ExifProfile profile = image.MetaData.ExifProfile;
+ Assert.NotNull(profile);
+
+ foreach (ExifValue value in profile.Values)
+ {
+ if (value.DataType == ExifDataType.Undefined)
+ {
+ Assert.Equal(4, value.NumberOfComponents);
+ }
+ }
+ }
+
private static ExifProfile GetExifProfile()
{
Image image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateImage();
diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs
index f0a0e8dd8..5be1240ef 100644
--- a/tests/ImageSharp.Tests/TestImages.cs
+++ b/tests/ImageSharp.Tests/TestImages.cs
@@ -61,6 +61,7 @@ namespace ImageSharp.Tests
public static class Bad
{
public const string MissingEOF = "Jpg/baseline/badeof.jpg";
+ public const string ExifUndefType = "Jpg/baseline/ExifUndefType.jpg";
}
public const string Cmyk = "Jpg/baseline/cmyk.jpg";
@@ -102,6 +103,7 @@ namespace ImageSharp.Tests
public const string Rings = "Gif/rings.gif";
public const string Giphy = "Gif/giphy.gif";
public const string Cheers = "Gif/cheers.gif";
+ public const string Trans = "Gif/trans.gif";
}
}
}
diff --git a/tests/ImageSharp.Tests/TestImages/Formats/Gif/trans.gif b/tests/ImageSharp.Tests/TestImages/Formats/Gif/trans.gif
new file mode 100644
index 000000000..6ae92e97c
Binary files /dev/null and b/tests/ImageSharp.Tests/TestImages/Formats/Gif/trans.gif differ
diff --git a/tests/ImageSharp.Tests/TestImages/Formats/Jpg/baseline/ExifUndefType.jpg b/tests/ImageSharp.Tests/TestImages/Formats/Jpg/baseline/ExifUndefType.jpg
new file mode 100644
index 000000000..3a7f29c7d
Binary files /dev/null and b/tests/ImageSharp.Tests/TestImages/Formats/Jpg/baseline/ExifUndefType.jpg differ