/* * Greenshot - a free and open source screenshot tool * Copyright (C) 2007-2011 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.Imaging; using System.IO; using System.Drawing.Drawing2D; using GreenshotPlugin.UnmanagedHelpers; using GreenshotPlugin.Core; using IniFile; 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(); public static Image CreateThumbnail(Image image, int thumbWidth, int thumbHeight) { return CreateThumbnail(image, thumbWidth, thumbHeight, -1, -1); } 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 = (image as Bitmap).Clone(cropRectangle, image.PixelFormat); image.Dispose(); image = returnImage; return true; } } LOG.Warn("Can't crop a null/zero size image!"); return false; } private static Rectangle FindAutoCropRectangle(BitmapBuffer buffer, Point colorPoint) { Rectangle cropRectangle = Rectangle.Empty; Color referenceColor = buffer.GetColorAtWithoutAlpha(colorPoint.X,colorPoint.Y); Point min = new Point(int.MaxValue, int.MaxValue); Point max = new Point(int.MinValue, int.MinValue); if (conf.AutoCropDifference > 0) { for(int y = 0; y < buffer.Height; y++) { for(int x = 0; x < buffer.Width; x++) { Color currentColor = buffer.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) > conf.AutoCropDifference) { 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 < buffer.Height; y++) { for(int x = 0; x < buffer.Width; x++) { Color currentColor = buffer.GetColorAtWithoutAlpha(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(buffer.Width-1, buffer.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) { Rectangle cropRectangle = Rectangle.Empty; using (BitmapBuffer buffer = new BitmapBuffer((Bitmap)image, false)) { buffer.Lock(); 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)); // find biggest area foreach(Point checkPoint in checkPoints) { currentRectangle = FindAutoCropRectangle(buffer, checkPoint); if (currentRectangle.Width * currentRectangle.Height > cropRectangle.Width * cropRectangle.Height) { cropRectangle = currentRectangle; } } } return cropRectangle; } public static Bitmap LoadBitmap(string filename) { if (string.IsNullOrEmpty(filename)) { return null; } Bitmap fileBitmap = 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) { fileBitmap = CloneImageToBitmap(tmpImage); } } } catch (Exception vistaIconException) { LOG.Warn("Can't read icon from " + filename, vistaIconException); } if (fileBitmap == 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()) { fileBitmap = ImageHelper.CloneImageToBitmap(tmpImage); } } } catch (Exception iconException) { LOG.Warn("Can't read icon from " + filename, iconException); } } } if (fileBitmap == 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)) { fileBitmap = ImageHelper.CloneImageToBitmap(tmpImage); } } } return fileBitmap; } /// /// Clone the image to a bitmap /// /// Image to clone /// Bitmap public static Bitmap CloneImageToBitmap(Image srcImage) { Bitmap returnImage; int width = srcImage.Width; int height = srcImage.Height; float horizontalResolution = srcImage.HorizontalResolution; float verticalResolution = srcImage.VerticalResolution; PixelFormat pixelFormat = srcImage.PixelFormat; if (srcImage is Metafile) { pixelFormat = PixelFormat.Format32bppArgb; } // Make sure Greenshot supports the pixelformat, if not convert to one we support if (!isSupported(pixelFormat)) { pixelFormat = PixelFormat.Format24bppRgb; } returnImage = new Bitmap(width, height, pixelFormat); returnImage.SetResolution(horizontalResolution, verticalResolution); using (Graphics graphics = Graphics.FromImage(returnImage)) { if (Image.IsAlphaPixelFormat(pixelFormat)) { graphics.Clear(Color.Transparent); } else { graphics.Clear(Color.White); } graphics.DrawImageUnscaled(srcImage, 0, 0); } return returnImage; } /** * Checks if we support the supplied PixelFormat */ private static bool isSupported(PixelFormat pixelformat) { return (PixelFormat.Format32bppArgb.Equals(pixelformat)|| PixelFormat.Format32bppRgb.Equals(pixelformat) || PixelFormat.Format24bppRgb.Equals(pixelformat)); } // Based on: http://www.codeproject.com/KB/cs/IconExtractor.aspx // And a hint from: http://www.codeproject.com/KB/cs/IconLib.aspx public 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 /// Make the picture look like it's torn /// /// Bitmap to make torn edge off /// Changed bitmap public static Bitmap CreateTornEdge(Bitmap sourceBitmap) { Rectangle cropRectangle = new Rectangle(Point.Empty, sourceBitmap.Size); Bitmap returnImage = sourceBitmap.Clone(cropRectangle, PixelFormat.Format32bppArgb); try { returnImage.SetResolution(sourceBitmap.HorizontalResolution, sourceBitmap.VerticalResolution); } catch (Exception ex) { LOG.Warn("An exception occured while setting the resolution.", ex); } GraphicsPath path = new GraphicsPath(); Random random = new Random(); int regionWidth = 20; int regionHeight = 20; int HorizontalRegions = (int)(sourceBitmap.Width / regionWidth); int VerticalRegions = (int)(sourceBitmap.Height / regionHeight); int distance = 12; // Start Point previousEndingPoint = Point.Empty; Point newEndingPoint = Point.Empty; path.AddLine(new Point(sourceBitmap.Width, 0), Point.Empty); // Top for (int i = 0; i < HorizontalRegions; i++) { int x = (int)previousEndingPoint.X + regionWidth; int y = random.Next(1, distance); newEndingPoint = new Point(x, y); path.AddLine(previousEndingPoint, newEndingPoint); previousEndingPoint = newEndingPoint; } // end top newEndingPoint = new Point(sourceBitmap.Width, 0); path.AddLine(previousEndingPoint, newEndingPoint); previousEndingPoint = newEndingPoint; path.CloseFigure(); // Right for (int i = 0; i < VerticalRegions; i++) { int x = sourceBitmap.Width - random.Next(1, distance); int y = (int)previousEndingPoint.Y + regionHeight; newEndingPoint = new Point(x, y); path.AddLine(previousEndingPoint, newEndingPoint); previousEndingPoint = newEndingPoint; } // end right newEndingPoint = new Point(sourceBitmap.Width, sourceBitmap.Height); path.AddLine(previousEndingPoint, newEndingPoint); previousEndingPoint = newEndingPoint; path.CloseFigure(); // Bottom for (int i = 0; i < HorizontalRegions; i++) { int x = (int)previousEndingPoint.X - regionWidth; int y = sourceBitmap.Height - random.Next(1, distance); newEndingPoint = new Point(x, y); path.AddLine(previousEndingPoint, newEndingPoint); previousEndingPoint = newEndingPoint; } // end Bottom newEndingPoint = new Point(0, sourceBitmap.Height); path.AddLine(previousEndingPoint, newEndingPoint); previousEndingPoint = newEndingPoint; path.CloseFigure(); // Left for (int i = 0; i < VerticalRegions; i++) { int x = random.Next(1, distance); int y = (int)previousEndingPoint.Y - regionHeight; newEndingPoint = new Point(x, y); path.AddLine(previousEndingPoint, newEndingPoint); previousEndingPoint = newEndingPoint; } // end Left newEndingPoint = new Point(0, 0); path.AddLine(previousEndingPoint, newEndingPoint); previousEndingPoint = newEndingPoint; path.CloseFigure(); // Draw using (Graphics graphics = Graphics.FromImage(returnImage)) { graphics.SmoothingMode = SmoothingMode.HighQuality; graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; graphics.CompositingQuality = CompositingQuality.HighQuality; graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; if (Image.IsAlphaPixelFormat(returnImage.PixelFormat)) { // When using transparency we can't draw with Color.Transparency so we clear graphics.SetClip(path); graphics.Clear(Color.Transparent); } else { using (Brush brush = new SolidBrush(Color.White)) { 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; } /// /// Apply sourceBitmap with a blur to the targetGraphics /// /// Target to draw to /// Source to blur /// Area to blur /// Use full 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 void ApplyBlur(Graphics targetGraphics, Bitmap sourceBitmap, Rectangle rect, bool export, int blurRadius, double previewQuality, bool invert, Rectangle parentBounds) { Rectangle applyRect = CreateIntersectRectangle(sourceBitmap.Size, rect, invert); if (applyRect.Height <= 0 || applyRect.Width <= 0) { return; } // do nothing when nothing can be done! if (blurRadius < 1) { return; } Color nullColor = Color.White; if (sourceBitmap.PixelFormat == PixelFormat.Format32bppArgb) { nullColor = Color.Transparent; } using (BitmapBuffer bbbDest = new BitmapBuffer(sourceBitmap, applyRect)) { bbbDest.Lock(); using (BitmapBuffer bbbSrc = new BitmapBuffer(sourceBitmap, applyRect)) { bbbSrc.Lock(); Random rand = new Random(); int r = blurRadius; int[] w = CreateGaussianBlurRow(r); int wlen = w.Length; long[] waSums = new long[wlen]; long[] wcSums = new long[wlen]; long[] aSums = new long[wlen]; long[] bSums = new long[wlen]; long[] gSums = new long[wlen]; long[] rSums = new long[wlen]; for (int y = 0; y < applyRect.Height; ++y) { long waSum = 0; long wcSum = 0; long aSum = 0; long bSum = 0; long gSum = 0; long rSum = 0; for (int wx = 0; wx < wlen; ++wx) { int srcX = wx - r; waSums[wx] = 0; wcSums[wx] = 0; aSums[wx] = 0; bSums[wx] = 0; gSums[wx] = 0; rSums[wx] = 0; if (srcX >= 0 && srcX < bbbDest.Width) { for (int wy = 0; wy < wlen; ++wy) { int srcY = y + wy - r; if (srcY >= 0 && srcY < bbbDest.Height) { int[] colors = bbbSrc.GetColorArrayAt(srcX, srcY); int wp = w[wy]; waSums[wx] += wp; wp *= colors[0] + (colors[0] >> 7); wcSums[wx] += wp; wp >>= 8; aSums[wx] += wp * colors[0]; bSums[wx] += wp * colors[3]; gSums[wx] += wp * colors[2]; rSums[wx] += wp * colors[1]; } } int wwx = w[wx]; waSum += wwx * waSums[wx]; wcSum += wwx * wcSums[wx]; aSum += wwx * aSums[wx]; bSum += wwx * bSums[wx]; gSum += wwx * gSums[wx]; rSum += wwx * rSums[wx]; } } wcSum >>= 8; if (waSum == 0 || wcSum == 0) { if (parentBounds.Contains(applyRect.Left, applyRect.Top + y) ^ invert) { bbbDest.SetColorAt(0, y, nullColor); } } else { int alpha = (int)(aSum / waSum); int blue = (int)(bSum / wcSum); int green = (int)(gSum / wcSum); int red = (int)(rSum / wcSum); if (parentBounds.Contains(applyRect.Left, applyRect.Top + y) ^ invert) { bbbDest.SetColorAt(0, y, Color.FromArgb(alpha, red, green, blue)); } } 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]; bSums[i] = bSums[i + 1]; gSums[i] = gSums[i + 1]; rSums[i] = rSums[i + 1]; } waSum = 0; wcSum = 0; aSum = 0; bSum = 0; gSum = 0; rSum = 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]; bSum += wwx * bSums[wx]; gSum += wwx * gSums[wx]; rSum += wwx * rSums[wx]; } wx = wlen - 1; waSums[wx] = 0; wcSums[wx] = 0; aSums[wx] = 0; bSums[wx] = 0; gSums[wx] = 0; rSums[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 ((export || rand.NextDouble() < previewQuality) && srcY >= 0 && srcY < applyRect.Height) { int[] colors = bbbSrc.GetColorArrayAt(srcX, srcY); int wp = w[wy]; waSums[wx] += wp; wp *= colors[0] + (colors[0] >> 7); wcSums[wx] += wp; wp >>= 8; aSums[wx] += wp * (long)colors[0]; bSums[wx] += wp * (long)colors[3]; gSums[wx] += wp * (long)colors[2]; rSums[wx] += wp * (long)colors[1]; } } int wr = w[wx]; waSum += (long)wr * waSums[wx]; wcSum += (long)wr * wcSums[wx]; aSum += (long)wr * aSums[wx]; bSum += (long)wr * bSums[wx]; gSum += (long)wr * gSums[wx]; rSum += (long)wr * rSums[wx]; } wcSum >>= 8; if (waSum == 0 || wcSum == 0) { if (parentBounds.Contains(applyRect.Left + x, applyRect.Top + y) ^ invert) { bbbDest.SetColorAt(x, y, nullColor); } } else { int alpha = (int)(aSum / waSum); int blue = (int)(bSum / wcSum); int green = (int)(gSum / wcSum); int red = (int)(rSum / wcSum); if (parentBounds.Contains(applyRect.Left + x, applyRect.Top + y) ^ invert) { bbbDest.SetColorAt(x, y, Color.FromArgb(alpha, red, green, blue)); } } } } } bbbDest.DrawTo(targetGraphics, applyRect.Location); } } /** * 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(Bitmap sourceBitmap, float darkness, int shadowSize, PixelFormat targetPixelformat, out Point offset) { Bitmap newImage = new Bitmap(sourceBitmap.Width + (shadowSize * 2), sourceBitmap.Height + (shadowSize * 2), targetPixelformat); using (Graphics graphics = Graphics.FromImage(newImage)) { graphics.SmoothingMode = SmoothingMode.HighQuality; graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; graphics.CompositingQuality = CompositingQuality.HighQuality; graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; if (Image.IsAlphaPixelFormat(targetPixelformat)) { graphics.Clear(Color.Transparent); } else { graphics.Clear(Color.White); } ImageAttributes ia = new ImageAttributes(); ColorMatrix cm = new ColorMatrix(); cm.Matrix00 = 0; cm.Matrix11 = 0; cm.Matrix22 = 0; cm.Matrix33 = darkness; ia.SetColorMatrix(cm); // Draw "shadow" offsetted graphics.DrawImage(sourceBitmap, new Rectangle(new Point(shadowSize, shadowSize), sourceBitmap.Size), 0, 0, sourceBitmap.Width, sourceBitmap.Height, GraphicsUnit.Pixel, ia); // blur "shadow", apply to whole new image Rectangle applyRectangle = new Rectangle(Point.Empty, newImage.Size); ApplyBlur(graphics, newImage, applyRectangle, true, shadowSize, 1d, false, applyRectangle); // draw original offset = new Point(shadowSize - 2, shadowSize - 2); graphics.DrawImage(sourceBitmap, offset); } return newImage; } } }