From 2de0f339d407f6d9144b707b8810f7cdfd0b0dcc Mon Sep 17 00:00:00 2001 From: James South Date: Sat, 3 May 2014 00:29:48 +0100 Subject: [PATCH] Fixed gif output Replace quantizer and added wait to semaphore slim. TODO: Replace method call in ImageFactory methods. Former-commit-id: 581dce3f0e11cc6769c4c243034c96683564be94 --- .../HttpModules/ImageProcessingModule.cs | 1 + .../NET45/ImageFactoryExtensions.cs | 19 +- src/ImageProcessor/ImageFactory.cs | 13 +- src/ImageProcessor/ImageProcessor.csproj | 2 + src/ImageProcessor/Imaging/ColorQuantizer.cs | 836 ------------------ src/ImageProcessor/Imaging/GifEncoder.cs | 39 +- src/ImageProcessor/Imaging/OctreeQuantizer.cs | 558 ++++++++++++ src/ImageProcessor/Imaging/Quantizer.cs | 350 ++++++++ src/ImageProcessor/Settings.StyleCop | 3 + 9 files changed, 940 insertions(+), 881 deletions(-) delete mode 100644 src/ImageProcessor/Imaging/ColorQuantizer.cs create mode 100644 src/ImageProcessor/Imaging/OctreeQuantizer.cs create mode 100644 src/ImageProcessor/Imaging/Quantizer.cs diff --git a/src/ImageProcessor.Web/NET45/HttpModules/ImageProcessingModule.cs b/src/ImageProcessor.Web/NET45/HttpModules/ImageProcessingModule.cs index 7d7024584..63f81a8b5 100644 --- a/src/ImageProcessor.Web/NET45/HttpModules/ImageProcessingModule.cs +++ b/src/ImageProcessor.Web/NET45/HttpModules/ImageProcessingModule.cs @@ -180,6 +180,7 @@ namespace ImageProcessor.Web.HttpModules // Dispose of any managed resources here. foreach (KeyValuePair semaphore in SemaphoreSlims) { + semaphore.Value.Wait(); semaphore.Value.Dispose(); } diff --git a/src/ImageProcessor.Web/NET45/ImageFactoryExtensions.cs b/src/ImageProcessor.Web/NET45/ImageFactoryExtensions.cs index e32b837de..e950cf13a 100644 --- a/src/ImageProcessor.Web/NET45/ImageFactoryExtensions.cs +++ b/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 } /// - /// The process image. + /// Processes the image. /// /// /// 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 { diff --git a/src/ImageProcessor/ImageFactory.cs b/src/ImageProcessor/ImageFactory.cs index aac5df72d..a0496e92e 100644 --- a/src/ImageProcessor/ImageFactory.cs +++ b/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 { diff --git a/src/ImageProcessor/ImageProcessor.csproj b/src/ImageProcessor/ImageProcessor.csproj index 66959a3d7..ed83f0121 100644 --- a/src/ImageProcessor/ImageProcessor.csproj +++ b/src/ImageProcessor/ImageProcessor.csproj @@ -74,6 +74,7 @@ + @@ -93,6 +94,7 @@ + diff --git a/src/ImageProcessor/Imaging/ColorQuantizer.cs b/src/ImageProcessor/Imaging/ColorQuantizer.cs deleted file mode 100644 index 004e1bc35..000000000 --- a/src/ImageProcessor/Imaging/ColorQuantizer.cs +++ /dev/null @@ -1,836 +0,0 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Copyright (c) James South. -// Licensed under the Apache License, Version 2.0. -// -// -// The color quantizer. -// -// -------------------------------------------------------------------------------------------------------------------- - -namespace ImageProcessor.Imaging -{ - #region Using - using System; - using System.Diagnostics.CodeAnalysis; - using System.Drawing; - using System.Drawing.Imaging; - using System.Runtime.InteropServices; - #endregion - - /// - /// The color quantizer. - /// - [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "Reviewed. Suppression is OK here.")] - public static class ColorQuantizer - { - #region Quantize methods - /// The quantize. - /// The image. - /// The bitmap pixel format. - /// The quantized image with the recalculated color palette. - public static Bitmap Quantize(Image image, PixelFormat bitmapPixelFormat) - { - // Use dither by default - return Quantize(image, bitmapPixelFormat, true); - } - - /// The quantize. - /// The image. - /// The pixel format. - /// The use dither. - /// The quantized image with the recalculated color palette. - 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); - } - } - - /// - /// Does the quantize. - /// - /// The bitmap source. - /// The pixel format. - /// if set to true [use dither]. - /// The quantized image with the recalculated color palette. - 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; - } - - /// - /// Dithers the source pixel. - /// - /// The buffer. - /// The row start. - /// The column. - /// The delta red. - /// The delta green. - /// The delta blue. - /// The weight. - 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); - } - - /// - /// Gets the source pixel. - /// - /// The buffer. - /// The row start. - /// The column. - /// The source pixel. - 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 - - /// Gets the channel adjustment. - /// The current. - /// The offset. - /// The channel adjustment. - private static byte ChannelAdjustment(byte current, int offset) - { - return (byte)Math.Min(255, Math.Max(0, current + offset)); - } - - #region Octree class - - /// data structure for storing and reducing colors used in the source image - private class Octree - { - /// The m_max colors. - private readonly int octreeMaxColors; - - /// The m_reducible nodes. - private readonly OctreeNode[] octreeReducibleNodes; - - /// The m_color count. - private int octreeColorCount; - - /// The m_has transparent. - private bool octreeHasTransparent; - - /// The m_last argb. - private int octreeLastArgb; - - /// The m_last node. - private OctreeNode octreeLastNode; - - /// The m_palette. - private Color[] octreePalette; - - /// The m_root. - private OctreeNode octreeRoot; - - /// Initializes a new instance of the class. Constructor - /// desired pixel format - 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); - } - - /// Add the given pixel color to the octree - /// points to the pixel color we want to add - 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; - } - } - - /// - /// 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. - /// - /// pointer to the pixel color we want to look up - /// index of the palette entry we want to use for this color - 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; - } - - /// - /// Return a color palette for the computed octree. - /// - /// A color palette for the computed octree - 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; - } - - /// set up the values we need to reuse the given pointer if the next color is argb - /// last node to which we added a color - /// last color we added - private void SetLastNode(OctreeNode node, int argb) - { - this.octreeLastNode = node; - this.octreeLastArgb = argb; - } - - /// When a reducible node is added, this method is called to add it to the appropriate - /// reducible node list (given its level) - /// node to add to a reducible list - 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 - - /// Node for an Octree structure - private class OctreeNode - { - /// The s_level masks. - private static readonly byte[] NodeLevelMasks = { 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 }; - - /// The m_level. - private readonly int nodeLevel; - - /// The m_octree. - private readonly Octree nodeOctree; - - /// The m_blue sum. - private int nodeBlueSum; - - /// The m_child nodes. - private OctreeNode[] nodeChildNodes; - - /// The m_green sum. - private int nodeGreenSum; - - /// The m_is leaf. - private bool nodeIsLeaf; - - /// The m_palette index. - private int nodePaletteIndex; - - /// - /// The pixel count.Information we need to calculate the average color for a set of pixels - /// - private int nodePixelCount; - - /// The m_red sum. - private int nodeRedSum; - - /// Initializes a new instance of the class. Constructor - /// level for this node - /// owning octree - 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); - } - } - - /// Gets Level. - internal int Level - { - get { return this.nodeLevel; } - } - - /// - /// 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. - /// - internal OctreeNode NextReducibleNode { get; set; } - - /// - /// Gets the average color for this node. - /// - 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); - } - } - - /// - /// Add the given color to this node if it is a leaf, otherwise recurse - /// down the appropriate child - /// - /// color to add - /// true if a new color was added to the octree - 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; - } - - /// - /// 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. - /// - /// source color to look up - /// palette index to use - 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; - } - - /// Reduce this node by combining all child nodes - /// number of nodes removed - 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; - } - - /// - /// 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. - /// - /// array of colors - /// index of the next empty slot in the array - 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); - } - } - } - } - - /// - /// Return the child index for a given color. - /// Depends on which level this node is in. - /// - /// color pixel to compute - /// child index (0-7) - 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 - /// - /// Structure of a Format32bppArgb pixel in memory. - /// - private class Pixel - { - /// - /// Gets or sets the blue component of the pixel. - /// - public byte Blue { get; set; } - - /// - /// Gets or sets the green component of the pixel. - /// - public byte Green { get; set; } - - /// - /// Gets or sets the red component of the pixel. - /// - public byte Red { get; set; } - - /// - /// Gets or sets the alpha component of the pixel. - /// - public byte Alpha { get; set; } - - /// - /// Gets the argb combination of the pixel. - /// - public int Argb - { - get - { - return this.Alpha << 24 | this.Red << 16 | this.Green << 8 | this.Blue; - } - } - } - #endregion - } -} diff --git a/src/ImageProcessor/Imaging/GifEncoder.cs b/src/ImageProcessor/Imaging/GifEncoder.cs index a46669b8d..09cb1aea9 100644 --- a/src/ImageProcessor/Imaging/GifEncoder.cs +++ b/src/ImageProcessor/Imaging/GifEncoder.cs @@ -114,7 +114,8 @@ namespace ImageProcessor.Imaging /// /// The stream. /// - private Stream inputStream; + // ReSharper disable once FieldCanBeMadeReadOnly.Local + private MemoryStream inputStream; /// /// The height. @@ -166,7 +167,7 @@ namespace ImageProcessor.Imaging /// /// The number of times to repeat the animation. /// - 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; } - /// - /// Returns the gif as an image. - /// - /// - /// The containing the animated gif. - /// - 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); - } - /// /// Disposes the object and frees resources for the Garbage Collector. /// @@ -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 diff --git a/src/ImageProcessor/Imaging/OctreeQuantizer.cs b/src/ImageProcessor/Imaging/OctreeQuantizer.cs new file mode 100644 index 000000000..e8ff086f0 --- /dev/null +++ b/src/ImageProcessor/Imaging/OctreeQuantizer.cs @@ -0,0 +1,558 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) James South. +// Licensed under the Apache License, Version 2.0. +// +// +// Encapsulates methods to calculate the colour palette if an image using an octree pattern. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace ImageProcessor.Imaging +{ + using System; + using System.Collections; + using System.Drawing; + using System.Drawing.Imaging; + + /// + /// Encapsulates methods to calculate the colour palette if an image using an octree pattern. + /// + public class OctreeQuantizer : Quantizer + { + /// + /// Stores the tree. + /// + private readonly Octree octree; + + /// + /// The maximum allowed color depth. + /// + private readonly int maxColors; + + /// + /// Initializes a new instance of the class. + /// + /// + /// 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 + /// + /// + /// The maximum number of colors to return + /// + /// + /// The number of significant bits + /// + 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; + } + + /// + /// Override this to process the pixel in the first pass of the algorithm + /// + /// The pixel to quantize + /// + /// This function need only be overridden if your quantize algorithm needs two passes, + /// such as an Octree quantizer. + /// + protected override void InitialQuantizePixel(Color32 pixel) + { + // Add the color to the octree + this.octree.AddColor(pixel); + } + + /// + /// Override this to process the pixel in the second pass of the algorithm + /// + /// The pixel to quantize + /// + /// The quantized value + /// + 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; + } + + /// + /// Retrieve the palette for the quantized image + /// + /// Any old palette, this is overwritten + /// + /// The new color palette + /// + 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; + } + + /// + /// Describes a tree data structure in which each internal node has exactly eight children. + /// + private class Octree + { + /// + /// Mask used when getting the appropriate pixels for a given node + /// + private static readonly int[] Mask = { 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 }; + + /// + /// The root of the octree + /// + private readonly OctreeNode root; + + /// + /// Array of reducible nodes + /// + private readonly OctreeNode[] reducibleNodes; + + /// + /// Maximum number of significant bits in the image + /// + private readonly int maxColorBits; + + /// + /// Number of leaves in the tree + /// + private int leafCount; + + /// + /// Store the last node quantized + /// + private OctreeNode previousNode; + + /// + /// Cache the previous color quantized + /// + private int previousColor; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The maximum number of significant bits in the image + /// + 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; + } + + /// + /// Gets or sets the number of leaves in the tree + /// + private int Leaves + { + get { return this.leafCount; } + set { this.leafCount = value; } + } + + /// + /// Add a given colour value to the octree + /// + /// + /// The color value to add. + /// + 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); + } + } + + /// + /// Convert the nodes in the octree to a palette with a maximum of colorCount colors + /// + /// The maximum number of colors + /// An array-list with the palletized colors + 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; + } + + /// + /// Get the palette index for the passed colour. + /// + /// + /// The color to return the palette index for. + /// + /// + /// The palette index for the passed colour. + /// + public int GetPaletteIndex(Color32 pixel) + { + return this.root.GetPaletteIndex(pixel, 0); + } + + /// + /// Return the array of reducible nodes + /// + /// + /// The array of . + /// + protected OctreeNode[] ReducibleNodes() + { + return this.reducibleNodes; + } + + /// + /// Keep track of the previous node that was quantized + /// + /// The node last quantized + protected void TrackPrevious(OctreeNode node) + { + this.previousNode = node; + } + + /// + /// Reduce the depth of the tree + /// + 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; + } + + /// + /// Class which encapsulates each node in the tree + /// + protected class OctreeNode + { + /// + /// Pointers to any child nodes + /// + private readonly OctreeNode[] children; + + /// + /// Pointer to next reducible node + /// + private readonly OctreeNode nextReducible; + + /// + /// Flag indicating that this is a leaf node + /// + private bool leaf; + + /// + /// Number of pixels in this node + /// + private int pixelCount; + + /// + /// Red component + /// + private int red; + + /// + /// Green Component + /// + private int green; + + /// + /// Blue component + /// + private int blue; + + /// + /// The index of this node in the palette + /// + private int paletteIndex; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The level in the tree = 0 - 7 + /// + /// + /// The number of significant color bits in the image + /// + /// + /// The tree to which this node belongs + /// + 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]; + } + } + + /// + /// Gets the next reducible node. + /// + public OctreeNode NextReducible + { + get { return this.nextReducible; } + } + + /// + /// Add a color into the tree + /// + /// The color + /// The number of significant color bits + /// The level in the tree + /// The tree to which this node belongs + 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); + } + } + } + + /// + /// Reduce this node by removing all of its children + /// + /// The number of leaves removed + 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; + } + + /// + /// Traverse the tree, building up the color palette + /// + /// The palette + /// The current palette index + 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); + } + } + } + } + + /// + /// Return the palette index for the passed color. + /// + /// + /// The pixel. + /// + /// + /// The level. + /// + /// + /// The palette index for the passed color. + /// + 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; + } + + /// + /// Increment the pixel count and add to the color information + /// + /// + /// The pixel. + /// + public void Increment(Color32 pixel) + { + this.pixelCount++; + this.red += pixel.Red; + this.green += pixel.Green; + this.blue += pixel.Blue; + } + } + } + } +} \ No newline at end of file diff --git a/src/ImageProcessor/Imaging/Quantizer.cs b/src/ImageProcessor/Imaging/Quantizer.cs new file mode 100644 index 000000000..8241397e3 --- /dev/null +++ b/src/ImageProcessor/Imaging/Quantizer.cs @@ -0,0 +1,350 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) James South. +// Licensed under the Apache License, Version 2.0. +// +// +// Defines the Quantizer type. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace ImageProcessor.Imaging +{ + using System; + using System.Drawing; + using System.Drawing.Imaging; + using System.Runtime.InteropServices; + + /// + /// Encapsulates methods to calculate the color palette of an image. + /// + public abstract class Quantizer + { + /// + /// The flag used to indicate whether a single pass or two passes are needed for quantization. + /// + private readonly bool singlePass; + + /// + /// The size in bytes of the 32 bytes per pixel Color structure. + /// + private readonly int pixelSize; + + /// + /// Initializes a new instance of the Quantizer class. + /// + /// + /// If set to , then the quantizer will loop through the source pixels once; + /// otherwise, . + /// + protected Quantizer(bool singlePass) + { + this.singlePass = singlePass; + this.pixelSize = Marshal.SizeOf(typeof(Color32)); + } + + /// + /// Quantizes the given Image and returns the resulting output + /// Bitmap. + /// + /// The image to quantize + /// + /// A quantized Bitmap version of the Image + /// + 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; + } + + /// + /// Execute the first pass through the pixels in the image + /// + /// The source data + /// The width in pixels of the image + /// The height in pixels of the image + 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); + } + } + + /// + /// Execute a second pass through the bitmap + /// + /// The source bitmap, locked into memory + /// The output bitmap + /// The width in pixels of the image + /// The height in pixels of the image + /// The bounding rectangle + 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); + } + } + + /// + /// Override this to process the pixel in the first pass of the algorithm + /// + /// The pixel to quantize + /// + /// This function need only be overridden if your quantize algorithm needs two passes, + /// such as an Octree quantizer. + /// + protected virtual void InitialQuantizePixel(Color32 pixel) + { + } + + /// + /// Override this to process the pixel in the second pass of the algorithm + /// + /// The pixel to quantize + /// The quantized value + protected abstract byte QuantizePixel(Color32 pixel); + + /// + /// Retrieve the palette for the quantized image + /// + /// Any old palette, this is overwritten + /// The new color palette + protected abstract ColorPalette GetPalette(ColorPalette original); + + /// + /// Structure that defines a 32 bit color + /// + /// + /// 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 + /// + [StructLayout(LayoutKind.Explicit)] + public struct Color32 + { + /// + /// Holds the blue component of the colour + /// + [FieldOffset(0)] + private byte blue; + + /// + /// Holds the green component of the colour + /// + [FieldOffset(1)] + private byte green; + + /// + /// Holds the red component of the colour + /// + [FieldOffset(2)] + private byte red; + + /// + /// Holds the alpha component of the colour + /// + [FieldOffset(3)] + private byte alpha; + + /// + /// Permits the color32 to be treated as a 32 bit integer. + /// + [FieldOffset(0)] + private int argb; + + /// + /// Initializes a new instance of the Color32 structure. + /// + /// The pointer to the pixel. + public Color32(IntPtr sourcePixel) + { + this = (Color32)Marshal.PtrToStructure(sourcePixel, typeof(Color32)); + } + + /// + /// Gets or sets the blue component of the colour + /// + public byte Blue + { + get { return this.blue; } + set { this.blue = value; } + } + + /// + /// Gets or sets the green component of the colour + /// + public byte Green + { + get { return this.green; } + set { this.green = value; } + } + + /// + /// Gets or sets the red component of the colour + /// + public byte Red + { + get { return this.red; } + set { this.red = value; } + } + + /// + /// Gets or sets the alpha component of the colour + /// + public byte Alpha + { + get { return this.alpha; } + set { this.alpha = value; } + } + + /// + /// Gets or sets the ARGB component, permitting the color32 to be treated as a 32 bit integer. + /// + public int Argb + { + get { return this.argb; } + set { this.argb = value; } + } + + /// + /// Gets the color for this Color32 object + /// + public Color Color + { + get { return Color.FromArgb(this.Alpha, this.Red, this.Green, this.Blue); } + } + } + } +} \ No newline at end of file diff --git a/src/ImageProcessor/Settings.StyleCop b/src/ImageProcessor/Settings.StyleCop index e9d49b9f2..29724f842 100644 --- a/src/ImageProcessor/Settings.StyleCop +++ b/src/ImageProcessor/Settings.StyleCop @@ -2,7 +2,10 @@ behaviour + colour lomograph + octree + quantizer