public Bitmap ToGrayscale(Bitmap originalBmp, bool parallel) { FastBitmap fastOriginal = new FastBitmap(originalBmp); int h = originalBmp.Height, w = originalBmp.Width; if (parallel) { Parallel.For(0, h, (y) => { Parallel.For(0, w, (x) => { Color c = fastOriginal.GetColor(x, y); int gray = (byte)(c.R * 0.11 + c.G * 0.59 + c.R * 0.3); fastOriginal.SetColor(x, y, Color.FromArgb(gray, gray, gray)); }); }); } else { for (int y = 0; y < originalBmp.Height; y++) { for (int x = 0; x < originalBmp.Width; x++) { Color c = fastOriginal.GetColor(x, y); int gray = (byte)(c.R * 0.11 + c.G * 0.59 + c.R * 0.3); fastOriginal.SetColor(x, y, Color.FromArgb(gray, gray, gray)); } } } fastOriginal.Dispose(); return(originalBmp); }
public Bitmap convertToGray(Bitmap originalBmp) { FastBitmap fastOriginal = new FastBitmap(originalBmp); int h = originalBmp.Height, w = originalBmp.Width; for (int y = 0; y < originalBmp.Height; y++) { for (int x = 0; x < originalBmp.Width; x++) { Color c = fastOriginal.GetColor(x, y); int gray = (byte)(c.R * 0.11 + c.G * 0.59 + c.R * 0.3); fastOriginal.SetColor(x, y, Color.FromArgb(gray, gray, gray)); } } fastOriginal.Dispose(); return(originalBmp); }
/// <summary>Implements a b&w + hue-based transformation for an image.</summary> /// <param name="original">The original image.</param> /// <param name="selectedPixels">The location in the original image of the selected pixels for hue /// comparison.</param> /// <param name="epsilon">Allowed hue variation from selected pixels.</param> /// <param name="paths">GraphicPath instances demarcating regions containing possible pixels to be /// left in color.</param> /// <param name="parallel">Whether to run in parallel.</param> /// <returns>The new Bitmap.</returns> public Bitmap Colorize(Bitmap original, List <Point> selectedPixels, int epsilon, List <GraphicsPath> paths, bool parallel) { // Create a new bitmap with the same size as the original int width = original.Width, height = original.Height; Bitmap colorizedImage = new Bitmap(width, height); // Optimization: For every GraphicsPath, get a bounding rectangle. This allows for quickly // ruling out pixels that are definitely not containing within the selected region. Rectangle [] pathsBounds = null; if (paths != null && paths.Count > 0) { pathsBounds = new Rectangle[paths.Count]; for (int i = 0; i < pathsBounds.Length; i++) { pathsBounds[i] = Rectangle.Ceiling(paths[i].GetBounds()); } } // Optimization: Hit-testing against GraphicPaths is relatively slow. Hit testing // against rectangles is very fast. As such, appromixate the area of the GraphicsPath // with rectangles which can be hit tested against instead of the paths. Not quite // as accurate, but much faster. List <RectangleF[]> compositions = null; if (paths != null && paths.Count > 0) { compositions = new List <RectangleF[]>(paths.Count); using (Matrix m = new Matrix()) { for (int i = 0; i < paths.Count; i++) { using (Region r = new Region(paths[i])) compositions.Add(r.GetRegionScans(m)); } } } // Use FastBitmap instances to provide unsafe/faster access to the pixels // in the original and in the new images using (FastBitmap fastColorizedImage = new FastBitmap(colorizedImage)) using (FastBitmap fastOriginalImage = new FastBitmap(original)) { // Extract the selected hues from the selected pixels List <float> selectedHues = new List <float>(selectedPixels.Count); foreach (Point p in selectedPixels) { selectedHues.Add(fastOriginalImage.GetColor(p.X, p.Y).GetHue()); } // For progress update purposes, figure out how many pixels there // are in total, and how many constitute 1% so that we can raise // events after every additional 1% has been completed. long totalPixels = height * width; long pixelsPerProgressUpdate = totalPixels / 100; if (pixelsPerProgressUpdate == 0) { pixelsPerProgressUpdate = 1; } long pixelsProcessed = 0; // Pixels close to the selected hue but not close enough may be // left partially desaturated. The saturation window determines // what pixels fall into that range. const int maxSaturationWindow = 10; int saturationWindow = Math.Min(maxSaturationWindow, epsilon); // Separated out the body of the loop just to make it easier // to switch between sequential and parallel for demo purposes Action <int> processRow = y => { for (int x = 0; x < width; x++) { // Get the color/hue of th epixel Color c = fastOriginalImage.GetColor(x, y); float pixelHue = c.GetHue(); // Use hit-testing to determine if the pixel is in the selected region. bool pixelInSelectedRegion = false; // First, if there are no paths, by definition it is in the selected // region, since the whole image is then selected if (paths == null || paths.Count == 0) { pixelInSelectedRegion = true; } else { // For each path, first see if the pixel is within the bounding // rectangle; if it's not, it's not in the selected region. Point p = new Point(x, y); for (int i = 0; i < pathsBounds.Length && !pixelInSelectedRegion; i++) { if (pathsBounds[i].Contains(p)) { // The pixel is within a bounding rectangle, so now // see if it's within the composition rectangles // approximating the region. foreach (RectangleF bound in compositions[i]) { if (bound.Contains(x, y)) { // If it is, it's in the region. pixelInSelectedRegion = true; break; } } } } } // Now that we know whether a pixel is in the region, // we can figure out what to do with it. If the pixel // is not in the selected region, it needs to be converted // to grayscale. bool useGrayscale = true; if (pixelInSelectedRegion) { // If it is in the selected region, get the color hue's distance // from each target hue. If that distance is less than the user-selected // hue variation limit, leave it in color. If it's greater than the // variation limit, but within the saturation window of the limit, // desaturate it proportionally to the distance from the limit. foreach (float selectedHue in selectedHues) { // A hue wheel is 360 degrees. If you pick two points on a wheel, there // will be two distances between them, depending on which way you go around // the wheel from one to the other (unless they're exactly opposite from // each other on the wheel, the two distances will be different). We always // want to do our work based on the smaller of the two distances (e.g. a hue // with the value 359 is very, very close to a hue with the value 1). So, // we take the absolute value of the difference between the two hues. If that // distance is 180 degrees, then both distances are the same, so it doesn't // matter which we go with. If that difference is less than 180 degrees, // we know this must be the smaller of the two distances, since the sum of the // two distances must add up to 360. If, however, it's larger than 180, it's the // longer distance, so to get the shorter one, we have to subtract it from 360. float distance = Math.Abs(pixelHue - selectedHue); if (distance > 180) { distance = 360 - distance; } if (distance <= epsilon) { useGrayscale = false; break; } else if ((distance - epsilon) / saturationWindow < 1.0f) { useGrayscale = false; c = ColorFromHsb( pixelHue, c.GetSaturation() * (1.0f - ((distance - epsilon) / maxSaturationWindow)), c.GetBrightness()); break; } } } // Set the pixel color into the new image if (useGrayscale) { c = ToGrayscale(c); } fastColorizedImage.SetColor(x, y, c); } // Notify any listeners of our progress, if enough progress has been made Interlocked.Add(ref pixelsProcessed, width); OnProgressChanged((int)(100 * pixelsProcessed / (double)totalPixels)); }; // Copy over every single pixel, and possibly transform it in the process if (parallel) { Parallel.For(0, height, processRow); } else { for (int y = 0; y < height; y++) { processRow(y); } } } // We're done creating the image. Return it. return(colorizedImage); }
/// <summary>Implements a b&w + hue-based transformation for an image.</summary> /// <param name="original">The original image.</param> /// <param name="selectedPixels">The location in the original image of the selected pixels for hue /// comparison.</param> /// <param name="epsilon">Allowed hue variation from selected pixels.</param> /// <param name="paths">GraphicPath instances demarcating regions containing possible pixels to be /// left in color.</param> /// <param name="parallel">Whether to run in parallel.</param> /// <returns>The new Bitmap.</returns> public Bitmap Colorize(Bitmap original, List<Point> selectedPixels, int epsilon, List<GraphicsPath> paths, bool parallel) { // Create a new bitmap with the same size as the original int width = original.Width, height = original.Height; Bitmap colorizedImage = new Bitmap(width, height); // Optimization: For every GraphicsPath, get a bounding rectangle. This allows for quickly // ruling out pixels that are definitely not containing within the selected region. Rectangle [] pathsBounds = null; if (paths != null && paths.Count > 0) { pathsBounds = new Rectangle[paths.Count]; for(int i=0; i<pathsBounds.Length; i++) { pathsBounds[i] = Rectangle.Ceiling(paths[i].GetBounds()); } } // Optimization: Hit-testing against GraphicPaths is relatively slow. Hit testing // against rectangles is very fast. As such, appromixate the area of the GraphicsPath // with rectangles which can be hit tested against instead of the paths. Not quite // as accurate, but much faster. List<RectangleF[]> compositions = null; if (paths != null && paths.Count > 0) { compositions = new List<RectangleF[]>(paths.Count); using (Matrix m = new Matrix()) { for(int i=0; i<paths.Count; i++) { using (Region r = new Region(paths[i])) compositions.Add(r.GetRegionScans(m)); } } } // Use FastBitmap instances to provide unsafe/faster access to the pixels // in the original and in the new images using (FastBitmap fastColorizedImage = new FastBitmap(colorizedImage)) using (FastBitmap fastOriginalImage = new FastBitmap(original)) { // Extract the selected hues from the selected pixels List<float> selectedHues = new List<float>(selectedPixels.Count); foreach (Point p in selectedPixels) { selectedHues.Add(fastOriginalImage.GetColor(p.X, p.Y).GetHue()); } // For progress update purposes, figure out how many pixels there // are in total, and how many constitute 1% so that we can raise // events after every additional 1% has been completed. long totalPixels = height * width; long pixelsPerProgressUpdate = totalPixels / 100; if (pixelsPerProgressUpdate == 0) pixelsPerProgressUpdate = 1; long pixelsProcessed = 0; // Pixels close to the selected hue but not close enough may be // left partially desaturated. The saturation window determines // what pixels fall into that range. const int maxSaturationWindow = 10; int saturationWindow = Math.Min(maxSaturationWindow, epsilon); // Separated out the body of the loop just to make it easier // to switch between sequential and parallel for demo purposes Action<int> processRow = y => { for (int x = 0; x < width; x++) { // Get the color/hue of th epixel Color c = fastOriginalImage.GetColor(x, y); float pixelHue = c.GetHue(); // Use hit-testing to determine if the pixel is in the selected region. bool pixelInSelectedRegion = false; // First, if there are no paths, by definition it is in the selected // region, since the whole image is then selected if (paths == null || paths.Count == 0) pixelInSelectedRegion = true; else { // For each path, first see if the pixel is within the bounding // rectangle; if it's not, it's not in the selected region. Point p = new Point(x, y); for (int i = 0; i < pathsBounds.Length && !pixelInSelectedRegion; i++) { if (pathsBounds[i].Contains(p)) { // The pixel is within a bounding rectangle, so now // see if it's within the composition rectangles // approximating the region. foreach (RectangleF bound in compositions[i]) { if (bound.Contains(x, y)) { // If it is, it's in the region. pixelInSelectedRegion = true; break; } } } } } // Now that we know whether a pixel is in the region, // we can figure out what to do with it. If the pixel // is not in the selected region, it needs to be converted // to grayscale. bool useGrayscale = true; if (pixelInSelectedRegion) { // If it is in the selected region, get the color hue's distance // from each target hue. If that distance is less than the user-selected // hue variation limit, leave it in color. If it's greater than the // variation limit, but within the saturation window of the limit, // desaturate it proportionally to the distance from the limit. foreach (float selectedHue in selectedHues) { // A hue wheel is 360 degrees. If you pick two points on a wheel, there // will be two distances between them, depending on which way you go around // the wheel from one to the other (unless they're exactly opposite from // each other on the wheel, the two distances will be different). We always // want to do our work based on the smaller of the two distances (e.g. a hue // with the value 359 is very, very close to a hue with the value 1). So, // we take the absolute value of the difference between the two hues. If that // distance is 180 degrees, then both distances are the same, so it doesn't // matter which we go with. If that difference is less than 180 degrees, // we know this must be the smaller of the two distances, since the sum of the // two distances must add up to 360. If, however, it's larger than 180, it's the // longer distance, so to get the shorter one, we have to subtract it from 360. float distance = Math.Abs(pixelHue - selectedHue); if (distance > 180) distance = 360 - distance; if (distance <= epsilon) { useGrayscale = false; break; } else if ((distance - epsilon) / saturationWindow < 1.0f) { useGrayscale = false; c = ColorFromHsb( pixelHue, c.GetSaturation() * (1.0f - ((distance - epsilon) / maxSaturationWindow)), c.GetBrightness()); break; } } } // Set the pixel color into the new image if (useGrayscale) c = ToGrayscale(c); fastColorizedImage.SetColor(x, y, c); } // Notify any listeners of our progress, if enough progress has been made Interlocked.Add(ref pixelsProcessed, width); OnProgressChanged((int)(100 * pixelsProcessed / (double)totalPixels)); }; // Copy over every single pixel, and possibly transform it in the process if (parallel) { Parallel.For(0, height, processRow); } else { for (int y = 0; y < height; y++) processRow(y); } } // We're done creating the image. Return it. return colorizedImage; }
double MeasureError(Rectangle blockRect, FastBitmap block) { double error = 0; for (int y = 0; y < blockRect.Height; y++) { for (int x = 0; x < blockRect.Width; x++) { Color m = _main.GetColor(blockRect.X + x, blockRect.Y + y); Color b = block.GetColor(x, y); double dist = Math.Sqrt( (m.R - b.R) * (m.R - b.R) + (m.G - b.G) * (m.G - b.G) + (m.B - b.B) * (m.B - b.B)); error += dist; } } return error; }
public bool IsMatch(FastBitmap bitmap, int tolerance) { Color c1 = bitmap.GetColor(X, Y); Color c2 = this.ToColor(); if (Math.Abs(c1.R - c2.R) > tolerance || Math.Abs(c1.G - c2.G) > tolerance || Math.Abs(c1.B - c2.B) > tolerance) return false; return true; }
/// <summary> /// Matches until difference is more than total tolerance. /// </summary> /// <param name="fast_bitmap"></param> /// <param name="sub_rect"></param> /// <returns></returns> public bool IsIn(FastBitmap fast_bitmap, Point basepoint, int tolerance) { int total = 0; for (int y = 0; y < this.Height; y++) { for (int x = 0; x < this.Width; x++) { total += BitmapAnalyzer.AbsoluteDiff(this.GetColor(x, y), fast_bitmap.GetColor(basepoint.X + x, basepoint.Y + y)); if (total > tolerance) return false; } } return true; }
public static bool IsMatch(FastBitmap fast_bitmap1, FastBitmap fast_bitmap2, int total_tolerance) { if (fast_bitmap1.Width != fast_bitmap2.Width || fast_bitmap1.Height != fast_bitmap2.Height) return false; int total = 0; for (int y = 0; y < fast_bitmap1.Height; y++) { for (int x = 0; x < fast_bitmap1.Width; x++) { total += BitmapAnalyzer.AbsoluteDiff(fast_bitmap1.GetColor(x, y), fast_bitmap2.GetColor(x, y)); if (total > total_tolerance) return false; } } return true; }