/* * Greenshot - a free and open source screenshot tool * Copyright (C) 2007-2013 Thomas Braun, Jens Klingen, Robin Krom * * For more information see: http://getgreenshot.org/ * The Greenshot project is hosted on Sourceforge: http://sourceforge.net/projects/greenshot/ * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 1 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ using System; using System.Collections.Generic; using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Imaging; using System.IO; using System.Runtime.InteropServices; using Greenshot.IniFile; using GreenshotPlugin.UnmanagedHelpers; using Greenshot.Plugin; using Greenshot.Core; namespace GreenshotPlugin.Core { /// /// Description of ImageHelper. /// public static class ImageHelper { private static log4net.ILog LOG = log4net.LogManager.GetLogger(typeof(ImageHelper)); private static CoreConfiguration conf = IniConfig.GetIniSection(); /// /// Create a thumbnail from an image /// /// /// /// /// public static Image CreateThumbnail(Image image, int thumbWidth, int thumbHeight) { return CreateThumbnail(image, thumbWidth, thumbHeight, -1, -1); } /// /// Create a Thumbnail /// /// /// /// /// /// /// public static Image CreateThumbnail(Image image, int thumbWidth, int thumbHeight, int maxWidth, int maxHeight) { int srcWidth=image.Width; int srcHeight=image.Height; if (thumbHeight < 0) { thumbHeight = (int)(thumbWidth * ((float)srcHeight / (float)srcWidth)); } if (thumbWidth < 0) { thumbWidth = (int)(thumbHeight * ((float)srcWidth / (float)srcHeight)); } if (maxWidth > 0 && thumbWidth > maxWidth) { thumbWidth = Math.Min(thumbWidth, maxWidth); thumbHeight = (int)(thumbWidth * ((float)srcHeight / (float)srcWidth)); } if (maxHeight > 0 && thumbHeight > maxHeight) { thumbHeight = Math.Min(thumbHeight, maxHeight); thumbWidth = (int)(thumbHeight * ((float)srcWidth / (float)srcHeight)); } Bitmap bmp = new Bitmap(thumbWidth, thumbHeight); using (Graphics graphics = System.Drawing.Graphics.FromImage(bmp)) { graphics.SmoothingMode = SmoothingMode.HighQuality; graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; graphics.CompositingQuality = CompositingQuality.HighQuality; graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; Rectangle rectDestination = new Rectangle(0, 0, thumbWidth, thumbHeight); graphics.DrawImage(image, rectDestination, 0, 0, srcWidth, srcHeight, GraphicsUnit.Pixel); } return bmp; } /// /// Crops the image to the specified rectangle /// /// Image to crop /// Rectangle with bitmap coordinates, will be "intersected" to the bitmap public static bool Crop(ref Image image, ref Rectangle cropRectangle) { Image returnImage = null; if (image != null && image is Bitmap && ((image.Width * image.Height) > 0)) { cropRectangle.Intersect(new Rectangle(0,0, image.Width, image.Height)); if (cropRectangle.Width != 0 || cropRectangle.Height != 0) { returnImage = CloneArea(image, cropRectangle, PixelFormat.DontCare); image.Dispose(); image = returnImage; return true; } } LOG.Warn("Can't crop a null/zero size image!"); return false; } /// /// Private helper method for the FindAutoCropRectangle /// /// /// /// Rectangle private static Rectangle FindAutoCropRectangle(IFastBitmap fastBitmap, Point colorPoint, int cropDifference) { Rectangle cropRectangle = Rectangle.Empty; Color referenceColor = fastBitmap.GetColorAt(colorPoint.X, colorPoint.Y); Point min = new Point(int.MaxValue, int.MaxValue); Point max = new Point(int.MinValue, int.MinValue); if (cropDifference > 0) { for (int y = 0; y < fastBitmap.Height; y++) { for (int x = 0; x < fastBitmap.Width; x++) { Color currentColor = fastBitmap.GetColorAt(x, y); int diffR = Math.Abs(currentColor.R - referenceColor.R); int diffG = Math.Abs(currentColor.G - referenceColor.G); int diffB = Math.Abs(currentColor.B - referenceColor.B); if (((diffR + diffG + diffB) / 3) > cropDifference) { if (x < min.X) min.X = x; if (y < min.Y) min.Y = y; if (x > max.X) max.X = x; if (y > max.Y) max.Y = y; } } } } else { for (int y = 0; y < fastBitmap.Height; y++) { for (int x = 0; x < fastBitmap.Width; x++) { Color currentColor = fastBitmap.GetColorAt(x, y); if (referenceColor.Equals(currentColor)) { if (x < min.X) min.X = x; if (y < min.Y) min.Y = y; if (x > max.X) max.X = x; if (y > max.Y) max.Y = y; } } } } if (!(Point.Empty.Equals(min) && max.Equals(new Point(fastBitmap.Width - 1, fastBitmap.Height - 1)))) { if (!(min.X == int.MaxValue || min.Y == int.MaxValue || max.X == int.MinValue || min.X == int.MinValue)) { cropRectangle = new Rectangle(min.X, min.Y, max.X - min.X + 1, max.Y - min.Y + 1); } } return cropRectangle; } /// /// Get a rectangle for the image which crops the image of all colors equal to that on 0,0 /// /// /// Rectangle public static Rectangle FindAutoCropRectangle(Image image, int cropDifference) { Rectangle cropRectangle = Rectangle.Empty; Rectangle currentRectangle = Rectangle.Empty; List checkPoints = new List(); // Top Left checkPoints.Add(new Point(0, 0)); // Bottom Left checkPoints.Add(new Point(0, image.Height - 1)); // Top Right checkPoints.Add(new Point(image.Width - 1, 0)); // Bottom Right checkPoints.Add(new Point(image.Width - 1, image.Height - 1)); using (IFastBitmap fastBitmap = FastBitmap.Create((Bitmap)image)) { fastBitmap.Lock(); // find biggest area foreach(Point checkPoint in checkPoints) { currentRectangle = FindAutoCropRectangle(fastBitmap, checkPoint, cropDifference); if (currentRectangle.Width * currentRectangle.Height > cropRectangle.Width * cropRectangle.Height) { cropRectangle = currentRectangle; } } } return cropRectangle; } /// /// Load an image from file /// /// /// public static Image LoadImage(string filename) { if (string.IsNullOrEmpty(filename)) { return null; } if (!File.Exists(filename)) { return null; } Image fileImage = null; LOG.InfoFormat("Loading image from file {0}", filename); // Fixed lock problem Bug #3431881 using (Stream imageFileStream = File.OpenRead(filename)) { // And fixed problem that the bitmap stream is disposed... by Cloning the image // This also ensures the bitmap is correctly created if (filename.EndsWith(".ico")) { // Icon logic, try to get the Vista icon, else the biggest possible try { using (Image tmpImage = ExtractVistaIcon(imageFileStream)) { if (tmpImage != null) { fileImage = Clone(tmpImage); } } } catch (Exception vistaIconException) { LOG.Warn("Can't read icon from " + filename, vistaIconException); } if (fileImage == null) { try { // No vista icon, try normal icon imageFileStream.Position = 0; // We create a copy of the bitmap, so everything else can be disposed using (Icon tmpIcon = new Icon(imageFileStream, new Size(1024,1024))) { using (Image tmpImage = tmpIcon.ToBitmap()) { fileImage = Clone(tmpImage); } } } catch (Exception iconException) { LOG.Warn("Can't read icon from " + filename, iconException); } } } if (fileImage == null) { // We create a copy of the bitmap, so everything else can be disposed imageFileStream.Position = 0; using (Image tmpImage = Image.FromStream(imageFileStream, true, true)) { LOG.DebugFormat("Loaded {0} with Size {1}x{2} and PixelFormat {3}", filename, tmpImage.Width, tmpImage.Height, tmpImage.PixelFormat); fileImage = Clone(tmpImage); } } } if (fileImage != null) { LOG.InfoFormat("Information about file {0}: {1}x{2}-{3} Resolution {4}x{5}", filename, fileImage.Width, fileImage.Height, fileImage.PixelFormat, fileImage.HorizontalResolution, fileImage.VerticalResolution); } return fileImage; } /// /// Based on: http://www.codeproject.com/KB/cs/IconExtractor.aspx /// And a hint from: http://www.codeproject.com/KB/cs/IconLib.aspx /// /// Stream with the icon information /// Bitmap with the Vista Icon (256x256) private static Bitmap ExtractVistaIcon(Stream iconStream) { const int SizeICONDIR = 6; const int SizeICONDIRENTRY = 16; Bitmap bmpPngExtracted = null; try { byte[] srcBuf = new byte[iconStream.Length]; iconStream.Read(srcBuf, 0, (int)iconStream.Length); int iCount = BitConverter.ToInt16(srcBuf, 4); for (int iIndex=0; iIndex /// See: http://msdn.microsoft.com/en-us/library/windows/desktop/ms648069%28v=vs.85%29.aspx /// /// The icon to /// The file (EXE or DLL) to get the icon from /// Index of the icon /// true if the large icon is wanted /// Icon public static Icon ExtractAssociatedIcon(string location, int index, bool takeLarge) { IntPtr large; IntPtr small; Shell32.ExtractIconEx(location, index, out large, out small, 1); Icon returnIcon = null; bool isLarge = false; bool isSmall = false; try { if (takeLarge && !IntPtr.Zero.Equals(large)) { returnIcon = Icon.FromHandle(large); isLarge = true; } else if (!IntPtr.Zero.Equals(small)) { returnIcon = Icon.FromHandle(small); isSmall = true; } else if (!IntPtr.Zero.Equals(large)) { returnIcon = Icon.FromHandle(large); isLarge = true; } } finally { if (isLarge && !IntPtr.Zero.Equals(small)) { User32.DestroyIcon(small); } if (isSmall && !IntPtr.Zero.Equals(large)) { User32.DestroyIcon(large); } } return returnIcon; } /// /// Get the number of icon in the file /// /// /// Location of the EXE or DLL /// public static int CountAssociatedIcons(string location) { IntPtr large = IntPtr.Zero; IntPtr small = IntPtr.Zero; return Shell32.ExtractIconEx(location, -1, out large, out small, 0); } /// /// Apply the effect to the bitmap /// /// Bitmap /// IEffect /// Bitmap public static Image ApplyEffect(Image sourceImage, IEffect effect, out Point offset) { List effects = new List(); effects.Add(effect); return ApplyEffects(sourceImage, effects, out offset); } /// /// Apply the effects in the supplied order to the bitmap /// /// Bitmap /// List /// Bitmap public static Image ApplyEffects(Image sourceImage, List effects, out Point offset) { Image currentImage = sourceImage; bool disposeImage = false; // Default out value for the offset, will be modified there where needed offset = new Point(0, 0); Point tmpPoint; foreach (IEffect effect in effects) { Image tmpImage = effect.Apply(currentImage, out tmpPoint); if (tmpImage != null) { offset.Offset(tmpPoint); if (disposeImage) { currentImage.Dispose(); } currentImage = tmpImage; tmpImage = null; // Make sure the "new" image is disposed disposeImage = true; } } return currentImage; } /// /// Make the picture look like it's torn /// /// Bitmap to make torn edge off /// How large (height) is each tooth /// How wide is a horizontal tooth /// How wide is a vertical tooth /// Changed bitmap public static Image CreateTornEdge(Image sourceImage, int toothHeight, int horizontalToothRange, int verticalToothRange) { Image returnImage = CreateEmpty(sourceImage.Width, sourceImage.Height, PixelFormat.Format32bppArgb, Color.Empty, sourceImage.HorizontalResolution, sourceImage.VerticalResolution); using (GraphicsPath path = new GraphicsPath()) { Random random = new Random(); int HorizontalRegions = (int)(sourceImage.Width / horizontalToothRange); int VerticalRegions = (int)(sourceImage.Height / verticalToothRange); // Start Point previousEndingPoint = new Point(horizontalToothRange, random.Next(1, toothHeight)); Point newEndingPoint; // Top for (int i = 0; i < HorizontalRegions; i++) { int x = (int)previousEndingPoint.X + horizontalToothRange; int y = random.Next(1, toothHeight); newEndingPoint = new Point(x, y); path.AddLine(previousEndingPoint, newEndingPoint); previousEndingPoint = newEndingPoint; } // Right for (int i = 0; i < VerticalRegions; i++) { int x = sourceImage.Width - random.Next(1, toothHeight); int y = (int)previousEndingPoint.Y + verticalToothRange; newEndingPoint = new Point(x, y); path.AddLine(previousEndingPoint, newEndingPoint); previousEndingPoint = newEndingPoint; } // Bottom for (int i = 0; i < HorizontalRegions; i++) { int x = (int)previousEndingPoint.X - horizontalToothRange; int y = sourceImage.Height - random.Next(1, toothHeight); newEndingPoint = new Point(x, y); path.AddLine(previousEndingPoint, newEndingPoint); previousEndingPoint = newEndingPoint; } // Left for (int i = 0; i < VerticalRegions; i++) { int x = random.Next(1, toothHeight); int y = (int)previousEndingPoint.Y - verticalToothRange; newEndingPoint = new Point(x, y); path.AddLine(previousEndingPoint, newEndingPoint); previousEndingPoint = newEndingPoint; } path.CloseFigure(); // Draw the created figure with the original image by using a TextureBrush so we have anti-aliasing using (Graphics graphics = Graphics.FromImage(returnImage)) { graphics.SmoothingMode = SmoothingMode.HighQuality; graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; graphics.CompositingQuality = CompositingQuality.HighQuality; graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; using (Brush brush = new TextureBrush(sourceImage)) { // Imporant note: If the target wouldn't be at 0,0 we need to translate-transform!! graphics.FillPath(brush, path); } } } return returnImage; } /// /// Helper Method for the ApplyBlur /// /// /// private static int[] CreateGaussianBlurRow(int amount) { int size = 1 + (amount * 2); int[] weights = new int[size]; for (int i = 0; i <= amount; ++i) { // 1 + aa - aa + 2ai - ii weights[i] = 16 * (i + 1); weights[weights.Length - i - 1] = weights[i]; } return weights; } /// /// Create a new bitmap with the sourceBitmap blurred /// /// Source to blur /// Area to blur /// Use best quality /// Radius of the blur /// Quality, use 1d for normal anything less skipps calculations /// true if the blur needs to occur outside of the area /// Rectangle limiting the area when using invert public static unsafe Bitmap CreateBlur(Bitmap sourceBitmap, Rectangle applyRect, bool useExportQuality, int blurRadius, double previewQuality, bool invert, Rectangle parentBounds) { if (applyRect.Height <= 0 || applyRect.Width <= 0) { return null; } // do nothing when nothing can be done! if (blurRadius < 1) { return null; } byte[] nullColor = new byte[] { 255, 255, 255, 255 }; if (sourceBitmap.PixelFormat == PixelFormat.Format32bppArgb || sourceBitmap.PixelFormat == PixelFormat.Format32bppPArgb) { nullColor = new byte[] { 0, 0, 0, 0 }; } byte[] settingColor = new byte[4]; byte[] readingColor = new byte[4]; using (BitmapBuffer dst = new BitmapBuffer(sourceBitmap, applyRect, true)) { //using (IFastBitmap dst = FastBitmap.CreateEmpty(sourceBitmap.Size, sourceBitmap.PixelFormat, Color.Empty)) { dst.Lock(); using (BitmapBuffer src = new BitmapBuffer(sourceBitmap, applyRect, false)) { //using (IFastBitmap src = FastBitmap.Create(sourceBitmap)) { src.Lock(); Random rand = new Random(); unchecked { int r = blurRadius; int[] w = CreateGaussianBlurRow(r); int wlen = w.Length; long* waSums = stackalloc long[wlen]; long* wcSums = stackalloc long[wlen]; long* aSums = stackalloc long[wlen]; long* rSums = stackalloc long[wlen]; long* gSums = stackalloc long[wlen]; long* bSums = stackalloc long[wlen]; for (int y = 0; y < applyRect.Height; ++y) { long waSum = 0; long wcSum = 0; long aSum = 0; long rSum = 0; long gSum = 0; long bSum = 0; for (int wx = 0; wx < wlen; ++wx) { int srcX = wx - r; waSums[wx] = 0; wcSums[wx] = 0; aSums[wx] = 0; rSums[wx] = 0; gSums[wx] = 0; bSums[wx] = 0; if (srcX >= 0 && srcX < src.Width) { for (int wy = 0; wy < wlen; ++wy) { int srcY = y + wy - r; if (srcY >= 0 && srcY < src.Height) { src.GetColorAt(srcX, srcY, readingColor); int wp = w[wy]; waSums[wx] += wp; wp *= readingColor[0] + (readingColor[0] >> 7); wcSums[wx] += wp; wp >>= 8; aSums[wx] += wp * readingColor[0]; rSums[wx] += wp * readingColor[1]; gSums[wx] += wp * readingColor[2]; bSums[wx] += wp * readingColor[3]; } } int wwx = w[wx]; waSum += wwx * waSums[wx]; wcSum += wwx * wcSums[wx]; aSum += wwx * aSums[wx]; rSum += wwx * rSums[wx]; gSum += wwx * gSums[wx]; bSum += wwx * bSums[wx]; } } wcSum >>= 8; if (parentBounds.Contains(applyRect.Left, applyRect.Top + y) ^ invert) { if (waSum == 0 || wcSum == 0) { dst.SetColorAt(0, y, nullColor); } else { settingColor[0] = (byte)(aSum / waSum); settingColor[1] = (byte)(rSum / wcSum); settingColor[2] = (byte)(gSum / wcSum); settingColor[3] = (byte)(bSum / wcSum); dst.SetColorAt(0, y, settingColor); } } for (int x = 1; x < applyRect.Width; ++x) { for (int i = 0; i < wlen - 1; ++i) { waSums[i] = waSums[i + 1]; wcSums[i] = wcSums[i + 1]; aSums[i] = aSums[i + 1]; rSums[i] = rSums[i + 1]; gSums[i] = gSums[i + 1]; bSums[i] = bSums[i + 1]; } waSum = 0; wcSum = 0; aSum = 0; rSum = 0; gSum = 0; bSum = 0; int wx; for (wx = 0; wx < wlen - 1; ++wx) { long wwx = (long)w[wx]; waSum += wwx * waSums[wx]; wcSum += wwx * wcSums[wx]; aSum += wwx * aSums[wx]; rSum += wwx * rSums[wx]; gSum += wwx * gSums[wx]; bSum += wwx * bSums[wx]; } wx = wlen - 1; waSums[wx] = 0; wcSums[wx] = 0; aSums[wx] = 0; rSums[wx] = 0; gSums[wx] = 0; bSums[wx] = 0; int srcX = x + wx - r; if (srcX >= 0 && srcX < applyRect.Width) { for (int wy = 0; wy < wlen; ++wy) { int srcY = y + wy - r; // only when in EDIT mode, ignore some pixels depending on preview quality if ((useExportQuality || rand.NextDouble() < previewQuality) && srcY >= 0 && srcY < applyRect.Height) { int wp = w[wy]; waSums[wx] += wp; src.GetColorAt(srcX, srcY, readingColor); wp *= readingColor[0] + (readingColor[0] >> 7); wcSums[wx] += wp; wp >>= 8; aSums[wx] += wp * readingColor[0]; rSums[wx] += wp * readingColor[1]; gSums[wx] += wp * readingColor[2]; bSums[wx] += wp * readingColor[3]; } } int wr = w[wx]; waSum += wr * waSums[wx]; wcSum += wr * wcSums[wx]; aSum += wr * aSums[wx]; rSum += wr * rSums[wx]; gSum += wr * gSums[wx]; bSum += wr * bSums[wx]; } wcSum >>= 8; if (parentBounds.Contains(applyRect.Left + x, applyRect.Top + y) ^ invert) { if (waSum == 0 || wcSum == 0) { dst.SetColorAt(x, y, nullColor); } else { settingColor[0] = (byte)(aSum / waSum); settingColor[1] = (byte)(rSum / wcSum); settingColor[2] = (byte)(gSum / wcSum); settingColor[3] = (byte)(bSum / wcSum); dst.SetColorAt(x, y, settingColor); } } } } } } return dst.UnlockAndReturnBitmap(); } } /// /// Create a new Bitmap with the BoxBlur result of the sourceBitmap /// /// Bitmap to blur /// Must be ODD! /// Bitmap public static Bitmap BoxBlur(Bitmap sourceBitmap, int range) { if ((range & 1) == 0) { range++; //throw new InvalidOperationException("Range must be odd!"); } using (IFastBitmap bbbDest = FastBitmap.CreateCloneOf(sourceBitmap, PixelFormat.Format32bppArgb)) { BoxBlurHorizontal(bbbDest, range); BoxBlurVertical(bbbDest, range); BoxBlurHorizontal(bbbDest, range + 1); BoxBlurVertical(bbbDest, range + 1); return bbbDest.UnlockAndReturnBitmap(); } } /// /// BoxBlurHorizontal is a private helper method for the BoxBlur /// /// Source BitmapBuffer /// Target BitmapBuffer /// Range must be odd! private static void BoxBlurHorizontal(IFastBitmap bbbDest, int range) { int w = bbbDest.Width; int h = bbbDest.Height; int halfRange = range / 2; Color[] newColors = new Color[w]; byte[] tmpColor = new byte[4]; for (int y = 0; y < h; y++) { int hits = 0; int a = 0; int r = 0; int g = 0; int b = 0; for (int x = -halfRange; x < w; x++) { int oldPixel = x - halfRange - 1; if (oldPixel >= 0) { bbbDest.GetColorAt(oldPixel, y, tmpColor); a -= tmpColor[0]; r -= tmpColor[1]; g -= tmpColor[2]; b -= tmpColor[3]; hits--; } int newPixel = x + halfRange; if (newPixel < w) { bbbDest.GetColorAt(newPixel, y, tmpColor); a += tmpColor[0]; r += tmpColor[1]; g += tmpColor[2]; b += tmpColor[3]; hits++; } if (x >= 0) { newColors[x] = Color.FromArgb((byte)(a / hits), (byte)(r / hits), (byte)(g / hits), (byte)(b / hits)); } } for (int x = 0; x < w; x++) { bbbDest.SetColorAt(x, y, newColors[x]); } } } /// /// BoxBlurVertical is a private helper method for the BoxBlur /// /// BitmapBuffer which previously was created with BoxBlurHorizontal /// Range must be odd! private static void BoxBlurVertical(IFastBitmap bbbDest, int range) { int w = bbbDest.Width; int h = bbbDest.Height; int halfRange = range / 2; Color[] newColors = new Color[h]; int oldPixelOffset = -(halfRange + 1) * w; int newPixelOffset = (halfRange) * w; byte[] tmpColor = new byte[4]; for (int x = 0; x < w; x++) { int hits = 0; int a = 0; int r = 0; int g = 0; int b = 0; for (int y = -halfRange; y < h; y++) { int oldPixel = y - halfRange - 1; if (oldPixel >= 0) { //int colorg = pixels[index + oldPixelOffset]; bbbDest.GetColorAt(x, oldPixel, tmpColor); a -= tmpColor[0]; r -= tmpColor[1]; g -= tmpColor[2]; b -= tmpColor[3]; hits--; } int newPixel = y + halfRange; if (newPixel < h) { //int colorg = pixels[index + newPixelOffset]; bbbDest.GetColorAt(x, newPixel, tmpColor); a += tmpColor[0]; r += tmpColor[1]; g += tmpColor[2]; b += tmpColor[3]; hits++; } if (y >= 0) { newColors[y] = Color.FromArgb((byte)(a / hits), (byte)(r / hits), (byte)(g / hits), (byte)(b / hits)); } } for (int y = 0; y < h; y++) { bbbDest.SetColorAt(x, y, newColors[y]); } } } /// /// This method fixes the problem that we can't apply a filter outside the target bitmap, /// therefor the filtered-bitmap will be shifted if we try to draw it outside the target bitmap. /// It will also account for the Invert flag. /// /// /// /// /// public static Rectangle CreateIntersectRectangle(Size applySize, Rectangle rect, bool invert) { Rectangle myRect; if (invert) { myRect = new Rectangle(0, 0, applySize.Width, applySize.Height); } else { Rectangle applyRect = new Rectangle(0, 0, applySize.Width, applySize.Height); myRect = new Rectangle(rect.X, rect.Y, rect.Width, rect.Height); myRect.Intersect(applyRect); } return myRect; } /// /// Create a new bitmap where the sourceBitmap has a shadow /// /// Bitmap to make a shadow on /// How dark is the shadow /// Size of the shadow /// What pixel format must the returning bitmap have /// How many pixels is the original image moved? /// Bitmap with the shadow, is bigger than the sourceBitmap!! public static Bitmap CreateShadow(Image sourceBitmap, float darkness, int shadowSize, Point shadowOffset, out Point offset, PixelFormat targetPixelformat) { // Create a new "clean" image Bitmap returnImage = null; offset = shadowOffset; offset.X += shadowSize - 1; offset.Y += shadowSize - 1; using (Bitmap tmpImage = CreateEmpty(sourceBitmap.Width + (shadowSize * 2), sourceBitmap.Height + (shadowSize * 2), targetPixelformat, Color.Empty, sourceBitmap.HorizontalResolution, sourceBitmap.VerticalResolution)) { using (Graphics graphics = Graphics.FromImage(tmpImage)) { // Make sure we draw with the best quality! graphics.SmoothingMode = SmoothingMode.HighQuality; graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; graphics.CompositingQuality = CompositingQuality.HighQuality; graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; // Draw "shadow" offsetted ImageAttributes ia = new ImageAttributes(); ColorMatrix cm = new ColorMatrix(); cm.Matrix00 = 0; cm.Matrix11 = 0; cm.Matrix22 = 0; cm.Matrix33 = darkness; ia.SetColorMatrix(cm); Rectangle shadowRectangle = new Rectangle(new Point(shadowSize, shadowSize), sourceBitmap.Size); graphics.DrawImage(sourceBitmap, shadowRectangle, 0, 0, sourceBitmap.Width, sourceBitmap.Height, GraphicsUnit.Pixel, ia); } // blur "shadow", apply to whole new image //using (Bitmap blurImage = FastBlur(newImage, shadowSize-1)) { // Gaussian Rectangle newImageRectangle = new Rectangle(0, 0, tmpImage.Width, tmpImage.Height); returnImage = CreateBlur(tmpImage, newImageRectangle, true, shadowSize, 1d, false, newImageRectangle); // Box //returnImage = BoxBlur(tmpImage, shadowSize); //returnImage = FastBlur(tmpImage, shadowSize - 1); } if (returnImage != null) { using (Graphics graphics = Graphics.FromImage(returnImage)) { // Make sure we draw with the best quality! graphics.SmoothingMode = SmoothingMode.HighQuality; graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; graphics.CompositingQuality = CompositingQuality.HighQuality; graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; // draw original with a TextureBrush so we have nice antialiasing! using (Brush textureBrush = new TextureBrush(sourceBitmap, WrapMode.Clamp)) { // We need to do a translate-tranform otherwise the image is wrapped graphics.TranslateTransform(offset.X, offset.Y); graphics.FillRectangle(textureBrush, 0, 0, sourceBitmap.Width, sourceBitmap.Height); } } } return returnImage; } /// /// Return negative of Bitmap /// /// Bitmap to create a negative off /// Negative bitmap public static Bitmap CreateNegative(Image sourceImage) { using (BitmapBuffer bb = new BitmapBuffer(sourceImage, true)) { bb.Lock(); for (int y = 0; y < bb.Height; y++) { for (int x = 0; x < bb.Width; x++) { Color color = bb.GetColorAt(x, y); Color invertedColor = Color.FromArgb(color.A, color.R ^ 255, color.G ^ 255, color.B ^ 255); bb.SetColorAt(x, y, invertedColor); } } bb.Unlock(); return bb.Bitmap; } } /// /// Returns a b/w of Bitmap /// /// Bitmap to create a b/w of /// Threshold for monochrome filter (0 - 255), lower value means less black /// b/w bitmap public static Bitmap CreateMonochrome(Image sourceImage, byte threshold) { using (IFastBitmap fastBitmap = FastBitmap.CreateCloneOf(sourceImage, sourceImage.PixelFormat)) { fastBitmap.Lock(); for (int y = 0; y < fastBitmap.Height; y++) { for (int x = 0; x < fastBitmap.Width; x++) { Color color = fastBitmap.GetColorAt(x, y); int colorBrightness = ((color.R + color.G + color.B) / 3 > threshold) ? 255 : 0; Color monoColor = Color.FromArgb(color.A, colorBrightness, colorBrightness, colorBrightness); fastBitmap.SetColorAt(x, y, monoColor); } } return fastBitmap.UnlockAndReturnBitmap(); } } /// /// Create a new bitmap where the sourceBitmap has a Simple border around it /// /// Bitmap to make a border on /// Size of the border /// Color of the border /// What pixel format must the returning bitmap have /// How many pixels is the original image moved? /// Bitmap with the shadow, is bigger than the sourceBitmap!! public static Image CreateBorder(Image sourceImage, int borderSize, Color borderColor, PixelFormat targetPixelformat, out Point offset) { // "return" the shifted offset, so the caller can e.g. move elements offset = new Point(borderSize, borderSize); // Create a new "clean" image Bitmap newImage = CreateEmpty(sourceImage.Width + (borderSize * 2), sourceImage.Height + (borderSize * 2), targetPixelformat, Color.Empty, sourceImage.HorizontalResolution, sourceImage.VerticalResolution); using (Graphics graphics = Graphics.FromImage(newImage)) { // Make sure we draw with the best quality! graphics.SmoothingMode = SmoothingMode.HighQuality; graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; graphics.CompositingQuality = CompositingQuality.HighQuality; graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; using (GraphicsPath path = new GraphicsPath()) { path.AddRectangle(new Rectangle(borderSize >> 1, borderSize >> 1, newImage.Width - (borderSize), newImage.Height - (borderSize))); using (Pen pen = new Pen(borderColor, borderSize)) { pen.LineJoin = LineJoin.Round; pen.StartCap = LineCap.Round; pen.EndCap = LineCap.Round; graphics.DrawPath(pen, path); } } // draw original with a TextureBrush so we have nice antialiasing! using (Brush textureBrush = new TextureBrush(sourceImage, WrapMode.Clamp)) { // We need to do a translate-tranform otherwise the image is wrapped graphics.TranslateTransform(offset.X, offset.Y); graphics.FillRectangle(textureBrush, 0, 0, sourceImage.Width, sourceImage.Height); } } return newImage; } /// /// Adjust the brightness, contract or gamma of an image. /// Use the value "1.0f" for no changes. /// /// Original bitmap /// Bitmap with grayscale public static Image Adjust(Image sourceImage, float brightness, float contrast, float gamma) { //create a blank bitmap the same size as original // If using 8bpp than the following exception comes: A Graphics object cannot be created from an image that has an indexed pixel format. Bitmap newBitmap = CreateEmpty(sourceImage.Width, sourceImage.Height, PixelFormat.Format24bppRgb, Color.Empty, sourceImage.HorizontalResolution, sourceImage.VerticalResolution); float adjustedBrightness = brightness - 1.0f; // get a graphics object from the new image using (Graphics graphics = Graphics.FromImage(newBitmap)) { // create the grayscale ColorMatrix ColorMatrix colorMatrix = new ColorMatrix( new float[][] { new float[] {contrast, 0, 0, 0, 0}, // scale red new float[] {0, contrast, 0, 0, 0}, // scale green new float[] {0, 0, contrast, 0, 0}, // scale blue new float[] {0, 0, 0, 1.0f, 0}, // don't scale alpha new float[] {adjustedBrightness, adjustedBrightness, adjustedBrightness, 0, 1} }); //create some image attributes ImageAttributes attributes = new ImageAttributes(); attributes.ClearColorMatrix(); attributes.SetColorMatrix(colorMatrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap); attributes.SetGamma(gamma, ColorAdjustType.Bitmap); //draw the original image on the new image using the grayscale color matrix graphics.DrawImage(sourceImage, new Rectangle(0, 0, sourceImage.Width, sourceImage.Height), 0, 0, sourceImage.Width, sourceImage.Height, GraphicsUnit.Pixel, attributes); } return newBitmap; } /// /// Create a new bitmap where the sourceBitmap is in grayscale /// /// Original bitmap /// Bitmap with grayscale public static Image CreateGrayscale(Image sourceImage) { //create a blank bitmap the same size as original // If using 8bpp than the following exception comes: A Graphics object cannot be created from an image that has an indexed pixel format. Bitmap newBitmap = CreateEmpty(sourceImage.Width, sourceImage.Height, PixelFormat.Format24bppRgb, Color.Empty, sourceImage.HorizontalResolution, sourceImage.VerticalResolution); //ColorPalette imagePalette = newBitmap.Palette; //for (int i = 0; i <= 255; i++) { // // create greyscale color table // imagePalette.Entries[i] = Color.FromArgb(i, i, i); //} //newBitmap.Palette = imagePalette; // get a graphics object from the new image using (Graphics graphics = Graphics.FromImage(newBitmap)) { // create the grayscale ColorMatrix ColorMatrix colorMatrix = new ColorMatrix( new float[][] { new float[] {.3f, .3f, .3f, 0, 0}, new float[] {.59f, .59f, .59f, 0, 0}, new float[] {.11f, .11f, .11f, 0, 0}, new float[] {0, 0, 0, 1, 0}, new float[] {0, 0, 0, 0, 1} }); //create some image attributes ImageAttributes attributes = new ImageAttributes(); //set the color matrix attribute attributes.SetColorMatrix(colorMatrix); //draw the original image on the new image using the grayscale color matrix graphics.DrawImage(sourceImage, new Rectangle(0, 0, sourceImage.Width, sourceImage.Height), 0, 0, sourceImage.Width, sourceImage.Height, GraphicsUnit.Pixel, attributes); } return newBitmap; } /// /// Checks if the supplied Bitmap has a PixelFormat we support /// /// bitmap to check /// bool if we support it public static bool SupportsPixelFormat(Image image) { return SupportsPixelFormat(image.PixelFormat); } /// /// Checks if we support the pixel format /// /// PixelFormat to check /// bool if we support it public static bool SupportsPixelFormat(PixelFormat pixelformat) { return (pixelformat.Equals(PixelFormat.Format32bppArgb) || pixelformat.Equals(PixelFormat.Format32bppPArgb) || pixelformat.Equals(PixelFormat.Format32bppRgb) || pixelformat.Equals(PixelFormat.Format24bppRgb)); } /// /// Wrapper for just cloning which calls the CloneArea /// /// Image to clone /// Bitmap with clone image data public static Image Clone(Image sourceImage) { if (sourceImage is Metafile) { return (Image)sourceImage.Clone(); } return CloneArea(sourceImage, Rectangle.Empty, PixelFormat.DontCare); } /// /// Wrapper for just cloning & TargetFormat which calls the CloneArea /// /// Image to clone /// Target Format, use PixelFormat.DontCare if you want the original (or a default if the source PixelFormat is not supported) /// Bitmap with clone image data public static Bitmap Clone(Image sourceBitmap, PixelFormat targetFormat) { return CloneArea(sourceBitmap, Rectangle.Empty, targetFormat); } /// /// Clone an image, taking some rules into account: /// 1) When sourceRect is the whole bitmap there is a GDI+ bug in Clone /// Clone will than return the same PixelFormat as the source /// a quick workaround is using new Bitmap which uses a default of Format32bppArgb /// 2) When going from a transparent to a non transparent bitmap, we draw the background white! /// /// Source bitmap to clone /// Rectangle to copy from the source, use Rectangle.Empty for all /// Target Format, use PixelFormat.DontCare if you want the original (or a default if the source PixelFormat is not supported) /// public static Bitmap CloneArea(Image sourceImage, Rectangle sourceRect, PixelFormat targetFormat) { Bitmap newImage = null; Rectangle bitmapRect = new Rectangle(0, 0, sourceImage.Width, sourceImage.Height); // Make sure the source is not Rectangle.Empty if (Rectangle.Empty.Equals(sourceRect)) { sourceRect = new Rectangle(0, 0, sourceImage.Width, sourceImage.Height); } // If no pixelformat is supplied if (PixelFormat.DontCare == targetFormat || PixelFormat.Undefined == targetFormat) { if (SupportsPixelFormat(sourceImage.PixelFormat)) { targetFormat = sourceImage.PixelFormat; } else if (Image.IsAlphaPixelFormat(sourceImage.PixelFormat)) { targetFormat = PixelFormat.Format32bppArgb; } else { targetFormat = PixelFormat.Format24bppRgb; } } // check the target format if (!SupportsPixelFormat(targetFormat)) { if (Image.IsAlphaPixelFormat(targetFormat)) { targetFormat = PixelFormat.Format32bppArgb; } else { targetFormat = PixelFormat.Format24bppRgb; } } bool destinationIsTransparent = Image.IsAlphaPixelFormat(targetFormat); bool sourceIsTransparent = Image.IsAlphaPixelFormat(sourceImage.PixelFormat); bool fromTransparentToNon = !destinationIsTransparent && sourceIsTransparent; bool isBitmap = sourceImage is Bitmap; bool isAreaEqual = sourceRect.Equals(bitmapRect); if (isAreaEqual || fromTransparentToNon || !isBitmap) { // Rule 1: if the areas are equal, always copy ourselves newImage = new Bitmap(bitmapRect.Width, bitmapRect.Height, targetFormat); // Make sure both images have the same resolution newImage.SetResolution(sourceImage.HorizontalResolution, sourceImage.VerticalResolution); using (Graphics graphics = Graphics.FromImage(newImage)) { if (fromTransparentToNon) { // Rule 2: Make sure the background color is white graphics.Clear(Color.White); } // decide fastest copy method if (isAreaEqual) { graphics.DrawImageUnscaled(sourceImage, 0, 0); } else { graphics.DrawImage(sourceImage, 0, 0, sourceRect, GraphicsUnit.Pixel); } } } else { // Let GDI+ decide how to convert, need to test what is quicker... newImage = (sourceImage as Bitmap).Clone(sourceRect, targetFormat); // Make sure both images have the same resolution newImage.SetResolution(sourceImage.HorizontalResolution, sourceImage.VerticalResolution); } return newImage; } /// /// Rotate the bitmap /// /// /// /// public static Image RotateFlip(Image sourceImage, RotateFlipType rotateFlipType) { Image returnImage = Clone(sourceImage); returnImage.RotateFlip(rotateFlipType); return returnImage; } /// /// A generic way to create an empty image /// /// the source bitmap as the specifications for the new bitmap /// The color to fill with, or Color.Empty to take the default depending on the pixel format /// public static Bitmap CreateEmptyLike(Image sourceImage, Color backgroundColor) { PixelFormat pixelFormat = sourceImage.PixelFormat; if (backgroundColor.A < 255) { pixelFormat = PixelFormat.Format32bppArgb; } return CreateEmpty(sourceImage.Width, sourceImage.Height, pixelFormat, backgroundColor, sourceImage.HorizontalResolution, sourceImage.VerticalResolution); } /// /// A generic way to create an empty image /// /// /// /// /// The color to fill with, or Color.Empty to take the default depending on the pixel format /// /// /// Bitmap public static Bitmap CreateEmpty(int width, int height, PixelFormat format, Color backgroundColor, float horizontalResolution, float verticalResolution) { // Create a new "clean" image Bitmap newImage = new Bitmap(width, height, format); newImage.SetResolution(horizontalResolution, verticalResolution); if (format != PixelFormat.Format8bppIndexed) { using (Graphics graphics = Graphics.FromImage(newImage)) { // Make sure the background color is what we want (transparent or white, depending on the pixel format) if (!Color.Empty.Equals(backgroundColor)) { graphics.Clear(backgroundColor); } else if (Image.IsAlphaPixelFormat(format)) { graphics.Clear(Color.Transparent); } else { graphics.Clear(Color.White); } } } return newImage; } /// /// Get a scaled version of the sourceBitmap /// /// /// 1-99 to make smaller, use 101 and more to make the picture bigger /// public static Bitmap ScaleByPercent(Bitmap sourceBitmap, int percent) { float nPercent = ((float)percent/100); int sourceWidth = sourceBitmap.Width; int sourceHeight = sourceBitmap.Height; int destWidth = (int)(sourceWidth * nPercent); int destHeight = (int)(sourceHeight * nPercent); Bitmap scaledBitmap = CreateEmpty(destWidth, destHeight, sourceBitmap.PixelFormat, Color.Empty, sourceBitmap.HorizontalResolution, sourceBitmap.VerticalResolution); using (Graphics graphics = Graphics.FromImage(scaledBitmap)) { graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; graphics.DrawImage(sourceBitmap, new Rectangle(0, 0, destWidth, destHeight), new Rectangle(0, 0, sourceWidth, sourceHeight), GraphicsUnit.Pixel); } return scaledBitmap; } /// /// Resize canvas with pixel to the left, right, top and bottom /// /// /// The color to fill with, or Color.Empty to take the default depending on the pixel format /// /// /// /// /// a new bitmap with the source copied on it public static Image ResizeCanvas(Image sourceImage, Color backgroundColor, int left, int right, int top, int bottom) { Bitmap newBitmap = CreateEmpty(sourceImage.Width + left + right, sourceImage.Height + top + bottom, sourceImage.PixelFormat, backgroundColor, sourceImage.HorizontalResolution, sourceImage.VerticalResolution); using (Graphics graphics = Graphics.FromImage(newBitmap)) { graphics.DrawImageUnscaled(sourceImage, left, top); } return newBitmap; } /// /// Wrapper for the more complex Resize, this resize could be used for e.g. Thumbnails /// /// /// true to maintain the aspect ratio /// /// /// public static Image ResizeImage(Image sourceImage, bool maintainAspectRatio, int newWidth, int newHeight) { Point throwAway; return ResizeImage(sourceImage, maintainAspectRatio, false, Color.Empty, newWidth, newHeight, out throwAway); } /// /// Count how many times the supplied color exists /// /// Image to count the pixels of /// Color to count/param> /// true if Alpha needs to be checked /// int with the number of pixels which have colorToCount public static int CountColor(Image sourceImage, Color colorToCount, bool includeAlpha) { int colors = 0; int toCount = colorToCount.ToArgb(); if (!includeAlpha) { toCount = toCount & 0xffffff; } using (BitmapBuffer bb = new BitmapBuffer(sourceImage, false)) { bb.Lock(); for (int y = 0; y < bb.Height; y++) { for (int x = 0; x < bb.Width; x++) { int bitmapcolor = bb.GetColorAt(x, y).ToArgb(); if (!includeAlpha) { bitmapcolor = bitmapcolor & 0xffffff; } if (bitmapcolor == toCount) { colors++; } } } bb.Unlock(); return colors; } } /// /// Scale the bitmap, keeping aspect ratio, but the canvas will always have the specified size. /// /// Image to scale /// true to maintain the aspect ratio /// The color to fill with, or Color.Empty to take the default depending on the pixel format /// new width /// new height /// a new bitmap with the specified size, the source-Image scaled to fit with aspect ratio locked public static Image ResizeImage(Image sourceImage, bool maintainAspectRatio, bool canvasUseNewSize, Color backgroundColor, int newWidth, int newHeight, out Point offset) { int destX = 0; int destY = 0; float nPercentW = 0; float nPercentH = 0; nPercentW = ((float)newWidth / (float)sourceImage.Width); nPercentH = ((float)newHeight / (float)sourceImage.Height); if (maintainAspectRatio) { if (nPercentW == 1) { nPercentW = nPercentH; if (canvasUseNewSize) { destX = Math.Max(0, System.Convert.ToInt32((newWidth - (sourceImage.Width * nPercentW)) / 2)); } } else if (nPercentH == 1) { nPercentH = nPercentW; if (canvasUseNewSize) { destY = Math.Max(0, System.Convert.ToInt32((newHeight - (sourceImage.Height * nPercentH)) / 2)); } } else if (nPercentH != 0 && nPercentH < nPercentW) { nPercentW = nPercentH; if (canvasUseNewSize) { destX = Math.Max(0, System.Convert.ToInt32((newWidth - (sourceImage.Width * nPercentW)) / 2)); } } else { nPercentH = nPercentW; if (canvasUseNewSize) { destY = Math.Max(0, System.Convert.ToInt32((newHeight - (sourceImage.Height * nPercentH)) / 2)); } } } offset = new Point(destX, destY); int destWidth = (int)(sourceImage.Width * nPercentW); int destHeight = (int)(sourceImage.Height * nPercentH); if (newWidth == 0) { newWidth = destWidth; } if (newHeight == 0) { newHeight = destHeight; } Image newImage = null; if (maintainAspectRatio && canvasUseNewSize) { newImage = CreateEmpty(newWidth, newHeight, sourceImage.PixelFormat, backgroundColor, sourceImage.HorizontalResolution, sourceImage.VerticalResolution); } else { newImage = CreateEmpty(destWidth, destHeight, sourceImage.PixelFormat, backgroundColor, sourceImage.HorizontalResolution, sourceImage.VerticalResolution); } using (Graphics graphics = Graphics.FromImage(newImage)) { graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; graphics.DrawImage(sourceImage, new Rectangle(destX, destY, destWidth, destHeight), new Rectangle(0, 0, sourceImage.Width, sourceImage.Height), GraphicsUnit.Pixel); } return newImage; } } }