public void ThenShouldReturnPointWithX2AndY2GivenCenterPoint25AndNegative25AndPointToRotateXAndY( int pointToRotateX, int pointToRotateY, int expectedX, int expectedY) { // Arrange var pointToRotate = new Point(pointToRotateX, pointToRotateY); // Act var rotatePoint = ImageMaths.RotatePoint(pointToRotate, 90, new Point(25, -25)); // Assert Assert.That(rotatePoint, Is.EqualTo(new Point(expectedX, expectedY))); }
/// <summary> /// The process image. /// </summary> /// <param name="factory"> /// The factory. /// </param> /// <returns> /// The <see cref="Image"/>. /// </returns> /// <exception cref="ImageProcessingException"> /// </exception> public Image ProcessImage(ImageFactory factory) { Bitmap cyan = null; Bitmap magenta = null; Bitmap yellow = null; Bitmap keyline = null; Bitmap newImage = null; Image image = factory.Image; try { int width = image.Width; int height = image.Height; // Angles taken from Wikipedia page. float cyanAngle = 15f; float magentaAngle = 75f; float yellowAngle = 0f; float keylineAngle = 45f; int diameter = 4; float multiplier = 4 * (float)Math.Sqrt(2); // Cyan color sampled from Wikipedia page. Brush cyanBrush = new SolidBrush(Color.FromArgb(0, 153, 239)); Brush magentaBrush = Brushes.Magenta; Brush yellowBrush = Brushes.Yellow; Brush keylineBrush; // Create our images. cyan = new Bitmap(width, height); magenta = new Bitmap(width, height); yellow = new Bitmap(width, height); keyline = new Bitmap(width, height); newImage = new Bitmap(width, height); // Ensure the correct resolution is set. cyan.SetResolution(image.HorizontalResolution, image.VerticalResolution); magenta.SetResolution(image.HorizontalResolution, image.VerticalResolution); yellow.SetResolution(image.HorizontalResolution, image.VerticalResolution); keyline.SetResolution(image.HorizontalResolution, image.VerticalResolution); newImage.SetResolution(image.HorizontalResolution, image.VerticalResolution); // Check bounds against this. Rectangle rectangle = new Rectangle(0, 0, width, height); using (Graphics graphicsCyan = Graphics.FromImage(cyan)) using (Graphics graphicsMagenta = Graphics.FromImage(magenta)) using (Graphics graphicsYellow = Graphics.FromImage(yellow)) using (Graphics graphicsKeyline = Graphics.FromImage(keyline)) { // Ensure cleared out. graphicsCyan.Clear(Color.Transparent); graphicsMagenta.Clear(Color.Transparent); graphicsYellow.Clear(Color.Transparent); graphicsKeyline.Clear(Color.Transparent); // This is too slow. The graphics object can't be called within a parallel // loop so we have to do it old school. :( using (FastBitmap sourceBitmap = new FastBitmap(image)) { for (int y = -height * 2; y < height * 2; y += diameter) { for (int x = -width * 2; x < width * 2; x += diameter) { Color color; CmykColor cmykColor; float brushWidth; // Cyan Point rotatedPoint = ImageMaths.RotatePoint(new Point(x, y), cyanAngle); int angledX = rotatedPoint.X; int angledY = rotatedPoint.Y; if (rectangle.Contains(new Point(angledX, angledY))) { color = sourceBitmap.GetPixel(angledX, angledY); cmykColor = color; brushWidth = diameter * (cmykColor.C / 255f) * multiplier; graphicsCyan.FillEllipse(cyanBrush, angledX, angledY, brushWidth, brushWidth); } // Magenta rotatedPoint = ImageMaths.RotatePoint(new Point(x, y), magentaAngle); angledX = rotatedPoint.X; angledY = rotatedPoint.Y; if (rectangle.Contains(new Point(angledX, angledY))) { color = sourceBitmap.GetPixel(angledX, angledY); cmykColor = color; brushWidth = diameter * (cmykColor.M / 255f) * multiplier; graphicsMagenta.FillEllipse(magentaBrush, angledX, angledY, brushWidth, brushWidth); } // Yellow rotatedPoint = ImageMaths.RotatePoint(new Point(x, y), yellowAngle); angledX = rotatedPoint.X; angledY = rotatedPoint.Y; if (rectangle.Contains(new Point(angledX, angledY))) { color = sourceBitmap.GetPixel(angledX, angledY); cmykColor = color; brushWidth = diameter * (cmykColor.Y / 255f) * multiplier; graphicsYellow.FillEllipse(yellowBrush, angledX, angledY, brushWidth, brushWidth); } // Keyline rotatedPoint = ImageMaths.RotatePoint(new Point(x, y), keylineAngle); angledX = rotatedPoint.X; angledY = rotatedPoint.Y; if (rectangle.Contains(new Point(angledX, angledY))) { color = sourceBitmap.GetPixel(angledX, angledY); cmykColor = color; brushWidth = diameter * (cmykColor.K / 255f) * multiplier; // Just using blck is too dark. keylineBrush = new SolidBrush(CmykColor.FromCmykColor(0, 0, 0, cmykColor.K)); graphicsKeyline.FillEllipse(keylineBrush, angledX, angledY, brushWidth, brushWidth); } } } } // Set our white background. using (Graphics graphics = Graphics.FromImage(newImage)) { graphics.Clear(Color.White); } // Blend the colors now to mimic adaptive blending. using (FastBitmap cyanBitmap = new FastBitmap(cyan)) using (FastBitmap magentaBitmap = new FastBitmap(magenta)) using (FastBitmap yellowBitmap = new FastBitmap(yellow)) using (FastBitmap keylineBitmap = new FastBitmap(keyline)) using (FastBitmap destinationBitmap = new FastBitmap(newImage)) { Parallel.For( 0, height, y => { for (int x = 0; x < width; x++) { // ReSharper disable AccessToDisposedClosure Color cyanPixel = cyanBitmap.GetPixel(x, y); Color magentaPixel = magentaBitmap.GetPixel(x, y); Color yellowPixel = yellowBitmap.GetPixel(x, y); Color keylinePixel = keylineBitmap.GetPixel(x, y); CmykColor blended = cyanPixel.AddAsCmykColor(magentaPixel, yellowPixel, keylinePixel); destinationBitmap.SetPixel(x, y, blended); // ReSharper restore AccessToDisposedClosure } }); } } cyan.Dispose(); magenta.Dispose(); yellow.Dispose(); keyline.Dispose(); image.Dispose(); image = newImage; } catch (Exception ex) { if (cyan != null) { cyan.Dispose(); } if (magenta != null) { magenta.Dispose(); } if (yellow != null) { yellow.Dispose(); } if (keyline != null) { keyline.Dispose(); } if (newImage != null) { newImage.Dispose(); } throw new ImageProcessingException("Error processing image with " + this.GetType().Name, ex); } return(image); }
/// <summary> /// Applies the halftone filter. /// </summary> /// <param name="source"> /// The <see cref="Bitmap"/> to apply the filter to. /// </param> /// <returns> /// The <see cref="Bitmap"/> with the filter applied. /// </returns> public Bitmap ApplyFilter(Bitmap source) { // TODO: Make this class implement an interface? Bitmap padded = null; Bitmap cyan = null; Bitmap magenta = null; Bitmap yellow = null; Bitmap keyline = null; Bitmap newImage = null; try { int sourceWidth = source.Width; int sourceHeight = source.Height; int width = source.Width + this.distance; int height = source.Height + this.distance; // Draw a slightly larger image, flipping the top/left pixels to prevent // jagged edge of output. padded = new Bitmap(width, height, PixelFormat.Format32bppPArgb); padded.SetResolution(source.HorizontalResolution, source.VerticalResolution); using (Graphics graphicsPadded = Graphics.FromImage(padded)) { graphicsPadded.Clear(Color.White); Rectangle destinationRectangle = new Rectangle(0, 0, sourceWidth + this.distance, source.Height + this.distance); using (TextureBrush tb = new TextureBrush(source)) { tb.WrapMode = WrapMode.TileFlipXY; tb.TranslateTransform(this.distance, this.distance); graphicsPadded.FillRectangle(tb, destinationRectangle); } } // Calculate min and max widths/heights. Rectangle rotatedBounds = this.GetBoundingRectangle(width, height); int minY = -(rotatedBounds.Height + height); int maxY = rotatedBounds.Height + height; int minX = -(rotatedBounds.Width + width); int maxX = rotatedBounds.Width + width; Point center = Point.Empty; // Yellow oversaturates the output. int offset = this.distance; float yellowMultiplier = this.distance * 1.587f; float magentaMultiplier = this.distance * 2.176f; float multiplier = this.distance * 2.2f; float max = this.distance * (float)Math.Sqrt(2); float magentaMax = this.distance * (float)Math.Sqrt(1.4545); // Bump up the keyline max so that black looks black. float keylineMax = max * (float)Math.Sqrt(2); // Color sampled process colours from Wikipedia pages. // Keyline brush is declared separately. Brush cyanBrush = new SolidBrush(Color.FromArgb(0, 183, 235)); Brush magentaBrush = new SolidBrush(Color.FromArgb(255, 0, 144)); Brush yellowBrush = new SolidBrush(Color.FromArgb(255, 239, 0)); // Create our images. cyan = new Bitmap(width, height, PixelFormat.Format32bppPArgb); magenta = new Bitmap(width, height, PixelFormat.Format32bppPArgb); yellow = new Bitmap(width, height, PixelFormat.Format32bppPArgb); keyline = new Bitmap(width, height, PixelFormat.Format32bppPArgb); newImage = new Bitmap(sourceWidth, sourceHeight, PixelFormat.Format32bppPArgb); // Ensure the correct resolution is set. cyan.SetResolution(source.HorizontalResolution, source.VerticalResolution); magenta.SetResolution(source.HorizontalResolution, source.VerticalResolution); yellow.SetResolution(source.HorizontalResolution, source.VerticalResolution); keyline.SetResolution(source.HorizontalResolution, source.VerticalResolution); newImage.SetResolution(source.HorizontalResolution, source.VerticalResolution); // Check bounds against this. Rectangle rectangle = new Rectangle(0, 0, width, height); using (Graphics graphicsCyan = Graphics.FromImage(cyan)) using (Graphics graphicsMagenta = Graphics.FromImage(magenta)) using (Graphics graphicsYellow = Graphics.FromImage(yellow)) using (Graphics graphicsKeyline = Graphics.FromImage(keyline)) { // Set the quality properties. graphicsCyan.PixelOffsetMode = PixelOffsetMode.Half; graphicsMagenta.PixelOffsetMode = PixelOffsetMode.Half; graphicsYellow.PixelOffsetMode = PixelOffsetMode.Half; graphicsKeyline.PixelOffsetMode = PixelOffsetMode.Half; graphicsCyan.SmoothingMode = SmoothingMode.AntiAlias; graphicsMagenta.SmoothingMode = SmoothingMode.AntiAlias; graphicsYellow.SmoothingMode = SmoothingMode.AntiAlias; graphicsKeyline.SmoothingMode = SmoothingMode.AntiAlias; graphicsCyan.CompositingQuality = CompositingQuality.HighQuality; graphicsMagenta.CompositingQuality = CompositingQuality.HighQuality; graphicsYellow.CompositingQuality = CompositingQuality.HighQuality; graphicsKeyline.CompositingQuality = CompositingQuality.HighQuality; // Set up the canvas. graphicsCyan.Clear(Color.White); graphicsMagenta.Clear(Color.White); graphicsYellow.Clear(Color.White); graphicsKeyline.Clear(Color.White); // This is too slow. The graphics object can't be called within a parallel // loop so we have to do it old school. :( using (FastBitmap sourceBitmap = new FastBitmap(padded)) { for (int y = minY; y < maxY; y += offset) { for (int x = minX; x < maxX; x += offset) { Color color; CmykColor cmykColor; float brushWidth; // Cyan Point rotatedPoint = ImageMaths.RotatePoint(new Point(x, y), this.cyanAngle, center); int angledX = rotatedPoint.X; int angledY = rotatedPoint.Y; if (rectangle.Contains(new Point(angledX, angledY))) { color = sourceBitmap.GetPixel(angledX, angledY); cmykColor = color; brushWidth = Math.Min((cmykColor.C / 100f) * multiplier, max); graphicsCyan.FillEllipse(cyanBrush, angledX, angledY, brushWidth, brushWidth); } // Magenta rotatedPoint = ImageMaths.RotatePoint(new Point(x, y), this.magentaAngle, center); angledX = rotatedPoint.X; angledY = rotatedPoint.Y; if (rectangle.Contains(new Point(angledX, angledY))) { color = sourceBitmap.GetPixel(angledX, angledY); cmykColor = color; brushWidth = Math.Min((cmykColor.M / 100f) * magentaMultiplier, magentaMax); graphicsMagenta.FillEllipse(magentaBrush, angledX, angledY, brushWidth, brushWidth); } // Yellow rotatedPoint = ImageMaths.RotatePoint(new Point(x, y), this.yellowAngle, center); angledX = rotatedPoint.X; angledY = rotatedPoint.Y; if (rectangle.Contains(new Point(angledX, angledY))) { color = sourceBitmap.GetPixel(angledX, angledY); cmykColor = color; brushWidth = Math.Min((cmykColor.Y / 100f) * yellowMultiplier, max); graphicsYellow.FillEllipse(yellowBrush, angledX, angledY, brushWidth, brushWidth); } // Keyline rotatedPoint = ImageMaths.RotatePoint(new Point(x, y), this.keylineAngle, center); angledX = rotatedPoint.X; angledY = rotatedPoint.Y; if (rectangle.Contains(new Point(angledX, angledY))) { color = sourceBitmap.GetPixel(angledX, angledY); cmykColor = color; brushWidth = Math.Min((cmykColor.K / 100f) * multiplier, keylineMax); // Just using black is too dark. Brush keylineBrush = new SolidBrush(CmykColor.FromCmykColor(0, 0, 0, cmykColor.K)); graphicsKeyline.FillEllipse(keylineBrush, angledX, angledY, brushWidth, brushWidth); } } } } // Set our white background. using (Graphics graphics = Graphics.FromImage(newImage)) { graphics.Clear(Color.White); } // Blend the colors now to mimic adaptive blending. using (FastBitmap cyanBitmap = new FastBitmap(cyan)) using (FastBitmap magentaBitmap = new FastBitmap(magenta)) using (FastBitmap yellowBitmap = new FastBitmap(yellow)) using (FastBitmap keylineBitmap = new FastBitmap(keyline)) using (FastBitmap destinationBitmap = new FastBitmap(newImage)) { Parallel.For( offset, height, y => { for (int x = offset; x < width; x++) { // ReSharper disable AccessToDisposedClosure Color cyanPixel = cyanBitmap.GetPixel(x, y); Color magentaPixel = magentaBitmap.GetPixel(x, y); Color yellowPixel = yellowBitmap.GetPixel(x, y); Color keylinePixel = keylineBitmap.GetPixel(x, y); // Negate the offset. int xBack = x - offset; int yBack = y - offset; CmykColor blended = cyanPixel.AddAsCmykColor(magentaPixel, yellowPixel, keylinePixel); if (rectangle.Contains(new Point(xBack, yBack))) { destinationBitmap.SetPixel(xBack, yBack, blended); } // ReSharper restore AccessToDisposedClosure } }); } } padded.Dispose(); cyan.Dispose(); magenta.Dispose(); yellow.Dispose(); keyline.Dispose(); source.Dispose(); source = newImage; } catch { if (padded != null) { padded.Dispose(); } if (cyan != null) { cyan.Dispose(); } if (magenta != null) { magenta.Dispose(); } if (yellow != null) { yellow.Dispose(); } if (keyline != null) { keyline.Dispose(); } if (newImage != null) { newImage.Dispose(); } } return(source); }
/// <summary> /// Applies the halftone filter. /// </summary> /// <param name="source"> /// The <see cref="Bitmap"/> to apply the filter to. /// </param> /// <returns> /// The <see cref="Bitmap"/> with the filter applied. /// </returns> public Bitmap ApplyFilter(Bitmap source) { // TODO: Make this class implement an interface? Bitmap cyan = null; Bitmap magenta = null; Bitmap yellow = null; Bitmap keyline = null; Bitmap newImage = null; try { int width = source.Width; int height = source.Height; // Calculate min and max widths/heights. Rectangle rotatedBounds = this.GetBoundingRectangle(width, height); int minY = -(rotatedBounds.Height + height); int maxY = rotatedBounds.Height + height; int minX = -(rotatedBounds.Width + width); int maxX = rotatedBounds.Width + width; Point center = Point.Empty; // Yellow oversaturates the output. const float YellowMultiplier = 4; float multiplier = YellowMultiplier * (float)Math.Sqrt(2); float max = this.distance; // Bump up the keyline max so that black looks black. float keylineMax = max + ((float)Math.Sqrt(2) * 1.44f); // Color sampled process colours from Wikipedia pages. // Keyline brush is declared separately. Brush cyanBrush = new SolidBrush(Color.FromArgb(0, 183, 235)); Brush magentaBrush = new SolidBrush(Color.FromArgb(255, 0, 144)); Brush yellowBrush = new SolidBrush(Color.FromArgb(255, 239, 0)); // Create our images. cyan = new Bitmap(width, height); magenta = new Bitmap(width, height); yellow = new Bitmap(width, height); keyline = new Bitmap(width, height); newImage = new Bitmap(width, height); // Ensure the correct resolution is set. cyan.SetResolution(source.HorizontalResolution, source.VerticalResolution); magenta.SetResolution(source.HorizontalResolution, source.VerticalResolution); yellow.SetResolution(source.HorizontalResolution, source.VerticalResolution); keyline.SetResolution(source.HorizontalResolution, source.VerticalResolution); newImage.SetResolution(source.HorizontalResolution, source.VerticalResolution); // Check bounds against this. Rectangle rectangle = new Rectangle(0, 0, width, height); using (Graphics graphicsCyan = Graphics.FromImage(cyan)) using (Graphics graphicsMagenta = Graphics.FromImage(magenta)) using (Graphics graphicsYellow = Graphics.FromImage(yellow)) using (Graphics graphicsKeyline = Graphics.FromImage(keyline)) { // Set the quality properties. graphicsCyan.PixelOffsetMode = PixelOffsetMode.HighQuality; graphicsMagenta.PixelOffsetMode = PixelOffsetMode.HighQuality; graphicsYellow.PixelOffsetMode = PixelOffsetMode.HighQuality; graphicsKeyline.PixelOffsetMode = PixelOffsetMode.HighQuality; // Set up the canvas. graphicsCyan.Clear(Color.Transparent); graphicsMagenta.Clear(Color.Transparent); graphicsYellow.Clear(Color.Transparent); graphicsKeyline.Clear(Color.Transparent); int d = this.distance; // This is too slow. The graphics object can't be called within a parallel // loop so we have to do it old school. :( using (FastBitmap sourceBitmap = new FastBitmap(source)) { for (int y = minY; y < maxY; y += d) { for (int x = minX; x < maxX; x += d) { Color color; CmykColor cmykColor; float brushWidth; int offsetX = x - (d >> 1); int offsetY = y - (d >> 1); // Cyan Point rotatedPoint = ImageMaths.RotatePoint(new Point(offsetX, offsetY), this.cyanAngle, center); int angledX = rotatedPoint.X; int angledY = rotatedPoint.Y; if (rectangle.Contains(new Point(angledX, angledY))) { color = sourceBitmap.GetPixel(angledX, angledY); cmykColor = color; brushWidth = ImageMaths.Clamp(d * (cmykColor.C / 255f) * multiplier, 0, max); graphicsCyan.FillEllipse(cyanBrush, angledX, angledY, brushWidth, brushWidth); } // Magenta rotatedPoint = ImageMaths.RotatePoint(new Point(offsetX, offsetY), this.magentaAngle, center); angledX = rotatedPoint.X; angledY = rotatedPoint.Y; if (rectangle.Contains(new Point(angledX, angledY))) { color = sourceBitmap.GetPixel(angledX, angledY); cmykColor = color; brushWidth = ImageMaths.Clamp(d * (cmykColor.M / 255f) * multiplier, 0, max); graphicsMagenta.FillEllipse(magentaBrush, angledX, angledY, brushWidth, brushWidth); } // Yellow rotatedPoint = ImageMaths.RotatePoint(new Point(offsetX, offsetY), this.yellowAngle, center); angledX = rotatedPoint.X; angledY = rotatedPoint.Y; if (rectangle.Contains(new Point(angledX, angledY))) { color = sourceBitmap.GetPixel(angledX, angledY); cmykColor = color; brushWidth = ImageMaths.Clamp(d * (cmykColor.Y / 255f) * YellowMultiplier, 0, max); graphicsYellow.FillEllipse(yellowBrush, angledX, angledY, brushWidth, brushWidth); } // Keyline rotatedPoint = ImageMaths.RotatePoint(new Point(offsetX, offsetY), this.keylineAngle, center); angledX = rotatedPoint.X; angledY = rotatedPoint.Y; if (rectangle.Contains(new Point(angledX, angledY))) { color = sourceBitmap.GetPixel(angledX, angledY); cmykColor = color; brushWidth = ImageMaths.Clamp(d * (cmykColor.K / 255f) * multiplier, 0, keylineMax); // Just using black is too dark. Brush keylineBrush = new SolidBrush(CmykColor.FromCmykColor(0, 0, 0, cmykColor.K)); graphicsKeyline.FillEllipse(keylineBrush, angledX, angledY, brushWidth, brushWidth); } } } } // Set our white background. using (Graphics graphics = Graphics.FromImage(newImage)) { graphics.Clear(Color.White); } // Blend the colors now to mimic adaptive blending. using (FastBitmap cyanBitmap = new FastBitmap(cyan)) using (FastBitmap magentaBitmap = new FastBitmap(magenta)) using (FastBitmap yellowBitmap = new FastBitmap(yellow)) using (FastBitmap keylineBitmap = new FastBitmap(keyline)) using (FastBitmap destinationBitmap = new FastBitmap(newImage)) { Parallel.For( 0, height, y => { for (int x = 0; x < width; x++) { // ReSharper disable AccessToDisposedClosure Color cyanPixel = cyanBitmap.GetPixel(x, y); Color magentaPixel = magentaBitmap.GetPixel(x, y); Color yellowPixel = yellowBitmap.GetPixel(x, y); Color keylinePixel = keylineBitmap.GetPixel(x, y); CmykColor blended = cyanPixel.AddAsCmykColor(magentaPixel, yellowPixel, keylinePixel); if (rectangle.Contains(new Point(x, y))) { destinationBitmap.SetPixel(x, y, blended); } // ReSharper restore AccessToDisposedClosure } }); } } cyan.Dispose(); magenta.Dispose(); yellow.Dispose(); keyline.Dispose(); source.Dispose(); source = newImage; } catch { if (cyan != null) { cyan.Dispose(); } if (magenta != null) { magenta.Dispose(); } if (yellow != null) { yellow.Dispose(); } if (keyline != null) { keyline.Dispose(); } if (newImage != null) { newImage.Dispose(); } } return(source); }