Browse Source

Quantizers now implement threshold.

Former-commit-id: fb4a043db98f56ae1261cb3a7c3b1044798d5f16
Former-commit-id: 6abe27ce1f96183dbd6e1bcd76bb2af0654d21bd
Former-commit-id: 9f742e5ca28a2adb62976f9b73fed8b7d773da16
af/merge-core
James Jackson-South 10 years ago
parent
commit
56b9d15017
  1. 12
      src/ImageProcessorCore/Formats/Gif/GifEncoder.cs
  2. 5
      src/ImageProcessorCore/Quantizers/IQuantizer.cs
  3. 5
      src/ImageProcessorCore/Quantizers/Octree/OctreeQuantizer.cs
  4. 42
      src/ImageProcessorCore/Quantizers/Octree/Quantizer.cs
  5. 21
      src/ImageProcessorCore/Quantizers/Palette/PaletteQuantizer.cs
  6. 45
      src/ImageProcessorCore/Quantizers/Wu/WuQuantizer.cs
  7. 2
      tests/ImageProcessorCore.Tests/Processors/Formats/EncoderDecoderTests.cs

12
src/ImageProcessorCore/Formats/Gif/GifEncoder.cs

@ -23,10 +23,15 @@ namespace ImageProcessorCore.Formats
/// <remarks>For gifs the value ranges from 1 to 256.</remarks> /// <remarks>For gifs the value ranges from 1 to 256.</remarks>
public int Quality { get; set; } public int Quality { get; set; }
/// <summary>
/// Gets or sets the transparency threshold.
/// </summary>
public byte Threshold { get; set; } = 128;
/// <summary> /// <summary>
/// The quantizer for reducing the color count. /// The quantizer for reducing the color count.
/// </summary> /// </summary>
public IQuantizer Quantizer { get; set; } = new OctreeQuantizer(); public IQuantizer Quantizer { get; set; }
/// <inheritdoc/> /// <inheritdoc/>
public string Extension => "gif"; public string Extension => "gif";
@ -51,6 +56,11 @@ namespace ImageProcessorCore.Formats
Image image = (Image)imageBase; Image image = (Image)imageBase;
if (this.Quantizer == null)
{
this.Quantizer = new OctreeQuantizer { Threshold = this.Threshold };
}
// Write the header. // Write the header.
// File Header signature and version. // File Header signature and version.
this.WriteString(stream, GifConstants.FileType); this.WriteString(stream, GifConstants.FileType);

5
src/ImageProcessorCore/Quantizers/IQuantizer.cs

@ -10,6 +10,11 @@ namespace ImageProcessorCore.Quantizers
/// </summary> /// </summary>
public interface IQuantizer public interface IQuantizer
{ {
/// <summary>
/// Gets or sets the transparency threshold.
/// </summary>
byte Threshold { get; set; }
/// <summary> /// <summary>
/// Quantize an image and return the resulting output pixels. /// Quantize an image and return the resulting output pixels.
/// </summary> /// </summary>

5
src/ImageProcessorCore/Quantizers/Octree/OctreeQuantizer.cs

@ -36,11 +36,6 @@ namespace ImageProcessorCore.Quantizers
{ {
} }
/// <summary>
/// Gets or sets the transparency threshold.
/// </summary>
public byte Threshold { get; set; } = 128;
/// <inheritdoc/> /// <inheritdoc/>
public override QuantizedImage Quantize(ImageBase image, int maxColors) public override QuantizedImage Quantize(ImageBase image, int maxColors)
{ {

42
src/ImageProcessorCore/Quantizers/Octree/Quantizer.cs

@ -7,6 +7,7 @@ namespace ImageProcessorCore.Quantizers
{ {
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks;
/// <summary> /// <summary>
/// Encapsulates methods to calculate the color palette of an image. /// Encapsulates methods to calculate the color palette of an image.
@ -39,6 +40,9 @@ namespace ImageProcessorCore.Quantizers
/// </summary> /// </summary>
public int TransparentIndex { get; protected set; } = -1; public int TransparentIndex { get; protected set; } = -1;
/// <inheritdoc/>
public byte Threshold { get; set; }
/// <inheritdoc/> /// <inheritdoc/>
public virtual QuantizedImage Quantize(ImageBase image, int maxColors) public virtual QuantizedImage Quantize(ImageBase image, int maxColors)
{ {
@ -95,35 +99,17 @@ namespace ImageProcessorCore.Quantizers
/// <param name="height">The height in pixels of the image</param> /// <param name="height">The height in pixels of the image</param>
protected virtual void SecondPass(ImageBase source, byte[] output, int width, int height) protected virtual void SecondPass(ImageBase source, byte[] output, int width, int height)
{ {
int i = 0; Parallel.For(
0,
// Convert the first pixel, so that I have values going into the loop. source.Height,
// Implicit cast here from Color. y =>
Bgra32 previousPixel = source[0, 0];
byte pixelValue = this.QuantizePixel(previousPixel);
output[0] = pixelValue;
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
Bgra32 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 (sourcePixel != previousPixel)
{ {
// Quantize the pixel for (int x = 0; x < source.Width; x++)
pixelValue = this.QuantizePixel(sourcePixel); {
Bgra32 sourcePixel = source[x, y];
// And setup the previous pointer output[(y * source.Width) + x] = this.QuantizePixel(sourcePixel);
previousPixel = sourcePixel; }
} });
output[i++] = pixelValue;
}
}
} }
/// <summary> /// <summary>

21
src/ImageProcessorCore/Quantizers/Palette/PaletteQuantizer.cs

@ -35,9 +35,16 @@ namespace ImageProcessorCore.Quantizers
public PaletteQuantizer(Color[] palette = null) public PaletteQuantizer(Color[] palette = null)
: base(true) : base(true)
{ {
this.colors = palette == null if (palette == null)
? ColorConstants.WebSafeColors.Select(c => (Bgra32)c).ToArray() {
: palette.Select(c => (Bgra32)c).ToArray(); List<Bgra32> safe = ColorConstants.WebSafeColors.Select(c => (Bgra32)c).ToList();
safe.Insert(0, Bgra32.Empty);
this.colors = safe.ToArray();
}
else
{
this.colors = palette.Select(c => (Bgra32)c).ToArray();
}
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -61,8 +68,8 @@ namespace ImageProcessorCore.Quantizers
else else
{ {
// Not found - loop through the palette and find the nearest match. // Not found - loop through the palette and find the nearest match.
// Firstly check the alpha value - if 0, lookup the transparent color // Firstly check the alpha value - if less than the threshold, lookup the transparent color
if (pixel.A == 0) if (!(pixel.A > this.Threshold))
{ {
// Transparent. Lookup the first color with an alpha value of 0 // Transparent. Lookup the first color with an alpha value of 0
for (int index = 0; index < this.colors.Length; index++) for (int index = 0; index < this.colors.Length; index++)
@ -91,7 +98,7 @@ namespace ImageProcessorCore.Quantizers
int redDistance = paletteColor.R - red; int redDistance = paletteColor.R - red;
int greenDistance = paletteColor.G - green; int greenDistance = paletteColor.G - green;
int blueDistance = paletteColor.B - blue; int blueDistance = paletteColor.B - blue;
int distance = (redDistance * redDistance) + int distance = (redDistance * redDistance) +
(greenDistance * greenDistance) + (greenDistance * greenDistance) +
(blueDistance * blueDistance); (blueDistance * blueDistance);
@ -111,7 +118,7 @@ namespace ImageProcessorCore.Quantizers
} }
// Now I have the color, pop it into the cache for next time // Now I have the color, pop it into the cache for next time
this.colorMap.Add(colorHash, colorIndex); this.colorMap[colorHash] = colorIndex;
} }
return colorIndex; return colorIndex;

45
src/ImageProcessorCore/Quantizers/Wu/WuQuantizer.cs

@ -7,6 +7,7 @@ namespace ImageProcessorCore.Quantizers
{ {
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks;
/// <summary> /// <summary>
/// An implementation of Wu's color quantizer with alpha channel. /// An implementation of Wu's color quantizer with alpha channel.
@ -110,6 +111,9 @@ namespace ImageProcessorCore.Quantizers
this.tag = new byte[TableLength]; this.tag = new byte[TableLength];
} }
/// <inheritdoc/>
public byte Threshold { get; set; }
/// <inheritdoc/> /// <inheritdoc/>
public QuantizedImage Quantize(ImageBase image, int maxColors) public QuantizedImage Quantize(ImageBase image, int maxColors)
{ {
@ -730,7 +734,7 @@ namespace ImageProcessorCore.Quantizers
byte b = (byte)(Volume(cube[k], this.vmb) / weight); byte b = (byte)(Volume(cube[k], this.vmb) / weight);
byte a = (byte)(Volume(cube[k], this.vma) / weight); byte a = (byte)(Volume(cube[k], this.vma) / weight);
var color = new Bgra32(b, g, r, a); Bgra32 color = new Bgra32(b, g, r, a);
if (color == Bgra32.Empty) if (color == Bgra32.Empty)
{ {
@ -746,22 +750,29 @@ namespace ImageProcessorCore.Quantizers
} }
} }
// TODO: Optimize here. Parallel.For(
int i = 0; 0,
for (int y = 0; y < image.Height; y++) image.Height,
{ y =>
for (int x = 0; x < image.Width; x++) {
{ for (int x = 0; x < image.Width; x++)
Bgra32 color = image[x, y]; {
int a = color.A >> (8 - IndexAlphaBits); Bgra32 color = image[x, y];
int r = color.R >> (8 - IndexBits); int a = color.A >> (8 - IndexAlphaBits);
int g = color.G >> (8 - IndexBits); int r = color.R >> (8 - IndexBits);
int b = color.B >> (8 - IndexBits); int g = color.G >> (8 - IndexBits);
int b = color.B >> (8 - IndexBits);
int ind = GetPaletteIndex(r + 1, g + 1, b + 1, a + 1);
pixels[i++] = this.tag[ind]; if (transparentIndex > -1 && color.A <= this.Threshold)
} {
} pixels[(y * image.Width) + x] = (byte)transparentIndex;
continue;
}
int ind = GetPaletteIndex(r + 1, g + 1, b + 1, a + 1);
pixels[(y * image.Width) + x] = this.tag[ind];
}
});
return new QuantizedImage(image.Width, image.Height, pallette.ToArray(), pixels, transparentIndex); return new QuantizedImage(image.Width, image.Height, pallette.ToArray(), pixels, transparentIndex);
} }

2
tests/ImageProcessorCore.Tests/Processors/Formats/EncoderDecoderTests.cs

@ -62,7 +62,7 @@
using (FileStream output = File.OpenWrite($"TestOutput/Quantize/{Path.GetFileName(file)}")) using (FileStream output = File.OpenWrite($"TestOutput/Quantize/{Path.GetFileName(file)}"))
{ {
quantizedImage.ToImage().Save(output); quantizedImage.ToImage().Save(output, image.CurrentImageFormat);
} }
} }
} }

Loading…
Cancel
Save