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

5
src/ImageProcessorCore/Quantizers/IQuantizer.cs

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

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

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

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

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

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

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

Loading…
Cancel
Save