diff --git a/src/ImageProcessor/ImageFactory.cs b/src/ImageProcessor/ImageFactory.cs index c671d7255..2497b1139 100644 --- a/src/ImageProcessor/ImageFactory.cs +++ b/src/ImageProcessor/ImageFactory.cs @@ -178,6 +178,13 @@ namespace ImageProcessor // Set the other properties. format.Quality = DefaultQuality; format.IsIndexed = FormatUtilities.IsIndexed(this.Image); + + IQuantizableImageFormat imageFormat = format as IQuantizableImageFormat; + if (imageFormat != null) + { + imageFormat.ColorCount = FormatUtilities.GetColorCount(this.Image); + } + this.backupFormat = format; this.CurrentImageFormat = format; @@ -240,6 +247,13 @@ namespace ImageProcessor // Set the other properties. format.Quality = DefaultQuality; format.IsIndexed = FormatUtilities.IsIndexed(this.Image); + + IQuantizableImageFormat imageFormat = format as IQuantizableImageFormat; + if (imageFormat != null) + { + imageFormat.ColorCount = FormatUtilities.GetColorCount(this.Image); + } + this.backupFormat = format; this.CurrentImageFormat = format; diff --git a/src/ImageProcessor/Imaging/FastBitmap.cs b/src/ImageProcessor/Imaging/FastBitmap.cs index 2bed9cf1e..13cd1bc17 100644 --- a/src/ImageProcessor/Imaging/FastBitmap.cs +++ b/src/ImageProcessor/Imaging/FastBitmap.cs @@ -38,42 +38,79 @@ namespace ImageProcessor.Imaging private readonly int height; /// - /// The number of bytes in a row. + /// The size of the a single pixel. /// - private int bytesInARow; + private readonly int pixelSize; /// - /// The size of the color32 structure. + /// The color channel - blue, green, red, alpha. /// - private int color32Size; + private readonly int channel; /// - /// The color channel - blue, green, red, alpha. + /// Whether to compute tilted integral rectangles. /// - private int channel; + private readonly bool computeTilted; /// - /// Whether to compute tilted integral rectangles. + /// The normal integral image. + /// + private readonly long[,] normalSumImage; + + /// + /// The squared integral image. + /// + private readonly long[,] squaredSumImage; + + /// + /// The tilted sum image. + /// + private readonly long[,] tiltedSumImage; + + /// + /// The normal width. + /// + private readonly int normalWidth; + + /// + /// The tilted width. + /// + private readonly int tiltedWidth; + + /// + /// The number of bytes in a row. + /// + private int bytesInARow; + + /// + /// The normal integral sum. /// - private bool computeTilted; + private long* normalSum; - private long[,] nSumImage; // normal integral image - private long[,] sSumImage; // squared integral image - private long[,] tSumImage; // tilted integral image + /// + /// The squared integral sum. + /// + private long* squaredSum; - private long* nSum; // normal integral image - private long* sSum; // squared integral image - private long* tSum; // tilted integral image + /// + /// The tilted integral sum. + /// + private long* tiltedSum; - private GCHandle nSumHandle; - private GCHandle sSumHandle; - private GCHandle tSumHandle; + /// + /// The normal sum handle. + /// + private GCHandle normalSumHandle; - private int nWidth; - private int nHeight; + /// + /// The squared sum handle. + /// + private GCHandle squaredSumHandle; - private int tWidth; - private int tHeight; + /// + /// The tilted sum handle. + /// + private GCHandle tiltedSumHandle; /// /// The bitmap data. @@ -103,7 +140,7 @@ namespace ImageProcessor.Imaging /// /// The input bitmap. public FastBitmap(Image bitmap) - : this(bitmap, 2, false) + : this(bitmap, false) { } @@ -111,39 +148,47 @@ namespace ImageProcessor.Imaging /// Initializes a new instance of the class. /// /// The input bitmap. - /// - /// The integral color channel. Blue, Green, Red, or Alpha. - /// /// /// Whether to compute tilted integral rectangles. /// - public FastBitmap(Image bitmap, int integralColorChannel, bool computeTilted) + public FastBitmap(Image bitmap, bool computeTilted) { + // Check image format + if (!(bitmap.PixelFormat == PixelFormat.Format8bppIndexed || + bitmap.PixelFormat == PixelFormat.Format24bppRgb || + bitmap.PixelFormat == PixelFormat.Format32bppArgb || + bitmap.PixelFormat == PixelFormat.Format32bppPArgb)) + { + throw new ArgumentException("Only 8bpp, 24bpp and 32bpp images are supported."); + } + + this.pixelSize = Image.GetPixelFormatSize(bitmap.PixelFormat) / 8; + this.bitmap = (Bitmap)bitmap; this.width = this.bitmap.Width; this.height = this.bitmap.Height; - this.channel = integralColorChannel; + this.channel = this.bitmap.PixelFormat == PixelFormat.Format8bppIndexed ? 0 : 2; this.computeTilted = computeTilted; - this.nWidth = this.width + 1; - this.nHeight = this.height + 1; + this.normalWidth = this.width + 1; + int normalHeight = this.height + 1; - this.tWidth = this.width + 2; - this.tHeight = this.height + 2; + this.tiltedWidth = this.width + 2; + int tiltedHeight = this.height + 2; - this.nSumImage = new long[this.nHeight, this.nWidth]; - this.nSumHandle = GCHandle.Alloc(this.nSumImage, GCHandleType.Pinned); - this.nSum = (long*)this.nSumHandle.AddrOfPinnedObject().ToPointer(); + this.normalSumImage = new long[normalHeight, this.normalWidth]; + this.normalSumHandle = GCHandle.Alloc(this.normalSumImage, GCHandleType.Pinned); + this.normalSum = (long*)this.normalSumHandle.AddrOfPinnedObject().ToPointer(); - this.sSumImage = new long[this.nHeight, this.nWidth]; - this.sSumHandle = GCHandle.Alloc(this.sSumImage, GCHandleType.Pinned); - this.sSum = (long*)this.sSumHandle.AddrOfPinnedObject().ToPointer(); + this.squaredSumImage = new long[normalHeight, this.normalWidth]; + this.squaredSumHandle = GCHandle.Alloc(this.squaredSumImage, GCHandleType.Pinned); + this.squaredSum = (long*)this.squaredSumHandle.AddrOfPinnedObject().ToPointer(); if (this.computeTilted) { - this.tSumImage = new long[this.tHeight, this.tWidth]; - this.tSumHandle = GCHandle.Alloc(this.tSumImage, GCHandleType.Pinned); - this.tSum = (long*)this.tSumHandle.AddrOfPinnedObject().ToPointer(); + this.tiltedSumImage = new long[tiltedHeight, this.tiltedWidth]; + this.tiltedSumHandle = GCHandle.Alloc(this.tiltedSumImage, GCHandleType.Pinned); + this.tiltedSum = (long*)this.tiltedSumHandle.AddrOfPinnedObject().ToPointer(); } this.LockBitmap(); @@ -172,6 +217,30 @@ namespace ImageProcessor.Imaging } } + /// + /// Gets the Integral Image for values' sum. + /// + public long[,] NormalImage + { + get { return this.normalSumImage; } + } + + /// + /// Gets the Integral Image for values' squared sum. + /// + public long[,] SquaredImage + { + get { return this.squaredSumImage; } + } + + /// + /// Gets the Integral Image for tilted values' sum. + /// + public long[,] RotatedImage + { + get { return this.tiltedSumImage; } + } + /// /// Gets the pixel data for the given position. /// @@ -186,18 +255,18 @@ namespace ImageProcessor.Imaging /// private Color32* this[int x, int y] { - get { return (Color32*)(this.pixelBase + (y * this.bytesInARow) + (x * this.color32Size)); } + get { return (Color32*)(this.pixelBase + (y * this.bytesInARow) + (x * this.pixelSize)); } } /// /// Allows the implicit conversion of an instance of to a - /// . + /// . /// /// /// The instance of to convert. /// /// - /// An instance of . + /// An instance of . /// public static implicit operator Image(FastBitmap fastBitmap) { @@ -239,7 +308,14 @@ namespace ImageProcessor.Imaging } #endif Color32* data = this[x, y]; - return Color.FromArgb(data->A, data->R, data->G, data->B); + + if (this.bitmap.PixelFormat == PixelFormat.Format32bppArgb || + this.bitmap.PixelFormat == PixelFormat.Format32bppPArgb) + { + return Color.FromArgb(data->A, data->R, data->G, data->B); + } + + return Color.FromArgb(data->R, data->G, data->B); } /// @@ -268,7 +344,12 @@ namespace ImageProcessor.Imaging data->R = color.R; data->G = color.G; data->B = color.B; - data->A = color.A; + + if (this.bitmap.PixelFormat == PixelFormat.Format32bppArgb || + this.bitmap.PixelFormat == PixelFormat.Format32bppPArgb) + { + data->A = color.A; + } } /// @@ -284,12 +365,12 @@ namespace ImageProcessor.Imaging /// public long GetSum(int x, int y, int rectangleWidth, int rectangleHeight) { - int a = (this.nWidth * y) + x; - int b = (this.nWidth * (y + rectangleHeight)) + (x + rectangleWidth); - int c = (this.nWidth * (y + rectangleHeight)) + x; - int d = (this.nWidth * y) + (x + rectangleWidth); + int a = (this.normalWidth * y) + x; + int b = (this.normalWidth * (y + rectangleHeight)) + (x + rectangleWidth); + int c = (this.normalWidth * (y + rectangleHeight)) + x; + int d = (this.normalWidth * y) + (x + rectangleWidth); - return this.nSum[a] + this.nSum[b] - this.nSum[c] - this.nSum[d]; + return this.normalSum[a] + this.normalSum[b] - this.normalSum[c] - this.normalSum[d]; } /// @@ -305,12 +386,12 @@ namespace ImageProcessor.Imaging /// public long GetSum2(int x, int y, int rectangleWidth, int rectangleHeight) { - int a = (this.nWidth * y) + x; - int b = (this.nWidth * (y + rectangleHeight)) + (x + rectangleWidth); - int c = (this.nWidth * (y + rectangleHeight)) + x; - int d = (this.nWidth * y) + (x + rectangleWidth); + int a = (this.normalWidth * y) + x; + int b = (this.normalWidth * (y + rectangleHeight)) + (x + rectangleWidth); + int c = (this.normalWidth * (y + rectangleHeight)) + x; + int d = (this.normalWidth * y) + (x + rectangleWidth); - return this.sSum[a] + this.sSum[b] - this.sSum[c] - this.sSum[d]; + return this.squaredSum[a] + this.squaredSum[b] - this.squaredSum[c] - this.squaredSum[d]; } /// @@ -326,12 +407,12 @@ namespace ImageProcessor.Imaging /// public long GetSumT(int x, int y, int rectangleWidth, int rectangleHeight) { - int a = (this.tWidth * (y + rectangleWidth)) + (x + rectangleWidth + 1); - int b = (this.tWidth * (y + rectangleHeight)) + (x - rectangleHeight + 1); - int c = (this.tWidth * y) + (x + 1); - int d = (this.tWidth * (y + rectangleWidth + rectangleHeight)) + (x + rectangleWidth - rectangleHeight + 1); + int a = (this.tiltedWidth * (y + rectangleWidth)) + (x + rectangleWidth + 1); + int b = (this.tiltedWidth * (y + rectangleHeight)) + (x - rectangleHeight + 1); + int c = (this.tiltedWidth * y) + (x + 1); + int d = (this.tiltedWidth * (y + rectangleWidth + rectangleHeight)) + (x + rectangleWidth - rectangleHeight + 1); - return this.tSum[a] + this.tSum[b] - this.tSum[c] - this.tSum[d]; + return this.tiltedSum[a] + this.tiltedSum[b] - this.tiltedSum[c] - this.tiltedSum[d]; } /// @@ -398,22 +479,22 @@ namespace ImageProcessor.Imaging // Call the appropriate methods to clean up // unmanaged resources here. - if (this.nSumHandle.IsAllocated) + if (this.normalSumHandle.IsAllocated) { - this.nSumHandle.Free(); - this.nSum = null; + this.normalSumHandle.Free(); + this.normalSum = null; } - if (this.sSumHandle.IsAllocated) + if (this.squaredSumHandle.IsAllocated) { - this.sSumHandle.Free(); - this.sSum = null; + this.squaredSumHandle.Free(); + this.squaredSum = null; } - if (this.tSumHandle.IsAllocated) + if (this.tiltedSumHandle.IsAllocated) { - this.tSumHandle.Free(); - this.tSum = null; + this.tiltedSumHandle.Free(); + this.tiltedSum = null; } // Note disposing is done. @@ -430,15 +511,14 @@ namespace ImageProcessor.Imaging // Figure out the number of bytes in a row. This is rounded up to be a multiple // of 4 bytes, since a scan line in an image must always be a multiple of 4 bytes // in length. - this.color32Size = sizeof(Color32); - this.bytesInARow = bounds.Width * this.color32Size; + this.bytesInARow = bounds.Width * this.pixelSize; if (this.bytesInARow % 4 != 0) { this.bytesInARow = 4 * ((this.bytesInARow / 4) + 1); } // Lock the bitmap - this.bitmapData = this.bitmap.LockBits(bounds, ImageLockMode.ReadWrite, PixelFormat.Format32bppPArgb); + this.bitmapData = this.bitmap.LockBits(bounds, ImageLockMode.ReadWrite, this.bitmap.PixelFormat); // Set the value to the first scan line this.pixelBase = (byte*)this.bitmapData.Scan0.ToPointer(); @@ -452,21 +532,19 @@ namespace ImageProcessor.Imaging // Calculate integral and integral squared values. int stride = this.bitmapData.Stride; int offset = stride - this.bytesInARow; - byte* srcStart = this.pixelBase + this.channel; // Do the job byte* src = srcStart; // For each line - // TODO. Make this parallel for (int y = 1; y <= this.height; y++) { - int yy = this.nWidth * y; - int y1 = this.nWidth * (y - 1); + int yy = this.normalWidth * y; + int y1 = this.normalWidth * (y - 1); // For each pixel - for (int x = 1; x <= this.width; x++, src += this.color32Size) + for (int x = 1; x <= this.width; x++, src += this.pixelSize) { int pixel = *src; int pixelSquared = pixel * pixel; @@ -474,10 +552,10 @@ namespace ImageProcessor.Imaging int r = yy + x; int a = yy + (x - 1); int b = y1 + x; - int c = y1 + (x - 1); + int g = y1 + (x - 1); - this.nSum[r] = pixel + this.nSum[a] + this.nSum[b] - this.nSum[c]; - this.sSum[r] = pixelSquared + this.sSum[a] + this.sSum[b] - this.sSum[c]; + this.normalSum[r] = pixel + this.normalSum[a] + this.normalSum[b] - this.normalSum[g]; + this.squaredSum[r] = pixelSquared + this.squaredSum[a] + this.squaredSum[b] - this.squaredSum[g]; } src += offset; @@ -490,60 +568,60 @@ namespace ImageProcessor.Imaging // Left-to-right, top-to-bottom pass for (int y = 1; y <= this.height; y++, src += offset) { - int yy = this.tWidth * y; - int y1 = this.tWidth * (y - 1); + int yy = this.tiltedWidth * y; + int y1 = this.tiltedWidth * (y - 1); - for (int x = 2; x < this.width + 2; x++, src += this.color32Size) + for (int x = 2; x < this.width + 2; x++, src += this.pixelSize) { int a = y1 + (x - 1); int b = yy + (x - 1); - int c = y1 + (x - 2); + int g = y1 + (x - 2); int r = yy + x; - this.tSum[r] = *src + this.tSum[a] + this.tSum[b] - this.tSum[c]; + this.tiltedSum[r] = *src + this.tiltedSum[a] + this.tiltedSum[b] - this.tiltedSum[g]; } } { - int yy = this.tWidth * this.height; - int y1 = this.tWidth * (this.height + 1); + int yy = this.tiltedWidth * this.height; + int y1 = this.tiltedWidth * (this.height + 1); - for (int x = 2; x < this.width + 2; x++, src += this.color32Size) + for (int x = 2; x < this.width + 2; x++, src += this.pixelSize) { int a = yy + (x - 1); int c = yy + (x - 2); int b = y1 + (x - 1); int r = y1 + x; - this.tSum[r] = this.tSum[a] + this.tSum[b] - this.tSum[c]; + this.tiltedSum[r] = this.tiltedSum[a] + this.tiltedSum[b] - this.tiltedSum[c]; } } // Right-to-left, bottom-to-top pass for (int y = this.height; y >= 0; y--) { - int yy = this.tWidth * y; - int y1 = this.tWidth * (y + 1); + int yy = this.tiltedWidth * y; + int y1 = this.tiltedWidth * (y + 1); for (int x = this.width + 1; x >= 1; x--) { int r = yy + x; int b = y1 + (x - 1); - this.tSum[r] += this.tSum[b]; + this.tiltedSum[r] += this.tiltedSum[b]; } } for (int y = this.height + 1; y >= 0; y--) { - int yy = this.tWidth * y; + int yy = this.tiltedWidth * y; for (int x = this.width + 1; x >= 2; x--) { int r = yy + x; int b = yy + (x - 2); - this.tSum[r] -= this.tSum[b]; + this.tiltedSum[r] -= this.tiltedSum[b]; } } } diff --git a/src/ImageProcessor/Imaging/Filters/ObjectDetection/HaarObjectDetector.cs b/src/ImageProcessor/Imaging/Filters/ObjectDetection/HaarObjectDetector.cs index 100f5c686..674495d1c 100644 --- a/src/ImageProcessor/Imaging/Filters/ObjectDetection/HaarObjectDetector.cs +++ b/src/ImageProcessor/Imaging/Filters/ObjectDetection/HaarObjectDetector.cs @@ -92,7 +92,7 @@ namespace ImageProcessor.Imaging.Filters.ObjectDetection private Size minSize = new Size(15, 15); private Size maxSize = new Size(500, 500); private float factor = 1.2f; - private int channel = new Color32().R; + // private int channel = new Color32().R; private Rectangle[] lastObjects; private int steadyThreshold = 2; @@ -238,11 +238,11 @@ namespace ImageProcessor.Imaging.Filters.ObjectDetection /// Gets or sets the color channel to use when processing color images. /// /// - public int Channel - { - get { return channel; } - set { channel = value; } - } + //public int Channel + //{ + // get { return channel; } + // set { channel = value; } + //} /// /// Gets or sets the scaling factor to rescale the window during search. @@ -361,13 +361,13 @@ namespace ImageProcessor.Imaging.Filters.ObjectDetection /// public Rectangle[] ProcessFrame(Bitmap image) { - int colorChannel = - image.PixelFormat == PixelFormat.Format8bppIndexed ? 0 : channel; + // int colorChannel = + // image.PixelFormat == PixelFormat.Format8bppIndexed ? 0 : channel; Rectangle[] objects; // Creates an integral image representation of the frame - using (FastBitmap fastBitmap = new FastBitmap(image, colorChannel, this.classifier.Cascade.HasTiltedFeatures)) + using (FastBitmap fastBitmap = new FastBitmap(image, this.classifier.Cascade.HasTiltedFeatures)) { // Creates a new list of detected objects. this.detectedObjects.Clear(); diff --git a/src/ImageProcessor/Imaging/Formats/FormatUtilities.cs b/src/ImageProcessor/Imaging/Formats/FormatUtilities.cs index cb27c8d50..c35db1852 100644 --- a/src/ImageProcessor/Imaging/Formats/FormatUtilities.cs +++ b/src/ImageProcessor/Imaging/Formats/FormatUtilities.cs @@ -11,12 +11,14 @@ namespace ImageProcessor.Imaging.Formats { using System; + using System.Collections.Concurrent; using System.Collections.Generic; using System.Drawing; using System.Drawing.Imaging; using System.IO; using System.Linq; using System.Reflection; + using System.Threading.Tasks; using ImageProcessor.Configuration; @@ -83,6 +85,42 @@ namespace ImageProcessor.Imaging.Formats return (image.PixelFormat & PixelFormat.Indexed) != 0; } + /// + /// Returns the color count from the palette of the given image. + /// + /// + /// The to get the colors from. + /// + /// + /// The representing the color count. + /// + public static int GetColorCount(Image image) + { + ConcurrentDictionary colors = new ConcurrentDictionary(); + int width = image.Width; + int height = image.Height; + + using (FastBitmap fastBitmap = new FastBitmap(image)) + { + Parallel.For( + 0, + height, + y => + { + for (int x = 0; x < width; x++) + { + // ReSharper disable once AccessToDisposedClosure + Color color = fastBitmap.GetPixel(x, y); + colors.TryAdd(color, color); + } + }); + } + + int count = colors.Count; + colors.Clear(); + return count; + } + /// /// Returns a value indicating whether the given image is indexed. /// diff --git a/src/ImageProcessor/Imaging/Formats/GifFormat.cs b/src/ImageProcessor/Imaging/Formats/GifFormat.cs index e27766dc7..81b1df7e6 100644 --- a/src/ImageProcessor/Imaging/Formats/GifFormat.cs +++ b/src/ImageProcessor/Imaging/Formats/GifFormat.cs @@ -16,6 +16,7 @@ namespace ImageProcessor.Imaging.Formats using System.IO; using System.Text; + using ImageProcessor.Imaging.Helpers; using ImageProcessor.Imaging.Quantizers; /// @@ -28,6 +29,11 @@ namespace ImageProcessor.Imaging.Formats /// private IQuantizer quantizer = new OctreeQuantizer(255, 8); + /// + /// The color count. + /// + private int colorCount; + /// /// Gets the file headers. /// @@ -88,6 +94,24 @@ namespace ImageProcessor.Imaging.Formats } } + /// + /// Gets or sets the color count. + /// + public int ColorCount + { + get + { + return this.colorCount; + } + + set + { + int count = ImageMaths.Clamp(value, 0, 255); + this.colorCount = count; + this.quantizer = new OctreeQuantizer(count, 8); + } + } + /// /// Applies the given processor the current image. /// diff --git a/src/ImageProcessor/Imaging/Formats/IQuantizableImageFormat.cs b/src/ImageProcessor/Imaging/Formats/IQuantizableImageFormat.cs index 21065459a..575f5c83a 100644 --- a/src/ImageProcessor/Imaging/Formats/IQuantizableImageFormat.cs +++ b/src/ImageProcessor/Imaging/Formats/IQuantizableImageFormat.cs @@ -21,5 +21,10 @@ namespace ImageProcessor.Imaging.Formats /// Gets or sets the quantizer for reducing the image palette. /// IQuantizer Quantizer { get; set; } + + /// + /// Gets or sets the color count. + /// + int ColorCount { get; set; } } } diff --git a/src/ImageProcessor/Imaging/Formats/PngFormat.cs b/src/ImageProcessor/Imaging/Formats/PngFormat.cs index bdf4295b9..38cfab429 100644 --- a/src/ImageProcessor/Imaging/Formats/PngFormat.cs +++ b/src/ImageProcessor/Imaging/Formats/PngFormat.cs @@ -87,6 +87,11 @@ namespace ImageProcessor.Imaging.Formats } } + /// + /// Gets or sets the color count. + /// + public int ColorCount { get; set; } + /// /// Saves the current image to the specified output stream. /// diff --git a/src/ImageProcessor/Imaging/Quantizers/OctreeQuantizer.cs b/src/ImageProcessor/Imaging/Quantizers/OctreeQuantizer.cs index 4f9496fba..d9d57ac71 100644 --- a/src/ImageProcessor/Imaging/Quantizers/OctreeQuantizer.cs +++ b/src/ImageProcessor/Imaging/Quantizers/OctreeQuantizer.cs @@ -109,7 +109,7 @@ namespace ImageProcessor.Imaging.Quantizers /// protected override byte QuantizePixel(Color32* pixel) { - // The color at [_maxColors] is set to transparent + // The color at [maxColors] is set to transparent byte paletteIndex = (byte)this.maxColors; // Get the palette index if this non-transparent