diff --git a/src/ImageProcessor.Web/NET45/Caching/DiskCache.cs b/src/ImageProcessor.Web/NET45/Caching/DiskCache.cs index 9bb8917838..24d46dc282 100644 --- a/src/ImageProcessor.Web/NET45/Caching/DiskCache.cs +++ b/src/ImageProcessor.Web/NET45/Caching/DiskCache.cs @@ -179,7 +179,7 @@ namespace ImageProcessor.Web.Caching { // Can't check the last write time so check to see if the cached image is set to expire // or if the max age is different. - if (cachedImage.CreationTimeUtc.AddDays(MaxFileCachedDuration) < DateTime.UtcNow.AddDays(-MaxFileCachedDuration)) + if (this.IsExpired(cachedImage.CreationTimeUtc)) { CacheIndexer.Remove(path); isUpdated = true; @@ -208,7 +208,7 @@ namespace ImageProcessor.Web.Caching // Check to see if the last write time is different of whether the // cached image is set to expire or if the max age is different. if (!this.RoughDateTimeCompare(imageFileInfo.LastWriteTimeUtc, cachedImage.LastWriteTimeUtc) - || cachedImage.CreationTimeUtc.AddDays(MaxFileCachedDuration) < DateTime.UtcNow.AddDays(-MaxFileCachedDuration)) + || this.IsExpired(cachedImage.CreationTimeUtc)) { CacheIndexer.Remove(path); isUpdated = true; @@ -285,7 +285,6 @@ namespace ImageProcessor.Web.Caching DateTime creationTime = DateTime.MinValue.ToUniversalTime(); DateTime lastWriteTime = DateTime.MinValue.ToUniversalTime(); - if (this.isRemote) { if (cachedFileInfo.Exists) @@ -330,7 +329,8 @@ namespace ImageProcessor.Web.Caching { // If the group count is equal to the max count minus 1 then we know we // have reduced the number of items below the maximum allowed. - if (count <= MaxFilesCount - 1) + // We'll cleanup any orphaned expired files though. + if (!this.IsExpired(fileInfo.CreationTimeUtc) && count <= MaxFilesCount - 1) { break; } @@ -404,6 +404,21 @@ namespace ImageProcessor.Web.Caching return false; } + + /// + /// Gets a value indicating whether the given images creation date is out with + /// the prescribed limit. + /// + /// + /// The creation date. + /// + /// + /// The true if the date is out with the limit, otherwise; false. + /// + private bool IsExpired(DateTime creationDate) + { + return creationDate.AddDays(MaxFileCachedDuration) < DateTime.UtcNow.AddDays(-MaxFileCachedDuration); + } #endregion #endregion } diff --git a/src/ImageProcessor.Web/NET45/HttpModules/ImageProcessingModule.cs b/src/ImageProcessor.Web/NET45/HttpModules/ImageProcessingModule.cs index 1a7296ed1c..a6a86f01d2 100644 --- a/src/ImageProcessor.Web/NET45/HttpModules/ImageProcessingModule.cs +++ b/src/ImageProcessor.Web/NET45/HttpModules/ImageProcessingModule.cs @@ -314,8 +314,17 @@ namespace ImageProcessor.Web.HttpModules } else { + // Check to see if the file exists. + // ReSharper disable once AssignNullToNotNullAttribute + FileInfo fileInfo = new FileInfo(requestPath); + + if (!fileInfo.Exists) + { + throw new HttpException(404, "No image exists at " + fullPath); + } + imageFactory.Load(fullPath).AutoProcess().Save(cachedPath); - + // Ensure that the LastWriteTime property of the source and cached file match. Tuple creationAndLastWriteDateTimes = await cache.SetCachedLastWriteTimeAsync(); diff --git a/src/ImageProcessor/ImageFactory.cs b/src/ImageProcessor/ImageFactory.cs index adcfb9178d..4278122e88 100644 --- a/src/ImageProcessor/ImageFactory.cs +++ b/src/ImageProcessor/ImageFactory.cs @@ -364,7 +364,7 @@ namespace ImageProcessor { if (this.ShouldProcess) { - ResizeLayer layer = new ResizeLayer(size, ResizeMode.Max); + ResizeLayer layer = new ResizeLayer(size, Color.Transparent, ResizeMode.Max); return this.Resize(layer); } @@ -487,6 +487,102 @@ namespace ImageProcessor return this; } + /// + /// Uses a Gaussian kernel to blur the current image. + /// + /// + /// The sigma and threshold values applied to the kernel are + /// 1.4 and 0 respectively. + /// + /// + /// + /// + /// The size to set the Gaussian kernel to. + /// + /// + /// The current instance of the class. + /// + public ImageFactory GaussianBlur(int size) + { + if (this.ShouldProcess && size > 0) + { + GaussianLayer layer = new GaussianLayer(size); + GaussianBlur gaussianBlur = new GaussianBlur { DynamicParameter = layer }; + this.Image = gaussianBlur.ProcessImage(this); + } + + return this; + } + + /// + /// Uses a Gaussian kernel to blur the current image. + /// + /// + /// The for applying sharpening and + /// blurring methods to an image. + /// + /// + /// The current instance of the class. + /// + public ImageFactory GaussianBlur(GaussianLayer gaussianLayer) + { + if (this.ShouldProcess) + { + GaussianBlur gaussianBlur = new GaussianBlur { DynamicParameter = gaussianLayer }; + this.Image = gaussianBlur.ProcessImage(this); + } + + return this; + } + + /// + /// Uses a Gaussian kernel to sharpen the current image. + /// + /// + /// The sigma and threshold values applied to the kernel are + /// 1.4 and 0 respectively. + /// + /// + /// + /// + /// The size to set the Gaussian kernel to. + /// + /// + /// The current instance of the class. + /// + public ImageFactory GaussianSharpen(int size) + { + if (this.ShouldProcess && size > 0) + { + GaussianLayer layer = new GaussianLayer(size); + GaussianSharpen gaussianSharpen = new GaussianSharpen { DynamicParameter = layer }; + this.Image = gaussianSharpen.ProcessImage(this); + } + + return this; + } + + /// + /// Uses a Gaussian kernel to sharpen the current image. + /// + /// + /// The for applying sharpening and + /// blurring methods to an image. + /// + /// + /// The current instance of the class. + /// + public ImageFactory GaussianSharpen(GaussianLayer gaussianLayer) + { + if (this.ShouldProcess) + { + GaussianSharpen gaussianSharpen = new GaussianSharpen { DynamicParameter = gaussianLayer }; + this.Image = gaussianSharpen.ProcessImage(this); + } + + return this; + } + /// /// Alters the output quality of the current image. /// diff --git a/src/ImageProcessor/ImageProcessor.csproj b/src/ImageProcessor/ImageProcessor.csproj index 236724acdc..bac0ae9805 100644 --- a/src/ImageProcessor/ImageProcessor.csproj +++ b/src/ImageProcessor/ImageProcessor.csproj @@ -59,7 +59,6 @@ - diff --git a/src/ImageProcessor/Imaging/Convolution.cs b/src/ImageProcessor/Imaging/Convolution.cs index 61187eb4fc..5e8aac4332 100644 --- a/src/ImageProcessor/Imaging/Convolution.cs +++ b/src/ImageProcessor/Imaging/Convolution.cs @@ -253,103 +253,34 @@ namespace ImageProcessor.Imaging } /// - /// Convert a Bitmap to an a multidimensional array of raw pixel values + /// Processes the given kernel to produce an array of pixels representing a bitmap. /// - /// The image to convert - /// a multidimensional array of raw pixel values - public Pixel[,] BitmapToPixels(Bitmap image) + /// The the image to process. + /// The Gaussian kernel to use when performing the method + /// A processed bitmap. + public Bitmap ProcessKernel(Bitmap sourceBitmap, double[,] kernel) { - Pixel[,] pixels = new Pixel[image.Width, image.Height]; + int width = sourceBitmap.Width; + int height = sourceBitmap.Height; - BitmapData sourceData = image.LockBits( - new Rectangle(0, 0, image.Width, image.Height), + BitmapData sourceData = sourceBitmap.LockBits( + new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, - image.PixelFormat); - - byte[] pixelBuffer = new byte[sourceData.Stride * sourceData.Height]; - - Marshal.Copy(sourceData.Scan0, pixelBuffer, 0, pixelBuffer.Length); - image.UnlockBits(sourceData); - - int pixelSize = Image.GetPixelFormatSize(image.PixelFormat) / 8; - int stride = sourceData.Stride; - - for (int x = 0; x < image.Width; x++) - { - for (int y = 0; y < image.Height; y++) - { - int byteOffset = (y * stride) + (x * pixelSize); - - pixels[x, y] = new Pixel - { - A = pixelBuffer[byteOffset + 3], - R = pixelBuffer[byteOffset + 2], - G = pixelBuffer[byteOffset + 1], - B = pixelBuffer[byteOffset] - }; - } - } - - return pixels; - } - - /// - /// Convert a multidimensional array of raw pixel values to a bitmap - /// - /// The pixels to convert - /// a bitmap - public Bitmap PixelsToBitmap(Pixel[,] pixels) - { - int width = pixels.GetLength(0); - int height = pixels.GetLength(1); - Bitmap resultBitmap = new Bitmap(width, height); - BitmapData resultData = resultBitmap.LockBits( - new Rectangle(0, 0, resultBitmap.Width, resultBitmap.Height), - ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb); - byte[] pixelBuffer = new byte[resultData.Stride * resultData.Height]; - int stride = resultData.Stride; - int pixelSize = Image.GetPixelFormatSize(resultBitmap.PixelFormat) / 8; - - for (int x = 0; x < width; x++) - { - for (int y = 0; y < height; y++) - { - int byteOffset = (y * stride) + (x * pixelSize); + int strideWidth = sourceData.Stride; + int scanHeight = sourceData.Height; - double pixelRed = pixels[x, y].R; - double pixelGreen = pixels[x, y].G; - double pixelBlue = pixels[x, y].B; - double pixelAlpha = pixels[x, y].A; + int bufferSize = strideWidth * scanHeight; + byte[] pixelBuffer = new byte[bufferSize]; + byte[] resultBuffer = new byte[bufferSize]; - pixelBuffer[byteOffset] = (byte)pixelBlue; - pixelBuffer[byteOffset + 1] = (byte)pixelGreen; - pixelBuffer[byteOffset + 2] = (byte)pixelRed; - pixelBuffer[byteOffset + 3] = (byte)pixelAlpha; - } - } - - Marshal.Copy(pixelBuffer, 0, resultData.Scan0, pixelBuffer.Length); - resultBitmap.UnlockBits(resultData); - - return resultBitmap; - } + Marshal.Copy(sourceData.Scan0, pixelBuffer, 0, pixelBuffer.Length); + sourceBitmap.UnlockBits(sourceData); - /// - /// Processes the given kernel to produce an array of pixels representing a bitmap. - /// - /// The raw pixels of the image to blur - /// The Gaussian kernel to use when performing the method - /// An array of pixels representing the bitmap. - public Pixel[,] ProcessKernel(Pixel[,] pixels, double[,] kernel) - { - int width = pixels.GetLength(0); - int height = pixels.GetLength(1); int kernelLength = kernel.GetLength(0); int radius = kernelLength >> 1; int kernelSize = kernelLength * kernelLength; - Pixel[,] result = new Pixel[width, height]; // For each line for (int y = 0; y < height; y++) @@ -367,6 +298,9 @@ namespace ImageProcessor.Imaging double green; double red = green = blue = alpha = divider = processedKernelSize = 0; + // The location of the pixel bytes. + int byteOffset = (y * strideWidth) + (x * 4); + // For each kernel row for (int i = 0; i < kernelLength; i++) { @@ -399,15 +333,19 @@ namespace ImageProcessor.Imaging if (offsetX < width) { - double k = kernel[i, j]; - Pixel pixel = pixels[offsetX, offsetY]; + int calcOffset = (offsetX * 4) + (offsetY * sourceData.Stride); + byte sourceBlue = pixelBuffer[calcOffset]; + byte sourceGreen = pixelBuffer[calcOffset + 1]; + byte sourceRed = pixelBuffer[calcOffset + 2]; + byte sourceAlpha = pixelBuffer[calcOffset + 3]; + double k = kernel[i, j]; divider += k; - red += k * pixel.R; - green += k * pixel.G; - blue += k * pixel.B; - alpha += k * pixel.A; + red += k * sourceRed; + green += k * sourceGreen; + blue += k * sourceBlue; + alpha += k * sourceAlpha; processedKernelSize++; } @@ -445,14 +383,24 @@ namespace ImageProcessor.Imaging blue += this.Threshold; alpha += this.Threshold; - result[x, y].R = (byte)((red > 255) ? 255 : ((red < 0) ? 0 : red)); - result[x, y].G = (byte)((green > 255) ? 255 : ((green < 0) ? 0 : green)); - result[x, y].B = (byte)((blue > 255) ? 255 : ((blue < 0) ? 0 : blue)); - result[x, y].A = (byte)((alpha > 255) ? 255 : ((alpha < 0) ? 0 : alpha)); + resultBuffer[byteOffset] = (byte)((blue > 255) ? 255 : ((blue < 0) ? 0 : blue)); + resultBuffer[byteOffset + 1] = (byte)((green > 255) ? 255 : ((green < 0) ? 0 : green)); + resultBuffer[byteOffset + 2] = (byte)((red > 255) ? 255 : ((red < 0) ? 0 : red)); + resultBuffer[byteOffset + 3] = (byte)((alpha > 255) ? 255 : ((alpha < 0) ? 0 : alpha)); } } - return result; + Bitmap resultBitmap = new Bitmap(width, height); + + BitmapData resultData = resultBitmap.LockBits( + new Rectangle(0, 0, width, height), + ImageLockMode.WriteOnly, + PixelFormat.Format32bppArgb); + + Marshal.Copy(resultBuffer, 0, resultData.Scan0, resultBuffer.Length); + resultBitmap.UnlockBits(resultData); + + return resultBitmap; } #region Private diff --git a/src/ImageProcessor/Imaging/Filters/ColorMatrixes.cs b/src/ImageProcessor/Imaging/Filters/ColorMatrixes.cs index 784370ccb3..fd54cef225 100644 --- a/src/ImageProcessor/Imaging/Filters/ColorMatrixes.cs +++ b/src/ImageProcessor/Imaging/Filters/ColorMatrixes.cs @@ -1,19 +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. // -// ----------------------------------------------------------------------- +// +// A list of available color matrices to apply to an image. +// +// -------------------------------------------------------------------------------------------------------------------- namespace ImageProcessor.Imaging.Filters { #region Using - using System; - using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; - using System.Linq; - using System.Text; - using System.Drawing.Imaging; + using System.Drawing.Imaging; #endregion /// @@ -175,5 +174,46 @@ namespace ImageProcessor.Imaging.Filters }); } } + + /// + /// Gets for generating the high pass + /// on the comic book filter. + /// + internal static ColorMatrix ComicHigh + { + get + { + return new ColorMatrix( + new float[][] + { + new float[] { 2, -0.5f, -0.5f, 0, 0 }, + new float[] { -0.5f, 2, -0.5f, 0, 0 }, + new float[] { -0.5f, -0.5f, 2, 0, 0 }, + new float[] { 0, 0, 0, 1, 0 }, + new float[] { 0, 0, 0, 0, 1 } + }); + } + } + + /// + /// Gets for generating the low pass + /// on the comic book filter. + /// + internal static ColorMatrix ComicLow + { + get + { + return new ColorMatrix( + new float[][] + { + new float[] { 1, 0, 0, 0, 0 }, + new float[] { 0, 1, 0, 0, 0 }, + new float[] { 0, 0, 1, 0, 0 }, + new float[] { 0, 0, 0, 1, 0 }, + new float[] { .075f, .075f, .075f, 0, 1 } + }); + } + } + } } diff --git a/src/ImageProcessor/Imaging/Filters/ComicMatrixFilter.cs b/src/ImageProcessor/Imaging/Filters/ComicMatrixFilter.cs index 5cdf120763..d4506969c0 100644 --- a/src/ImageProcessor/Imaging/Filters/ComicMatrixFilter.cs +++ b/src/ImageProcessor/Imaging/Filters/ComicMatrixFilter.cs @@ -50,13 +50,13 @@ namespace ImageProcessor.Imaging.Filters /// Alpha = 3 } - + /// /// Gets the for this filter instance. /// public ColorMatrix Matrix { - get { return ColorMatrixes.LoSatch; } + get { return ColorMatrixes.ComicLow; } } /// @@ -74,61 +74,80 @@ namespace ImageProcessor.Imaging.Filters public Image TransformImage(ImageFactory factory, Image image, Image newImage) { // Bitmaps for comic pattern - Bitmap hisatchBitmap = null; + Bitmap highBitmap = null; + Bitmap lowBitmap = null; Bitmap patternBitmap = null; try { - using (Graphics graphics = Graphics.FromImage(newImage)) + using (ImageAttributes attributes = new ImageAttributes()) { - using (ImageAttributes attributes = new ImageAttributes()) - { - attributes.SetColorMatrix(this.Matrix); + Rectangle rectangle = new Rectangle(0, 0, image.Width, image.Height); - Rectangle rectangle = new Rectangle(0, 0, image.Width, image.Height); + attributes.SetColorMatrix(ColorMatrixes.ComicHigh); - // Set the attributes to LoSatch and draw the image. - graphics.DrawImage(image, rectangle, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, attributes); + // Draw the image with the high comic colormatrix. + highBitmap = new Bitmap(rectangle.Width, rectangle.Height, PixelFormat.Format32bppPArgb); - // Create a bitmap for overlaying. - hisatchBitmap = new Bitmap(rectangle.Width, rectangle.Height, PixelFormat.Format32bppPArgb); + // Apply a oil painting filter to the image. + highBitmap = OilPaintFilter((Bitmap)image, 3, 5); - // Set the color matrix - attributes.SetColorMatrix(ColorMatrixes.HiSatch); + using (Graphics graphics = Graphics.FromImage(highBitmap)) + { + graphics.DrawImage(highBitmap, rectangle, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, attributes); + } - // Draw the image with the hisatch colormatrix. - using (var g = Graphics.FromImage(hisatchBitmap)) - { - g.DrawImage(image, rectangle, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, attributes); - } + // Create a bitmap for overlaying. + lowBitmap = new Bitmap(rectangle.Width, rectangle.Height, PixelFormat.Format32bppPArgb); + + // Set the color matrix + attributes.SetColorMatrix(this.Matrix); + + // Draw the image with the losatch colormatrix. + using (Graphics graphics = Graphics.FromImage(lowBitmap)) + { + graphics.DrawImage(highBitmap, rectangle, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, attributes); + } - // We need to create a new image now with the hi saturation colormatrix and a pattern mask to paint it - // onto the other image with. - patternBitmap = new Bitmap(rectangle.Width, rectangle.Height, PixelFormat.Format32bppPArgb); + // We need to create a new image now with a pattern mask to paint it + // onto the other image with. + patternBitmap = new Bitmap(rectangle.Width, rectangle.Height, PixelFormat.Format32bppPArgb); - // Create the pattern mask. - using (var g = Graphics.FromImage(patternBitmap)) + // Create the pattern mask. + using (Graphics graphics = Graphics.FromImage(patternBitmap)) + { + graphics.Clear(Color.Black); + graphics.SmoothingMode = SmoothingMode.HighQuality; + + for (int y = 0; y < image.Height; y += 8) { - g.Clear(Color.Black); - g.SmoothingMode = SmoothingMode.HighQuality; - for (var y = 0; y < image.Height; y += 10) + for (int x = 0; x < image.Width; x += 4) { - for (var x = 0; x < image.Width; x += 6) - { - g.FillEllipse(Brushes.White, x, y, 4, 4); - g.FillEllipse(Brushes.White, x + 3, y + 5, 4, 4); - } + graphics.FillEllipse(Brushes.White, x, y, 3, 3); + graphics.FillEllipse(Brushes.White, x + 2, y + 4, 3, 3); } } + } - // Transfer the alpha channel from the mask to the high saturation image. - TransferOneArgbChannelFromOneBitmapToAnother(patternBitmap, hisatchBitmap, ChannelArgb.Blue, ChannelArgb.Alpha); + // Transfer the alpha channel from the mask to the high saturation image. + TransferOneArgbChannelFromOneBitmapToAnother(patternBitmap, lowBitmap, ChannelArgb.Blue, ChannelArgb.Alpha); + using (Graphics graphics = Graphics.FromImage(newImage)) + { // Overlay the image. - graphics.DrawImage(hisatchBitmap, 0, 0); + graphics.DrawImage(highBitmap, 0, 0); + graphics.DrawImage(lowBitmap, 0, 0); + + // Draw an edge around the image. + using (Pen blackPen = new Pen(Color.Black)) + { + blackPen.Width = 4; + graphics.DrawRectangle(blackPen, rectangle); + } // Dispose of the other images - hisatchBitmap.Dispose(); + highBitmap.Dispose(); + lowBitmap.Dispose(); patternBitmap.Dispose(); } } @@ -144,9 +163,14 @@ namespace ImageProcessor.Imaging.Filters newImage.Dispose(); } - if (hisatchBitmap != null) + if (highBitmap != null) { - hisatchBitmap.Dispose(); + highBitmap.Dispose(); + } + + if (lowBitmap != null) + { + lowBitmap.Dispose(); } if (patternBitmap != null) @@ -158,6 +182,335 @@ namespace ImageProcessor.Imaging.Filters return image; } + /// + /// Applies an oil paint filter. + /// TODO: Move this to another class and add to the factory + /// + /// + /// The source bitmap. + /// + /// + /// The levels. + /// + /// + /// The filter size. + /// + /// + /// The . + /// + private static Bitmap OilPaintFilter(Bitmap sourceBitmap, int levels, int filterSize) + { + int width = sourceBitmap.Width; + int height = sourceBitmap.Height; + + BitmapData sourceData = sourceBitmap.LockBits( + new Rectangle(0, 0, width, height), + ImageLockMode.ReadOnly, + PixelFormat.Format32bppArgb); + + int strideWidth = sourceData.Stride; + int scanHeight = sourceData.Height; + + int bufferSize = strideWidth * scanHeight; + byte[] pixelBuffer = new byte[bufferSize]; + byte[] resultBuffer = new byte[bufferSize]; + + Marshal.Copy(sourceData.Scan0, pixelBuffer, 0, pixelBuffer.Length); + sourceBitmap.UnlockBits(sourceData); + + levels = levels - 1; + + int radius = filterSize >> 1; + + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + int maxIntensity = 0; + int maxIndex = 0; + int[] intensityBin = new int[levels + 1]; + int[] blueBin = new int[levels + 1]; + int[] greenBin = new int[levels + 1]; + int[] redBin = new int[levels + 1]; + + int byteOffset = (y * strideWidth) + (x * 4); + + for (int i = 0; i <= radius; i++) + { + int ir = i - radius; + int offsetY = y + ir; + + // Skip the current row + if (offsetY < 0) + { + continue; + } + + // Outwith the current bounds so break. + if (offsetY >= height) + { + break; + } + + for (int j = 0; j <= radius; j++) + { + int jr = j - radius; + int offsetX = x + jr; + + // Skip the column + if (offsetX < 0) + { + continue; + } + + if (offsetX < width) + { + int calcOffset = (offsetX * 4) + (offsetY * sourceData.Stride); + + byte sourceBlue = pixelBuffer[calcOffset]; + byte sourceGreen = pixelBuffer[calcOffset + 1]; + byte sourceRed = pixelBuffer[calcOffset + 2]; + + int currentIntensity = (int)Math.Round(((sourceBlue + sourceGreen + sourceRed) / 3.0 * levels) / 255.0); + + intensityBin[currentIntensity] += 1; + blueBin[currentIntensity] += sourceBlue; + greenBin[currentIntensity] += sourceGreen; + redBin[currentIntensity] += sourceRed; + + if (intensityBin[currentIntensity] > maxIntensity) + { + maxIntensity = intensityBin[currentIntensity]; + maxIndex = currentIntensity; + } + } + } + } + + double blue = Math.Abs(blueBin[maxIndex] / maxIntensity); + double green = Math.Abs(greenBin[maxIndex] / maxIntensity); + double red = Math.Abs(redBin[maxIndex] / maxIntensity); + + blue = blue > 255 ? 255 : (blue < 0 ? 0 : blue); + green = green > 255 ? 255 : (green < 0 ? 0 : green); + red = red > 255 ? 255 : (red < 0 ? 0 : red); + + resultBuffer[byteOffset] = (byte)blue; + resultBuffer[byteOffset + 1] = (byte)green; + resultBuffer[byteOffset + 2] = (byte)red; + resultBuffer[byteOffset + 3] = 255; + } + } + + Bitmap resultBitmap = new Bitmap(width, height); + + BitmapData resultData = resultBitmap.LockBits( + new Rectangle(0, 0, width, height), + ImageLockMode.WriteOnly, + PixelFormat.Format32bppArgb); + + Marshal.Copy(resultBuffer, 0, resultData.Scan0, resultBuffer.Length); + resultBitmap.UnlockBits(resultData); + + return resultBitmap; + } + + /// + /// Detects and draws edges. + /// TODO: Move this to another class and do move edge detection. + /// + /// + /// The source bitmap. + /// + /// + /// The threshold. + /// + /// + /// The . + /// + private static Bitmap DrawEdges(Bitmap sourceBitmap, byte threshold = 0) + { + Color color = Color.Black; + int width = sourceBitmap.Width; + int height = sourceBitmap.Height; + + BitmapData sourceData = sourceBitmap.LockBits( + new Rectangle(0, 0, width, height), + ImageLockMode.ReadOnly, + PixelFormat.Format32bppArgb); + + int strideWidth = sourceData.Stride; + int scanHeight = sourceData.Height; + + int bufferSize = strideWidth * scanHeight; + byte[] pixelBuffer = new byte[bufferSize]; + byte[] resultBuffer = new byte[bufferSize]; + + Marshal.Copy(sourceData.Scan0, pixelBuffer, 0, pixelBuffer.Length); + + sourceBitmap.UnlockBits(sourceData); + + for (int offsetY = 1; offsetY < height - 1; offsetY++) + { + for (int offsetX = 1; offsetX < width - 1; offsetX++) + { + int byteOffset = (offsetY * strideWidth) + (offsetX * 4); + + int blueGradient = Math.Abs(pixelBuffer[byteOffset - 4] - pixelBuffer[byteOffset + 4]); + + blueGradient += + Math.Abs( + pixelBuffer[byteOffset - strideWidth] - pixelBuffer[byteOffset + strideWidth]); + + byteOffset++; + + int greenGradient = Math.Abs(pixelBuffer[byteOffset - 4] - pixelBuffer[byteOffset + 4]); + + greenGradient += + Math.Abs( + pixelBuffer[byteOffset - strideWidth] - pixelBuffer[byteOffset + strideWidth]); + + byteOffset++; + + int redGradient = Math.Abs(pixelBuffer[byteOffset - 4] - pixelBuffer[byteOffset + 4]); + + redGradient += + Math.Abs( + pixelBuffer[byteOffset - strideWidth] - pixelBuffer[byteOffset + strideWidth]); + + bool exceedsThreshold; + if (blueGradient + greenGradient + redGradient > threshold) + { + exceedsThreshold = true; + } + else + { + byteOffset -= 2; + + blueGradient = Math.Abs(pixelBuffer[byteOffset - 4] - pixelBuffer[byteOffset + 4]); + byteOffset++; + + greenGradient = Math.Abs(pixelBuffer[byteOffset - 4] - pixelBuffer[byteOffset + 4]); + byteOffset++; + + redGradient = Math.Abs(pixelBuffer[byteOffset - 4] - pixelBuffer[byteOffset + 4]); + + if (blueGradient + greenGradient + redGradient > threshold) + { + exceedsThreshold = true; + } + else + { + byteOffset -= 2; + + blueGradient = + Math.Abs(pixelBuffer[byteOffset - strideWidth] - pixelBuffer[byteOffset + strideWidth]); + + byteOffset++; + + greenGradient = + Math.Abs(pixelBuffer[byteOffset - strideWidth] - pixelBuffer[byteOffset + strideWidth]); + + byteOffset++; + + redGradient = + Math.Abs(pixelBuffer[byteOffset - strideWidth] - pixelBuffer[byteOffset + strideWidth]); + + if (blueGradient + greenGradient + redGradient > threshold) + { + exceedsThreshold = true; + } + else + { + byteOffset -= 2; + + blueGradient = + Math.Abs( + pixelBuffer[byteOffset - 4 - strideWidth] + - pixelBuffer[byteOffset + 4 + strideWidth]); + + blueGradient += + Math.Abs( + pixelBuffer[byteOffset - strideWidth + 4] + - pixelBuffer[byteOffset + strideWidth - 4]); + + byteOffset++; + + greenGradient = + Math.Abs( + pixelBuffer[byteOffset - 4 - strideWidth] + - pixelBuffer[byteOffset + 4 + strideWidth]); + + greenGradient += + Math.Abs( + pixelBuffer[byteOffset - strideWidth + 4] + - pixelBuffer[byteOffset + strideWidth - 4]); + + byteOffset++; + + redGradient = + Math.Abs( + pixelBuffer[byteOffset - 4 - strideWidth] + - pixelBuffer[byteOffset + 4 + strideWidth]); + + redGradient += + Math.Abs( + pixelBuffer[byteOffset - strideWidth + 4] + - pixelBuffer[byteOffset + strideWidth - 4]); + + exceedsThreshold = blueGradient + greenGradient + redGradient > threshold; + } + } + } + + byteOffset -= 2; + + double blue; + double red; + double green; + double alpha; + if (exceedsThreshold) + { + blue = color.B; // 0; + green = color.G; // 0; + red = color.R; // 0; + alpha = 255; + } + else + { + // These would normally be used to transfer the correct value accross. + // blue = pixelBuffer[byteOffset]; + // green = pixelBuffer[byteOffset + 1]; + // red = pixelBuffer[byteOffset + 2]; + blue = 255; + green = 255; + red = 255; + alpha = 0; + } + + blue = blue > 255 ? 255 : (blue < 0 ? 0 : blue); + green = green > 255 ? 255 : (green < 0 ? 0 : green); + red = red > 255 ? 255 : (red < 0 ? 0 : red); + resultBuffer[byteOffset] = (byte)blue; + resultBuffer[byteOffset + 1] = (byte)green; + resultBuffer[byteOffset + 2] = (byte)red; + resultBuffer[byteOffset + 3] = (byte)alpha; + } + } + + Bitmap resultBitmap = new Bitmap(width, height); + + BitmapData resultData = resultBitmap.LockBits( + new Rectangle(0, 0, resultBitmap.Width, resultBitmap.Height), + ImageLockMode.WriteOnly, + PixelFormat.Format32bppArgb); + + Marshal.Copy(resultBuffer, 0, resultData.Scan0, resultBuffer.Length); + + resultBitmap.UnlockBits(resultData); + return resultBitmap; + } + /// /// Transfers a single ARGB channel from one image to another. /// @@ -211,6 +564,7 @@ namespace ImageProcessor.Imaging.Filters for (int i = rectangle.Height * rectangle.Width; i > 0; i--) { + // Copy the alpha values across. destinationRgbValues[d] = sourceRgbValues[s]; d += 4; s += 4; @@ -223,4 +577,4 @@ namespace ImageProcessor.Imaging.Filters destination.UnlockBits(bitmapDataDestination); } } -} +} \ No newline at end of file diff --git a/src/ImageProcessor/Imaging/ResizeLayer.cs b/src/ImageProcessor/Imaging/ResizeLayer.cs index c35a4d3d3a..02c636b3d0 100644 --- a/src/ImageProcessor/Imaging/ResizeLayer.cs +++ b/src/ImageProcessor/Imaging/ResizeLayer.cs @@ -26,64 +26,29 @@ namespace ImageProcessor.Imaging /// /// The containing the width and height to set the image to. /// - public ResizeLayer(Size size) - { - this.Size = size; - this.ResizeMode = ResizeMode.Pad; - this.AnchorPosition = AnchorPosition.Center; - this.BackgroundColor = Color.Transparent; - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The containing the width and height to set the image to. - /// - /// - /// The to apply to resized image. - /// - public ResizeLayer(Size size, ResizeMode resizeMode) - { - this.Size = size; - this.ResizeMode = resizeMode; - this.AnchorPosition = AnchorPosition.Center; - this.BackgroundColor = Color.Transparent; - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The containing the width and height to set the image to. - /// - /// - /// The to apply to resized image. - /// - public ResizeLayer(Size size, AnchorPosition anchorPosition) - { - this.Size = size; - this.AnchorPosition = anchorPosition; - this.ResizeMode = ResizeMode.Pad; - this.BackgroundColor = Color.Transparent; - } - - /// - /// Initializes a new instance of the class. - /// /// /// The to set as the background color. - /// Used for image formats that do not support transparency + /// Used for image formats that do not support transparency (Default transparent) /// /// - /// The resize mode to apply to resized image. + /// The resize mode to apply to resized image. (Default ResizeMode.Pad) /// /// - /// The to apply to resized image. + /// The to apply to resized image. (Default AnchorPosition.Center) + /// + /// + /// Whether to allow up-scaling of images. (Default true) /// - public ResizeLayer(Color backgroundColor, ResizeMode resizeMode = ResizeMode.Pad, AnchorPosition anchorPosition = AnchorPosition.Center) + public ResizeLayer( + Size size, + Color? backgroundColor = null, + ResizeMode resizeMode = ResizeMode.Pad, + AnchorPosition anchorPosition = AnchorPosition.Center, + bool upscale = true) { - this.BackgroundColor = backgroundColor; + this.Size = size; + this.Upscale = upscale; + this.BackgroundColor = backgroundColor ?? Color.Transparent; this.ResizeMode = resizeMode; this.AnchorPosition = anchorPosition; } @@ -109,6 +74,11 @@ namespace ImageProcessor.Imaging /// Gets or sets the anchor position. /// public AnchorPosition AnchorPosition { get; set; } + + /// + /// Gets or sets a value indicating whether to allow up-scaling of images. + /// + public bool Upscale { get; set; } #endregion /// @@ -135,7 +105,8 @@ namespace ImageProcessor.Imaging return this.Size == resizeLayer.Size && this.ResizeMode == resizeLayer.ResizeMode && this.AnchorPosition == resizeLayer.AnchorPosition - && this.BackgroundColor == resizeLayer.BackgroundColor; + && this.BackgroundColor == resizeLayer.BackgroundColor + && this.Upscale == resizeLayer.Upscale; } /// @@ -146,7 +117,11 @@ namespace ImageProcessor.Imaging /// public override int GetHashCode() { - return this.Size.GetHashCode() + this.ResizeMode.GetHashCode() + this.AnchorPosition.GetHashCode() + this.BackgroundColor.GetHashCode(); + return this.Size.GetHashCode() + + this.ResizeMode.GetHashCode() + + this.AnchorPosition.GetHashCode() + + this.BackgroundColor.GetHashCode() + + this.Upscale.GetHashCode(); } } } diff --git a/src/ImageProcessor/Processors/Filter.cs b/src/ImageProcessor/Processors/Filter.cs index 0cee24c0da..cfdd05cb8b 100644 --- a/src/ImageProcessor/Processors/Filter.cs +++ b/src/ImageProcessor/Processors/Filter.cs @@ -120,6 +120,7 @@ namespace ImageProcessor.Processors try { // Don't use an object initializer here. + // ReSharper disable once UseObjectOrCollectionInitializer newImage = new Bitmap(image.Width, image.Height, PixelFormat.Format32bppPArgb); newImage.Tag = image.Tag; diff --git a/src/ImageProcessor/Processors/GaussianBlur.cs b/src/ImageProcessor/Processors/GaussianBlur.cs index 7da860121c..d6d697508c 100644 --- a/src/ImageProcessor/Processors/GaussianBlur.cs +++ b/src/ImageProcessor/Processors/GaussianBlur.cs @@ -139,10 +139,8 @@ namespace ImageProcessor.Processors GaussianLayer gaussianLayer = (GaussianLayer)this.DynamicParameter; Convolution convolution = new Convolution(gaussianLayer.Sigma) { Threshold = gaussianLayer.Threshold }; - Pixel[,] pixels = convolution.BitmapToPixels(newImage); double[,] kernel = convolution.CreateGuassianBlurFilter(gaussianLayer.Size); - pixels = convolution.ProcessKernel(pixels, kernel); - newImage = convolution.PixelsToBitmap(pixels); + newImage = convolution.ProcessKernel(newImage, kernel); newImage.Tag = image.Tag; image.Dispose(); @@ -194,9 +192,9 @@ namespace ImageProcessor.Processors /// private int ParseThreshold(string input) { + // ReSharper disable once LoopCanBeConvertedToQuery foreach (Match match in ThresholdRegex.Matches(input)) { - // split on text- return Convert.ToInt32(match.Value.Split('-')[1]); } @@ -215,9 +213,9 @@ namespace ImageProcessor.Processors /// private int ParseBlur(string input) { + // ReSharper disable once LoopCanBeConvertedToQuery foreach (Match match in BlurRegex.Matches(input)) { - // split on text- return Convert.ToInt32(match.Value.Split('=')[1]); } diff --git a/src/ImageProcessor/Processors/GaussianSharpen.cs b/src/ImageProcessor/Processors/GaussianSharpen.cs index eab7460a16..5bd4cacab4 100644 --- a/src/ImageProcessor/Processors/GaussianSharpen.cs +++ b/src/ImageProcessor/Processors/GaussianSharpen.cs @@ -138,10 +138,8 @@ namespace ImageProcessor.Processors GaussianLayer gaussianLayer = (GaussianLayer)this.DynamicParameter; Convolution convolution = new Convolution(gaussianLayer.Sigma) { Threshold = gaussianLayer.Threshold }; - Pixel[,] pixels = convolution.BitmapToPixels(newImage); double[,] kernel = convolution.CreateGuassianSharpenFilter(gaussianLayer.Size); - pixels = convolution.ProcessKernel(pixels, kernel); - newImage = convolution.PixelsToBitmap(pixels); + newImage = convolution.ProcessKernel(newImage, kernel); newImage.Tag = image.Tag; image.Dispose(); @@ -193,9 +191,9 @@ namespace ImageProcessor.Processors /// private int ParseThreshold(string input) { + // ReSharper disable once LoopCanBeConvertedToQuery foreach (Match match in ThresholdRegex.Matches(input)) { - // split on text- return Convert.ToInt32(match.Value.Split('-')[1]); } @@ -214,9 +212,9 @@ namespace ImageProcessor.Processors /// private int ParseSharpen(string input) { + // ReSharper disable once LoopCanBeConvertedToQuery foreach (Match match in SharpenRegex.Matches(input)) { - // split on text- return Convert.ToInt32(match.Value.Split('=')[1]); } diff --git a/src/ImageProcessor/Processors/Resize.cs b/src/ImageProcessor/Processors/Resize.cs index 7fac2cc420..d0ba5539a8 100644 --- a/src/ImageProcessor/Processors/Resize.cs +++ b/src/ImageProcessor/Processors/Resize.cs @@ -32,7 +32,7 @@ namespace ImageProcessor.Processors /// /// The regular expression to search strings for. /// - private static readonly Regex QueryRegex = new Regex(@"((width|height)=\d+)|(mode=(pad|stretch|crop|max))|(anchor=(top|bottom|left|right|center))|(bgcolor=([0-9a-fA-F]{3}){1,2})", RegexOptions.Compiled); + private static readonly Regex QueryRegex = new Regex(@"((width|height)=\d+)|(mode=(pad|stretch|crop|max))|(anchor=(top|bottom|left|right|center))|(bgcolor=([0-9a-fA-F]{3}){1,2})|(upscale=false)", RegexOptions.Compiled); /// /// The regular expression to search strings for the size attribute. @@ -54,6 +54,10 @@ namespace ImageProcessor.Processors /// private static readonly Regex ColorRegex = new Regex(@"bgcolor=([0-9a-fA-F]{3}){1,2}", RegexOptions.Compiled); + /// + /// The regular expression to search strings for the upscale attribute. + /// + private static readonly Regex UpscaleRegex = new Regex(@"upscale=false", RegexOptions.Compiled); #region IGraphicsProcessor Members /// /// Gets the regular expression to search strings for. @@ -136,7 +140,8 @@ namespace ImageProcessor.Processors { ResizeMode = this.ParseMode(toParse), AnchorPosition = this.ParsePosition(toParse), - BackgroundColor = this.ParseColor(toParse) + BackgroundColor = this.ParseColor(toParse), + Upscale = !UpscaleRegex.IsMatch(toParse) }; this.DynamicParameter = resizeLayer; @@ -160,13 +165,14 @@ namespace ImageProcessor.Processors ResizeMode mode = this.DynamicParameter.ResizeMode; AnchorPosition anchor = this.DynamicParameter.AnchorPosition; Color backgroundColor = this.DynamicParameter.BackgroundColor; + bool upscale = this.DynamicParameter.Upscale; int defaultMaxWidth; int defaultMaxHeight; int.TryParse(this.Settings["MaxWidth"], out defaultMaxWidth); int.TryParse(this.Settings["MaxHeight"], out defaultMaxHeight); - return this.ResizeImage(factory, width, height, defaultMaxWidth, defaultMaxHeight, backgroundColor, mode, anchor); + return this.ResizeImage(factory, width, height, defaultMaxWidth, defaultMaxHeight, backgroundColor, mode, anchor, upscale); } #endregion @@ -198,6 +204,9 @@ namespace ImageProcessor.Processors /// /// The anchor position to place the image at. /// + /// + /// Whether to allow up-scaling of images. (Default true) + /// /// /// The processed image from the current instance of the class. /// @@ -209,7 +218,8 @@ namespace ImageProcessor.Processors int defaultMaxHeight, Color backgroundColor, ResizeMode resizeMode = ResizeMode.Pad, - AnchorPosition anchorPosition = AnchorPosition.Center) + AnchorPosition anchorPosition = AnchorPosition.Center, + bool upscale = true) { Bitmap newImage = null; Image image = factory.Image; @@ -332,6 +342,12 @@ namespace ImageProcessor.Processors if (width > 0 && height > 0 && width <= maxWidth && height <= maxHeight) { + // Exit if upscaling is not allowed. + if ((width > sourceWidth || height > sourceHeight) && upscale == false && resizeMode != ResizeMode.Stretch) + { + return image; + } + // Don't use an object initializer here. // ReSharper disable once UseObjectOrCollectionInitializer newImage = new Bitmap(width, height, PixelFormat.Format32bppPArgb); diff --git a/src/TestWebsites/NET4/Images/Thumbs.db.REMOVED.git-id b/src/TestWebsites/NET4/Images/Thumbs.db.REMOVED.git-id index 3619925b06..32de55cbda 100644 --- a/src/TestWebsites/NET4/Images/Thumbs.db.REMOVED.git-id +++ b/src/TestWebsites/NET4/Images/Thumbs.db.REMOVED.git-id @@ -1 +1 @@ -32c1c68dd25fc55293b1a21c1ea25002c53f984b \ No newline at end of file +1d78931ad90a644551671f9b1252e4e772f2493b \ No newline at end of file diff --git a/src/TestWebsites/NET45/Test_Website_NET45/Images/Penguins-200.jpg b/src/TestWebsites/NET45/Test_Website_NET45/Images/Penguins-200.jpg new file mode 100644 index 0000000000..07605996ff --- /dev/null +++ b/src/TestWebsites/NET45/Test_Website_NET45/Images/Penguins-200.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:672de68017f17260126901065f1c6ade2b2981d33dea0dea1606bf7cfb6fdcf3 +size 10119 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..60a8946afe --- /dev/null +++ b/src/TestWebsites/NET45/Test_Website_NET45/Images/Thumbs.db.REMOVED.git-id @@ -0,0 +1 @@ +5a5eaebf9a5eafe87321a87bf497baf7e2152653 \ No newline at end of file diff --git a/src/TestWebsites/NET45/Test_Website_NET45/Images/emma.jpg b/src/TestWebsites/NET45/Test_Website_NET45/Images/emma.jpg new file mode 100644 index 0000000000..4c26271434 --- /dev/null +++ b/src/TestWebsites/NET45/Test_Website_NET45/Images/emma.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a0e660d63df4245aafc03d68de5ba6ead499cff3610745c42072b00a40cdadda +size 29617 diff --git a/src/TestWebsites/NET45/Test_Website_NET45/Images/MSwanson - Wide Large - Rock 02.jpg.REMOVED.git-id b/src/TestWebsites/NET45/Test_Website_NET45/Images/rocks.jpg.REMOVED.git-id similarity index 100% rename from src/TestWebsites/NET45/Test_Website_NET45/Images/MSwanson - Wide Large - Rock 02.jpg.REMOVED.git-id rename to src/TestWebsites/NET45/Test_Website_NET45/Images/rocks.jpg.REMOVED.git-id diff --git a/src/TestWebsites/NET45/Test_Website_NET45/Images/thor.jpg.REMOVED.git-id b/src/TestWebsites/NET45/Test_Website_NET45/Images/thor.jpg.REMOVED.git-id new file mode 100644 index 0000000000..2b94932d0a --- /dev/null +++ b/src/TestWebsites/NET45/Test_Website_NET45/Images/thor.jpg.REMOVED.git-id @@ -0,0 +1 @@ +4087bfc9fb25acb1bb13a15478f74e088f617934 \ No newline at end of file diff --git a/src/TestWebsites/NET45/Test_Website_NET45/Images/udendørs-374.jpg b/src/TestWebsites/NET45/Test_Website_NET45/Images/udendørs-374.jpg new file mode 100644 index 0000000000..b7d01ee065 --- /dev/null +++ b/src/TestWebsites/NET45/Test_Website_NET45/Images/udendørs-374.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7a736ad3e043195ebc95fd427cf22f44e9e640bfaa4b37c188dd0ca6ad096eee +size 20944 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 41ee89e666..29f1dbe8f2 100644 --- a/src/TestWebsites/NET45/Test_Website_NET45/Test_Website_NET45.csproj +++ b/src/TestWebsites/NET45/Test_Website_NET45/Test_Website_NET45.csproj @@ -138,15 +138,19 @@ + + + + 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 7bc0464b9b..6572d1734d 100644 --- a/src/TestWebsites/NET45/Test_Website_NET45/Views/Home/Index.cshtml +++ b/src/TestWebsites/NET45/Test_Website_NET45/Views/Home/Index.cshtml @@ -8,8 +8,6 @@

Resized

- -

Foreign language test.

@@ -53,6 +51,17 @@
+
+
+

Resize Max - No Upscale

+
+ +
+
+ +
+
+

Resize Stretch

@@ -160,9 +169,19 @@
+
+
+
+

Gaussian Blur

+ +
+
+

Gaussian Sharpen

+ +
+
+
- -

Color Profiles

@*
diff --git a/src/TestWebsites/NET45/Test_Website_NET45/Web.config b/src/TestWebsites/NET45/Test_Website_NET45/Web.config index 70a09ab843..001fd564ff 100644 --- a/src/TestWebsites/NET45/Test_Website_NET45/Web.config +++ b/src/TestWebsites/NET45/Test_Website_NET45/Web.config @@ -18,7 +18,7 @@ - +