diff --git a/src/ImageProcessor.Web/NET4/Settings.StyleCop b/src/ImageProcessor.Web/NET4/Settings.StyleCop
new file mode 100644
index 0000000000..2538255678
--- /dev/null
+++ b/src/ImageProcessor.Web/NET4/Settings.StyleCop
@@ -0,0 +1,11 @@
+
+
+
+
+ James South
+ Copyright (c) James South.
+Licensed under the Apache License, Version 2.0.
+
+
+
+
\ No newline at end of file
diff --git a/src/ImageProcessor.Web/NET45/ImageFactoryExtensions.cs b/src/ImageProcessor.Web/NET45/ImageFactoryExtensions.cs
index 2a6eb26a90..bbf71e4d8f 100644
--- a/src/ImageProcessor.Web/NET45/ImageFactoryExtensions.cs
+++ b/src/ImageProcessor.Web/NET45/ImageFactoryExtensions.cs
@@ -9,6 +9,7 @@ namespace ImageProcessor.Web
{
#region Using
using System.Collections.Generic;
+ using System.Drawing;
using System.Linq;
using ImageProcessor.Processors;
using ImageProcessor.Web.Config;
@@ -41,7 +42,7 @@ namespace ImageProcessor.Web
// It's faster to lock and run through our activated list than to create new instances.
lock (SyncRoot)
{
- // Get a list of all graphics processors that have parsed and matched the querystring.
+ // Get a list of all graphics processors that have parsed and matched the query string.
List graphicsProcessors =
ImageProcessorConfig.Instance.GraphicsProcessors
.Where(x => x.MatchRegexIndex(factory.QueryString) != int.MaxValue)
@@ -51,7 +52,8 @@ namespace ImageProcessor.Web
// Loop through and process the image.
foreach (IGraphicsProcessor graphicsProcessor in graphicsProcessors)
{
- factory.Image = graphicsProcessor.ProcessImage(factory);
+ Image img = graphicsProcessor.ProcessImage(factory);
+ factory.Update(img);
}
}
}
diff --git a/src/ImageProcessor/ImageFactory.cs b/src/ImageProcessor/ImageFactory.cs
index 328bd4e2d2..02f8028f86 100644
--- a/src/ImageProcessor/ImageFactory.cs
+++ b/src/ImageProcessor/ImageFactory.cs
@@ -1,22 +1,25 @@
-// -----------------------------------------------------------------------
+// --------------------------------------------------------------------------------------------------------------------
//
-// Copyright (c) James South.
-// Licensed under the Apache License, Version 2.0.
+// Copyright (c) James South.
+// Licensed under the Apache License, Version 2.0.
//
-// -----------------------------------------------------------------------
+//
+// Encapsulates methods for processing image files.
+//
+// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor
{
#region Using
using System;
using System.Collections.Generic;
+ using System.Diagnostics.CodeAnalysis;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using ImageProcessor.Imaging;
using ImageProcessor.Processors;
-
#endregion
///
@@ -35,6 +38,11 @@ namespace ImageProcessor
///
private ImageFormat backupImageFormat;
+ ///
+ /// Whether the image is indexed.
+ ///
+ private bool isIndexed;
+
///
/// A value indicating whether this instance of the given entity has been disposed.
///
@@ -72,9 +80,9 @@ namespace ImageProcessor
#region Properties
///
- /// Gets or sets the local image for manipulation.
+ /// Gets the local image for manipulation.
///
- public Image Image { get; set; }
+ public Image Image { get; private set; }
///
/// Gets the path to the local image for manipulation.
@@ -114,7 +122,7 @@ namespace ImageProcessor
///
public ImageFactory Load(MemoryStream memoryStream)
{
- // Set our image as the memorystream value.
+ // Set our image as the memory stream value.
this.Image = Image.FromStream(memoryStream);
// Store the stream in the image Tag property so we can dispose of it later.
@@ -124,6 +132,7 @@ namespace ImageProcessor
this.JpegQuality = DefaultJpegQuality;
this.backupImageFormat = ImageFormat.Jpeg;
this.ImageFormat = ImageFormat.Jpeg;
+ this.isIndexed = ImageUtils.IsIndexed(this.Image);
this.ShouldProcess = true;
return this;
@@ -154,7 +163,7 @@ namespace ImageProcessor
this.ImagePath = path;
this.QueryString = query;
- // Open a filstream to prevent the need for lock.
+ // Open a file stream to prevent the need for lock.
using (FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read))
{
MemoryStream memoryStream = new MemoryStream();
@@ -165,7 +174,7 @@ namespace ImageProcessor
// Set the position to 0 afterwards.
fileStream.Position = memoryStream.Position = 0;
- // Set our image as the memorystream value.
+ // Set our image as the memory stream value.
this.Image = Image.FromStream(memoryStream);
// Store the stream in the image Tag property so we can dispose of it later.
@@ -176,6 +185,7 @@ namespace ImageProcessor
ImageFormat imageFormat = ImageUtils.GetImageFormat(imageName);
this.backupImageFormat = imageFormat;
this.ImageFormat = imageFormat;
+ this.isIndexed = ImageUtils.IsIndexed(this.Image);
this.ShouldProcess = true;
}
}
@@ -183,6 +193,23 @@ namespace ImageProcessor
return this;
}
+ ///
+ /// Updates the specified image. Used by the various IProcessors.
+ ///
+ /// The image.
+ ///
+ /// The current instance of the class.
+ ///
+ public ImageFactory Update(Image image)
+ {
+ if (this.ShouldProcess)
+ {
+ this.Image = image;
+ }
+
+ return this;
+ }
+
///
/// Resets the current image to its original loaded state.
///
@@ -195,7 +222,7 @@ namespace ImageProcessor
{
MemoryStream memoryStream = (MemoryStream)this.Image.Tag;
- // Set our new image as the memorystream value.
+ // Set our new image as the memory stream value.
Image newImage = Image.FromStream(memoryStream);
// Store the stream in the image Tag property so we can dispose of it later.
@@ -208,6 +235,7 @@ namespace ImageProcessor
// Set the other properties.
this.JpegQuality = DefaultJpegQuality;
this.ImageFormat = this.backupImageFormat;
+ this.isIndexed = ImageUtils.IsIndexed(this.Image);
}
return this;
@@ -386,13 +414,16 @@ namespace ImageProcessor
/// Sets the output format of the current image to the matching .
///
/// The . to set the image to.
+ /// Whether the pixel format of the image should be indexed. Used for generating Png8 images.
///
/// The current instance of the class.
///
- public ImageFactory Format(ImageFormat imageFormat)
+ [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "Reviewed. Suppression is OK here.")]
+ public ImageFactory Format(ImageFormat imageFormat, bool indexedFormat = false)
{
if (this.ShouldProcess)
{
+ this.isIndexed = indexedFormat;
this.ImageFormat = imageFormat;
}
@@ -584,8 +615,8 @@ namespace ImageProcessor
string extension = ImageUtils.GetExtensionFromImageFormat(this.ImageFormat);
filePath = length == -1 ? filePath + extension : filePath.Substring(0, length) + extension;
- // Fix the colour palette of gif images.
- this.FixGifs();
+ // Fix the colour palette of indexed images.
+ this.FixIndexedPallete();
if (this.ImageFormat.Equals(ImageFormat.Jpeg))
{
@@ -594,12 +625,14 @@ namespace ImageProcessor
using (EncoderParameters encoderParameters = ImageUtils.GetEncodingParameters(this.JpegQuality))
{
ImageCodecInfo imageCodecInfo =
- ImageCodecInfo.GetImageEncoders().FirstOrDefault(
- ici => ici.MimeType.Equals("image/jpeg", StringComparison.OrdinalIgnoreCase));
+ ImageCodecInfo.GetImageEncoders()
+ .FirstOrDefault(
+ ici => ici.MimeType.Equals("image/jpeg", StringComparison.OrdinalIgnoreCase));
- // ReSharper disable AssignNullToNotNullAttribute
- this.Image.Save(filePath, imageCodecInfo, encoderParameters);
- // ReSharper restore AssignNullToNotNullAttribute
+ if (imageCodecInfo != null)
+ {
+ this.Image.Save(filePath, imageCodecInfo, encoderParameters);
+ }
}
}
else
@@ -625,7 +658,7 @@ namespace ImageProcessor
if (this.ShouldProcess)
{
// Fix the colour palette of gif images.
- this.FixGifs();
+ this.FixIndexedPallete();
if (this.ImageFormat.Equals(ImageFormat.Jpeg))
{
@@ -704,17 +737,15 @@ namespace ImageProcessor
#endregion
///
- /// Uses the
+ /// Uses the
/// to fix the color palette of gif images.
///
- private void FixGifs()
+ private void FixIndexedPallete()
{
- // Fix the colour palette of gif images.
- // TODO: Why does the palette not get fixed when resized to the same dimensions.
- if (object.Equals(this.ImageFormat, ImageFormat.Gif))
+ // Fix the colour palette of indexed images.
+ if (this.isIndexed)
{
- OctreeQuantizer quantizer = new OctreeQuantizer(255, 8);
- this.Image = quantizer.Quantize(this.Image);
+ this.Image = ColorQuantizer.Quantize(this.Image, PixelFormat.Format8bppIndexed);
}
}
#endregion
diff --git a/src/ImageProcessor/ImageProcessor.csproj b/src/ImageProcessor/ImageProcessor.csproj
index df2f93ff8e..038064bffd 100644
--- a/src/ImageProcessor/ImageProcessor.csproj
+++ b/src/ImageProcessor/ImageProcessor.csproj
@@ -61,6 +61,7 @@
+
@@ -74,8 +75,6 @@
-
-
diff --git a/src/ImageProcessor/Imaging/ColorQuantizer.cs b/src/ImageProcessor/Imaging/ColorQuantizer.cs
new file mode 100644
index 0000000000..932da35090
--- /dev/null
+++ b/src/ImageProcessor/Imaging/ColorQuantizer.cs
@@ -0,0 +1,836 @@
+// --------------------------------------------------------------------------------------------------------------------
+//
+// 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/Filters/GothamMatrixFilter.cs b/src/ImageProcessor/Imaging/Filters/GothamMatrixFilter.cs
index 04e0b55280..8f46d9f1d8 100644
--- a/src/ImageProcessor/Imaging/Filters/GothamMatrixFilter.cs
+++ b/src/ImageProcessor/Imaging/Filters/GothamMatrixFilter.cs
@@ -76,11 +76,11 @@ namespace ImageProcessor.Imaging.Filters
}
// Add brightness and contrast to finish the effect.
- factory.Image = newImage;
+ factory.Update(newImage);
Brightness brightness = new Brightness { DynamicParameter = 5 };
newImage = (Bitmap)brightness.ProcessImage(factory);
- factory.Image = newImage;
+ factory.Update(newImage);
Contrast contrast = new Contrast { DynamicParameter = 85 };
newImage = (Bitmap)contrast.ProcessImage(factory);
diff --git a/src/ImageProcessor/Imaging/Filters/LomographMatrixFilter.cs b/src/ImageProcessor/Imaging/Filters/LomographMatrixFilter.cs
index 12e6927f06..121d2a887a 100644
--- a/src/ImageProcessor/Imaging/Filters/LomographMatrixFilter.cs
+++ b/src/ImageProcessor/Imaging/Filters/LomographMatrixFilter.cs
@@ -57,7 +57,7 @@ namespace ImageProcessor.Imaging.Filters
}
// Add a vignette to finish the effect.
- factory.Image = newImage;
+ factory.Update(newImage);
Vignette vignette = new Vignette();
newImage = (Bitmap)vignette.ProcessImage(factory);
diff --git a/src/ImageProcessor/Imaging/Filters/PolaroidMatrixFilter.cs b/src/ImageProcessor/Imaging/Filters/PolaroidMatrixFilter.cs
index 6a708d49b1..0253e2c56d 100644
--- a/src/ImageProcessor/Imaging/Filters/PolaroidMatrixFilter.cs
+++ b/src/ImageProcessor/Imaging/Filters/PolaroidMatrixFilter.cs
@@ -86,7 +86,7 @@ namespace ImageProcessor.Imaging.Filters
}
// Add a vignette to finish the effect.
- factory.Image = newImage;
+ factory.Update(newImage);
Vignette vignette = new Vignette();
newImage = (Bitmap)vignette.ProcessImage(factory);
diff --git a/src/ImageProcessor/Imaging/ImageUtils.cs b/src/ImageProcessor/Imaging/ImageUtils.cs
index 8a62f2e3c1..fdfb0ca83c 100644
--- a/src/ImageProcessor/Imaging/ImageUtils.cs
+++ b/src/ImageProcessor/Imaging/ImageUtils.cs
@@ -1,14 +1,18 @@
-// -----------------------------------------------------------------------
+// --------------------------------------------------------------------------------------------------------------------
//
-// Copyright (c) James South.
-// Licensed under the Apache License, Version 2.0.
+// Copyright (c) James South.
+// Licensed under the Apache License, Version 2.0.
//
-// -----------------------------------------------------------------------
+//
+// Encapsulates useful image utility methods.
+//
+// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Imaging
{
#region Using
using System;
+ using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
@@ -24,7 +28,7 @@ namespace ImageProcessor.Imaging
///
/// The image format regex.
///
- private static readonly Regex FormatRegex = new Regex(@"j(pg|peg)|bmp|png|gif", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.RightToLeft);
+ private static readonly Regex FormatRegex = new Regex(@"(\.?)(j(pg|peg)|bmp|png|gif|ti(f|ff))$", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.RightToLeft);
///
/// Returns the correct response type based on the given request path.
@@ -39,14 +43,17 @@ namespace ImageProcessor.Imaging
{
foreach (Match match in FormatRegex.Matches(request))
{
- switch (match.Value)
+ switch (match.Value.ToUpperInvariant())
{
- case "png":
+ case "PNG":
return ResponseType.Png;
- case "bmp":
+ case "BMP":
return ResponseType.Bmp;
- case "gif":
+ case "GIF":
return ResponseType.Gif;
+ case "TIF":
+ case "TIFF":
+ return ResponseType.Tiff;
default:
return ResponseType.Jpeg;
}
@@ -75,6 +82,9 @@ namespace ImageProcessor.Imaging
return ImageFormat.Bmp;
case ".GIF":
return ImageFormat.Gif;
+ case ".TIF":
+ case ".TIFF":
+ return ImageFormat.Tiff;
default:
// Should be a jpeg.
return ImageFormat.Jpeg;
@@ -104,6 +114,10 @@ namespace ImageProcessor.Imaging
return ".bmp";
case "Png":
return ".png";
+ case "Tif":
+ return ".tif";
+ case "Tiff":
+ return ".tif";
default:
return ".jpg";
}
@@ -126,6 +140,8 @@ namespace ImageProcessor.Imaging
return ImageFormat.Bmp;
case ResponseType.Gif:
return ImageFormat.Gif;
+ case ResponseType.Tiff:
+ return ImageFormat.Tiff;
default:
// Should be a jpeg.
return ImageFormat.Jpeg;
@@ -181,25 +197,16 @@ namespace ImageProcessor.Imaging
/// True the value contains a valid image extension, otherwise false.
public static bool IsValidImageExtension(string fileName)
{
- bool isValid = false;
-
- if (!string.IsNullOrWhiteSpace(fileName))
- {
- string[] fileExtensions = { ".BMP", ".JPG", ".PNG", ".GIF", ".JPEG" };
-
- Parallel.ForEach(
- fileExtensions,
- (extension, loop) =>
- {
- if (fileName.ToUpperInvariant().EndsWith(extension))
- {
- isValid = true;
- loop.Stop();
- }
- });
- }
+ return FormatRegex.IsMatch(fileName);
+ }
- return isValid;
+ /// Returns a value indicating whether or not the given bitmap is indexed.
+ /// The image to check
+ /// Whether or not the given bitmap is indexed.
+ public static bool IsIndexed(Image image)
+ {
+ // Test value of flags using bitwise AND.
+ return (image.PixelFormat & PixelFormat.Indexed) != 0;
}
}
}
diff --git a/src/ImageProcessor/Imaging/OctreeQuantizer.cs b/src/ImageProcessor/Imaging/OctreeQuantizer.cs
deleted file mode 100644
index af30b3f379..0000000000
--- a/src/ImageProcessor/Imaging/OctreeQuantizer.cs
+++ /dev/null
@@ -1,543 +0,0 @@
-// -----------------------------------------------------------------------
-//
-// Copyright (c) James South.
-// Licensed under the Apache License, Version 2.0.
-//
-// -----------------------------------------------------------------------
-
-namespace ImageProcessor.Imaging
-{
- #region Using
- using System;
- using System.Collections;
- using System.Drawing;
- using System.Drawing.Imaging;
- #endregion
-
- ///
- /// Encapsulates methods to calculate the colour palette if an image using an octree pattern.
- ///
- internal class OctreeQuantizer : Quantizer
- {
- #region Fields
- ///
- /// Stores the tree.
- ///
- private readonly Octree octree;
-
- ///
- /// The maximum allowed color depth.
- ///
- private readonly int maxColors;
- #endregion
-
- ///
- /// Initializes a new instance of the OctreeQuantizer class.
- ///
- ///
- /// The Octree quantizer is a two pass algorithm. The initial pass sets up the octree,
- /// the second pass quantizes a colour based on the nodes in the tree
- ///
- /// The maximum number of colours to return, maximum 255.
- /// The number of significant bits minimum 1, maximum 8.
- public OctreeQuantizer(int maxColors, int maxColorBits)
- : base(false)
- {
- if (maxColors > 255)
- {
- throw new ArgumentOutOfRangeException("maxColors", maxColors, "The number of colours 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;
- }
-
- ///
- /// 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 colour 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 colour at [this.maxColors] is set to transparent
- byte paletteIndex;
-
- // Get the palette index if this non-transparent
- if (pixel.Alpha > 0)
- {
- paletteIndex = (byte)this.octree.GetPaletteIndex(pixel);
- }
- else
- {
- paletteIndex = (byte)this.maxColors;
- }
-
- return paletteIndex;
- }
-
- ///
- /// Retrieve the palette for the quantized image
- ///
- /// Any old palette, this is overwritten
- /// The new colour palette
- protected override ColorPalette GetPalette(ColorPalette original)
- {
- // First off convert the octree to this.maxColors colours
- ArrayList palette = this.octree.Palletize(this.maxColors - 1);
-
- // Then convert the palette based on those colours
- for (int index = 0; index < palette.Count; index++)
- {
- original.Entries[index] = (Color)palette[index];
- }
-
- // Add the transparent colour
- original.Entries[this.maxColors] = Color.FromArgb(0, 0, 0, 0);
-
- return original;
- }
-
- ///
- /// Describes a tree data structure in which each internal node has exactly eight children.
- ///
- private class Octree
- {
- #region Fields
- ///
- /// Mask used when getting the appropriate pixels for a given node
- ///
- private static readonly int[] mask = new int[8] { 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 };
-
- ///
- /// The root of the octree
- ///
- private readonly OctreeNode root;
-
- ///
- /// Number of leaves in the tree
- ///
- private int leafCount;
-
- ///
- /// Array of reducible nodes
- ///
- private OctreeNode[] reducibleNodes;
-
- ///
- /// Maximum number of significant bits in the image
- ///
- private int maxColorBits;
-
- ///
- /// Store the last node quantized
- ///
- private OctreeNode previousNode;
-
- ///
- /// Cache the previous color quantized
- ///
- private int previousColor;
- #endregion
-
- #region Constructors
- ///
- /// Initializes a new instance of the Octree class.
- ///
- /// The maximum number of significant bits in the image
- public Octree(int maxBits)
- {
- this.maxColorBits = maxBits;
- this.leafCount = 0;
- this.reducibleNodes = new OctreeNode[9];
- this.root = new OctreeNode(0, this.maxColorBits, this);
- this.previousColor = 0;
- this.previousNode = null;
- }
- #endregion
-
- #region Properties
- ///
- /// Gets or sets the number of leaves in the tree
- ///
- public int Leaves
- {
- get { return this.leafCount; }
- set { this.leafCount = value; }
- }
-
- ///
- /// Gets the array of reducible nodes
- ///
- protected OctreeNode[] ReducibleNodes
- {
- get { return this.reducibleNodes; }
- }
- #endregion
-
- ///
- /// 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 colour as the last
- if (this.previousColor == pixel.ARGB)
- {
- // If so, check if I have a previous node setup. This will only occur if the first colour 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);
- }
- }
-
- ///
- /// Reduce the depth of the tree
- ///
- public void Reduce()
- {
- // Find the deepest level containing at least one reducible node
- int index = this.maxColorBits - 1;
- while ((index > 0) && (this.reducibleNodes[index] == null))
- {
- 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;
- }
-
- ///
- /// Convert the nodes in the octree to a palette with a maximum of colorCount colours
- ///
- /// The maximum number of colours
- /// An array list with the palletized colours
- public ArrayList Palletize(int colorCount)
- {
- while (this.Leaves > colorCount)
- {
- this.Reduce();
- }
-
- // Now palletize 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);
- }
-
- ///
- /// Keep track of the previous node that was quantized
- ///
- /// The node last quantized
- protected void TrackPrevious(OctreeNode node)
- {
- this.previousNode = node;
- }
-
- ///
- /// Class which encapsulates each node in the tree
- ///
- protected class OctreeNode
- {
- #region Fields
- ///
- /// 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;
-
- ///
- /// Pointers to any child nodes
- ///
- private OctreeNode[] children;
-
- ///
- /// The index of this node in the palette
- ///
- private int paletteIndex;
- #endregion
-
- #region Constructors
- ///
- /// 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
- this.NextReducible = octree.ReducibleNodes[level];
- octree.ReducibleNodes[level] = this;
- this.children = new OctreeNode[8];
- }
- }
- #endregion
-
- #region Properties
-
- ///
- /// Gets or the next reducible node
- ///
- public OctreeNode NextReducible { get; private set; }
-
- ///
- /// Gets the child nodes
- ///
- private OctreeNode[] Children
- {
- get { return this.children; }
- }
- #endregion
-
- #region Methods
- ///
- /// 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
- {
- // 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 & 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 childPosition = 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;
- ++childPosition;
- 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 childPosition - 1;
- }
-
- ///
- /// Traverse the tree, building up the color palette
- ///
- /// The palette
- /// The current palette index
- public void ConstructPalette(ArrayList palette, ref int currentPaletteIndex)
- {
- if (this.leaf)
- {
- // Consume the next palette index
- this.paletteIndex = currentPaletteIndex++;
-
- // 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 index = 0; index < 8; index++)
- {
- if (null != this.children[index])
- {
- this.children[index].ConstructPalette(palette, ref currentPaletteIndex);
- }
- }
- }
- }
-
- ///
- /// 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 currentPaletteIndex = this.paletteIndex;
-
- if (!this.leaf)
- {
- int shift = 7 - level;
- int index = ((pixel.Red & mask[level]) >> (shift - 2)) |
- ((pixel.Green & mask[level]) >> (shift - 1)) |
- ((pixel.Blue & mask[level]) >> shift);
-
- if (null != this.children[index])
- {
- currentPaletteIndex = this.children[index].GetPaletteIndex(pixel, level + 1);
- }
- else
- {
- throw new Exception("Didn't expect this!");
- }
- }
-
- return currentPaletteIndex;
- }
-
- ///
- /// 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;
- }
- #endregion
- }
- }
- }
-}
diff --git a/src/ImageProcessor/Imaging/Quantizer.cs b/src/ImageProcessor/Imaging/Quantizer.cs
deleted file mode 100644
index f2506fb5a4..0000000000
--- a/src/ImageProcessor/Imaging/Quantizer.cs
+++ /dev/null
@@ -1,315 +0,0 @@
-// -----------------------------------------------------------------------
-//
-// Copyright (c) James South.
-// Licensed under the Apache License, Version 2.0.
-//
-// -----------------------------------------------------------------------
-
-namespace ImageProcessor.Imaging
-{
- #region Using
- using System;
- using System.Drawing;
- using System.Drawing.Imaging;
- using System.Runtime.InteropServices;
- #endregion
-
- ///
- /// Encapsulates methods to calculate the color palette of an image.
- ///
- internal abstract class Quantizer
- {
- #region Fields
- ///
- /// 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 bpp Colour structure.
- ///
- private readonly int pixelSize;
- #endregion
-
- ///
- /// 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
- using (Bitmap copy = new Bitmap(width, height, PixelFormat.Format32bppArgb))
- {
- Bitmap output = null;
-
- // Define a pointer to the bitmap data
- BitmapData sourceData = null;
- try
- {
- // And construct an 8bpp version
- output = new Bitmap(width, height, PixelFormat.Format8bppIndexed);
-
- // Now lock the bitmap into memory
- using (Graphics graphics = Graphics.FromImage(copy))
- {
- graphics.PageUnit = GraphicsUnit.Pixel;
-
- // Draw the source image onto the copy bitmap,
- // which will effect a widening as appropriate.
- graphics.DrawImage(source, bounds);
- }
-
- // 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 colour 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);
- }
- catch
- {
- if (output != null)
- {
- output.Dispose();
- }
- }
- 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.ReadByte(previousPixel) != Marshal.ReadByte(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 color
- ///
- [FieldOffset(0)]
- public byte Blue;
-
- ///
- /// Holds the green component of the color
- ///
- [FieldOffset(1)]
- public byte Green;
-
- ///
- /// Holds the red component of the color
- ///
- [FieldOffset(2)]
- public byte Red;
-
- ///
- /// Holds the alpha component of the color
- ///
- [FieldOffset(3)]
- public byte Alpha;
-
- ///
- /// Permits the color32 to be treated as a 32 bit integer.
- ///
- [FieldOffset(0)]
- public 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 the color for this Color32 object
- ///
- public Color Color
- {
- get { return Color.FromArgb(this.Alpha, this.Red, this.Green, this.Blue); }
- }
- }
- }
-}
diff --git a/src/ImageProcessor/Processors/Format.cs b/src/ImageProcessor/Processors/Format.cs
index 336e5dd4c6..42efc8bd83 100644
--- a/src/ImageProcessor/Processors/Format.cs
+++ b/src/ImageProcessor/Processors/Format.cs
@@ -1,9 +1,12 @@
-// -----------------------------------------------------------------------
+// --------------------------------------------------------------------------------------------------------------------
//
-// Copyright (c) James South.
-// Licensed under the Apache License, Version 2.0.
+// Copyright (c) James South.
+// Licensed under the Apache License, Version 2.0.
//
-// -----------------------------------------------------------------------
+//
+// Sets the output of the image to a specific format.
+//
+// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Processors
{
@@ -22,7 +25,7 @@ namespace ImageProcessor.Processors
///
/// The regular expression to search strings for.
///
- private static readonly Regex QueryRegex = new Regex(@"format=(jpeg|png|bmp|gif)", RegexOptions.Compiled);
+ private static readonly Regex QueryRegex = new Regex(@"format=(jpeg|png|png8|bmp|gif|tif)", RegexOptions.Compiled);
#region IGraphicsProcessor Members
///
@@ -110,17 +113,26 @@ namespace ImageProcessor.Processors
public Image ProcessImage(ImageFactory factory)
{
string format = this.DynamicParameter;
+ bool isIndexed = false;
ImageFormat imageFormat;
switch (format)
{
case "png":
imageFormat = ImageFormat.Png;
break;
+ case "png8":
+ imageFormat = ImageFormat.Png;
+ isIndexed = true;
+ break;
case "bmp":
imageFormat = ImageFormat.Bmp;
break;
case "gif":
imageFormat = ImageFormat.Gif;
+ isIndexed = true;
+ break;
+ case "tif":
+ imageFormat = ImageFormat.Tiff;
break;
default:
// Should be a jpeg.
@@ -129,7 +141,7 @@ namespace ImageProcessor.Processors
}
// Set the internal property.
- factory.Format(imageFormat);
+ factory.Format(imageFormat, isIndexed);
return factory.Image;
}
diff --git a/src/ImageProcessor/Settings.StyleCop b/src/ImageProcessor/Settings.StyleCop
new file mode 100644
index 0000000000..2538255678
--- /dev/null
+++ b/src/ImageProcessor/Settings.StyleCop
@@ -0,0 +1,11 @@
+
+
+
+
+ James South
+ Copyright (c) James South.
+Licensed under the Apache License, Version 2.0.
+
+
+
+
\ No newline at end of file
diff --git a/src/TestWebsites/NET45/Test_Website_NET45/Images/Penguins-8.png.REMOVED.git-id b/src/TestWebsites/NET45/Test_Website_NET45/Images/Penguins-8.png.REMOVED.git-id
new file mode 100644
index 0000000000..aa9a70e0f4
--- /dev/null
+++ b/src/TestWebsites/NET45/Test_Website_NET45/Images/Penguins-8.png.REMOVED.git-id
@@ -0,0 +1 @@
+c3d556d9d486b8b8b49cdbcc9c12a9d3a2db4c1f
\ No newline at end of file
diff --git a/src/TestWebsites/NET45/Test_Website_NET45/Images/Penguins.tif.REMOVED.git-id b/src/TestWebsites/NET45/Test_Website_NET45/Images/Penguins.tif.REMOVED.git-id
new file mode 100644
index 0000000000..5f7b97e71a
--- /dev/null
+++ b/src/TestWebsites/NET45/Test_Website_NET45/Images/Penguins.tif.REMOVED.git-id
@@ -0,0 +1 @@
+c789aaec248568c24394b05c02db4233e0c5a4eb
\ No newline at end of file
diff --git a/src/TestWebsites/NET45/Test_Website_NET45/Images/Thumbs.db.REMOVED.git-id b/src/TestWebsites/NET45/Test_Website_NET45/Images/Thumbs.db.REMOVED.git-id
new file mode 100644
index 0000000000..367d139ca7
--- /dev/null
+++ b/src/TestWebsites/NET45/Test_Website_NET45/Images/Thumbs.db.REMOVED.git-id
@@ -0,0 +1 @@
+4815095bb7f32beb6d9e9564a77c83a64120b5c6
\ No newline at end of file
diff --git a/src/TestWebsites/NET45/Test_Website_NET45/Test_Website_NET45.csproj b/src/TestWebsites/NET45/Test_Website_NET45/Test_Website_NET45.csproj
index 76b9beb86c..499234b9f2 100644
--- a/src/TestWebsites/NET45/Test_Website_NET45/Test_Website_NET45.csproj
+++ b/src/TestWebsites/NET45/Test_Website_NET45/Test_Website_NET45.csproj
@@ -136,6 +136,7 @@
+
diff --git a/src/TestWebsites/NET45/Test_Website_NET45/Views/Home/Index.cshtml b/src/TestWebsites/NET45/Test_Website_NET45/Views/Home/Index.cshtml
index 8e99ec3743..2efa215112 100644
--- a/src/TestWebsites/NET45/Test_Website_NET45/Views/Home/Index.cshtml
+++ b/src/TestWebsites/NET45/Test_Website_NET45/Views/Home/Index.cshtml
@@ -308,6 +308,102 @@
+
+ Png8
+
+
+
+
Resized
+

+
+
+
Cropped
+

+
+
+
+
+ Filter
+
+
+
blackwhite
+

+
+
+
comic
+

+
+
+
+
+
lomograph
+

+
+
+
greyscale
+

+
+
+
+
+
polaroid
+

+
+
+
sepia
+

+
+
+
+
+
gotham
+

+
+
+
hisatch
+

+
+
+
+
+
losatch
+

+
+
+
+
+
+
+
Watermark
+

+
+
+
Format
+

+
+
+
+
+
+
+
Rotate
+

+
+
+
Quality
+

+
+
+
+
+
+
+
Alpha
+

+
+
+
+
Bmp
+
+ Tiff
+
+
+
+
Resized
+

+
+
+
Cropped
+

+
+
+
+
+ Filter
+
+
+
blackwhite
+

+
+
+
comic
+

+
+
+
+
+
lomograph
+

+
+
+
greyscale
+

+
+
+
+
+
polaroid
+

+
+
+
sepia
+

+
+
+
+
+
gotham
+

+
+
+
hisatch
+

+
+
+
+
+
losatch
+

+
+
+
+
+
+
+
Watermark
+

+
+
+
Format
+

+
+
+
+
+
+
+
Rotate
+

+
+
+
Quality
+

+
+
+
+
+
+
+
Alpha
+

+
+
+
+