diff --git a/src/ImageProcessor.Playground/Program.cs b/src/ImageProcessor.Playground/Program.cs index 692593f76..abafa234a 100644 --- a/src/ImageProcessor.Playground/Program.cs +++ b/src/ImageProcessor.Playground/Program.cs @@ -50,7 +50,7 @@ namespace ImageProcessor.PlayGround } Image mask = Image.FromFile(Path.Combine(resolvedPath, "mask2.png")); - IEnumerable files = GetFilesByExtensions(di, ".gif"); + IEnumerable files = GetFilesByExtensions(di, ".png"); //IEnumerable files = GetFilesByExtensions(di, ".gif", ".webp", ".bmp", ".jpg", ".png", ".tif"); foreach (FileInfo fileInfo in files) @@ -75,6 +75,7 @@ namespace ImageProcessor.PlayGround //}; // Load, resize, set the format and quality and save an image. imageFactory.Load(inStream) + //.Alpha(50) //.BackgroundColor(Color.White) //.Resize(new Size((int)(size.Width * 1.1), 0)) //.ContentAwareResize(layer) @@ -86,8 +87,8 @@ namespace ImageProcessor.PlayGround //.ReplaceColor(Color.FromArgb(255, 1, 107, 165), Color.FromArgb(255, 1, 165, 13), 80) //.Resize(size) // .Resize(new ResizeLayer(size, ResizeMode.Max)) - // .Resize(new ResizeLayer(size, ResizeMode.Stretch)) - .DetectEdges(new SobelEdgeFilter(), true) + // .Resize(new ResizeLayer(size, ResizeMode.Stretch)) + //.DetectEdges(new SobelEdgeFilter(), true) //.DetectEdges(new LaplacianOfGaussianEdgeFilter()) //.EntropyCrop() //.Filter(MatrixFilters.Invert) @@ -97,13 +98,16 @@ namespace ImageProcessor.PlayGround //.Filter(MatrixFilters.HiSatch) //.Pixelate(8) //.GaussianSharpen(10) + .Format(new PngFormat() { IsIndexed = true }) .Save(Path.GetFullPath(Path.Combine(Path.GetDirectoryName(path), @"..\..\images\output", fileInfo.Name))); stopwatch.Stop(); } } - Console.WriteLine("Processed: " + fileInfo.Name + " in " + stopwatch.ElapsedMilliseconds + "ms"); + Console.WriteLine(@"Completed {0} in {1:s\.fff} secs with peak memory usage of {2}.", fileInfo.Name, stopwatch.Elapsed, Process.GetCurrentProcess().PeakWorkingSet64.ToString("#,#")); + + //Console.WriteLine("Processed: " + fileInfo.Name + " in " + stopwatch.ElapsedMilliseconds + "ms"); } Console.ReadLine(); diff --git a/src/ImageProcessor.Playground/images/input/circle.png b/src/ImageProcessor.Playground/images/input/circle.png index 7e46427ac..74417f5b0 100644 --- a/src/ImageProcessor.Playground/images/input/circle.png +++ b/src/ImageProcessor.Playground/images/input/circle.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:73f4d8e08292df487a457be0765cb7994f2d91600b1824170a82e8f20f0a6d0b -size 5089 +oid sha256:33ceb5405f4f51976646815e3a90bcdbd7d5fcc8773db595001e4ca83a22fc24 +size 1738 diff --git a/src/ImageProcessor.UnitTests/Imaging/CropLayerUnitTests.cs b/src/ImageProcessor.UnitTests/Imaging/CropLayerUnitTests.cs index 01c03c745..f1db63d01 100644 --- a/src/ImageProcessor.UnitTests/Imaging/CropLayerUnitTests.cs +++ b/src/ImageProcessor.UnitTests/Imaging/CropLayerUnitTests.cs @@ -19,6 +19,21 @@ namespace ImageProcessor.UnitTests.Imaging /// /// Tests that the constructor saves the provided data /// + /// + /// The left position. + /// + /// + /// The top position. + /// + /// + /// The right position. + /// + /// + /// The bottom position. + /// + /// + /// The . + /// [Test] [TestCase(10.5F, 11.2F, 15.6F, 108.9F, CropMode.Percentage)] [TestCase(15.1F, 20.7F, 65.8F, 156.7F, CropMode.Pixels)] diff --git a/src/ImageProcessor/Common/Extensions/ImageExtensions.cs b/src/ImageProcessor/Common/Extensions/ImageExtensions.cs new file mode 100644 index 000000000..b0ffd301d --- /dev/null +++ b/src/ImageProcessor/Common/Extensions/ImageExtensions.cs @@ -0,0 +1,24 @@ + + +namespace ImageProcessor.Common.Extensions +{ + using System.Drawing; + using System.Drawing.Imaging; + + public static class ImageExtensions + { + public static Image ChangePixelFormat(this Image image, PixelFormat format) + { + Bitmap clone = new Bitmap(image.Width, image.Height, format); + clone.SetResolution(image.HorizontalResolution, image.VerticalResolution); + + using (Graphics graphics = Graphics.FromImage(clone)) + { + graphics.DrawImage(image, new Rectangle(0, 0, clone.Width, clone.Height)); + } + + image = new Bitmap(clone); + return image; + } + } +} diff --git a/src/ImageProcessor/ImageProcessor.csproj b/src/ImageProcessor/ImageProcessor.csproj index 5112ef796..a728fc9be 100644 --- a/src/ImageProcessor/ImageProcessor.csproj +++ b/src/ImageProcessor/ImageProcessor.csproj @@ -126,6 +126,7 @@ + @@ -160,6 +161,7 @@ + @@ -182,6 +184,19 @@ + + + + + + + + Code + + + + + @@ -230,7 +245,9 @@ - + + + \ No newline at end of file diff --git a/src/ImageProcessor/Imaging/Colors/Color32.cs b/src/ImageProcessor/Imaging/Colors/Color32.cs index df62aef8f..06d336932 100644 --- a/src/ImageProcessor/Imaging/Colors/Color32.cs +++ b/src/ImageProcessor/Imaging/Colors/Color32.cs @@ -52,7 +52,43 @@ namespace ImageProcessor.Imaging.Colors /// Permits the color32 to be treated as a 32 bit integer. /// [FieldOffset(0)] - public int ARGB; + public int Argb; + + /// + /// Initializes a new instance of the struct. + /// + /// + /// The alpha component. + /// + /// + /// The red component. + /// + /// + /// The green component. + /// + /// + /// The blue component. + /// + public Color32(byte alpha, byte red, byte green, byte blue) + : this() + { + this.A = alpha; + this.R = red; + this.G = green; + this.B = blue; + } + + /// + /// Initializes a new instance of the struct. + /// + /// + /// The combined color components. + /// + public Color32(int argb) + : this() + { + this.Argb = argb; + } /// /// Gets the color for this Color32 object diff --git a/src/ImageProcessor/Imaging/Formats/PngFormat.cs b/src/ImageProcessor/Imaging/Formats/PngFormat.cs index 23a3f300e..81ef4cbdb 100644 --- a/src/ImageProcessor/Imaging/Formats/PngFormat.cs +++ b/src/ImageProcessor/Imaging/Formats/PngFormat.cs @@ -14,8 +14,11 @@ namespace ImageProcessor.Imaging.Formats using System.Drawing.Imaging; using System.IO; + using ImageProcessor.Common.Extensions; using ImageProcessor.Imaging.Quantizers; + using nQuant; + /// /// Provides the necessary information to support png images. /// @@ -98,7 +101,26 @@ namespace ImageProcessor.Imaging.Formats { if (this.IsIndexed) { - image = new OctreeQuantizer(255, 8).Quantize(image); + // The Wu Quantizer expects a 32bbp image. + //if (Image.GetPixelFormatSize(image.PixelFormat) != 32) + //{ + Bitmap clone = new Bitmap(image.Width, image.Height, PixelFormat.Format32bppPArgb); + clone.SetResolution(image.HorizontalResolution, image.VerticalResolution); + + using (Graphics graphics = Graphics.FromImage(clone)) + { + graphics.Clear(Color.Transparent); + graphics.DrawImage(image, new Rectangle(0, 0, clone.Width, clone.Height)); + } + + image.Dispose(); + + image = new WuQuantizer().QuantizeImage(clone); + //} + //else + //{ + // image = new WuQuantizer().QuantizeImage((Bitmap)image); + //} } return base.Save(path, image); diff --git a/src/ImageProcessor/Imaging/Helpers/Adjustments.cs b/src/ImageProcessor/Imaging/Helpers/Adjustments.cs index 3550639df..672cb37f7 100644 --- a/src/ImageProcessor/Imaging/Helpers/Adjustments.cs +++ b/src/ImageProcessor/Imaging/Helpers/Adjustments.cs @@ -19,6 +19,53 @@ namespace ImageProcessor.Imaging.Helpers /// public static class Adjustments { + /// + /// Adjusts the alpha component of the given image. + /// + /// + /// The source to adjust. + /// + /// + /// The percentage value between 0 and 100 for adjusting the opacity. + /// + /// The rectangle to define the bounds of the area to adjust the opacity. + /// If null then the effect is applied to the entire image. + /// + /// The with the alpha component adjusted. + /// + /// + /// Thrown if the percentage value falls outside the acceptable range. + /// + public static Bitmap Alpha(Image source, int percentage, Rectangle? rectangle = null) + { + if (percentage > 100 || percentage < 0) + { + throw new ArgumentOutOfRangeException("percentage", "Percentage should be between 0 and 100."); + } + + Rectangle bounds = rectangle.HasValue ? rectangle.Value : new Rectangle(0, 0, source.Width, source.Height); + + ColorMatrix colorMatrix = new ColorMatrix(); + colorMatrix.Matrix00 = colorMatrix.Matrix11 = colorMatrix.Matrix22 = colorMatrix.Matrix44 = 1; + colorMatrix.Matrix33 = (float)percentage / 100; + + Bitmap alpha = new Bitmap(source.Width, source.Height); + alpha.SetResolution(source.HorizontalResolution, source.VerticalResolution); + + using (Graphics graphics = Graphics.FromImage(alpha)) + { + graphics.Clear(Color.Transparent); + using (ImageAttributes imageAttributes = new ImageAttributes()) + { + imageAttributes.SetColorMatrix(colorMatrix); + graphics.DrawImage(source, bounds, 0, 0, source.Width, source.Height, GraphicsUnit.Pixel, imageAttributes); + } + } + + source.Dispose(); + return alpha; + } + /// /// Adjusts the brightness component of the given image. /// @@ -101,7 +148,7 @@ namespace ImageProcessor.Imaging.Helpers contrastFactor++; float factorTransform = 0.5f * (1.0f - contrastFactor); - ColorMatrix colorMatrix = + ColorMatrix colorMatrix = new ColorMatrix( new[] { diff --git a/src/ImageProcessor/Imaging/ImageLayer.cs b/src/ImageProcessor/Imaging/ImageLayer.cs index 5daa1e8ac..d33e8fca2 100644 --- a/src/ImageProcessor/Imaging/ImageLayer.cs +++ b/src/ImageProcessor/Imaging/ImageLayer.cs @@ -1,4 +1,12 @@ - +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) James South. +// Licensed under the Apache License, Version 2.0. +// +// +// Encapsulates the properties required to add an image layer to an image. +// +// -------------------------------------------------------------------------------------------------------------------- namespace ImageProcessor.Imaging { @@ -46,5 +54,50 @@ namespace ImageProcessor.Imaging get { return this.position; } set { this.position = value; } } + + /// + /// Returns a value that indicates whether the specified object is an + /// object that is equivalent to + /// this object. + /// + /// + /// The object to test. + /// + /// + /// True if the given object is an object that is equivalent to + /// this object; otherwise, false. + /// + public override bool Equals(object obj) + { + ImageLayer imageLayer = obj as ImageLayer; + + if (imageLayer == null) + { + return false; + } + + return this.Image == imageLayer.Image + && this.Size == imageLayer.Size + && this.Opacity == imageLayer.Opacity + && this.Position == imageLayer.Position; + } + + /// + /// Returns the hash code for this instance. + /// + /// + /// A 32-bit signed integer that is the hash code for this instance. + /// + public override int GetHashCode() + { + unchecked + { + int hashCode = this.Image.GetHashCode(); + hashCode = (hashCode * 397) ^ this.Size.GetHashCode(); + hashCode = (hashCode * 397) ^ this.Opacity; + hashCode = (hashCode * 397) ^ this.Position.GetHashCode(); + return hashCode; + } + } } } diff --git a/src/ImageProcessor/Imaging/Quantizers/OctreeQuantizer.cs b/src/ImageProcessor/Imaging/Quantizers/OctreeQuantizer.cs index 34358a754..65aa828a5 100644 --- a/src/ImageProcessor/Imaging/Quantizers/OctreeQuantizer.cs +++ b/src/ImageProcessor/Imaging/Quantizers/OctreeQuantizer.cs @@ -203,13 +203,13 @@ namespace ImageProcessor.Imaging.Quantizers public void AddColor(Color32* pixel) { // Check if this request is for the same color as the last - if (this.previousColor == pixel->ARGB) + if (this.previousColor == pixel->Argb) { // If so, check if I have a previous node setup. This will only occur if the first color in the image // happens to be black, with an alpha component of zero. if (null == this.previousNode) { - this.previousColor = pixel->ARGB; + this.previousColor = pixel->Argb; this.root.AddColor(pixel, this.maxColorBits, 0, this); } else @@ -220,7 +220,7 @@ namespace ImageProcessor.Imaging.Quantizers } else { - this.previousColor = pixel->ARGB; + this.previousColor = pixel->Argb; this.root.AddColor(pixel, this.maxColorBits, 0, this); } } diff --git a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/Box.cs b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/Box.cs new file mode 100644 index 000000000..9ec158be5 --- /dev/null +++ b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/Box.cs @@ -0,0 +1,15 @@ +namespace nQuant +{ + public struct Box + { + public byte AlphaMinimum; + public byte AlphaMaximum; + public byte RedMinimum; + public byte RedMaximum; + public byte GreenMinimum; + public byte GreenMaximum; + public byte BlueMinimum; + public byte BlueMaximum; + public int Size; + } +} \ No newline at end of file diff --git a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/ColorData.cs b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/ColorData.cs new file mode 100644 index 000000000..13bf0230e --- /dev/null +++ b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/ColorData.cs @@ -0,0 +1,92 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) James South. +// Licensed under the Apache License, Version 2.0. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace nQuant +{ + /// + /// The color data. + /// + public class ColorData + { + /// + /// The pixels. + /// + private readonly Pixel[] pixels; + + /// + /// The pixels count. + /// + private readonly int pixelsCount; + + /// + /// The pixel filling counter. + /// + private int pixelFillingCounter; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The data granularity. + /// + /// + /// The bitmap width. + /// + /// + /// The bitmap height. + /// + public ColorData(int dataGranularity, int bitmapWidth, int bitmapHeight) + { + dataGranularity++; + + this.Moments = new ColorMoment[dataGranularity, dataGranularity, dataGranularity, dataGranularity]; + this.pixelsCount = bitmapWidth * bitmapHeight; + this.pixels = new Pixel[this.pixelsCount]; + } + + /// + /// Gets the moments. + /// + public ColorMoment[, , ,] Moments { get; private set; } + + /// + /// Gets the pixels. + /// + public Pixel[] Pixels + { + get + { + return this.pixels; + } + } + + /// + /// Gets the pixels count. + /// + public int PixelsCount + { + get + { + return this.pixelsCount; + } + } + + /// + /// The add pixel. + /// + /// + /// The pixel. + /// + /// + /// The quantized pixel. + /// + public void AddPixel(Pixel pixel, Pixel quantizedPixel) + { + this.pixels[this.pixelFillingCounter++] = pixel; + } + } +} \ No newline at end of file diff --git a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/ColorMoment.cs b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/ColorMoment.cs new file mode 100644 index 000000000..7087a48a3 --- /dev/null +++ b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/ColorMoment.cs @@ -0,0 +1,73 @@ + +namespace nQuant +{ + public struct ColorMoment + { + public long Alpha; + public long Red; + public long Green; + public long Blue; + public int Weight; + public float Moment; + + public static ColorMoment operator +(ColorMoment c1, ColorMoment c2) + { + c1.Alpha += c2.Alpha; + c1.Red += c2.Red; + c1.Green += c2.Green; + c1.Blue += c2.Blue; + c1.Weight += c2.Weight; + c1.Moment += c2.Moment; + return c1; + } + + public static ColorMoment operator -(ColorMoment c1, ColorMoment c2) + { + c1.Alpha -= c2.Alpha; + c1.Red -= c2.Red; + c1.Green -= c2.Green; + c1.Blue -= c2.Blue; + c1.Weight -= c2.Weight; + c1.Moment -= c2.Moment; + return c1; + } + + public static ColorMoment operator -(ColorMoment c1) + { + c1.Alpha = -c1.Alpha; + c1.Red = -c1.Red; + c1.Green = -c1.Green; + c1.Blue = -c1.Blue; + c1.Weight = -c1.Weight; + c1.Moment = -c1.Moment; + return c1; + } + + public static ColorMoment operator +(ColorMoment m, Pixel p) + { + m.Alpha += p.Alpha; + m.Red += p.Red; + m.Green += p.Green; + m.Blue += p.Blue; + m.Weight++; + m.Moment += p.Distance(); + return m; + } + + public long Distance() + { + return (Alpha * Alpha) + (Red * Red) + (Green * Green) + (Blue * Blue); + } + + public long WeightedDistance() + { + return Distance() / Weight; + } + + public float Variance() + { + var result = Moment - ((float)Distance() / Weight); + return float.IsNaN(result) ? 0.0f : result; + } + } +} \ No newline at end of file diff --git a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/CubeCut.cs b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/CubeCut.cs new file mode 100644 index 000000000..6500b2493 --- /dev/null +++ b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/CubeCut.cs @@ -0,0 +1,14 @@ +namespace nQuant +{ + internal struct CubeCut + { + public readonly byte? Position; + public readonly float Value; + + public CubeCut(byte? cutPoint, float result) + { + Position = cutPoint; + Value = result; + } + } +} \ No newline at end of file diff --git a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/IWuQuantizer.cs b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/IWuQuantizer.cs new file mode 100644 index 000000000..28f708cb8 --- /dev/null +++ b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/IWuQuantizer.cs @@ -0,0 +1,9 @@ +using System.Drawing; + +namespace nQuant +{ + public interface IWuQuantizer + { + Image QuantizeImage(Bitmap image, int alphaThreshold, int alphaFader); + } +} \ No newline at end of file diff --git a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/Lookup.cs b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/Lookup.cs new file mode 100644 index 000000000..88a702e9c --- /dev/null +++ b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/Lookup.cs @@ -0,0 +1,10 @@ +namespace nQuant +{ + public class Lookup + { + public int Alpha; + public int Red; + public int Green; + public int Blue; + } +} \ No newline at end of file diff --git a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/Pixel.cs b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/Pixel.cs new file mode 100644 index 000000000..0101b4af3 --- /dev/null +++ b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/Pixel.cs @@ -0,0 +1,50 @@ +namespace nQuant +{ + using System.Runtime.InteropServices; + + [StructLayout(LayoutKind.Explicit)] + public struct Pixel + { + public Pixel(byte alpha, byte red, byte green, byte blue) + : this() + { + Alpha = alpha; + Red = red; + Green = green; + Blue = blue; + } + + /// + /// Initializes a new instance of the struct. + /// + /// + /// The combined color components. + /// + public Pixel(int argb) + : this() + { + this.Argb = argb; + } + + public long Distance() + { + return (Alpha * Alpha) + (Red * Red) + (Green * Green) + (Blue * Blue); + } + + [FieldOffsetAttribute(3)] + public byte Alpha; + [FieldOffsetAttribute(2)] + public byte Red; + [FieldOffsetAttribute(1)] + public byte Green; + [FieldOffsetAttribute(0)] + public byte Blue; + [FieldOffset(0)] + public int Argb; + + public override string ToString() + { + return string.Format("Alpha:{0} Red:{1} Green:{2} Blue:{3}", Alpha, Red, Green, Blue); + } + } +} \ No newline at end of file diff --git a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/QuantizationException.cs b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/QuantizationException.cs new file mode 100644 index 000000000..da644a5f5 --- /dev/null +++ b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/QuantizationException.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace nQuant +{ + public class QuantizationException : ApplicationException + { + public QuantizationException(string message) : base(message) + { + + } + } +} diff --git a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/QuantizedPalette.cs b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/QuantizedPalette.cs new file mode 100644 index 000000000..13fd8a651 --- /dev/null +++ b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/QuantizedPalette.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using System.Drawing; + +namespace nQuant +{ + public class QuantizedPalette + { + public QuantizedPalette(int size) + { + Colors = new List(); + PixelIndex = new byte[size]; + } + public IList Colors { get; private set; } + + public byte[] PixelIndex { get; private set; } + } +} \ No newline at end of file diff --git a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/WuQuantizer.cs b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/WuQuantizer.cs new file mode 100644 index 000000000..2354984fd --- /dev/null +++ b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/WuQuantizer.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using System.Drawing; + +namespace nQuant +{ + public class WuQuantizer : WuQuantizerBase, IWuQuantizer + { + protected override QuantizedPalette GetQuantizedPalette(int colorCount, ColorData data, Box[] cubes, int alphaThreshold) + { + int pixelsCount = data.Pixels.Length; + Lookup[] lookups = BuildLookups(cubes, data); + + var alphas = new int[colorCount + 1]; + var reds = new int[colorCount + 1]; + var greens = new int[colorCount + 1]; + var blues = new int[colorCount + 1]; + var sums = new int[colorCount + 1]; + var palette = new QuantizedPalette(pixelsCount); + + IList pixels = data.Pixels; + + Dictionary cachedMaches = new Dictionary(); + + for (int pixelIndex = 0; pixelIndex < pixelsCount; pixelIndex++) + { + Pixel pixel = pixels[pixelIndex]; + + if (pixel.Alpha > alphaThreshold) + { + byte bestMatch; + int argb = pixel.Argb; + + if (!cachedMaches.TryGetValue(argb, out bestMatch)) + { + int bestDistance = int.MaxValue; + + for (int lookupIndex = 0; lookupIndex < lookups.Length; lookupIndex++) + { + Lookup lookup = lookups[lookupIndex]; + var deltaAlpha = pixel.Alpha - lookup.Alpha; + var deltaRed = pixel.Red - lookup.Red; + var deltaGreen = pixel.Green - lookup.Green; + var deltaBlue = pixel.Blue - lookup.Blue; + + int distance = deltaAlpha * deltaAlpha + deltaRed * deltaRed + deltaGreen * deltaGreen + deltaBlue * deltaBlue; + + if (distance >= bestDistance) + continue; + + bestDistance = distance; + bestMatch = (byte)lookupIndex; + } + + cachedMaches[argb] = bestMatch; + } + + alphas[bestMatch] += pixel.Alpha; + reds[bestMatch] += pixel.Red; + greens[bestMatch] += pixel.Green; + blues[bestMatch] += pixel.Blue; + sums[bestMatch]++; + + palette.PixelIndex[pixelIndex] = bestMatch; + } + else + { + palette.PixelIndex[pixelIndex] = AlphaColor; + } + } + + for (var paletteIndex = 0; paletteIndex < colorCount; paletteIndex++) + { + if (sums[paletteIndex] > 0) + { + alphas[paletteIndex] /= sums[paletteIndex]; + reds[paletteIndex] /= sums[paletteIndex]; + greens[paletteIndex] /= sums[paletteIndex]; + blues[paletteIndex] /= sums[paletteIndex]; + } + + var color = Color.FromArgb(alphas[paletteIndex], reds[paletteIndex], greens[paletteIndex], blues[paletteIndex]); + palette.Colors.Add(color); + } + + palette.Colors.Add(Color.FromArgb(0, 0, 0, 0)); + + return palette; + } + } +} diff --git a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/WuQuantizerBase.cs b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/WuQuantizerBase.cs new file mode 100644 index 000000000..3baff786b --- /dev/null +++ b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/WuQuantizerBase.cs @@ -0,0 +1,506 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Imaging; +using System.Linq; +using System.Runtime.InteropServices; + +namespace nQuant +{ + public abstract class WuQuantizerBase + { + private const int MaxColor = 256; + protected const byte AlphaColor = 255; + protected const int Alpha = 3; + protected const int Red = 2; + protected const int Green = 1; + protected const int Blue = 0; + private const int SideSize = 33; + private const int MaxSideIndex = 32; + + public Image QuantizeImage(Bitmap image) + { + return QuantizeImage(image, 10, 70); + } + + public Image QuantizeImage(Bitmap image, int alphaThreshold, int alphaFader) + { + var colorCount = MaxColor; + var data = BuildHistogram(image, alphaThreshold, alphaFader); + data = CalculateMoments(data); + var cubes = SplitData(ref colorCount, data); + var palette = GetQuantizedPalette(colorCount, data, cubes, alphaThreshold); + return ProcessImagePixels(image, palette); + } + + private static Bitmap ProcessImagePixels(Image sourceImage, QuantizedPalette palette) + { + var result = new Bitmap(sourceImage.Width, sourceImage.Height, PixelFormat.Format8bppIndexed); + var newPalette = result.Palette; + for (var index = 0; index < palette.Colors.Count; index++) + newPalette.Entries[index] = palette.Colors[index]; + result.Palette = newPalette; + + BitmapData targetData = null; + try + { + var resultHeight = result.Height; + var resultWidth = result.Width; + targetData = result.LockBits(Rectangle.FromLTRB(0, 0, resultWidth, resultHeight), ImageLockMode.WriteOnly, result.PixelFormat); + const byte targetBitDepth = 8; + var targetByteLength = targetData.Stride < 0 ? -targetData.Stride : targetData.Stride; + var targetByteCount = Math.Max(1, targetBitDepth >> 3); + var targetBuffer = new byte[targetByteLength]; + var targetValue = new byte[targetByteCount]; + var pixelIndex = 0; + + + + for (var y = 0; y < resultHeight; y++) + { + var targetIndex = 0; + for (var x = 0; x < resultWidth; x++) + { + var targetIndexOffset = targetIndex >> 3; + targetValue[0] = + (byte) + (palette.PixelIndex[pixelIndex] == AlphaColor + ? palette.Colors.Count - 1 + : palette.PixelIndex[pixelIndex]); + pixelIndex++; + + for (var valueIndex = 0; valueIndex < targetByteCount; valueIndex++) + { + targetBuffer[valueIndex + targetIndexOffset] = targetValue[valueIndex]; + } + + targetIndex += targetBitDepth; + } + + Marshal.Copy(targetBuffer, 0, targetData.Scan0 + (targetByteLength * y), targetByteLength); + } + } + finally + { + if (targetData != null) + { + result.UnlockBits(targetData); + } + } + + return result; + } + + private static ColorData BuildHistogram(Bitmap sourceImage, int alphaThreshold, int alphaFader) + { + int bitmapWidth = sourceImage.Width; + int bitmapHeight = sourceImage.Height; + + BitmapData data = sourceImage.LockBits( + Rectangle.FromLTRB(0, 0, bitmapWidth, bitmapHeight), + ImageLockMode.ReadOnly, + sourceImage.PixelFormat); + ColorData colorData = new ColorData(MaxSideIndex, bitmapWidth, bitmapHeight); + + try + { + var bitDepth = Image.GetPixelFormatSize(sourceImage.PixelFormat); + if (bitDepth != 32) + throw new QuantizationException(string.Format("Thie image you are attempting to quantize does not contain a 32 bit ARGB palette. This image has a bit depth of {0} with {1} colors.", bitDepth, sourceImage.Palette.Entries.Length)); + var byteLength = data.Stride < 0 ? -data.Stride : data.Stride; + var byteCount = Math.Max(1, bitDepth >> 3); + var buffer = new Byte[byteLength]; + + var value = new Byte[byteCount]; + + for (int y = 0; y < bitmapHeight; y++) + { + Marshal.Copy(data.Scan0 + (byteLength * y), buffer, 0, buffer.Length); + + var index = 0; + for (int x = 0; x < bitmapWidth; x++) + { + var indexOffset = index >> 3; + + for (var valueIndex = 0; valueIndex < byteCount; valueIndex++) + { + value[valueIndex] = buffer[valueIndex + indexOffset]; + } + + Pixel pixelValue = new Pixel(value[Alpha], value[Red], value[Green], value[Blue]); + + var indexAlpha = (byte)((value[Alpha] >> 3) + 1); + var indexRed = (byte)((value[Red] >> 3) + 1); + var indexGreen = (byte)((value[Green] >> 3) + 1); + var indexBlue = (byte)((value[Blue] >> 3) + 1); + + if (value[Alpha] > alphaThreshold) + { + if (value[Alpha] < 255) + { + var alpha = value[Alpha] + (value[Alpha] % alphaFader); + value[Alpha] = (byte)(alpha > 255 ? 255 : alpha); + indexAlpha = (byte)((value[Alpha] >> 3) + 1); + } + + colorData.Moments[indexAlpha, indexRed, indexGreen, indexBlue] += pixelValue; + } + + colorData.AddPixel( + pixelValue, + new Pixel(indexAlpha, indexRed, indexGreen, indexBlue)); + index += bitDepth; + } + } + } + finally + { + sourceImage.UnlockBits(data); + } + return colorData; + } + + private static ColorData CalculateMoments(ColorData data) + { + var xarea = new ColorMoment[SideSize, SideSize]; + var xPreviousArea = new ColorMoment[SideSize, SideSize]; + var area = new ColorMoment[SideSize]; + for (var alphaIndex = 1; alphaIndex <= MaxSideIndex; ++alphaIndex) + { + for (var redIndex = 1; redIndex <= MaxSideIndex; ++redIndex) + { + Array.Clear(area, 0, area.Length); + for (var greenIndex = 1; greenIndex <= MaxSideIndex; ++greenIndex) + { + ColorMoment line = new ColorMoment(); + for (var blueIndex = 1; blueIndex <= MaxSideIndex; ++blueIndex) + { + line += data.Moments[alphaIndex, redIndex, greenIndex, blueIndex]; + area[blueIndex] += line; + + xarea[greenIndex, blueIndex] = xPreviousArea[greenIndex, blueIndex] + area[blueIndex]; + data.Moments[alphaIndex, redIndex, greenIndex, blueIndex] = data.Moments[alphaIndex - 1, redIndex, greenIndex, blueIndex] + xarea[greenIndex, blueIndex]; + } + } + + var temp = xarea; + xarea = xPreviousArea; + xPreviousArea = temp; + } + } + return data; + } + + private static ColorMoment Top(Box cube, int direction, int position, ColorMoment[, , ,] moment) + { + switch (direction) + { + case Alpha: + return (moment[position, cube.RedMaximum, cube.GreenMaximum, cube.BlueMaximum] - + moment[position, cube.RedMaximum, cube.GreenMinimum, cube.BlueMaximum] - + moment[position, cube.RedMinimum, cube.GreenMaximum, cube.BlueMaximum] + + moment[position, cube.RedMinimum, cube.GreenMinimum, cube.BlueMaximum]) - + (moment[position, cube.RedMaximum, cube.GreenMaximum, cube.BlueMinimum] - + moment[position, cube.RedMaximum, cube.GreenMinimum, cube.BlueMinimum] - + moment[position, cube.RedMinimum, cube.GreenMaximum, cube.BlueMinimum] + + moment[position, cube.RedMinimum, cube.GreenMinimum, cube.BlueMinimum]); + + case Red: + return (moment[cube.AlphaMaximum, position, cube.GreenMaximum, cube.BlueMaximum] - + moment[cube.AlphaMaximum, position, cube.GreenMinimum, cube.BlueMaximum] - + moment[cube.AlphaMinimum, position, cube.GreenMaximum, cube.BlueMaximum] + + moment[cube.AlphaMinimum, position, cube.GreenMinimum, cube.BlueMaximum]) - + (moment[cube.AlphaMaximum, position, cube.GreenMaximum, cube.BlueMinimum] - + moment[cube.AlphaMaximum, position, cube.GreenMinimum, cube.BlueMinimum] - + moment[cube.AlphaMinimum, position, cube.GreenMaximum, cube.BlueMinimum] + + moment[cube.AlphaMinimum, position, cube.GreenMinimum, cube.BlueMinimum]); + + case Green: + return (moment[cube.AlphaMaximum, cube.RedMaximum, position, cube.BlueMaximum] - + moment[cube.AlphaMaximum, cube.RedMinimum, position, cube.BlueMaximum] - + moment[cube.AlphaMinimum, cube.RedMaximum, position, cube.BlueMaximum] + + moment[cube.AlphaMinimum, cube.RedMinimum, position, cube.BlueMaximum]) - + (moment[cube.AlphaMaximum, cube.RedMaximum, position, cube.BlueMinimum] - + moment[cube.AlphaMaximum, cube.RedMinimum, position, cube.BlueMinimum] - + moment[cube.AlphaMinimum, cube.RedMaximum, position, cube.BlueMinimum] + + moment[cube.AlphaMinimum, cube.RedMinimum, position, cube.BlueMinimum]); + + case Blue: + return (moment[cube.AlphaMaximum, cube.RedMaximum, cube.GreenMaximum, position] - + moment[cube.AlphaMaximum, cube.RedMaximum, cube.GreenMinimum, position] - + moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMaximum, position] + + moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMinimum, position]) - + (moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMaximum, position] - + moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMinimum, position] - + moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMaximum, position] + + moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMinimum, position]); + + default: + return new ColorMoment(); + } + } + + private static ColorMoment Bottom(Box cube, int direction, ColorMoment[, , ,] moment) + { + switch (direction) + { + case Alpha: + return (-moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMaximum, cube.BlueMaximum] + + moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMaximum] + + moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMaximum] - + moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMaximum]) - + (-moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMaximum, cube.BlueMinimum] + + moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMinimum] + + moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMinimum] - + moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMinimum]); + + case Red: + return (-moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMaximum] + + moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMaximum] + + moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMaximum] - + moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMaximum]) - + (-moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMinimum] + + moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMinimum] + + moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMinimum] - + moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMinimum]); + + case Green: + return (-moment[cube.AlphaMaximum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMaximum] + + moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMaximum] + + moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMaximum] - + moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMaximum]) - + (-moment[cube.AlphaMaximum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMinimum] + + moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMinimum] + + moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMinimum] - + moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMinimum]); + + case Blue: + return (-moment[cube.AlphaMaximum, cube.RedMaximum, cube.GreenMaximum, cube.BlueMinimum] + + moment[cube.AlphaMaximum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMinimum] + + moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMinimum] - + moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMinimum]) - + (-moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMaximum, cube.BlueMinimum] + + moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMinimum] + + moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMinimum] - + moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMinimum]); + + default: + return new ColorMoment(); + } + } + + private static CubeCut Maximize(ColorData data, Box cube, int direction, byte first, byte last, ColorMoment whole) + { + var bottom = Bottom(cube, direction, data.Moments); + float result = 0.0f; + byte? cutPoint = null; + + for (byte position = first; position < last; ++position) + { + var half = bottom + Top(cube, direction, position, data.Moments); + if (half.Weight == 0) + { + continue; + } + + long temp = half.WeightedDistance(); + half = whole - half; + if (half.Weight != 0) + { + temp += half.WeightedDistance(); + + if (temp > result) + { + result = temp; + cutPoint = position; + } + } + } + + return new CubeCut(cutPoint, result); + } + + private bool Cut(ColorData data, ref Box first, ref Box second) + { + int direction; + var whole = Volume(first, data.Moments); + var maxAlpha = Maximize(data, first, Alpha, (byte)(first.AlphaMinimum + 1), first.AlphaMaximum, whole); + var maxRed = Maximize(data, first, Red, (byte)(first.RedMinimum + 1), first.RedMaximum, whole); + var maxGreen = Maximize(data, first, Green, (byte)(first.GreenMinimum + 1), first.GreenMaximum, whole); + var maxBlue = Maximize(data, first, Blue, (byte)(first.BlueMinimum + 1), first.BlueMaximum, whole); + + if ((maxAlpha.Value >= maxRed.Value) && (maxAlpha.Value >= maxGreen.Value) && (maxAlpha.Value >= maxBlue.Value)) + { + direction = Alpha; + if (maxAlpha.Position == null) return false; + } + else if ((maxRed.Value >= maxAlpha.Value) && (maxRed.Value >= maxGreen.Value) && (maxRed.Value >= maxBlue.Value)) + direction = Red; + else + { + if ((maxGreen.Value >= maxAlpha.Value) && (maxGreen.Value >= maxRed.Value) && (maxGreen.Value >= maxBlue.Value)) + direction = Green; + else + direction = Blue; + } + + second.AlphaMaximum = first.AlphaMaximum; + second.RedMaximum = first.RedMaximum; + second.GreenMaximum = first.GreenMaximum; + second.BlueMaximum = first.BlueMaximum; + + switch (direction) + { + case Alpha: + second.AlphaMinimum = first.AlphaMaximum = (byte)maxAlpha.Position; + second.RedMinimum = first.RedMinimum; + second.GreenMinimum = first.GreenMinimum; + second.BlueMinimum = first.BlueMinimum; + break; + + case Red: + second.RedMinimum = first.RedMaximum = (byte)maxRed.Position; + second.AlphaMinimum = first.AlphaMinimum; + second.GreenMinimum = first.GreenMinimum; + second.BlueMinimum = first.BlueMinimum; + break; + + case Green: + second.GreenMinimum = first.GreenMaximum = (byte)maxGreen.Position; + second.AlphaMinimum = first.AlphaMinimum; + second.RedMinimum = first.RedMinimum; + second.BlueMinimum = first.BlueMinimum; + break; + + case Blue: + second.BlueMinimum = first.BlueMaximum = (byte)maxBlue.Position; + second.AlphaMinimum = first.AlphaMinimum; + second.RedMinimum = first.RedMinimum; + second.GreenMinimum = first.GreenMinimum; + break; + } + + first.Size = (first.AlphaMaximum - first.AlphaMinimum) * (first.RedMaximum - first.RedMinimum) * (first.GreenMaximum - first.GreenMinimum) * (first.BlueMaximum - first.BlueMinimum); + second.Size = (second.AlphaMaximum - second.AlphaMinimum) * (second.RedMaximum - second.RedMinimum) * (second.GreenMaximum - second.GreenMinimum) * (second.BlueMaximum - second.BlueMinimum); + + return true; + } + + private static float CalculateVariance(ColorData data, Box cube) + { + ColorMoment volume = Volume(cube, data.Moments); + return volume.Variance(); + } + + private static ColorMoment Volume(Box cube, ColorMoment[, , ,] moment) + { + return (moment[cube.AlphaMaximum, cube.RedMaximum, cube.GreenMaximum, cube.BlueMaximum] - + moment[cube.AlphaMaximum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMaximum] - + moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMaximum] + + moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMaximum] - + moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMaximum, cube.BlueMaximum] + + moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMaximum] + + moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMaximum] - + moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMaximum]) - + + (moment[cube.AlphaMaximum, cube.RedMaximum, cube.GreenMaximum, cube.BlueMinimum] - + moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMaximum, cube.BlueMinimum] - + moment[cube.AlphaMaximum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMinimum] + + moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMinimum] - + moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMinimum] + + moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMinimum] + + moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMinimum] - + moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMinimum]); + } + + private static float VolumeFloat(Box cube, float[, , ,] moment) + { + return (moment[cube.AlphaMaximum, cube.RedMaximum, cube.GreenMaximum, cube.BlueMaximum] - + moment[cube.AlphaMaximum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMaximum] - + moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMaximum] + + moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMaximum] - + moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMaximum, cube.BlueMaximum] + + moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMaximum] + + moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMaximum] - + moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMaximum]) - + + (moment[cube.AlphaMaximum, cube.RedMaximum, cube.GreenMaximum, cube.BlueMinimum] - + moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMaximum, cube.BlueMinimum] - + moment[cube.AlphaMaximum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMinimum] + + moment[cube.AlphaMinimum, cube.RedMaximum, cube.GreenMinimum, cube.BlueMinimum] - + moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMinimum] + + moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMaximum, cube.BlueMinimum] + + moment[cube.AlphaMaximum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMinimum] - + moment[cube.AlphaMinimum, cube.RedMinimum, cube.GreenMinimum, cube.BlueMinimum]); + } + + private Box[] SplitData(ref int colorCount, ColorData data) + { + --colorCount; + var next = 0; + var volumeVariance = new float[MaxColor]; + var cubes = new Box[MaxColor]; + cubes[0].AlphaMaximum = MaxSideIndex; + cubes[0].RedMaximum = MaxSideIndex; + cubes[0].GreenMaximum = MaxSideIndex; + cubes[0].BlueMaximum = MaxSideIndex; + for (var cubeIndex = 1; cubeIndex < colorCount; ++cubeIndex) + { + if (Cut(data, ref cubes[next], ref cubes[cubeIndex])) + { + volumeVariance[next] = cubes[next].Size > 1 ? CalculateVariance(data, cubes[next]) : 0.0f; + volumeVariance[cubeIndex] = cubes[cubeIndex].Size > 1 ? CalculateVariance(data, cubes[cubeIndex]) : 0.0f; + } + else + { + volumeVariance[next] = 0.0f; + cubeIndex--; + } + + next = 0; + var temp = volumeVariance[0]; + + for (var index = 1; index <= cubeIndex; ++index) + { + if (volumeVariance[index] <= temp) continue; + temp = volumeVariance[index]; + next = index; + } + + if (temp > 0.0) continue; + colorCount = cubeIndex + 1; + break; + } + return cubes.Take(colorCount).ToArray(); + } + + protected Lookup[] BuildLookups(Box[] cubes, ColorData data) + { + List lookups = new List(cubes.Length); + + foreach (var cube in cubes) + { + var volume = Volume(cube, data.Moments); + + if (volume.Weight <= 0) + { + continue; + } + + var lookup = new Lookup + { + Alpha = (int)(volume.Alpha / volume.Weight), + Red = (int)(volume.Red / volume.Weight), + Green = (int)(volume.Green / volume.Weight), + Blue = (int)(volume.Blue / volume.Weight) + }; + + lookups.Add(lookup); + } + + return lookups.ToArray(); + } + + protected abstract QuantizedPalette GetQuantizedPalette(int colorCount, ColorData data, Box[] cubes, int alphaThreshold); + } +} \ No newline at end of file diff --git a/src/ImageProcessor/Processors/Alpha.cs b/src/ImageProcessor/Processors/Alpha.cs index 5adff071b..9f05b29f7 100644 --- a/src/ImageProcessor/Processors/Alpha.cs +++ b/src/ImageProcessor/Processors/Alpha.cs @@ -13,9 +13,9 @@ namespace ImageProcessor.Processors using System; using System.Collections.Generic; using System.Drawing; - using System.Drawing.Imaging; using ImageProcessor.Common.Exceptions; + using ImageProcessor.Imaging.Helpers; /// /// Encapsulates methods to change the alpha component of the image to effect its transparency. @@ -65,35 +65,13 @@ namespace ImageProcessor.Processors try { - int alphaPercent = this.DynamicParameter; + int percentage = this.DynamicParameter; - newImage = new Bitmap(image.Width, image.Height); - newImage.SetResolution(image.HorizontalResolution, image.VerticalResolution); + newImage = new Bitmap(image); + newImage = Adjustments.Alpha(newImage, percentage); - ColorMatrix colorMatrix = new ColorMatrix(); - colorMatrix.Matrix00 = colorMatrix.Matrix11 = colorMatrix.Matrix22 = colorMatrix.Matrix44 = 1; - colorMatrix.Matrix33 = (float)alphaPercent / 100; - - using (Graphics graphics = Graphics.FromImage(newImage)) - { - using (ImageAttributes imageAttributes = new ImageAttributes()) - { - imageAttributes.SetColorMatrix(colorMatrix); - - graphics.DrawImage( - image, - new Rectangle(0, 0, image.Width, image.Height), - 0, - 0, - image.Width, - image.Height, - GraphicsUnit.Pixel, - imageAttributes); - - image.Dispose(); - image = newImage; - } - } + image.Dispose(); + image = newImage; } catch (Exception ex) { diff --git a/src/ImageProcessor/Processors/Overlay.cs b/src/ImageProcessor/Processors/Overlay.cs index ff4579bd7..da499710e 100644 --- a/src/ImageProcessor/Processors/Overlay.cs +++ b/src/ImageProcessor/Processors/Overlay.cs @@ -14,6 +14,9 @@ namespace ImageProcessor.Processors using System.Collections.Generic; using System.Drawing; + using ImageProcessor.Common.Exceptions; + using ImageProcessor.Imaging; + /// /// Adds an image overlay to the current image. /// @@ -57,7 +60,34 @@ namespace ImageProcessor.Processors /// public Image ProcessImage(ImageFactory factory) { - throw new NotImplementedException(); + Bitmap newImage = null; + Image image = factory.Image; + + try + { + newImage = new Bitmap(image); + ImageLayer imageLayer = this.DynamicParameter; + Image overlay = imageLayer.Image; + Size size = imageLayer.Size; + int opacity = Math.Min((int)Math.Ceiling((imageLayer.Opacity / 100f) * 255), 255); + + + + image.Dispose(); + image = newImage; + + } + catch (Exception ex) + { + if (newImage != null) + { + newImage.Dispose(); + } + + throw new ImageProcessingException("Error processing image with " + this.GetType().Name, ex); + } + + return image; } } }