Browse Source

Merge remote-tracking branch 'refs/remotes/origin/master' into feature/icc

pull/181/head
James Jackson-South 9 years ago
parent
commit
8a9563e564
  1. 87
      README.md
  2. BIN
      build/icons/imagesharp-logo-128.png
  3. BIN
      build/icons/imagesharp-logo-256.png
  4. BIN
      build/icons/imagesharp-logo-32.png
  5. BIN
      build/icons/imagesharp-logo-512.png
  6. BIN
      build/icons/imagesharp-logo-64.png
  7. BIN
      build/icons/imagesharp-logo-heading.png
  8. BIN
      build/icons/imagesharp-logo.png
  9. 2
      build/icons/imagesharp-logo.svg
  10. 4
      src/ImageSharp.Drawing/ImageSharp.Drawing.csproj
  11. 7
      src/ImageSharp.Drawing/Text/DrawText.cs
  12. 6
      src/ImageSharp.Drawing/Text/TextGraphicsOptions.cs
  13. 15
      src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuser.cs
  14. 18
      src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs
  15. 6
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  16. 48
      src/ImageSharp/Formats/Png/PngEncoderCore.cs
  17. 2
      src/ImageSharp/Formats/Png/PngEncoderOptions.cs
  18. 4
      src/ImageSharp/Image.FromStream.cs
  19. 7
      src/ImageSharp/MetaData/Profiles/Exif/ExifReader.cs
  20. 2
      src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs
  21. 1
      src/ImageSharp/Quantizers/Box.cs
  22. 72
      src/ImageSharp/Quantizers/OctreeQuantizer.cs
  23. 11
      src/ImageSharp/Quantizers/PaletteQuantizer.cs
  24. 5
      src/ImageSharp/Quantizers/Quantization.cs
  25. 1
      src/ImageSharp/Quantizers/QuantizedImage.cs
  26. 17
      src/ImageSharp/Quantizers/Quantizer.cs
  27. 36
      src/ImageSharp/Quantizers/WuArrayPool.cs
  28. 441
      src/ImageSharp/Quantizers/WuQuantizer.cs
  29. 3
      tests/ImageSharp.Tests/FileTestBase.cs
  30. 44
      tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs
  31. 14
      tests/ImageSharp.Tests/Image/ImageLoadTests.cs
  32. 50
      tests/ImageSharp.Tests/Image/NoneSeekableStream.cs
  33. 18
      tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs
  34. 2
      tests/ImageSharp.Tests/TestImages.cs
  35. BIN
      tests/ImageSharp.Tests/TestImages/Formats/Gif/trans.gif
  36. BIN
      tests/ImageSharp.Tests/TestImages/Formats/Jpg/baseline/ExifUndefType.jpg

87
README.md

@ -1,9 +1,13 @@
# <img src="build/icons/imagesharp-logo-heading.png" alt="ImageSharp"/>
# <img src="https://github.com/JimBobSquarePants/ImageSharp/blob/master/build/icons/imagesharp-logo-256.png" alt="ImageSharp" width="52"/> 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).
[![GitHub license](https://img.shields.io/badge/license-Apache%202-blue.svg)](https://raw.githubusercontent.com/JimBobSquarePants/ImageSharp/master/APACHE-2.0-LICENSE.txt)
[![GitHub issues](https://img.shields.io/github/issues/JimBobSquarePants/ImageSharp.svg)](https://github.com/JimBobSquarePants/ImageSharp/issues)
@ -11,6 +15,9 @@
[![GitHub forks](https://img.shields.io/github/forks/JimBobSquarePants/ImageSharp.svg)](https://github.com/JimBobSquarePants/ImageSharp/network)
[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/ImageSharp/General?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Twitter](https://img.shields.io/twitter/url/https/github.com/JimBobSquarePants/ImageSharp.svg?style=social)](https://twitter.com/intent/tweet?hashtags=imagesharp,dotnet,oss&text=ImageSharp.+A+new+cross-platform+2D+graphics+API+in+C%23&url=https%3a%2f%2fgithub.com%2fJimBobSquarePants%2fImageSharp&via=james_m_south)
[![OpenCollective](https://opencollective.com/imagesharp/backers/badge.svg)](#backers)
[![OpenCollective](https://opencollective.com/imagesharp/sponsors/badge.svg)](#sponsors)
| |Build Status|Code Coverage|
@ -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)]
<a href="https://opencollective.com/imagesharp/backer/0/website" target="_blank"><img src="https://opencollective.com/imagesharp/backer/0/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/backer/1/website" target="_blank"><img src="https://opencollective.com/imagesharp/backer/1/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/backer/2/website" target="_blank"><img src="https://opencollective.com/imagesharp/backer/2/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/backer/3/website" target="_blank"><img src="https://opencollective.com/imagesharp/backer/3/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/backer/4/website" target="_blank"><img src="https://opencollective.com/imagesharp/backer/4/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/backer/5/website" target="_blank"><img src="https://opencollective.com/imagesharp/backer/5/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/backer/6/website" target="_blank"><img src="https://opencollective.com/imagesharp/backer/6/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/backer/7/website" target="_blank"><img src="https://opencollective.com/imagesharp/backer/7/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/backer/8/website" target="_blank"><img src="https://opencollective.com/imagesharp/backer/8/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/backer/9/website" target="_blank"><img src="https://opencollective.com/imagesharp/backer/9/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/backer/10/website" target="_blank"><img src="https://opencollective.com/imagesharp/backer/10/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/backer/11/website" target="_blank"><img src="https://opencollective.com/imagesharp/backer/11/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/backer/12/website" target="_blank"><img src="https://opencollective.com/imagesharp/backer/12/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/backer/13/website" target="_blank"><img src="https://opencollective.com/imagesharp/backer/13/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/backer/14/website" target="_blank"><img src="https://opencollective.com/imagesharp/backer/14/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/backer/15/website" target="_blank"><img src="https://opencollective.com/imagesharp/backer/15/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/backer/16/website" target="_blank"><img src="https://opencollective.com/imagesharp/backer/16/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/backer/17/website" target="_blank"><img src="https://opencollective.com/imagesharp/backer/17/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/backer/18/website" target="_blank"><img src="https://opencollective.com/imagesharp/backer/18/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/backer/19/website" target="_blank"><img src="https://opencollective.com/imagesharp/backer/19/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/backer/20/website" target="_blank"><img src="https://opencollective.com/imagesharp/backer/20/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/backer/21/website" target="_blank"><img src="https://opencollective.com/imagesharp/backer/21/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/backer/22/website" target="_blank"><img src="https://opencollective.com/imagesharp/backer/22/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/backer/23/website" target="_blank"><img src="https://opencollective.com/imagesharp/backer/23/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/backer/24/website" target="_blank"><img src="https://opencollective.com/imagesharp/backer/24/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/backer/25/website" target="_blank"><img src="https://opencollective.com/imagesharp/backer/25/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/backer/26/website" target="_blank"><img src="https://opencollective.com/imagesharp/backer/26/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/backer/27/website" target="_blank"><img src="https://opencollective.com/imagesharp/backer/27/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/backer/28/website" target="_blank"><img src="https://opencollective.com/imagesharp/backer/28/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/backer/29/website" target="_blank"><img src="https://opencollective.com/imagesharp/backer/29/avatar.svg"></a>
### 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)]
<a href="https://opencollective.com/imagesharp/sponsor/0/website" target="_blank"><img src="https://opencollective.com/imagesharp/sponsor/0/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/sponsor/1/website" target="_blank"><img src="https://opencollective.com/imagesharp/sponsor/1/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/sponsor/2/website" target="_blank"><img src="https://opencollective.com/imagesharp/sponsor/2/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/sponsor/3/website" target="_blank"><img src="https://opencollective.com/imagesharp/sponsor/3/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/sponsor/4/website" target="_blank"><img src="https://opencollective.com/imagesharp/sponsor/4/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/sponsor/5/website" target="_blank"><img src="https://opencollective.com/imagesharp/sponsor/5/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/sponsor/6/website" target="_blank"><img src="https://opencollective.com/imagesharp/sponsor/6/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/sponsor/7/website" target="_blank"><img src="https://opencollective.com/imagesharp/sponsor/7/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/sponsor/8/website" target="_blank"><img src="https://opencollective.com/imagesharp/sponsor/8/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/sponsor/9/website" target="_blank"><img src="https://opencollective.com/imagesharp/sponsor/9/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/sponsor/10/website" target="_blank"><img src="https://opencollective.com/imagesharp/sponsor/10/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/sponsor/11/website" target="_blank"><img src="https://opencollective.com/imagesharp/sponsor/11/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/sponsor/12/website" target="_blank"><img src="https://opencollective.com/imagesharp/sponsor/12/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/sponsor/13/website" target="_blank"><img src="https://opencollective.com/imagesharp/sponsor/13/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/sponsor/14/website" target="_blank"><img src="https://opencollective.com/imagesharp/sponsor/14/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/sponsor/15/website" target="_blank"><img src="https://opencollective.com/imagesharp/sponsor/15/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/sponsor/16/website" target="_blank"><img src="https://opencollective.com/imagesharp/sponsor/16/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/sponsor/17/website" target="_blank"><img src="https://opencollective.com/imagesharp/sponsor/17/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/sponsor/18/website" target="_blank"><img src="https://opencollective.com/imagesharp/sponsor/18/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/sponsor/19/website" target="_blank"><img src="https://opencollective.com/imagesharp/sponsor/19/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/sponsor/20/website" target="_blank"><img src="https://opencollective.com/imagesharp/sponsor/20/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/sponsor/21/website" target="_blank"><img src="https://opencollective.com/imagesharp/sponsor/21/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/sponsor/22/website" target="_blank"><img src="https://opencollective.com/imagesharp/sponsor/22/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/sponsor/23/website" target="_blank"><img src="https://opencollective.com/imagesharp/sponsor/23/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/sponsor/24/website" target="_blank"><img src="https://opencollective.com/imagesharp/sponsor/24/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/sponsor/25/website" target="_blank"><img src="https://opencollective.com/imagesharp/sponsor/25/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/sponsor/26/website" target="_blank"><img src="https://opencollective.com/imagesharp/sponsor/26/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/sponsor/27/website" target="_blank"><img src="https://opencollective.com/imagesharp/sponsor/27/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/sponsor/28/website" target="_blank"><img src="https://opencollective.com/imagesharp/sponsor/28/avatar.svg"></a>
<a href="https://opencollective.com/imagesharp/sponsor/29/website" target="_blank"><img src="https://opencollective.com/imagesharp/sponsor/29/avatar.svg"></a>

BIN
build/icons/imagesharp-logo-128.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 7.9 KiB

BIN
build/icons/imagesharp-logo-256.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 16 KiB

BIN
build/icons/imagesharp-logo-32.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
build/icons/imagesharp-logo-512.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 34 KiB

BIN
build/icons/imagesharp-logo-64.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

BIN
build/icons/imagesharp-logo-heading.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 11 KiB

BIN
build/icons/imagesharp-logo.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 34 KiB

2
build/icons/imagesharp-logo.svg

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

4
src/ImageSharp.Drawing/ImageSharp.Drawing.csproj

@ -39,8 +39,8 @@
<PackageReference Include="StyleCop.Analyzers" Version="1.1.0-beta001">
<PrivateAssets>All</PrivateAssets>
</PackageReference>
<PackageReference Include="SixLabors.Fonts" Version="0.1.0-alpha0002" />
<PackageReference Include="SixLabors.Shapes" Version="0.1.0-alpha0009" />
<PackageReference Include="SixLabors.Fonts" Version="0.1.0-alpha0005" />
<PackageReference Include="SixLabors.Shapes" Version="0.1.0-alpha0010" />
</ItemGroup>
<PropertyGroup>
<CodeAnalysisRuleSet>..\..\ImageSharp.ruleset</CodeAnalysisRuleSet>

7
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<SixLabors.Shapes.IPath> shapesToDraw = glyphBuilder.Paths;

6
src/ImageSharp.Drawing/Text/TextGraphicsOptions.cs

@ -41,6 +41,11 @@ namespace ImageSharp.Drawing
/// </summary>
public bool UseImageResolution;
/// <summary>
/// If greater than zero determine the width at which text should wrap.
/// </summary>
public float WrapTextWidth;
/// <summary>
/// Initializes a new instance of the <see cref="TextGraphicsOptions" /> struct.
/// </summary>
@ -52,6 +57,7 @@ namespace ImageSharp.Drawing
this.TabWidth = 4;
this.AntialiasSubpixelDepth = 16;
this.UseImageResolution = false;
this.WrapTextWidth = 0;
}
/// <summary>

15
src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuser.cs

@ -71,8 +71,19 @@ namespace ImageSharp.Dithering
public void Dither<TColor>(PixelAccessor<TColor> pixels, TColor source, TColor transformed, int x, int y, int width, int height)
where TColor : struct, IPixel<TColor>
{
// Assign the transformed pixel to the array.
pixels[x, y] = transformed;
this.Dither(pixels, source, transformed, x, y, width, height, true);
}
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Dither<TColor>(PixelAccessor<TColor> pixels, TColor source, TColor transformed, int x, int y, int width, int height, bool replacePixel)
where TColor : struct, IPixel<TColor>
{
if (replacePixel)
{
// Assign the transformed pixel to the array.
pixels[x, y] = transformed;
}
// Calculate the error
Vector4 error = source.ToVector4() - transformed.ToVector4();

18
src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs

@ -25,5 +25,23 @@ namespace ImageSharp.Dithering
/// <typeparam name="TColor">The pixel format.</typeparam>
void Dither<TColor>(PixelAccessor<TColor> pixels, TColor source, TColor transformed, int x, int y, int width, int height)
where TColor : struct, IPixel<TColor>;
/// <summary>
/// Transforms the image applying the dither matrix. This method alters the input pixels array
/// </summary>
/// <param name="pixels">The pixel accessor </param>
/// <param name="source">The source pixel</param>
/// <param name="transformed">The transformed pixel</param>
/// <param name="x">The column index.</param>
/// <param name="y">The row index.</param>
/// <param name="width">The image width.</param>
/// <param name="height">The image height.</param>
/// <param name="replacePixel">
/// 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.
/// </param>
/// <typeparam name="TColor">The pixel format.</typeparam>
void Dither<TColor>(PixelAccessor<TColor> pixels, TColor source, TColor transformed, int x, int y, int width, int height, bool replacePixel)
where TColor : struct, IPixel<TColor>;
}
}

6
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
}
}
}
}
}

48
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<TColor>();
this.quantizer = new WuQuantizer<TColor>();
}
// 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<byte> transparentPixels = new List<byte>();
byte pixelCount = palette.Length.ToByte();
// Get max colors for bit depth.
int colorTableLength = (int)Math.Pow(2, header.BitDepth) * 3;
byte[] colorTable = ArrayPool<byte>.Shared.Rent(colorTableLength);
byte[] alphaTable = ArrayPool<byte>.Shared.Rent(pixelCount);
byte[] bytes = ArrayPool<byte>.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<byte>.Shared.Return(colorTable);
ArrayPool<byte>.Shared.Return(alphaTable);
ArrayPool<byte>.Shared.Return(bytes);
}
// Write the transparency data
if (transparentPixels.Any())
{
this.WriteChunk(stream, PngChunkTypes.PaletteAlpha, transparentPixels.ToArray());
}
return quantized;
}

2
src/ImageSharp/Formats/Png/PngEncoderOptions.cs

@ -60,7 +60,7 @@ namespace ImageSharp.Formats
/// <summary>
/// Gets or sets the transparency threshold.
/// </summary>
public byte Threshold { get; set; } = 0;
public byte Threshold { get; set; } = 255;
/// <summary>
/// Gets or sets a value indicating whether this instance should write

4
src/ImageSharp/Image.FromStream.cs

@ -201,7 +201,7 @@ namespace ImageSharp
{
config = config ?? Configuration.Default;
Image<TColor> img = WithSeekableStream(stream, s => Decode<TColor>(stream, options, config));
Image<TColor> img = WithSeekableStream(stream, s => Decode<TColor>(s, options, config));
if (img != null)
{
@ -238,7 +238,7 @@ namespace ImageSharp
stream.CopyTo(ms);
ms.Position = 0;
return action(stream);
return action(ms);
}
}
}

7
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);

2
src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs

@ -168,7 +168,7 @@ namespace ImageSharp.Processing.Processors
/// <returns>The weights</returns>
public WeightsWindow GetWeightsWindow(int destIdx, int leftIdx, int rightIdx)
{
BufferSpan<float> span = this.dataBuffer.GetRowSpan(destIdx).Slice(leftIdx, rightIdx - leftIdx);
BufferSpan<float> span = this.dataBuffer.GetRowSpan(destIdx).Slice(leftIdx, rightIdx - leftIdx + 1);
return new WeightsWindow(leftIdx, span);
}
}

1
src/ImageSharp/Quantizers/Wu/Box.cs → src/ImageSharp/Quantizers/Box.cs

@ -7,6 +7,7 @@ namespace ImageSharp.Quantizers
{
/// <summary>
/// Represents a box color cube.
/// TODO: This should be a struct for performance
/// </summary>
internal sealed class Box
{

72
src/ImageSharp/Quantizers/Octree/OctreeQuantizer.cs → src/ImageSharp/Quantizers/OctreeQuantizer.cs

@ -58,18 +58,12 @@ namespace ImageSharp.Quantizers
public override QuantizedImage<TColor> Quantize(ImageBase<TColor> 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);
}
/// <summary>
/// Execute a second pass through the bitmap
/// </summary>
/// <param name="source">The source image.</param>
/// <param name="output">The output pixel array</param>
/// <param name="width">The width in pixels of the image</param>
/// <param name="height">The height in pixels of the image</param>
/// <inheritdoc/>
protected override void SecondPass(PixelAccessor<TColor> 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
}
}
/// <summary>
/// Process the pixel in the first pass of the algorithm
/// </summary>
/// <param name="pixel">
/// The pixel to quantize
/// </param>
/// <remarks>
/// This function need only be overridden if your quantize algorithm needs two passes,
/// such as an Octree quantizer.
/// </remarks>
/// <inheritdoc/>
protected override void InitialQuantizePixel(TColor pixel)
{
// Add the color to the Octree
this.octree.AddColor(pixel, this.pixelBuffer);
}
/// <summary>
/// Retrieve the palette for the quantized image.
/// </summary>
/// <returns>
/// The new color palette
/// </returns>
/// <inheritdoc/>
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)));
}
/// <summary>
@ -175,6 +150,7 @@ namespace ImageSharp.Quantizers
/// <returns>
/// The <see cref="int"/>
/// </returns>
[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
/// </summary>
// 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 };
/// <summary>
/// The root of the Octree
@ -379,11 +355,6 @@ namespace ImageSharp.Quantizers
/// </summary>
private int blue;
/// <summary>
/// Alpha component
/// </summary>
private int alpha;
/// <summary>
/// The index of this node in the palette
/// </summary>
@ -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
/// <returns>The number of leaves removed</returns>
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];
}
}
}
}
}
}

11
src/ImageSharp/Quantizers/Palette/PaletteQuantizer.cs → 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;
/// <summary>
@ -71,13 +70,7 @@ namespace ImageSharp.Quantizers
return base.Quantize(image, maxColors);
}
/// <summary>
/// Execute a second pass through the bitmap
/// </summary>
/// <param name="source">The source image.</param>
/// <param name="output">The output pixel array</param>
/// <param name="width">The width in pixels of the image</param>
/// <param name="height">The height in pixels of the image</param>
/// <inheritdoc/>
protected override void SecondPass(PixelAccessor<TColor> 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;

5
src/ImageSharp/Quantizers/Options/Quantization.cs → src/ImageSharp/Quantizers/Quantization.cs

@ -12,17 +12,20 @@ namespace ImageSharp
{
/// <summary>
/// An adaptive Octree quantizer. Fast with good quality.
/// The quantizer only supports a single alpha value.
/// </summary>
Octree,
/// <summary>
/// Xiaolin Wu's Color Quantizer which generates high quality output.
/// The quantizer supports multiple alpha values.
/// </summary>
Wu,
/// <summary>
/// Palette based, Uses the collection of web-safe colors by default.
/// The quantizer supports multiple alpha values.
/// </summary>
Palette
}
}
}

1
src/ImageSharp/Quantizers/QuantizedImage.cs

@ -6,7 +6,6 @@
namespace ImageSharp.Quantizers
{
using System;
using System.Threading.Tasks;
/// <summary>
/// Represents a quantized image where the pixels indexed by a color palette.

17
src/ImageSharp/Quantizers/Octree/Quantizer.cs → 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<TColor> clone = new Image<TColor>(image))
using (PixelAccessor<TColor> 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<TColor>(width, height, colorPalette, quantizedPixels);

36
src/ImageSharp/Quantizers/WuArrayPool.cs

@ -0,0 +1,36 @@
// <copyright file="WuArrayPool.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Quantizers
{
using System.Buffers;
/// <summary>
/// Provides array pooling for the <see cref="WuQuantizer{TColor}"/>.
/// This is a separate class so that the pools can be shared accross multiple generic quantizer instaces.
/// </summary>
internal static class WuArrayPool
{
/// <summary>
/// The long array pool.
/// </summary>
public static readonly ArrayPool<long> LongPool = ArrayPool<long>.Create(TableLength, 25);
/// <summary>
/// The float array pool.
/// </summary>
public static readonly ArrayPool<float> FloatPool = ArrayPool<float>.Create(TableLength, 5);
/// <summary>
/// The byte array pool.
/// </summary>
public static readonly ArrayPool<byte> BytePool = ArrayPool<byte>.Create(TableLength, 5);
/// <summary>
/// The table length. Matches the calculated value in <see cref="WuQuantizer{TColor}"/>
/// </summary>
private const int TableLength = 2471625;
}
}

441
src/ImageSharp/Quantizers/Wu/WuQuantizer.cs → 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;
/// <summary>
/// An implementation of Wu's color quantizer with alpha channel.
@ -30,7 +31,7 @@ namespace ImageSharp.Quantizers
/// </para>
/// </remarks>
/// <typeparam name="TColor">The pixel format.</typeparam>
public sealed class WuQuantizer<TColor> : IQuantizer<TColor>
public class WuQuantizer<TColor> : Quantizer<TColor>
where TColor : struct, IPixel<TColor>
{
/// <summary>
@ -59,103 +60,238 @@ namespace ImageSharp.Quantizers
private const int TableLength = IndexCount * IndexCount * IndexCount * IndexAlphaCount;
/// <summary>
/// The long array pool.
/// </summary>
private static readonly ArrayPool<long> LongPool = ArrayPool<long>.Create(TableLength, 25);
/// <summary>
/// The double array pool.
/// A buffer for storing pixels
/// </summary>
private static readonly ArrayPool<double> DoublePool = ArrayPool<double>.Create(TableLength, 5);
private readonly byte[] rgbaBuffer = new byte[4];
/// <summary>
/// The byte array pool.
/// A lookup table for colors
/// </summary>
private static readonly ArrayPool<byte> BytePool = ArrayPool<byte>.Create(TableLength, 5);
private readonly Dictionary<TColor, byte> colorMap = new Dictionary<TColor, byte>();
/// <summary>
/// Moment of <c>P(c)</c>.
/// </summary>
private readonly long[] vwt;
private long[] vwt;
/// <summary>
/// Moment of <c>r*P(c)</c>.
/// </summary>
private readonly long[] vmr;
private long[] vmr;
/// <summary>
/// Moment of <c>g*P(c)</c>.
/// </summary>
private readonly long[] vmg;
private long[] vmg;
/// <summary>
/// Moment of <c>b*P(c)</c>.
/// </summary>
private readonly long[] vmb;
private long[] vmb;
/// <summary>
/// Moment of <c>a*P(c)</c>.
/// </summary>
private readonly long[] vma;
private long[] vma;
/// <summary>
/// Moment of <c>c^2*P(c)</c>.
/// </summary>
private readonly double[] m2;
private float[] m2;
/// <summary>
/// Color space tag.
/// </summary>
private readonly byte[] tag;
private byte[] tag;
/// <summary>
/// A buffer for storing pixels
/// Maximum allowed color depth
/// </summary>
private readonly byte[] rgbaBuffer = new byte[4];
private int colors;
/// <summary>
/// The reduced image palette
/// </summary>
private TColor[] palette;
/// <summary>
/// The color cube representing the image palette
/// </summary>
private Box[] colorCube;
/// <summary>
/// Initializes a new instance of the <see cref="WuQuantizer{TColor}"/> class.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
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);
}
/// <inheritdoc/>
public QuantizedImage<TColor> Quantize(ImageBase<TColor> image, int maxColors)
public override QuantizedImage<TColor> Quantize(ImageBase<TColor> 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);
}
}
/// <inheritdoc/>
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;
}
/// <inheritdoc/>
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);
}
/// <inheritdoc/>
protected override void FirstPass(PixelAccessor<TColor> 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<TColor> imagePixels = image.Lock())
/// <inheritdoc/>
protected override void SecondPass(PixelAccessor<TColor> 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;
}
}
}
/// <summary>
/// Gets an index.
/// Gets the index index of the given color in the palette.
/// </summary>
/// <param name="r">The red value.</param>
/// <param name="g">The green value.</param>
/// <param name="b">The blue value.</param>
/// <param name="a">The alpha value.</param>
/// <returns>The index.</returns>
[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
/// <param name="cube">The cube.</param>
/// <param name="moment">The moment.</param>
/// <returns>The result.</returns>
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
}
}
/// <summary>
/// Clears the tables.
/// </summary>
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);
}
/// <summary>
/// Builds a 3-D color histogram of <c>counts, r/g/b, c^2</c>.
/// </summary>
/// <param name="pixels">The pixel accessor.</param>
private void Build3DHistogram(PixelAccessor<TColor> 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);
}
}
}
/// <summary>
/// Converts the histogram into moments so that we can rapidly calculate the sums of the above quantities over any desired box.
/// </summary>
@ -369,14 +456,14 @@ namespace ImageSharp.Quantizers
long[] volumeG = ArrayPool<long>.Shared.Rent(IndexCount * IndexAlphaCount);
long[] volumeB = ArrayPool<long>.Shared.Rent(IndexCount * IndexAlphaCount);
long[] volumeA = ArrayPool<long>.Shared.Rent(IndexCount * IndexAlphaCount);
double[] volume2 = ArrayPool<double>.Shared.Rent(IndexCount * IndexAlphaCount);
float[] volume2 = ArrayPool<float>.Shared.Rent(IndexCount * IndexAlphaCount);
long[] area = ArrayPool<long>.Shared.Rent(IndexAlphaCount);
long[] areaR = ArrayPool<long>.Shared.Rent(IndexAlphaCount);
long[] areaG = ArrayPool<long>.Shared.Rent(IndexAlphaCount);
long[] areaB = ArrayPool<long>.Shared.Rent(IndexAlphaCount);
long[] areaA = ArrayPool<long>.Shared.Rent(IndexAlphaCount);
double[] area2 = ArrayPool<double>.Shared.Rent(IndexAlphaCount);
float[] area2 = ArrayPool<float>.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<long>.Shared.Return(volumeG);
ArrayPool<long>.Shared.Return(volumeB);
ArrayPool<long>.Shared.Return(volumeA);
ArrayPool<double>.Shared.Return(volume2);
ArrayPool<float>.Shared.Return(volume2);
ArrayPool<long>.Shared.Return(area);
ArrayPool<long>.Shared.Return(areaR);
ArrayPool<long>.Shared.Return(areaG);
ArrayPool<long>.Shared.Return(areaB);
ArrayPool<long>.Shared.Return(areaA);
ArrayPool<double>.Shared.Return(area2);
ArrayPool<float>.Shared.Return(area2);
}
}
@ -469,15 +556,15 @@ namespace ImageSharp.Quantizers
/// Computes the weighted variance of a box cube.
/// </summary>
/// <param name="cube">The cube.</param>
/// <returns>The <see cref="double"/>.</returns>
private double Variance(Box cube)
/// <returns>The <see cref="float"/>.</returns>
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
/// <param name="wholeB">The whole blue.</param>
/// <param name="wholeA">The whole alpha.</param>
/// <param name="wholeW">The whole weight.</param>
/// <returns>The <see cref="double"/>.</returns>
private double Maximize(Box cube, int direction, int first, int last, out int cut, double wholeR, double wholeG, double wholeB, double wholeA, double wholeW)
/// <returns>The <see cref="float"/>.</returns>
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>Returns a value indicating whether the box has been split.</returns>
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
/// <summary>
/// Builds the cube.
/// </summary>
/// <param name="cube">The cube.</param>
/// <param name="colorCount">The color count.</param>
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;
}
}
}
/// <summary>
/// Generates the quantized result.
/// Process the pixel in the second pass of the algorithm
/// </summary>
/// <param name="imagePixels">The image pixels.</param>
/// <param name="colorCount">The color count.</param>
/// <param name="cube">The cube.</param>
/// <returns>The result.</returns>
private QuantizedImage<TColor> GenerateResult(PixelAccessor<TColor> imagePixels, int colorCount, Box[] cube)
/// <param name="pixel">The pixel to quantize</param>
/// <returns>
/// The quantized value
/// </returns>
[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<byte>.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<byte>.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<TColor>(width, height, pallette, pixels);
return this.tag[GetPaletteIndex(r + 1, g + 1, b + 1, a + 1)];
}
}
}

3
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
};

44
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<TColor>(TestImageProvider<TColor> provider)
// where TColor : struct, IPixel<TColor>
//{
// // does saving a file then repoening mean both files are identical???
// using (Image<TColor> 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<Color> pixels1 = img1.Lock())
// using (PixelAccessor<Color> 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<TColor>(TestImageProvider<TColor> provider)

14
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()
{

50
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();
}
}
}

18
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();

2
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";
}
}
}

BIN
tests/ImageSharp.Tests/TestImages/Formats/Gif/trans.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
tests/ImageSharp.Tests/TestImages/Formats/Jpg/baseline/ExifUndefType.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Loading…
Cancel
Save