Browse Source

Rewriting quantizer, fixing resize error.

Former-commit-id: 3015452932f8cca4e0f756ee75d0cdc032ba6a38
Former-commit-id: 459d68ea0a563da7a1108c9e0d6196f91e702fe9
pull/17/head
James South 11 years ago
parent
commit
e06030a38c
  1. 8
      src/ImageProcessor.Playground/Program.cs
  2. 8
      src/ImageProcessor/ImageProcessor.csproj
  3. 52
      src/ImageProcessor/Imaging/Colors/Color32.cs
  4. 20
      src/ImageProcessor/Imaging/FastBitmap.cs
  5. 4
      src/ImageProcessor/Imaging/Formats/FormatUtilities.cs
  6. 2
      src/ImageProcessor/Imaging/Formats/GifFormat.cs
  7. 2
      src/ImageProcessor/Imaging/Formats/PngFormat.cs
  8. 198
      src/ImageProcessor/Imaging/Quantizers/OctreeQuantizer.cs
  9. 201
      src/ImageProcessor/Imaging/Quantizers/Quantizer.cs
  10. 12
      src/ImageProcessor/Processors/Resize.cs

8
src/ImageProcessor.Playground/Program.cs

@ -50,7 +50,7 @@ namespace ImageProcessor.PlayGround
}
Image mask = Image.FromFile(Path.Combine(resolvedPath, "mask2.png"));
IEnumerable<FileInfo> files = GetFilesByExtensions(di, ".jpg");
IEnumerable<FileInfo> files = GetFilesByExtensions(di, ".gif");
//IEnumerable<FileInfo> files = GetFilesByExtensions(di, ".gif", ".webp", ".bmp", ".jpg", ".png", ".tif");
foreach (FileInfo fileInfo in files)
@ -66,7 +66,7 @@ namespace ImageProcessor.PlayGround
{
using (ImageFactory imageFactory = new ImageFactory(true))
{
Size size = new Size(800, 0);
Size size = new Size(200, 0);
ResizeLayer layer = new ResizeLayer(size, ResizeMode.Max, AnchorPosition.Center, false);
//ContentAwareResizeLayer layer = new ContentAwareResizeLayer(size)
@ -79,7 +79,7 @@ namespace ImageProcessor.PlayGround
//.Resize(new Size((int)(size.Width * 1.1), 0))
//.ContentAwareResize(layer)
//.Constrain(size)
.Rotate(66)
//.Rotate(66)
//.Mask(mask)
//.Format(new PngFormat())
//.BackgroundColor(Color.Cyan)
@ -89,7 +89,7 @@ namespace ImageProcessor.PlayGround
//.DetectEdges(new LaplacianOfGaussianEdgeFilter())
//.EntropyCrop()
//.Filter(MatrixFilters.Invert)
//.Filter(MatrixFilters.Comic)
.Filter(MatrixFilters.Comic)
//.Filter(MatrixFilters.HiSatch)
//.Pixelate(8)
//.GaussianSharpen(10)

8
src/ImageProcessor/ImageProcessor.csproj

@ -131,6 +131,9 @@
<Compile Include="Common\Extensions\DoubleExtensions.cs" />
<Compile Include="Configuration\NativeBinaryFactory.cs" />
<Compile Include="Configuration\NativeMethods.cs" />
<Compile Include="Imaging\Colors\Color32.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Imaging\Colors\HslaColor.cs" />
<Compile Include="Imaging\Colors\RgbaColor.cs" />
<Compile Include="Imaging\Colors\RgbaComponent.cs" />
@ -177,8 +180,8 @@
<Compile Include="Imaging\Formats\GifFrame.cs" />
<Compile Include="Imaging\Helpers\Adjustments.cs" />
<Compile Include="Imaging\Helpers\Effects.cs" />
<Compile Include="Imaging\PixelData.cs" />
<Compile Include="Imaging\Quantizer.cs" />
<Compile Include="Imaging\Quantizers\OctreeQuantizer.cs" />
<Compile Include="Imaging\Quantizers\Quantizer.cs" />
<Compile Include="Imaging\ResizeLayer.cs" />
<Compile Include="Imaging\Filters\Photo\BlackWhiteMatrixFilter.cs" />
<Compile Include="Imaging\Filters\Photo\ColorMatrixes.cs" />
@ -196,7 +199,6 @@
<Compile Include="Imaging\Resizer.cs" />
<Compile Include="Imaging\RoundedCornerLayer.cs" />
<Compile Include="Imaging\TextLayer.cs" />
<Compile Include="Imaging\OctreeQuantizer.cs" />
<Compile Include="Processors\Alpha.cs" />
<Compile Include="Processors\EntropyCrop.cs" />
<Compile Include="Processors\BackgroundColor.cs" />

52
src/ImageProcessor/Imaging/PixelData.cs → src/ImageProcessor/Imaging/Colors/Color32.cs

@ -1,47 +1,67 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="PixelData.cs" company="James South">
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="Color32.cs" company="James South">
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// Contains the component parts that make up a single pixel.
// Structure that defines a 32 bit color
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Imaging
namespace ImageProcessor.Imaging.Colors
{
using System.Drawing;
using System.Runtime.InteropServices;
/// <summary>
/// Contains the component parts that make up a single pixel.
/// Structure that defines a 32 bits per pixel color, used for pixel manipulation not for color conversion.
/// </summary>
/// <remarks>
/// This structure is used to read data from a 32 bits per pixel image
/// in memory, and is ordered in this manner as this is the way that
/// the data is laid out in memory
/// </remarks>
[StructLayout(LayoutKind.Explicit)]
public struct PixelData
public struct Color32
{
/// <summary>
/// The blue component.
/// Holds the blue component of the colour
/// </summary>
[FieldOffset(0)]
public byte B;
/// <summary>
/// The green component.
/// Holds the green component of the colour
/// </summary>
[FieldOffset(1)]
public byte G;
/// <summary>
/// The red component.
/// Holds the red component of the colour
/// </summary>
[FieldOffset(2)]
public byte R;
/// <summary>
/// The alpha component.
/// Holds the alpha component of the colour
/// </summary>
[FieldOffset(3)]
public byte A;
/// <summary>
/// Permits the color32 to be treated as a 32 bit integer.
/// </summary>
[FieldOffset(0)]
public int ARGB;
/// <summary>
/// Gets the color for this Color32 object
/// </summary>
public Color Color
{
get { return Color.FromArgb(this.A, this.R, this.G, this.B); }
}
/// <summary>
/// Indicates whether this instance and a specified object are equal.
/// </summary>
@ -51,11 +71,11 @@ namespace ImageProcessor.Imaging
/// <param name="obj">Another object to compare to. </param>
public override bool Equals(object obj)
{
if (obj is PixelData)
if (obj is Color32)
{
PixelData pixelData = (PixelData)obj;
Color32 color32 = (Color32)obj;
return this.B == pixelData.B && this.G == pixelData.G & this.R == pixelData.R & this.A == pixelData.A;
return this.B == color32.B && this.G == color32.G & this.R == color32.R & this.A == color32.A;
}
return false;
@ -76,12 +96,12 @@ namespace ImageProcessor.Imaging
/// Returns the hash code for the given instance.
/// </summary>
/// <param name="obj">
/// The instance of <see cref="PixelData"/> to return the hash code for.
/// The instance of <see cref="Color32"/> to return the hash code for.
/// </param>
/// <returns>
/// A 32-bit signed integer that is the hash code for this instance.
/// </returns>
private int GetHashCode(PixelData obj)
private int GetHashCode(Color32 obj)
{
unchecked
{
@ -93,4 +113,4 @@ namespace ImageProcessor.Imaging
}
}
}
}
}

20
src/ImageProcessor/Imaging/FastBitmap.cs

@ -14,6 +14,8 @@ namespace ImageProcessor.Imaging
using System.Drawing;
using System.Drawing.Imaging;
using ImageProcessor.Imaging.Colors;
/// <summary>
/// Allows fast access to <see cref="System.Drawing.Bitmap"/>'s pixel data.
/// </summary>
@ -40,9 +42,9 @@ namespace ImageProcessor.Imaging
private int bytesInARow;
/// <summary>
/// The size of the pixel data.
/// The size of the color32 structure.
/// </summary>
private int pixelDataSize;
private int color32Size;
/// <summary>
/// The bitmap data.
@ -111,11 +113,11 @@ namespace ImageProcessor.Imaging
/// The y position of the pixel.
/// </param>
/// <returns>
/// The <see cref="PixelData"/>.
/// The <see cref="Color32"/>.
/// </returns>
private PixelData* this[int x, int y]
private Color32* this[int x, int y]
{
get { return (PixelData*)(this.pixelBuffer + (y * this.bytesInARow) + (x * this.pixelDataSize)); }
get { return (Color32*)(this.pixelBuffer + (y * this.bytesInARow) + (x * this.color32Size)); }
}
/// <summary>
@ -167,7 +169,7 @@ namespace ImageProcessor.Imaging
throw new ArgumentOutOfRangeException("y", "Value cannot be less than zero or greater than the bitmap height.");
}
#endif
PixelData* data = this[x, y];
Color32* data = this[x, y];
return Color.FromArgb(data->A, data->R, data->G, data->B);
}
@ -193,7 +195,7 @@ namespace ImageProcessor.Imaging
throw new ArgumentOutOfRangeException("y", "Value cannot be less than zero or greater than the bitmap height.");
}
#endif
PixelData* data = this[x, y];
Color32* data = this[x, y];
data->R = color.R;
data->G = color.G;
data->B = color.B;
@ -278,8 +280,8 @@ namespace ImageProcessor.Imaging
// Figure out the number of bytes in a row. This is rounded up to be a multiple
// of 4 bytes, since a scan line in an image must always be a multiple of 4 bytes
// in length.
this.pixelDataSize = sizeof(PixelData);
this.bytesInARow = bounds.Width * this.pixelDataSize;
this.color32Size = sizeof(Color32);
this.bytesInARow = bounds.Width * this.color32Size;
if (this.bytesInARow % 4 != 0)
{
this.bytesInARow = 4 * ((this.bytesInARow / 4) + 1);

4
src/ImageProcessor/Imaging/Formats/FormatUtilities.cs

@ -135,7 +135,7 @@ namespace ImageProcessor.Imaging.Formats
int frameCount = image.GetFrameCount(FrameDimension.Time);
int last = frameCount - 1;
int length = 0;
List<GifFrame> gifFrames = new List<GifFrame>();
// Get the times stored in the gif.
@ -152,7 +152,7 @@ namespace ImageProcessor.Imaging.Formats
image.SelectActiveFrame(FrameDimension.Time, i);
// TODO: Get positions.
gifFrames.Add(new GifFrame { Delay = delay, Image = (Image)image.Clone() });
gifFrames.Add(new GifFrame { Delay = delay, Image = new Bitmap(image) });
// Reset the position.
if (i == last)

2
src/ImageProcessor/Imaging/Formats/GifFormat.cs

@ -16,6 +16,8 @@ namespace ImageProcessor.Imaging.Formats
using System.IO;
using System.Text;
using ImageProcessor.Imaging.Quantizers;
/// <summary>
/// Provides the necessary information to support gif images.
/// </summary>

2
src/ImageProcessor/Imaging/Formats/PngFormat.cs

@ -14,6 +14,8 @@ namespace ImageProcessor.Imaging.Formats
using System.Drawing.Imaging;
using System.IO;
using ImageProcessor.Imaging.Quantizers;
/// <summary>
/// Provides the necessary information to support png images.
/// </summary>

198
src/ImageProcessor/Imaging/OctreeQuantizer.cs → src/ImageProcessor/Imaging/Quantizers/OctreeQuantizer.cs

@ -4,29 +4,33 @@
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// Encapsulates methods to calculate the colour palette if an image using an octree pattern.
// Encapsulates methods to calculate the colour palette if an image using an Octree pattern.
// <see href="http://msdn.microsoft.com/en-us/library/aa479306.aspx" />
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Imaging
namespace ImageProcessor.Imaging.Quantizers
{
using System;
using System.Collections;
using System.Drawing;
using System.Drawing.Imaging;
using ImageProcessor.Imaging.Colors;
/// <summary>
/// Encapsulates methods to calculate the colour palette if an image using an octree pattern.
/// Encapsulates methods to calculate the colour palette if an image using an Octree pattern.
/// <see href="http://msdn.microsoft.com/en-us/library/aa479306.aspx"/>
/// </summary>
public class OctreeQuantizer : Quantizer
public unsafe class OctreeQuantizer : Quantizer
{
/// <summary>
/// Stores the tree.
/// Stores the tree
/// </summary>
private readonly Octree octree;
/// <summary>
/// The maximum allowed color depth.
/// Maximum allowed color depth
/// </summary>
private readonly int maxColors;
@ -34,7 +38,7 @@ namespace ImageProcessor.Imaging
/// Initializes a new instance of the <see cref="OctreeQuantizer"/> class.
/// </summary>
/// <remarks>
/// The Octree quantizer is a two pass algorithm. The initial pass sets up the octree,
/// The Octree quantizer is a two pass algorithm. The initial pass sets up the Octree,
/// the second pass quantizes a color based on the nodes in the tree
/// </remarks>
/// <param name="maxColors">
@ -56,22 +60,23 @@ namespace ImageProcessor.Imaging
throw new ArgumentOutOfRangeException("maxColorBits", maxColorBits, "This should be between 1 and 8");
}
// Construct the octree
// Construct the Octree
this.octree = new Octree(maxColorBits);
this.maxColors = maxColors;
}
/// <summary>
/// Override this to process the pixel in the first pass of the algorithm
/// 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>
protected override void InitialQuantizePixel(Color32 pixel)
protected override void InitialQuantizePixel(Color32* pixel)
{
// Add the color to the octree
// Add the color to the Octree
this.octree.AddColor(pixel);
}
@ -79,16 +84,14 @@ namespace ImageProcessor.Imaging
/// Override this to process the pixel in the second pass of the algorithm
/// </summary>
/// <param name="pixel">The pixel to quantize</param>
/// <returns>
/// The quantized value
/// </returns>
protected override byte QuantizePixel(Color32 pixel)
/// <returns>The quantized value</returns>
protected override byte QuantizePixel(Color32* pixel)
{
// The color at [_maxColors] is set to transparent
byte paletteIndex = (byte)this.maxColors;
// Get the palette index if this non-transparent
if (pixel.Alpha > 0)
if (pixel->A > 0)
{
paletteIndex = (byte)this.octree.GetPaletteIndex(pixel);
}
@ -100,41 +103,26 @@ namespace ImageProcessor.Imaging
/// Retrieve the palette for the quantized image
/// </summary>
/// <param name="original">Any old palette, this is overwritten</param>
/// <returns>
/// The new color palette
/// </returns>
/// <returns>The new color palette</returns>
protected override ColorPalette GetPalette(ColorPalette original)
{
// First off convert the octree to _maxColors colors
// First off convert the Octree to _maxColors colors
ArrayList palette = this.octree.Palletize(this.maxColors - 1);
// Then convert the palette based on those colors
for (int index = 0; index < palette.Count; index++)
{
Color testColor = (Color)palette[index];
// Test set transparent color when color transparency used
if (testColor.ToArgb() == Color.Transparent.ToArgb())
{
testColor = Color.FromArgb(0, 0, 0, 0);
}
original.Entries[index] = testColor;
original.Entries[index] = (Color)palette[index];
}
// Clear unused palette entries
for (int index = palette.Count; index < this.maxColors; index++)
{
original.Entries[index] = Color.FromArgb(255, 0, 0, 0);
}
// Add the transparent color
original.Entries[this.maxColors] = Color.FromArgb(0, 0, 0, 0);
// Add the transparent color when alpha transparency used
original.Entries[this.maxColors] = Color.FromArgb(0, Color.Transparent);
return original;
}
/// <summary>
/// Describes a tree data structure in which each internal node has exactly eight children.
/// Class which does the actual quantization
/// </summary>
private class Octree
{
@ -144,7 +132,7 @@ namespace ImageProcessor.Imaging
private static readonly int[] Mask = { 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 };
/// <summary>
/// The root of the octree
/// The root of the Octree
/// </summary>
private readonly OctreeNode root;
@ -199,21 +187,29 @@ namespace ImageProcessor.Imaging
}
/// <summary>
/// Add a given colour value to the octree
/// Gets the array of reducible nodes
/// </summary>
private OctreeNode[] ReducibleNodes
{
get { return this.reducibleNodes; }
}
/// <summary>
/// Add a given color value to the Octree
/// </summary>
/// <param name="pixel">
/// The color value to add.
/// The <see cref="Color32"/>containing color information to add.
/// </param>
public void AddColor(Color32 pixel)
public void AddColor(Color32* pixel)
{
// Check if this request is for the same color as the last
if (this.previousColor == pixel.Argb)
if (this.previousColor == pixel->ARGB)
{
// If so, check if I have a previous node setup. This will only ocurr if the first color in the image
// If so, check if I have a previous node setup. This will only occur if the first color in the image
// happens to be black, with an alpha component of zero.
if (null == this.previousNode)
{
this.previousColor = pixel.Argb;
this.previousColor = pixel->ARGB;
this.root.AddColor(pixel, this.maxColorBits, 0, this);
}
else
@ -224,16 +220,16 @@ namespace ImageProcessor.Imaging
}
else
{
this.previousColor = pixel.Argb;
this.previousColor = pixel->ARGB;
this.root.AddColor(pixel, this.maxColorBits, 0, this);
}
}
/// <summary>
/// Convert the nodes in the octree to a palette with a maximum of colorCount colors
/// Convert the nodes in the Octree to a palette with a maximum of colorCount colors
/// </summary>
/// <param name="colorCount">The maximum number of colors</param>
/// <returns>An array-list with the palletized colors</returns>
/// <returns>An <see cref="ArrayList"/> with the palletized colors</returns>
public ArrayList Palletize(int colorCount)
{
while (this.Leaves > colorCount)
@ -241,7 +237,7 @@ namespace ImageProcessor.Imaging
this.Reduce();
}
// Now palletized the nodes
// Now palletize the nodes
ArrayList palette = new ArrayList(this.Leaves);
int paletteIndex = 0;
this.root.ConstructPalette(palette, ref paletteIndex);
@ -251,30 +247,19 @@ namespace ImageProcessor.Imaging
}
/// <summary>
/// Get the palette index for the passed colour.
/// Get the palette index for the passed color
/// </summary>
/// <param name="pixel">
/// The color to return the palette index for.
/// The <see cref="Color32"/> containing the pixel data.
/// </param>
/// <returns>
/// The palette index for the passed colour.
/// The index of the given structure.
/// </returns>
public int GetPaletteIndex(Color32 pixel)
public int GetPaletteIndex(Color32* pixel)
{
return this.root.GetPaletteIndex(pixel, 0);
}
/// <summary>
/// Return the array of reducible nodes
/// </summary>
/// <returns>
/// The array of <see cref="OctreeNode"/>.
/// </returns>
protected OctreeNode[] ReducibleNodes()
{
return this.reducibleNodes;
}
/// <summary>
/// Keep track of the previous node that was quantized
/// </summary>
@ -289,8 +274,6 @@ namespace ImageProcessor.Imaging
/// </summary>
private void Reduce()
{
// Find the deepest level containing at least one reducible node
// for (index = _maxColorBits - 1; (index > 0) && (null == _reducibleNodes[index]); index--) ;
// Find the deepest level containing at least one reducible node
int index = this.maxColorBits - 1;
while ((index > 0) && (null == this.reducibleNodes[index]))
@ -385,15 +368,14 @@ namespace ImageProcessor.Imaging
else
{
// Otherwise add this to the reducible nodes
var repNodes = octree.ReducibleNodes();
this.nextReducible = repNodes[level];
repNodes[level] = this;
this.nextReducible = octree.ReducibleNodes[level];
octree.ReducibleNodes[level] = this;
this.children = new OctreeNode[8];
}
}
/// <summary>
/// Gets the next reducible node.
/// Gets the next reducible node
/// </summary>
public OctreeNode NextReducible
{
@ -407,7 +389,7 @@ namespace ImageProcessor.Imaging
/// <param name="colorBits">The number of significant color bits</param>
/// <param name="level">The level in the tree</param>
/// <param name="octree">The tree to which this node belongs</param>
public void AddColor(Color32 pixel, int colorBits, int level, Octree octree)
public void AddColor(Color32* pixel, int colorBits, int level, Octree octree)
{
// Update the color information if this is a leaf
if (this.leaf)
@ -419,26 +401,23 @@ namespace ImageProcessor.Imaging
}
else
{
checked
{
// Go to the next level down in the tree
int shift = 7 - level;
int index = ((pixel.Red & Mask[level]) >> (shift - 2)) |
((pixel.Green & Mask[level]) >> (shift - 1)) |
((pixel.Blue & Mask[level]) >> shift);
// Go to the next level down in the tree
int shift = 7 - level;
int index = ((pixel->R & Mask[level]) >> (shift - 2)) |
((pixel->G & Mask[level]) >> (shift - 1)) |
((pixel->B & Mask[level]) >> shift);
OctreeNode child = this.children[index];
OctreeNode child = this.children[index];
if (null == child)
{
// Create a new child node and store in the array
child = new OctreeNode(level + 1, colorBits, octree);
this.children[index] = child;
}
// Add the color to the child node
child.AddColor(pixel, colorBits, level + 1, octree);
if (null == child)
{
// Create a new child node & store in the array
child = new OctreeNode(level + 1, colorBits, octree);
this.children[index] = child;
}
// Add the color to the child node
child.AddColor(pixel, colorBits, level + 1, octree);
}
}
@ -501,38 +480,35 @@ namespace ImageProcessor.Imaging
}
/// <summary>
/// Return the palette index for the passed color.
/// Return the palette index for the passed color
/// </summary>
/// <param name="pixel">
/// The pixel.
/// The <see cref="Color32"/> representing the pixel.
/// </param>
/// <param name="level">
/// The level.
/// </param>
/// <returns>
/// The palette index for the passed color.
/// The <see cref="int"/> representing the index of the pixel in the palette.
/// </returns>
public int GetPaletteIndex(Color32 pixel, int level)
public int GetPaletteIndex(Color32* pixel, int level)
{
int index = this.paletteIndex;
if (!this.leaf)
{
checked
{
int shift = 7 - level;
int i = ((pixel.Red & Mask[level]) >> (shift - 2))
| ((pixel.Green & Mask[level]) >> (shift - 1))
| ((pixel.Blue & Mask[level]) >> shift);
int shift = 7 - level;
int pixelIndex = ((pixel->R & Mask[level]) >> (shift - 2)) |
((pixel->G & Mask[level]) >> (shift - 1)) |
((pixel->B & Mask[level]) >> shift);
if (null != this.children[i])
{
index = this.children[i].GetPaletteIndex(pixel, level + 1);
}
else
{
throw new ArgumentException("Didn't expect this!");
}
if (null != this.children[pixelIndex])
{
index = this.children[pixelIndex].GetPaletteIndex(pixel, level + 1);
}
else
{
throw new Exception("Didn't expect this!");
}
}
@ -543,16 +519,16 @@ namespace ImageProcessor.Imaging
/// Increment the pixel count and add to the color information
/// </summary>
/// <param name="pixel">
/// The pixel.
/// The pixel to add.
/// </param>
public void Increment(Color32 pixel)
public void Increment(Color32* pixel)
{
this.pixelCount++;
this.red += pixel.Red;
this.green += pixel.Green;
this.blue += pixel.Blue;
this.red += pixel->R;
this.green += pixel->G;
this.blue += pixel->B;
}
}
}
}
}
}

201
src/ImageProcessor/Imaging/Quantizer.cs → src/ImageProcessor/Imaging/Quantizers/Quantizer.cs

@ -4,53 +4,50 @@
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// Defines the Quantizer type.
// Encapsulates methods to calculate the color palette of an image.
// <see href="http://msdn.microsoft.com/en-us/library/aa479306.aspx" />
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Imaging
namespace ImageProcessor.Imaging.Quantizers
{
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using ImageProcessor.Imaging.Colors;
/// <summary>
/// Encapsulates methods to calculate the color palette of an image.
/// <see href="http://msdn.microsoft.com/en-us/library/aa479306.aspx"/>
/// </summary>
public abstract class Quantizer
public unsafe abstract class Quantizer
{
/// <summary>
/// The flag used to indicate whether a single pass or two passes are needed for quantization.
/// Flag used to indicate whether a single pass or two passes are needed for quantization.
/// </summary>
private readonly bool singlePass;
/// <summary>
/// The size in bytes of the 32 bytes per pixel Color structure.
/// </summary>
private readonly int pixelSize;
/// <summary>
/// Initializes a new instance of the <see cref="T:ImageProcessor.Imaging.Quantizer">Quantizer</see> class.
/// Initializes a new instance of the <see cref="Quantizer"/> class.
/// </summary>
/// <param name="singlePass">
/// If set to <see langword="true"/>, then the quantizer will loop through the source pixels once;
/// otherwise, <see langword="false"/>.
/// If true, the quantization only needs to loop through the source pixels once
/// </param>
/// <remarks>
/// If you construct this class with a true value for singlePass, then the code will, when quantizing your image,
/// only call the 'QuantizeImage' function. If two passes are required, the code will call 'InitialQuantizeImage'
/// and then 'QuantizeImage'.
/// </remarks>
protected Quantizer(bool singlePass)
{
this.singlePass = singlePass;
this.pixelSize = Marshal.SizeOf(typeof(Color32));
}
/// <summary>
/// Quantizes the given <see cref="T:System.Drawing.Image">Image</see> and returns the resulting output
/// <see cref="T:System.Drawing.Bitmap">Bitmap.</see>
/// Quantize an image and return the resulting output bitmap
/// </summary>
/// <param name="source">The image to quantize</param>
/// <returns>
/// A quantized <see cref="T:System.Drawing.Bitmap">Bitmap</see> version of the <see cref="T:System.Drawing.Image">Image</see>
/// </returns>
/// <returns>A quantized version of the image</returns>
public Bitmap Quantize(Image source)
{
// Get the size of the source image
@ -62,11 +59,9 @@ namespace ImageProcessor.Imaging
// First off take a 32bpp copy of the image
Bitmap copy = new Bitmap(width, height, PixelFormat.Format32bppArgb);
copy.SetResolution(source.HorizontalResolution, source.VerticalResolution);
// And construct an 8bpp version
Bitmap output = new Bitmap(width, height, PixelFormat.Format8bppIndexed);
output.SetResolution(source.HorizontalResolution, source.VerticalResolution);
// Now lock the bitmap into memory
using (Graphics g = Graphics.FromImage(copy))
@ -75,7 +70,7 @@ namespace ImageProcessor.Imaging
// Draw the source image onto the copy bitmap,
// which will effect a widening as appropriate.
g.DrawImage(source, bounds);
g.DrawImageUnscaled(source, bounds);
}
// Define a pointer to the bitmap data
@ -87,7 +82,7 @@ namespace ImageProcessor.Imaging
sourceData = copy.LockBits(bounds, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
// Call the FirstPass function if not a single pass algorithm.
// For something like an octree quantizer, this will run through
// For something like an Octree quantizer, this will run through
// all image pixels, build a data structure, and create a palette.
if (!this.singlePass)
{
@ -120,25 +115,24 @@ namespace ImageProcessor.Imaging
protected virtual void FirstPass(BitmapData sourceData, int width, int height)
{
// Define the source data pointers. The source row is a byte to
// keep addition of the stride value easier (as this is in bytes)
IntPtr sourceRow = sourceData.Scan0;
// keep addition of the stride value easier (as this is in bytes)
byte* sourceRow = (byte*)sourceData.Scan0.ToPointer();
// Loop through each row
for (int row = 0; row < height; row++)
{
// Set the source pixel to the first pixel in this row
IntPtr sourcePixel = sourceRow;
int* sourcePixel = (int*)sourceRow;
// And loop through each column
for (int col = 0; col < width; col++)
for (int col = 0; col < width; col++, sourcePixel++)
{
this.InitialQuantizePixel(new Color32(sourcePixel));
sourcePixel = (IntPtr)((long)sourcePixel + this.pixelSize);
// Now I have the pixel, call the FirstPassQuantize function...
this.InitialQuantizePixel((Color32*)sourcePixel);
}
// Now I have the pixel, call the FirstPassQuantize function...
// Add the stride to the source row
sourceRow = (IntPtr)((long)sourceRow + sourceData.Stride);
sourceRow += sourceData.Stride;
}
}
@ -161,55 +155,52 @@ namespace ImageProcessor.Imaging
// Define the source data pointers. The source row is a byte to
// keep addition of the stride value easier (as this is in bytes)
IntPtr sourceRow = sourceData.Scan0;
IntPtr sourcePixel = sourceRow;
IntPtr previousPixel = sourcePixel;
byte* sourceRow = (byte*)sourceData.Scan0.ToPointer();
int* sourcePixel = (int*)sourceRow;
int* previousPixel = sourcePixel;
// Now define the destination data pointers
IntPtr destinationRow = outputData.Scan0;
IntPtr destinationPixel = destinationRow;
byte* destinationRow = (byte*)outputData.Scan0.ToPointer();
byte* destinationPixel = destinationRow;
// And convert the first pixel, so that I have values going into the loop.
byte pixelValue = this.QuantizePixel(new Color32(sourcePixel));
// And convert the first pixel, so that I have values going into the loop
byte pixelValue = this.QuantizePixel((Color32*)sourcePixel);
// Assign the value of the first pixel
Marshal.WriteByte(destinationPixel, pixelValue);
*destinationPixel = pixelValue;
// Loop through each row
for (int row = 0; row < height; row++)
{
// Set the source pixel to the first pixel in this row
sourcePixel = sourceRow;
sourcePixel = (int*)sourceRow;
// And set the destination pixel pointer to the first pixel in the row
destinationPixel = destinationRow;
// Loop through each pixel on this scan line
for (int col = 0; col < width; col++)
for (int col = 0; col < width; col++, sourcePixel++, destinationPixel++)
{
// Check if this is the same as the last pixel. If so use that value
// rather than calculating it again. This is an inexpensive optimisation.
if (Marshal.ReadInt32(previousPixel) != Marshal.ReadInt32(sourcePixel))
if (*previousPixel != *sourcePixel)
{
// Quantize the pixel
pixelValue = this.QuantizePixel(new Color32(sourcePixel));
pixelValue = this.QuantizePixel((Color32*)sourcePixel);
// And setup the previous pointer
previousPixel = sourcePixel;
}
// And set the pixel in the output
Marshal.WriteByte(destinationPixel, pixelValue);
sourcePixel = (IntPtr)((long)sourcePixel + this.pixelSize);
destinationPixel = (IntPtr)((long)destinationPixel + 1);
*destinationPixel = pixelValue;
}
// Add the stride to the source row
sourceRow = (IntPtr)((long)sourceRow + sourceData.Stride);
sourceRow += sourceData.Stride;
// And to the destination row
destinationRow = (IntPtr)((long)destinationRow + outputData.Stride);
destinationRow += outputData.Stride;
}
}
finally
@ -227,7 +218,7 @@ namespace ImageProcessor.Imaging
/// This function need only be overridden if your quantize algorithm needs two passes,
/// such as an Octree quantizer.
/// </remarks>
protected virtual void InitialQuantizePixel(Color32 pixel)
protected virtual void InitialQuantizePixel(Color32* pixel)
{
}
@ -236,7 +227,7 @@ namespace ImageProcessor.Imaging
/// </summary>
/// <param name="pixel">The pixel to quantize</param>
/// <returns>The quantized value</returns>
protected abstract byte QuantizePixel(Color32 pixel);
protected abstract byte QuantizePixel(Color32* pixel);
/// <summary>
/// Retrieve the palette for the quantized image
@ -244,109 +235,5 @@ namespace ImageProcessor.Imaging
/// <param name="original">Any old palette, this is overwritten</param>
/// <returns>The new color palette</returns>
protected abstract ColorPalette GetPalette(ColorPalette original);
/// <summary>
/// Structure that defines a 32 bit color
/// </summary>
/// <remarks>
/// This structure is used to read data from a 32 bits per pixel image
/// in memory, and is ordered in this manner as this is the way that
/// the data is laid out in memory
/// </remarks>
[StructLayout(LayoutKind.Explicit)]
public struct Color32
{
/// <summary>
/// Holds the blue component of the colour
/// </summary>
[FieldOffset(0)]
private byte blue;
/// <summary>
/// Holds the green component of the colour
/// </summary>
[FieldOffset(1)]
private byte green;
/// <summary>
/// Holds the red component of the colour
/// </summary>
[FieldOffset(2)]
private byte red;
/// <summary>
/// Holds the alpha component of the colour
/// </summary>
[FieldOffset(3)]
private byte alpha;
/// <summary>
/// Permits the color32 to be treated as a 32 bit integer.
/// </summary>
[FieldOffset(0)]
private int argb;
/// <summary>
/// Initializes a new instance of the <see cref="T:ImageProcessor.Imaging.Quantizer.Color32">Color32</see> structure.
/// </summary>
/// <param name="sourcePixel">The pointer to the pixel.</param>
public Color32(IntPtr sourcePixel)
{
this = (Color32)Marshal.PtrToStructure(sourcePixel, typeof(Color32));
}
/// <summary>
/// Gets or sets the blue component of the colour
/// </summary>
public byte Blue
{
get { return this.blue; }
set { this.blue = value; }
}
/// <summary>
/// Gets or sets the green component of the colour
/// </summary>
public byte Green
{
get { return this.green; }
set { this.green = value; }
}
/// <summary>
/// Gets or sets the red component of the colour
/// </summary>
public byte Red
{
get { return this.red; }
set { this.red = value; }
}
/// <summary>
/// Gets or sets the alpha component of the colour
/// </summary>
public byte Alpha
{
get { return this.alpha; }
set { this.alpha = value; }
}
/// <summary>
/// Gets or sets the ARGB component, permitting the color32 to be treated as a 32 bit integer.
/// </summary>
public int Argb
{
get { return this.argb; }
set { this.argb = value; }
}
/// <summary>
/// Gets the color for this Color32 object
/// </summary>
public Color Color
{
get { return Color.FromArgb(this.Alpha, this.Red, this.Green, this.Blue); }
}
}
}
}
}

12
src/ImageProcessor/Processors/Resize.cs

@ -73,7 +73,7 @@ namespace ImageProcessor.Processors
try
{
ResizeLayer resizeLayer = this.DynamicParameter;
// Augment the layer with the extra information.
resizeLayer.RestrictedSizes = this.RestrictedSizes;
Size maxSize = new Size();
@ -91,9 +91,13 @@ namespace ImageProcessor.Processors
Resizer resizer = new Resizer(resizeLayer);
newImage = (Bitmap)resizer.ResizeImage(image);
// Reassign the image.
image.Dispose();
image = newImage;
// Check that the original image has not been returned.
if (newImage != image)
{
// Reassign the image.
image.Dispose();
image = newImage;
}
}
catch (Exception ex)
{

Loading…
Cancel
Save