Browse Source

Fixed gif output

Replace quantizer and added wait to semaphore slim.
TODO: Replace method call in ImageFactory methods.

Former-commit-id: 581dce3f0e11cc6769c4c243034c96683564be94
af/merge-core
James South 12 years ago
parent
commit
2de0f339d4
  1. 1
      src/ImageProcessor.Web/NET45/HttpModules/ImageProcessingModule.cs
  2. 19
      src/ImageProcessor.Web/NET45/ImageFactoryExtensions.cs
  3. 13
      src/ImageProcessor/ImageFactory.cs
  4. 2
      src/ImageProcessor/ImageProcessor.csproj
  5. 836
      src/ImageProcessor/Imaging/ColorQuantizer.cs
  6. 39
      src/ImageProcessor/Imaging/GifEncoder.cs
  7. 558
      src/ImageProcessor/Imaging/OctreeQuantizer.cs
  8. 350
      src/ImageProcessor/Imaging/Quantizer.cs
  9. 3
      src/ImageProcessor/Settings.StyleCop

1
src/ImageProcessor.Web/NET45/HttpModules/ImageProcessingModule.cs

@ -180,6 +180,7 @@ namespace ImageProcessor.Web.HttpModules
// Dispose of any managed resources here.
foreach (KeyValuePair<string, SemaphoreSlim> semaphore in SemaphoreSlims)
{
semaphore.Value.Wait();
semaphore.Value.Dispose();
}

19
src/ImageProcessor.Web/NET45/ImageFactoryExtensions.cs

@ -15,10 +15,8 @@ namespace ImageProcessor.Web
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using ImageProcessor.Extensions;
using ImageProcessor.Imaging;
using ImageProcessor.Processors;
@ -63,8 +61,6 @@ namespace ImageProcessor.Web
foreach (IGraphicsProcessor graphicsProcessor in graphicsProcessors)
{
ProcessImage(graphicsProcessor.ProcessImage, factory);
//Image img = graphicsProcessor.ProcessImage(factory);
//factory.Update(img);
}
}
}
@ -73,7 +69,7 @@ namespace ImageProcessor.Web
}
/// <summary>
/// The process image.
/// Processes the image.
/// </summary>
/// <param name="processor">
/// The processor.
@ -87,21 +83,20 @@ namespace ImageProcessor.Web
if (imageInfo.IsAnimated)
{
Image image;
using (GifEncoder encoder = new GifEncoder(new MemoryStream(4096), null, null, imageInfo.LoopCount))
OctreeQuantizer quantizer = new OctreeQuantizer(255, 8);
MemoryStream stream = new MemoryStream(4096);
using (GifEncoder encoder = new GifEncoder(stream, null, null, imageInfo.LoopCount))
{
foreach (GifFrame frame in imageInfo.GifFrames)
{
factory.Update(frame.Image);
frame.Image = new Bitmap(ColorQuantizer.Quantize(processor.Invoke(factory), PixelFormat.Format8bppIndexed));
frame.Image = quantizer.Quantize(processor.Invoke(factory));
encoder.AddFrame(frame);
}
image = encoder.Save();
}
factory.Update(image);
stream.Position = 0;
factory.Update(new Bitmap(stream));
}
else
{

13
src/ImageProcessor/ImageFactory.cs

@ -968,7 +968,7 @@ namespace ImageProcessor
if (!imageInfo.IsAnimated)
{
this.Image = ColorQuantizer.Quantize(this.Image, PixelFormat.Format8bppIndexed);
this.Image = new OctreeQuantizer(255, 8).Quantize(this.Image);
}
}
}
@ -991,17 +991,20 @@ namespace ImageProcessor
if (imageInfo.IsAnimated)
{
using (GifEncoder encoder = new GifEncoder(new MemoryStream(4096), width, height, imageInfo.LoopCount))
OctreeQuantizer quantizer = new OctreeQuantizer(255, 8);
MemoryStream stream = new MemoryStream(4096);
using (GifEncoder encoder = new GifEncoder(stream, width, height, imageInfo.LoopCount))
{
foreach (GifFrame frame in imageInfo.GifFrames)
{
this.Image = frame.Image;
frame.Image = new Bitmap(ColorQuantizer.Quantize(processor.Invoke(this), PixelFormat.Format8bppIndexed));
frame.Image = quantizer.Quantize(processor.Invoke(this));
encoder.AddFrame(frame);
}
this.Image = encoder.Save();
}
stream.Position = 0;
this.Image = Image.FromStream(stream);
}
else
{

2
src/ImageProcessor/ImageProcessor.csproj

@ -74,6 +74,7 @@
<Compile Include="Imaging\ColorQuantizer.cs" />
<Compile Include="Imaging\GifEncoder.cs" />
<Compile Include="Imaging\GifFrame.cs" />
<Compile Include="Imaging\Quantizer.cs" />
<Compile Include="Imaging\ResizeLayer.cs" />
<Compile Include="Imaging\Filters\BlackWhiteMatrixFilter.cs" />
<Compile Include="Imaging\Filters\ColorMatrixes.cs" />
@ -93,6 +94,7 @@
<Compile Include="Imaging\RotateLayer.cs" />
<Compile Include="Imaging\RoundedCornerLayer.cs" />
<Compile Include="Imaging\TextLayer.cs" />
<Compile Include="Imaging\OctreeQuantizer.cs" />
<Compile Include="Processors\Alpha.cs" />
<Compile Include="Processors\GaussianBlur.cs" />
<Compile Include="Processors\Brightness.cs" />

836
src/ImageProcessor/Imaging/ColorQuantizer.cs

@ -1,836 +0,0 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="ColorQuantizer.cs" company="James South">
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// The color quantizer.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Imaging
{
#region Using
using System;
using System.Diagnostics.CodeAnalysis;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
#endregion
/// <summary>
/// The color quantizer.
/// </summary>
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "Reviewed. Suppression is OK here.")]
public static class ColorQuantizer
{
#region Quantize methods
/// <summary>The quantize.</summary>
/// <param name="image">The image.</param>
/// <param name="bitmapPixelFormat">The bitmap pixel format.</param>
/// <returns>The quantized image with the recalculated color palette.</returns>
public static Bitmap Quantize(Image image, PixelFormat bitmapPixelFormat)
{
// Use dither by default
return Quantize(image, bitmapPixelFormat, true);
}
/// <summary>The quantize.</summary>
/// <param name="image">The image.</param>
/// <param name="pixelFormat">The pixel format.</param>
/// <param name="useDither">The use dither.</param>
/// <returns>The quantized image with the recalculated color palette.</returns>
public static Bitmap Quantize(Image image, PixelFormat pixelFormat, bool useDither)
{
Bitmap tryBitmap = image as Bitmap;
if (tryBitmap != null && tryBitmap.PixelFormat == PixelFormat.Format32bppArgb)
{
// The image passed to us is ALREADY a bitmap in the right format. No need to create
// a copy and work from there.
return DoQuantize(tryBitmap, pixelFormat, useDither);
}
// We use these values a lot
int width = image.Width;
int height = image.Height;
Rectangle sourceRect = Rectangle.FromLTRB(0, 0, width, height);
// Create a 24-bit rgb version of the source image
using (Bitmap bitmapSource = new Bitmap(width, height, PixelFormat.Format32bppArgb))
{
using (Graphics grfx = Graphics.FromImage(bitmapSource))
{
grfx.DrawImage(image, sourceRect, 0, 0, width, height, GraphicsUnit.Pixel);
}
return DoQuantize(bitmapSource, pixelFormat, useDither);
}
}
/// <summary>
/// Does the quantize.
/// </summary>
/// <param name="bitmapSource">The bitmap source.</param>
/// <param name="pixelFormat">The pixel format.</param>
/// <param name="useDither">if set to <c>true</c> [use dither].</param>
/// <returns>The quantized image with the recalculated color palette.</returns>
private static Bitmap DoQuantize(Bitmap bitmapSource, PixelFormat pixelFormat, bool useDither)
{
// We use these values a lot
int width = bitmapSource.Width;
int height = bitmapSource.Height;
Rectangle sourceRect = Rectangle.FromLTRB(0, 0, width, height);
Bitmap bitmapOptimized = null;
try
{
// Create a bitmap with the same dimensions and the desired format
bitmapOptimized = new Bitmap(width, height, pixelFormat);
// Lock the bits of the source image for reading.
// we will need to write if we do the dither.
BitmapData bitmapDataSource = bitmapSource.LockBits(
sourceRect,
ImageLockMode.ReadWrite,
PixelFormat.Format32bppArgb);
try
{
// Perform the first pass, which generates the octree data
// Create an Octree
Octree octree = new Octree(pixelFormat);
// Stride might be negative, indicating inverted row order.
// Allocate a managed buffer for the pixel data, and copy it from the unmanaged pointer.
int strideSource = Math.Abs(bitmapDataSource.Stride);
byte[] sourceDataBuffer = new byte[strideSource * height];
Marshal.Copy(bitmapDataSource.Scan0, sourceDataBuffer, 0, sourceDataBuffer.Length);
// We could skip every other row and/or every other column when sampling the colors
// of the source image, rather than hitting every other pixel. It doesn't seem to
// degrade the resulting image too much. But it doesn't really help the performance
// too much because the majority of the time seems to be spent in other places.
// For every row
int rowStartSource = 0;
for (int ndxRow = 0; ndxRow < height; ndxRow += 1)
{
// For each column
for (int ndxCol = 0; ndxCol < width; ndxCol += 1)
{
// Add the color (4 bytes per pixel - ARGB)
Pixel pixel = GetSourcePixel(sourceDataBuffer, rowStartSource, ndxCol);
octree.AddColor(pixel);
}
rowStartSource += strideSource;
}
// Get the optimized colors
Color[] colors = octree.GetPaletteColors();
// Set the palette from the octree
ColorPalette palette = bitmapOptimized.Palette;
for (var ndx = 0; ndx < palette.Entries.Length; ++ndx)
{
// Use the colors we calculated
// for the rest, just set to transparent
palette.Entries[ndx] = (ndx < colors.Length)
? colors[ndx]
: Color.Transparent;
}
bitmapOptimized.Palette = palette;
// Lock the bits of the optimized bitmap for writing.
// we will also need to read if we are doing 1bpp or 4bpp
BitmapData bitmapDataOutput = bitmapOptimized.LockBits(sourceRect, ImageLockMode.ReadWrite, pixelFormat);
try
{
// Create a managed array for the destination bytes given the desired color depth
// and marshal the unmanaged data to the managed array
int strideOutput = Math.Abs(bitmapDataOutput.Stride);
byte[] bitmapOutputBuffer = new byte[strideOutput * height];
// For each source pixel, compute the appropriate color index
rowStartSource = 0;
int rowStartOutput = 0;
for (int ndxRow = 0; ndxRow < height; ++ndxRow)
{
// For each column
for (int ndxCol = 0; ndxCol < width; ++ndxCol)
{
// Get the source color
Pixel pixel = GetSourcePixel(sourceDataBuffer, rowStartSource, ndxCol);
// Get the closest palette index
int paletteIndex = octree.GetPaletteIndex(pixel);
// If we want to dither and this isn't the transparent pixel
if (useDither && pixel.Alpha != 0)
{
// Calculate the error
Color paletteColor = colors[paletteIndex];
int deltaRed = pixel.Red - paletteColor.R;
int deltaGreen = pixel.Green - paletteColor.G;
int deltaBlue = pixel.Blue - paletteColor.B;
// Propagate the dither error.
// we'll use a standard Floyd-Steinberg matrix (1/16):
// | 0 0 0 |
// | 0 x 7 |
// | 3 5 1 |
// Make sure we're not on the right-hand edge
if (ndxCol + 1 < width)
{
DitherSourcePixel(sourceDataBuffer, rowStartSource, ndxCol + 1, deltaRed, deltaGreen, deltaBlue, 7);
}
// Make sure we're not already on the bottom row
if (ndxRow + 1 < height)
{
int nextRow = rowStartSource + strideSource;
// Make sure we're not on the left-hand column
if (ndxCol > 0)
{
// Down one row, but back one pixel
DitherSourcePixel(sourceDataBuffer, nextRow, ndxCol - 1, deltaRed, deltaGreen, deltaBlue, 3);
}
// pixel directly below us
DitherSourcePixel(sourceDataBuffer, nextRow, ndxCol, deltaRed, deltaGreen, deltaBlue, 5);
// Make sure we're not on the right-hand column
if (ndxCol + 1 < width)
{
// Down one row, but right one pixel
DitherSourcePixel(sourceDataBuffer, nextRow, ndxCol + 1, deltaRed, deltaGreen, deltaBlue, 1);
}
}
}
// Set the bitmap index based on the format
switch (pixelFormat)
{
case PixelFormat.Format8bppIndexed:
// Each byte is a palette index
bitmapOutputBuffer[rowStartOutput + ndxCol] = (byte)paletteIndex;
break;
case PixelFormat.Format4bppIndexed:
// Each byte contains two pixels
bitmapOutputBuffer[rowStartOutput + (ndxCol >> 1)] |= ((ndxCol & 1) == 1)
? (byte)(paletteIndex & 0x0f) // lower nibble
: (byte)(paletteIndex << 4); // upper nibble
break;
case PixelFormat.Format1bppIndexed:
// Each byte contains eight pixels
if (paletteIndex != 0)
{
bitmapOutputBuffer[rowStartOutput + (ndxCol >> 3)] |= (byte)(0x80 >> (ndxCol & 0x07));
}
break;
}
}
rowStartSource += strideSource;
rowStartOutput += strideOutput;
}
// Now copy the calculated pixel bytes from the managed array to the unmanaged bitmap
Marshal.Copy(bitmapOutputBuffer, 0, bitmapDataOutput.Scan0, bitmapOutputBuffer.Length);
}
finally
{
bitmapOptimized.UnlockBits(bitmapDataOutput);
bitmapDataOutput = null;
}
}
finally
{
bitmapSource.UnlockBits(bitmapDataSource);
bitmapDataSource = null;
}
}
catch (Exception)
{
// If any exception is thrown, dispose of the bitmap object
// we've been working on before we rethrow and bail
if (bitmapOptimized != null)
{
bitmapOptimized.Dispose();
}
throw;
}
// Caller is responsible for disposing of this bitmap!
return bitmapOptimized;
}
/// <summary>
/// Dithers the source pixel.
/// </summary>
/// <param name="buffer">The buffer.</param>
/// <param name="rowStart">The row start.</param>
/// <param name="col">The column.</param>
/// <param name="deltaRed">The delta red.</param>
/// <param name="deltaGreen">The delta green.</param>
/// <param name="deltaBlue">The delta blue.</param>
/// <param name="weight">The weight.</param>
private static void DitherSourcePixel(byte[] buffer, int rowStart, int col, int deltaRed, int deltaGreen, int deltaBlue, int weight)
{
int colorIndex = rowStart + (col * 4);
buffer[colorIndex + 2] = ChannelAdjustment(buffer[colorIndex + 2], (deltaRed * weight) >> 4);
buffer[colorIndex + 1] = ChannelAdjustment(buffer[colorIndex + 1], (deltaGreen * weight) >> 4);
buffer[colorIndex] = ChannelAdjustment(buffer[colorIndex], (deltaBlue * weight) >> 4);
}
/// <summary>
/// Gets the source pixel.
/// </summary>
/// <param name="buffer">The buffer.</param>
/// <param name="rowStart">The row start.</param>
/// <param name="col">The column.</param>
/// <returns>The source pixel.</returns>
private static Pixel GetSourcePixel(byte[] buffer, int rowStart, int col)
{
int colorIndex = rowStart + (col * 4);
return new Pixel
{
Alpha = buffer[colorIndex + 3],
Red = buffer[colorIndex + 2],
Green = buffer[colorIndex + 1],
Blue = buffer[colorIndex]
};
}
#endregion
/// <summary>Gets the channel adjustment.</summary>
/// <param name="current">The current.</param>
/// <param name="offset">The offset.</param>
/// <returns>The channel adjustment.</returns>
private static byte ChannelAdjustment(byte current, int offset)
{
return (byte)Math.Min(255, Math.Max(0, current + offset));
}
#region Octree class
/// <summary>data structure for storing and reducing colors used in the source image</summary>
private class Octree
{
/// <summary>The m_max colors.</summary>
private readonly int octreeMaxColors;
/// <summary>The m_reducible nodes.</summary>
private readonly OctreeNode[] octreeReducibleNodes;
/// <summary>The m_color count.</summary>
private int octreeColorCount;
/// <summary>The m_has transparent.</summary>
private bool octreeHasTransparent;
/// <summary>The m_last argb.</summary>
private int octreeLastArgb;
/// <summary>The m_last node.</summary>
private OctreeNode octreeLastNode;
/// <summary>The m_palette.</summary>
private Color[] octreePalette;
/// <summary>The m_root.</summary>
private OctreeNode octreeRoot;
/// <summary>Initializes a new instance of the <see cref="Octree"/> class. Constructor</summary>
/// <param name="pixelFormat">desired pixel format</param>
internal Octree(PixelFormat pixelFormat)
{
// figure out the maximum colors from the pixel format passed in
switch (pixelFormat)
{
case PixelFormat.Format1bppIndexed:
this.octreeMaxColors = 2;
break;
case PixelFormat.Format4bppIndexed:
this.octreeMaxColors = 16;
break;
case PixelFormat.Format8bppIndexed:
this.octreeMaxColors = 256;
break;
default:
throw new ArgumentException("Invalid Pixel Format", "pixelFormat");
}
// we need a list for each level that may have reducible nodes.
// since the last level (level 7) is only made up of leaf nodes,
// we don't need an array entry for it.
this.octreeReducibleNodes = new OctreeNode[7];
// add the initial level-0 root node
this.octreeRoot = new OctreeNode(0, this);
}
/// <summary>Add the given pixel color to the octree</summary>
/// <param name="pixel">points to the pixel color we want to add</param>
internal void AddColor(Pixel pixel)
{
// If the A value is non-zero (ignore the transparent color)
if (pixel.Alpha > 0)
{
// If we have a previous node and this color is the same as the last...
if (this.octreeLastNode != null && pixel.Argb == this.octreeLastArgb)
{
// Just add this color to the same last node
this.octreeLastNode.AddColor(pixel);
}
else
{
// Just start at the root. If a new color is added,
// add one to the count (otherwise 0).
this.octreeColorCount += this.octreeRoot.AddColor(pixel) ? 1 : 0;
}
}
else
{
// Flag that we have a transparent color.
this.octreeHasTransparent = true;
}
}
/// <summary>
/// Given a pixel color, return the index of the palette entry
/// we want to use in the reduced image. If the color is not in the
/// octree, OctreeNode.GetPaletteIndex will return a negative number.
/// In that case, we will have to calculate the palette index the brute-force
/// method by computing the least distance to each color in the palette array.
/// </summary>
/// <param name="pixel">pointer to the pixel color we want to look up</param>
/// <returns>index of the palette entry we want to use for this color</returns>
internal int GetPaletteIndex(Pixel pixel)
{
int paletteIndex = 0;
// transparent is always the first entry, so if this is transparent,
// don't do anything.
if (pixel.Alpha > 0)
{
paletteIndex = this.octreeRoot.GetPaletteIndex(pixel);
// returns -1 if this value isn't in the octree.
if (paletteIndex < 0)
{
// Use the brute-force method of calculating the closest color
// in the palette to the one we want
int minDistance = int.MaxValue;
for (int ndx = 0; ndx < this.octreePalette.Length; ++ndx)
{
Color paletteColor = this.octreePalette[ndx];
// Calculate the delta for each channel
int deltaRed = pixel.Red - paletteColor.R;
int deltaGreen = pixel.Green - paletteColor.G;
int deltaBlue = pixel.Blue - paletteColor.B;
// Calculate the distance-squared by summing each channel's square
int distance = (deltaRed * deltaRed) + (deltaGreen * deltaGreen) + (deltaBlue * deltaBlue);
if (distance < minDistance)
{
minDistance = distance;
paletteIndex = ndx;
}
}
}
}
return paletteIndex;
}
/// <summary>
/// Return a color palette for the computed octree.
/// </summary>
/// <returns>A color palette for the computed octree</returns>
internal Color[] GetPaletteColors()
{
// If we haven't already computed it, compute it now
if (this.octreePalette == null)
{
// Start at the second-to-last reducible level
int reductionLevel = this.octreeReducibleNodes.Length - 1;
// We want to subtract one from the target if we have a transparent
// bit because we want to save room for that special color
int targetCount = this.octreeMaxColors - (this.octreeHasTransparent ? 1 : 0);
// While we still have more colors than the target...
while (this.octreeColorCount > targetCount)
{
// Find the first reducible node, starting with the last level
// that can have reducible nodes
while (reductionLevel > 0 && this.octreeReducibleNodes[reductionLevel] == null)
{
--reductionLevel;
}
if (this.octreeReducibleNodes[reductionLevel] == null)
{
// Shouldn't get here
break;
}
// We should have a node now
OctreeNode newLeaf = this.octreeReducibleNodes[reductionLevel];
this.octreeReducibleNodes[reductionLevel] = newLeaf.NextReducibleNode;
this.octreeColorCount -= newLeaf.Reduce() - 1;
}
if (reductionLevel == 0 && !this.octreeHasTransparent)
{
// If this was the top-most level, we now only have a single color
// representing the average. That's not what we want.
// use just black and white
this.octreePalette = new Color[2];
this.octreePalette[0] = Color.Black;
this.octreePalette[1] = Color.White;
// And empty the octree so it always picks the closer of the black and white entries
this.octreeRoot = new OctreeNode(0, this);
}
else
{
// Now walk the tree, adding all the remaining colors to the list
int paletteIndex = 0;
this.octreePalette = new Color[this.octreeColorCount + (this.octreeHasTransparent ? 1 : 0)];
// Add the transparent color if we need it
if (this.octreeHasTransparent)
{
this.octreePalette[paletteIndex++] = Color.Transparent;
}
// Have the nodes insert their leaf colors
this.octreeRoot.AddColorsToPalette(this.octreePalette, ref paletteIndex);
}
}
return this.octreePalette;
}
/// <summary>set up the values we need to reuse the given pointer if the next color is argb</summary>
/// <param name="node">last node to which we added a color</param>
/// <param name="argb">last color we added</param>
private void SetLastNode(OctreeNode node, int argb)
{
this.octreeLastNode = node;
this.octreeLastArgb = argb;
}
/// <summary>When a reducible node is added, this method is called to add it to the appropriate
/// reducible node list (given its level)</summary>
/// <param name="reducibleNode">node to add to a reducible list</param>
private void AddReducibleNode(OctreeNode reducibleNode)
{
// hook this node into the front of the list.
// this means the last one added will be the first in the list.
reducibleNode.NextReducibleNode = this.octreeReducibleNodes[reducibleNode.Level];
this.octreeReducibleNodes[reducibleNode.Level] = reducibleNode;
}
#region OctreeNode class
/// <summary>Node for an Octree structure</summary>
private class OctreeNode
{
/// <summary>The s_level masks.</summary>
private static readonly byte[] NodeLevelMasks = { 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 };
/// <summary>The m_level.</summary>
private readonly int nodeLevel;
/// <summary>The m_octree.</summary>
private readonly Octree nodeOctree;
/// <summary>The m_blue sum.</summary>
private int nodeBlueSum;
/// <summary>The m_child nodes.</summary>
private OctreeNode[] nodeChildNodes;
/// <summary>The m_green sum.</summary>
private int nodeGreenSum;
/// <summary>The m_is leaf.</summary>
private bool nodeIsLeaf;
/// <summary>The m_palette index.</summary>
private int nodePaletteIndex;
/// <summary>
/// The pixel count.Information we need to calculate the average color for a set of pixels
/// </summary>
private int nodePixelCount;
/// <summary>The m_red sum.</summary>
private int nodeRedSum;
/// <summary>Initializes a new instance of the <see cref="OctreeNode"/> class. Constructor</summary>
/// <param name="level">level for this node</param>
/// <param name="octree">owning octree</param>
internal OctreeNode(int level, Octree octree)
{
this.nodeOctree = octree;
this.nodeLevel = level;
// Since there are only eight levels, if we get to level 7
// We automatically make this a leaf node
this.nodeIsLeaf = level == 7;
if (!this.nodeIsLeaf)
{
// Create the child array
this.nodeChildNodes = new OctreeNode[8];
// Add it to the tree's reducible node list
this.nodeOctree.AddReducibleNode(this);
}
}
/// <summary>Gets Level.</summary>
internal int Level
{
get { return this.nodeLevel; }
}
/// <summary>
/// Gets or sets NextReducibleNode.
/// Once we compute a palette, this will be set
/// to the palette index associated with this leaf node.
/// Nodes are arranged in linked lists of reducible nodes for a given level.
/// this field and property is used to traverse that list.
/// </summary>
internal OctreeNode NextReducibleNode { get; set; }
/// <summary>
/// Gets the average color for this node.
/// </summary>
private Color NodeColor
{
get
{
// Average color is the sum of each channel divided by the pixel count
return Color.FromArgb(
this.nodeRedSum / this.nodePixelCount,
this.nodeGreenSum / this.nodePixelCount,
this.nodeBlueSum / this.nodePixelCount);
}
}
/// <summary>
/// Add the given color to this node if it is a leaf, otherwise recurse
/// down the appropriate child
/// </summary>
/// <param name="pixel">color to add</param>
/// <returns>true if a new color was added to the octree</returns>
internal bool AddColor(Pixel pixel)
{
bool colorAdded;
if (this.nodeIsLeaf)
{
// Increase the pixel count for this node, and if
// the result is 1, then this is a new color
colorAdded = ++this.nodePixelCount == 1;
// Add the color to the running sum for this node
this.nodeRedSum += pixel.Red;
this.nodeGreenSum += pixel.Green;
this.nodeBlueSum += pixel.Blue;
// Set the last node so we can quickly process adjacent pixels
// with the same color
this.nodeOctree.SetLastNode(this, pixel.Argb);
}
else
{
// Get the index at this level for the rgb values
int childIndex = this.GetChildIndex(pixel);
// If there is no child, add one now to the next level
if (this.nodeChildNodes[childIndex] == null)
{
this.nodeChildNodes[childIndex] = new OctreeNode(this.nodeLevel + 1, this.nodeOctree);
}
// Recurse...
colorAdded = this.nodeChildNodes[childIndex].AddColor(pixel);
}
return colorAdded;
}
/// <summary>
/// Given a source color, return the palette index to use for the reduced image.
/// Returns -1 if the color is not represented in the octree (this happens if
/// the color has been dithered into a new color that did not appear in the
/// original image when the octree was formed in pass 1.
/// </summary>
/// <param name="pixel">source color to look up</param>
/// <returns>palette index to use</returns>
internal int GetPaletteIndex(Pixel pixel)
{
int paletteIndex = -1;
if (this.nodeIsLeaf)
{
// Use this leaf node's palette index
paletteIndex = this.nodePaletteIndex;
}
else
{
// Get the index at this level for the rgb values
var childIndex = this.GetChildIndex(pixel);
if (this.nodeChildNodes[childIndex] != null)
{
// Recurse...
paletteIndex = this.nodeChildNodes[childIndex].GetPaletteIndex(pixel);
}
}
return paletteIndex;
}
/// <summary>Reduce this node by combining all child nodes</summary>
/// <returns>number of nodes removed</returns>
internal int Reduce()
{
int numReduced = 0;
if (!this.nodeIsLeaf)
{
// For each child
foreach (OctreeNode node in this.nodeChildNodes)
{
if (node != null)
{
OctreeNode childNode = node;
// add the pixel count from the child
this.nodePixelCount += childNode.nodePixelCount;
// add the running color sums from the child
this.nodeRedSum += childNode.nodeRedSum;
this.nodeGreenSum += childNode.nodeGreenSum;
this.nodeBlueSum += childNode.nodeBlueSum;
++numReduced;
}
}
this.nodeChildNodes = null;
this.nodeIsLeaf = true;
}
return numReduced;
}
/// <summary>
/// If this is a leaf node, add its color to the palette array at the given index
/// and increment the index. If not a leaf, recurse the children nodes.
/// </summary>
/// <param name="colorArray">array of colors</param>
/// <param name="paletteIndex">index of the next empty slot in the array</param>
internal void AddColorsToPalette(Color[] colorArray, ref int paletteIndex)
{
if (this.nodeIsLeaf)
{
// Save our index and increment the running index
this.nodePaletteIndex = paletteIndex++;
// The color for this node is the average color, which is created by
// dividing the running sums for each channel by the pixel count
colorArray[this.nodePaletteIndex] = this.NodeColor;
}
else
{
// Just run through all the non-null children and recurse
foreach (OctreeNode node in this.nodeChildNodes)
{
if (node != null)
{
node.AddColorsToPalette(colorArray, ref paletteIndex);
}
}
}
}
/// <summary>
/// Return the child index for a given color.
/// Depends on which level this node is in.
/// </summary>
/// <param name="pixel">color pixel to compute</param>
/// <returns>child index (0-7)</returns>
private int GetChildIndex(Pixel pixel)
{
// lvl: 0 1 2 3 4 5 6 7
// bit: 7 6 5 4 3 2 1 0
var shift = 7 - this.nodeLevel;
int mask = NodeLevelMasks[this.nodeLevel];
return ((pixel.Red & mask) >> (shift - 2)) |
((pixel.Green & mask) >> (shift - 1)) |
((pixel.Blue & mask) >> shift);
}
}
#endregion
}
#endregion
#region Pixel class for ARGB values
/// <summary>
/// Structure of a Format32bppArgb pixel in memory.
/// </summary>
private class Pixel
{
/// <summary>
/// Gets or sets the blue component of the pixel.
/// </summary>
public byte Blue { get; set; }
/// <summary>
/// Gets or sets the green component of the pixel.
/// </summary>
public byte Green { get; set; }
/// <summary>
/// Gets or sets the red component of the pixel.
/// </summary>
public byte Red { get; set; }
/// <summary>
/// Gets or sets the alpha component of the pixel.
/// </summary>
public byte Alpha { get; set; }
/// <summary>
/// Gets the argb combination of the pixel.
/// </summary>
public int Argb
{
get
{
return this.Alpha << 24 | this.Red << 16 | this.Green << 8 | this.Blue;
}
}
}
#endregion
}
}

39
src/ImageProcessor/Imaging/GifEncoder.cs

@ -114,7 +114,8 @@ namespace ImageProcessor.Imaging
/// <summary>
/// The stream.
/// </summary>
private Stream inputStream;
// ReSharper disable once FieldCanBeMadeReadOnly.Local
private MemoryStream inputStream;
/// <summary>
/// The height.
@ -166,7 +167,7 @@ namespace ImageProcessor.Imaging
/// <param name="repeatCount">
/// The number of times to repeat the animation.
/// </param>
public GifEncoder(Stream stream, int? width = null, int? height = null, int? repeatCount = null)
public GifEncoder(MemoryStream stream, int? width = null, int? height = null, int? repeatCount = null)
{
this.inputStream = stream;
this.width = width;
@ -225,26 +226,6 @@ namespace ImageProcessor.Imaging
this.isFirstImage = false;
}
/// <summary>
/// Returns the gif as an image.
/// </summary>
/// <returns>
/// The <see cref="Image"/> containing the animated gif.
/// </returns>
public Image Save()
{
// Complete Application Block
this.WriteByte(0);
// Complete File
this.WriteByte(FileTrailer);
// Push the data
// this.inputStream.Flush();
this.inputStream.Position = 0;
return Image.FromStream(this.inputStream);
}
/// <summary>
/// Disposes the object and frees resources for the Garbage Collector.
/// </summary>
@ -277,12 +258,14 @@ namespace ImageProcessor.Imaging
if (disposing)
{
// Dispose of any managed resources here.
//if (this.inputStream != null)
//{
// this.inputStream.Dispose();
// this.inputStream = null;
//}
// Complete Application Block
this.WriteByte(0);
// Complete File
this.WriteByte(FileTrailer);
// Push the data
this.inputStream.Flush();
}
// Call the appropriate methods to clean up

558
src/ImageProcessor/Imaging/OctreeQuantizer.cs

@ -0,0 +1,558 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="OctreeQuantizer.cs" company="James South">
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// Encapsulates methods to calculate the colour palette if an image using an octree pattern.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Imaging
{
using System;
using System.Collections;
using System.Drawing;
using System.Drawing.Imaging;
/// <summary>
/// Encapsulates methods to calculate the colour palette if an image using an octree pattern.
/// </summary>
public class OctreeQuantizer : Quantizer
{
/// <summary>
/// Stores the tree.
/// </summary>
private readonly Octree octree;
/// <summary>
/// The maximum allowed color depth.
/// </summary>
private readonly int maxColors;
/// <summary>
/// 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 second pass quantizes a color based on the nodes in the tree
/// </remarks>
/// <param name="maxColors">
/// The maximum number of colors to return
/// </param>
/// <param name="maxColorBits">
/// The number of significant bits
/// </param>
public OctreeQuantizer(int maxColors, int maxColorBits)
: base(false)
{
if (maxColors > 255)
{
throw new ArgumentOutOfRangeException("maxColors", maxColors, "The number of colors should be less than 256");
}
if ((maxColorBits < 1) | (maxColorBits > 8))
{
throw new ArgumentOutOfRangeException("maxColorBits", maxColorBits, "This should be between 1 and 8");
}
// 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
/// </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)
{
// Add the color to the octree
this.octree.AddColor(pixel);
}
/// <summary>
/// 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)
{
// 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)
{
paletteIndex = (byte)this.octree.GetPaletteIndex(pixel);
}
return paletteIndex;
}
/// <summary>
/// Retrieve the palette for the quantized image
/// </summary>
/// <param name="original">Any old palette, this is overwritten</param>
/// <returns>
/// The new color palette
/// </returns>
protected override ColorPalette GetPalette(ColorPalette original)
{
// 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;
}
// 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 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.
/// </summary>
private class Octree
{
/// <summary>
/// Mask used when getting the appropriate pixels for a given node
/// </summary>
private static readonly int[] Mask = { 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 };
/// <summary>
/// The root of the octree
/// </summary>
private readonly OctreeNode root;
/// <summary>
/// Array of reducible nodes
/// </summary>
private readonly OctreeNode[] reducibleNodes;
/// <summary>
/// Maximum number of significant bits in the image
/// </summary>
private readonly int maxColorBits;
/// <summary>
/// Number of leaves in the tree
/// </summary>
private int leafCount;
/// <summary>
/// Store the last node quantized
/// </summary>
private OctreeNode previousNode;
/// <summary>
/// Cache the previous color quantized
/// </summary>
private int previousColor;
/// <summary>
/// Initializes a new instance of the <see cref="Octree"/> class.
/// </summary>
/// <param name="maxColorBits">
/// The maximum number of significant bits in the image
/// </param>
public Octree(int maxColorBits)
{
this.maxColorBits = maxColorBits;
this.leafCount = 0;
this.reducibleNodes = new OctreeNode[9];
this.root = new OctreeNode(0, this.maxColorBits, this);
this.previousColor = 0;
this.previousNode = null;
}
/// <summary>
/// Gets or sets the number of leaves in the tree
/// </summary>
private int Leaves
{
get { return this.leafCount; }
set { this.leafCount = value; }
}
/// <summary>
/// Add a given colour value to the octree
/// </summary>
/// <param name="pixel">
/// The color value to add.
/// </param>
public void AddColor(Color32 pixel)
{
// Check if this request is for the same color as the last
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
// happens to be black, with an alpha component of zero.
if (null == this.previousNode)
{
this.previousColor = pixel.Argb;
this.root.AddColor(pixel, this.maxColorBits, 0, this);
}
else
{
// Just update the previous node
this.previousNode.Increment(pixel);
}
}
else
{
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
/// </summary>
/// <param name="colorCount">The maximum number of colors</param>
/// <returns>An array-list with the palletized colors</returns>
public ArrayList Palletize(int colorCount)
{
while (this.Leaves > colorCount)
{
this.Reduce();
}
// Now palletized the nodes
ArrayList palette = new ArrayList(this.Leaves);
int paletteIndex = 0;
this.root.ConstructPalette(palette, ref paletteIndex);
// And return the palette
return palette;
}
/// <summary>
/// Get the palette index for the passed colour.
/// </summary>
/// <param name="pixel">
/// The color to return the palette index for.
/// </param>
/// <returns>
/// The palette index for the passed colour.
/// </returns>
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>
/// <param name="node">The node last quantized</param>
protected void TrackPrevious(OctreeNode node)
{
this.previousNode = node;
}
/// <summary>
/// Reduce the depth of the tree
/// </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]))
{
index--;
}
// Reduce the node most recently added to the list at level 'index'
OctreeNode node = this.reducibleNodes[index];
this.reducibleNodes[index] = node.NextReducible;
// Decrement the leaf count after reducing the node
this.leafCount -= node.Reduce();
// And just in case I've reduced the last color to be added, and the next color to
// be added is the same, invalidate the previousNode...
this.previousNode = null;
}
/// <summary>
/// Class which encapsulates each node in the tree
/// </summary>
protected class OctreeNode
{
/// <summary>
/// Pointers to any child nodes
/// </summary>
private readonly OctreeNode[] children;
/// <summary>
/// Pointer to next reducible node
/// </summary>
private readonly OctreeNode nextReducible;
/// <summary>
/// Flag indicating that this is a leaf node
/// </summary>
private bool leaf;
/// <summary>
/// Number of pixels in this node
/// </summary>
private int pixelCount;
/// <summary>
/// Red component
/// </summary>
private int red;
/// <summary>
/// Green Component
/// </summary>
private int green;
/// <summary>
/// Blue component
/// </summary>
private int blue;
/// <summary>
/// The index of this node in the palette
/// </summary>
private int paletteIndex;
/// <summary>
/// Initializes a new instance of the <see cref="OctreeNode"/> class.
/// </summary>
/// <param name="level">
/// The level in the tree = 0 - 7
/// </param>
/// <param name="colorBits">
/// The number of significant color bits in the image
/// </param>
/// <param name="octree">
/// The tree to which this node belongs
/// </param>
public OctreeNode(int level, int colorBits, Octree octree)
{
// Construct the new node
this.leaf = level == colorBits;
this.red = this.green = this.blue = 0;
this.pixelCount = 0;
// If a leaf, increment the leaf count
if (this.leaf)
{
octree.Leaves++;
this.nextReducible = null;
this.children = null;
}
else
{
// Otherwise add this to the reducible nodes
var repNodes = octree.ReducibleNodes();
this.nextReducible = repNodes[level];
repNodes[level] = this;
this.children = new OctreeNode[8];
}
}
/// <summary>
/// Gets the next reducible node.
/// </summary>
public OctreeNode NextReducible
{
get { return this.nextReducible; }
}
/// <summary>
/// Add a color into the tree
/// </summary>
/// <param name="pixel">The color</param>
/// <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)
{
// Update the color information if this is a leaf
if (this.leaf)
{
this.Increment(pixel);
// Setup the previous node
octree.TrackPrevious(this);
}
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);
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);
}
}
}
/// <summary>
/// Reduce this node by removing all of its children
/// </summary>
/// <returns>The number of leaves removed</returns>
public int Reduce()
{
this.red = this.green = this.blue = 0;
int childNodes = 0;
// Loop through all children and add their information to this node
for (int index = 0; index < 8; index++)
{
if (null != this.children[index])
{
this.red += this.children[index].red;
this.green += this.children[index].green;
this.blue += this.children[index].blue;
this.pixelCount += this.children[index].pixelCount;
++childNodes;
this.children[index] = null;
}
}
// Now change this to a leaf node
this.leaf = true;
// Return the number of nodes to decrement the leaf count by
return childNodes - 1;
}
/// <summary>
/// Traverse the tree, building up the color palette
/// </summary>
/// <param name="palette">The palette</param>
/// <param name="index">The current palette index</param>
public void ConstructPalette(ArrayList palette, ref int index)
{
if (this.leaf)
{
// Consume the next palette index
this.paletteIndex = index++;
// And set the color of the palette entry
palette.Add(Color.FromArgb(this.red / this.pixelCount, this.green / this.pixelCount, this.blue / this.pixelCount));
}
else
{
// Loop through children looking for leaves
for (int i = 0; i < 8; i++)
{
if (null != this.children[i])
{
this.children[i].ConstructPalette(palette, ref index);
}
}
}
}
/// <summary>
/// Return the palette index for the passed color.
/// </summary>
/// <param name="pixel">
/// The pixel.
/// </param>
/// <param name="level">
/// The level.
/// </param>
/// <returns>
/// The palette index for the passed color.
/// </returns>
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);
if (null != this.children[i])
{
index = this.children[i].GetPaletteIndex(pixel, level + 1);
}
else
{
throw new ArgumentException("Didn't expect this!");
}
}
}
return index;
}
/// <summary>
/// Increment the pixel count and add to the color information
/// </summary>
/// <param name="pixel">
/// The pixel.
/// </param>
public void Increment(Color32 pixel)
{
this.pixelCount++;
this.red += pixel.Red;
this.green += pixel.Green;
this.blue += pixel.Blue;
}
}
}
}
}

350
src/ImageProcessor/Imaging/Quantizer.cs

@ -0,0 +1,350 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="Quantizer.cs" company="James South">
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// Defines the Quantizer type.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Imaging
{
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
/// <summary>
/// Encapsulates methods to calculate the color palette of an image.
/// </summary>
public abstract class Quantizer
{
/// <summary>
/// The 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.
/// </summary>
/// <param name="singlePass">
/// If set to <see langword="true"/>, then the quantizer will loop through the source pixels once;
/// otherwise, <see langword="false"/>.
/// </param>
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>
/// </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>
public Bitmap Quantize(Image source)
{
// Get the size of the source image
int height = source.Height;
int width = source.Width;
// And construct a rectangle from these dimensions
Rectangle bounds = new Rectangle(0, 0, width, height);
// First off take a 32bpp copy of the image
Bitmap copy = new Bitmap(width, height, PixelFormat.Format32bppArgb);
// And construct an 8bpp version
Bitmap output = new Bitmap(width, height, PixelFormat.Format8bppIndexed);
// Now lock the bitmap into memory
using (Graphics g = Graphics.FromImage(copy))
{
g.PageUnit = GraphicsUnit.Pixel;
// Draw the source image onto the copy bitmap,
// which will effect a widening as appropriate.
g.DrawImage(source, bounds);
}
// Define a pointer to the bitmap data
BitmapData sourceData = null;
try
{
// Get the source image bits and lock into memory
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
// all image pixels, build a data structure, and create a palette.
if (!this.singlePass)
{
this.FirstPass(sourceData, width, height);
}
// Then set the color palette on the output bitmap. I'm passing in the current palette
// as there's no way to construct a new, empty palette.
output.Palette = this.GetPalette(output.Palette);
// Then call the second pass which actually does the conversion
this.SecondPass(sourceData, output, width, height, bounds);
}
finally
{
// Ensure that the bits are unlocked
copy.UnlockBits(sourceData);
}
// Last but not least, return the output bitmap
return output;
}
/// <summary>
/// Execute the first pass through the pixels in the image
/// </summary>
/// <param name="sourceData">The source data</param>
/// <param name="width">The width in pixels of the image</param>
/// <param name="height">The height in pixels of the image</param>
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;
// 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;
// And loop through each column
for (int col = 0; col < width; col++)
{
this.InitialQuantizePixel(new Color32(sourcePixel));
sourcePixel = (IntPtr)((int)sourcePixel + this.pixelSize);
}
// Now I have the pixel, call the FirstPassQuantize function...
// Add the stride to the source row
sourceRow = (IntPtr)((long)sourceRow + sourceData.Stride);
}
}
/// <summary>
/// Execute a second pass through the bitmap
/// </summary>
/// <param name="sourceData">The source bitmap, locked into memory</param>
/// <param name="output">The output bitmap</param>
/// <param name="width">The width in pixels of the image</param>
/// <param name="height">The height in pixels of the image</param>
/// <param name="bounds">The bounding rectangle</param>
protected virtual void SecondPass(BitmapData sourceData, Bitmap output, int width, int height, Rectangle bounds)
{
BitmapData outputData = null;
try
{
// Lock the output bitmap into memory
outputData = output.LockBits(bounds, ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed);
// 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;
// Now define the destination data pointers
IntPtr destinationRow = outputData.Scan0;
IntPtr destinationPixel = destinationRow;
// And convert the first pixel, so that I have values going into the loop.
byte pixelValue = this.QuantizePixel(new Color32(sourcePixel));
// Assign the value of the first pixel
Marshal.WriteByte(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;
// 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++)
{
// 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))
{
// Quantize the pixel
pixelValue = this.QuantizePixel(new 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);
}
// Add the stride to the source row
sourceRow = (IntPtr)((long)sourceRow + sourceData.Stride);
// And to the destination row
destinationRow = (IntPtr)((long)destinationRow + outputData.Stride);
}
}
finally
{
// Ensure that I unlock the output bits
output.UnlockBits(outputData);
}
}
/// <summary>
/// Override this to 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 virtual void InitialQuantizePixel(Color32 pixel)
{
}
/// <summary>
/// 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 abstract byte QuantizePixel(Color32 pixel);
/// <summary>
/// Retrieve the palette for the quantized image
/// </summary>
/// <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); }
}
}
}
}

3
src/ImageProcessor/Settings.StyleCop

@ -2,7 +2,10 @@
<GlobalSettings>
<CollectionProperty Name="RecognizedWords">
<Value>behaviour</Value>
<Value>colour</Value>
<Value>lomograph</Value>
<Value>octree</Value>
<Value>quantizer</Value>
</CollectionProperty>
</GlobalSettings>
<Analyzers>

Loading…
Cancel
Save